001/*
002 * Copyright 2007-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2008-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk;
037
038
039
040import java.util.ArrayList;
041import java.util.Arrays;
042import java.util.Collection;
043import java.util.Collections;
044import java.util.Iterator;
045import java.util.List;
046import java.util.Timer;
047import java.util.concurrent.LinkedBlockingQueue;
048import java.util.concurrent.TimeUnit;
049import java.util.logging.Level;
050
051import com.unboundid.asn1.ASN1Buffer;
052import com.unboundid.asn1.ASN1BufferSequence;
053import com.unboundid.asn1.ASN1Element;
054import com.unboundid.asn1.ASN1OctetString;
055import com.unboundid.asn1.ASN1Sequence;
056import com.unboundid.ldap.matchingrules.MatchingRule;
057import com.unboundid.ldap.protocol.LDAPMessage;
058import com.unboundid.ldap.protocol.LDAPResponse;
059import com.unboundid.ldap.protocol.ProtocolOp;
060import com.unboundid.ldif.LDIFAddChangeRecord;
061import com.unboundid.ldif.LDIFChangeRecord;
062import com.unboundid.ldif.LDIFException;
063import com.unboundid.ldif.LDIFReader;
064import com.unboundid.util.Debug;
065import com.unboundid.util.InternalUseOnly;
066import com.unboundid.util.Mutable;
067import com.unboundid.util.StaticUtils;
068import com.unboundid.util.ThreadSafety;
069import com.unboundid.util.ThreadSafetyLevel;
070import com.unboundid.util.Validator;
071
072import static com.unboundid.ldap.sdk.LDAPMessages.*;
073
074
075
076/**
077 * This class implements the processing necessary to perform an LDAPv3 add
078 * operation, which creates a new entry in the directory.  An add request
079 * contains the DN for the entry and the set of attributes to include.  It may
080 * also include a set of controls to send to the server.
081 * <BR><BR>
082 * The contents of the entry to may be specified as a separate DN and collection
083 * of attributes, as an {@link Entry} object, or as a list of the lines that
084 * comprise the LDIF representation of the entry to add as described in
085 * <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A>.  For example, the
086 * following code demonstrates creating an add request from the LDIF
087 * representation of the entry:
088 * <PRE>
089 *   AddRequest addRequest = new AddRequest(
090 *     "dn: dc=example,dc=com",
091 *     "objectClass: top",
092 *     "objectClass: domain",
093 *     "dc: example");
094 * </PRE>
095 * <BR><BR>
096 * {@code AddRequest} objects are mutable and therefore can be altered and
097 * re-used for multiple requests.  Note, however, that {@code AddRequest}
098 * objects are not threadsafe and therefore a single {@code AddRequest} object
099 * instance should not be used to process multiple requests at the same time.
100 */
101@Mutable()
102@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
103public final class AddRequest
104       extends UpdatableLDAPRequest
105       implements ReadOnlyAddRequest, ResponseAcceptor, ProtocolOp
106{
107  /**
108   * The serial version UID for this serializable class.
109   */
110  private static final long serialVersionUID = 1320730292848237219L;
111
112
113
114  // The queue that will be used to receive response messages from the server.
115  private final LinkedBlockingQueue<LDAPResponse> responseQueue =
116       new LinkedBlockingQueue<>();
117
118  // The set of attributes to include in the entry to add.
119  private ArrayList<Attribute> attributes;
120
121  // The message ID from the last LDAP message sent from this request.
122  private int messageID = -1;
123
124  // The DN of the entry to be added.
125  private String dn;
126
127
128
129  /**
130   * Creates a new add request with the provided DN and set of attributes.
131   *
132   * @param  dn          The DN for the entry to add.  It must not be
133   *                     {@code null}.
134   * @param  attributes  The set of attributes to include in the entry to add.
135   *                     It must not be {@code null}.
136   */
137  public AddRequest(final String dn, final Attribute... attributes)
138  {
139    super(null);
140
141    Validator.ensureNotNull(dn, attributes);
142
143    this.dn = dn;
144
145    this.attributes = new ArrayList<>(attributes.length);
146    this.attributes.addAll(Arrays.asList(attributes));
147  }
148
149
150
151  /**
152   * Creates a new add request with the provided DN and set of attributes.
153   *
154   * @param  dn          The DN for the entry to add.  It must not be
155   *                     {@code null}.
156   * @param  attributes  The set of attributes to include in the entry to add.
157   *                     It must not be {@code null}.
158   * @param  controls    The set of controls to include in the request.
159   */
160  public AddRequest(final String dn, final Attribute[] attributes,
161                    final Control[] controls)
162  {
163    super(controls);
164
165    Validator.ensureNotNull(dn, attributes);
166
167    this.dn = dn;
168
169    this.attributes = new ArrayList<>(attributes.length);
170    this.attributes.addAll(Arrays.asList(attributes));
171  }
172
173
174
175  /**
176   * Creates a new add request with the provided DN and set of attributes.
177   *
178   * @param  dn          The DN for the entry to add.  It must not be
179   *                     {@code null}.
180   * @param  attributes  The set of attributes to include in the entry to add.
181   *                     It must not be {@code null}.
182   */
183  public AddRequest(final String dn, final Collection<Attribute> attributes)
184  {
185    super(null);
186
187    Validator.ensureNotNull(dn, attributes);
188
189    this.dn         = dn;
190    this.attributes = new ArrayList<>(attributes);
191  }
192
193
194
195  /**
196   * Creates a new add request with the provided DN and set of attributes.
197   *
198   * @param  dn          The DN for the entry to add.  It must not be
199   *                     {@code null}.
200   * @param  attributes  The set of attributes to include in the entry to add.
201   *                     It must not be {@code null}.
202   * @param  controls    The set of controls to include in the request.
203   */
204  public AddRequest(final String dn, final Collection<Attribute> attributes,
205                    final Control[] controls)
206  {
207    super(controls);
208
209    Validator.ensureNotNull(dn, attributes);
210
211    this.dn         = dn;
212    this.attributes = new ArrayList<>(attributes);
213  }
214
215
216
217  /**
218   * Creates a new add request with the provided DN and set of attributes.
219   *
220   * @param  dn          The DN for the entry to add.  It must not be
221   *                     {@code null}.
222   * @param  attributes  The set of attributes to include in the entry to add.
223   *                     It must not be {@code null}.
224   */
225  public AddRequest(final DN dn, final Attribute... attributes)
226  {
227    super(null);
228
229    Validator.ensureNotNull(dn, attributes);
230
231    this.dn = dn.toString();
232
233    this.attributes = new ArrayList<>(attributes.length);
234    this.attributes.addAll(Arrays.asList(attributes));
235  }
236
237
238
239  /**
240   * Creates a new add request with the provided DN and set of attributes.
241   *
242   * @param  dn          The DN for the entry to add.  It must not be
243   *                     {@code null}.
244   * @param  attributes  The set of attributes to include in the entry to add.
245   *                     It must not be {@code null}.
246   * @param  controls    The set of controls to include in the request.
247   */
248  public AddRequest(final DN dn, final Attribute[] attributes,
249                    final Control[] controls)
250  {
251    super(controls);
252
253    Validator.ensureNotNull(dn, attributes);
254
255    this.dn = dn.toString();
256
257    this.attributes = new ArrayList<>(attributes.length);
258    this.attributes.addAll(Arrays.asList(attributes));
259  }
260
261
262
263  /**
264   * Creates a new add request with the provided DN and set of attributes.
265   *
266   * @param  dn          The DN for the entry to add.  It must not be
267   *                     {@code null}.
268   * @param  attributes  The set of attributes to include in the entry to add.
269   *                     It must not be {@code null}.
270   */
271  public AddRequest(final DN dn, final Collection<Attribute> attributes)
272  {
273    super(null);
274
275    Validator.ensureNotNull(dn, attributes);
276
277    this.dn         = dn.toString();
278    this.attributes = new ArrayList<>(attributes);
279  }
280
281
282
283  /**
284   * Creates a new add request with the provided DN and set of attributes.
285   *
286   * @param  dn          The DN for the entry to add.  It must not be
287   *                     {@code null}.
288   * @param  attributes  The set of attributes to include in the entry to add.
289   *                     It must not be {@code null}.
290   * @param  controls    The set of controls to include in the request.
291   */
292  public AddRequest(final DN dn, final Collection<Attribute> attributes,
293                    final Control[] controls)
294  {
295    super(controls);
296
297    Validator.ensureNotNull(dn, attributes);
298
299    this.dn         = dn.toString();
300    this.attributes = new ArrayList<>(attributes);
301  }
302
303
304
305  /**
306   * Creates a new add request to add the provided entry.
307   *
308   * @param  entry  The entry to be added.  It must not be {@code null}.
309   */
310  public AddRequest(final Entry entry)
311  {
312    super(null);
313
314    Validator.ensureNotNull(entry);
315
316    dn         = entry.getDN();
317    attributes = new ArrayList<>(entry.getAttributes());
318  }
319
320
321
322  /**
323   * Creates a new add request to add the provided entry.
324   *
325   * @param  entry     The entry to be added.  It must not be {@code null}.
326   * @param  controls  The set of controls to include in the request.
327   */
328  public AddRequest(final Entry entry, final Control[] controls)
329  {
330    super(controls);
331
332    Validator.ensureNotNull(entry);
333
334    dn         = entry.getDN();
335    attributes = new ArrayList<>(entry.getAttributes());
336  }
337
338
339
340  /**
341   * Creates a new add request with the provided entry in LDIF form.
342   *
343   * @param  ldifLines  The lines that comprise the LDIF representation of the
344   *                    entry to add.  It must not be {@code null} or empty.  It
345   *                    may represent a standard LDIF entry, or it may represent
346   *                    an LDIF add change record (optionally including
347   *                    controls).
348   *
349   * @throws  LDIFException  If the provided LDIF data cannot be decoded as an
350   *                         entry.
351   */
352  public AddRequest(final String... ldifLines)
353         throws LDIFException
354  {
355    super(null);
356
357    final LDIFChangeRecord changeRecord =
358         LDIFReader.decodeChangeRecord(true, ldifLines);
359    if (changeRecord instanceof LDIFAddChangeRecord)
360    {
361      dn = changeRecord.getDN();
362      attributes = new ArrayList<>(Arrays.asList(
363           ((LDIFAddChangeRecord) changeRecord).getAttributes()));
364      setControls(changeRecord.getControls());
365    }
366    else
367    {
368      throw new LDIFException(
369           ERR_ADD_INAPPROPRIATE_CHANGE_TYPE.get(
370                changeRecord.getChangeType().name()),
371           0L, true, Arrays.asList(ldifLines), null);
372    }
373  }
374
375
376
377  /**
378   * {@inheritDoc}
379   */
380  @Override()
381  public String getDN()
382  {
383    return dn;
384  }
385
386
387
388  /**
389   * Specifies the DN for this add request.
390   *
391   * @param  dn  The DN for this add request.  It must not be {@code null}.
392   */
393  public void setDN(final String dn)
394  {
395    Validator.ensureNotNull(dn);
396
397    this.dn = dn;
398  }
399
400
401
402  /**
403   * Specifies the DN for this add request.
404   *
405   * @param  dn  The DN for this add request.  It must not be {@code null}.
406   */
407  public void setDN(final DN dn)
408  {
409    Validator.ensureNotNull(dn);
410
411    this.dn = dn.toString();
412  }
413
414
415
416  /**
417   * {@inheritDoc}
418   */
419  @Override()
420  public List<Attribute> getAttributes()
421  {
422    return Collections.unmodifiableList(attributes);
423  }
424
425
426
427  /**
428   * {@inheritDoc}
429   */
430  @Override()
431  public Attribute getAttribute(final String attributeName)
432  {
433    Validator.ensureNotNull(attributeName);
434
435    for (final Attribute a : attributes)
436    {
437      if (a.getName().equalsIgnoreCase(attributeName))
438      {
439        return a;
440      }
441    }
442
443    return null;
444  }
445
446
447
448  /**
449   * {@inheritDoc}
450   */
451  @Override()
452  public boolean hasAttribute(final String attributeName)
453  {
454    return (getAttribute(attributeName) != null);
455  }
456
457
458
459  /**
460   * {@inheritDoc}
461   */
462  @Override()
463  public boolean hasAttribute(final Attribute attribute)
464  {
465    Validator.ensureNotNull(attribute);
466
467    final Attribute a = getAttribute(attribute.getName());
468    return ((a != null) && attribute.equals(a));
469  }
470
471
472
473  /**
474   * {@inheritDoc}
475   */
476  @Override()
477  public boolean hasAttributeValue(final String attributeName,
478                                   final String attributeValue)
479  {
480    Validator.ensureNotNull(attributeName, attributeValue);
481
482    final Attribute a = getAttribute(attributeName);
483    return ((a != null) && a.hasValue(attributeValue));
484  }
485
486
487
488  /**
489   * {@inheritDoc}
490   */
491  @Override()
492  public boolean hasAttributeValue(final String attributeName,
493                                   final String attributeValue,
494                                   final MatchingRule matchingRule)
495  {
496    Validator.ensureNotNull(attributeName, attributeValue);
497
498    final Attribute a = getAttribute(attributeName);
499    return ((a != null) && a.hasValue(attributeValue, matchingRule));
500  }
501
502
503
504  /**
505   * {@inheritDoc}
506   */
507  @Override()
508  public boolean hasAttributeValue(final String attributeName,
509                                   final byte[] attributeValue)
510  {
511    Validator.ensureNotNull(attributeName, attributeValue);
512
513    final Attribute a = getAttribute(attributeName);
514    return ((a != null) && a.hasValue(attributeValue));
515  }
516
517
518
519  /**
520   * {@inheritDoc}
521   */
522  @Override()
523  public boolean hasAttributeValue(final String attributeName,
524                                   final byte[] attributeValue,
525                                   final MatchingRule matchingRule)
526  {
527    Validator.ensureNotNull(attributeName, attributeValue);
528
529    final Attribute a = getAttribute(attributeName);
530    return ((a != null) && a.hasValue(attributeValue, matchingRule));
531  }
532
533
534
535  /**
536   * {@inheritDoc}
537   */
538  @Override()
539  public boolean hasObjectClass(final String objectClassName)
540  {
541    return hasAttributeValue("objectClass", objectClassName);
542  }
543
544
545
546  /**
547   * {@inheritDoc}
548   */
549  @Override()
550  public Entry toEntry()
551  {
552    return new Entry(dn, attributes);
553  }
554
555
556
557  /**
558   * Specifies the set of attributes for this add request.  It must not be
559   * {@code null}.
560   *
561   * @param  attributes  The set of attributes for this add request.
562   */
563  public void setAttributes(final Attribute[] attributes)
564  {
565    Validator.ensureNotNull(attributes);
566
567    this.attributes.clear();
568    this.attributes.addAll(Arrays.asList(attributes));
569  }
570
571
572
573  /**
574   * Specifies the set of attributes for this add request.  It must not be
575   * {@code null}.
576   *
577   * @param  attributes  The set of attributes for this add request.
578   */
579  public void setAttributes(final Collection<Attribute> attributes)
580  {
581    Validator.ensureNotNull(attributes);
582
583    this.attributes.clear();
584    this.attributes.addAll(attributes);
585  }
586
587
588
589  /**
590   * Adds the provided attribute to the entry to add.
591   *
592   * @param  attribute  The attribute to be added to the entry to add.  It must
593   *                    not be {@code null}.
594   */
595  public void addAttribute(final Attribute attribute)
596  {
597    Validator.ensureNotNull(attribute);
598
599    for (int i=0 ; i < attributes.size(); i++)
600    {
601      final Attribute a = attributes.get(i);
602      if (a.getName().equalsIgnoreCase(attribute.getName()))
603      {
604        attributes.set(i, Attribute.mergeAttributes(a, attribute));
605        return;
606      }
607    }
608
609    attributes.add(attribute);
610  }
611
612
613
614  /**
615   * Adds the provided attribute to the entry to add.
616   *
617   * @param  name   The name of the attribute to add.  It must not be
618   *                {@code null}.
619   * @param  value  The value for the attribute to add.  It must not be
620   *                {@code null}.
621   */
622  public void addAttribute(final String name, final String value)
623  {
624    Validator.ensureNotNull(name, value);
625    addAttribute(new Attribute(name, value));
626  }
627
628
629
630  /**
631   * Adds the provided attribute to the entry to add.
632   *
633   * @param  name   The name of the attribute to add.  It must not be
634   *                {@code null}.
635   * @param  value  The value for the attribute to add.  It must not be
636   *                {@code null}.
637   */
638  public void addAttribute(final String name, final byte[] value)
639  {
640    Validator.ensureNotNull(name, value);
641    addAttribute(new Attribute(name, value));
642  }
643
644
645
646  /**
647   * Adds the provided attribute to the entry to add.
648   *
649   * @param  name    The name of the attribute to add.  It must not be
650   *                 {@code null}.
651   * @param  values  The set of values for the attribute to add.  It must not be
652   *                 {@code null}.
653   */
654  public void addAttribute(final String name, final String... values)
655  {
656    Validator.ensureNotNull(name, values);
657    addAttribute(new Attribute(name, values));
658  }
659
660
661
662  /**
663   * Adds the provided attribute to the entry to add.
664   *
665   * @param  name    The name of the attribute to add.  It must not be
666   *                 {@code null}.
667   * @param  values  The set of values for the attribute to add.  It must not be
668   *                 {@code null}.
669   */
670  public void addAttribute(final String name, final byte[]... values)
671  {
672    Validator.ensureNotNull(name, values);
673    addAttribute(new Attribute(name, values));
674  }
675
676
677
678  /**
679   * Removes the attribute with the specified name from the entry to add.
680   *
681   * @param  attributeName  The name of the attribute to remove.  It must not be
682   *                        {@code null}.
683   *
684   * @return  {@code true} if the attribute was removed from this add request,
685   *          or {@code false} if the add request did not include the specified
686   *          attribute.
687   */
688  public boolean removeAttribute(final String attributeName)
689  {
690    Validator.ensureNotNull(attributeName);
691
692    final Iterator<Attribute> iterator = attributes.iterator();
693    while (iterator.hasNext())
694    {
695      final Attribute a = iterator.next();
696      if (a.getName().equalsIgnoreCase(attributeName))
697      {
698        iterator.remove();
699        return true;
700      }
701    }
702
703    return false;
704  }
705
706
707
708  /**
709   * Removes the specified attribute value from this add request.
710   *
711   * @param  name   The name of the attribute to remove.  It must not be
712   *                {@code null}.
713   * @param  value  The value of the attribute to remove.  It must not be
714   *                {@code null}.
715   *
716   * @return  {@code true} if the attribute value was removed from this add
717   *          request, or {@code false} if the add request did not include the
718   *          specified attribute value.
719   */
720  public boolean removeAttributeValue(final String name, final String value)
721  {
722    Validator.ensureNotNull(name, value);
723
724    int pos = -1;
725    for (int i=0; i < attributes.size(); i++)
726    {
727      final Attribute a = attributes.get(i);
728      if (a.getName().equalsIgnoreCase(name))
729      {
730        pos = i;
731        break;
732      }
733    }
734
735    if (pos < 0)
736    {
737      return false;
738    }
739
740    final Attribute a = attributes.get(pos);
741    final Attribute newAttr =
742         Attribute.removeValues(a, new Attribute(name, value));
743
744    if (a.getRawValues().length == newAttr.getRawValues().length)
745    {
746      return false;
747    }
748
749    if (newAttr.getRawValues().length == 0)
750    {
751      attributes.remove(pos);
752    }
753    else
754    {
755      attributes.set(pos, newAttr);
756    }
757
758    return true;
759  }
760
761
762
763  /**
764   * Removes the specified attribute value from this add request.
765   *
766   * @param  name   The name of the attribute to remove.  It must not be
767   *                {@code null}.
768   * @param  value  The value of the attribute to remove.  It must not be
769   *                {@code null}.
770   *
771   * @return  {@code true} if the attribute value was removed from this add
772   *          request, or {@code false} if the add request did not include the
773   *          specified attribute value.
774   */
775  public boolean removeAttribute(final String name, final byte[] value)
776  {
777    Validator.ensureNotNull(name, value);
778
779    int pos = -1;
780    for (int i=0; i < attributes.size(); i++)
781    {
782      final Attribute a = attributes.get(i);
783      if (a.getName().equalsIgnoreCase(name))
784      {
785        pos = i;
786        break;
787      }
788    }
789
790    if (pos < 0)
791    {
792      return false;
793    }
794
795    final Attribute a = attributes.get(pos);
796    final Attribute newAttr =
797         Attribute.removeValues(a, new Attribute(name, value));
798
799    if (a.getRawValues().length == newAttr.getRawValues().length)
800    {
801      return false;
802    }
803
804    if (newAttr.getRawValues().length == 0)
805    {
806      attributes.remove(pos);
807    }
808    else
809    {
810      attributes.set(pos, newAttr);
811    }
812
813    return true;
814  }
815
816
817
818  /**
819   * Replaces the specified attribute in the entry to add.  If no attribute with
820   * the given name exists in the add request, it will be added.
821   *
822   * @param  attribute  The attribute to be replaced in this add request.  It
823   *                    must not be {@code null}.
824   */
825  public void replaceAttribute(final Attribute attribute)
826  {
827    Validator.ensureNotNull(attribute);
828
829    for (int i=0; i < attributes.size(); i++)
830    {
831      if (attributes.get(i).getName().equalsIgnoreCase(attribute.getName()))
832      {
833        attributes.set(i, attribute);
834        return;
835      }
836    }
837
838    attributes.add(attribute);
839  }
840
841
842
843  /**
844   * Replaces the specified attribute in the entry to add.  If no attribute with
845   * the given name exists in the add request, it will be added.
846   *
847   * @param  name   The name of the attribute to be replaced.  It must not be
848   *                {@code null}.
849   * @param  value  The new value for the attribute.  It must not be
850   *                {@code null}.
851   */
852  public void replaceAttribute(final String name, final String value)
853  {
854    Validator.ensureNotNull(name, value);
855
856    for (int i=0; i < attributes.size(); i++)
857    {
858      if (attributes.get(i).getName().equalsIgnoreCase(name))
859      {
860        attributes.set(i, new Attribute(name, value));
861        return;
862      }
863    }
864
865    attributes.add(new Attribute(name, value));
866  }
867
868
869
870  /**
871   * Replaces the specified attribute in the entry to add.  If no attribute with
872   * the given name exists in the add request, it will be added.
873   *
874   * @param  name   The name of the attribute to be replaced.  It must not be
875   *                {@code null}.
876   * @param  value  The new value for the attribute.  It must not be
877   *                {@code null}.
878   */
879  public void replaceAttribute(final String name, final byte[] value)
880  {
881    Validator.ensureNotNull(name, value);
882
883    for (int i=0; i < attributes.size(); i++)
884    {
885      if (attributes.get(i).getName().equalsIgnoreCase(name))
886      {
887        attributes.set(i, new Attribute(name, value));
888        return;
889      }
890    }
891
892    attributes.add(new Attribute(name, value));
893  }
894
895
896
897  /**
898   * Replaces the specified attribute in the entry to add.  If no attribute with
899   * the given name exists in the add request, it will be added.
900   *
901   * @param  name    The name of the attribute to be replaced.  It must not be
902   *                 {@code null}.
903   * @param  values  The new set of values for the attribute.  It must not be
904   *                 {@code null}.
905   */
906  public void replaceAttribute(final String name, final String... values)
907  {
908    Validator.ensureNotNull(name, values);
909
910    for (int i=0; i < attributes.size(); i++)
911    {
912      if (attributes.get(i).getName().equalsIgnoreCase(name))
913      {
914        attributes.set(i, new Attribute(name, values));
915        return;
916      }
917    }
918
919    attributes.add(new Attribute(name, values));
920  }
921
922
923
924  /**
925   * Replaces the specified attribute in the entry to add.  If no attribute with
926   * the given name exists in the add request, it will be added.
927   *
928   * @param  name    The name of the attribute to be replaced.  It must not be
929   *                 {@code null}.
930   * @param  values  The new set of values for the attribute.  It must not be
931   *                 {@code null}.
932   */
933  public void replaceAttribute(final String name, final byte[]... values)
934  {
935    Validator.ensureNotNull(name, values);
936
937    for (int i=0; i < attributes.size(); i++)
938    {
939      if (attributes.get(i).getName().equalsIgnoreCase(name))
940      {
941        attributes.set(i, new Attribute(name, values));
942        return;
943      }
944    }
945
946    attributes.add(new Attribute(name, values));
947  }
948
949
950
951  /**
952   * {@inheritDoc}
953   */
954  @Override()
955  public byte getProtocolOpType()
956  {
957    return LDAPMessage.PROTOCOL_OP_TYPE_ADD_REQUEST;
958  }
959
960
961
962  /**
963   * {@inheritDoc}
964   */
965  @Override()
966  public void writeTo(final ASN1Buffer buffer)
967  {
968    final ASN1BufferSequence requestSequence =
969         buffer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_ADD_REQUEST);
970    buffer.addOctetString(dn);
971
972    final ASN1BufferSequence attrSequence = buffer.beginSequence();
973    for (final Attribute a : attributes)
974    {
975      a.writeTo(buffer);
976    }
977    attrSequence.end();
978
979    requestSequence.end();
980  }
981
982
983
984  /**
985   * Encodes the add request protocol op to an ASN.1 element.
986   *
987   * @return  The ASN.1 element with the encoded add request protocol op.
988   */
989  @Override()
990  public ASN1Element encodeProtocolOp()
991  {
992    // Create the add request protocol op.
993    final ASN1Element[] attrElements = new ASN1Element[attributes.size()];
994    for (int i=0; i < attrElements.length; i++)
995    {
996      attrElements[i] = attributes.get(i).encode();
997    }
998
999    final ASN1Element[] addRequestElements =
1000    {
1001      new ASN1OctetString(dn),
1002      new ASN1Sequence(attrElements)
1003    };
1004
1005    return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_ADD_REQUEST,
1006                            addRequestElements);
1007  }
1008
1009
1010
1011  /**
1012   * Sends this add request to the directory server over the provided connection
1013   * and returns the associated response.
1014   *
1015   * @param  connection  The connection to use to communicate with the directory
1016   *                     server.
1017   * @param  depth       The current referral depth for this request.  It should
1018   *                     always be one for the initial request, and should only
1019   *                     be incremented when following referrals.
1020   *
1021   * @return  An LDAP result object that provides information about the result
1022   *          of the add processing.
1023   *
1024   * @throws  LDAPException  If a problem occurs while sending the request or
1025   *                         reading the response.
1026   */
1027  @Override()
1028  protected LDAPResult process(final LDAPConnection connection, final int depth)
1029            throws LDAPException
1030  {
1031    if (connection.synchronousMode())
1032    {
1033      @SuppressWarnings("deprecation")
1034      final boolean autoReconnect =
1035           connection.getConnectionOptions().autoReconnect();
1036      return processSync(connection, depth, autoReconnect);
1037    }
1038
1039    final long requestTime = System.nanoTime();
1040    processAsync(connection, null);
1041
1042    try
1043    {
1044      // Wait for and process the response.
1045      final LDAPResponse response;
1046      try
1047      {
1048        final long responseTimeout = getResponseTimeoutMillis(connection);
1049        if (responseTimeout > 0)
1050        {
1051          response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
1052        }
1053        else
1054        {
1055          response = responseQueue.take();
1056        }
1057      }
1058      catch (final InterruptedException ie)
1059      {
1060        Debug.debugException(ie);
1061        Thread.currentThread().interrupt();
1062        throw new LDAPException(ResultCode.LOCAL_ERROR,
1063             ERR_ADD_INTERRUPTED.get(connection.getHostPort()), ie);
1064      }
1065
1066      return handleResponse(connection, response, requestTime, depth, false);
1067    }
1068    finally
1069    {
1070      connection.deregisterResponseAcceptor(messageID);
1071    }
1072  }
1073
1074
1075
1076  /**
1077   * Sends this add request to the directory server over the provided connection
1078   * and returns the message ID for the request.
1079   *
1080   * @param  connection      The connection to use to communicate with the
1081   *                         directory server.
1082   * @param  resultListener  The async result listener that is to be notified
1083   *                         when the response is received.  It may be
1084   *                         {@code null} only if the result is to be processed
1085   *                         by this class.
1086   *
1087   * @return  The async request ID created for the operation, or {@code null} if
1088   *          the provided {@code resultListener} is {@code null} and the
1089   *          operation will not actually be processed asynchronously.
1090   *
1091   * @throws  LDAPException  If a problem occurs while sending the request.
1092   */
1093  AsyncRequestID processAsync(final LDAPConnection connection,
1094                              final AsyncResultListener resultListener)
1095                 throws LDAPException
1096  {
1097    // Create the LDAP message.
1098    messageID = connection.nextMessageID();
1099    final LDAPMessage message =
1100         new LDAPMessage(messageID,  this, getControls());
1101
1102
1103    // If the provided async result listener is {@code null}, then we'll use
1104    // this class as the message acceptor.  Otherwise, create an async helper
1105    // and use it as the message acceptor.
1106    final AsyncRequestID asyncRequestID;
1107    final long timeout = getResponseTimeoutMillis(connection);
1108    if (resultListener == null)
1109    {
1110      asyncRequestID = null;
1111      connection.registerResponseAcceptor(messageID, this);
1112    }
1113    else
1114    {
1115      final AsyncHelper helper = new AsyncHelper(connection, OperationType.ADD,
1116           messageID, resultListener, getIntermediateResponseListener());
1117      connection.registerResponseAcceptor(messageID, helper);
1118      asyncRequestID = helper.getAsyncRequestID();
1119
1120      if (timeout > 0L)
1121      {
1122        final Timer timer = connection.getTimer();
1123        final AsyncTimeoutTimerTask timerTask =
1124             new AsyncTimeoutTimerTask(helper);
1125        timer.schedule(timerTask, timeout);
1126        asyncRequestID.setTimerTask(timerTask);
1127      }
1128    }
1129
1130
1131    // Send the request to the server.
1132    try
1133    {
1134      Debug.debugLDAPRequest(Level.INFO, this, messageID, connection);
1135
1136      final LDAPConnectionLogger logger =
1137           connection.getConnectionOptions().getConnectionLogger();
1138      if (logger != null)
1139      {
1140        logger.logAddRequest(connection, messageID, this);
1141      }
1142
1143      connection.getConnectionStatistics().incrementNumAddRequests();
1144      connection.sendMessage(message, timeout);
1145      return asyncRequestID;
1146    }
1147    catch (final LDAPException le)
1148    {
1149      Debug.debugException(le);
1150
1151      connection.deregisterResponseAcceptor(messageID);
1152      throw le;
1153    }
1154  }
1155
1156
1157
1158  /**
1159   * Processes this add operation in synchronous mode, in which the same thread
1160   * will send the request and read the response.
1161   *
1162   * @param  connection  The connection to use to communicate with the directory
1163   *                     server.
1164   * @param  depth       The current referral depth for this request.  It should
1165   *                     always be one for the initial request, and should only
1166   *                     be incremented when following referrals.
1167   * @param  allowRetry  Indicates whether the request may be re-tried on a
1168   *                     re-established connection if the initial attempt fails
1169   *                     in a way that indicates the connection is no longer
1170   *                     valid and autoReconnect is true.
1171   *
1172   * @return  An LDAP result object that provides information about the result
1173   *          of the add processing.
1174   *
1175   * @throws  LDAPException  If a problem occurs while sending the request or
1176   *                         reading the response.
1177   */
1178  private LDAPResult processSync(final LDAPConnection connection,
1179                                 final int depth, final boolean allowRetry)
1180          throws LDAPException
1181  {
1182    // Create the LDAP message.
1183    messageID = connection.nextMessageID();
1184    final LDAPMessage message =
1185         new LDAPMessage(messageID,  this, getControls());
1186
1187
1188    // Send the request to the server.
1189    final long requestTime = System.nanoTime();
1190    Debug.debugLDAPRequest(Level.INFO, this, messageID, connection);
1191
1192    final LDAPConnectionLogger logger =
1193         connection.getConnectionOptions().getConnectionLogger();
1194    if (logger != null)
1195    {
1196      logger.logAddRequest(connection, messageID, this);
1197    }
1198
1199    connection.getConnectionStatistics().incrementNumAddRequests();
1200    try
1201    {
1202      connection.sendMessage(message, getResponseTimeoutMillis(connection));
1203    }
1204    catch (final LDAPException le)
1205    {
1206      Debug.debugException(le);
1207
1208      if (allowRetry)
1209      {
1210        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
1211             le.getResultCode());
1212        if (retryResult != null)
1213        {
1214          return retryResult;
1215        }
1216      }
1217
1218      throw le;
1219    }
1220
1221    while (true)
1222    {
1223      final LDAPResponse response;
1224      try
1225      {
1226        response = connection.readResponse(messageID);
1227      }
1228      catch (final LDAPException le)
1229      {
1230        Debug.debugException(le);
1231
1232        if ((le.getResultCode() == ResultCode.TIMEOUT) &&
1233            connection.getConnectionOptions().abandonOnTimeout())
1234        {
1235          connection.abandon(messageID);
1236        }
1237
1238        if (allowRetry)
1239        {
1240          final LDAPResult retryResult = reconnectAndRetry(connection, depth,
1241               le.getResultCode());
1242          if (retryResult != null)
1243          {
1244            return retryResult;
1245          }
1246        }
1247
1248        throw le;
1249      }
1250
1251      if (response instanceof IntermediateResponse)
1252      {
1253        final IntermediateResponseListener listener =
1254             getIntermediateResponseListener();
1255        if (listener != null)
1256        {
1257          listener.intermediateResponseReturned(
1258               (IntermediateResponse) response);
1259        }
1260      }
1261      else
1262      {
1263        return handleResponse(connection, response, requestTime, depth,
1264             allowRetry);
1265      }
1266    }
1267  }
1268
1269
1270
1271  /**
1272   * Performs the necessary processing for handling a response.
1273   *
1274   * @param  connection   The connection used to read the response.
1275   * @param  response     The response to be processed.
1276   * @param  requestTime  The time the request was sent to the server.
1277   * @param  depth        The current referral depth for this request.  It
1278   *                      should always be one for the initial request, and
1279   *                      should only be incremented when following referrals.
1280   * @param  allowRetry   Indicates whether the request may be re-tried on a
1281   *                      re-established connection if the initial attempt fails
1282   *                      in a way that indicates the connection is no longer
1283   *                      valid and autoReconnect is true.
1284   *
1285   * @return  The add result.
1286   *
1287   * @throws  LDAPException  If a problem occurs.
1288   */
1289  private LDAPResult handleResponse(final LDAPConnection connection,
1290                                    final LDAPResponse response,
1291                                    final long requestTime, final int depth,
1292                                    final boolean allowRetry)
1293          throws LDAPException
1294  {
1295    if (response == null)
1296    {
1297      final long waitTime =
1298           StaticUtils.nanosToMillis(System.nanoTime() - requestTime);
1299      if (connection.getConnectionOptions().abandonOnTimeout())
1300      {
1301        connection.abandon(messageID);
1302      }
1303
1304      throw new LDAPException(ResultCode.TIMEOUT,
1305           ERR_ADD_CLIENT_TIMEOUT.get(waitTime, messageID, dn,
1306                connection.getHostPort()));
1307    }
1308
1309    connection.getConnectionStatistics().incrementNumAddResponses(
1310         System.nanoTime() - requestTime);
1311
1312    if (response instanceof ConnectionClosedResponse)
1313    {
1314      // The connection was closed while waiting for the response.
1315      if (allowRetry)
1316      {
1317        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
1318             ResultCode.SERVER_DOWN);
1319        if (retryResult != null)
1320        {
1321          return retryResult;
1322        }
1323      }
1324
1325      final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
1326      final String message = ccr.getMessage();
1327      if (message == null)
1328      {
1329        throw new LDAPException(ccr.getResultCode(),
1330             ERR_CONN_CLOSED_WAITING_FOR_ADD_RESPONSE.get(
1331                  connection.getHostPort(), toString()));
1332      }
1333      else
1334      {
1335        throw new LDAPException(ccr.getResultCode(),
1336             ERR_CONN_CLOSED_WAITING_FOR_ADD_RESPONSE_WITH_MESSAGE.get(
1337                  connection.getHostPort(), toString(), message));
1338      }
1339    }
1340
1341    final LDAPResult result = (LDAPResult) response;
1342    if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1343        followReferrals(connection))
1344    {
1345      if (depth >= connection.getConnectionOptions().getReferralHopLimit())
1346      {
1347        return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED,
1348                              ERR_TOO_MANY_REFERRALS.get(),
1349                              result.getMatchedDN(),
1350                              result.getReferralURLs(),
1351                              result.getResponseControls());
1352      }
1353
1354      return followReferral(result, connection, depth);
1355    }
1356    else
1357    {
1358      if (allowRetry)
1359      {
1360        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
1361             result.getResultCode());
1362        if (retryResult != null)
1363        {
1364          return retryResult;
1365        }
1366      }
1367
1368      return result;
1369    }
1370  }
1371
1372
1373
1374  /**
1375   * Attempts to re-establish the connection and retry processing this request
1376   * on it.
1377   *
1378   * @param  connection  The connection to be re-established.
1379   * @param  depth       The current referral depth for this request.  It should
1380   *                     always be one for the initial request, and should only
1381   *                     be incremented when following referrals.
1382   * @param  resultCode  The result code for the previous operation attempt.
1383   *
1384   * @return  The result from re-trying the add, or {@code null} if it could not
1385   *          be re-tried.
1386   */
1387  private LDAPResult reconnectAndRetry(final LDAPConnection connection,
1388                                       final int depth,
1389                                       final ResultCode resultCode)
1390  {
1391    try
1392    {
1393      // We will only want to retry for certain result codes that indicate a
1394      // connection problem.
1395      switch (resultCode.intValue())
1396      {
1397        case ResultCode.SERVER_DOWN_INT_VALUE:
1398        case ResultCode.DECODING_ERROR_INT_VALUE:
1399        case ResultCode.CONNECT_ERROR_INT_VALUE:
1400          connection.reconnect();
1401          return processSync(connection, depth, false);
1402      }
1403    }
1404    catch (final Exception e)
1405    {
1406      Debug.debugException(e);
1407    }
1408
1409    return null;
1410  }
1411
1412
1413
1414  /**
1415   * Attempts to follow a referral to perform an add operation in the target
1416   * server.
1417   *
1418   * @param  referralResult  The LDAP result object containing information about
1419   *                         the referral to follow.
1420   * @param  connection      The connection on which the referral was received.
1421   * @param  depth           The number of referrals followed in the course of
1422   *                         processing this request.
1423   *
1424   * @return  The result of attempting to process the add operation by following
1425   *          the referral.
1426   *
1427   * @throws  LDAPException  If a problem occurs while attempting to establish
1428   *                         the referral connection, sending the request, or
1429   *                         reading the result.
1430   */
1431  private LDAPResult followReferral(final LDAPResult referralResult,
1432                                    final LDAPConnection connection,
1433                                    final int depth)
1434          throws LDAPException
1435  {
1436    for (final String urlString : referralResult.getReferralURLs())
1437    {
1438      try
1439      {
1440        final LDAPURL referralURL = new LDAPURL(urlString);
1441        final String host = referralURL.getHost();
1442
1443        if (host == null)
1444        {
1445          // We can't handle a referral in which there is no host.
1446          continue;
1447        }
1448
1449        final AddRequest addRequest;
1450        if (referralURL.baseDNProvided())
1451        {
1452          addRequest = new AddRequest(referralURL.getBaseDN(), attributes,
1453                                      getControls());
1454        }
1455        else
1456        {
1457          addRequest = this;
1458        }
1459
1460        final LDAPConnection referralConn = getReferralConnector(connection).
1461             getReferralConnection(referralURL, connection);
1462        try
1463        {
1464          return addRequest.process(referralConn, (depth+1));
1465        }
1466        finally
1467        {
1468          referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1469          referralConn.close();
1470        }
1471      }
1472      catch (final LDAPException le)
1473      {
1474        Debug.debugException(le);
1475      }
1476    }
1477
1478    // If we've gotten here, then we could not follow any of the referral URLs,
1479    // so we'll just return the original referral result.
1480    return referralResult;
1481  }
1482
1483
1484
1485  /**
1486   * {@inheritDoc}
1487   */
1488  @Override()
1489  public int getLastMessageID()
1490  {
1491    return messageID;
1492  }
1493
1494
1495
1496  /**
1497   * {@inheritDoc}
1498   */
1499  @Override()
1500  public OperationType getOperationType()
1501  {
1502    return OperationType.ADD;
1503  }
1504
1505
1506
1507  /**
1508   * {@inheritDoc}
1509   */
1510  @Override()
1511  public AddRequest duplicate()
1512  {
1513    return duplicate(getControls());
1514  }
1515
1516
1517
1518  /**
1519   * {@inheritDoc}
1520   */
1521  @Override()
1522  public AddRequest duplicate(final Control[] controls)
1523  {
1524    final ArrayList<Attribute> attrs = new ArrayList<>(attributes);
1525    final AddRequest r = new AddRequest(dn, attrs, controls);
1526
1527    if (followReferralsInternal() != null)
1528    {
1529      r.setFollowReferrals(followReferralsInternal());
1530    }
1531
1532    if (getReferralConnectorInternal() != null)
1533    {
1534      r.setReferralConnector(getReferralConnectorInternal());
1535    }
1536
1537    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1538
1539    return r;
1540  }
1541
1542
1543
1544  /**
1545   * {@inheritDoc}
1546   */
1547  @InternalUseOnly()
1548  @Override()
1549  public void responseReceived(final LDAPResponse response)
1550         throws LDAPException
1551  {
1552    try
1553    {
1554      responseQueue.put(response);
1555    }
1556    catch (final Exception e)
1557    {
1558      Debug.debugException(e);
1559
1560      if (e instanceof InterruptedException)
1561      {
1562        Thread.currentThread().interrupt();
1563      }
1564
1565      throw new LDAPException(ResultCode.LOCAL_ERROR,
1566           ERR_EXCEPTION_HANDLING_RESPONSE.get(
1567                StaticUtils.getExceptionMessage(e)),
1568           e);
1569    }
1570  }
1571
1572
1573
1574  /**
1575   * {@inheritDoc}
1576   */
1577  @Override()
1578  public LDIFAddChangeRecord toLDIFChangeRecord()
1579  {
1580    return new LDIFAddChangeRecord(this);
1581  }
1582
1583
1584
1585  /**
1586   * {@inheritDoc}
1587   */
1588  @Override()
1589  public String[] toLDIF()
1590  {
1591    return toLDIFChangeRecord().toLDIF();
1592  }
1593
1594
1595
1596  /**
1597   * {@inheritDoc}
1598   */
1599  @Override()
1600  public String toLDIFString()
1601  {
1602    return toLDIFChangeRecord().toLDIFString();
1603  }
1604
1605
1606
1607  /**
1608   * {@inheritDoc}
1609   */
1610  @Override()
1611  public void toString(final StringBuilder buffer)
1612  {
1613    buffer.append("AddRequest(dn='");
1614    buffer.append(dn);
1615    buffer.append("', attrs={");
1616
1617    for (int i=0; i < attributes.size(); i++)
1618    {
1619      if (i > 0)
1620      {
1621        buffer.append(", ");
1622      }
1623
1624      buffer.append(attributes.get(i));
1625    }
1626    buffer.append('}');
1627
1628    final Control[] controls = getControls();
1629    if (controls.length > 0)
1630    {
1631      buffer.append(", controls={");
1632      for (int i=0; i < controls.length; i++)
1633      {
1634        if (i > 0)
1635        {
1636          buffer.append(", ");
1637        }
1638
1639        buffer.append(controls[i]);
1640      }
1641      buffer.append('}');
1642    }
1643
1644    buffer.append(')');
1645  }
1646
1647
1648
1649  /**
1650   * {@inheritDoc}
1651   */
1652  @Override()
1653  public void toCode(final List<String> lineList, final String requestID,
1654                     final int indentSpaces, final boolean includeProcessing)
1655  {
1656    // Create the request variable.
1657    final ArrayList<ToCodeArgHelper> constructorArgs =
1658         new ArrayList<>(attributes.size() + 1);
1659    constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN"));
1660
1661    boolean firstAttribute = true;
1662    for (final Attribute a : attributes)
1663    {
1664      final String comment;
1665      if (firstAttribute)
1666      {
1667        firstAttribute = false;
1668        comment = "Entry Attributes";
1669      }
1670      else
1671      {
1672        comment = null;
1673      }
1674
1675      constructorArgs.add(ToCodeArgHelper.createAttribute(a, comment));
1676    }
1677
1678    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "AddRequest",
1679         requestID + "Request", "new AddRequest", constructorArgs);
1680
1681
1682    // If there are any controls, then add them to the request.
1683    for (final Control c : getControls())
1684    {
1685      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1686           requestID + "Request.addControl",
1687           ToCodeArgHelper.createControl(c, null));
1688    }
1689
1690
1691    // Add lines for processing the request and obtaining the result.
1692    if (includeProcessing)
1693    {
1694      // Generate a string with the appropriate indent.
1695      final StringBuilder buffer = new StringBuilder();
1696      for (int i=0; i < indentSpaces; i++)
1697      {
1698        buffer.append(' ');
1699      }
1700      final String indent = buffer.toString();
1701
1702      lineList.add("");
1703      lineList.add(indent + "try");
1704      lineList.add(indent + '{');
1705      lineList.add(indent + "  LDAPResult " + requestID +
1706           "Result = connection.add(" + requestID + "Request);");
1707      lineList.add(indent + "  // The add was processed successfully.");
1708      lineList.add(indent + '}');
1709      lineList.add(indent + "catch (LDAPException e)");
1710      lineList.add(indent + '{');
1711      lineList.add(indent + "  // The add failed.  Maybe the following will " +
1712           "help explain why.");
1713      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
1714      lineList.add(indent + "  String message = e.getMessage();");
1715      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
1716      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
1717      lineList.add(indent + "  Control[] responseControls = " +
1718           "e.getResponseControls();");
1719      lineList.add(indent + '}');
1720    }
1721  }
1722}