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