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