001/* 002 * Copyright 2008-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2015-2019 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk.unboundidds.extensions; 022 023 024 025import java.text.ParseException; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.Date; 029import java.util.Iterator; 030import java.util.LinkedHashMap; 031import java.util.Map; 032import java.util.NoSuchElementException; 033 034import com.unboundid.asn1.ASN1Element; 035import com.unboundid.asn1.ASN1OctetString; 036import com.unboundid.asn1.ASN1Sequence; 037import com.unboundid.ldap.sdk.Control; 038import com.unboundid.ldap.sdk.ExtendedResult; 039import com.unboundid.ldap.sdk.LDAPException; 040import com.unboundid.ldap.sdk.ResultCode; 041import com.unboundid.util.Debug; 042import com.unboundid.util.NotMutable; 043import com.unboundid.util.StaticUtils; 044import com.unboundid.util.ThreadSafety; 045import com.unboundid.util.ThreadSafetyLevel; 046 047import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*; 048 049 050 051/** 052 * This class implements a data structure for storing the information from an 053 * extended result for the password policy state extended request as used in the 054 * Ping Identity, UnboundID, or Nokia/Alcatel-Lucent 8661 Directory Server. It 055 * is able to decode a generic extended result to obtain the user DN and 056 * operations. See the documentation in the 057 * {@link PasswordPolicyStateExtendedRequest} class for an example that 058 * demonstrates the use of the password policy state extended operation. 059 * <BR> 060 * <BLOCKQUOTE> 061 * <B>NOTE:</B> This class, and other classes within the 062 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 063 * supported for use against Ping Identity, UnboundID, and 064 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 065 * for proprietary functionality or for external specifications that are not 066 * considered stable or mature enough to be guaranteed to work in an 067 * interoperable way with other types of LDAP servers. 068 * </BLOCKQUOTE> 069 * <BR> 070 * This extended result does not have an OID. If the request was processed 071 * successfully, then the result will have a value that has the same encoding as 072 * the request, which was described in the class-level documentation for the 073 * {@link PasswordPolicyStateExtendedRequest} class. 074 */ 075@NotMutable() 076@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 077public final class PasswordPolicyStateExtendedResult 078 extends ExtendedResult 079{ 080 /** 081 * The serial version UID for this serializable class. 082 */ 083 private static final long serialVersionUID = 7140468768443263344L; 084 085 086 087 // A map containing all of the response operations, indexed by operation type. 088 private final Map<Integer,PasswordPolicyStateOperation> operations; 089 090 // The user DN from the response. 091 private final String userDN; 092 093 094 095 /** 096 * Creates a new password policy state extended result from the provided 097 * extended result. 098 * 099 * @param extendedResult The extended result to be decoded as a password 100 * policy state extended result. It must not be 101 * {@code null}. 102 * 103 * @throws LDAPException If the provided extended result cannot be decoded 104 * as a password policy state extended result. 105 */ 106 public PasswordPolicyStateExtendedResult(final ExtendedResult extendedResult) 107 throws LDAPException 108 { 109 super(extendedResult); 110 111 final ASN1OctetString value = extendedResult.getValue(); 112 if (value == null) 113 { 114 userDN = null; 115 operations = Collections.emptyMap(); 116 return; 117 } 118 119 final ASN1Element[] elements; 120 try 121 { 122 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 123 elements = ASN1Sequence.decodeAsSequence(valueElement).elements(); 124 } 125 catch (final Exception e) 126 { 127 Debug.debugException(e); 128 throw new LDAPException(ResultCode.DECODING_ERROR, 129 ERR_PWP_STATE_RESPONSE_VALUE_NOT_SEQUENCE.get(e), 130 e); 131 } 132 133 if ((elements.length < 1) || (elements.length > 2)) 134 { 135 throw new LDAPException(ResultCode.DECODING_ERROR, 136 ERR_PWP_STATE_RESPONSE_INVALID_ELEMENT_COUNT.get( 137 elements.length)); 138 } 139 140 userDN = ASN1OctetString.decodeAsOctetString(elements[0]).stringValue(); 141 142 final LinkedHashMap<Integer,PasswordPolicyStateOperation> ops = 143 new LinkedHashMap<>(StaticUtils.computeMapCapacity(20)); 144 if (elements.length == 2) 145 { 146 try 147 { 148 final ASN1Element[] opElements = 149 ASN1Sequence.decodeAsSequence(elements[1]).elements(); 150 for (final ASN1Element e : opElements) 151 { 152 final PasswordPolicyStateOperation op = 153 PasswordPolicyStateOperation.decode(e); 154 ops.put(op.getOperationType(), op); 155 } 156 } 157 catch (final Exception e) 158 { 159 Debug.debugException(e); 160 throw new LDAPException(ResultCode.DECODING_ERROR, 161 ERR_PWP_STATE_RESPONSE_CANNOT_DECODE_OPS.get(e), 162 e); 163 } 164 } 165 166 operations = Collections.unmodifiableMap(ops); 167 } 168 169 170 171 /** 172 * Creates a new password policy state extended result with the provided 173 * information. 174 * @param messageID The message ID for the LDAP message that is 175 * associated with this LDAP result. 176 * @param resultCode The result code from the response. 177 * @param diagnosticMessage The diagnostic message from the response, if 178 * available. 179 * @param matchedDN The matched DN from the response, if available. 180 * @param referralURLs The set of referral URLs from the response, if 181 * available. 182 * @param userDN The user DN from the response. 183 * @param operations The set of operations from the response, mapped 184 * from operation type to the corresponding 185 * operation data. 186 * @param responseControls The set of controls from the response, if 187 * available. 188 */ 189 public PasswordPolicyStateExtendedResult(final int messageID, 190 final ResultCode resultCode, final String diagnosticMessage, 191 final String matchedDN, final String[] referralURLs, 192 final String userDN, 193 final PasswordPolicyStateOperation[] operations, 194 final Control[] responseControls) 195 { 196 super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs, 197 null, encodeValue(userDN, operations), responseControls); 198 199 this.userDN = userDN; 200 201 if ((operations == null) || (operations.length == 0)) 202 { 203 this.operations = Collections.emptyMap(); 204 } 205 else 206 { 207 final LinkedHashMap<Integer,PasswordPolicyStateOperation> ops = 208 new LinkedHashMap<>(StaticUtils.computeMapCapacity( 209 operations.length)); 210 for (final PasswordPolicyStateOperation o : operations) 211 { 212 ops.put(o.getOperationType(), o); 213 } 214 this.operations = Collections.unmodifiableMap(ops); 215 } 216 } 217 218 219 220 /** 221 * Encodes the provided information into a suitable value for this control. 222 * 223 * @param userDN The user DN from the response. 224 * @param operations The set of operations from the response, mapped 225 * from operation type to the corresponding 226 * operation data. 227 * 228 * @return An ASN.1 octet string containing the appropriately-encoded value 229 * for this control, or {@code null} if there should not be a value. 230 */ 231 private static ASN1OctetString encodeValue(final String userDN, 232 final PasswordPolicyStateOperation[] operations) 233 { 234 if ((userDN == null) && ((operations == null) || (operations.length == 0))) 235 { 236 return null; 237 } 238 239 final ArrayList<ASN1Element> elements = new ArrayList<>(2); 240 elements.add(new ASN1OctetString(userDN)); 241 242 if ((operations != null) && (operations.length > 0)) 243 { 244 final ASN1Element[] opElements = new ASN1Element[operations.length]; 245 for (int i=0; i < operations.length; i++) 246 { 247 opElements[i] = operations[i].encode(); 248 } 249 250 elements.add(new ASN1Sequence(opElements)); 251 } 252 253 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 254 } 255 256 257 258 /** 259 * Retrieves the user DN included in the response. 260 * 261 * @return The user DN included in the response, or {@code null} if the user 262 * DN is not available (e.g., if this is an error response). 263 */ 264 public String getUserDN() 265 { 266 return userDN; 267 } 268 269 270 271 /** 272 * Retrieves the set of password policy operations included in the response. 273 * 274 * @return The set of password policy operations included in the response. 275 */ 276 public Iterable<PasswordPolicyStateOperation> getOperations() 277 { 278 return operations.values(); 279 } 280 281 282 283 /** 284 * Retrieves the specified password policy state operation from the response. 285 * 286 * @param opType The operation type for the password policy state operation 287 * to retrieve. 288 * 289 * @return The requested password policy state operation, or {@code null} if 290 * no such operation was included in the response. 291 */ 292 public PasswordPolicyStateOperation getOperation(final int opType) 293 { 294 return operations.get(opType); 295 } 296 297 298 299 /** 300 * Retrieves the value for the specified password policy state operation as a 301 * string. 302 * 303 * @param opType The operation type for the password policy state operation 304 * to retrieve. 305 * 306 * @return The string value of the requested password policy state operation, 307 * or {@code null} if the specified operation was not included in the 308 * response or did not have any values. 309 */ 310 public String getStringValue(final int opType) 311 { 312 final PasswordPolicyStateOperation op = operations.get(opType); 313 if (op == null) 314 { 315 return null; 316 } 317 318 return op.getStringValue(); 319 } 320 321 322 323 /** 324 * Retrieves the set of string values for the specified password policy state 325 * operation. 326 * 327 * @param opType The operation type for the password policy state operation 328 * to retrieve. 329 * 330 * @return The set of string values for the requested password policy state 331 * operation, or {@code null} if the specified operation was not 332 * included in the response. 333 */ 334 public String[] getStringValues(final int opType) 335 { 336 final PasswordPolicyStateOperation op = operations.get(opType); 337 if (op == null) 338 { 339 return null; 340 } 341 342 return op.getStringValues(); 343 } 344 345 346 347 /** 348 * Retrieves the value of the specified password policy state operation as a 349 * boolean. 350 * 351 * @param opType The operation type for the password policy state operation 352 * to retrieve. 353 * 354 * @return The boolean value of the requested password policy state 355 * operation. 356 * 357 * @throws NoSuchElementException If the specified operation was not 358 * included in the response. 359 * 360 * @throws IllegalStateException If the specified password policy state 361 * operation does not have exactly one value, 362 * or if the value cannot be parsed as a 363 * boolean value. 364 */ 365 public boolean getBooleanValue(final int opType) 366 throws NoSuchElementException, IllegalStateException 367 { 368 final PasswordPolicyStateOperation op = operations.get(opType); 369 if (op == null) 370 { 371 throw new NoSuchElementException( 372 ERR_PWP_STATE_RESPONSE_NO_SUCH_OPERATION.get()); 373 } 374 375 return op.getBooleanValue(); 376 } 377 378 379 380 /** 381 * Retrieves the value of the specified password policy state operation as an 382 * integer. 383 * 384 * @param opType The operation type for the password policy state operation 385 * to retrieve. 386 * 387 * @return The integer value of the requested password policy state 388 * operation. 389 * 390 * @throws NoSuchElementException If the specified operation was not 391 * included in the response. 392 * 393 * @throws IllegalStateException If the value of the specified password 394 * policy state operation cannot be parsed as 395 * an integer value. 396 */ 397 public int getIntValue(final int opType) 398 throws NoSuchElementException, IllegalStateException 399 { 400 final PasswordPolicyStateOperation op = operations.get(opType); 401 if (op == null) 402 { 403 throw new NoSuchElementException( 404 ERR_PWP_STATE_RESPONSE_NO_SUCH_OPERATION.get()); 405 } 406 407 return op.getIntValue(); 408 } 409 410 411 412 /** 413 * Retrieves the value for the specified password policy state operation as a 414 * {@code Date} in generalized time format. 415 * 416 * @param opType The operation type for the password policy state operation 417 * to retrieve. 418 * 419 * @return The value of the requested password policy state operation as a 420 * {@code Date}, or {@code null} if the specified operation was not 421 * included in the response or did not have any values. 422 * 423 * @throws ParseException If the value cannot be parsed as a date in 424 * generalized time format. 425 */ 426 public Date getGeneralizedTimeValue(final int opType) 427 throws ParseException 428 { 429 final PasswordPolicyStateOperation op = operations.get(opType); 430 if (op == null) 431 { 432 return null; 433 } 434 435 return op.getGeneralizedTimeValue(); 436 } 437 438 439 440 /** 441 * Retrieves the set of values for the specified password policy state 442 * operation as {@code Date}s in generalized time format. 443 * 444 * @param opType The operation type for the password policy state operation 445 * to retrieve. 446 * 447 * @return The set of values of the requested password policy state operation 448 * as {@code Date}s. 449 * 450 * @throws ParseException If any of the values cannot be parsed as a date in 451 * generalized time format. 452 */ 453 public Date[] getGeneralizedTimeValues(final int opType) 454 throws ParseException 455 { 456 final PasswordPolicyStateOperation op = operations.get(opType); 457 if (op == null) 458 { 459 return null; 460 } 461 462 return op.getGeneralizedTimeValues(); 463 } 464 465 466 467 /** 468 * {@inheritDoc} 469 */ 470 @Override() 471 public String getExtendedResultName() 472 { 473 return INFO_EXTENDED_RESULT_NAME_PW_POLICY_STATE.get(); 474 } 475 476 477 478 /** 479 * Appends a string representation of this extended result to the provided 480 * buffer. 481 * 482 * @param buffer The buffer to which a string representation of this 483 * extended result will be appended. 484 */ 485 @Override() 486 public void toString(final StringBuilder buffer) 487 { 488 buffer.append("PasswordPolicyStateExtendedResult(resultCode="); 489 buffer.append(getResultCode()); 490 491 final int messageID = getMessageID(); 492 if (messageID >= 0) 493 { 494 buffer.append(", messageID="); 495 buffer.append(messageID); 496 } 497 498 buffer.append(", userDN='"); 499 buffer.append(userDN); 500 buffer.append("', operations={"); 501 502 final Iterator<PasswordPolicyStateOperation> iterator = 503 operations.values().iterator(); 504 while (iterator.hasNext()) 505 { 506 iterator.next().toString(buffer); 507 if (iterator.hasNext()) 508 { 509 buffer.append(", "); 510 } 511 } 512 buffer.append('}'); 513 514 final String diagnosticMessage = getDiagnosticMessage(); 515 if (diagnosticMessage != null) 516 { 517 buffer.append(", diagnosticMessage='"); 518 buffer.append(diagnosticMessage); 519 buffer.append('\''); 520 } 521 522 final String matchedDN = getMatchedDN(); 523 if (matchedDN != null) 524 { 525 buffer.append(", matchedDN='"); 526 buffer.append(matchedDN); 527 buffer.append('\''); 528 } 529 530 final String[] referralURLs = getReferralURLs(); 531 if (referralURLs.length > 0) 532 { 533 buffer.append(", referralURLs={"); 534 for (int i=0; i < referralURLs.length; i++) 535 { 536 if (i > 0) 537 { 538 buffer.append(", "); 539 } 540 541 buffer.append('\''); 542 buffer.append(referralURLs[i]); 543 buffer.append('\''); 544 } 545 buffer.append('}'); 546 } 547 548 final Control[] responseControls = getResponseControls(); 549 if (responseControls.length > 0) 550 { 551 buffer.append(", responseControls={"); 552 for (int i=0; i < responseControls.length; i++) 553 { 554 if (i > 0) 555 { 556 buffer.append(", "); 557 } 558 559 buffer.append(responseControls[i]); 560 } 561 buffer.append('}'); 562 } 563 564 buffer.append(')'); 565 } 566}