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