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