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