001/*
002 * Copyright 2012-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.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 Alcatel-Lucent 8661
068 *   server products.  These classes provide support for proprietary
069 *   functionality or for external specifications that are not considered stable
070 *   or mature enough to be guaranteed to work in an interoperable way with
071 *   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<ObjectPair<OperationType,LDAPResult>>(
167                responseSetElements.length);
168      for (final ASN1Element rse : responseSetElements)
169      {
170        final ASN1Element[] elements =
171             ASN1Sequence.decodeAsSequence(rse).elements();
172        final Control[] controls;
173        if (elements.length == 2)
174        {
175          controls = Control.decodeControls(
176               ASN1Sequence.decodeAsSequence(elements[1]));
177        }
178        else
179        {
180          controls = null;
181        }
182
183        switch (elements[0].getType())
184        {
185          case LDAPMessage.PROTOCOL_OP_TYPE_ADD_RESPONSE:
186            rl.add(new ObjectPair<OperationType,LDAPResult>(OperationType.ADD,
187                 AddResponseProtocolOp.decodeProtocolOp(elements[0]).
188                      toLDAPResult(controls)));
189            break;
190          case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_RESPONSE:
191            rl.add(new ObjectPair<OperationType,LDAPResult>(
192                 OperationType.DELETE,
193                 DeleteResponseProtocolOp.decodeProtocolOp(elements[0]).
194                      toLDAPResult(controls)));
195            break;
196          case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE:
197            rl.add(new ObjectPair<OperationType,LDAPResult>(
198                 OperationType.EXTENDED,
199                 ExtendedResponseProtocolOp.decodeProtocolOp(elements[0]).
200                      toExtendedResult(controls)));
201            break;
202          case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_RESPONSE:
203            rl.add(new ObjectPair<OperationType,LDAPResult>(
204                 OperationType.MODIFY,
205                 ModifyResponseProtocolOp.decodeProtocolOp(elements[0]).
206                      toLDAPResult(controls)));
207            break;
208          case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_RESPONSE:
209            rl.add(new ObjectPair<OperationType,LDAPResult>(
210                 OperationType.MODIFY_DN,
211                 ModifyDNResponseProtocolOp.decodeProtocolOp(elements[0]).
212                      toLDAPResult(controls)));
213            break;
214          default:
215            throw new LDAPException(ResultCode.DECODING_ERROR,
216                 ERR_MULTI_UPDATE_RESULT_DECODE_INVALID_OP_TYPE.get(
217                      StaticUtils.toHex(elements[0].getType())));
218        }
219      }
220
221      results = Collections.unmodifiableList(rl);
222    }
223    catch (final LDAPException le)
224    {
225      Debug.debugException(le);
226      throw le;
227    }
228    catch (final Exception e)
229    {
230      Debug.debugException(e);
231      throw new LDAPException(ResultCode.DECODING_ERROR,
232           ERR_MULTI_UPDATE_RESULT_CANNOT_DECODE_VALUE.get(
233                StaticUtils.getExceptionMessage(e)),
234           e);
235    }
236  }
237
238
239
240  /**
241   * Creates a new multi-update extended request with the provided information.
242   *
243   * @param  messageID          The message ID for this extended result.
244   * @param  resultCode         The result code for this result.  It must not be
245   *                            {@code null}.
246   * @param  diagnosticMessage  The diagnostic message to include in the result.
247   *                            It may be {@code null} if no diagnostic message
248   *                            should be included.
249   * @param  matchedDN          The matched DN to include in the result.  It may
250   *                            be {@code null} if no matched DN should be
251   *                            included.
252   * @param  referralURLs       The set of referral URLs to include in the
253   *                            result.  It may be {@code null} or empty if no
254   *                            referral URLs should be included.
255   * @param  changesApplied     The value which indicates whether any or all of
256   *                            the changes from the request were successfully
257   *                            applied.
258   * @param  results            The set of operation results to be included in
259   *                            the extended result value.  It may be
260   *                            {@code null} or empty if no operation results
261   *                            should be included.
262   * @param  controls           The set of controls to include in the
263   *                            multi-update result.  It may be {@code null} or
264   *                            empty if no controls should be included.
265   *
266   * @throws  LDAPException  If any of the results are for an inappropriate
267   *                         operation type.
268   */
269  public MultiUpdateExtendedResult(final int messageID,
270              final ResultCode resultCode, final String diagnosticMessage,
271              final String matchedDN, final String[] referralURLs,
272              final MultiUpdateChangesApplied changesApplied,
273              final List<ObjectPair<OperationType,LDAPResult>> results,
274              final Control... controls)
275         throws LDAPException
276  {
277    super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs,
278         MULTI_UPDATE_RESULT_OID, encodeValue(changesApplied, results),
279         controls);
280
281    this.changesApplied = changesApplied;
282
283    if (results == null)
284    {
285      this.results = Collections.emptyList();
286    }
287    else
288    {
289      this.results = Collections.unmodifiableList(results);
290    }
291  }
292
293
294
295  /**
296   * Encodes the information from the provided set of results into a form
297   * suitable for use as the value of a multi-update extended result.
298   *
299   * @param  changesApplied  The value which indicates whether any or all of the
300   *                         changes from the request were successfully applied.
301   * @param  results         The set of operation results to be included in the
302   *                         extended result value.  It may be {@code null} or
303   *                         empty if no operation results should be included.
304   *
305   * @return  An ASN.1 element suitable for use as the value of a multi-update
306   *          extended result.
307   *
308   * @throws  LDAPException  If any of the results are for an inappropriate
309   *                         operation type.
310   */
311  private static ASN1OctetString encodeValue(
312                      final MultiUpdateChangesApplied changesApplied,
313                      final List<ObjectPair<OperationType,LDAPResult>> results)
314          throws LDAPException
315  {
316    if ((results == null) || results.isEmpty())
317    {
318      return null;
319    }
320
321    final ArrayList<ASN1Element> opElements =
322         new ArrayList<ASN1Element>(results.size());
323    for (final ObjectPair<OperationType,LDAPResult> p : results)
324    {
325      final OperationType t = p.getFirst();
326      final LDAPResult    r = p.getSecond();
327
328      final ASN1Element protocolOpElement;
329      switch (t)
330      {
331        case ADD:
332          protocolOpElement = new AddResponseProtocolOp(r).encodeProtocolOp();
333          break;
334        case DELETE:
335          protocolOpElement =
336               new DeleteResponseProtocolOp(r).encodeProtocolOp();
337          break;
338        case EXTENDED:
339          protocolOpElement =
340               new ExtendedResponseProtocolOp(r).encodeProtocolOp();
341          break;
342        case MODIFY:
343          protocolOpElement =
344               new ModifyResponseProtocolOp(r).encodeProtocolOp();
345          break;
346        case MODIFY_DN:
347          protocolOpElement =
348               new ModifyDNResponseProtocolOp(r).encodeProtocolOp();
349          break;
350        default:
351          throw new LDAPException(ResultCode.PARAM_ERROR,
352               ERR_MULTI_UPDATE_RESULT_INVALID_OP_TYPE.get(t.name()));
353      }
354
355      final Control[] controls = r.getResponseControls();
356      if ((controls == null) || (controls.length == 0))
357      {
358        opElements.add(new ASN1Sequence(protocolOpElement));
359      }
360      else
361      {
362        opElements.add(new ASN1Sequence(
363             protocolOpElement,
364             Control.encodeControls(controls)));
365
366      }
367    }
368
369    final ASN1Sequence valueSequence = new ASN1Sequence(
370         new ASN1Enumerated(changesApplied.intValue()),
371         new ASN1Sequence(opElements));
372    return new ASN1OctetString(valueSequence.encode());
373  }
374
375
376
377  /**
378   * Retrieves the value that indicates whether any or all changes from the
379   * multi-update request were successfully applied.
380   *
381   * @return  The value that indicates whether any or all changes from the
382   *          multi-update request were successfully applied.
383   */
384  public MultiUpdateChangesApplied getChangesApplied()
385  {
386    return changesApplied;
387  }
388
389
390
391  /**
392   * Retrieves a list of the results for operations processed as part of the
393   * multi-update operation, with each result paired with its corresponding
394   * operation type.
395   *
396   * @return  A list of the results for operations processed as part of the
397   *          multi-update operation.  The returned list may be empty if no
398   *          operation results were available.
399   */
400  public List<ObjectPair<OperationType,LDAPResult>> getResults()
401  {
402    return results;
403  }
404
405
406
407  /**
408   * {@inheritDoc}
409   */
410  @Override()
411  public String getExtendedResultName()
412  {
413    return INFO_EXTENDED_RESULT_NAME_MULTI_UPDATE.get();
414  }
415
416
417
418  /**
419   * Appends a string representation of this extended result to the provided
420   * buffer.
421   *
422   * @param  buffer  The buffer to which a string representation of this
423   *                 extended result will be appended.
424   */
425  @Override()
426  public void toString(final StringBuilder buffer)
427  {
428    buffer.append("MultiUpdateExtendedResult(resultCode=");
429    buffer.append(getResultCode());
430
431    final int messageID = getMessageID();
432    if (messageID >= 0)
433    {
434      buffer.append(", messageID=");
435      buffer.append(messageID);
436    }
437
438    buffer.append(", changesApplied=");
439    buffer.append(changesApplied.name());
440    buffer.append(", results={");
441
442    final Iterator<ObjectPair<OperationType,LDAPResult>> resultIterator =
443         results.iterator();
444    while (resultIterator.hasNext())
445    {
446      resultIterator.next().getSecond().toString(buffer);
447      if (resultIterator.hasNext())
448      {
449        buffer.append(", ");
450      }
451    }
452
453    final String diagnosticMessage = getDiagnosticMessage();
454    if (diagnosticMessage != null)
455    {
456      buffer.append(", diagnosticMessage='");
457      buffer.append(diagnosticMessage);
458      buffer.append('\'');
459    }
460
461    final String matchedDN = getMatchedDN();
462    if (matchedDN != null)
463    {
464      buffer.append(", matchedDN='");
465      buffer.append(matchedDN);
466      buffer.append('\'');
467    }
468
469    final String[] referralURLs = getReferralURLs();
470    if (referralURLs.length > 0)
471    {
472      buffer.append(", referralURLs={");
473      for (int i=0; i < referralURLs.length; i++)
474      {
475        if (i > 0)
476        {
477          buffer.append(", ");
478        }
479
480        buffer.append('\'');
481        buffer.append(referralURLs[i]);
482        buffer.append('\'');
483      }
484      buffer.append('}');
485    }
486
487    final Control[] responseControls = getResponseControls();
488    if (responseControls.length > 0)
489    {
490      buffer.append(", responseControls={");
491      for (int i=0; i < responseControls.length; i++)
492      {
493        if (i > 0)
494        {
495          buffer.append(", ");
496        }
497
498        buffer.append(responseControls[i]);
499      }
500      buffer.append('}');
501    }
502
503    buffer.append(')');
504  }
505}