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