001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk;
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    import com.unboundid.util.Mutable;
041    import com.unboundid.util.ThreadSafety;
042    import com.unboundid.util.ThreadSafetyLevel;
043    
044    import static com.unboundid.ldap.sdk.LDAPMessages.*;
045    import static com.unboundid.util.Debug.*;
046    import static com.unboundid.util.StaticUtils.*;
047    import 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)
098    public 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    }