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