001    /*
002     * Copyright 2008-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.Arrays;
027    import java.util.Collections;
028    import java.util.Iterator;
029    import java.util.List;
030    
031    import com.unboundid.asn1.ASN1Element;
032    import com.unboundid.asn1.ASN1Enumerated;
033    import com.unboundid.asn1.ASN1OctetString;
034    import com.unboundid.asn1.ASN1Sequence;
035    import com.unboundid.ldap.protocol.AddRequestProtocolOp;
036    import com.unboundid.ldap.protocol.DeleteRequestProtocolOp;
037    import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp;
038    import com.unboundid.ldap.protocol.LDAPMessage;
039    import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp;
040    import com.unboundid.ldap.protocol.ModifyRequestProtocolOp;
041    import com.unboundid.ldap.sdk.AddRequest;
042    import com.unboundid.ldap.sdk.Control;
043    import com.unboundid.ldap.sdk.DeleteRequest;
044    import com.unboundid.ldap.sdk.ExtendedRequest;
045    import com.unboundid.ldap.sdk.ExtendedResult;
046    import com.unboundid.ldap.sdk.LDAPConnection;
047    import com.unboundid.ldap.sdk.LDAPException;
048    import com.unboundid.ldap.sdk.LDAPRequest;
049    import com.unboundid.ldap.sdk.ModifyRequest;
050    import com.unboundid.ldap.sdk.ModifyDNRequest;
051    import com.unboundid.ldap.sdk.ResultCode;
052    import com.unboundid.util.Debug;
053    import com.unboundid.util.NotMutable;
054    import com.unboundid.util.StaticUtils;
055    import com.unboundid.util.ThreadSafety;
056    import com.unboundid.util.ThreadSafetyLevel;
057    
058    import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
059    
060    
061    
062    /**
063     * <BLOCKQUOTE>
064     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
065     *   LDAP SDK for Java.  It is not available for use in applications that
066     *   include only the Standard Edition of the LDAP SDK, and is not supported for
067     *   use in conjunction with non-UnboundID products.
068     * </BLOCKQUOTE>
069     * This class provides an implementation of an extended request that can be used
070     * to send multiple update requests to the server in a single packet, optionally
071     * processing them as a single atomic unit.  The OID for this request is
072     * 1.3.6.1.4.1.30221.2.6.17, and the value must have the following encoding:
073     * <BR><BR>
074     * <PRE>
075     *   MultiUpdateRequestValue ::= SEQUENCE {
076     *        errorBehavior     ENUMERATED {
077     *             atomic              (0),
078     *             quitOnError         (1),
079     *             continueOnError     (2),
080     *             ... },
081     *        requests          SEQUENCE OF SEQUENCE {
082     *             updateOp     CHOICE {
083     *                  modifyRequest     ModifyRequest,
084     *                  addRequest        AddRequest,
085     *                  delRequest        DelRequest,
086     *                  modDNRequest      ModifyDNRequest,
087     *                  extendedReq       ExtendedRequest,
088     *                  ... },
089     *             controls     [0] Controls OPTIONAL,
090     *             ... },
091     *        ... }
092     * </PRE>
093     * <BR><BR>
094     * <H2>Example</H2>
095     * The following example demonstrates the use of the multi-update extended
096     * request to create a new user entry and modify an existing group entry to add
097     * the new user as a member:
098     * <PRE>
099     * MultiUpdateExtendedRequest multiUpdateRequest =
100     *      new MultiUpdateExtendedRequest(
101     *           MultiUpdateErrorBehavior.ABORT_ON_ERROR,
102     *           new AddRequest(
103     *                "dn: uid=new.user,ou=People,dc=example,dc=com",
104     *                "objectClass: top",
105     *                "objectClass: person",
106     *                "objectClass: organizationalPerson",
107     *                "objectClass: inetOrgPerson",
108     *                "uid: new.user",
109     *                "givenName: New",
110     *                "sn: User",
111     *                "cn: New User"),
112     *           new ModifyRequest(
113     *                "dn: cn=Test Group,ou=Groups,dc=example,dc=com",
114     *                "changetype: modify",
115     *                "add: member",
116     *                "member: uid=new.user,ou=People,dc=example,dc=com"));
117     *
118     * MultiUpdateExtendedResult multiUpdateResult =
119     *      (MultiUpdateExtendedResult)
120     *      connection.processExtendedOperation(multiUpdateRequest);
121     * if (multiUpdateResult.getResultCode() == ResultCode.SUCCESS)
122     * {
123     *   // The server successfully processed the multi-update request, although
124     *   // this does not necessarily mean that any or all of the changes
125     *   // contained in it were successful.  For that, we should look at the
126     *   // changes applied and/or results element of the response.
127     *   switch (multiUpdateResult.getChangesApplied())
128     *   {
129     *     case NONE:
130     *       // There were no changes applied.  Based on the configuration of the
131     *       // request, this means that the attempt to create the user failed
132     *       // and there was no subsequent attempt to add that user to a group.
133     *       break;
134     *     case ALL:
135     *       // Both parts of the update succeeded.  The user was created and
136     *       // successfully added to a group.
137     *       break;
138     *     case PARTIAL:
139     *       // At least one update succeeded, and at least one failed.  Based on
140     *       // the configuration of the request, this means that the user was
141     *       // successfully created but not added to the target group.
142     *       break;
143     *   }
144     * }
145     * else
146     * {
147     *   // The server encountered a failure while attempting to parse or process
148     *   // the multi-update operation itself and did not attempt to process any
149     *   // of the changes contained in the request.
150     * }
151     * </PRE>
152     *
153     * @see  MultiUpdateErrorBehavior
154     * @see  MultiUpdateExtendedResult
155     */
156    @NotMutable()
157    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
158    public final class MultiUpdateExtendedRequest
159           extends ExtendedRequest
160    {
161      /**
162       * The OID (1.3.6.1.4.1.30221.2.6.17) for the multi-update extended request.
163       */
164      public static final String MULTI_UPDATE_REQUEST_OID =
165           "1.3.6.1.4.1.30221.2.6.17";
166    
167    
168    
169      /**
170       * The serial version UID for this serializable class.
171       */
172      private static final long serialVersionUID = 6101686180473949142L;
173    
174    
175    
176      // The set of update requests to be processed.
177      private final List<LDAPRequest> requests;
178    
179      // The behavior to exhibit if an error is encountered during processing.
180      private final MultiUpdateErrorBehavior errorBehavior;
181    
182    
183    
184      /**
185       * Creates a new multi-update extended request with the provided information.
186       *
187       * @param  errorBehavior  The behavior to exhibit if errors are encountered.
188       *                        It must not be {@code null}.
189       * @param  requests       The  set of requests to be processed.  It must not
190       *                        be {@code null} or empty.  Only add, delete, modify,
191       *                        modify DN, and certain extended requests (as
192       *                        determined by the server) should be included.
193       *
194       * @throws  LDAPException  If the set of requests includes one or more invalid
195       *                         request types.
196       */
197      public MultiUpdateExtendedRequest(
198                  final MultiUpdateErrorBehavior errorBehavior,
199                  final LDAPRequest... requests)
200             throws LDAPException
201      {
202        this(errorBehavior, Arrays.asList(requests));
203      }
204    
205    
206    
207      /**
208       * Creates a new multi-update extended request with the provided information.
209       *
210       * @param  errorBehavior  The behavior to exhibit if errors are encountered.
211       *                        It must not be {@code null}.
212       * @param  requests       The  set of requests to be processed.  It must not
213       *                        be {@code null} or empty.  Only add, delete, modify,
214       *                        modify DN, and certain extended requests (as
215       *                        determined by the server) should be included.  Each
216       *                        request may include zero or more controls that
217       *                        should apply only to that request.
218       * @param  controls       The set of controls to be included in the
219       *                        multi-update extended request.  It may be empty or
220       *                        {@code null} if no extended request controls are
221       *                        needed in the multi-update request.
222       *
223       * @throws  LDAPException  If the set of requests includes one or more invalid
224       *                         request types.
225       */
226      public MultiUpdateExtendedRequest(
227                  final MultiUpdateErrorBehavior errorBehavior,
228                  final LDAPRequest[] requests,
229                  final Control... controls)
230             throws LDAPException
231      {
232        this(errorBehavior, Arrays.asList(requests), controls);
233      }
234    
235    
236    
237      /**
238       * Creates a new multi-update extended request with the provided information.
239       *
240       * @param  errorBehavior  The behavior to exhibit if errors are encountered.
241       *                        It must not be {@code null}.
242       * @param  requests       The  set of requests to be processed.  It must not
243       *                        be {@code null} or empty.  Only add, delete, modify,
244       *                        modify DN, and certain extended requests (as
245       *                        determined by the server) should be included.  Each
246       *                        request may include zero or more controls that
247       *                        should apply only to that request.
248       * @param  controls       The set of controls to be included in the
249       *                        multi-update extended request.  It may be empty or
250       *                        {@code null} if no extended request controls are
251       *                        needed in the multi-update request.
252       *
253       * @throws  LDAPException  If the set of requests includes one or more invalid
254       *                         request types.
255       */
256      public MultiUpdateExtendedRequest(
257                  final MultiUpdateErrorBehavior errorBehavior,
258                  final List<LDAPRequest> requests,
259                  final Control... controls)
260             throws LDAPException
261      {
262        super(MULTI_UPDATE_REQUEST_OID, encodeValue(errorBehavior, requests),
263              controls);
264    
265        this.errorBehavior = errorBehavior;
266    
267        final ArrayList<LDAPRequest> requestList =
268             new ArrayList<LDAPRequest>(requests.size());
269        for (final LDAPRequest r : requests)
270        {
271          switch (r.getOperationType())
272          {
273            case ADD:
274            case DELETE:
275            case MODIFY:
276            case MODIFY_DN:
277            case EXTENDED:
278              requestList.add(r);
279              break;
280            default:
281              throw new LDAPException(ResultCode.PARAM_ERROR,
282                   ERR_MULTI_UPDATE_REQUEST_INVALID_REQUEST_TYPE.get(
283                        r.getOperationType().name()));
284          }
285        }
286        this.requests = Collections.unmodifiableList(requestList);
287      }
288    
289    
290    
291      /**
292       * Creates a new multi-update extended request with the provided information.
293       *
294       * @param  errorBehavior  The behavior to exhibit if errors are encountered.
295       *                        It must not be {@code null}.
296       * @param  requests       The  set of requests to be processed.  It must not
297       *                        be {@code null} or empty.  Only add, delete, modify,
298       *                        modify DN, and certain extended requests (as
299       *                        determined by the server) should be included.  Each
300       *                        request may include zero or more controls that
301       *                        should apply only to that request.
302       * @param  encodedValue   The encoded representation of the value for this
303       *                        request.
304       * @param  controls       The set of controls to be included in the
305       *                        multi-update extended request.  It may be empty or
306       *                        {@code null} if no extended request controls are
307       *                        needed in the multi-update request.
308       */
309      private MultiUpdateExtendedRequest(
310                   final MultiUpdateErrorBehavior errorBehavior,
311                   final List<LDAPRequest> requests,
312                   final ASN1OctetString encodedValue,
313                   final Control... controls)
314      {
315        super(MULTI_UPDATE_REQUEST_OID, encodedValue, controls);
316    
317        this.errorBehavior = errorBehavior;
318        this.requests      = requests;
319      }
320    
321    
322    
323      /**
324       * Creates a new multi-update extended request from the provided generic
325       * extended request.
326       *
327       * @param  extendedRequest  The generic extended request to use to create this
328       *                          multi-update extended request.
329       *
330       * @throws  LDAPException  If a problem occurs while decoding the request.
331       */
332      public MultiUpdateExtendedRequest(final ExtendedRequest extendedRequest)
333             throws LDAPException
334      {
335        super(extendedRequest);
336    
337        final ASN1OctetString value = extendedRequest.getValue();
338        if (value == null)
339        {
340          throw new LDAPException(ResultCode.DECODING_ERROR,
341               ERR_MULTI_UPDATE_REQUEST_NO_VALUE.get());
342        }
343    
344        try
345        {
346          final ASN1Element[] ve =
347               ASN1Sequence.decodeAsSequence(value.getValue()).elements();
348    
349          errorBehavior = MultiUpdateErrorBehavior.valueOf(
350               ASN1Enumerated.decodeAsEnumerated(ve[0]).intValue());
351          if (errorBehavior == null)
352          {
353            throw new LDAPException(ResultCode.DECODING_ERROR,
354                 ERR_MULTI_UPDATE_REQUEST_INVALID_ERROR_BEHAVIOR.get(
355                      ASN1Enumerated.decodeAsEnumerated(ve[0]).intValue()));
356          }
357    
358          final ASN1Element[] requestSequenceElements =
359               ASN1Sequence.decodeAsSequence(ve[1]).elements();
360          final ArrayList<LDAPRequest> rl =
361               new ArrayList<LDAPRequest>(requestSequenceElements.length);
362          for (final ASN1Element rse : requestSequenceElements)
363          {
364            final Control[] controls;
365            final ASN1Element[] requestElements =
366                 ASN1Sequence.decodeAsSequence(rse).elements();
367            if (requestElements.length == 2)
368            {
369              controls = Control.decodeControls(
370                   ASN1Sequence.decodeAsSequence(requestElements[1]));
371            }
372            else
373            {
374              controls = StaticUtils.NO_CONTROLS;
375            }
376    
377            switch (requestElements[0].getType())
378            {
379              case LDAPMessage.PROTOCOL_OP_TYPE_ADD_REQUEST:
380                rl.add(AddRequestProtocolOp.decodeProtocolOp(
381                     requestElements[0]).toAddRequest(controls));
382                break;
383    
384              case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST:
385                rl.add(DeleteRequestProtocolOp.decodeProtocolOp(
386                     requestElements[0]).toDeleteRequest(controls));
387                break;
388    
389              case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST:
390                rl.add(ExtendedRequestProtocolOp.decodeProtocolOp(
391                     requestElements[0]).toExtendedRequest(controls));
392                break;
393    
394              case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST:
395                rl.add(ModifyRequestProtocolOp.decodeProtocolOp(
396                     requestElements[0]).toModifyRequest(controls));
397                break;
398    
399              case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST:
400                rl.add(ModifyDNRequestProtocolOp.decodeProtocolOp(
401                     requestElements[0]).toModifyDNRequest(controls));
402                break;
403    
404              default:
405                throw new LDAPException(ResultCode.DECODING_ERROR,
406                     ERR_MULTI_UPDATE_REQUEST_INVALID_OP_TYPE.get(
407                          StaticUtils.toHex(requestElements[0].getType())));
408            }
409          }
410    
411          requests = Collections.unmodifiableList(rl);
412        }
413        catch (final LDAPException le)
414        {
415          Debug.debugException(le);
416          throw le;
417        }
418        catch (final Exception e)
419        {
420          Debug.debugException(e);
421          throw new LDAPException(ResultCode.DECODING_ERROR,
422               ERR_MULTI_UPDATE_REQUEST_CANNOT_DECODE_VALUE.get(
423                    StaticUtils.getExceptionMessage(e)),
424               e);
425        }
426      }
427    
428    
429    
430      /**
431       * Generates an ASN.1 octet string suitable for use as the value of a
432       * multi-update extended request.
433       *
434       * @param  errorBehavior  The behavior to exhibit if errors are encountered.
435       *                        It must not be {@code null}.
436       * @param  requests       The  set of requests to be processed.  It must not
437       *                        be {@code null} or empty.  Only add, delete, modify,
438       *                        modify DN, and certain extended requests (as
439       *                        determined by the server) should be included.  Each
440       *                        request may include zero or more controls that
441       *                        should apply only to that request.
442       *
443       * @return  An ASN.1 octet string suitable for use as the value of a
444       *          multi-update extended request.
445       */
446      private static ASN1OctetString encodeValue(
447                          final MultiUpdateErrorBehavior errorBehavior,
448                          final List<LDAPRequest> requests)
449      {
450        final ArrayList<ASN1Element> requestElements = new
451             ArrayList<ASN1Element>(requests.size());
452        for (final LDAPRequest r : requests)
453        {
454          final ArrayList<ASN1Element> rsElements = new ArrayList<ASN1Element>(2);
455          switch (r.getOperationType())
456          {
457            case ADD:
458              rsElements.add(((AddRequest) r).encodeProtocolOp());
459              break;
460            case DELETE:
461              rsElements.add(((DeleteRequest) r).encodeProtocolOp());
462              break;
463            case MODIFY:
464              rsElements.add(((ModifyRequest) r).encodeProtocolOp());
465              break;
466            case MODIFY_DN:
467              rsElements.add(((ModifyDNRequest) r).encodeProtocolOp());
468              break;
469            case EXTENDED:
470              rsElements.add(((ExtendedRequest) r).encodeProtocolOp());
471              break;
472          }
473    
474          if (r.hasControl())
475          {
476            rsElements.add(Control.encodeControls(r.getControls()));
477          }
478    
479          requestElements.add(new ASN1Sequence(rsElements));
480        }
481    
482        final ASN1Sequence valueSequence = new ASN1Sequence(
483             new ASN1Enumerated(errorBehavior.intValue()),
484             new ASN1Sequence(requestElements));
485        return new ASN1OctetString(valueSequence.encode());
486      }
487    
488    
489    
490      /**
491       * {@inheritDoc}
492       */
493      @Override()
494      public MultiUpdateExtendedResult process(final LDAPConnection connection,
495                                               final int depth)
496             throws LDAPException
497      {
498        final ExtendedResult extendedResponse = super.process(connection, depth);
499        return new MultiUpdateExtendedResult(extendedResponse);
500      }
501    
502    
503    
504      /**
505       * Retrieves the behavior to exhibit if errors are encountered.
506       *
507       * @return  The behavior to exhibit if errors are encountered.
508       */
509      public MultiUpdateErrorBehavior getErrorBehavior()
510      {
511        return errorBehavior;
512      }
513    
514    
515    
516      /**
517       *
518       * Retrieves the set of requests to be processed.
519       *
520       * @return  The set of requests to be processed.
521       */
522      public List<LDAPRequest> getRequests()
523      {
524        return requests;
525      }
526    
527    
528    
529      /**
530       * {@inheritDoc}
531       */
532      @Override()
533      public MultiUpdateExtendedRequest duplicate()
534      {
535        return duplicate(getControls());
536      }
537    
538    
539    
540      /**
541       * {@inheritDoc}
542       */
543      @Override()
544      public MultiUpdateExtendedRequest duplicate(final Control[] controls)
545      {
546        final MultiUpdateExtendedRequest r =
547             new MultiUpdateExtendedRequest(errorBehavior, requests,
548                  getValue(), controls);
549        r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
550        return r;
551      }
552    
553    
554    
555      /**
556       * {@inheritDoc}
557       */
558      @Override()
559      public String getExtendedRequestName()
560      {
561        return INFO_EXTENDED_REQUEST_NAME_MULTI_UPDATE.get();
562      }
563    
564    
565    
566      /**
567       * {@inheritDoc}
568       */
569      @Override()
570      public void toString(final StringBuilder buffer)
571      {
572        buffer.append("MultiUpdateExtendedRequest(errorBehavior=");
573        buffer.append(errorBehavior.name());
574        buffer.append(", requests={");
575    
576        final Iterator<LDAPRequest> iterator = requests.iterator();
577        while (iterator.hasNext())
578        {
579          iterator.next().toString(buffer);
580          if (iterator.hasNext())
581          {
582            buffer.append(", ");
583          }
584        }
585    
586        buffer.append('}');
587    
588        final Control[] controls = getControls();
589        if (controls.length > 0)
590        {
591          buffer.append(", controls={");
592          for (int i=0; i < controls.length; i++)
593          {
594            if (i > 0)
595            {
596              buffer.append(", ");
597            }
598    
599            buffer.append(controls[i]);
600          }
601          buffer.append('}');
602        }
603    
604        buffer.append(')');
605      }
606    }