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