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