001/*
002 * Copyright 2007-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2019 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.Debug;
043import com.unboundid.util.InternalUseOnly;
044import com.unboundid.util.Mutable;
045import com.unboundid.util.StaticUtils;
046import com.unboundid.util.ThreadSafety;
047import com.unboundid.util.ThreadSafetyLevel;
048import com.unboundid.util.Validator;
049
050import static com.unboundid.ldap.sdk.LDAPMessages.*;
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<>();
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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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        Debug.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      Debug.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      Debug.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    Debug.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      Debug.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        Debug.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 =
875           StaticUtils.nanosToMillis(System.nanoTime() - requestTime);
876      if (connection.getConnectionOptions().abandonOnTimeout())
877      {
878        connection.abandon(messageID);
879      }
880
881      throw new LDAPException(ResultCode.TIMEOUT,
882           ERR_MODIFY_DN_CLIENT_TIMEOUT.get(waitTime, messageID, dn,
883                connection.getHostPort()));
884    }
885
886    connection.getConnectionStatistics().incrementNumModifyDNResponses(
887         System.nanoTime() - requestTime);
888    if (response instanceof ConnectionClosedResponse)
889    {
890      // The connection was closed while waiting for the response.
891      if (allowRetry)
892      {
893        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
894             ResultCode.SERVER_DOWN);
895        if (retryResult != null)
896        {
897          return retryResult;
898        }
899      }
900
901      final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
902      final String message = ccr.getMessage();
903      if (message == null)
904      {
905        throw new LDAPException(ccr.getResultCode(),
906             ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE.get(
907                  connection.getHostPort(), toString()));
908      }
909      else
910      {
911        throw new LDAPException(ccr.getResultCode(),
912             ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE_WITH_MESSAGE.get(
913                  connection.getHostPort(), toString(), message));
914      }
915    }
916
917    final LDAPResult result = (LDAPResult) response;
918    if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
919        followReferrals(connection))
920    {
921      if (depth >= connection.getConnectionOptions().getReferralHopLimit())
922      {
923        return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED,
924                              ERR_TOO_MANY_REFERRALS.get(),
925                              result.getMatchedDN(), result.getReferralURLs(),
926                              result.getResponseControls());
927      }
928
929      return followReferral(result, connection, depth);
930    }
931    else
932    {
933      if (allowRetry)
934      {
935        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
936             result.getResultCode());
937        if (retryResult != null)
938        {
939          return retryResult;
940        }
941      }
942
943      return result;
944    }
945  }
946
947
948
949  /**
950   * Attempts to re-establish the connection and retry processing this request
951   * on it.
952   *
953   * @param  connection  The connection to be re-established.
954   * @param  depth       The current referral depth for this request.  It should
955   *                     always be one for the initial request, and should only
956   *                     be incremented when following referrals.
957   * @param  resultCode  The result code for the previous operation attempt.
958   *
959   * @return  The result from re-trying the add, or {@code null} if it could not
960   *          be re-tried.
961   */
962  private LDAPResult reconnectAndRetry(final LDAPConnection connection,
963                                       final int depth,
964                                       final ResultCode resultCode)
965  {
966    try
967    {
968      // We will only want to retry for certain result codes that indicate a
969      // connection problem.
970      switch (resultCode.intValue())
971      {
972        case ResultCode.SERVER_DOWN_INT_VALUE:
973        case ResultCode.DECODING_ERROR_INT_VALUE:
974        case ResultCode.CONNECT_ERROR_INT_VALUE:
975          connection.reconnect();
976          return processSync(connection, depth, false);
977      }
978    }
979    catch (final Exception e)
980    {
981      Debug.debugException(e);
982    }
983
984    return null;
985  }
986
987
988
989  /**
990   * Attempts to follow a referral to perform a modify DN operation in the
991   * target server.
992   *
993   * @param  referralResult  The LDAP result object containing information about
994   *                         the referral to follow.
995   * @param  connection      The connection on which the referral was received.
996   * @param  depth           The number of referrals followed in the course of
997   *                         processing this request.
998   *
999   * @return  The result of attempting to process the modify DN operation by
1000   *          following the referral.
1001   *
1002   * @throws  LDAPException  If a problem occurs while attempting to establish
1003   *                         the referral connection, sending the request, or
1004   *                         reading the result.
1005   */
1006  private LDAPResult followReferral(final LDAPResult referralResult,
1007                                    final LDAPConnection connection,
1008                                    final int depth)
1009          throws LDAPException
1010  {
1011    for (final String urlString : referralResult.getReferralURLs())
1012    {
1013      try
1014      {
1015        final LDAPURL referralURL = new LDAPURL(urlString);
1016        final String host = referralURL.getHost();
1017
1018        if (host == null)
1019        {
1020          // We can't handle a referral in which there is no host.
1021          continue;
1022        }
1023
1024        final ModifyDNRequest modifyDNRequest;
1025        if (referralURL.baseDNProvided())
1026        {
1027          modifyDNRequest =
1028               new ModifyDNRequest(referralURL.getBaseDN().toString(),
1029                                   newRDN, deleteOldRDN, newSuperiorDN,
1030                                   getControls());
1031        }
1032        else
1033        {
1034          modifyDNRequest = this;
1035        }
1036
1037        final LDAPConnection referralConn = getReferralConnector(connection).
1038             getReferralConnection(referralURL, connection);
1039        try
1040        {
1041          return modifyDNRequest.process(referralConn, depth+1);
1042        }
1043        finally
1044        {
1045          referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1046          referralConn.close();
1047        }
1048      }
1049      catch (final LDAPException le)
1050      {
1051        Debug.debugException(le);
1052      }
1053    }
1054
1055    // If we've gotten here, then we could not follow any of the referral URLs,
1056    // so we'll just return the original referral result.
1057    return referralResult;
1058  }
1059
1060
1061
1062  /**
1063   * {@inheritDoc}
1064   */
1065  @InternalUseOnly()
1066  @Override()
1067  public void responseReceived(final LDAPResponse response)
1068         throws LDAPException
1069  {
1070    try
1071    {
1072      responseQueue.put(response);
1073    }
1074    catch (final Exception e)
1075    {
1076      Debug.debugException(e);
1077
1078      if (e instanceof InterruptedException)
1079      {
1080        Thread.currentThread().interrupt();
1081      }
1082
1083      throw new LDAPException(ResultCode.LOCAL_ERROR,
1084           ERR_EXCEPTION_HANDLING_RESPONSE.get(
1085                StaticUtils.getExceptionMessage(e)), e);
1086    }
1087  }
1088
1089
1090
1091  /**
1092   * {@inheritDoc}
1093   */
1094  @Override()
1095  public int getLastMessageID()
1096  {
1097    return messageID;
1098  }
1099
1100
1101
1102  /**
1103   * {@inheritDoc}
1104   */
1105  @Override()
1106  public OperationType getOperationType()
1107  {
1108    return OperationType.MODIFY_DN;
1109  }
1110
1111
1112
1113  /**
1114   * {@inheritDoc}
1115   */
1116  @Override()
1117  public ModifyDNRequest duplicate()
1118  {
1119    return duplicate(getControls());
1120  }
1121
1122
1123
1124  /**
1125   * {@inheritDoc}
1126   */
1127  @Override()
1128  public ModifyDNRequest duplicate(final Control[] controls)
1129  {
1130    final ModifyDNRequest r = new ModifyDNRequest(dn, newRDN, deleteOldRDN,
1131         newSuperiorDN, controls);
1132
1133    if (followReferralsInternal() != null)
1134    {
1135      r.setFollowReferrals(followReferralsInternal());
1136    }
1137
1138    if (getReferralConnectorInternal() != null)
1139    {
1140      r.setReferralConnector(getReferralConnectorInternal());
1141    }
1142
1143    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1144
1145    return r;
1146  }
1147
1148
1149
1150  /**
1151   * {@inheritDoc}
1152   */
1153  @Override()
1154  public LDIFModifyDNChangeRecord toLDIFChangeRecord()
1155  {
1156    return new LDIFModifyDNChangeRecord(this);
1157  }
1158
1159
1160
1161  /**
1162   * {@inheritDoc}
1163   */
1164  @Override()
1165  public String[] toLDIF()
1166  {
1167    return toLDIFChangeRecord().toLDIF();
1168  }
1169
1170
1171
1172  /**
1173   * {@inheritDoc}
1174   */
1175  @Override()
1176  public String toLDIFString()
1177  {
1178    return toLDIFChangeRecord().toLDIFString();
1179  }
1180
1181
1182
1183  /**
1184   * {@inheritDoc}
1185   */
1186  @Override()
1187  public void toString(final StringBuilder buffer)
1188  {
1189    buffer.append("ModifyDNRequest(dn='");
1190    buffer.append(dn);
1191    buffer.append("', newRDN='");
1192    buffer.append(newRDN);
1193    buffer.append("', deleteOldRDN=");
1194    buffer.append(deleteOldRDN);
1195
1196    if (newSuperiorDN != null)
1197    {
1198      buffer.append(", newSuperiorDN='");
1199      buffer.append(newSuperiorDN);
1200      buffer.append('\'');
1201    }
1202
1203    final Control[] controls = getControls();
1204    if (controls.length > 0)
1205    {
1206      buffer.append(", controls={");
1207      for (int i=0; i < controls.length; i++)
1208      {
1209        if (i > 0)
1210        {
1211          buffer.append(", ");
1212        }
1213
1214        buffer.append(controls[i]);
1215      }
1216      buffer.append('}');
1217    }
1218
1219    buffer.append(')');
1220  }
1221
1222
1223
1224  /**
1225   * {@inheritDoc}
1226   */
1227  @Override()
1228  public void toCode(final List<String> lineList, final String requestID,
1229                     final int indentSpaces, final boolean includeProcessing)
1230  {
1231    // Create the request variable.
1232    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(4);
1233    constructorArgs.add(ToCodeArgHelper.createString(dn, "Current DN"));
1234    constructorArgs.add(ToCodeArgHelper.createString(newRDN, "New RDN"));
1235    constructorArgs.add(ToCodeArgHelper.createBoolean(deleteOldRDN,
1236         "Delete Old RDN Value(s)"));
1237
1238    if (newSuperiorDN != null)
1239    {
1240      constructorArgs.add(ToCodeArgHelper.createString(newSuperiorDN,
1241           "New Superior Entry DN"));
1242    }
1243
1244    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyDNRequest",
1245         requestID + "Request", "new ModifyDNRequest", constructorArgs);
1246
1247
1248    // If there are any controls, then add them to the request.
1249    for (final Control c : getControls())
1250    {
1251      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1252           requestID + "Request.addControl",
1253           ToCodeArgHelper.createControl(c, null));
1254    }
1255
1256
1257    // Add lines for processing the request and obtaining the result.
1258    if (includeProcessing)
1259    {
1260      // Generate a string with the appropriate indent.
1261      final StringBuilder buffer = new StringBuilder();
1262      for (int i=0; i < indentSpaces; i++)
1263      {
1264        buffer.append(' ');
1265      }
1266      final String indent = buffer.toString();
1267
1268      lineList.add("");
1269      lineList.add(indent + "try");
1270      lineList.add(indent + '{');
1271      lineList.add(indent + "  LDAPResult " + requestID +
1272           "Result = connection.modifyDN(" + requestID + "Request);");
1273      lineList.add(indent + "  // The modify DN was processed successfully.");
1274      lineList.add(indent + '}');
1275      lineList.add(indent + "catch (LDAPException e)");
1276      lineList.add(indent + '{');
1277      lineList.add(indent + "  // The modify DN failed.  Maybe the following " +
1278           "will help explain why.");
1279      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
1280      lineList.add(indent + "  String message = e.getMessage();");
1281      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
1282      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
1283      lineList.add(indent + "  Control[] responseControls = " +
1284           "e.getResponseControls();");
1285      lineList.add(indent + '}');
1286    }
1287  }
1288}