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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.util.ArrayList;
026import java.util.List;
027import java.util.Timer;
028import java.util.concurrent.LinkedBlockingQueue;
029import java.util.concurrent.TimeUnit;
030
031import com.unboundid.asn1.ASN1Boolean;
032import com.unboundid.asn1.ASN1Buffer;
033import com.unboundid.asn1.ASN1BufferSequence;
034import com.unboundid.asn1.ASN1Element;
035import com.unboundid.asn1.ASN1OctetString;
036import com.unboundid.asn1.ASN1Sequence;
037import com.unboundid.ldap.protocol.LDAPMessage;
038import com.unboundid.ldap.protocol.LDAPResponse;
039import com.unboundid.ldap.protocol.ProtocolOp;
040import com.unboundid.ldif.LDIFModifyDNChangeRecord;
041import com.unboundid.util.InternalUseOnly;
042import com.unboundid.util.Mutable;
043import com.unboundid.util.ThreadSafety;
044import com.unboundid.util.ThreadSafetyLevel;
045
046import static com.unboundid.ldap.sdk.LDAPMessages.*;
047import static com.unboundid.util.Debug.*;
048import static com.unboundid.util.StaticUtils.*;
049import 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)
100public 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}