001/*
002 * Copyright 2012-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.util.ArrayList;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.List;
029
030import com.unboundid.asn1.ASN1Element;
031import com.unboundid.asn1.ASN1Enumerated;
032import com.unboundid.asn1.ASN1OctetString;
033import com.unboundid.asn1.ASN1Sequence;
034import com.unboundid.ldap.protocol.AddResponseProtocolOp;
035import com.unboundid.ldap.protocol.DeleteResponseProtocolOp;
036import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp;
037import com.unboundid.ldap.protocol.LDAPMessage;
038import com.unboundid.ldap.protocol.ModifyDNResponseProtocolOp;
039import com.unboundid.ldap.protocol.ModifyResponseProtocolOp;
040import com.unboundid.ldap.sdk.Control;
041import com.unboundid.ldap.sdk.ExtendedResult;
042import com.unboundid.ldap.sdk.LDAPException;
043import com.unboundid.ldap.sdk.LDAPResult;
044import com.unboundid.ldap.sdk.OperationType;
045import com.unboundid.ldap.sdk.ResultCode;
046import com.unboundid.util.Debug;
047import com.unboundid.util.NotMutable;
048import com.unboundid.util.ObjectPair;
049import com.unboundid.util.StaticUtils;
050import com.unboundid.util.ThreadSafety;
051import com.unboundid.util.ThreadSafetyLevel;
052
053import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
054
055
056
057/**
058 * This class provides an implementation of an extended result that can be used
059 * to provide information about the processing for a
060 * {@link MultiUpdateExtendedRequest}.  The OID for this result is
061 * 1.3.6.1.4.1.30221.2.6.18, and the value (if present) should have the
062 * following encoding:
063 * <BR>
064 * <BLOCKQUOTE>
065 *   <B>NOTE:</B>  This class, and other classes within the
066 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
067 *   supported for use against Ping Identity, UnboundID, and
068 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
069 *   for proprietary functionality or for external specifications that are not
070 *   considered stable or mature enough to be guaranteed to work in an
071 *   interoperable way with other types of LDAP servers.
072 * </BLOCKQUOTE>
073 * <BR>
074 * <PRE>
075 *   MultiUpdateResultValue ::= SEQUENCE {
076 *        changesApplied     ENUMERATED {
077 *             none        (0),
078 *             all         (1),
079 *             partial     (2),
080 *             ... },
081 *        responses     SEQUENCE OF SEQUENCE {
082 *             responseOp     CHOICE {
083 *                  modifyResponse     ModifyResponse,
084 *                  addResponse        AddResponse,
085 *                  delResponse        DelResponse,
086 *                  modDNResponse      ModifyDNResponse,
087 *                  extendedResp       ExtendedResponse,
088 *                  ... },
089 *             controls       [0] Controls OPTIONAL,
090 *             ... },
091 *        ... }
092 * </PRE>
093 *
094 * @see MultiUpdateChangesApplied
095 * @see MultiUpdateExtendedRequest
096 */
097@NotMutable()
098@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
099public final class MultiUpdateExtendedResult
100       extends ExtendedResult
101{
102  /**
103   * The OID (1.3.6.1.4.1.30221.2.6.18) for the multi-update extended result.
104   */
105  public static final String MULTI_UPDATE_RESULT_OID =
106       "1.3.6.1.4.1.30221.2.6.18";
107
108
109
110  /**
111   * The serial version UID for this serializable class.
112   */
113  private static final long serialVersionUID = -2529988892013489969L;
114
115
116
117  // The set of results for the operations that were processed.
118  private final List<ObjectPair<OperationType,LDAPResult>> results;
119
120  // The changes applied value for this result.
121  private final MultiUpdateChangesApplied changesApplied;
122
123
124
125  /**
126   * Creates a new multi-update extended result from the provided extended
127   * result.
128   *
129   * @param  extendedResult  The extended result to be decoded as a multi-update
130   *                         result.
131   *
132   * @throws  LDAPException  If a problem is encountered while attempting to
133   *                         decode the provided extended result as a
134   *                         multi-update result.
135   */
136  public MultiUpdateExtendedResult(final ExtendedResult extendedResult)
137         throws LDAPException
138  {
139    super(extendedResult);
140
141    final ASN1OctetString value = extendedResult.getValue();
142    if (value == null)
143    {
144      changesApplied = MultiUpdateChangesApplied.NONE;
145      results        = Collections.emptyList();
146      return;
147    }
148
149    try
150    {
151      final ASN1Element[] outerSequenceElements =
152           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
153
154      final int cav = ASN1Enumerated.decodeAsEnumerated(
155           outerSequenceElements[0]).intValue();
156      changesApplied = MultiUpdateChangesApplied.valueOf(cav);
157      if (changesApplied == null)
158      {
159        throw new LDAPException(ResultCode.DECODING_ERROR,
160             ERR_MULTI_UPDATE_RESULT_INVALID_CHANGES_APPLIED.get(cav));
161      }
162
163      final ASN1Element[] responseSetElements =
164           ASN1Sequence.decodeAsSequence(outerSequenceElements[1]).elements();
165      final ArrayList<ObjectPair<OperationType,LDAPResult>> rl =
166           new ArrayList<>(responseSetElements.length);
167      for (final ASN1Element rse : responseSetElements)
168      {
169        final ASN1Element[] elements =
170             ASN1Sequence.decodeAsSequence(rse).elements();
171        final Control[] controls;
172        if (elements.length == 2)
173        {
174          controls = Control.decodeControls(
175               ASN1Sequence.decodeAsSequence(elements[1]));
176        }
177        else
178        {
179          controls = null;
180        }
181
182        switch (elements[0].getType())
183        {
184          case LDAPMessage.PROTOCOL_OP_TYPE_ADD_RESPONSE:
185            rl.add(new ObjectPair<>(OperationType.ADD,
186                 AddResponseProtocolOp.decodeProtocolOp(elements[0]).
187                      toLDAPResult(controls)));
188            break;
189          case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_RESPONSE:
190            rl.add(new ObjectPair<>(OperationType.DELETE,
191                 DeleteResponseProtocolOp.decodeProtocolOp(elements[0]).
192                      toLDAPResult(controls)));
193            break;
194          case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE:
195            rl.add(new ObjectPair<OperationType,LDAPResult>(
196                 OperationType.EXTENDED,
197                 ExtendedResponseProtocolOp.decodeProtocolOp(elements[0]).
198                      toExtendedResult(controls)));
199            break;
200          case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_RESPONSE:
201            rl.add(new ObjectPair<>(OperationType.MODIFY,
202                 ModifyResponseProtocolOp.decodeProtocolOp(elements[0]).
203                      toLDAPResult(controls)));
204            break;
205          case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_RESPONSE:
206            rl.add(new ObjectPair<>(OperationType.MODIFY_DN,
207                 ModifyDNResponseProtocolOp.decodeProtocolOp(elements[0]).
208                      toLDAPResult(controls)));
209            break;
210          default:
211            throw new LDAPException(ResultCode.DECODING_ERROR,
212                 ERR_MULTI_UPDATE_RESULT_DECODE_INVALID_OP_TYPE.get(
213                      StaticUtils.toHex(elements[0].getType())));
214        }
215      }
216
217      results = Collections.unmodifiableList(rl);
218    }
219    catch (final LDAPException le)
220    {
221      Debug.debugException(le);
222      throw le;
223    }
224    catch (final Exception e)
225    {
226      Debug.debugException(e);
227      throw new LDAPException(ResultCode.DECODING_ERROR,
228           ERR_MULTI_UPDATE_RESULT_CANNOT_DECODE_VALUE.get(
229                StaticUtils.getExceptionMessage(e)),
230           e);
231    }
232  }
233
234
235
236  /**
237   * Creates a new multi-update extended request with the provided information.
238   *
239   * @param  messageID          The message ID for this extended result.
240   * @param  resultCode         The result code for this result.  It must not be
241   *                            {@code null}.
242   * @param  diagnosticMessage  The diagnostic message to include in the result.
243   *                            It may be {@code null} if no diagnostic message
244   *                            should be included.
245   * @param  matchedDN          The matched DN to include in the result.  It may
246   *                            be {@code null} if no matched DN should be
247   *                            included.
248   * @param  referralURLs       The set of referral URLs to include in the
249   *                            result.  It may be {@code null} or empty if no
250   *                            referral URLs should be included.
251   * @param  changesApplied     The value which indicates whether any or all of
252   *                            the changes from the request were successfully
253   *                            applied.
254   * @param  results            The set of operation results to be included in
255   *                            the extended result value.  It may be
256   *                            {@code null} or empty if no operation results
257   *                            should be included.
258   * @param  controls           The set of controls to include in the
259   *                            multi-update result.  It may be {@code null} or
260   *                            empty if no controls should be included.
261   *
262   * @throws  LDAPException  If any of the results are for an inappropriate
263   *                         operation type.
264   */
265  public MultiUpdateExtendedResult(final int messageID,
266              final ResultCode resultCode, final String diagnosticMessage,
267              final String matchedDN, final String[] referralURLs,
268              final MultiUpdateChangesApplied changesApplied,
269              final List<ObjectPair<OperationType,LDAPResult>> results,
270              final Control... controls)
271         throws LDAPException
272  {
273    super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs,
274         MULTI_UPDATE_RESULT_OID, encodeValue(changesApplied, results),
275         controls);
276
277    this.changesApplied = changesApplied;
278
279    if (results == null)
280    {
281      this.results = Collections.emptyList();
282    }
283    else
284    {
285      this.results = Collections.unmodifiableList(results);
286    }
287  }
288
289
290
291  /**
292   * Encodes the information from the provided set of results into a form
293   * suitable for use as the value of a multi-update extended result.
294   *
295   * @param  changesApplied  The value which indicates whether any or all of the
296   *                         changes from the request were successfully applied.
297   * @param  results         The set of operation results to be included in the
298   *                         extended result value.  It may be {@code null} or
299   *                         empty if no operation results should be included.
300   *
301   * @return  An ASN.1 element suitable for use as the value of a multi-update
302   *          extended result.
303   *
304   * @throws  LDAPException  If any of the results are for an inappropriate
305   *                         operation type.
306   */
307  private static ASN1OctetString encodeValue(
308                      final MultiUpdateChangesApplied changesApplied,
309                      final List<ObjectPair<OperationType,LDAPResult>> results)
310          throws LDAPException
311  {
312    if ((results == null) || results.isEmpty())
313    {
314      return null;
315    }
316
317    final ArrayList<ASN1Element> opElements = new ArrayList<>(results.size());
318    for (final ObjectPair<OperationType,LDAPResult> p : results)
319    {
320      final OperationType t = p.getFirst();
321      final LDAPResult    r = p.getSecond();
322
323      final ASN1Element protocolOpElement;
324      switch (t)
325      {
326        case ADD:
327          protocolOpElement = new AddResponseProtocolOp(r).encodeProtocolOp();
328          break;
329        case DELETE:
330          protocolOpElement =
331               new DeleteResponseProtocolOp(r).encodeProtocolOp();
332          break;
333        case EXTENDED:
334          protocolOpElement =
335               new ExtendedResponseProtocolOp(r).encodeProtocolOp();
336          break;
337        case MODIFY:
338          protocolOpElement =
339               new ModifyResponseProtocolOp(r).encodeProtocolOp();
340          break;
341        case MODIFY_DN:
342          protocolOpElement =
343               new ModifyDNResponseProtocolOp(r).encodeProtocolOp();
344          break;
345        default:
346          throw new LDAPException(ResultCode.PARAM_ERROR,
347               ERR_MULTI_UPDATE_RESULT_INVALID_OP_TYPE.get(t.name()));
348      }
349
350      final Control[] controls = r.getResponseControls();
351      if ((controls == null) || (controls.length == 0))
352      {
353        opElements.add(new ASN1Sequence(protocolOpElement));
354      }
355      else
356      {
357        opElements.add(new ASN1Sequence(
358             protocolOpElement,
359             Control.encodeControls(controls)));
360
361      }
362    }
363
364    final ASN1Sequence valueSequence = new ASN1Sequence(
365         new ASN1Enumerated(changesApplied.intValue()),
366         new ASN1Sequence(opElements));
367    return new ASN1OctetString(valueSequence.encode());
368  }
369
370
371
372  /**
373   * Retrieves the value that indicates whether any or all changes from the
374   * multi-update request were successfully applied.
375   *
376   * @return  The value that indicates whether any or all changes from the
377   *          multi-update request were successfully applied.
378   */
379  public MultiUpdateChangesApplied getChangesApplied()
380  {
381    return changesApplied;
382  }
383
384
385
386  /**
387   * Retrieves a list of the results for operations processed as part of the
388   * multi-update operation, with each result paired with its corresponding
389   * operation type.
390   *
391   * @return  A list of the results for operations processed as part of the
392   *          multi-update operation.  The returned list may be empty if no
393   *          operation results were available.
394   */
395  public List<ObjectPair<OperationType,LDAPResult>> getResults()
396  {
397    return results;
398  }
399
400
401
402  /**
403   * {@inheritDoc}
404   */
405  @Override()
406  public String getExtendedResultName()
407  {
408    return INFO_EXTENDED_RESULT_NAME_MULTI_UPDATE.get();
409  }
410
411
412
413  /**
414   * Appends a string representation of this extended result to the provided
415   * buffer.
416   *
417   * @param  buffer  The buffer to which a string representation of this
418   *                 extended result will be appended.
419   */
420  @Override()
421  public void toString(final StringBuilder buffer)
422  {
423    buffer.append("MultiUpdateExtendedResult(resultCode=");
424    buffer.append(getResultCode());
425
426    final int messageID = getMessageID();
427    if (messageID >= 0)
428    {
429      buffer.append(", messageID=");
430      buffer.append(messageID);
431    }
432
433    buffer.append(", changesApplied=");
434    buffer.append(changesApplied.name());
435    buffer.append(", results={");
436
437    final Iterator<ObjectPair<OperationType,LDAPResult>> resultIterator =
438         results.iterator();
439    while (resultIterator.hasNext())
440    {
441      resultIterator.next().getSecond().toString(buffer);
442      if (resultIterator.hasNext())
443      {
444        buffer.append(", ");
445      }
446    }
447
448    final String diagnosticMessage = getDiagnosticMessage();
449    if (diagnosticMessage != null)
450    {
451      buffer.append(", diagnosticMessage='");
452      buffer.append(diagnosticMessage);
453      buffer.append('\'');
454    }
455
456    final String matchedDN = getMatchedDN();
457    if (matchedDN != null)
458    {
459      buffer.append(", matchedDN='");
460      buffer.append(matchedDN);
461      buffer.append('\'');
462    }
463
464    final String[] referralURLs = getReferralURLs();
465    if (referralURLs.length > 0)
466    {
467      buffer.append(", referralURLs={");
468      for (int i=0; i < referralURLs.length; i++)
469      {
470        if (i > 0)
471        {
472          buffer.append(", ");
473        }
474
475        buffer.append('\'');
476        buffer.append(referralURLs[i]);
477        buffer.append('\'');
478      }
479      buffer.append('}');
480    }
481
482    final Control[] responseControls = getResponseControls();
483    if (responseControls.length > 0)
484    {
485      buffer.append(", responseControls={");
486      for (int i=0; i < responseControls.length; i++)
487      {
488        if (i > 0)
489        {
490          buffer.append(", ");
491        }
492
493        buffer.append(responseControls[i]);
494      }
495      buffer.append('}');
496    }
497
498    buffer.append(')');
499  }
500}