001/*
002 * Copyright 2007-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2016 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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collections;
028import java.util.List;
029import java.util.Timer;
030import java.util.concurrent.LinkedBlockingQueue;
031import java.util.concurrent.TimeUnit;
032
033import com.unboundid.asn1.ASN1Buffer;
034import com.unboundid.asn1.ASN1BufferSequence;
035import com.unboundid.asn1.ASN1Element;
036import com.unboundid.asn1.ASN1OctetString;
037import com.unboundid.asn1.ASN1Sequence;
038import com.unboundid.ldap.protocol.LDAPMessage;
039import com.unboundid.ldap.protocol.LDAPResponse;
040import com.unboundid.ldap.protocol.ProtocolOp;
041import com.unboundid.ldif.LDIFChangeRecord;
042import com.unboundid.ldif.LDIFException;
043import com.unboundid.ldif.LDIFModifyChangeRecord;
044import com.unboundid.ldif.LDIFReader;
045import com.unboundid.util.InternalUseOnly;
046import com.unboundid.util.Mutable;
047import com.unboundid.util.ThreadSafety;
048import com.unboundid.util.ThreadSafetyLevel;
049
050import static com.unboundid.ldap.sdk.LDAPMessages.*;
051import static com.unboundid.util.Debug.*;
052import static com.unboundid.util.StaticUtils.*;
053import static com.unboundid.util.Validator.*;
054
055
056
057/**
058 * This class implements the processing necessary to perform an LDAPv3 modify
059 * operation, which can be used to update an entry in the directory server.  A
060 * modify request contains the DN of the entry to modify, as well as one or more
061 * changes to apply to that entry.  See the {@link Modification} class for more
062 * information about the types of modifications that may be processed.
063 * <BR><BR>
064 * A modify request can be created with a DN and set of modifications, but it
065 * can also be as a list of the lines that comprise the LDIF representation of
066 * the modification as described in
067 * <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A>.  For example, the
068 * following code demonstrates creating a modify request from the LDIF
069 * representation of the modification:
070 * <PRE>
071 *   ModifyRequest modifyRequest = new ModifyRequest(
072 *     "dn: dc=example,dc=com",
073 *     "changetype: modify",
074 *     "replace: description",
075 *     "description: This is the new description.");
076 * </PRE>
077 * <BR><BR>
078 * {@code ModifyRequest} objects are mutable and therefore can be altered and
079 * re-used for multiple requests.  Note, however, that {@code ModifyRequest}
080 * objects are not threadsafe and therefore a single {@code ModifyRequest}
081 * object instance should not be used to process multiple requests at the same
082 * time.
083 */
084@Mutable()
085@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
086public final class ModifyRequest
087       extends UpdatableLDAPRequest
088       implements ReadOnlyModifyRequest, ResponseAcceptor, ProtocolOp
089{
090  /**
091   * The serial version UID for this serializable class.
092   */
093  private static final long serialVersionUID = -4747622844001634758L;
094
095
096
097  // The queue that will be used to receive response messages from the server.
098  private final LinkedBlockingQueue<LDAPResponse> responseQueue =
099       new LinkedBlockingQueue<LDAPResponse>();
100
101  // The set of modifications to perform.
102  private final ArrayList<Modification> modifications;
103
104  // The message ID from the last LDAP message sent from this request.
105  private int messageID = -1;
106
107  // The DN of the entry to modify.
108  private String dn;
109
110
111
112  /**
113   * Creates a new modify request with the provided information.
114   *
115   * @param  dn   The DN of the entry to modify.  It must not be {@code null}.
116   * @param  mod  The modification to apply to the entry.  It must not be
117   *              {@code null}.
118   */
119  public ModifyRequest(final String dn, final Modification mod)
120  {
121    super(null);
122
123    ensureNotNull(dn, mod);
124
125    this.dn = dn;
126
127    modifications = new ArrayList<Modification>(1);
128    modifications.add(mod);
129  }
130
131
132
133  /**
134   * Creates a new modify request with the provided information.
135   *
136   * @param  dn    The DN of the entry to modify.  It must not be {@code null}.
137   * @param  mods  The set of modifications to apply to the entry.  It must not
138   *               be {@code null} or empty.
139   */
140  public ModifyRequest(final String dn, final Modification... mods)
141  {
142    super(null);
143
144    ensureNotNull(dn, mods);
145    ensureFalse(mods.length == 0,
146         "ModifyRequest.mods must not be empty.");
147
148    this.dn = dn;
149
150    modifications = new ArrayList<Modification>(mods.length);
151    modifications.addAll(Arrays.asList(mods));
152  }
153
154
155
156  /**
157   * Creates a new modify request with the provided information.
158   *
159   * @param  dn    The DN of the entry to modify.  It must not be {@code null}.
160   * @param  mods  The set of modifications to apply to the entry.  It must not
161   *               be {@code null} or empty.
162   */
163  public ModifyRequest(final String dn, final List<Modification> mods)
164  {
165    super(null);
166
167    ensureNotNull(dn, mods);
168    ensureFalse(mods.isEmpty(),
169                "ModifyRequest.mods must not be empty.");
170
171    this.dn = dn;
172
173    modifications = new ArrayList<Modification>(mods);
174  }
175
176
177
178  /**
179   * Creates a new modify request with the provided information.
180   *
181   * @param  dn   The DN of the entry to modify.  It must not be {@code null}.
182   * @param  mod  The modification to apply to the entry.  It must not be
183   *              {@code null}.
184   */
185  public ModifyRequest(final DN dn, final Modification mod)
186  {
187    super(null);
188
189    ensureNotNull(dn, mod);
190
191    this.dn = dn.toString();
192
193    modifications = new ArrayList<Modification>(1);
194    modifications.add(mod);
195  }
196
197
198
199  /**
200   * Creates a new modify request with the provided information.
201   *
202   * @param  dn    The DN of the entry to modify.  It must not be {@code null}.
203   * @param  mods  The set of modifications to apply to the entry.  It must not
204   *               be {@code null} or empty.
205   */
206  public ModifyRequest(final DN dn, final Modification... mods)
207  {
208    super(null);
209
210    ensureNotNull(dn, mods);
211    ensureFalse(mods.length == 0,
212         "ModifyRequest.mods must not be empty.");
213
214    this.dn = dn.toString();
215
216    modifications = new ArrayList<Modification>(mods.length);
217    modifications.addAll(Arrays.asList(mods));
218  }
219
220
221
222  /**
223   * Creates a new modify request with the provided information.
224   *
225   * @param  dn    The DN of the entry to modify.  It must not be {@code null}.
226   * @param  mods  The set of modifications to apply to the entry.  It must not
227   *               be {@code null} or empty.
228   */
229  public ModifyRequest(final DN dn, final List<Modification> mods)
230  {
231    super(null);
232
233    ensureNotNull(dn, mods);
234    ensureFalse(mods.isEmpty(),
235                "ModifyRequest.mods must not be empty.");
236
237    this.dn = dn.toString();
238
239    modifications = new ArrayList<Modification>(mods);
240  }
241
242
243
244  /**
245   * Creates a new modify request with the provided information.
246   *
247   * @param  dn        The DN of the entry to modify.  It must not be
248   *                   {@code null}.
249   * @param  mod       The modification to apply to the entry.  It must not be
250   *                   {@code null}.
251   * @param  controls  The set of controls to include in the request.
252   */
253  public ModifyRequest(final String dn, final Modification mod,
254                       final Control[] controls)
255  {
256    super(controls);
257
258    ensureNotNull(dn, mod);
259
260    this.dn = dn;
261
262    modifications = new ArrayList<Modification>(1);
263    modifications.add(mod);
264  }
265
266
267
268  /**
269   * Creates a new modify request with the provided information.
270   *
271   * @param  dn        The DN of the entry to modify.  It must not be
272   *                   {@code null}.
273   * @param  mods      The set of modifications to apply to the entry.  It must
274   *                   not be {@code null} or empty.
275   * @param  controls  The set of controls to include in the request.
276   */
277  public ModifyRequest(final String dn, final Modification[] mods,
278                       final Control[] controls)
279  {
280    super(controls);
281
282    ensureNotNull(dn, mods);
283    ensureFalse(mods.length == 0,
284         "ModifyRequest.mods must not be empty.");
285
286    this.dn = dn;
287
288    modifications = new ArrayList<Modification>(mods.length);
289    modifications.addAll(Arrays.asList(mods));
290  }
291
292
293
294  /**
295   * Creates a new modify request with the provided information.
296   *
297   * @param  dn        The DN of the entry to modify.  It must not be
298   *                   {@code null}.
299   * @param  mods      The set of modifications to apply to the entry.  It must
300   *                   not be {@code null} or empty.
301   * @param  controls  The set of controls to include in the request.
302   */
303  public ModifyRequest(final String dn, final List<Modification> mods,
304                       final Control[] controls)
305  {
306    super(controls);
307
308    ensureNotNull(dn, mods);
309    ensureFalse(mods.isEmpty(),
310                "ModifyRequest.mods must not be empty.");
311
312    this.dn = dn;
313
314    modifications = new ArrayList<Modification>(mods);
315  }
316
317
318
319  /**
320   * Creates a new modify request with the provided information.
321   *
322   * @param  dn        The DN of the entry to modify.  It must not be
323   *                   {@code null}.
324   * @param  mod       The modification to apply to the entry.  It must not be
325   *                   {@code null}.
326   * @param  controls  The set of controls to include in the request.
327   */
328  public ModifyRequest(final DN dn, final Modification mod,
329                       final Control[] controls)
330  {
331    super(controls);
332
333    ensureNotNull(dn, mod);
334
335    this.dn = dn.toString();
336
337    modifications = new ArrayList<Modification>(1);
338    modifications.add(mod);
339  }
340
341
342
343  /**
344   * Creates a new modify request with the provided information.
345   *
346   * @param  dn        The DN of the entry to modify.  It must not be
347   *                   {@code null}.
348   * @param  mods      The set of modifications to apply to the entry.  It must
349   *                   not be {@code null} or empty.
350   * @param  controls  The set of controls to include in the request.
351   */
352  public ModifyRequest(final DN dn, final Modification[] mods,
353                       final Control[] controls)
354  {
355    super(controls);
356
357    ensureNotNull(dn, mods);
358    ensureFalse(mods.length == 0,
359         "ModifyRequest.mods must not be empty.");
360
361    this.dn = dn.toString();
362
363    modifications = new ArrayList<Modification>(mods.length);
364    modifications.addAll(Arrays.asList(mods));
365  }
366
367
368
369  /**
370   * Creates a new modify request with the provided information.
371   *
372   * @param  dn        The DN of the entry to modify.  It must not be
373   *                   {@code null}.
374   * @param  mods      The set of modifications to apply to the entry.  It must
375   *                   not be {@code null} or empty.
376   * @param  controls  The set of controls to include in the request.
377   */
378  public ModifyRequest(final DN dn, final List<Modification> mods,
379                       final Control[] controls)
380  {
381    super(controls);
382
383    ensureNotNull(dn, mods);
384    ensureFalse(mods.isEmpty(),
385                "ModifyRequest.mods must not be empty.");
386
387    this.dn = dn.toString();
388
389    modifications = new ArrayList<Modification>(mods);
390  }
391
392
393
394  /**
395   * Creates a new modify request from the provided LDIF representation of the
396   * changes.
397   *
398   * @param  ldifModificationLines  The lines that comprise an LDIF
399   *                                representation of a modify change record.
400   *                                It must not be {@code null} or empty.
401   *
402   * @throws  LDIFException  If the provided set of lines cannot be parsed as an
403   *                         LDIF modify change record.
404   */
405  public ModifyRequest(final String... ldifModificationLines)
406         throws LDIFException
407  {
408    super(null);
409
410    final LDIFChangeRecord changeRecord =
411         LDIFReader.decodeChangeRecord(ldifModificationLines);
412    if (! (changeRecord instanceof LDIFModifyChangeRecord))
413    {
414      throw new LDIFException(ERR_MODIFY_INVALID_LDIF.get(), 0, false,
415                              ldifModificationLines, null);
416    }
417
418    final LDIFModifyChangeRecord modifyRecord =
419         (LDIFModifyChangeRecord) changeRecord;
420    final ModifyRequest r = modifyRecord.toModifyRequest();
421
422    dn            = r.dn;
423    modifications = r.modifications;
424  }
425
426
427
428  /**
429   * {@inheritDoc}
430   */
431  public String getDN()
432  {
433    return dn;
434  }
435
436
437
438  /**
439   * Specifies the DN of the entry to modify.
440   *
441   * @param  dn  The DN of the entry to modify.  It must not be {@code null}.
442   */
443  public void setDN(final String dn)
444  {
445    ensureNotNull(dn);
446
447    this.dn = dn;
448  }
449
450
451
452  /**
453   * Specifies the DN of the entry to modify.
454   *
455   * @param  dn  The DN of the entry to modify.  It must not be {@code null}.
456   */
457  public void setDN(final DN dn)
458  {
459    ensureNotNull(dn);
460
461    this.dn = dn.toString();
462  }
463
464
465
466  /**
467   * {@inheritDoc}
468   */
469  public List<Modification> getModifications()
470  {
471    return Collections.unmodifiableList(modifications);
472  }
473
474
475
476  /**
477   * Adds the provided modification to the set of modifications for this modify
478   * request.
479   *
480   * @param  mod  The modification to be added.  It must not be {@code null}.
481   */
482  public void addModification(final Modification mod)
483  {
484    ensureNotNull(mod);
485
486    modifications.add(mod);
487  }
488
489
490
491  /**
492   * Removes the provided modification from the set of modifications for this
493   * modify request.
494   *
495   * @param  mod  The modification to be removed.  It must not be {@code null}.
496   *
497   * @return  {@code true} if the specified modification was found and removed,
498   *          or {@code false} if not.
499   */
500  public boolean removeModification(final Modification mod)
501  {
502    ensureNotNull(mod);
503
504    return modifications.remove(mod);
505  }
506
507
508
509  /**
510   * Replaces the existing set of modifications for this modify request with the
511   * provided modification.
512   *
513   * @param  mod  The modification to use for this modify request.  It must not
514   *              be {@code null}.
515   */
516  public void setModifications(final Modification mod)
517  {
518    ensureNotNull(mod);
519
520    modifications.clear();
521    modifications.add(mod);
522  }
523
524
525
526  /**
527   * Replaces the existing set of modifications for this modify request with the
528   * provided modifications.
529   *
530   * @param  mods  The set of modification to use for this modify request.  It
531   *               must not be {@code null} or empty.
532   */
533  public void setModifications(final Modification[] mods)
534  {
535    ensureNotNull(mods);
536    ensureFalse(mods.length == 0,
537         "ModifyRequest.setModifications.mods must not be empty.");
538
539    modifications.clear();
540    modifications.addAll(Arrays.asList(mods));
541  }
542
543
544
545  /**
546   * Replaces the existing set of modifications for this modify request with the
547   * provided modifications.
548   *
549   * @param  mods  The set of modification to use for this modify request.  It
550   *               must not be {@code null} or empty.
551   */
552  public void setModifications(final List<Modification> mods)
553  {
554    ensureNotNull(mods);
555    ensureFalse(mods.isEmpty(),
556                "ModifyRequest.setModifications.mods must not be empty.");
557
558    modifications.clear();
559    modifications.addAll(mods);
560  }
561
562
563
564  /**
565   * {@inheritDoc}
566   */
567  public byte getProtocolOpType()
568  {
569    return LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST;
570  }
571
572
573
574  /**
575   * {@inheritDoc}
576   */
577  public void writeTo(final ASN1Buffer writer)
578  {
579    final ASN1BufferSequence requestSequence =
580         writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST);
581    writer.addOctetString(dn);
582
583    final ASN1BufferSequence modSequence = writer.beginSequence();
584    for (final Modification m : modifications)
585    {
586      m.writeTo(writer);
587    }
588    modSequence.end();
589    requestSequence.end();
590  }
591
592
593
594  /**
595   * Encodes the modify request protocol op to an ASN.1 element.
596   *
597   * @return  The ASN.1 element with the encoded modify request protocol op.
598   */
599  public ASN1Element encodeProtocolOp()
600  {
601    final ASN1Element[] modElements = new ASN1Element[modifications.size()];
602    for (int i=0; i < modElements.length; i++)
603    {
604      modElements[i] = modifications.get(i).encode();
605    }
606
607    final ASN1Element[] protocolOpElements =
608    {
609      new ASN1OctetString(dn),
610      new ASN1Sequence(modElements)
611    };
612
613
614
615    return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST,
616                            protocolOpElements);
617  }
618
619
620
621  /**
622   * Sends this modify request to the directory server over the provided
623   * connection and returns the associated response.
624   *
625   * @param  connection  The connection to use to communicate with the directory
626   *                     server.
627   * @param  depth       The current referral depth for this request.  It should
628   *                     always be one for the initial request, and should only
629   *                     be incremented when following referrals.
630   *
631   * @return  An LDAP result object that provides information about the result
632   *          of the modify processing.
633   *
634   * @throws  LDAPException  If a problem occurs while sending the request or
635   *                         reading the response.
636   */
637  @Override()
638  protected LDAPResult process(final LDAPConnection connection, final int depth)
639            throws LDAPException
640  {
641    if (connection.synchronousMode())
642    {
643      @SuppressWarnings("deprecation")
644      final boolean autoReconnect =
645           connection.getConnectionOptions().autoReconnect();
646      return processSync(connection, depth, autoReconnect);
647    }
648
649    final long requestTime = System.nanoTime();
650    processAsync(connection, null);
651
652    try
653    {
654      // Wait for and process the response.
655      final LDAPResponse response;
656      try
657      {
658        final long responseTimeout = getResponseTimeoutMillis(connection);
659        if (responseTimeout > 0)
660        {
661          response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
662        }
663        else
664        {
665          response = responseQueue.take();
666        }
667      }
668      catch (InterruptedException ie)
669      {
670        debugException(ie);
671        throw new LDAPException(ResultCode.LOCAL_ERROR,
672             ERR_MODIFY_INTERRUPTED.get(connection.getHostPort()), ie);
673      }
674
675      return handleResponse(connection, response, requestTime, depth, false);
676    }
677    finally
678    {
679      connection.deregisterResponseAcceptor(messageID);
680    }
681  }
682
683
684
685  /**
686   * Sends this modify request to the directory server over the provided
687   * connection and returns the message ID for the request.
688   *
689   * @param  connection      The connection to use to communicate with the
690   *                         directory server.
691   * @param  resultListener  The async result listener that is to be notified
692   *                         when the response is received.  It may be
693   *                         {@code null} only if the result is to be processed
694   *                         by this class.
695   *
696   * @return  The async request ID created for the operation, or {@code null} if
697   *          the provided {@code resultListener} is {@code null} and the
698   *          operation will not actually be processed asynchronously.
699   *
700   * @throws  LDAPException  If a problem occurs while sending the request.
701   */
702  AsyncRequestID processAsync(final LDAPConnection connection,
703                              final AsyncResultListener resultListener)
704                 throws LDAPException
705  {
706    // Create the LDAP message.
707    messageID = connection.nextMessageID();
708    final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
709
710
711    // If the provided async result listener is {@code null}, then we'll use
712    // this class as the message acceptor.  Otherwise, create an async helper
713    // and use it as the message acceptor.
714    final AsyncRequestID asyncRequestID;
715    if (resultListener == null)
716    {
717      asyncRequestID = null;
718      connection.registerResponseAcceptor(messageID, this);
719    }
720    else
721    {
722      final AsyncHelper helper = new AsyncHelper(connection,
723           OperationType.MODIFY, messageID, resultListener,
724           getIntermediateResponseListener());
725      connection.registerResponseAcceptor(messageID, helper);
726      asyncRequestID = helper.getAsyncRequestID();
727
728      final long timeout = getResponseTimeoutMillis(connection);
729      if (timeout > 0L)
730      {
731        final Timer timer = connection.getTimer();
732        final AsyncTimeoutTimerTask timerTask =
733             new AsyncTimeoutTimerTask(helper);
734        timer.schedule(timerTask, timeout);
735        asyncRequestID.setTimerTask(timerTask);
736      }
737    }
738
739
740    // Send the request to the server.
741    try
742    {
743      debugLDAPRequest(this);
744      connection.getConnectionStatistics().incrementNumModifyRequests();
745      connection.sendMessage(message);
746      return asyncRequestID;
747    }
748    catch (LDAPException le)
749    {
750      debugException(le);
751
752      connection.deregisterResponseAcceptor(messageID);
753      throw le;
754    }
755  }
756
757
758
759  /**
760   * Processes this modify operation in synchronous mode, in which the same
761   * thread will send the request and read the response.
762   *
763   * @param  connection  The connection to use to communicate with the directory
764   *                     server.
765   * @param  depth       The current referral depth for this request.  It should
766   *                     always be one for the initial request, and should only
767   *                     be incremented when following referrals.
768   * @param  allowRetry  Indicates whether the request may be re-tried on a
769   *                     re-established connection if the initial attempt fails
770   *                     in a way that indicates the connection is no longer
771   *                     valid and autoReconnect is true.
772   *
773   * @return  An LDAP result object that provides information about the result
774   *          of the modify processing.
775   *
776   * @throws  LDAPException  If a problem occurs while sending the request or
777   *                         reading the response.
778   */
779  private LDAPResult processSync(final LDAPConnection connection,
780                                 final int depth, final boolean allowRetry)
781          throws LDAPException
782  {
783    // Create the LDAP message.
784    messageID = connection.nextMessageID();
785    final LDAPMessage message =
786         new LDAPMessage(messageID,  this, getControls());
787
788
789    // Set the appropriate timeout on the socket.
790    try
791    {
792      connection.getConnectionInternals(true).getSocket().setSoTimeout(
793           (int) getResponseTimeoutMillis(connection));
794    }
795    catch (Exception e)
796    {
797      debugException(e);
798    }
799
800
801    // Send the request to the server.
802    final long requestTime = System.nanoTime();
803    debugLDAPRequest(this);
804    connection.getConnectionStatistics().incrementNumModifyRequests();
805    try
806    {
807      connection.sendMessage(message);
808    }
809    catch (final LDAPException le)
810    {
811      debugException(le);
812
813      if (allowRetry)
814      {
815        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
816             le.getResultCode());
817        if (retryResult != null)
818        {
819          return retryResult;
820        }
821      }
822
823      throw le;
824    }
825
826    while (true)
827    {
828      final LDAPResponse response;
829      try
830      {
831        response = connection.readResponse(messageID);
832      }
833      catch (final LDAPException le)
834      {
835        debugException(le);
836
837        if ((le.getResultCode() == ResultCode.TIMEOUT) &&
838            connection.getConnectionOptions().abandonOnTimeout())
839        {
840          connection.abandon(messageID);
841        }
842
843        if (allowRetry)
844        {
845          final LDAPResult retryResult = reconnectAndRetry(connection, depth,
846               le.getResultCode());
847          if (retryResult != null)
848          {
849            return retryResult;
850          }
851        }
852
853        throw le;
854      }
855
856      if (response instanceof IntermediateResponse)
857      {
858        final IntermediateResponseListener listener =
859             getIntermediateResponseListener();
860        if (listener != null)
861        {
862          listener.intermediateResponseReturned(
863               (IntermediateResponse) response);
864        }
865      }
866      else
867      {
868        return handleResponse(connection, response, requestTime, depth,
869             allowRetry);
870      }
871    }
872  }
873
874
875
876  /**
877   * Performs the necessary processing for handling a response.
878   *
879   * @param  connection   The connection used to read the response.
880   * @param  response     The response to be processed.
881   * @param  requestTime  The time the request was sent to the server.
882   * @param  depth        The current referral depth for this request.  It
883   *                      should always be one for the initial request, and
884   *                      should only be incremented when following referrals.
885   * @param  allowRetry   Indicates whether the request may be re-tried on a
886   *                      re-established connection if the initial attempt fails
887   *                      in a way that indicates the connection is no longer
888   *                      valid and autoReconnect is true.
889   *
890   * @return  The modify result.
891   *
892   * @throws  LDAPException  If a problem occurs.
893   */
894  private LDAPResult handleResponse(final LDAPConnection connection,
895                                    final LDAPResponse response,
896                                    final long requestTime, final int depth,
897                                    final boolean allowRetry)
898          throws LDAPException
899  {
900    if (response == null)
901    {
902      final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
903      if (connection.getConnectionOptions().abandonOnTimeout())
904      {
905        connection.abandon(messageID);
906      }
907
908      throw new LDAPException(ResultCode.TIMEOUT,
909           ERR_MODIFY_CLIENT_TIMEOUT.get(waitTime, messageID, dn,
910                connection.getHostPort()));
911    }
912
913    connection.getConnectionStatistics().incrementNumModifyResponses(
914         System.nanoTime() - requestTime);
915    if (response instanceof ConnectionClosedResponse)
916    {
917      // The connection was closed while waiting for the response.
918      if (allowRetry)
919      {
920        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
921             ResultCode.SERVER_DOWN);
922        if (retryResult != null)
923        {
924          return retryResult;
925        }
926      }
927
928      final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
929      final String message = ccr.getMessage();
930      if (message == null)
931      {
932        throw new LDAPException(ccr.getResultCode(),
933             ERR_CONN_CLOSED_WAITING_FOR_MODIFY_RESPONSE.get(
934                  connection.getHostPort(), toString()));
935      }
936      else
937      {
938        throw new LDAPException(ccr.getResultCode(),
939             ERR_CONN_CLOSED_WAITING_FOR_MODIFY_RESPONSE_WITH_MESSAGE.get(
940                  connection.getHostPort(), toString(), message));
941      }
942    }
943
944    final LDAPResult result = (LDAPResult) response;
945    if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
946        followReferrals(connection))
947    {
948      if (depth >= connection.getConnectionOptions().getReferralHopLimit())
949      {
950        return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED,
951                              ERR_TOO_MANY_REFERRALS.get(),
952                              result.getMatchedDN(), result.getReferralURLs(),
953                              result.getResponseControls());
954      }
955
956      return followReferral(result, connection, depth);
957    }
958    else
959    {
960      if (allowRetry)
961      {
962        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
963             result.getResultCode());
964        if (retryResult != null)
965        {
966          return retryResult;
967        }
968      }
969
970      return result;
971    }
972  }
973
974
975
976  /**
977   * Attempts to re-establish the connection and retry processing this request
978   * on it.
979   *
980   * @param  connection  The connection to be re-established.
981   * @param  depth       The current referral depth for this request.  It should
982   *                     always be one for the initial request, and should only
983   *                     be incremented when following referrals.
984   * @param  resultCode  The result code for the previous operation attempt.
985   *
986   * @return  The result from re-trying the add, or {@code null} if it could not
987   *          be re-tried.
988   */
989  private LDAPResult reconnectAndRetry(final LDAPConnection connection,
990                                       final int depth,
991                                       final ResultCode resultCode)
992  {
993    try
994    {
995      // We will only want to retry for certain result codes that indicate a
996      // connection problem.
997      switch (resultCode.intValue())
998      {
999        case ResultCode.SERVER_DOWN_INT_VALUE:
1000        case ResultCode.DECODING_ERROR_INT_VALUE:
1001        case ResultCode.CONNECT_ERROR_INT_VALUE:
1002          connection.reconnect();
1003          return processSync(connection, depth, false);
1004      }
1005    }
1006    catch (final Exception e)
1007    {
1008      debugException(e);
1009    }
1010
1011    return null;
1012  }
1013
1014
1015
1016  /**
1017   * Attempts to follow a referral to perform a modify operation in the target
1018   * server.
1019   *
1020   * @param  referralResult  The LDAP result object containing information about
1021   *                         the referral to follow.
1022   * @param  connection      The connection on which the referral was received.
1023   * @param  depth           The number of referrals followed in the course of
1024   *                         processing this request.
1025   *
1026   * @return  The result of attempting to process the modify operation by
1027   *          following the referral.
1028   *
1029   * @throws  LDAPException  If a problem occurs while attempting to establish
1030   *                         the referral connection, sending the request, or
1031   *                         reading the result.
1032   */
1033  private LDAPResult followReferral(final LDAPResult referralResult,
1034                                    final LDAPConnection connection,
1035                                    final int depth)
1036          throws LDAPException
1037  {
1038    for (final String urlString : referralResult.getReferralURLs())
1039    {
1040      try
1041      {
1042        final LDAPURL referralURL = new LDAPURL(urlString);
1043        final String host = referralURL.getHost();
1044
1045        if (host == null)
1046        {
1047          // We can't handle a referral in which there is no host.
1048          continue;
1049        }
1050
1051        final ModifyRequest modifyRequest;
1052        if (referralURL.baseDNProvided())
1053        {
1054          modifyRequest = new ModifyRequest(referralURL.getBaseDN(),
1055                                            modifications, getControls());
1056        }
1057        else
1058        {
1059          modifyRequest = this;
1060        }
1061
1062        final LDAPConnection referralConn = connection.getReferralConnector().
1063             getReferralConnection(referralURL, connection);
1064        try
1065        {
1066          return modifyRequest.process(referralConn, depth+1);
1067        }
1068        finally
1069        {
1070          referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1071          referralConn.close();
1072        }
1073      }
1074      catch (LDAPException le)
1075      {
1076        debugException(le);
1077      }
1078    }
1079
1080    // If we've gotten here, then we could not follow any of the referral URLs,
1081    // so we'll just return the original referral result.
1082    return referralResult;
1083  }
1084
1085
1086
1087  /**
1088   * {@inheritDoc}
1089   */
1090  @InternalUseOnly()
1091  public void responseReceived(final LDAPResponse response)
1092         throws LDAPException
1093  {
1094    try
1095    {
1096      responseQueue.put(response);
1097    }
1098    catch (Exception e)
1099    {
1100      debugException(e);
1101      throw new LDAPException(ResultCode.LOCAL_ERROR,
1102           ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
1103    }
1104  }
1105
1106
1107
1108  /**
1109   * {@inheritDoc}
1110   */
1111  @Override()
1112  public int getLastMessageID()
1113  {
1114    return messageID;
1115  }
1116
1117
1118
1119  /**
1120   * {@inheritDoc}
1121   */
1122  @Override()
1123  public OperationType getOperationType()
1124  {
1125    return OperationType.MODIFY;
1126  }
1127
1128
1129
1130  /**
1131   * {@inheritDoc}
1132   */
1133  public ModifyRequest duplicate()
1134  {
1135    return duplicate(getControls());
1136  }
1137
1138
1139
1140  /**
1141   * {@inheritDoc}
1142   */
1143  public ModifyRequest duplicate(final Control[] controls)
1144  {
1145    final ModifyRequest r = new ModifyRequest(dn,
1146         new ArrayList<Modification>(modifications), controls);
1147
1148    if (followReferralsInternal() != null)
1149    {
1150      r.setFollowReferrals(followReferralsInternal());
1151    }
1152
1153    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1154
1155    return r;
1156  }
1157
1158
1159
1160  /**
1161   * {@inheritDoc}
1162   */
1163  public LDIFModifyChangeRecord toLDIFChangeRecord()
1164  {
1165    return new LDIFModifyChangeRecord(this);
1166  }
1167
1168
1169
1170  /**
1171   * {@inheritDoc}
1172   */
1173  public String[] toLDIF()
1174  {
1175    return toLDIFChangeRecord().toLDIF();
1176  }
1177
1178
1179
1180  /**
1181   * {@inheritDoc}
1182   */
1183  public String toLDIFString()
1184  {
1185    return toLDIFChangeRecord().toLDIFString();
1186  }
1187
1188
1189
1190  /**
1191   * {@inheritDoc}
1192   */
1193  @Override()
1194  public void toString(final StringBuilder buffer)
1195  {
1196    buffer.append("ModifyRequest(dn='");
1197    buffer.append(dn);
1198    buffer.append("', mods={");
1199    for (int i=0; i < modifications.size(); i++)
1200    {
1201      final Modification m = modifications.get(i);
1202
1203      if (i > 0)
1204      {
1205        buffer.append(", ");
1206      }
1207
1208      switch (m.getModificationType().intValue())
1209      {
1210        case 0:
1211          buffer.append("ADD ");
1212          break;
1213
1214        case 1:
1215          buffer.append("DELETE ");
1216          break;
1217
1218        case 2:
1219          buffer.append("REPLACE ");
1220          break;
1221
1222        case 3:
1223          buffer.append("INCREMENT ");
1224          break;
1225      }
1226
1227      buffer.append(m.getAttributeName());
1228    }
1229    buffer.append('}');
1230
1231    final Control[] controls = getControls();
1232    if (controls.length > 0)
1233    {
1234      buffer.append(", controls={");
1235      for (int i=0; i < controls.length; i++)
1236      {
1237        if (i > 0)
1238        {
1239          buffer.append(", ");
1240        }
1241
1242        buffer.append(controls[i]);
1243      }
1244      buffer.append('}');
1245    }
1246
1247    buffer.append(')');
1248  }
1249
1250
1251
1252  /**
1253   * {@inheritDoc}
1254   */
1255  public void toCode(final List<String> lineList, final String requestID,
1256                     final int indentSpaces, final boolean includeProcessing)
1257  {
1258    // Create the request variable.
1259    final ArrayList<ToCodeArgHelper> constructorArgs =
1260         new ArrayList<ToCodeArgHelper>(modifications.size() + 1);
1261    constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN"));
1262
1263    boolean firstMod = true;
1264    for (final Modification m : modifications)
1265    {
1266      final String comment;
1267      if (firstMod)
1268      {
1269        firstMod = false;
1270        comment = "Modifications";
1271      }
1272      else
1273      {
1274        comment = null;
1275      }
1276
1277      constructorArgs.add(ToCodeArgHelper.createModification(m, comment));
1278    }
1279
1280    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyRequest",
1281         requestID + "Request", "new ModifyRequest", constructorArgs);
1282
1283
1284    // If there are any controls, then add them to the request.
1285    for (final Control c : getControls())
1286    {
1287      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1288           requestID + "Request.addControl",
1289           ToCodeArgHelper.createControl(c, null));
1290    }
1291
1292
1293    // Add lines for processing the request and obtaining the result.
1294    if (includeProcessing)
1295    {
1296      // Generate a string with the appropriate indent.
1297      final StringBuilder buffer = new StringBuilder();
1298      for (int i=0; i < indentSpaces; i++)
1299      {
1300        buffer.append(' ');
1301      }
1302      final String indent = buffer.toString();
1303
1304      lineList.add("");
1305      lineList.add(indent + "try");
1306      lineList.add(indent + '{');
1307      lineList.add(indent + "  LDAPResult " + requestID +
1308           "Result = connection.modify(" + requestID + "Request);");
1309      lineList.add(indent + "  // The modify was processed successfully.");
1310      lineList.add(indent + '}');
1311      lineList.add(indent + "catch (LDAPException e)");
1312      lineList.add(indent + '{');
1313      lineList.add(indent + "  // The modify failed.  Maybe the following " +
1314           "will help explain why.");
1315      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
1316      lineList.add(indent + "  String message = e.getMessage();");
1317      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
1318      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
1319      lineList.add(indent + "  Control[] responseControls = " +
1320           "e.getResponseControls();");
1321      lineList.add(indent + '}');
1322    }
1323  }
1324}