001 /*
002 * Copyright 2007-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2016 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
043 import static com.unboundid.ldap.sdk.LDAPMessages.*;
044 import static com.unboundid.util.Debug.*;
045 import static com.unboundid.util.StaticUtils.*;
046 import static com.unboundid.util.Validator.*;
047
048
049
050 /**
051 * This class implements the processing necessary to perform an LDAPv3 modify DN
052 * operation, which can be used to rename and/or move an entry or subtree in the
053 * directory. A modify DN request contains the DN of the target entry, the new
054 * RDN to use for that entry, and a flag which indicates whether to remove the
055 * current RDN attribute value(s) from the entry. It may optionally contain a
056 * new superior DN, which will cause the entry to be moved below that new parent
057 * entry.
058 * <BR><BR>
059 * Note that some directory servers may not support all possible uses of the
060 * modify DN operation. In particular, some servers may not support the use of
061 * a new superior DN, especially if it may cause the entry to be moved to a
062 * different database or another server. Also, some servers may not support
063 * renaming or moving non-leaf entries (i.e., entries that have one or more
064 * subordinates).
065 * <BR><BR>
066 * {@code ModifyDNRequest} objects are mutable and therefore can be altered and
067 * re-used for multiple requests. Note, however, that {@code ModifyDNRequest}
068 * objects are not threadsafe and therefore a single {@code ModifyDNRequest}
069 * object instance should not be used to process multiple requests at the same
070 * time.
071 * <BR><BR>
072 * <H2>Example</H2>
073 * The following example demonstrates the process for performing a modify DN
074 * operation. In this case, it will rename "ou=People,dc=example,dc=com" to
075 * "ou=Users,dc=example,dc=com". It will not move the entry below a new parent.
076 * <PRE>
077 * ModifyDNRequest modifyDNRequest =
078 * new ModifyDNRequest("ou=People,dc=example,dc=com", "ou=Users", true);
079 * LDAPResult modifyDNResult;
080 *
081 * try
082 * {
083 * modifyDNResult = connection.modifyDN(modifyDNRequest);
084 * // If we get here, the delete was successful.
085 * }
086 * catch (LDAPException le)
087 * {
088 * // The modify DN operation failed.
089 * modifyDNResult = le.toLDAPResult();
090 * ResultCode resultCode = le.getResultCode();
091 * String errorMessageFromServer = le.getDiagnosticMessage();
092 * }
093 * </PRE>
094 */
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 @SuppressWarnings("deprecation")
613 final boolean autoReconnect =
614 connection.getConnectionOptions().autoReconnect();
615 return processSync(connection, depth, autoReconnect);
616 }
617
618 final long requestTime = System.nanoTime();
619 processAsync(connection, null);
620
621 try
622 {
623 // Wait for and process the response.
624 final LDAPResponse response;
625 try
626 {
627 final long responseTimeout = getResponseTimeoutMillis(connection);
628 if (responseTimeout > 0)
629 {
630 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
631 }
632 else
633 {
634 response = responseQueue.take();
635 }
636 }
637 catch (InterruptedException ie)
638 {
639 debugException(ie);
640 throw new LDAPException(ResultCode.LOCAL_ERROR,
641 ERR_MODDN_INTERRUPTED.get(connection.getHostPort()), ie);
642 }
643
644 return handleResponse(connection, response, requestTime, depth, false);
645 }
646 finally
647 {
648 connection.deregisterResponseAcceptor(messageID);
649 }
650 }
651
652
653
654 /**
655 * Sends this modify DN request to the directory server over the provided
656 * connection and returns the message ID for the request.
657 *
658 * @param connection The connection to use to communicate with the
659 * directory server.
660 * @param resultListener The async result listener that is to be notified
661 * when the response is received. It may be
662 * {@code null} only if the result is to be processed
663 * by this class.
664 *
665 * @return The async request ID created for the operation, or {@code null} if
666 * the provided {@code resultListener} is {@code null} and the
667 * operation will not actually be processed asynchronously.
668 *
669 * @throws LDAPException If a problem occurs while sending the request.
670 */
671 AsyncRequestID processAsync(final LDAPConnection connection,
672 final AsyncResultListener resultListener)
673 throws LDAPException
674 {
675 // Create the LDAP message.
676 messageID = connection.nextMessageID();
677 final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
678
679
680 // If the provided async result listener is {@code null}, then we'll use
681 // this class as the message acceptor. Otherwise, create an async helper
682 // and use it as the message acceptor.
683 final AsyncRequestID asyncRequestID;
684 if (resultListener == null)
685 {
686 asyncRequestID = null;
687 connection.registerResponseAcceptor(messageID, this);
688 }
689 else
690 {
691 final AsyncHelper helper = new AsyncHelper(connection,
692 OperationType.MODIFY_DN, messageID, resultListener,
693 getIntermediateResponseListener());
694 connection.registerResponseAcceptor(messageID, helper);
695 asyncRequestID = helper.getAsyncRequestID();
696
697 final long timeout = getResponseTimeoutMillis(connection);
698 if (timeout > 0L)
699 {
700 final Timer timer = connection.getTimer();
701 final AsyncTimeoutTimerTask timerTask =
702 new AsyncTimeoutTimerTask(helper);
703 timer.schedule(timerTask, timeout);
704 asyncRequestID.setTimerTask(timerTask);
705 }
706 }
707
708
709 // Send the request to the server.
710 try
711 {
712 debugLDAPRequest(this);
713 connection.getConnectionStatistics().incrementNumModifyDNRequests();
714 connection.sendMessage(message);
715 return asyncRequestID;
716 }
717 catch (LDAPException le)
718 {
719 debugException(le);
720
721 connection.deregisterResponseAcceptor(messageID);
722 throw le;
723 }
724 }
725
726
727
728 /**
729 * Processes this modify DN operation in synchronous mode, in which the same
730 * thread will send the request and read the response.
731 *
732 * @param connection The connection to use to communicate with the directory
733 * server.
734 * @param depth The current referral depth for this request. It should
735 * always be one for the initial request, and should only
736 * be incremented when following referrals.
737 * @param allowRetry Indicates whether the request may be re-tried on a
738 * re-established connection if the initial attempt fails
739 * in a way that indicates the connection is no longer
740 * valid and autoReconnect is true.
741 *
742 * @return An LDAP result object that provides information about the result
743 * of the modify DN processing.
744 *
745 * @throws LDAPException If a problem occurs while sending the request or
746 * reading the response.
747 */
748 private LDAPResult processSync(final LDAPConnection connection,
749 final int depth,
750 final boolean allowRetry)
751 throws LDAPException
752 {
753 // Create the LDAP message.
754 messageID = connection.nextMessageID();
755 final LDAPMessage message =
756 new LDAPMessage(messageID, this, getControls());
757
758
759 // Set the appropriate timeout on the socket.
760 try
761 {
762 connection.getConnectionInternals(true).getSocket().setSoTimeout(
763 (int) getResponseTimeoutMillis(connection));
764 }
765 catch (Exception e)
766 {
767 debugException(e);
768 }
769
770
771 // Send the request to the server.
772 final long requestTime = System.nanoTime();
773 debugLDAPRequest(this);
774 connection.getConnectionStatistics().incrementNumModifyDNRequests();
775 try
776 {
777 connection.sendMessage(message);
778 }
779 catch (final LDAPException le)
780 {
781 debugException(le);
782
783 if (allowRetry)
784 {
785 final LDAPResult retryResult = reconnectAndRetry(connection, depth,
786 le.getResultCode());
787 if (retryResult != null)
788 {
789 return retryResult;
790 }
791 }
792
793 throw le;
794 }
795
796 while (true)
797 {
798 final LDAPResponse response;
799 try
800 {
801 response = connection.readResponse(messageID);
802 }
803 catch (final LDAPException le)
804 {
805 debugException(le);
806
807 if ((le.getResultCode() == ResultCode.TIMEOUT) &&
808 connection.getConnectionOptions().abandonOnTimeout())
809 {
810 connection.abandon(messageID);
811 }
812
813 if (allowRetry)
814 {
815 final LDAPResult retryResult = reconnectAndRetry(connection, depth,
816 le.getResultCode());
817 if (retryResult != null)
818 {
819 return retryResult;
820 }
821 }
822
823 throw le;
824 }
825
826 if (response instanceof IntermediateResponse)
827 {
828 final IntermediateResponseListener listener =
829 getIntermediateResponseListener();
830 if (listener != null)
831 {
832 listener.intermediateResponseReturned(
833 (IntermediateResponse) response);
834 }
835 }
836 else
837 {
838 return handleResponse(connection, response, requestTime, depth,
839 allowRetry);
840 }
841 }
842 }
843
844
845
846 /**
847 * Performs the necessary processing for handling a response.
848 *
849 * @param connection The connection used to read the response.
850 * @param response The response to be processed.
851 * @param requestTime The time the request was sent to the server.
852 * @param depth The current referral depth for this request. It
853 * should always be one for the initial request, and
854 * should only be incremented when following referrals.
855 * @param allowRetry Indicates whether the request may be re-tried on a
856 * re-established connection if the initial attempt fails
857 * in a way that indicates the connection is no longer
858 * valid and autoReconnect is true.
859 *
860 * @return The modify DN result.
861 *
862 * @throws LDAPException If a problem occurs.
863 */
864 private LDAPResult handleResponse(final LDAPConnection connection,
865 final LDAPResponse response,
866 final long requestTime, final int depth,
867 final boolean allowRetry)
868 throws LDAPException
869 {
870 if (response == null)
871 {
872 final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
873 if (connection.getConnectionOptions().abandonOnTimeout())
874 {
875 connection.abandon(messageID);
876 }
877
878 throw new LDAPException(ResultCode.TIMEOUT,
879 ERR_MODIFY_DN_CLIENT_TIMEOUT.get(waitTime, messageID, dn,
880 connection.getHostPort()));
881 }
882
883 connection.getConnectionStatistics().incrementNumModifyDNResponses(
884 System.nanoTime() - requestTime);
885 if (response instanceof ConnectionClosedResponse)
886 {
887 // The connection was closed while waiting for the response.
888 if (allowRetry)
889 {
890 final LDAPResult retryResult = reconnectAndRetry(connection, depth,
891 ResultCode.SERVER_DOWN);
892 if (retryResult != null)
893 {
894 return retryResult;
895 }
896 }
897
898 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
899 final String message = ccr.getMessage();
900 if (message == null)
901 {
902 throw new LDAPException(ccr.getResultCode(),
903 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE.get(
904 connection.getHostPort(), toString()));
905 }
906 else
907 {
908 throw new LDAPException(ccr.getResultCode(),
909 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE_WITH_MESSAGE.get(
910 connection.getHostPort(), toString(), message));
911 }
912 }
913
914 final LDAPResult result = (LDAPResult) response;
915 if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
916 followReferrals(connection))
917 {
918 if (depth >= connection.getConnectionOptions().getReferralHopLimit())
919 {
920 return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED,
921 ERR_TOO_MANY_REFERRALS.get(),
922 result.getMatchedDN(), result.getReferralURLs(),
923 result.getResponseControls());
924 }
925
926 return followReferral(result, connection, depth);
927 }
928 else
929 {
930 if (allowRetry)
931 {
932 final LDAPResult retryResult = reconnectAndRetry(connection, depth,
933 result.getResultCode());
934 if (retryResult != null)
935 {
936 return retryResult;
937 }
938 }
939
940 return result;
941 }
942 }
943
944
945
946 /**
947 * Attempts to re-establish the connection and retry processing this request
948 * on it.
949 *
950 * @param connection The connection to be re-established.
951 * @param depth The current referral depth for this request. It should
952 * always be one for the initial request, and should only
953 * be incremented when following referrals.
954 * @param resultCode The result code for the previous operation attempt.
955 *
956 * @return The result from re-trying the add, or {@code null} if it could not
957 * be re-tried.
958 */
959 private LDAPResult reconnectAndRetry(final LDAPConnection connection,
960 final int depth,
961 final ResultCode resultCode)
962 {
963 try
964 {
965 // We will only want to retry for certain result codes that indicate a
966 // connection problem.
967 switch (resultCode.intValue())
968 {
969 case ResultCode.SERVER_DOWN_INT_VALUE:
970 case ResultCode.DECODING_ERROR_INT_VALUE:
971 case ResultCode.CONNECT_ERROR_INT_VALUE:
972 connection.reconnect();
973 return processSync(connection, depth, false);
974 }
975 }
976 catch (final Exception e)
977 {
978 debugException(e);
979 }
980
981 return null;
982 }
983
984
985
986 /**
987 * Attempts to follow a referral to perform a modify DN operation in the
988 * target server.
989 *
990 * @param referralResult The LDAP result object containing information about
991 * the referral to follow.
992 * @param connection The connection on which the referral was received.
993 * @param depth The number of referrals followed in the course of
994 * processing this request.
995 *
996 * @return The result of attempting to process the modify DN operation by
997 * following the referral.
998 *
999 * @throws LDAPException If a problem occurs while attempting to establish
1000 * the referral connection, sending the request, or
1001 * reading the result.
1002 */
1003 private LDAPResult followReferral(final LDAPResult referralResult,
1004 final LDAPConnection connection,
1005 final int depth)
1006 throws LDAPException
1007 {
1008 for (final String urlString : referralResult.getReferralURLs())
1009 {
1010 try
1011 {
1012 final LDAPURL referralURL = new LDAPURL(urlString);
1013 final String host = referralURL.getHost();
1014
1015 if (host == null)
1016 {
1017 // We can't handle a referral in which there is no host.
1018 continue;
1019 }
1020
1021 final ModifyDNRequest modifyDNRequest;
1022 if (referralURL.baseDNProvided())
1023 {
1024 modifyDNRequest =
1025 new ModifyDNRequest(referralURL.getBaseDN().toString(),
1026 newRDN, deleteOldRDN, newSuperiorDN,
1027 getControls());
1028 }
1029 else
1030 {
1031 modifyDNRequest = this;
1032 }
1033
1034 final LDAPConnection referralConn = connection.getReferralConnector().
1035 getReferralConnection(referralURL, connection);
1036 try
1037 {
1038 return modifyDNRequest.process(referralConn, depth+1);
1039 }
1040 finally
1041 {
1042 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1043 referralConn.close();
1044 }
1045 }
1046 catch (LDAPException le)
1047 {
1048 debugException(le);
1049 }
1050 }
1051
1052 // If we've gotten here, then we could not follow any of the referral URLs,
1053 // so we'll just return the original referral result.
1054 return referralResult;
1055 }
1056
1057
1058
1059 /**
1060 * {@inheritDoc}
1061 */
1062 @InternalUseOnly()
1063 public void responseReceived(final LDAPResponse response)
1064 throws LDAPException
1065 {
1066 try
1067 {
1068 responseQueue.put(response);
1069 }
1070 catch (Exception e)
1071 {
1072 debugException(e);
1073 throw new LDAPException(ResultCode.LOCAL_ERROR,
1074 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
1075 }
1076 }
1077
1078
1079
1080 /**
1081 * {@inheritDoc}
1082 */
1083 @Override()
1084 public int getLastMessageID()
1085 {
1086 return messageID;
1087 }
1088
1089
1090
1091 /**
1092 * {@inheritDoc}
1093 */
1094 @Override()
1095 public OperationType getOperationType()
1096 {
1097 return OperationType.MODIFY_DN;
1098 }
1099
1100
1101
1102 /**
1103 * {@inheritDoc}
1104 */
1105 public ModifyDNRequest duplicate()
1106 {
1107 return duplicate(getControls());
1108 }
1109
1110
1111
1112 /**
1113 * {@inheritDoc}
1114 */
1115 public ModifyDNRequest duplicate(final Control[] controls)
1116 {
1117 final ModifyDNRequest r = new ModifyDNRequest(dn, newRDN, deleteOldRDN,
1118 newSuperiorDN, controls);
1119
1120 if (followReferralsInternal() != null)
1121 {
1122 r.setFollowReferrals(followReferralsInternal());
1123 }
1124
1125 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1126
1127 return r;
1128 }
1129
1130
1131
1132 /**
1133 * {@inheritDoc}
1134 */
1135 public LDIFModifyDNChangeRecord toLDIFChangeRecord()
1136 {
1137 return new LDIFModifyDNChangeRecord(this);
1138 }
1139
1140
1141
1142 /**
1143 * {@inheritDoc}
1144 */
1145 public String[] toLDIF()
1146 {
1147 return toLDIFChangeRecord().toLDIF();
1148 }
1149
1150
1151
1152 /**
1153 * {@inheritDoc}
1154 */
1155 public String toLDIFString()
1156 {
1157 return toLDIFChangeRecord().toLDIFString();
1158 }
1159
1160
1161
1162 /**
1163 * {@inheritDoc}
1164 */
1165 @Override()
1166 public void toString(final StringBuilder buffer)
1167 {
1168 buffer.append("ModifyDNRequest(dn='");
1169 buffer.append(dn);
1170 buffer.append("', newRDN='");
1171 buffer.append(newRDN);
1172 buffer.append("', deleteOldRDN=");
1173 buffer.append(deleteOldRDN);
1174
1175 if (newSuperiorDN != null)
1176 {
1177 buffer.append(", newSuperiorDN='");
1178 buffer.append(newSuperiorDN);
1179 buffer.append('\'');
1180 }
1181
1182 final Control[] controls = getControls();
1183 if (controls.length > 0)
1184 {
1185 buffer.append(", controls={");
1186 for (int i=0; i < controls.length; i++)
1187 {
1188 if (i > 0)
1189 {
1190 buffer.append(", ");
1191 }
1192
1193 buffer.append(controls[i]);
1194 }
1195 buffer.append('}');
1196 }
1197
1198 buffer.append(')');
1199 }
1200
1201
1202
1203 /**
1204 * {@inheritDoc}
1205 */
1206 public void toCode(final List<String> lineList, final String requestID,
1207 final int indentSpaces, final boolean includeProcessing)
1208 {
1209 // Create the request variable.
1210 final ArrayList<ToCodeArgHelper> constructorArgs =
1211 new ArrayList<ToCodeArgHelper>(4);
1212 constructorArgs.add(ToCodeArgHelper.createString(dn, "Current DN"));
1213 constructorArgs.add(ToCodeArgHelper.createString(newRDN, "New RDN"));
1214 constructorArgs.add(ToCodeArgHelper.createBoolean(deleteOldRDN,
1215 "Delete Old RDN Value(s)"));
1216
1217 if (newSuperiorDN != null)
1218 {
1219 constructorArgs.add(ToCodeArgHelper.createString(newSuperiorDN,
1220 "New Superior Entry DN"));
1221 }
1222
1223 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyDNRequest",
1224 requestID + "Request", "new ModifyDNRequest", constructorArgs);
1225
1226
1227 // If there are any controls, then add them to the request.
1228 for (final Control c : getControls())
1229 {
1230 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1231 requestID + "Request.addControl",
1232 ToCodeArgHelper.createControl(c, null));
1233 }
1234
1235
1236 // Add lines for processing the request and obtaining the result.
1237 if (includeProcessing)
1238 {
1239 // Generate a string with the appropriate indent.
1240 final StringBuilder buffer = new StringBuilder();
1241 for (int i=0; i < indentSpaces; i++)
1242 {
1243 buffer.append(' ');
1244 }
1245 final String indent = buffer.toString();
1246
1247 lineList.add("");
1248 lineList.add(indent + "try");
1249 lineList.add(indent + '{');
1250 lineList.add(indent + " LDAPResult " + requestID +
1251 "Result = connection.modifyDN(" + requestID + "Request);");
1252 lineList.add(indent + " // The modify DN was processed successfully.");
1253 lineList.add(indent + '}');
1254 lineList.add(indent + "catch (LDAPException e)");
1255 lineList.add(indent + '{');
1256 lineList.add(indent + " // The modify DN failed. Maybe the following " +
1257 "will help explain why.");
1258 lineList.add(indent + " ResultCode resultCode = e.getResultCode();");
1259 lineList.add(indent + " String message = e.getMessage();");
1260 lineList.add(indent + " String matchedDN = e.getMatchedDN();");
1261 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();");
1262 lineList.add(indent + " Control[] responseControls = " +
1263 "e.getResponseControls();");
1264 lineList.add(indent + '}');
1265 }
1266 }
1267 }