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