001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2010 Sun Microsystems, Inc.
015 * Portions copyright 2011-2015 ForgeRock AS.
016 */
017
018package org.forgerock.opendj.ldap.requests;
019
020import static com.forgerock.opendj.util.StaticUtils.EMPTY_BYTES;
021import static com.forgerock.opendj.util.StaticUtils.getBytes;
022import static com.forgerock.opendj.ldap.CoreMessages.WARN_READ_LDIF_RECORD_CHANGE_RECORD_WRONG_TYPE;
023
024import javax.net.ssl.SSLContext;
025import javax.security.auth.Subject;
026
027import org.forgerock.i18n.LocalizableMessage;
028import org.forgerock.i18n.LocalizedIllegalArgumentException;
029import org.forgerock.opendj.ldap.AttributeDescription;
030import org.forgerock.opendj.ldap.ByteString;
031import org.forgerock.opendj.ldap.DN;
032import org.forgerock.opendj.ldap.Entries;
033import org.forgerock.opendj.ldap.Entry;
034import org.forgerock.opendj.ldap.Filter;
035import org.forgerock.opendj.ldap.LinkedHashMapEntry;
036import org.forgerock.opendj.ldap.ModificationType;
037import org.forgerock.opendj.ldap.RDN;
038import org.forgerock.opendj.ldap.SearchScope;
039import org.forgerock.opendj.ldif.ChangeRecord;
040import org.forgerock.opendj.ldif.LDIFChangeRecordReader;
041import org.forgerock.util.Reject;
042
043/**
044 * This class contains various methods for creating and manipulating requests.
045 * <p>
046 * All copy constructors of the form {@code copyOfXXXRequest} perform deep
047 * copies of their request parameter. More specifically, any controls,
048 * modifications, and attributes contained within the response will be
049 * duplicated.
050 * <p>
051 * Similarly, all unmodifiable views of request returned by methods of the form
052 * {@code unmodifiableXXXRequest} return deep unmodifiable views of their
053 * request parameter. More specifically, any controls, modifications, and
054 * attributes contained within the returned request will be unmodifiable.
055 */
056public final class Requests {
057
058    // TODO: search request from LDAP URL.
059
060    // TODO: update request from persistent search result.
061
062    // TODO: synchronized requests?
063
064    /**
065     * Creates a new abandon request that is an exact copy of the provided
066     * request.
067     *
068     * @param request
069     *            The abandon request to be copied.
070     * @return The new abandon request.
071     * @throws NullPointerException
072     *             If {@code request} was {@code null}
073     */
074    public static AbandonRequest copyOfAbandonRequest(final AbandonRequest request) {
075        return new AbandonRequestImpl(request);
076    }
077
078    /**
079     * Creates a new add request that is an exact copy of the provided request.
080     *
081     * @param request
082     *            The add request to be copied.
083     * @return The new add request.
084     * @throws NullPointerException
085     *             If {@code request} was {@code null} .
086     */
087    public static AddRequest copyOfAddRequest(final AddRequest request) {
088        return new AddRequestImpl(request);
089    }
090
091    /**
092     * Creates a new anonymous SASL bind request that is an exact copy of the
093     * provided request.
094     *
095     * @param request
096     *            The anonymous SASL bind request to be copied.
097     * @return The new anonymous SASL bind request.
098     * @throws NullPointerException
099     *             If {@code request} was {@code null} .
100     */
101    public static AnonymousSASLBindRequest copyOfAnonymousSASLBindRequest(
102            final AnonymousSASLBindRequest request) {
103        return new AnonymousSASLBindRequestImpl(request);
104    }
105
106    /**
107     * Creates a new cancel extended request that is an exact copy of the
108     * provided request.
109     *
110     * @param request
111     *            The cancel extended request to be copied.
112     * @return The new cancel extended request.
113     * @throws NullPointerException
114     *             If {@code request} was {@code null} .
115     */
116    public static CancelExtendedRequest copyOfCancelExtendedRequest(
117            final CancelExtendedRequest request) {
118        return new CancelExtendedRequestImpl(request);
119    }
120
121    /**
122     * Creates a new compare request that is an exact copy of the provided
123     * request.
124     *
125     * @param request
126     *            The compare request to be copied.
127     * @return The new compare request.
128     * @throws NullPointerException
129     *             If {@code request} was {@code null} .
130     */
131    public static CompareRequest copyOfCompareRequest(final CompareRequest request) {
132        return new CompareRequestImpl(request);
133    }
134
135    /**
136     * Creates a new CRAM MD5 SASL bind request that is an exact copy of the
137     * provided request.
138     *
139     * @param request
140     *            The CRAM MD5 SASL bind request to be copied.
141     * @return The new CRAM-MD5 SASL bind request.
142     * @throws NullPointerException
143     *             If {@code request} was {@code null}.
144     */
145    public static CRAMMD5SASLBindRequest copyOfCRAMMD5SASLBindRequest(
146            final CRAMMD5SASLBindRequest request) {
147        return new CRAMMD5SASLBindRequestImpl(request);
148    }
149
150    /**
151     * Creates a new delete request that is an exact copy of the provided
152     * request.
153     *
154     * @param request
155     *            The add request to be copied.
156     * @return The new delete request.
157     * @throws NullPointerException
158     *             If {@code request} was {@code null}.
159     */
160    public static DeleteRequest copyOfDeleteRequest(final DeleteRequest request) {
161        return new DeleteRequestImpl(request);
162    }
163
164    /**
165     * Creates a new digest MD5 SASL bind request that is an exact copy of the
166     * provided request.
167     *
168     * @param request
169     *            The digest MD5 SASL bind request to be copied.
170     * @return The new DIGEST-MD5 SASL bind request.
171     * @throws NullPointerException
172     *             If {@code request} was {@code null}.
173     */
174    public static DigestMD5SASLBindRequest copyOfDigestMD5SASLBindRequest(
175            final DigestMD5SASLBindRequest request) {
176        return new DigestMD5SASLBindRequestImpl(request);
177    }
178
179    /**
180     * Creates a new external SASL bind request that is an exact copy of the
181     * provided request.
182     *
183     * @param request
184     *            The external SASL bind request to be copied.
185     * @return The new External SASL bind request.
186     * @throws NullPointerException
187     *             If {@code request} was {@code null} .
188     */
189    public static ExternalSASLBindRequest copyOfExternalSASLBindRequest(
190            final ExternalSASLBindRequest request) {
191        return new ExternalSASLBindRequestImpl(request);
192    }
193
194    /**
195     * Creates a new generic bind request that is an exact copy of the provided
196     * request.
197     *
198     * @param request
199     *            The generic bind request to be copied.
200     * @return The new generic bind request.
201     * @throws NullPointerException
202     *             If {@code request} was {@code null} .
203     */
204    public static GenericBindRequest copyOfGenericBindRequest(final GenericBindRequest request) {
205        return new GenericBindRequestImpl(request);
206    }
207
208    /**
209     * Creates a new generic extended request that is an exact copy of the
210     * provided request.
211     *
212     * @param request
213     *            The generic extended request to be copied.
214     * @return The new generic extended request.
215     * @throws NullPointerException
216     *             If {@code request} was {@code null} .
217     */
218    public static GenericExtendedRequest copyOfGenericExtendedRequest(
219            final GenericExtendedRequest request) {
220        return new GenericExtendedRequestImpl(request);
221    }
222
223    /**
224     * Creates a new GSSAPI SASL bind request that is an exact copy of the
225     * provided request.
226     *
227     * @param request
228     *            The GSSAPI SASL bind request to be copied.
229     * @return The new GSSAPI SASL bind request.
230     * @throws NullPointerException
231     *             If {@code request} was {@code null}.
232     */
233    public static GSSAPISASLBindRequest copyOfGSSAPISASLBindRequest(
234            final GSSAPISASLBindRequest request) {
235        return new GSSAPISASLBindRequestImpl(request);
236    }
237
238    /**
239     * Creates a new modify DN request that is an exact copy of the provided
240     * request.
241     *
242     * @param request
243     *            The modify DN request to be copied.
244     * @return The new modify DN request.
245     * @throws NullPointerException
246     *             If {@code request} was {@code null} .
247     */
248    public static ModifyDNRequest copyOfModifyDNRequest(final ModifyDNRequest request) {
249        return new ModifyDNRequestImpl(request);
250    }
251
252    /**
253     * Creates a new modify request that is an exact copy of the provided
254     * request.
255     *
256     * @param request
257     *            The modify request to be copied.
258     * @return The new modify request.
259     * @throws NullPointerException
260     *             If {@code request} was {@code null} .
261     */
262    public static ModifyRequest copyOfModifyRequest(final ModifyRequest request) {
263        return new ModifyRequestImpl(request);
264    }
265
266    /**
267     * Creates a new password modify extended request that is an exact copy of
268     * the provided request.
269     *
270     * @param request
271     *            The password modify extended request to be copied.
272     * @return The new password modify extended request.
273     * @throws NullPointerException
274     *             If {@code request} was {@code null} .
275     */
276    public static PasswordModifyExtendedRequest copyOfPasswordModifyExtendedRequest(
277            final PasswordModifyExtendedRequest request) {
278        return new PasswordModifyExtendedRequestImpl(request);
279    }
280
281    /**
282     * Creates a new plain SASL bind request that is an exact copy of the
283     * provided request.
284     *
285     * @param request
286     *            The plain SASL bind request to be copied.
287     * @return The new Plain SASL bind request.
288     * @throws NullPointerException
289     *             If {@code request} was {@code null} .
290     */
291    public static PlainSASLBindRequest copyOfPlainSASLBindRequest(final PlainSASLBindRequest request) {
292        return new PlainSASLBindRequestImpl(request);
293    }
294
295    /**
296     * Creates a new search request that is an exact copy of the provided
297     * request.
298     *
299     * @param request
300     *            The search request to be copied.
301     * @return The new search request.
302     * @throws NullPointerException
303     *             If {@code request} was {@code null} .
304     */
305    public static SearchRequest copyOfSearchRequest(final SearchRequest request) {
306        return new SearchRequestImpl(request);
307    }
308
309    /**
310     * Creates a new simple bind request that is an exact copy of the provided
311     * request.
312     *
313     * @param request
314     *            The simple bind request to be copied.
315     * @return The new simple bind request.
316     * @throws NullPointerException
317     *             If {@code request} was {@code null} .
318     */
319    public static SimpleBindRequest copyOfSimpleBindRequest(final SimpleBindRequest request) {
320        return new SimpleBindRequestImpl(request);
321    }
322
323    /**
324     * Creates a new startTLS extended request that is an exact copy of the
325     * provided request.
326     *
327     * @param request
328     *            The startTLS extended request to be copied.
329     * @return The new start TLS extended request.
330     * @throws NullPointerException
331     *             If {@code request} was {@code null} .
332     */
333    public static StartTLSExtendedRequest copyOfStartTLSExtendedRequest(
334            final StartTLSExtendedRequest request) {
335        return new StartTLSExtendedRequestImpl(request);
336    }
337
338    /**
339     * Creates a new unbind request that is an exact copy of the provided
340     * request.
341     *
342     * @param request
343     *            The unbind request to be copied.
344     * @return The new unbind request.
345     * @throws NullPointerException
346     *             If {@code request} was {@code null} .
347     */
348    public static UnbindRequest copyOfUnbindRequest(final UnbindRequest request) {
349        return new UnbindRequestImpl(request);
350    }
351
352    /**
353     * Creates a new Who Am I extended request that is an exact copy of the
354     * provided request.
355     *
356     * @param request
357     *            The who Am I extended request to be copied.
358     * @return The new Who Am I extended request.
359     * @throws NullPointerException
360     *             If {@code request} was {@code null} .
361     */
362    public static WhoAmIExtendedRequest copyOfWhoAmIExtendedRequest(
363            final WhoAmIExtendedRequest request) {
364        return new WhoAmIExtendedRequestImpl(request);
365    }
366
367    /**
368     * Creates a new abandon request using the provided message ID.
369     *
370     * @param requestID
371     *            The request ID of the request to be abandoned.
372     * @return The new abandon request.
373     */
374    public static AbandonRequest newAbandonRequest(final int requestID) {
375        return new AbandonRequestImpl(requestID);
376    }
377
378    /**
379     * Creates a new add request using the provided distinguished name.
380     *
381     * @param name
382     *            The distinguished name of the entry to be added.
383     * @return The new add request.
384     * @throws NullPointerException
385     *             If {@code name} was {@code null}.
386     */
387    public static AddRequest newAddRequest(final DN name) {
388        final Entry entry = new LinkedHashMapEntry().setName(name);
389        return new AddRequestImpl(entry);
390    }
391
392    /**
393     * Creates a new add request backed by the provided entry. Modifications
394     * made to {@code entry} will be reflected in the returned add request. The
395     * returned add request supports updates to its list of controls, as well as
396     * updates to the name and attributes if the underlying entry allows.
397     *
398     * @param entry
399     *            The entry to be added.
400     * @return The new add request.
401     * @throws NullPointerException
402     *             If {@code entry} was {@code null} .
403     */
404    public static AddRequest newAddRequest(final Entry entry) {
405        Reject.ifNull(entry);
406        return new AddRequestImpl(entry);
407    }
408
409    /**
410     * Creates a new add request using the provided distinguished name decoded
411     * using the default schema.
412     *
413     * @param name
414     *            The distinguished name of the entry to be added.
415     * @return The new add request.
416     * @throws LocalizedIllegalArgumentException
417     *             If {@code name} could not be decoded using the default
418     *             schema.
419     * @throws NullPointerException
420     *             If {@code name} was {@code null}.
421     */
422    public static AddRequest newAddRequest(final String name) {
423        final Entry entry = new LinkedHashMapEntry().setName(name);
424        return new AddRequestImpl(entry);
425    }
426
427    /**
428     * Creates a new add request using the provided lines of LDIF decoded using
429     * the default schema.
430     *
431     * @param ldifLines
432     *            Lines of LDIF containing an LDIF add change record or an LDIF
433     *            entry record.
434     * @return The new add request.
435     * @throws LocalizedIllegalArgumentException
436     *             If {@code ldifLines} was empty, or contained invalid LDIF, or
437     *             could not be decoded using the default schema.
438     * @throws NullPointerException
439     *             If {@code ldifLines} was {@code null} .
440     */
441    public static AddRequest newAddRequest(final String... ldifLines) {
442        // LDIF change record reader is tolerant to missing change types.
443        final ChangeRecord record = LDIFChangeRecordReader.valueOfLDIFChangeRecord(ldifLines);
444
445        if (record instanceof AddRequest) {
446            return (AddRequest) record;
447        } else {
448            // Wrong change type.
449            final LocalizableMessage message =
450                    WARN_READ_LDIF_RECORD_CHANGE_RECORD_WRONG_TYPE.get("add");
451            throw new LocalizedIllegalArgumentException(message);
452        }
453    }
454
455    /**
456     * Creates a new anonymous SASL bind request having the provided trace
457     * string.
458     *
459     * @param traceString
460     *            The trace information, which has no semantic value, and can be
461     *            used by administrators in order to identify the user.
462     * @return The new anonymous SASL bind request.
463     * @throws NullPointerException
464     *             If {@code traceString} was {@code null}.
465     */
466    public static AnonymousSASLBindRequest newAnonymousSASLBindRequest(final String traceString) {
467        return new AnonymousSASLBindRequestImpl(traceString);
468    }
469
470    /**
471     * Creates a new cancel extended request using the provided message ID.
472     *
473     * @param requestID
474     *            The request ID of the request to be abandoned.
475     * @return The new cancel extended request.
476     */
477    public static CancelExtendedRequest newCancelExtendedRequest(final int requestID) {
478        return new CancelExtendedRequestImpl(requestID);
479    }
480
481    /**
482     * Creates a new change record (an add, delete, modify, or modify DN
483     * request) using the provided lines of LDIF decoded using the default
484     * schema.
485     *
486     * @param ldifLines
487     *            Lines of LDIF containing an LDIF change record or an LDIF
488     *            entry record.
489     * @return The new change record.
490     * @throws LocalizedIllegalArgumentException
491     *             If {@code ldifLines} was empty, or contained invalid LDIF, or
492     *             could not be decoded using the default schema.
493     * @throws NullPointerException
494     *             If {@code ldifLines} was {@code null} .
495     */
496    public static ChangeRecord newChangeRecord(final String... ldifLines) {
497        // LDIF change record reader is tolerant to missing change types.
498        return LDIFChangeRecordReader.valueOfLDIFChangeRecord(ldifLines);
499    }
500
501    /**
502     * Creates a new compare request using the provided distinguished name,
503     * attribute name, and assertion value.
504     * <p>
505     * If the assertion value is not an instance of {@code ByteString} then it
506     * will be converted using the {@link ByteString#valueOfObject(Object)} method.
507     *
508     * @param name
509     *            The distinguished name of the entry to be compared.
510     * @param attributeDescription
511     *            The name of the attribute to be compared.
512     * @param assertionValue
513     *            The assertion value to be compared.
514     * @return The new compare request.
515     * @throws NullPointerException
516     *             If {@code name}, {@code attributeDescription}, or
517     *             {@code assertionValue} was {@code null}.
518     */
519    public static CompareRequest newCompareRequest(final DN name,
520            final AttributeDescription attributeDescription, final Object assertionValue) {
521        Reject.ifNull(name, attributeDescription, assertionValue);
522        return new CompareRequestImpl(name, attributeDescription, ByteString
523                .valueOfObject(assertionValue));
524    }
525
526    /**
527     * Creates a new compare request using the provided distinguished name,
528     * attribute name, and assertion value decoded using the default schema.
529     * <p>
530     * If the assertion value is not an instance of {@code ByteString} then it
531     * will be converted using the {@link ByteString#valueOfObject(Object)} method.
532     *
533     * @param name
534     *            The distinguished name of the entry to be compared.
535     * @param attributeDescription
536     *            The name of the attribute to be compared.
537     * @param assertionValue
538     *            The assertion value to be compared.
539     * @return The new compare request.
540     * @throws LocalizedIllegalArgumentException
541     *             If {@code name} or {@code attributeDescription} could not be
542     *             decoded using the default schema.
543     * @throws NullPointerException
544     *             If {@code name}, {@code attributeDescription}, or
545     *             {@code assertionValue} was {@code null}.
546     */
547    public static CompareRequest newCompareRequest(final String name,
548            final String attributeDescription, final Object assertionValue) {
549        Reject.ifNull(name, attributeDescription, assertionValue);
550        return new CompareRequestImpl(DN.valueOf(name), AttributeDescription
551                .valueOf(attributeDescription), ByteString.valueOfObject(assertionValue));
552    }
553
554    /**
555     * Creates a new CRAM-MD5 SASL bind request having the provided
556     * authentication ID and password.
557     *
558     * @param authenticationID
559     *            The authentication ID of the user. The authentication ID
560     *            usually has the form "dn:" immediately followed by the
561     *            distinguished name of the user, or "u:" followed by a user ID
562     *            string, but other forms are permitted.
563     * @param password
564     *            The password of the user that the client wishes to bind as.
565     * @return The new CRAM-MD5 SASL bind request.
566     * @throws NullPointerException
567     *             If {@code authenticationID} or {@code password} was
568     *             {@code null}.
569     */
570    public static CRAMMD5SASLBindRequest newCRAMMD5SASLBindRequest(final String authenticationID,
571            final byte[] password) {
572        return new CRAMMD5SASLBindRequestImpl(authenticationID, password);
573    }
574
575    /**
576     * Creates a new CRAM-MD5 SASL bind request having the provided
577     * authentication ID and password.
578     *
579     * @param authenticationID
580     *            The authentication ID of the user. The authentication ID
581     *            usually has the form "dn:" immediately followed by the
582     *            distinguished name of the user, or "u:" followed by a user ID
583     *            string, but other forms are permitted.
584     * @param password
585     *            The password of the user that the client wishes to bind as.
586     *            The password will be converted to a UTF-8 octet string.
587     * @return The new CRAM-MD5 SASL bind request.
588     * @throws NullPointerException
589     *             If {@code authenticationID} or {@code password} was
590     *             {@code null}.
591     */
592    public static CRAMMD5SASLBindRequest newCRAMMD5SASLBindRequest(final String authenticationID,
593            final char[] password) {
594        return new CRAMMD5SASLBindRequestImpl(authenticationID, getBytes(password));
595    }
596
597    /**
598     * Creates a new delete request using the provided distinguished name.
599     *
600     * @param name
601     *            The distinguished name of the entry to be deleted.
602     * @return The new delete request.
603     * @throws NullPointerException
604     *             If {@code name} was {@code null}.
605     */
606    public static DeleteRequest newDeleteRequest(final DN name) {
607        Reject.ifNull(name);
608        return new DeleteRequestImpl(name);
609    }
610
611    /**
612     * Creates a new delete request using the provided distinguished name
613     * decoded using the default schema.
614     *
615     * @param name
616     *            The distinguished name of the entry to be deleted.
617     * @return The new delete request.
618     * @throws LocalizedIllegalArgumentException
619     *             If {@code name} could not be decoded using the default
620     *             schema.
621     * @throws NullPointerException
622     *             If {@code name} was {@code null}.
623     */
624    public static DeleteRequest newDeleteRequest(final String name) {
625        Reject.ifNull(name);
626        return new DeleteRequestImpl(DN.valueOf(name));
627    }
628
629    /**
630     * Creates a new DIGEST-MD5 SASL bind request having the provided
631     * authentication ID and password, but no realm or authorization ID.
632     *
633     * @param authenticationID
634     *            The authentication ID of the user. The authentication ID
635     *            usually has the form "dn:" immediately followed by the
636     *            distinguished name of the user, or "u:" followed by a user ID
637     *            string, but other forms are permitted.
638     * @param password
639     *            The password of the user that the client wishes to bind as.
640     * @return The new DIGEST-MD5 SASL bind request.
641     * @throws NullPointerException
642     *             If {@code authenticationID} or {@code password} was
643     *             {@code null}.
644     */
645    public static DigestMD5SASLBindRequest newDigestMD5SASLBindRequest(
646            final String authenticationID, final byte[] password) {
647        return new DigestMD5SASLBindRequestImpl(authenticationID, password);
648    }
649
650    /**
651     * Creates a new DIGEST-MD5 SASL bind request having the provided
652     * authentication ID and password, but no realm or authorization ID.
653     *
654     * @param authenticationID
655     *            The authentication ID of the user. The authentication ID
656     *            usually has the form "dn:" immediately followed by the
657     *            distinguished name of the user, or "u:" followed by a user ID
658     *            string, but other forms are permitted.
659     * @param password
660     *            The password of the user that the client wishes to bind as.
661     *            The password will be converted to a UTF-8 octet string.
662     * @return The new DIGEST-MD5 SASL bind request.
663     * @throws NullPointerException
664     *             If {@code authenticationID} or {@code password} was
665     *             {@code null}.
666     */
667    public static DigestMD5SASLBindRequest newDigestMD5SASLBindRequest(
668            final String authenticationID, final char[] password) {
669        return new DigestMD5SASLBindRequestImpl(authenticationID, getBytes(password));
670    }
671
672    /**
673     * Creates a new External SASL bind request with no authorization ID.
674     *
675     * @return The new External SASL bind request.
676     */
677    public static ExternalSASLBindRequest newExternalSASLBindRequest() {
678        return new ExternalSASLBindRequestImpl();
679    }
680
681    /**
682     * Creates a new generic bind request using an empty distinguished name,
683     * authentication type, and authentication information.
684     *
685     * @param authenticationType
686     *            The authentication mechanism identifier for this generic bind
687     *            request.
688     * @param authenticationValue
689     *            The authentication information for this generic bind request
690     *            in a form defined by the authentication mechanism.
691     * @return The new generic bind request.
692     * @throws NullPointerException
693     *             If {@code authenticationValue} was {@code null}.
694     */
695    public static GenericBindRequest newGenericBindRequest(final byte authenticationType,
696            final byte[] authenticationValue) {
697        Reject.ifNull(authenticationValue);
698        return new GenericBindRequestImpl("", authenticationType, authenticationValue);
699    }
700
701    /**
702     * Creates a new generic bind request using the provided name,
703     * authentication type, and authentication information.
704     * <p>
705     * The LDAP protocol defines the Bind name to be a distinguished name,
706     * however some LDAP implementations have relaxed this constraint and allow
707     * other identities to be used, such as the user's email address.
708     *
709     * @param name
710     *            The name of the Directory object that the client wishes to
711     *            bind as (may be empty).
712     * @param authenticationType
713     *            The authentication mechanism identifier for this generic bind
714     *            request.
715     * @param authenticationValue
716     *            The authentication information for this generic bind request
717     *            in a form defined by the authentication mechanism.
718     * @return The new generic bind request.
719     * @throws NullPointerException
720     *             If {@code name} or {@code authenticationValue} was
721     *             {@code null}.
722     */
723    public static GenericBindRequest newGenericBindRequest(final String name,
724            final byte authenticationType, final byte[] authenticationValue) {
725        Reject.ifNull(name, authenticationValue);
726        return new GenericBindRequestImpl(name, authenticationType, authenticationValue);
727    }
728
729    /**
730     * Creates a new generic extended request using the provided name and no
731     * value.
732     *
733     * @param requestName
734     *            The dotted-decimal representation of the unique OID
735     *            corresponding to this extended request.
736     * @return The new generic extended request.
737     * @throws NullPointerException
738     *             If {@code requestName} was {@code null}.
739     */
740    public static GenericExtendedRequest newGenericExtendedRequest(final String requestName) {
741        Reject.ifNull(requestName);
742        return new GenericExtendedRequestImpl(requestName);
743    }
744
745    /**
746     * Creates a new generic extended request using the provided name and
747     * optional value.
748     * <p>
749     * If the request value is not an instance of {@code ByteString} then it
750     * will be converted using the {@link ByteString#valueOfObject(Object)} method.
751     *
752     * @param requestName
753     *            The dotted-decimal representation of the unique OID
754     *            corresponding to this extended request.
755     * @param requestValue
756     *            The content of this generic extended request in a form defined
757     *            by the extended operation, or {@code null} if there is no
758     *            content.
759     * @return The new generic extended request.
760     * @throws NullPointerException
761     *             If {@code requestName} was {@code null}.
762     */
763    public static GenericExtendedRequest newGenericExtendedRequest(final String requestName,
764            final Object requestValue) {
765        Reject.ifNull(requestName);
766        return new GenericExtendedRequestImpl(requestName).setValue(requestValue);
767    }
768
769    /**
770     * Creates a new GSSAPI SASL bind request having the provided authentication
771     * ID and password, but no realm, KDC address, or authorization ID.
772     *
773     * @param authenticationID
774     *            The authentication ID of the user. The authentication ID
775     *            usually has the form "dn:" immediately followed by the
776     *            distinguished name of the user, or "u:" followed by a user ID
777     *            string, but other forms are permitted.
778     * @param password
779     *            The password of the user that the client wishes to bind as.
780     * @return The new GSSAPI SASL bind request.
781     * @throws NullPointerException
782     *             If {@code authenticationID} or {@code password} was
783     *             {@code null}.
784     */
785    public static GSSAPISASLBindRequest newGSSAPISASLBindRequest(final String authenticationID,
786            final byte[] password) {
787        return new GSSAPISASLBindRequestImpl(authenticationID, password);
788    }
789
790    /**
791     * Creates a new GSSAPI SASL bind request having the provided authentication
792     * ID and password, but no realm, KDC address, or authorization ID.
793     *
794     * @param authenticationID
795     *            The authentication ID of the user. The authentication ID
796     *            usually has the form "dn:" immediately followed by the
797     *            distinguished name of the user, or "u:" followed by a user ID
798     *            string, but other forms are permitted.
799     * @param password
800     *            The password of the user that the client wishes to bind as.
801     *            The password will be converted to a UTF-8 octet string.
802     * @return The new GSSAPI SASL bind request.
803     * @throws NullPointerException
804     *             If {@code authenticationID} or {@code password} was
805     *             {@code null}.
806     */
807    public static GSSAPISASLBindRequest newGSSAPISASLBindRequest(final String authenticationID,
808            final char[] password) {
809        return new GSSAPISASLBindRequestImpl(authenticationID, getBytes(password));
810    }
811
812    /**
813     * Creates a new GSSAPI SASL bind request having the provided subject, but
814     * no authorization ID.
815     *
816     * @param subject
817     *            The Kerberos subject of the user to be authenticated.
818     * @return The new GSSAPI SASL bind request.
819     * @throws NullPointerException
820     *             If {@code subject} was {@code null}.
821     */
822    public static GSSAPISASLBindRequest newGSSAPISASLBindRequest(final Subject subject) {
823        return new GSSAPISASLBindRequestImpl(subject);
824    }
825
826    /**
827     * Creates a new modify DN request using the provided distinguished name and
828     * new RDN. The new superior will be {@code null}, indicating that the
829     * renamed entry will remain under the same parent entry, and the old RDN
830     * attribute values will not be deleted.
831     *
832     * @param name
833     *            The distinguished name of the entry to be renamed.
834     * @param newRDN
835     *            The new RDN of the entry.
836     * @return The new modify DN request.
837     * @throws NullPointerException
838     *             If {@code name} or {@code newRDN} was {@code null}.
839     */
840    public static ModifyDNRequest newModifyDNRequest(final DN name, final RDN newRDN) {
841        Reject.ifNull(name);
842        Reject.ifNull(newRDN);
843        return new ModifyDNRequestImpl(name, newRDN);
844    }
845
846    /**
847     * Creates a new modify DN request using the provided distinguished name and
848     * new RDN decoded using the default schema. The new superior will be
849     * {@code null}, indicating that the renamed entry will remain under the
850     * same parent entry, and the old RDN attribute values will not be deleted.
851     *
852     * @param name
853     *            The distinguished name of the entry to be renamed.
854     * @param newRDN
855     *            The new RDN of the entry.
856     * @return The new modify DN request.
857     * @throws LocalizedIllegalArgumentException
858     *             If {@code name} or {@code newRDN} could not be decoded using
859     *             the default schema.
860     * @throws NullPointerException
861     *             If {@code name} or {@code newRDN} was {@code null}.
862     */
863    public static ModifyDNRequest newModifyDNRequest(final String name, final String newRDN) {
864        Reject.ifNull(name, newRDN);
865        return new ModifyDNRequestImpl(DN.valueOf(name), RDN.valueOf(newRDN));
866    }
867
868    /**
869     * Creates a new modify request using the provided distinguished name.
870     *
871     * @param name
872     *            The distinguished name of the entry to be modified.
873     * @return The new modify request.
874     * @throws NullPointerException
875     *             If {@code name} was {@code null}.
876     */
877    public static ModifyRequest newModifyRequest(final DN name) {
878        Reject.ifNull(name);
879        return new ModifyRequestImpl(name);
880    }
881
882    /**
883     * Creates a new modify request containing a list of modifications which can
884     * be used to transform {@code fromEntry} into entry {@code toEntry}.
885     * <p>
886     * The changes will be generated using a default set of
887     * {@link org.forgerock.opendj.ldap.Entries.DiffOptions options}. More
888     * specifically, only user attributes will be compared, attributes will be
889     * compared using their matching rules, and all generated changes will be
890     * reversible: it will contain only modifications of type
891     * {@link ModificationType#DELETE DELETE} then {@link ModificationType#ADD
892     * ADD}.
893     * <p>
894     * Finally, the modify request will use the distinguished name taken from
895     * {@code fromEntry}. Moreover, this method will not check to see if both
896     * {@code fromEntry} and {@code toEntry} have the same distinguished name.
897     * <p>
898     * This method is equivalent to:
899     *
900     * <pre>
901     * ModifyRequest request = Entries.diffEntries(fromEntry, toEntry);
902     * </pre>
903     *
904     * Or:
905     *
906     * <pre>
907     * ModifyRequest request = Entries.diffEntries(fromEntry, toEntry, Entries.diffOptions());
908     * </pre>
909     *
910     * @param fromEntry
911     *            The source entry.
912     * @param toEntry
913     *            The destination entry.
914     * @return A modify request containing a list of modifications which can be
915     *         used to transform {@code fromEntry} into entry {@code toEntry}.
916     *         The returned request will always be non-{@code null} but may not
917     *         contain any modifications.
918     * @throws NullPointerException
919     *             If {@code fromEntry} or {@code toEntry} were {@code null}.
920     * @see Entries#diffEntries(Entry, Entry)
921     */
922    public static ModifyRequest newModifyRequest(final Entry fromEntry, final Entry toEntry) {
923        return Entries.diffEntries(fromEntry, toEntry);
924    }
925
926    /**
927     * Creates a new modify request using the provided distinguished name
928     * decoded using the default schema.
929     *
930     * @param name
931     *            The distinguished name of the entry to be modified.
932     * @return The new modify request.
933     * @throws LocalizedIllegalArgumentException
934     *             If {@code name} could not be decoded using the default
935     *             schema.
936     * @throws NullPointerException
937     *             If {@code name} was {@code null}.
938     */
939    public static ModifyRequest newModifyRequest(final String name) {
940        Reject.ifNull(name);
941        return new ModifyRequestImpl(DN.valueOf(name));
942    }
943
944    /**
945     * Creates a new modify request using the provided lines of LDIF decoded
946     * using the default schema.
947     *
948     * @param ldifLines
949     *            Lines of LDIF containing a single LDIF modify change record.
950     * @return The new modify request.
951     * @throws LocalizedIllegalArgumentException
952     *             If {@code ldifLines} was empty, or contained invalid LDIF, or
953     *             could not be decoded using the default schema.
954     * @throws NullPointerException
955     *             If {@code ldifLines} was {@code null} .
956     */
957    public static ModifyRequest newModifyRequest(final String... ldifLines) {
958        // LDIF change record reader is tolerant to missing change types.
959        final ChangeRecord record = LDIFChangeRecordReader.valueOfLDIFChangeRecord(ldifLines);
960
961        if (record instanceof ModifyRequest) {
962            return (ModifyRequest) record;
963        } else {
964            // Wrong change type.
965            final LocalizableMessage message =
966                    WARN_READ_LDIF_RECORD_CHANGE_RECORD_WRONG_TYPE.get("modify");
967            throw new LocalizedIllegalArgumentException(message);
968        }
969    }
970
971    /**
972     * Creates a new password modify extended request, with no user identity,
973     * old password, or new password.
974     *
975     * @return The new password modify extended request.
976     */
977    public static PasswordModifyExtendedRequest newPasswordModifyExtendedRequest() {
978        return new PasswordModifyExtendedRequestImpl();
979    }
980
981    /**
982     * Creates a new Plain SASL bind request having the provided authentication
983     * ID and password, but no authorization ID.
984     *
985     * @param authenticationID
986     *            The authentication ID of the user. The authentication ID
987     *            usually has the form "dn:" immediately followed by the
988     *            distinguished name of the user, or "u:" followed by a user ID
989     *            string, but other forms are permitted.
990     * @param password
991     *            The password of the user that the client wishes to bind as.
992     * @return The new Plain SASL bind request.
993     * @throws NullPointerException
994     *             If {@code authenticationID} or {@code password} was
995     *             {@code null}.
996     */
997    public static PlainSASLBindRequest newPlainSASLBindRequest(final String authenticationID,
998            final byte[] password) {
999        return new PlainSASLBindRequestImpl(authenticationID, password);
1000    }
1001
1002    /**
1003     * Creates a new Plain SASL bind request having the provided authentication
1004     * ID and password, but no authorization ID.
1005     *
1006     * @param authenticationID
1007     *            The authentication ID of the user. The authentication ID
1008     *            usually has the form "dn:" immediately followed by the
1009     *            distinguished name of the user, or "u:" followed by a user ID
1010     *            string, but other forms are permitted.
1011     * @param password
1012     *            The password of the user that the client wishes to bind as.
1013     *            The password will be converted to a UTF-8 octet string.
1014     * @return The new Plain SASL bind request.
1015     * @throws NullPointerException
1016     *             If {@code authenticationID} or {@code password} was
1017     *             {@code null}.
1018     */
1019    public static PlainSASLBindRequest newPlainSASLBindRequest(final String authenticationID,
1020            final char[] password) {
1021        return new PlainSASLBindRequestImpl(authenticationID, getBytes(password));
1022    }
1023
1024    /**
1025     * Creates a new search request using the provided distinguished name,
1026     * scope, and filter.
1027     *
1028     * @param name
1029     *            The distinguished name of the base entry relative to which the
1030     *            search is to be performed.
1031     * @param scope
1032     *            The scope of the search.
1033     * @param filter
1034     *            The filter that defines the conditions that must be fulfilled
1035     *            in order for an entry to be returned.
1036     * @param attributeDescriptions
1037     *            The names of the attributes to be included with each entry.
1038     * @return The new search request.
1039     * @throws NullPointerException
1040     *             If the {@code name}, {@code scope}, or {@code filter} were
1041     *             {@code null}.
1042     */
1043    public static SearchRequest newSearchRequest(final DN name, final SearchScope scope,
1044            final Filter filter, final String... attributeDescriptions) {
1045        Reject.ifNull(name, scope, filter);
1046        final SearchRequest request = new SearchRequestImpl(name, scope, filter);
1047        for (final String attributeDescription : attributeDescriptions) {
1048            request.addAttribute(attributeDescription);
1049        }
1050        return request;
1051    }
1052
1053    /**
1054     * Creates a new search request using the provided distinguished name,
1055     * scope, and filter, decoded using the default schema.
1056     *
1057     * @param name
1058     *            The distinguished name of the base entry relative to which the
1059     *            search is to be performed.
1060     * @param scope
1061     *            The scope of the search.
1062     * @param filter
1063     *            The filter that defines the conditions that must be fulfilled
1064     *            in order for an entry to be returned.
1065     * @param attributeDescriptions
1066     *            The names of the attributes to be included with each entry.
1067     * @return The new search request.
1068     * @throws LocalizedIllegalArgumentException
1069     *             If {@code name} could not be decoded using the default
1070     *             schema, or if {@code filter} is not a valid LDAP string
1071     *             representation of a filter.
1072     * @throws NullPointerException
1073     *             If the {@code name}, {@code scope}, or {@code filter} were
1074     *             {@code null}.
1075     */
1076    public static SearchRequest newSearchRequest(final String name, final SearchScope scope,
1077            final String filter, final String... attributeDescriptions) {
1078        Reject.ifNull(name, scope, filter);
1079        final SearchRequest request =
1080                new SearchRequestImpl(DN.valueOf(name), scope, Filter.valueOf(filter));
1081        for (final String attributeDescription : attributeDescriptions) {
1082            request.addAttribute(attributeDescription);
1083        }
1084        return request;
1085    }
1086
1087    /**
1088     * Creates a new search request for a single entry, using the provided distinguished name,
1089     * scope, and filter.
1090     *
1091     * @param name
1092     *            The distinguished name of the base entry relative to which the
1093     *            search is to be performed.
1094     * @param scope
1095     *            The scope of the search.
1096     * @param filter
1097     *            The filter that defines the conditions that must be fulfilled
1098     *            in order for an entry to be returned.
1099     * @param attributeDescriptions
1100     *            The names of the attributes to be included with each entry.
1101     * @return The new search request.
1102     * @throws NullPointerException
1103     *             If the {@code name}, {@code scope}, or {@code filter} were
1104     *             {@code null}.
1105     */
1106    public static SearchRequest newSingleEntrySearchRequest(final DN name, final SearchScope scope,
1107            final Filter filter, final String... attributeDescriptions) {
1108        return newSearchRequest(name, scope, filter, attributeDescriptions).setSizeLimit(1);
1109    }
1110
1111    /**
1112     * Creates a new search request for a single entry, using the provided distinguished name,
1113     * scope, and filter, decoded using the default schema.
1114     *
1115     * @param name
1116     *            The distinguished name of the base entry relative to which the
1117     *            search is to be performed.
1118     * @param scope
1119     *            The scope of the search.
1120     * @param filter
1121     *            The filter that defines the conditions that must be fulfilled
1122     *            in order for an entry to be returned.
1123     * @param attributeDescriptions
1124     *            The names of the attributes to be included with each entry.
1125     * @return The new search request.
1126     * @throws LocalizedIllegalArgumentException
1127     *             If {@code name} could not be decoded using the default
1128     *             schema, or if {@code filter} is not a valid LDAP string
1129     *             representation of a filter.
1130     * @throws NullPointerException
1131     *             If the {@code name}, {@code scope}, or {@code filter} were
1132     *             {@code null}.
1133     */
1134    public static SearchRequest newSingleEntrySearchRequest(final String name, final SearchScope scope,
1135            final String filter, final String... attributeDescriptions) {
1136        return newSearchRequest(name, scope, filter, attributeDescriptions).setSizeLimit(1);
1137    }
1138
1139    /**
1140     * Creates a new simple bind request having an empty name and password
1141     * suitable for anonymous authentication.
1142     *
1143     * @return The new simple bind request.
1144     */
1145    public static SimpleBindRequest newSimpleBindRequest() {
1146        return new SimpleBindRequestImpl("", EMPTY_BYTES);
1147    }
1148
1149    /**
1150     * Creates a new simple bind request having the provided name and password
1151     * suitable for name/password authentication. The name will be decoded using
1152     * the default schema.
1153     * <p>
1154     * The LDAP protocol defines the Bind name to be a distinguished name,
1155     * however some LDAP implementations have relaxed this constraint and allow
1156     * other identities to be used, such as the user's email address.
1157     *
1158     * @param name
1159     *            The name of the Directory object that the client wishes to
1160     *            bind as, which may be empty.
1161     * @param password
1162     *            The password of the Directory object that the client wishes to
1163     *            bind as, which may be empty indicating that an unauthenticated
1164     *            bind is to be performed.
1165     * @return The new simple bind request.
1166     * @throws NullPointerException
1167     *             If {@code name} or {@code password} was {@code null}.
1168     */
1169    public static SimpleBindRequest newSimpleBindRequest(final String name, final byte[] password) {
1170        Reject.ifNull(name, password);
1171        return new SimpleBindRequestImpl(name, password);
1172    }
1173
1174    /**
1175     * Creates a new simple bind request having the provided name and password
1176     * suitable for name/password authentication. The name will be decoded using
1177     * the default schema.
1178     * <p>
1179     * The LDAP protocol defines the Bind name to be a distinguished name,
1180     * however some LDAP implementations have relaxed this constraint and allow
1181     * other identities to be used, such as the user's email address.
1182     *
1183     * @param name
1184     *            The name of the Directory object that the client wishes to
1185     *            bind as, which may be empty.
1186     * @param password
1187     *            The password of the Directory object that the client wishes to
1188     *            bind as, which may be empty indicating that an unauthenticated
1189     *            bind is to be performed. The password will be converted to a
1190     *            UTF-8 octet string.
1191     * @return The new simple bind request.
1192     * @throws NullPointerException
1193     *             If {@code name} or {@code password} was {@code null}.
1194     */
1195    public static SimpleBindRequest newSimpleBindRequest(final String name, final char[] password) {
1196        Reject.ifNull(name, password);
1197        return new SimpleBindRequestImpl(name, getBytes(password));
1198    }
1199
1200    /**
1201     * Creates a new start TLS extended request which will use the provided SSL
1202     * context.
1203     *
1204     * @param sslContext
1205     *            The SSLContext that should be used when installing the TLS
1206     *            layer.
1207     * @return The new start TLS extended request.
1208     * @throws NullPointerException
1209     *             If {@code sslContext} was {@code null}.
1210     */
1211    public static StartTLSExtendedRequest newStartTLSExtendedRequest(final SSLContext sslContext) {
1212        return new StartTLSExtendedRequestImpl(sslContext);
1213    }
1214
1215    /**
1216     * Creates a new unbind request.
1217     *
1218     * @return The new unbind request.
1219     */
1220    public static UnbindRequest newUnbindRequest() {
1221        return new UnbindRequestImpl();
1222    }
1223
1224    /**
1225     * Creates a new Who Am I extended request.
1226     *
1227     * @return The new Who Am I extended request.
1228     */
1229    public static WhoAmIExtendedRequest newWhoAmIExtendedRequest() {
1230        return new WhoAmIExtendedRequestImpl();
1231    }
1232
1233    /**
1234     * Creates an unmodifiable abandon request of the provided request.
1235     *
1236     * @param request
1237     *            The abandon request to be copied.
1238     * @return The new abandon request.
1239     * @throws NullPointerException
1240     *             If {@code request} was {@code null}
1241     */
1242    public static AbandonRequest unmodifiableAbandonRequest(final AbandonRequest request) {
1243        if (request instanceof UnmodifiableAbandonRequestImpl) {
1244            return request;
1245        }
1246        return new UnmodifiableAbandonRequestImpl(request);
1247    }
1248
1249    /**
1250     * Creates an unmodifiable add request of the provided request.
1251     *
1252     * @param request
1253     *            The add request to be copied.
1254     * @return The new add request.
1255     * @throws NullPointerException
1256     *             If {@code request} was {@code null} .
1257     */
1258    public static AddRequest unmodifiableAddRequest(final AddRequest request) {
1259        if (request instanceof UnmodifiableAddRequestImpl) {
1260            return request;
1261        }
1262        return new UnmodifiableAddRequestImpl(request);
1263    }
1264
1265    /**
1266     * Creates an unmodifiable anonymous SASL bind request of the provided
1267     * request.
1268     *
1269     * @param request
1270     *            The anonymous SASL bind request to be copied.
1271     * @return The new anonymous SASL bind request.
1272     * @throws NullPointerException
1273     *             If {@code request} was {@code null} .
1274     */
1275    public static AnonymousSASLBindRequest unmodifiableAnonymousSASLBindRequest(
1276            final AnonymousSASLBindRequest request) {
1277        if (request instanceof UnmodifiableAnonymousSASLBindRequestImpl) {
1278            return request;
1279        }
1280        return new UnmodifiableAnonymousSASLBindRequestImpl(request);
1281    }
1282
1283    /**
1284     * Creates an unmodifiable cancel extended request of the provided request.
1285     *
1286     * @param request
1287     *            The cancel extended request to be copied.
1288     * @return The new cancel extended request.
1289     * @throws NullPointerException
1290     *             If {@code request} was {@code null} .
1291     */
1292    public static CancelExtendedRequest unmodifiableCancelExtendedRequest(
1293            final CancelExtendedRequest request) {
1294        if (request instanceof UnmodifiableCancelExtendedRequestImpl) {
1295            return request;
1296        }
1297        return new UnmodifiableCancelExtendedRequestImpl(request);
1298    }
1299
1300    /**
1301     * Creates an unmodifiable compare request of the provided request.
1302     *
1303     * @param request
1304     *            The compare request to be copied.
1305     * @return The new compare request.
1306     * @throws NullPointerException
1307     *             If {@code request} was {@code null} .
1308     */
1309    public static CompareRequest unmodifiableCompareRequest(final CompareRequest request) {
1310        if (request instanceof UnmodifiableCompareRequestImpl) {
1311            return request;
1312        }
1313        return new UnmodifiableCompareRequestImpl(request);
1314    }
1315
1316    /**
1317     * Creates an unmodifiable CRAM MD5 SASL bind request of the provided
1318     * request.
1319     * <p>
1320     * The returned bind request creates defensive copies of the password in
1321     * order to maintain immutability.
1322     *
1323     * @param request
1324     *            The CRAM MD5 SASL bind request to be copied.
1325     * @return The new CRAM-MD5 SASL bind request.
1326     * @throws NullPointerException
1327     *             If {@code request} was {@code null}.
1328     */
1329    public static CRAMMD5SASLBindRequest unmodifiableCRAMMD5SASLBindRequest(
1330            final CRAMMD5SASLBindRequest request) {
1331        if (request instanceof UnmodifiableCRAMMD5SASLBindRequestImpl) {
1332            return request;
1333        }
1334        return new UnmodifiableCRAMMD5SASLBindRequestImpl(request);
1335    }
1336
1337    /**
1338     * Creates an unmodifiable delete request of the provided request.
1339     *
1340     * @param request
1341     *            The add request to be copied.
1342     * @return The new delete request.
1343     * @throws NullPointerException
1344     *             If {@code request} was {@code null}.
1345     */
1346    public static DeleteRequest unmodifiableDeleteRequest(final DeleteRequest request) {
1347        if (request instanceof UnmodifiableDeleteRequestImpl) {
1348            return request;
1349        }
1350        return new UnmodifiableDeleteRequestImpl(request);
1351    }
1352
1353    /**
1354     * Creates an unmodifiable digest MD5 SASL bind request of the provided
1355     * request.
1356     * <p>
1357     * The returned bind request creates defensive copies of the password in
1358     * order to maintain immutability.
1359     *
1360     * @param request
1361     *            The digest MD5 SASL bind request to be copied.
1362     * @return The new DIGEST-MD5 SASL bind request.
1363     * @throws NullPointerException
1364     *             If {@code request} was {@code null}.
1365     */
1366    public static DigestMD5SASLBindRequest unmodifiableDigestMD5SASLBindRequest(
1367            final DigestMD5SASLBindRequest request) {
1368        if (request instanceof UnmodifiableDigestMD5SASLBindRequestImpl) {
1369            return request;
1370        }
1371        return new UnmodifiableDigestMD5SASLBindRequestImpl(request);
1372    }
1373
1374    /**
1375     * Creates an unmodifiable external SASL bind request of the provided
1376     * request.
1377     *
1378     * @param request
1379     *            The external SASL bind request to be copied.
1380     * @return The new External SASL bind request.
1381     * @throws NullPointerException
1382     *             If {@code request} was {@code null} .
1383     */
1384    public static ExternalSASLBindRequest unmodifiableExternalSASLBindRequest(
1385            final ExternalSASLBindRequest request) {
1386        if (request instanceof UnmodifiableExternalSASLBindRequestImpl) {
1387            return request;
1388        }
1389        return new UnmodifiableExternalSASLBindRequestImpl(request);
1390    }
1391
1392    /**
1393     * Creates an unmodifiable generic bind request of the provided request.
1394     * <p>
1395     * The returned bind request creates defensive copies of the authentication
1396     * value in order to maintain immutability.
1397     *
1398     * @param request
1399     *            The generic bind request to be copied.
1400     * @return The new generic bind request.
1401     * @throws NullPointerException
1402     *             If {@code request} was {@code null} .
1403     */
1404    public static GenericBindRequest unmodifiableGenericBindRequest(final GenericBindRequest request) {
1405        if (request instanceof UnmodifiableGenericBindRequestImpl) {
1406            return request;
1407        }
1408        return new UnmodifiableGenericBindRequestImpl(request);
1409    }
1410
1411    /**
1412     * Creates an unmodifiable generic extended request of the provided request.
1413     *
1414     * @param request
1415     *            The generic extended request to be copied.
1416     * @return The new generic extended request.
1417     * @throws NullPointerException
1418     *             If {@code request} was {@code null} .
1419     */
1420    public static GenericExtendedRequest unmodifiableGenericExtendedRequest(
1421            final GenericExtendedRequest request) {
1422        if (request instanceof UnmodifiableGenericExtendedRequestImpl) {
1423            return request;
1424        }
1425        return new UnmodifiableGenericExtendedRequestImpl(request);
1426    }
1427
1428    /**
1429     * Creates an unmodifiable GSSAPI SASL bind request of the provided request.
1430     * <p>
1431     * The returned bind request creates defensive copies of the password in
1432     * order to maintain immutability.
1433     *
1434     * @param request
1435     *            The GSSAPI SASL bind request to be copied.
1436     * @return The new GSSAPI SASL bind request.
1437     * @throws NullPointerException
1438     *             If {@code request} was {@code null}.
1439     */
1440    public static GSSAPISASLBindRequest unmodifiableGSSAPISASLBindRequest(
1441            final GSSAPISASLBindRequest request) {
1442        if (request instanceof UnmodifiableGSSAPISASLBindRequestImpl) {
1443            return request;
1444        }
1445        return new UnmodifiableGSSAPISASLBindRequestImpl(request);
1446    }
1447
1448    /**
1449     * Creates an unmodifiable modify DN request of the provided request.
1450     *
1451     * @param request
1452     *            The modify DN request to be copied.
1453     * @return The new modify DN request.
1454     * @throws NullPointerException
1455     *             If {@code request} was {@code null} .
1456     */
1457    public static ModifyDNRequest unmodifiableModifyDNRequest(final ModifyDNRequest request) {
1458        if (request instanceof UnmodifiableModifyDNRequestImpl) {
1459            return request;
1460        }
1461        return new UnmodifiableModifyDNRequestImpl(request);
1462    }
1463
1464    /**
1465     * Creates an unmodifiable modify request of the provided request.
1466     *
1467     * @param request
1468     *            The modify request to be copied.
1469     * @return The new modify request.
1470     * @throws NullPointerException
1471     *             If {@code request} was {@code null} .
1472     */
1473    public static ModifyRequest unmodifiableModifyRequest(final ModifyRequest request) {
1474        if (request instanceof UnmodifiableModifyRequestImpl) {
1475            return request;
1476        }
1477        return new UnmodifiableModifyRequestImpl(request);
1478    }
1479
1480    /**
1481     * Creates an unmodifiable password modify extended request of the provided
1482     * request.
1483     *
1484     * @param request
1485     *            The password modify extended request to be copied.
1486     * @return The new password modify extended request.
1487     * @throws NullPointerException
1488     *             If {@code request} was {@code null} .
1489     */
1490    public static PasswordModifyExtendedRequest unmodifiablePasswordModifyExtendedRequest(
1491            final PasswordModifyExtendedRequest request) {
1492        if (request instanceof UnmodifiablePasswordModifyExtendedRequestImpl) {
1493            return request;
1494        }
1495        return new UnmodifiablePasswordModifyExtendedRequestImpl(request);
1496    }
1497
1498    /**
1499     * Creates an unmodifiable plain SASL bind request of the provided request.
1500     * <p>
1501     * The returned bind request creates defensive copies of the password in
1502     * order to maintain immutability.
1503     *
1504     * @param request
1505     *            The plain SASL bind request to be copied.
1506     * @return The new Plain SASL bind request.
1507     * @throws NullPointerException
1508     *             If {@code request} was {@code null} .
1509     */
1510    public static PlainSASLBindRequest unmodifiablePlainSASLBindRequest(
1511            final PlainSASLBindRequest request) {
1512        if (request instanceof UnmodifiablePlainSASLBindRequestImpl) {
1513            return request;
1514        }
1515        return new UnmodifiablePlainSASLBindRequestImpl(request);
1516    }
1517
1518    /**
1519     * Creates an unmodifiable search request of the provided request.
1520     *
1521     * @param request
1522     *            The search request to be copied.
1523     * @return The new search request.
1524     * @throws NullPointerException
1525     *             If {@code request} was {@code null} .
1526     */
1527    public static SearchRequest unmodifiableSearchRequest(final SearchRequest request) {
1528        if (request instanceof UnmodifiableSearchRequestImpl) {
1529            return request;
1530        }
1531        return new UnmodifiableSearchRequestImpl(request);
1532    }
1533
1534    /**
1535     * Creates an unmodifiable simple bind request of the provided request.
1536     * <p>
1537     * The returned bind request creates defensive copies of the password in
1538     * order to maintain immutability.
1539     *
1540     * @param request
1541     *            The simple bind request to be copied.
1542     * @return The new simple bind request.
1543     * @throws NullPointerException
1544     *             If {@code request} was {@code null} .
1545     */
1546    public static SimpleBindRequest unmodifiableSimpleBindRequest(final SimpleBindRequest request) {
1547        if (request instanceof UnmodifiableSimpleBindRequestImpl) {
1548            return request;
1549        }
1550        return new UnmodifiableSimpleBindRequestImpl(request);
1551    }
1552
1553    /**
1554     * Creates an unmodifiable startTLS extended request of the provided
1555     * request.
1556     *
1557     * @param request
1558     *            The startTLS extended request to be copied.
1559     * @return The new start TLS extended request.
1560     * @throws NullPointerException
1561     *             If {@code request} was {@code null} .
1562     */
1563    public static StartTLSExtendedRequest unmodifiableStartTLSExtendedRequest(
1564            final StartTLSExtendedRequest request) {
1565        if (request instanceof UnmodifiableStartTLSExtendedRequestImpl) {
1566            return request;
1567        }
1568        return new UnmodifiableStartTLSExtendedRequestImpl(request);
1569    }
1570
1571    /**
1572     * Creates an unmodifiable unbind request of the provided request.
1573     *
1574     * @param request
1575     *            The unbind request to be copied.
1576     * @return The new unbind request.
1577     * @throws NullPointerException
1578     *             If {@code request} was {@code null} .
1579     */
1580    public static UnbindRequest unmodifiableUnbindRequest(final UnbindRequest request) {
1581        if (request instanceof UnmodifiableUnbindRequestImpl) {
1582            return request;
1583        }
1584        return new UnmodifiableUnbindRequestImpl(request);
1585    }
1586
1587    /**
1588     * Creates an unmodifiable new Who Am I extended request of the provided
1589     * request.
1590     *
1591     * @param request
1592     *            The who Am I extended request to be copied.
1593     * @return The new Who Am I extended request.
1594     * @throws NullPointerException
1595     *             If {@code request} was {@code null} .
1596     */
1597    public static WhoAmIExtendedRequest unmodifiableWhoAmIExtendedRequest(
1598            final WhoAmIExtendedRequest request) {
1599        if (request instanceof UnmodifiableWhoAmIExtendedRequestImpl) {
1600            return request;
1601        }
1602        return new UnmodifiableWhoAmIExtendedRequestImpl(request);
1603    }
1604
1605    private Requests() {
1606        // Prevent instantiation.
1607    }
1608}