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