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