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