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