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