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