001    /*
002     * Copyright 2012-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2015 UnboundID Corp.
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     */
021    package com.unboundid.ldap.sdk.unboundidds.extensions;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Collections;
027    import java.util.Iterator;
028    import java.util.List;
029    
030    import com.unboundid.asn1.ASN1Element;
031    import com.unboundid.asn1.ASN1Enumerated;
032    import com.unboundid.asn1.ASN1OctetString;
033    import com.unboundid.asn1.ASN1Sequence;
034    import com.unboundid.ldap.protocol.AddResponseProtocolOp;
035    import com.unboundid.ldap.protocol.DeleteResponseProtocolOp;
036    import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp;
037    import com.unboundid.ldap.protocol.LDAPMessage;
038    import com.unboundid.ldap.protocol.ModifyDNResponseProtocolOp;
039    import com.unboundid.ldap.protocol.ModifyResponseProtocolOp;
040    import com.unboundid.ldap.sdk.Control;
041    import com.unboundid.ldap.sdk.ExtendedResult;
042    import com.unboundid.ldap.sdk.LDAPException;
043    import com.unboundid.ldap.sdk.LDAPResult;
044    import com.unboundid.ldap.sdk.OperationType;
045    import com.unboundid.ldap.sdk.ResultCode;
046    import com.unboundid.util.Debug;
047    import com.unboundid.util.NotMutable;
048    import com.unboundid.util.ObjectPair;
049    import com.unboundid.util.StaticUtils;
050    import com.unboundid.util.ThreadSafety;
051    import com.unboundid.util.ThreadSafetyLevel;
052    
053    import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
054    
055    
056    
057    /**
058     * <BLOCKQUOTE>
059     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
060     *   LDAP SDK for Java.  It is not available for use in applications that
061     *   include only the Standard Edition of the LDAP SDK, and is not supported for
062     *   use in conjunction with non-UnboundID products.
063     * </BLOCKQUOTE>
064     * This class provides an implementation of an extended result that can be used
065     * to provide information about the processing for a
066     * {@link MultiUpdateExtendedRequest}.  The OID for this result is
067     * 1.3.6.1.4.1.30221.2.6.18, and the value (if present) should have the
068     * following encoding:
069     * <BR><BR>
070     * <PRE>
071     *   MultiUpdateResultValue ::= SEQUENCE {
072     *        changesApplied     ENUMERATED {
073     *             none        (0),
074     *             all         (1),
075     *             partial     (2),
076     *             ... },
077     *        responses     SEQUENCE OF SEQUENCE {
078     *             responseOp     CHOICE {
079     *                  modifyResponse     ModifyResponse,
080     *                  addResponse        AddResponse,
081     *                  delResponse        DelResponse,
082     *                  modDNResponse      ModifyDNResponse,
083     *                  extendedResp       ExtendedResponse,
084     *                  ... },
085     *             controls       [0] Controls OPTIONAL,
086     *             ... },
087     *        }
088     * </PRE>
089     *
090     * @see MultiUpdateChangesApplied
091     * @see MultiUpdateExtendedRequest
092     */
093    @NotMutable()
094    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
095    public final class MultiUpdateExtendedResult
096           extends ExtendedResult
097    {
098      /**
099       * The OID (1.3.6.1.4.1.30221.2.6.18) for the multi-update extended result.
100       */
101      public static final String MULTI_UPDATE_RESULT_OID =
102           "1.3.6.1.4.1.30221.2.6.18";
103    
104    
105    
106      /**
107       * The serial version UID for this serializable class.
108       */
109      private static final long serialVersionUID = -2529988892013489969L;
110    
111    
112    
113      // The set of results for the operations that were processed.
114      private final List<ObjectPair<OperationType,LDAPResult>> results;
115    
116      // The changes applied value for this result.
117      private final MultiUpdateChangesApplied changesApplied;
118    
119    
120    
121      /**
122       * Creates a new multi-update extended result from the provided extended
123       * result.
124       *
125       * @param  extendedResult  The extended result to be decoded as a multi-update
126       *                         result.
127       *
128       * @throws  LDAPException  If a problem is encountered while attempting to
129       *                         decode the provided extended result as a
130       *                         multi-update result.
131       */
132      public MultiUpdateExtendedResult(final ExtendedResult extendedResult)
133             throws LDAPException
134      {
135        super(extendedResult);
136    
137        final ASN1OctetString value = extendedResult.getValue();
138        if (value == null)
139        {
140          changesApplied = MultiUpdateChangesApplied.NONE;
141          results        = Collections.emptyList();
142          return;
143        }
144    
145        try
146        {
147          final ASN1Element[] outerSequenceElements =
148               ASN1Sequence.decodeAsSequence(value.getValue()).elements();
149    
150          final int cav = ASN1Enumerated.decodeAsEnumerated(
151               outerSequenceElements[0]).intValue();
152          changesApplied = MultiUpdateChangesApplied.valueOf(cav);
153          if (changesApplied == null)
154          {
155            throw new LDAPException(ResultCode.DECODING_ERROR,
156                 ERR_MULTI_UPDATE_RESULT_INVALID_CHANGES_APPLIED.get(cav));
157          }
158    
159          final ASN1Element[] responseSetElements =
160               ASN1Sequence.decodeAsSequence(outerSequenceElements[1]).elements();
161          final ArrayList<ObjectPair<OperationType,LDAPResult>> rl =
162               new ArrayList<ObjectPair<OperationType,LDAPResult>>(
163                    responseSetElements.length);
164          for (final ASN1Element rse : responseSetElements)
165          {
166            final ASN1Element[] elements =
167                 ASN1Sequence.decodeAsSequence(rse).elements();
168            final Control[] controls;
169            if (elements.length == 2)
170            {
171              controls = Control.decodeControls(
172                   ASN1Sequence.decodeAsSequence(elements[1]));
173            }
174            else
175            {
176              controls = null;
177            }
178    
179            switch (elements[0].getType())
180            {
181              case LDAPMessage.PROTOCOL_OP_TYPE_ADD_RESPONSE:
182                rl.add(new ObjectPair<OperationType,LDAPResult>(OperationType.ADD,
183                     AddResponseProtocolOp.decodeProtocolOp(elements[0]).
184                          toLDAPResult(controls)));
185                break;
186              case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_RESPONSE:
187                rl.add(new ObjectPair<OperationType,LDAPResult>(
188                     OperationType.DELETE,
189                     DeleteResponseProtocolOp.decodeProtocolOp(elements[0]).
190                          toLDAPResult(controls)));
191                break;
192              case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE:
193                rl.add(new ObjectPair<OperationType,LDAPResult>(
194                     OperationType.EXTENDED,
195                     ExtendedResponseProtocolOp.decodeProtocolOp(elements[0]).
196                          toExtendedResult(controls)));
197                break;
198              case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_RESPONSE:
199                rl.add(new ObjectPair<OperationType,LDAPResult>(
200                     OperationType.MODIFY,
201                     ModifyResponseProtocolOp.decodeProtocolOp(elements[0]).
202                          toLDAPResult(controls)));
203                break;
204              case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_RESPONSE:
205                rl.add(new ObjectPair<OperationType,LDAPResult>(
206                     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 =
318             new ArrayList<ASN1Element>(results.size());
319        for (final ObjectPair<OperationType,LDAPResult> p : results)
320        {
321          final OperationType t = p.getFirst();
322          final LDAPResult    r = p.getSecond();
323    
324          final ASN1Element protocolOpElement;
325          switch (t)
326          {
327            case ADD:
328              protocolOpElement = new AddResponseProtocolOp(r).encodeProtocolOp();
329              break;
330            case DELETE:
331              protocolOpElement =
332                   new DeleteResponseProtocolOp(r).encodeProtocolOp();
333              break;
334            case EXTENDED:
335              protocolOpElement =
336                   new ExtendedResponseProtocolOp(r).encodeProtocolOp();
337              break;
338            case MODIFY:
339              protocolOpElement =
340                   new ModifyResponseProtocolOp(r).encodeProtocolOp();
341              break;
342            case MODIFY_DN:
343              protocolOpElement =
344                   new ModifyDNResponseProtocolOp(r).encodeProtocolOp();
345              break;
346            default:
347              throw new LDAPException(ResultCode.PARAM_ERROR,
348                   ERR_MULTI_UPDATE_RESULT_INVALID_OP_TYPE.get(t.name()));
349          }
350    
351          final Control[] controls = r.getResponseControls();
352          if ((controls == null) || (controls.length == 0))
353          {
354            opElements.add(new ASN1Sequence(protocolOpElement));
355          }
356          else
357          {
358            opElements.add(new ASN1Sequence(
359                 protocolOpElement,
360                 Control.encodeControls(controls)));
361    
362          }
363        }
364    
365        final ASN1Sequence valueSequence = new ASN1Sequence(
366             new ASN1Enumerated(changesApplied.intValue()),
367             new ASN1Sequence(opElements));
368        return new ASN1OctetString(valueSequence.encode());
369      }
370    
371    
372    
373      /**
374       * Retrieves the value that indicates whether any or all changes from the
375       * multi-update request were successfully applied.
376       *
377       * @return  The value that indicates whether any or all changes from the
378       *          multi-update request were successfully applied.
379       */
380      public MultiUpdateChangesApplied getChangesApplied()
381      {
382        return changesApplied;
383      }
384    
385    
386    
387      /**
388       * Retrieves a list of the results for operations processed as part of the
389       * multi-update operation, with each result paired with its corresponding
390       * operation type.
391       *
392       * @return  A list of the results for operations processed as part of the
393       *          multi-update operation.  The returned list may be empty if no
394       *          operation results were available.
395       */
396      public List<ObjectPair<OperationType,LDAPResult>> getResults()
397      {
398        return results;
399      }
400    
401    
402    
403      /**
404       * {@inheritDoc}
405       */
406      @Override()
407      public String getExtendedResultName()
408      {
409        return INFO_EXTENDED_RESULT_NAME_MULTI_UPDATE.get();
410      }
411    
412    
413    
414      /**
415       * Appends a string representation of this extended result to the provided
416       * buffer.
417       *
418       * @param  buffer  The buffer to which a string representation of this
419       *                 extended result will be appended.
420       */
421      @Override()
422      public void toString(final StringBuilder buffer)
423      {
424        buffer.append("MultiUpdateExtendedResult(resultCode=");
425        buffer.append(getResultCode());
426    
427        final int messageID = getMessageID();
428        if (messageID >= 0)
429        {
430          buffer.append(", messageID=");
431          buffer.append(messageID);
432        }
433    
434        buffer.append(", changesApplied=");
435        buffer.append(changesApplied.name());
436        buffer.append(", results={");
437    
438        final Iterator<ObjectPair<OperationType,LDAPResult>> resultIterator =
439             results.iterator();
440        while (resultIterator.hasNext())
441        {
442          resultIterator.next().getSecond().toString(buffer);
443          if (resultIterator.hasNext())
444          {
445            buffer.append(", ");
446          }
447        }
448    
449        final String diagnosticMessage = getDiagnosticMessage();
450        if (diagnosticMessage != null)
451        {
452          buffer.append(", diagnosticMessage='");
453          buffer.append(diagnosticMessage);
454          buffer.append('\'');
455        }
456    
457        final String matchedDN = getMatchedDN();
458        if (matchedDN != null)
459        {
460          buffer.append(", matchedDN='");
461          buffer.append(matchedDN);
462          buffer.append('\'');
463        }
464    
465        final String[] referralURLs = getReferralURLs();
466        if (referralURLs.length > 0)
467        {
468          buffer.append(", referralURLs={");
469          for (int i=0; i < referralURLs.length; i++)
470          {
471            if (i > 0)
472            {
473              buffer.append(", ");
474            }
475    
476            buffer.append('\'');
477            buffer.append(referralURLs[i]);
478            buffer.append('\'');
479          }
480          buffer.append('}');
481        }
482    
483        final Control[] responseControls = getResponseControls();
484        if (responseControls.length > 0)
485        {
486          buffer.append(", responseControls={");
487          for (int i=0; i < responseControls.length; i++)
488          {
489            if (i > 0)
490            {
491              buffer.append(", ");
492            }
493    
494            buffer.append(responseControls[i]);
495          }
496          buffer.append('}');
497        }
498    
499        buffer.append(')');
500      }
501    }