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