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