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