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