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