001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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        Thread.currentThread().interrupt();
672        throw new LDAPException(ResultCode.LOCAL_ERROR,
673             ERR_MODIFY_INTERRUPTED.get(connection.getHostPort()), ie);
674      }
675
676      return handleResponse(connection, response, requestTime, depth, false);
677    }
678    finally
679    {
680      connection.deregisterResponseAcceptor(messageID);
681    }
682  }
683
684
685
686  /**
687   * Sends this modify request to the directory server over the provided
688   * connection and returns the message ID for the request.
689   *
690   * @param  connection      The connection to use to communicate with the
691   *                         directory server.
692   * @param  resultListener  The async result listener that is to be notified
693   *                         when the response is received.  It may be
694   *                         {@code null} only if the result is to be processed
695   *                         by this class.
696   *
697   * @return  The async request ID created for the operation, or {@code null} if
698   *          the provided {@code resultListener} is {@code null} and the
699   *          operation will not actually be processed asynchronously.
700   *
701   * @throws  LDAPException  If a problem occurs while sending the request.
702   */
703  AsyncRequestID processAsync(final LDAPConnection connection,
704                              final AsyncResultListener resultListener)
705                 throws LDAPException
706  {
707    // Create the LDAP message.
708    messageID = connection.nextMessageID();
709    final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
710
711
712    // If the provided async result listener is {@code null}, then we'll use
713    // this class as the message acceptor.  Otherwise, create an async helper
714    // and use it as the message acceptor.
715    final AsyncRequestID asyncRequestID;
716    if (resultListener == null)
717    {
718      asyncRequestID = null;
719      connection.registerResponseAcceptor(messageID, this);
720    }
721    else
722    {
723      final AsyncHelper helper = new AsyncHelper(connection,
724           OperationType.MODIFY, messageID, resultListener,
725           getIntermediateResponseListener());
726      connection.registerResponseAcceptor(messageID, helper);
727      asyncRequestID = helper.getAsyncRequestID();
728
729      final long timeout = getResponseTimeoutMillis(connection);
730      if (timeout > 0L)
731      {
732        final Timer timer = connection.getTimer();
733        final AsyncTimeoutTimerTask timerTask =
734             new AsyncTimeoutTimerTask(helper);
735        timer.schedule(timerTask, timeout);
736        asyncRequestID.setTimerTask(timerTask);
737      }
738    }
739
740
741    // Send the request to the server.
742    try
743    {
744      debugLDAPRequest(this);
745      connection.getConnectionStatistics().incrementNumModifyRequests();
746      connection.sendMessage(message);
747      return asyncRequestID;
748    }
749    catch (LDAPException le)
750    {
751      debugException(le);
752
753      connection.deregisterResponseAcceptor(messageID);
754      throw le;
755    }
756  }
757
758
759
760  /**
761   * Processes this modify operation in synchronous mode, in which the same
762   * thread will send the request and read the response.
763   *
764   * @param  connection  The connection to use to communicate with the directory
765   *                     server.
766   * @param  depth       The current referral depth for this request.  It should
767   *                     always be one for the initial request, and should only
768   *                     be incremented when following referrals.
769   * @param  allowRetry  Indicates whether the request may be re-tried on a
770   *                     re-established connection if the initial attempt fails
771   *                     in a way that indicates the connection is no longer
772   *                     valid and autoReconnect is true.
773   *
774   * @return  An LDAP result object that provides information about the result
775   *          of the modify processing.
776   *
777   * @throws  LDAPException  If a problem occurs while sending the request or
778   *                         reading the response.
779   */
780  private LDAPResult processSync(final LDAPConnection connection,
781                                 final int depth, final boolean allowRetry)
782          throws LDAPException
783  {
784    // Create the LDAP message.
785    messageID = connection.nextMessageID();
786    final LDAPMessage message =
787         new LDAPMessage(messageID,  this, getControls());
788
789
790    // Set the appropriate timeout on the socket.
791    try
792    {
793      connection.getConnectionInternals(true).getSocket().setSoTimeout(
794           (int) getResponseTimeoutMillis(connection));
795    }
796    catch (Exception e)
797    {
798      debugException(e);
799    }
800
801
802    // Send the request to the server.
803    final long requestTime = System.nanoTime();
804    debugLDAPRequest(this);
805    connection.getConnectionStatistics().incrementNumModifyRequests();
806    try
807    {
808      connection.sendMessage(message);
809    }
810    catch (final LDAPException le)
811    {
812      debugException(le);
813
814      if (allowRetry)
815      {
816        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
817             le.getResultCode());
818        if (retryResult != null)
819        {
820          return retryResult;
821        }
822      }
823
824      throw le;
825    }
826
827    while (true)
828    {
829      final LDAPResponse response;
830      try
831      {
832        response = connection.readResponse(messageID);
833      }
834      catch (final LDAPException le)
835      {
836        debugException(le);
837
838        if ((le.getResultCode() == ResultCode.TIMEOUT) &&
839            connection.getConnectionOptions().abandonOnTimeout())
840        {
841          connection.abandon(messageID);
842        }
843
844        if (allowRetry)
845        {
846          final LDAPResult retryResult = reconnectAndRetry(connection, depth,
847               le.getResultCode());
848          if (retryResult != null)
849          {
850            return retryResult;
851          }
852        }
853
854        throw le;
855      }
856
857      if (response instanceof IntermediateResponse)
858      {
859        final IntermediateResponseListener listener =
860             getIntermediateResponseListener();
861        if (listener != null)
862        {
863          listener.intermediateResponseReturned(
864               (IntermediateResponse) response);
865        }
866      }
867      else
868      {
869        return handleResponse(connection, response, requestTime, depth,
870             allowRetry);
871      }
872    }
873  }
874
875
876
877  /**
878   * Performs the necessary processing for handling a response.
879   *
880   * @param  connection   The connection used to read the response.
881   * @param  response     The response to be processed.
882   * @param  requestTime  The time the request was sent to the server.
883   * @param  depth        The current referral depth for this request.  It
884   *                      should always be one for the initial request, and
885   *                      should only be incremented when following referrals.
886   * @param  allowRetry   Indicates whether the request may be re-tried on a
887   *                      re-established connection if the initial attempt fails
888   *                      in a way that indicates the connection is no longer
889   *                      valid and autoReconnect is true.
890   *
891   * @return  The modify result.
892   *
893   * @throws  LDAPException  If a problem occurs.
894   */
895  private LDAPResult handleResponse(final LDAPConnection connection,
896                                    final LDAPResponse response,
897                                    final long requestTime, final int depth,
898                                    final boolean allowRetry)
899          throws LDAPException
900  {
901    if (response == null)
902    {
903      final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
904      if (connection.getConnectionOptions().abandonOnTimeout())
905      {
906        connection.abandon(messageID);
907      }
908
909      throw new LDAPException(ResultCode.TIMEOUT,
910           ERR_MODIFY_CLIENT_TIMEOUT.get(waitTime, messageID, dn,
911                connection.getHostPort()));
912    }
913
914    connection.getConnectionStatistics().incrementNumModifyResponses(
915         System.nanoTime() - requestTime);
916    if (response instanceof ConnectionClosedResponse)
917    {
918      // The connection was closed while waiting for the response.
919      if (allowRetry)
920      {
921        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
922             ResultCode.SERVER_DOWN);
923        if (retryResult != null)
924        {
925          return retryResult;
926        }
927      }
928
929      final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
930      final String message = ccr.getMessage();
931      if (message == null)
932      {
933        throw new LDAPException(ccr.getResultCode(),
934             ERR_CONN_CLOSED_WAITING_FOR_MODIFY_RESPONSE.get(
935                  connection.getHostPort(), toString()));
936      }
937      else
938      {
939        throw new LDAPException(ccr.getResultCode(),
940             ERR_CONN_CLOSED_WAITING_FOR_MODIFY_RESPONSE_WITH_MESSAGE.get(
941                  connection.getHostPort(), toString(), message));
942      }
943    }
944
945    final LDAPResult result = (LDAPResult) response;
946    if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
947        followReferrals(connection))
948    {
949      if (depth >= connection.getConnectionOptions().getReferralHopLimit())
950      {
951        return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED,
952                              ERR_TOO_MANY_REFERRALS.get(),
953                              result.getMatchedDN(), result.getReferralURLs(),
954                              result.getResponseControls());
955      }
956
957      return followReferral(result, connection, depth);
958    }
959    else
960    {
961      if (allowRetry)
962      {
963        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
964             result.getResultCode());
965        if (retryResult != null)
966        {
967          return retryResult;
968        }
969      }
970
971      return result;
972    }
973  }
974
975
976
977  /**
978   * Attempts to re-establish the connection and retry processing this request
979   * on it.
980   *
981   * @param  connection  The connection to be re-established.
982   * @param  depth       The current referral depth for this request.  It should
983   *                     always be one for the initial request, and should only
984   *                     be incremented when following referrals.
985   * @param  resultCode  The result code for the previous operation attempt.
986   *
987   * @return  The result from re-trying the add, or {@code null} if it could not
988   *          be re-tried.
989   */
990  private LDAPResult reconnectAndRetry(final LDAPConnection connection,
991                                       final int depth,
992                                       final ResultCode resultCode)
993  {
994    try
995    {
996      // We will only want to retry for certain result codes that indicate a
997      // connection problem.
998      switch (resultCode.intValue())
999      {
1000        case ResultCode.SERVER_DOWN_INT_VALUE:
1001        case ResultCode.DECODING_ERROR_INT_VALUE:
1002        case ResultCode.CONNECT_ERROR_INT_VALUE:
1003          connection.reconnect();
1004          return processSync(connection, depth, false);
1005      }
1006    }
1007    catch (final Exception e)
1008    {
1009      debugException(e);
1010    }
1011
1012    return null;
1013  }
1014
1015
1016
1017  /**
1018   * Attempts to follow a referral to perform a modify operation in the target
1019   * server.
1020   *
1021   * @param  referralResult  The LDAP result object containing information about
1022   *                         the referral to follow.
1023   * @param  connection      The connection on which the referral was received.
1024   * @param  depth           The number of referrals followed in the course of
1025   *                         processing this request.
1026   *
1027   * @return  The result of attempting to process the modify operation by
1028   *          following the referral.
1029   *
1030   * @throws  LDAPException  If a problem occurs while attempting to establish
1031   *                         the referral connection, sending the request, or
1032   *                         reading the result.
1033   */
1034  private LDAPResult followReferral(final LDAPResult referralResult,
1035                                    final LDAPConnection connection,
1036                                    final int depth)
1037          throws LDAPException
1038  {
1039    for (final String urlString : referralResult.getReferralURLs())
1040    {
1041      try
1042      {
1043        final LDAPURL referralURL = new LDAPURL(urlString);
1044        final String host = referralURL.getHost();
1045
1046        if (host == null)
1047        {
1048          // We can't handle a referral in which there is no host.
1049          continue;
1050        }
1051
1052        final ModifyRequest modifyRequest;
1053        if (referralURL.baseDNProvided())
1054        {
1055          modifyRequest = new ModifyRequest(referralURL.getBaseDN(),
1056                                            modifications, getControls());
1057        }
1058        else
1059        {
1060          modifyRequest = this;
1061        }
1062
1063        final LDAPConnection referralConn = connection.getReferralConnector().
1064             getReferralConnection(referralURL, connection);
1065        try
1066        {
1067          return modifyRequest.process(referralConn, depth+1);
1068        }
1069        finally
1070        {
1071          referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1072          referralConn.close();
1073        }
1074      }
1075      catch (LDAPException le)
1076      {
1077        debugException(le);
1078      }
1079    }
1080
1081    // If we've gotten here, then we could not follow any of the referral URLs,
1082    // so we'll just return the original referral result.
1083    return referralResult;
1084  }
1085
1086
1087
1088  /**
1089   * {@inheritDoc}
1090   */
1091  @InternalUseOnly()
1092  public void responseReceived(final LDAPResponse response)
1093         throws LDAPException
1094  {
1095    try
1096    {
1097      responseQueue.put(response);
1098    }
1099    catch (Exception e)
1100    {
1101      debugException(e);
1102
1103      if (e instanceof InterruptedException)
1104      {
1105        Thread.currentThread().interrupt();
1106      }
1107
1108      throw new LDAPException(ResultCode.LOCAL_ERROR,
1109           ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
1110    }
1111  }
1112
1113
1114
1115  /**
1116   * {@inheritDoc}
1117   */
1118  @Override()
1119  public int getLastMessageID()
1120  {
1121    return messageID;
1122  }
1123
1124
1125
1126  /**
1127   * {@inheritDoc}
1128   */
1129  @Override()
1130  public OperationType getOperationType()
1131  {
1132    return OperationType.MODIFY;
1133  }
1134
1135
1136
1137  /**
1138   * {@inheritDoc}
1139   */
1140  public ModifyRequest duplicate()
1141  {
1142    return duplicate(getControls());
1143  }
1144
1145
1146
1147  /**
1148   * {@inheritDoc}
1149   */
1150  public ModifyRequest duplicate(final Control[] controls)
1151  {
1152    final ModifyRequest r = new ModifyRequest(dn,
1153         new ArrayList<Modification>(modifications), controls);
1154
1155    if (followReferralsInternal() != null)
1156    {
1157      r.setFollowReferrals(followReferralsInternal());
1158    }
1159
1160    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1161
1162    return r;
1163  }
1164
1165
1166
1167  /**
1168   * {@inheritDoc}
1169   */
1170  public LDIFModifyChangeRecord toLDIFChangeRecord()
1171  {
1172    return new LDIFModifyChangeRecord(this);
1173  }
1174
1175
1176
1177  /**
1178   * {@inheritDoc}
1179   */
1180  public String[] toLDIF()
1181  {
1182    return toLDIFChangeRecord().toLDIF();
1183  }
1184
1185
1186
1187  /**
1188   * {@inheritDoc}
1189   */
1190  public String toLDIFString()
1191  {
1192    return toLDIFChangeRecord().toLDIFString();
1193  }
1194
1195
1196
1197  /**
1198   * {@inheritDoc}
1199   */
1200  @Override()
1201  public void toString(final StringBuilder buffer)
1202  {
1203    buffer.append("ModifyRequest(dn='");
1204    buffer.append(dn);
1205    buffer.append("', mods={");
1206    for (int i=0; i < modifications.size(); i++)
1207    {
1208      final Modification m = modifications.get(i);
1209
1210      if (i > 0)
1211      {
1212        buffer.append(", ");
1213      }
1214
1215      switch (m.getModificationType().intValue())
1216      {
1217        case 0:
1218          buffer.append("ADD ");
1219          break;
1220
1221        case 1:
1222          buffer.append("DELETE ");
1223          break;
1224
1225        case 2:
1226          buffer.append("REPLACE ");
1227          break;
1228
1229        case 3:
1230          buffer.append("INCREMENT ");
1231          break;
1232      }
1233
1234      buffer.append(m.getAttributeName());
1235    }
1236    buffer.append('}');
1237
1238    final Control[] controls = getControls();
1239    if (controls.length > 0)
1240    {
1241      buffer.append(", controls={");
1242      for (int i=0; i < controls.length; i++)
1243      {
1244        if (i > 0)
1245        {
1246          buffer.append(", ");
1247        }
1248
1249        buffer.append(controls[i]);
1250      }
1251      buffer.append('}');
1252    }
1253
1254    buffer.append(')');
1255  }
1256
1257
1258
1259  /**
1260   * {@inheritDoc}
1261   */
1262  public void toCode(final List<String> lineList, final String requestID,
1263                     final int indentSpaces, final boolean includeProcessing)
1264  {
1265    // Create the request variable.
1266    final ArrayList<ToCodeArgHelper> constructorArgs =
1267         new ArrayList<ToCodeArgHelper>(modifications.size() + 1);
1268    constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN"));
1269
1270    boolean firstMod = true;
1271    for (final Modification m : modifications)
1272    {
1273      final String comment;
1274      if (firstMod)
1275      {
1276        firstMod = false;
1277        comment = "Modifications";
1278      }
1279      else
1280      {
1281        comment = null;
1282      }
1283
1284      constructorArgs.add(ToCodeArgHelper.createModification(m, comment));
1285    }
1286
1287    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyRequest",
1288         requestID + "Request", "new ModifyRequest", constructorArgs);
1289
1290
1291    // If there are any controls, then add them to the request.
1292    for (final Control c : getControls())
1293    {
1294      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1295           requestID + "Request.addControl",
1296           ToCodeArgHelper.createControl(c, null));
1297    }
1298
1299
1300    // Add lines for processing the request and obtaining the result.
1301    if (includeProcessing)
1302    {
1303      // Generate a string with the appropriate indent.
1304      final StringBuilder buffer = new StringBuilder();
1305      for (int i=0; i < indentSpaces; i++)
1306      {
1307        buffer.append(' ');
1308      }
1309      final String indent = buffer.toString();
1310
1311      lineList.add("");
1312      lineList.add(indent + "try");
1313      lineList.add(indent + '{');
1314      lineList.add(indent + "  LDAPResult " + requestID +
1315           "Result = connection.modify(" + requestID + "Request);");
1316      lineList.add(indent + "  // The modify was processed successfully.");
1317      lineList.add(indent + '}');
1318      lineList.add(indent + "catch (LDAPException e)");
1319      lineList.add(indent + '{');
1320      lineList.add(indent + "  // The modify failed.  Maybe the following " +
1321           "will help explain why.");
1322      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
1323      lineList.add(indent + "  String message = e.getMessage();");
1324      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
1325      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
1326      lineList.add(indent + "  Control[] responseControls = " +
1327           "e.getResponseControls();");
1328      lineList.add(indent + '}');
1329    }
1330  }
1331}