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