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