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 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, messageID, dn,
908 connection.getHostPort()));
909 }
910
911 connection.getConnectionStatistics().incrementNumModifyResponses(
912 System.nanoTime() - requestTime);
913 if (response instanceof ConnectionClosedResponse)
914 {
915 // The connection was closed while waiting for the response.
916 if (allowRetry)
917 {
918 final LDAPResult retryResult = reconnectAndRetry(connection, depth,
919 ResultCode.SERVER_DOWN);
920 if (retryResult != null)
921 {
922 return retryResult;
923 }
924 }
925
926 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
927 final String message = ccr.getMessage();
928 if (message == null)
929 {
930 throw new LDAPException(ccr.getResultCode(),
931 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_RESPONSE.get(
932 connection.getHostPort(), toString()));
933 }
934 else
935 {
936 throw new LDAPException(ccr.getResultCode(),
937 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_RESPONSE_WITH_MESSAGE.get(
938 connection.getHostPort(), toString(), message));
939 }
940 }
941
942 final LDAPResult result = (LDAPResult) response;
943 if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
944 followReferrals(connection))
945 {
946 if (depth >= connection.getConnectionOptions().getReferralHopLimit())
947 {
948 return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED,
949 ERR_TOO_MANY_REFERRALS.get(),
950 result.getMatchedDN(), result.getReferralURLs(),
951 result.getResponseControls());
952 }
953
954 return followReferral(result, connection, depth);
955 }
956 else
957 {
958 if (allowRetry)
959 {
960 final LDAPResult retryResult = reconnectAndRetry(connection, depth,
961 result.getResultCode());
962 if (retryResult != null)
963 {
964 return retryResult;
965 }
966 }
967
968 return result;
969 }
970 }
971
972
973
974 /**
975 * Attempts to re-establish the connection and retry processing this request
976 * on it.
977 *
978 * @param connection The connection to be re-established.
979 * @param depth The current referral depth for this request. It should
980 * always be one for the initial request, and should only
981 * be incremented when following referrals.
982 * @param resultCode The result code for the previous operation attempt.
983 *
984 * @return The result from re-trying the add, or {@code null} if it could not
985 * be re-tried.
986 */
987 private LDAPResult reconnectAndRetry(final LDAPConnection connection,
988 final int depth,
989 final ResultCode resultCode)
990 {
991 try
992 {
993 // We will only want to retry for certain result codes that indicate a
994 // connection problem.
995 switch (resultCode.intValue())
996 {
997 case ResultCode.SERVER_DOWN_INT_VALUE:
998 case ResultCode.DECODING_ERROR_INT_VALUE:
999 case ResultCode.CONNECT_ERROR_INT_VALUE:
1000 connection.reconnect();
1001 return processSync(connection, depth, false);
1002 }
1003 }
1004 catch (final Exception e)
1005 {
1006 debugException(e);
1007 }
1008
1009 return null;
1010 }
1011
1012
1013
1014 /**
1015 * Attempts to follow a referral to perform a modify operation in the target
1016 * server.
1017 *
1018 * @param referralResult The LDAP result object containing information about
1019 * the referral to follow.
1020 * @param connection The connection on which the referral was received.
1021 * @param depth The number of referrals followed in the course of
1022 * processing this request.
1023 *
1024 * @return The result of attempting to process the modify operation by
1025 * following the referral.
1026 *
1027 * @throws LDAPException If a problem occurs while attempting to establish
1028 * the referral connection, sending the request, or
1029 * reading the result.
1030 */
1031 private LDAPResult followReferral(final LDAPResult referralResult,
1032 final LDAPConnection connection,
1033 final int depth)
1034 throws LDAPException
1035 {
1036 for (final String urlString : referralResult.getReferralURLs())
1037 {
1038 try
1039 {
1040 final LDAPURL referralURL = new LDAPURL(urlString);
1041 final String host = referralURL.getHost();
1042
1043 if (host == null)
1044 {
1045 // We can't handle a referral in which there is no host.
1046 continue;
1047 }
1048
1049 final ModifyRequest modifyRequest;
1050 if (referralURL.baseDNProvided())
1051 {
1052 modifyRequest = new ModifyRequest(referralURL.getBaseDN(),
1053 modifications, getControls());
1054 }
1055 else
1056 {
1057 modifyRequest = this;
1058 }
1059
1060 final LDAPConnection referralConn = connection.getReferralConnector().
1061 getReferralConnection(referralURL, connection);
1062 try
1063 {
1064 return modifyRequest.process(referralConn, depth+1);
1065 }
1066 finally
1067 {
1068 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1069 referralConn.close();
1070 }
1071 }
1072 catch (LDAPException le)
1073 {
1074 debugException(le);
1075 }
1076 }
1077
1078 // If we've gotten here, then we could not follow any of the referral URLs,
1079 // so we'll just return the original referral result.
1080 return referralResult;
1081 }
1082
1083
1084
1085 /**
1086 * {@inheritDoc}
1087 */
1088 @InternalUseOnly()
1089 public void responseReceived(final LDAPResponse response)
1090 throws LDAPException
1091 {
1092 try
1093 {
1094 responseQueue.put(response);
1095 }
1096 catch (Exception e)
1097 {
1098 debugException(e);
1099 throw new LDAPException(ResultCode.LOCAL_ERROR,
1100 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
1101 }
1102 }
1103
1104
1105
1106 /**
1107 * {@inheritDoc}
1108 */
1109 @Override()
1110 public int getLastMessageID()
1111 {
1112 return messageID;
1113 }
1114
1115
1116
1117 /**
1118 * {@inheritDoc}
1119 */
1120 @Override()
1121 public OperationType getOperationType()
1122 {
1123 return OperationType.MODIFY;
1124 }
1125
1126
1127
1128 /**
1129 * {@inheritDoc}
1130 */
1131 public ModifyRequest duplicate()
1132 {
1133 return duplicate(getControls());
1134 }
1135
1136
1137
1138 /**
1139 * {@inheritDoc}
1140 */
1141 public ModifyRequest duplicate(final Control[] controls)
1142 {
1143 final ModifyRequest r = new ModifyRequest(dn,
1144 new ArrayList<Modification>(modifications), controls);
1145
1146 if (followReferralsInternal() != null)
1147 {
1148 r.setFollowReferrals(followReferralsInternal());
1149 }
1150
1151 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1152
1153 return r;
1154 }
1155
1156
1157
1158 /**
1159 * {@inheritDoc}
1160 */
1161 public LDIFModifyChangeRecord toLDIFChangeRecord()
1162 {
1163 return new LDIFModifyChangeRecord(this);
1164 }
1165
1166
1167
1168 /**
1169 * {@inheritDoc}
1170 */
1171 public String[] toLDIF()
1172 {
1173 return toLDIFChangeRecord().toLDIF();
1174 }
1175
1176
1177
1178 /**
1179 * {@inheritDoc}
1180 */
1181 public String toLDIFString()
1182 {
1183 return toLDIFChangeRecord().toLDIFString();
1184 }
1185
1186
1187
1188 /**
1189 * {@inheritDoc}
1190 */
1191 @Override()
1192 public void toString(final StringBuilder buffer)
1193 {
1194 buffer.append("ModifyRequest(dn='");
1195 buffer.append(dn);
1196 buffer.append("', mods={");
1197 for (int i=0; i < modifications.size(); i++)
1198 {
1199 final Modification m = modifications.get(i);
1200
1201 if (i > 0)
1202 {
1203 buffer.append(", ");
1204 }
1205
1206 switch (m.getModificationType().intValue())
1207 {
1208 case 0:
1209 buffer.append("ADD ");
1210 break;
1211
1212 case 1:
1213 buffer.append("DELETE ");
1214 break;
1215
1216 case 2:
1217 buffer.append("REPLACE ");
1218 break;
1219
1220 case 3:
1221 buffer.append("INCREMENT ");
1222 break;
1223 }
1224
1225 buffer.append(m.getAttributeName());
1226 }
1227 buffer.append('}');
1228
1229 final Control[] controls = getControls();
1230 if (controls.length > 0)
1231 {
1232 buffer.append(", controls={");
1233 for (int i=0; i < controls.length; i++)
1234 {
1235 if (i > 0)
1236 {
1237 buffer.append(", ");
1238 }
1239
1240 buffer.append(controls[i]);
1241 }
1242 buffer.append('}');
1243 }
1244
1245 buffer.append(')');
1246 }
1247 }