001    /*
002     * Copyright 2007-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2016 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            throw new LDAPException(ResultCode.LOCAL_ERROR,
646                 ERR_MODDN_INTERRUPTED.get(connection.getHostPort()), ie);
647          }
648    
649          return handleResponse(connection, response, requestTime, depth, false);
650        }
651        finally
652        {
653          connection.deregisterResponseAcceptor(messageID);
654        }
655      }
656    
657    
658    
659      /**
660       * Sends this modify DN request to the directory server over the provided
661       * connection and returns the message ID for the request.
662       *
663       * @param  connection      The connection to use to communicate with the
664       *                         directory server.
665       * @param  resultListener  The async result listener that is to be notified
666       *                         when the response is received.  It may be
667       *                         {@code null} only if the result is to be processed
668       *                         by this class.
669       *
670       * @return  The async request ID created for the operation, or {@code null} if
671       *          the provided {@code resultListener} is {@code null} and the
672       *          operation will not actually be processed asynchronously.
673       *
674       * @throws  LDAPException  If a problem occurs while sending the request.
675       */
676      AsyncRequestID processAsync(final LDAPConnection connection,
677                                  final AsyncResultListener resultListener)
678                     throws LDAPException
679      {
680        // Create the LDAP message.
681        messageID = connection.nextMessageID();
682        final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
683    
684    
685        // If the provided async result listener is {@code null}, then we'll use
686        // this class as the message acceptor.  Otherwise, create an async helper
687        // and use it as the message acceptor.
688        final AsyncRequestID asyncRequestID;
689        if (resultListener == null)
690        {
691          asyncRequestID = null;
692          connection.registerResponseAcceptor(messageID, this);
693        }
694        else
695        {
696          final AsyncHelper helper = new AsyncHelper(connection,
697               OperationType.MODIFY_DN, messageID, resultListener,
698               getIntermediateResponseListener());
699          connection.registerResponseAcceptor(messageID, helper);
700          asyncRequestID = helper.getAsyncRequestID();
701    
702          final long timeout = getResponseTimeoutMillis(connection);
703          if (timeout > 0L)
704          {
705            final Timer timer = connection.getTimer();
706            final AsyncTimeoutTimerTask timerTask =
707                 new AsyncTimeoutTimerTask(helper);
708            timer.schedule(timerTask, timeout);
709            asyncRequestID.setTimerTask(timerTask);
710          }
711        }
712    
713    
714        // Send the request to the server.
715        try
716        {
717          debugLDAPRequest(this);
718          connection.getConnectionStatistics().incrementNumModifyDNRequests();
719          connection.sendMessage(message);
720          return asyncRequestID;
721        }
722        catch (LDAPException le)
723        {
724          debugException(le);
725    
726          connection.deregisterResponseAcceptor(messageID);
727          throw le;
728        }
729      }
730    
731    
732    
733      /**
734       * Processes this modify DN operation in synchronous mode, in which the same
735       * thread will send the request and read the response.
736       *
737       * @param  connection  The connection to use to communicate with the directory
738       *                     server.
739       * @param  depth       The current referral depth for this request.  It should
740       *                     always be one for the initial request, and should only
741       *                     be incremented when following referrals.
742       * @param  allowRetry  Indicates whether the request may be re-tried on a
743       *                     re-established connection if the initial attempt fails
744       *                     in a way that indicates the connection is no longer
745       *                     valid and autoReconnect is true.
746       *
747       * @return  An LDAP result object that provides information about the result
748       *          of the modify DN processing.
749       *
750       * @throws  LDAPException  If a problem occurs while sending the request or
751       *                         reading the response.
752       */
753      private LDAPResult processSync(final LDAPConnection connection,
754                                     final int depth,
755                                     final boolean allowRetry)
756              throws LDAPException
757      {
758        // Create the LDAP message.
759        messageID = connection.nextMessageID();
760        final LDAPMessage message =
761             new LDAPMessage(messageID,  this, getControls());
762    
763    
764        // Set the appropriate timeout on the socket.
765        try
766        {
767          connection.getConnectionInternals(true).getSocket().setSoTimeout(
768               (int) getResponseTimeoutMillis(connection));
769        }
770        catch (Exception e)
771        {
772          debugException(e);
773        }
774    
775    
776        // Send the request to the server.
777        final long requestTime = System.nanoTime();
778        debugLDAPRequest(this);
779        connection.getConnectionStatistics().incrementNumModifyDNRequests();
780        try
781        {
782          connection.sendMessage(message);
783        }
784        catch (final LDAPException le)
785        {
786          debugException(le);
787    
788          if (allowRetry)
789          {
790            final LDAPResult retryResult = reconnectAndRetry(connection, depth,
791                 le.getResultCode());
792            if (retryResult != null)
793            {
794              return retryResult;
795            }
796          }
797    
798          throw le;
799        }
800    
801        while (true)
802        {
803          final LDAPResponse response;
804          try
805          {
806            response = connection.readResponse(messageID);
807          }
808          catch (final LDAPException le)
809          {
810            debugException(le);
811    
812            if ((le.getResultCode() == ResultCode.TIMEOUT) &&
813                connection.getConnectionOptions().abandonOnTimeout())
814            {
815              connection.abandon(messageID);
816            }
817    
818            if (allowRetry)
819            {
820              final LDAPResult retryResult = reconnectAndRetry(connection, depth,
821                   le.getResultCode());
822              if (retryResult != null)
823              {
824                return retryResult;
825              }
826            }
827    
828            throw le;
829          }
830    
831          if (response instanceof IntermediateResponse)
832          {
833            final IntermediateResponseListener listener =
834                 getIntermediateResponseListener();
835            if (listener != null)
836            {
837              listener.intermediateResponseReturned(
838                   (IntermediateResponse) response);
839            }
840          }
841          else
842          {
843            return handleResponse(connection, response, requestTime, depth,
844                 allowRetry);
845          }
846        }
847      }
848    
849    
850    
851      /**
852       * Performs the necessary processing for handling a response.
853       *
854       * @param  connection   The connection used to read the response.
855       * @param  response     The response to be processed.
856       * @param  requestTime  The time the request was sent to the server.
857       * @param  depth        The current referral depth for this request.  It
858       *                      should always be one for the initial request, and
859       *                      should only be incremented when following referrals.
860       * @param  allowRetry   Indicates whether the request may be re-tried on a
861       *                      re-established connection if the initial attempt fails
862       *                      in a way that indicates the connection is no longer
863       *                      valid and autoReconnect is true.
864       *
865       * @return  The modify DN result.
866       *
867       * @throws  LDAPException  If a problem occurs.
868       */
869      private LDAPResult handleResponse(final LDAPConnection connection,
870                                        final LDAPResponse response,
871                                        final long requestTime, final int depth,
872                                        final boolean allowRetry)
873              throws LDAPException
874      {
875        if (response == null)
876        {
877          final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
878          if (connection.getConnectionOptions().abandonOnTimeout())
879          {
880            connection.abandon(messageID);
881          }
882    
883          throw new LDAPException(ResultCode.TIMEOUT,
884               ERR_MODIFY_DN_CLIENT_TIMEOUT.get(waitTime, messageID, dn,
885                    connection.getHostPort()));
886        }
887    
888        connection.getConnectionStatistics().incrementNumModifyDNResponses(
889             System.nanoTime() - requestTime);
890        if (response instanceof ConnectionClosedResponse)
891        {
892          // The connection was closed while waiting for the response.
893          if (allowRetry)
894          {
895            final LDAPResult retryResult = reconnectAndRetry(connection, depth,
896                 ResultCode.SERVER_DOWN);
897            if (retryResult != null)
898            {
899              return retryResult;
900            }
901          }
902    
903          final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
904          final String message = ccr.getMessage();
905          if (message == null)
906          {
907            throw new LDAPException(ccr.getResultCode(),
908                 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE.get(
909                      connection.getHostPort(), toString()));
910          }
911          else
912          {
913            throw new LDAPException(ccr.getResultCode(),
914                 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE_WITH_MESSAGE.get(
915                      connection.getHostPort(), toString(), message));
916          }
917        }
918    
919        final LDAPResult result = (LDAPResult) response;
920        if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
921            followReferrals(connection))
922        {
923          if (depth >= connection.getConnectionOptions().getReferralHopLimit())
924          {
925            return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED,
926                                  ERR_TOO_MANY_REFERRALS.get(),
927                                  result.getMatchedDN(), result.getReferralURLs(),
928                                  result.getResponseControls());
929          }
930    
931          return followReferral(result, connection, depth);
932        }
933        else
934        {
935          if (allowRetry)
936          {
937            final LDAPResult retryResult = reconnectAndRetry(connection, depth,
938                 result.getResultCode());
939            if (retryResult != null)
940            {
941              return retryResult;
942            }
943          }
944    
945          return result;
946        }
947      }
948    
949    
950    
951      /**
952       * Attempts to re-establish the connection and retry processing this request
953       * on it.
954       *
955       * @param  connection  The connection to be re-established.
956       * @param  depth       The current referral depth for this request.  It should
957       *                     always be one for the initial request, and should only
958       *                     be incremented when following referrals.
959       * @param  resultCode  The result code for the previous operation attempt.
960       *
961       * @return  The result from re-trying the add, or {@code null} if it could not
962       *          be re-tried.
963       */
964      private LDAPResult reconnectAndRetry(final LDAPConnection connection,
965                                           final int depth,
966                                           final ResultCode resultCode)
967      {
968        try
969        {
970          // We will only want to retry for certain result codes that indicate a
971          // connection problem.
972          switch (resultCode.intValue())
973          {
974            case ResultCode.SERVER_DOWN_INT_VALUE:
975            case ResultCode.DECODING_ERROR_INT_VALUE:
976            case ResultCode.CONNECT_ERROR_INT_VALUE:
977              connection.reconnect();
978              return processSync(connection, depth, false);
979          }
980        }
981        catch (final Exception e)
982        {
983          debugException(e);
984        }
985    
986        return null;
987      }
988    
989    
990    
991      /**
992       * Attempts to follow a referral to perform a modify DN operation in the
993       * target server.
994       *
995       * @param  referralResult  The LDAP result object containing information about
996       *                         the referral to follow.
997       * @param  connection      The connection on which the referral was received.
998       * @param  depth           The number of referrals followed in the course of
999       *                         processing this request.
1000       *
1001       * @return  The result of attempting to process the modify DN operation by
1002       *          following the referral.
1003       *
1004       * @throws  LDAPException  If a problem occurs while attempting to establish
1005       *                         the referral connection, sending the request, or
1006       *                         reading the result.
1007       */
1008      private LDAPResult followReferral(final LDAPResult referralResult,
1009                                        final LDAPConnection connection,
1010                                        final int depth)
1011              throws LDAPException
1012      {
1013        for (final String urlString : referralResult.getReferralURLs())
1014        {
1015          try
1016          {
1017            final LDAPURL referralURL = new LDAPURL(urlString);
1018            final String host = referralURL.getHost();
1019    
1020            if (host == null)
1021            {
1022              // We can't handle a referral in which there is no host.
1023              continue;
1024            }
1025    
1026            final ModifyDNRequest modifyDNRequest;
1027            if (referralURL.baseDNProvided())
1028            {
1029              modifyDNRequest =
1030                   new ModifyDNRequest(referralURL.getBaseDN().toString(),
1031                                       newRDN, deleteOldRDN, newSuperiorDN,
1032                                       getControls());
1033            }
1034            else
1035            {
1036              modifyDNRequest = this;
1037            }
1038    
1039            final LDAPConnection referralConn = connection.getReferralConnector().
1040                 getReferralConnection(referralURL, connection);
1041            try
1042            {
1043              return modifyDNRequest.process(referralConn, depth+1);
1044            }
1045            finally
1046            {
1047              referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1048              referralConn.close();
1049            }
1050          }
1051          catch (LDAPException le)
1052          {
1053            debugException(le);
1054          }
1055        }
1056    
1057        // If we've gotten here, then we could not follow any of the referral URLs,
1058        // so we'll just return the original referral result.
1059        return referralResult;
1060      }
1061    
1062    
1063    
1064      /**
1065       * {@inheritDoc}
1066       */
1067      @InternalUseOnly()
1068      public void responseReceived(final LDAPResponse response)
1069             throws LDAPException
1070      {
1071        try
1072        {
1073          responseQueue.put(response);
1074        }
1075        catch (Exception e)
1076        {
1077          debugException(e);
1078          throw new LDAPException(ResultCode.LOCAL_ERROR,
1079               ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
1080        }
1081      }
1082    
1083    
1084    
1085      /**
1086       * {@inheritDoc}
1087       */
1088      @Override()
1089      public int getLastMessageID()
1090      {
1091        return messageID;
1092      }
1093    
1094    
1095    
1096      /**
1097       * {@inheritDoc}
1098       */
1099      @Override()
1100      public OperationType getOperationType()
1101      {
1102        return OperationType.MODIFY_DN;
1103      }
1104    
1105    
1106    
1107      /**
1108       * {@inheritDoc}
1109       */
1110      public ModifyDNRequest duplicate()
1111      {
1112        return duplicate(getControls());
1113      }
1114    
1115    
1116    
1117      /**
1118       * {@inheritDoc}
1119       */
1120      public ModifyDNRequest duplicate(final Control[] controls)
1121      {
1122        final ModifyDNRequest r = new ModifyDNRequest(dn, newRDN, deleteOldRDN,
1123             newSuperiorDN, controls);
1124    
1125        if (followReferralsInternal() != null)
1126        {
1127          r.setFollowReferrals(followReferralsInternal());
1128        }
1129    
1130        r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1131    
1132        return r;
1133      }
1134    
1135    
1136    
1137      /**
1138       * {@inheritDoc}
1139       */
1140      public LDIFModifyDNChangeRecord toLDIFChangeRecord()
1141      {
1142        return new LDIFModifyDNChangeRecord(this);
1143      }
1144    
1145    
1146    
1147      /**
1148       * {@inheritDoc}
1149       */
1150      public String[] toLDIF()
1151      {
1152        return toLDIFChangeRecord().toLDIF();
1153      }
1154    
1155    
1156    
1157      /**
1158       * {@inheritDoc}
1159       */
1160      public String toLDIFString()
1161      {
1162        return toLDIFChangeRecord().toLDIFString();
1163      }
1164    
1165    
1166    
1167      /**
1168       * {@inheritDoc}
1169       */
1170      @Override()
1171      public void toString(final StringBuilder buffer)
1172      {
1173        buffer.append("ModifyDNRequest(dn='");
1174        buffer.append(dn);
1175        buffer.append("', newRDN='");
1176        buffer.append(newRDN);
1177        buffer.append("', deleteOldRDN=");
1178        buffer.append(deleteOldRDN);
1179    
1180        if (newSuperiorDN != null)
1181        {
1182          buffer.append(", newSuperiorDN='");
1183          buffer.append(newSuperiorDN);
1184          buffer.append('\'');
1185        }
1186    
1187        final Control[] controls = getControls();
1188        if (controls.length > 0)
1189        {
1190          buffer.append(", controls={");
1191          for (int i=0; i < controls.length; i++)
1192          {
1193            if (i > 0)
1194            {
1195              buffer.append(", ");
1196            }
1197    
1198            buffer.append(controls[i]);
1199          }
1200          buffer.append('}');
1201        }
1202    
1203        buffer.append(')');
1204      }
1205    
1206    
1207    
1208      /**
1209       * {@inheritDoc}
1210       */
1211      public void toCode(final List<String> lineList, final String requestID,
1212                         final int indentSpaces, final boolean includeProcessing)
1213      {
1214        // Create the request variable.
1215        final ArrayList<ToCodeArgHelper> constructorArgs =
1216             new ArrayList<ToCodeArgHelper>(4);
1217        constructorArgs.add(ToCodeArgHelper.createString(dn, "Current DN"));
1218        constructorArgs.add(ToCodeArgHelper.createString(newRDN, "New RDN"));
1219        constructorArgs.add(ToCodeArgHelper.createBoolean(deleteOldRDN,
1220             "Delete Old RDN Value(s)"));
1221    
1222        if (newSuperiorDN != null)
1223        {
1224          constructorArgs.add(ToCodeArgHelper.createString(newSuperiorDN,
1225               "New Superior Entry DN"));
1226        }
1227    
1228        ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyDNRequest",
1229             requestID + "Request", "new ModifyDNRequest", constructorArgs);
1230    
1231    
1232        // If there are any controls, then add them to the request.
1233        for (final Control c : getControls())
1234        {
1235          ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1236               requestID + "Request.addControl",
1237               ToCodeArgHelper.createControl(c, null));
1238        }
1239    
1240    
1241        // Add lines for processing the request and obtaining the result.
1242        if (includeProcessing)
1243        {
1244          // Generate a string with the appropriate indent.
1245          final StringBuilder buffer = new StringBuilder();
1246          for (int i=0; i < indentSpaces; i++)
1247          {
1248            buffer.append(' ');
1249          }
1250          final String indent = buffer.toString();
1251    
1252          lineList.add("");
1253          lineList.add(indent + "try");
1254          lineList.add(indent + '{');
1255          lineList.add(indent + "  LDAPResult " + requestID +
1256               "Result = connection.modifyDN(" + requestID + "Request);");
1257          lineList.add(indent + "  // The modify DN was processed successfully.");
1258          lineList.add(indent + '}');
1259          lineList.add(indent + "catch (LDAPException e)");
1260          lineList.add(indent + '{');
1261          lineList.add(indent + "  // The modify DN failed.  Maybe the following " +
1262               "will help explain why.");
1263          lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
1264          lineList.add(indent + "  String message = e.getMessage();");
1265          lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
1266          lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
1267          lineList.add(indent + "  Control[] responseControls = " +
1268               "e.getResponseControls();");
1269          lineList.add(indent + '}');
1270        }
1271      }
1272    }