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