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.Debug;
050import com.unboundid.util.InternalUseOnly;
051import com.unboundid.util.Mutable;
052import com.unboundid.util.StaticUtils;
053import com.unboundid.util.ThreadSafety;
054import com.unboundid.util.ThreadSafetyLevel;
055import com.unboundid.util.Validator;
056
057import static com.unboundid.ldap.sdk.LDAPMessages.*;
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<>();
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    Validator.ensureNotNull(dn, attributes);
127
128    this.dn = dn;
129
130    this.attributes = new ArrayList<>(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    Validator.ensureNotNull(dn, attributes);
151
152    this.dn = dn;
153
154    this.attributes = new ArrayList<>(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    Validator.ensureNotNull(dn, attributes);
173
174    this.dn         = dn;
175    this.attributes = new ArrayList<>(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    Validator.ensureNotNull(dn, attributes);
195
196    this.dn         = dn;
197    this.attributes = new ArrayList<>(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    Validator.ensureNotNull(dn, attributes);
215
216    this.dn = dn.toString();
217
218    this.attributes = new ArrayList<>(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    Validator.ensureNotNull(dn, attributes);
239
240    this.dn = dn.toString();
241
242    this.attributes = new ArrayList<>(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    Validator.ensureNotNull(dn, attributes);
261
262    this.dn         = dn.toString();
263    this.attributes = new ArrayList<>(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    Validator.ensureNotNull(dn, attributes);
283
284    this.dn         = dn.toString();
285    this.attributes = new ArrayList<>(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    Validator.ensureNotNull(entry);
300
301    dn         = entry.getDN();
302    attributes = new ArrayList<>(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    Validator.ensureNotNull(entry);
318
319    dn         = entry.getDN();
320    attributes = new ArrayList<>(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<>(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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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    Validator.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        Debug.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      Debug.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      Debug.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    Debug.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      Debug.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        Debug.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 =
1267           StaticUtils.nanosToMillis(System.nanoTime() - requestTime);
1268      if (connection.getConnectionOptions().abandonOnTimeout())
1269      {
1270        connection.abandon(messageID);
1271      }
1272
1273      throw new LDAPException(ResultCode.TIMEOUT,
1274           ERR_ADD_CLIENT_TIMEOUT.get(waitTime, messageID, dn,
1275                connection.getHostPort()));
1276    }
1277
1278    connection.getConnectionStatistics().incrementNumAddResponses(
1279         System.nanoTime() - requestTime);
1280
1281    if (response instanceof ConnectionClosedResponse)
1282    {
1283      // The connection was closed while waiting for the response.
1284      if (allowRetry)
1285      {
1286        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
1287             ResultCode.SERVER_DOWN);
1288        if (retryResult != null)
1289        {
1290          return retryResult;
1291        }
1292      }
1293
1294      final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
1295      final String message = ccr.getMessage();
1296      if (message == null)
1297      {
1298        throw new LDAPException(ccr.getResultCode(),
1299             ERR_CONN_CLOSED_WAITING_FOR_ADD_RESPONSE.get(
1300                  connection.getHostPort(), toString()));
1301      }
1302      else
1303      {
1304        throw new LDAPException(ccr.getResultCode(),
1305             ERR_CONN_CLOSED_WAITING_FOR_ADD_RESPONSE_WITH_MESSAGE.get(
1306                  connection.getHostPort(), toString(), message));
1307      }
1308    }
1309
1310    final LDAPResult result = (LDAPResult) response;
1311    if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1312        followReferrals(connection))
1313    {
1314      if (depth >= connection.getConnectionOptions().getReferralHopLimit())
1315      {
1316        return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED,
1317                              ERR_TOO_MANY_REFERRALS.get(),
1318                              result.getMatchedDN(),
1319                              result.getReferralURLs(),
1320                              result.getResponseControls());
1321      }
1322
1323      return followReferral(result, connection, depth);
1324    }
1325    else
1326    {
1327      if (allowRetry)
1328      {
1329        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
1330             result.getResultCode());
1331        if (retryResult != null)
1332        {
1333          return retryResult;
1334        }
1335      }
1336
1337      return result;
1338    }
1339  }
1340
1341
1342
1343  /**
1344   * Attempts to re-establish the connection and retry processing this request
1345   * on it.
1346   *
1347   * @param  connection  The connection to be re-established.
1348   * @param  depth       The current referral depth for this request.  It should
1349   *                     always be one for the initial request, and should only
1350   *                     be incremented when following referrals.
1351   * @param  resultCode  The result code for the previous operation attempt.
1352   *
1353   * @return  The result from re-trying the add, or {@code null} if it could not
1354   *          be re-tried.
1355   */
1356  private LDAPResult reconnectAndRetry(final LDAPConnection connection,
1357                                       final int depth,
1358                                       final ResultCode resultCode)
1359  {
1360    try
1361    {
1362      // We will only want to retry for certain result codes that indicate a
1363      // connection problem.
1364      switch (resultCode.intValue())
1365      {
1366        case ResultCode.SERVER_DOWN_INT_VALUE:
1367        case ResultCode.DECODING_ERROR_INT_VALUE:
1368        case ResultCode.CONNECT_ERROR_INT_VALUE:
1369          connection.reconnect();
1370          return processSync(connection, depth, false);
1371      }
1372    }
1373    catch (final Exception e)
1374    {
1375      Debug.debugException(e);
1376    }
1377
1378    return null;
1379  }
1380
1381
1382
1383  /**
1384   * Attempts to follow a referral to perform an add operation in the target
1385   * server.
1386   *
1387   * @param  referralResult  The LDAP result object containing information about
1388   *                         the referral to follow.
1389   * @param  connection      The connection on which the referral was received.
1390   * @param  depth           The number of referrals followed in the course of
1391   *                         processing this request.
1392   *
1393   * @return  The result of attempting to process the add operation by following
1394   *          the referral.
1395   *
1396   * @throws  LDAPException  If a problem occurs while attempting to establish
1397   *                         the referral connection, sending the request, or
1398   *                         reading the result.
1399   */
1400  private LDAPResult followReferral(final LDAPResult referralResult,
1401                                    final LDAPConnection connection,
1402                                    final int depth)
1403          throws LDAPException
1404  {
1405    for (final String urlString : referralResult.getReferralURLs())
1406    {
1407      try
1408      {
1409        final LDAPURL referralURL = new LDAPURL(urlString);
1410        final String host = referralURL.getHost();
1411
1412        if (host == null)
1413        {
1414          // We can't handle a referral in which there is no host.
1415          continue;
1416        }
1417
1418        final AddRequest addRequest;
1419        if (referralURL.baseDNProvided())
1420        {
1421          addRequest = new AddRequest(referralURL.getBaseDN(), attributes,
1422                                      getControls());
1423        }
1424        else
1425        {
1426          addRequest = this;
1427        }
1428
1429        final LDAPConnection referralConn = getReferralConnector(connection).
1430             getReferralConnection(referralURL, connection);
1431        try
1432        {
1433          return addRequest.process(referralConn, (depth+1));
1434        }
1435        finally
1436        {
1437          referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1438          referralConn.close();
1439        }
1440      }
1441      catch (final LDAPException le)
1442      {
1443        Debug.debugException(le);
1444      }
1445    }
1446
1447    // If we've gotten here, then we could not follow any of the referral URLs,
1448    // so we'll just return the original referral result.
1449    return referralResult;
1450  }
1451
1452
1453
1454  /**
1455   * {@inheritDoc}
1456   */
1457  @Override()
1458  public int getLastMessageID()
1459  {
1460    return messageID;
1461  }
1462
1463
1464
1465  /**
1466   * {@inheritDoc}
1467   */
1468  @Override()
1469  public OperationType getOperationType()
1470  {
1471    return OperationType.ADD;
1472  }
1473
1474
1475
1476  /**
1477   * {@inheritDoc}
1478   */
1479  @Override()
1480  public AddRequest duplicate()
1481  {
1482    return duplicate(getControls());
1483  }
1484
1485
1486
1487  /**
1488   * {@inheritDoc}
1489   */
1490  @Override()
1491  public AddRequest duplicate(final Control[] controls)
1492  {
1493    final ArrayList<Attribute> attrs = new ArrayList<>(attributes);
1494    final AddRequest r = new AddRequest(dn, attrs, controls);
1495
1496    if (followReferralsInternal() != null)
1497    {
1498      r.setFollowReferrals(followReferralsInternal());
1499    }
1500
1501    if (getReferralConnectorInternal() != null)
1502    {
1503      r.setReferralConnector(getReferralConnectorInternal());
1504    }
1505
1506    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1507
1508    return r;
1509  }
1510
1511
1512
1513  /**
1514   * {@inheritDoc}
1515   */
1516  @InternalUseOnly()
1517  @Override()
1518  public void responseReceived(final LDAPResponse response)
1519         throws LDAPException
1520  {
1521    try
1522    {
1523      responseQueue.put(response);
1524    }
1525    catch (final Exception e)
1526    {
1527      Debug.debugException(e);
1528
1529      if (e instanceof InterruptedException)
1530      {
1531        Thread.currentThread().interrupt();
1532      }
1533
1534      throw new LDAPException(ResultCode.LOCAL_ERROR,
1535           ERR_EXCEPTION_HANDLING_RESPONSE.get(
1536                StaticUtils.getExceptionMessage(e)),
1537           e);
1538    }
1539  }
1540
1541
1542
1543  /**
1544   * {@inheritDoc}
1545   */
1546  @Override()
1547  public LDIFAddChangeRecord toLDIFChangeRecord()
1548  {
1549    return new LDIFAddChangeRecord(this);
1550  }
1551
1552
1553
1554  /**
1555   * {@inheritDoc}
1556   */
1557  @Override()
1558  public String[] toLDIF()
1559  {
1560    return toLDIFChangeRecord().toLDIF();
1561  }
1562
1563
1564
1565  /**
1566   * {@inheritDoc}
1567   */
1568  @Override()
1569  public String toLDIFString()
1570  {
1571    return toLDIFChangeRecord().toLDIFString();
1572  }
1573
1574
1575
1576  /**
1577   * {@inheritDoc}
1578   */
1579  @Override()
1580  public void toString(final StringBuilder buffer)
1581  {
1582    buffer.append("AddRequest(dn='");
1583    buffer.append(dn);
1584    buffer.append("', attrs={");
1585
1586    for (int i=0; i < attributes.size(); i++)
1587    {
1588      if (i > 0)
1589      {
1590        buffer.append(", ");
1591      }
1592
1593      buffer.append(attributes.get(i));
1594    }
1595    buffer.append('}');
1596
1597    final Control[] controls = getControls();
1598    if (controls.length > 0)
1599    {
1600      buffer.append(", controls={");
1601      for (int i=0; i < controls.length; i++)
1602      {
1603        if (i > 0)
1604        {
1605          buffer.append(", ");
1606        }
1607
1608        buffer.append(controls[i]);
1609      }
1610      buffer.append('}');
1611    }
1612
1613    buffer.append(')');
1614  }
1615
1616
1617
1618  /**
1619   * {@inheritDoc}
1620   */
1621  @Override()
1622  public void toCode(final List<String> lineList, final String requestID,
1623                     final int indentSpaces, final boolean includeProcessing)
1624  {
1625    // Create the request variable.
1626    final ArrayList<ToCodeArgHelper> constructorArgs =
1627         new ArrayList<>(attributes.size() + 1);
1628    constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN"));
1629
1630    boolean firstAttribute = true;
1631    for (final Attribute a : attributes)
1632    {
1633      final String comment;
1634      if (firstAttribute)
1635      {
1636        firstAttribute = false;
1637        comment = "Entry Attributes";
1638      }
1639      else
1640      {
1641        comment = null;
1642      }
1643
1644      constructorArgs.add(ToCodeArgHelper.createAttribute(a, comment));
1645    }
1646
1647    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "AddRequest",
1648         requestID + "Request", "new AddRequest", constructorArgs);
1649
1650
1651    // If there are any controls, then add them to the request.
1652    for (final Control c : getControls())
1653    {
1654      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1655           requestID + "Request.addControl",
1656           ToCodeArgHelper.createControl(c, null));
1657    }
1658
1659
1660    // Add lines for processing the request and obtaining the result.
1661    if (includeProcessing)
1662    {
1663      // Generate a string with the appropriate indent.
1664      final StringBuilder buffer = new StringBuilder();
1665      for (int i=0; i < indentSpaces; i++)
1666      {
1667        buffer.append(' ');
1668      }
1669      final String indent = buffer.toString();
1670
1671      lineList.add("");
1672      lineList.add(indent + "try");
1673      lineList.add(indent + '{');
1674      lineList.add(indent + "  LDAPResult " + requestID +
1675           "Result = connection.add(" + requestID + "Request);");
1676      lineList.add(indent + "  // The add was processed successfully.");
1677      lineList.add(indent + '}');
1678      lineList.add(indent + "catch (LDAPException e)");
1679      lineList.add(indent + '{');
1680      lineList.add(indent + "  // The add failed.  Maybe the following will " +
1681           "help explain why.");
1682      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
1683      lineList.add(indent + "  String message = e.getMessage();");
1684      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
1685      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
1686      lineList.add(indent + "  Control[] responseControls = " +
1687           "e.getResponseControls();");
1688      lineList.add(indent + '}');
1689    }
1690  }
1691}