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