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