001    /*
002     * Copyright 2007-2017 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2017 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            Thread.currentThread().interrupt();
1032            throw new LDAPException(ResultCode.LOCAL_ERROR,
1033                 ERR_ADD_INTERRUPTED.get(connection.getHostPort()), ie);
1034          }
1035    
1036          return handleResponse(connection, response, requestTime, depth, false);
1037        }
1038        finally
1039        {
1040          connection.deregisterResponseAcceptor(messageID);
1041        }
1042      }
1043    
1044    
1045    
1046      /**
1047       * Sends this add request to the directory server over the provided connection
1048       * and returns the message ID for the request.
1049       *
1050       * @param  connection      The connection to use to communicate with the
1051       *                         directory server.
1052       * @param  resultListener  The async result listener that is to be notified
1053       *                         when the response is received.  It may be
1054       *                         {@code null} only if the result is to be processed
1055       *                         by this class.
1056       *
1057       * @return  The async request ID created for the operation, or {@code null} if
1058       *          the provided {@code resultListener} is {@code null} and the
1059       *          operation will not actually be processed asynchronously.
1060       *
1061       * @throws  LDAPException  If a problem occurs while sending the request.
1062       */
1063      AsyncRequestID processAsync(final LDAPConnection connection,
1064                                  final AsyncResultListener resultListener)
1065                     throws LDAPException
1066      {
1067        // Create the LDAP message.
1068        messageID = connection.nextMessageID();
1069        final LDAPMessage message =
1070             new LDAPMessage(messageID,  this, getControls());
1071    
1072    
1073        // If the provided async result listener is {@code null}, then we'll use
1074        // this class as the message acceptor.  Otherwise, create an async helper
1075        // and use it as the message acceptor.
1076        final AsyncRequestID asyncRequestID;
1077        if (resultListener == null)
1078        {
1079          asyncRequestID = null;
1080          connection.registerResponseAcceptor(messageID, this);
1081        }
1082        else
1083        {
1084          final AsyncHelper helper = new AsyncHelper(connection, OperationType.ADD,
1085               messageID, resultListener, getIntermediateResponseListener());
1086          connection.registerResponseAcceptor(messageID, helper);
1087          asyncRequestID = helper.getAsyncRequestID();
1088    
1089          final long timeout = getResponseTimeoutMillis(connection);
1090          if (timeout > 0L)
1091          {
1092            final Timer timer = connection.getTimer();
1093            final AsyncTimeoutTimerTask timerTask =
1094                 new AsyncTimeoutTimerTask(helper);
1095            timer.schedule(timerTask, timeout);
1096            asyncRequestID.setTimerTask(timerTask);
1097          }
1098        }
1099    
1100    
1101        // Send the request to the server.
1102        try
1103        {
1104          debugLDAPRequest(this);
1105          connection.getConnectionStatistics().incrementNumAddRequests();
1106          connection.sendMessage(message);
1107          return asyncRequestID;
1108        }
1109        catch (LDAPException le)
1110        {
1111          debugException(le);
1112    
1113          connection.deregisterResponseAcceptor(messageID);
1114          throw le;
1115        }
1116      }
1117    
1118    
1119    
1120      /**
1121       * Processes this add operation in synchronous mode, in which the same thread
1122       * will send the request and read the response.
1123       *
1124       * @param  connection  The connection to use to communicate with the directory
1125       *                     server.
1126       * @param  depth       The current referral depth for this request.  It should
1127       *                     always be one for the initial request, and should only
1128       *                     be incremented when following referrals.
1129       * @param  allowRetry  Indicates whether the request may be re-tried on a
1130       *                     re-established connection if the initial attempt fails
1131       *                     in a way that indicates the connection is no longer
1132       *                     valid and autoReconnect is true.
1133       *
1134       * @return  An LDAP result object that provides information about the result
1135       *          of the add processing.
1136       *
1137       * @throws  LDAPException  If a problem occurs while sending the request or
1138       *                         reading the response.
1139       */
1140      private LDAPResult processSync(final LDAPConnection connection,
1141                                     final int depth, final boolean allowRetry)
1142              throws LDAPException
1143      {
1144        // Create the LDAP message.
1145        messageID = connection.nextMessageID();
1146        final LDAPMessage message =
1147             new LDAPMessage(messageID,  this, getControls());
1148    
1149    
1150        // Set the appropriate timeout on the socket.
1151        try
1152        {
1153          connection.getConnectionInternals(true).getSocket().setSoTimeout(
1154               (int) getResponseTimeoutMillis(connection));
1155        }
1156        catch (Exception e)
1157        {
1158          debugException(e);
1159        }
1160    
1161    
1162        // Send the request to the server.
1163        final long requestTime = System.nanoTime();
1164        debugLDAPRequest(this);
1165        connection.getConnectionStatistics().incrementNumAddRequests();
1166        try
1167        {
1168          connection.sendMessage(message);
1169        }
1170        catch (final LDAPException le)
1171        {
1172          debugException(le);
1173    
1174          if (allowRetry)
1175          {
1176            final LDAPResult retryResult = reconnectAndRetry(connection, depth,
1177                 le.getResultCode());
1178            if (retryResult != null)
1179            {
1180              return retryResult;
1181            }
1182          }
1183    
1184          throw le;
1185        }
1186    
1187        while (true)
1188        {
1189          final LDAPResponse response;
1190          try
1191          {
1192            response = connection.readResponse(messageID);
1193          }
1194          catch (final LDAPException le)
1195          {
1196            debugException(le);
1197    
1198            if ((le.getResultCode() == ResultCode.TIMEOUT) &&
1199                connection.getConnectionOptions().abandonOnTimeout())
1200            {
1201              connection.abandon(messageID);
1202            }
1203    
1204            if (allowRetry)
1205            {
1206              final LDAPResult retryResult = reconnectAndRetry(connection, depth,
1207                   le.getResultCode());
1208              if (retryResult != null)
1209              {
1210                return retryResult;
1211              }
1212            }
1213    
1214            throw le;
1215          }
1216    
1217          if (response instanceof IntermediateResponse)
1218          {
1219            final IntermediateResponseListener listener =
1220                 getIntermediateResponseListener();
1221            if (listener != null)
1222            {
1223              listener.intermediateResponseReturned(
1224                   (IntermediateResponse) response);
1225            }
1226          }
1227          else
1228          {
1229            return handleResponse(connection, response, requestTime, depth,
1230                 allowRetry);
1231          }
1232        }
1233      }
1234    
1235    
1236    
1237      /**
1238       * Performs the necessary processing for handling a response.
1239       *
1240       * @param  connection   The connection used to read the response.
1241       * @param  response     The response to be processed.
1242       * @param  requestTime  The time the request was sent to the server.
1243       * @param  depth        The current referral depth for this request.  It
1244       *                      should always be one for the initial request, and
1245       *                      should only be incremented when following referrals.
1246       * @param  allowRetry   Indicates whether the request may be re-tried on a
1247       *                      re-established connection if the initial attempt fails
1248       *                      in a way that indicates the connection is no longer
1249       *                      valid and autoReconnect is true.
1250       *
1251       * @return  The add result.
1252       *
1253       * @throws  LDAPException  If a problem occurs.
1254       */
1255      private LDAPResult handleResponse(final LDAPConnection connection,
1256                                        final LDAPResponse response,
1257                                        final long requestTime, final int depth,
1258                                        final boolean allowRetry)
1259              throws LDAPException
1260      {
1261        if (response == null)
1262        {
1263          final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
1264          if (connection.getConnectionOptions().abandonOnTimeout())
1265          {
1266            connection.abandon(messageID);
1267          }
1268    
1269          throw new LDAPException(ResultCode.TIMEOUT,
1270               ERR_ADD_CLIENT_TIMEOUT.get(waitTime, messageID, dn,
1271                    connection.getHostPort()));
1272        }
1273    
1274        connection.getConnectionStatistics().incrementNumAddResponses(
1275             System.nanoTime() - requestTime);
1276    
1277        if (response instanceof ConnectionClosedResponse)
1278        {
1279          // The connection was closed while waiting for the response.
1280          if (allowRetry)
1281          {
1282            final LDAPResult retryResult = reconnectAndRetry(connection, depth,
1283                 ResultCode.SERVER_DOWN);
1284            if (retryResult != null)
1285            {
1286              return retryResult;
1287            }
1288          }
1289    
1290          final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
1291          final String message = ccr.getMessage();
1292          if (message == null)
1293          {
1294            throw new LDAPException(ccr.getResultCode(),
1295                 ERR_CONN_CLOSED_WAITING_FOR_ADD_RESPONSE.get(
1296                      connection.getHostPort(), toString()));
1297          }
1298          else
1299          {
1300            throw new LDAPException(ccr.getResultCode(),
1301                 ERR_CONN_CLOSED_WAITING_FOR_ADD_RESPONSE_WITH_MESSAGE.get(
1302                      connection.getHostPort(), toString(), message));
1303          }
1304        }
1305    
1306        final LDAPResult result = (LDAPResult) response;
1307        if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1308            followReferrals(connection))
1309        {
1310          if (depth >= connection.getConnectionOptions().getReferralHopLimit())
1311          {
1312            return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED,
1313                                  ERR_TOO_MANY_REFERRALS.get(),
1314                                  result.getMatchedDN(),
1315                                  result.getReferralURLs(),
1316                                  result.getResponseControls());
1317          }
1318    
1319          return followReferral(result, connection, depth);
1320        }
1321        else
1322        {
1323          if (allowRetry)
1324          {
1325            final LDAPResult retryResult = reconnectAndRetry(connection, depth,
1326                 result.getResultCode());
1327            if (retryResult != null)
1328            {
1329              return retryResult;
1330            }
1331          }
1332    
1333          return result;
1334        }
1335      }
1336    
1337    
1338    
1339      /**
1340       * Attempts to re-establish the connection and retry processing this request
1341       * on it.
1342       *
1343       * @param  connection  The connection to be re-established.
1344       * @param  depth       The current referral depth for this request.  It should
1345       *                     always be one for the initial request, and should only
1346       *                     be incremented when following referrals.
1347       * @param  resultCode  The result code for the previous operation attempt.
1348       *
1349       * @return  The result from re-trying the add, or {@code null} if it could not
1350       *          be re-tried.
1351       */
1352      private LDAPResult reconnectAndRetry(final LDAPConnection connection,
1353                                           final int depth,
1354                                           final ResultCode resultCode)
1355      {
1356        try
1357        {
1358          // We will only want to retry for certain result codes that indicate a
1359          // connection problem.
1360          switch (resultCode.intValue())
1361          {
1362            case ResultCode.SERVER_DOWN_INT_VALUE:
1363            case ResultCode.DECODING_ERROR_INT_VALUE:
1364            case ResultCode.CONNECT_ERROR_INT_VALUE:
1365              connection.reconnect();
1366              return processSync(connection, depth, false);
1367          }
1368        }
1369        catch (final Exception e)
1370        {
1371          debugException(e);
1372        }
1373    
1374        return null;
1375      }
1376    
1377    
1378    
1379      /**
1380       * Attempts to follow a referral to perform an add operation in the target
1381       * server.
1382       *
1383       * @param  referralResult  The LDAP result object containing information about
1384       *                         the referral to follow.
1385       * @param  connection      The connection on which the referral was received.
1386       * @param  depth           The number of referrals followed in the course of
1387       *                         processing this request.
1388       *
1389       * @return  The result of attempting to process the add operation by following
1390       *          the referral.
1391       *
1392       * @throws  LDAPException  If a problem occurs while attempting to establish
1393       *                         the referral connection, sending the request, or
1394       *                         reading the result.
1395       */
1396      private LDAPResult followReferral(final LDAPResult referralResult,
1397                                        final LDAPConnection connection,
1398                                        final int depth)
1399              throws LDAPException
1400      {
1401        for (final String urlString : referralResult.getReferralURLs())
1402        {
1403          try
1404          {
1405            final LDAPURL referralURL = new LDAPURL(urlString);
1406            final String host = referralURL.getHost();
1407    
1408            if (host == null)
1409            {
1410              // We can't handle a referral in which there is no host.
1411              continue;
1412            }
1413    
1414            final AddRequest addRequest;
1415            if (referralURL.baseDNProvided())
1416            {
1417              addRequest = new AddRequest(referralURL.getBaseDN(), attributes,
1418                                          getControls());
1419            }
1420            else
1421            {
1422              addRequest = this;
1423            }
1424    
1425            final LDAPConnection referralConn = connection.getReferralConnector().
1426                 getReferralConnection(referralURL, connection);
1427            try
1428            {
1429              return addRequest.process(referralConn, (depth+1));
1430            }
1431            finally
1432            {
1433              referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1434              referralConn.close();
1435            }
1436          }
1437          catch (LDAPException le)
1438          {
1439            debugException(le);
1440          }
1441        }
1442    
1443        // If we've gotten here, then we could not follow any of the referral URLs,
1444        // so we'll just return the original referral result.
1445        return referralResult;
1446      }
1447    
1448    
1449    
1450      /**
1451       * {@inheritDoc}
1452       */
1453      @Override()
1454      public int getLastMessageID()
1455      {
1456        return messageID;
1457      }
1458    
1459    
1460    
1461      /**
1462       * {@inheritDoc}
1463       */
1464      @Override()
1465      public OperationType getOperationType()
1466      {
1467        return OperationType.ADD;
1468      }
1469    
1470    
1471    
1472      /**
1473       * {@inheritDoc}
1474       */
1475      public AddRequest duplicate()
1476      {
1477        return duplicate(getControls());
1478      }
1479    
1480    
1481    
1482      /**
1483       * {@inheritDoc}
1484       */
1485      public AddRequest duplicate(final Control[] controls)
1486      {
1487        final ArrayList<Attribute> attrs = new ArrayList<Attribute>(attributes);
1488        final AddRequest r = new AddRequest(dn, attrs, controls);
1489    
1490        if (followReferralsInternal() != null)
1491        {
1492          r.setFollowReferrals(followReferralsInternal());
1493        }
1494    
1495        r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1496    
1497        return r;
1498      }
1499    
1500    
1501    
1502      /**
1503       * {@inheritDoc}
1504       */
1505      @InternalUseOnly()
1506      public void responseReceived(final LDAPResponse response)
1507             throws LDAPException
1508      {
1509        try
1510        {
1511          responseQueue.put(response);
1512        }
1513        catch (Exception e)
1514        {
1515          debugException(e);
1516    
1517          if (e instanceof InterruptedException)
1518          {
1519            Thread.currentThread().interrupt();
1520          }
1521    
1522          throw new LDAPException(ResultCode.LOCAL_ERROR,
1523               ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
1524        }
1525      }
1526    
1527    
1528    
1529      /**
1530       * {@inheritDoc}
1531       */
1532      public LDIFAddChangeRecord toLDIFChangeRecord()
1533      {
1534        return new LDIFAddChangeRecord(this);
1535      }
1536    
1537    
1538    
1539      /**
1540       * {@inheritDoc}
1541       */
1542      public String[] toLDIF()
1543      {
1544        return toLDIFChangeRecord().toLDIF();
1545      }
1546    
1547    
1548    
1549      /**
1550       * {@inheritDoc}
1551       */
1552      public String toLDIFString()
1553      {
1554        return toLDIFChangeRecord().toLDIFString();
1555      }
1556    
1557    
1558    
1559      /**
1560       * {@inheritDoc}
1561       */
1562      @Override()
1563      public void toString(final StringBuilder buffer)
1564      {
1565        buffer.append("AddRequest(dn='");
1566        buffer.append(dn);
1567        buffer.append("', attrs={");
1568    
1569        for (int i=0; i < attributes.size(); i++)
1570        {
1571          if (i > 0)
1572          {
1573            buffer.append(", ");
1574          }
1575    
1576          buffer.append(attributes.get(i));
1577        }
1578        buffer.append('}');
1579    
1580        final Control[] controls = getControls();
1581        if (controls.length > 0)
1582        {
1583          buffer.append(", controls={");
1584          for (int i=0; i < controls.length; i++)
1585          {
1586            if (i > 0)
1587            {
1588              buffer.append(", ");
1589            }
1590    
1591            buffer.append(controls[i]);
1592          }
1593          buffer.append('}');
1594        }
1595    
1596        buffer.append(')');
1597      }
1598    
1599    
1600    
1601      /**
1602       * {@inheritDoc}
1603       */
1604      public void toCode(final List<String> lineList, final String requestID,
1605                         final int indentSpaces, final boolean includeProcessing)
1606      {
1607        // Create the request variable.
1608        final ArrayList<ToCodeArgHelper> constructorArgs =
1609             new ArrayList<ToCodeArgHelper>(attributes.size() + 1);
1610        constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN"));
1611    
1612        boolean firstAttribute = true;
1613        for (final Attribute a : attributes)
1614        {
1615          final String comment;
1616          if (firstAttribute)
1617          {
1618            firstAttribute = false;
1619            comment = "Entry Attributes";
1620          }
1621          else
1622          {
1623            comment = null;
1624          }
1625    
1626          constructorArgs.add(ToCodeArgHelper.createAttribute(a, comment));
1627        }
1628    
1629        ToCodeHelper.generateMethodCall(lineList, indentSpaces, "AddRequest",
1630             requestID + "Request", "new AddRequest", constructorArgs);
1631    
1632    
1633        // If there are any controls, then add them to the request.
1634        for (final Control c : getControls())
1635        {
1636          ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1637               requestID + "Request.addControl",
1638               ToCodeArgHelper.createControl(c, null));
1639        }
1640    
1641    
1642        // Add lines for processing the request and obtaining the result.
1643        if (includeProcessing)
1644        {
1645          // Generate a string with the appropriate indent.
1646          final StringBuilder buffer = new StringBuilder();
1647          for (int i=0; i < indentSpaces; i++)
1648          {
1649            buffer.append(' ');
1650          }
1651          final String indent = buffer.toString();
1652    
1653          lineList.add("");
1654          lineList.add(indent + "try");
1655          lineList.add(indent + '{');
1656          lineList.add(indent + "  LDAPResult " + requestID +
1657               "Result = connection.add(" + requestID + "Request);");
1658          lineList.add(indent + "  // The add was processed successfully.");
1659          lineList.add(indent + '}');
1660          lineList.add(indent + "catch (LDAPException e)");
1661          lineList.add(indent + '{');
1662          lineList.add(indent + "  // The add failed.  Maybe the following will " +
1663               "help explain why.");
1664          lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
1665          lineList.add(indent + "  String message = e.getMessage();");
1666          lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
1667          lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
1668          lineList.add(indent + "  Control[] responseControls = " +
1669               "e.getResponseControls();");
1670          lineList.add(indent + '}');
1671        }
1672      }
1673    }