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