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 2014-2015 ForgeRock AS.
015 */
016package org.forgerock.openig.ldap;
017
018import static org.forgerock.opendj.ldap.requests.Requests.newAddRequest;
019import static org.forgerock.opendj.ldap.requests.Requests.newCompareRequest;
020import static org.forgerock.opendj.ldap.requests.Requests.newDeleteRequest;
021import static org.forgerock.opendj.ldap.requests.Requests.newModifyDNRequest;
022import static org.forgerock.opendj.ldap.requests.Requests.newModifyRequest;
023import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
024import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
025
026import java.io.Closeable;
027import java.util.Collection;
028
029import org.forgerock.opendj.ldap.Connection;
030import org.forgerock.opendj.ldap.DN;
031import org.forgerock.opendj.ldap.Entry;
032import org.forgerock.opendj.ldap.LdapException;
033import org.forgerock.opendj.ldap.SearchScope;
034import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl;
035import org.forgerock.opendj.ldap.requests.AddRequest;
036import org.forgerock.opendj.ldap.requests.BindRequest;
037import org.forgerock.opendj.ldap.requests.CompareRequest;
038import org.forgerock.opendj.ldap.requests.DeleteRequest;
039import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
040import org.forgerock.opendj.ldap.requests.ModifyRequest;
041import org.forgerock.opendj.ldap.requests.Request;
042import org.forgerock.opendj.ldap.requests.SearchRequest;
043import org.forgerock.opendj.ldap.responses.BindResult;
044import org.forgerock.opendj.ldap.responses.CompareResult;
045import org.forgerock.opendj.ldap.responses.Result;
046import org.forgerock.opendj.ldap.responses.SearchResultEntry;
047import org.forgerock.opendj.ldap.responses.SearchResultReference;
048import org.forgerock.opendj.ldif.ConnectionEntryReader;
049import org.forgerock.services.TransactionId;
050
051import com.forgerock.opendj.ldap.controls.TransactionIdControl;
052
053/**
054 * Provides an adapted view of an OpenDJ LDAP connection exposing only the
055 * synchronous methods and protecting against future evolution of the
056 * {@link Connection} interface (e.g. migration to Promises).
057 */
058public final class LdapConnection implements Closeable {
059    private final Connection connection;
060    private final TransactionId rootTransactionId;
061
062    LdapConnection(final Connection connection) {
063        this(connection, null);
064    }
065
066    LdapConnection(final Connection connection, final TransactionId rootTransactionId) {
067        this.connection = connection;
068        this.rootTransactionId = rootTransactionId;
069    }
070
071    /**
072     * Adds an entry to the Directory Server using the provided add request.
073     *
074     * @param request The add request.
075     * @return The result of the operation.
076     * @throws LdapException If the result code indicates that the request failed for some
077     * reason.
078     * @throws UnsupportedOperationException If this connection does not support add operations.
079     * @throws IllegalStateException If this connection has already been closed, i.e. if
080     * {@code isClosed() == true}.
081     * @throws NullPointerException If {@code request} was {@code null}.
082     */
083    public Result add(AddRequest request) throws LdapException {
084        addTransactionIdControl(request);
085        return connection.add(request);
086    }
087
088    /**
089     * Adds the provided entry to the Directory Server.
090     * <p>
091     * This method is equivalent to the following code:
092     *
093     * <pre>
094     * {@code
095     * AddRequest request = newAddRequest(entry);
096     * connection.add(request);
097     * }
098     * </pre>
099     *
100     * @param entry
101     *            The entry to be added.
102     * @return The result of the operation.
103     * @throws LdapException
104     *             If the result code indicates that the request failed for some
105     *             reason.
106     * @throws UnsupportedOperationException
107     *             If this connection does not support add operations.
108     * @throws IllegalStateException
109     *             If this connection has already been closed, i.e. if
110     *             {@code isClosed() == true}.
111     * @throws NullPointerException
112     *             If {@code entry} was {@code null} .
113     */
114    public Result add(Entry entry) throws LdapException {
115        return add(newAddRequest(entry));
116    }
117
118    /**
119     * Adds an entry to the Directory Server using the provided lines of LDIF.
120     * <p>
121     * This method is equivalent to the following code:
122     *
123     * <pre>
124     * {@code
125     * AddRequest request = newAddRequest(ldifLines);
126     * connection.add(request);
127     * }
128     * </pre>
129     *
130     * @param ldifLines
131     *            Lines of LDIF containing the an LDIF add change record or an
132     *            LDIF entry record.
133     * @return The result of the operation.
134     * @throws LdapException
135     *             If the result code indicates that the request failed for some
136     *             reason.
137     * @throws UnsupportedOperationException
138     *             If this connection does not support add operations.
139     * @throws org.forgerock.i18n.LocalizedIllegalArgumentException
140     *             If {@code ldifLines} was empty, or contained invalid LDIF, or
141     *             could not be decoded using the default schema.
142     * @throws IllegalStateException
143     *             If this connection has already been closed, i.e. if
144     *             {@code isClosed() == true}.
145     * @throws NullPointerException
146     *             If {@code ldifLines} was {@code null} .
147     */
148    public Result add(String... ldifLines) throws LdapException {
149        return add(newAddRequest(ldifLines));
150    }
151
152    /**
153     * Authenticates to the Directory Server using the provided bind request.
154     *
155     * @param request The bind request.
156     * @return The result of the operation.
157     * @throws LdapException If the result code indicates that the request failed for some
158     * reason.
159     * @throws UnsupportedOperationException If this connection does not support bind operations.
160     * @throws IllegalStateException If this connection has already been closed, i.e. if
161     * {@code isClosed() == true}.
162     * @throws NullPointerException If {@code request} was {@code null}.
163     */
164    public BindResult bind(BindRequest request) throws LdapException {
165        addTransactionIdControl(request);
166        return connection.bind(request);
167    }
168
169    /**
170     * Authenticates to the Directory Server using simple authentication and the
171     * provided user name and password.
172     * <p>
173     * This method is equivalent to the following code:
174     *
175     * <pre>
176     * {@code
177     * BindRequest request = newSimpleBindRequest(name, password);
178     * connection.bind(request);
179     * }
180     * </pre>
181     *
182     * @param name
183     *            The distinguished name of the Directory object that the client
184     *            wishes to bind as, which may be empty.
185     * @param password
186     *            The password of the Directory object that the client wishes to
187     *            bind as, which may be empty.
188     * @return The result of the operation.
189     * @throws LdapException
190     *             If the result code indicates that the request failed for some
191     *             reason.
192     * @throws org.forgerock.i18n.LocalizedIllegalArgumentException
193     *             If {@code name} could not be decoded using the default
194     *             schema.
195     * @throws UnsupportedOperationException
196     *             If this connection does not support bind operations.
197     * @throws IllegalStateException
198     *             If this connection has already been closed, i.e. if
199     *             {@code isClosed() == true}.
200     * @throws NullPointerException
201     *             If {@code name} or {@code password} was {@code null}.
202     */
203    public BindResult bind(String name, char[] password) throws LdapException {
204        return bind(newSimpleBindRequest(name, password));
205    }
206
207    /**
208     * Releases any resources associated with this connection. For physical
209     * connections to a Directory Server this will mean that an unbind request
210     * is sent and the underlying socket is closed.
211     * <p>
212     * Other connection implementations may behave differently, and may choose
213     * not to send an unbind request if its use is inappropriate (for example a
214     * pooled connection will be released and returned to its connection pool
215     * without ever issuing an unbind request).
216     * <p>
217     * This method is equivalent to the following code:
218     *
219     * <pre>
220     * {@code
221     * UnbindRequest request = new UnbindRequest();
222     * connection.close(request);
223     * }
224     * </pre>
225     * <p>
226     * Calling {@code close} on a connection that is already closed has no
227     * effect.
228     *
229     * @see org.forgerock.opendj.ldap.Connections#uncloseable(Connection)
230     */
231    @Override
232    public void close() {
233        connection.close();
234    }
235
236    /**
237     * Compares an entry in the Directory Server using the provided compare
238     * request.
239     *
240     * @param request The compare request.
241     * @return The result of the operation.
242     * @throws LdapException If the result code indicates that the request failed for some
243     * reason.
244     * @throws UnsupportedOperationException If this connection does not support compare operations.
245     * @throws IllegalStateException If this connection has already been closed, i.e. if
246     * {@code isClosed() == true}.
247     * @throws NullPointerException If {@code request} was {@code null}.
248     */
249    public CompareResult compare(CompareRequest request) throws LdapException {
250        addTransactionIdControl(request);
251        return connection.compare(request);
252    }
253
254    /**
255     * Compares the named entry in the Directory Server against the provided
256     * attribute value assertion.
257     * <p>
258     * This method is equivalent to the following code:
259     *
260     * <pre>
261     * {@code
262     * CompareRequest request = newCompareRequest(name, attributeDescription, assertionValue);
263     * connection.compare(request);
264     * }
265     * </pre>
266     *
267     * @param name
268     *            The distinguished name of the entry to be compared.
269     * @param attributeDescription
270     *            The name of the attribute to be compared.
271     * @param assertionValue
272     *            The assertion value to be compared.
273     * @return The result of the operation.
274     * @throws LdapException
275     *             If the result code indicates that the request failed for some
276     *             reason.
277     * @throws org.forgerock.i18n.LocalizedIllegalArgumentException
278     *             If {@code name} or {@code AttributeDescription} could not be
279     *             decoded using the default schema.
280     * @throws UnsupportedOperationException
281     *             If this connection does not support compare operations.
282     * @throws IllegalStateException
283     *             If this connection has already been closed, i.e. if
284     *             {@code isClosed() == true}.
285     * @throws NullPointerException
286     *             If {@code name}, {@code attributeDescription}, or
287     *             {@code assertionValue} was {@code null}.
288     */
289    public CompareResult compare(String name, String attributeDescription, String assertionValue)
290            throws LdapException {
291        return compare(newCompareRequest(name, attributeDescription, assertionValue));
292    }
293
294    /**
295     * Deletes an entry from the Directory Server using the provided delete
296     * request.
297     *
298     * @param request The delete request.
299     * @return The result of the operation.
300     * @throws LdapException If the result code indicates that the request failed for some
301     * reason.
302     * @throws UnsupportedOperationException If this connection does not support delete operations.
303     * @throws IllegalStateException If this connection has already been closed, i.e. if
304     * {@code isClosed() == true}.
305     * @throws NullPointerException If {@code request} was {@code null}.
306     */
307    public Result delete(DeleteRequest request) throws LdapException {
308        addTransactionIdControl(request);
309        return connection.delete(request);
310    }
311
312    /**
313     * Deletes the named entry from the Directory Server.
314     * <p>
315     * This method is equivalent to the following code:
316     *
317     * <pre>
318     * {@code
319     * DeleteRequest request = newDeleteRequest(name);
320     * connection.delete(request);
321     * }
322     * </pre>
323     *
324     * @param name
325     *            The distinguished name of the entry to be deleted.
326     * @return The result of the operation.
327     * @throws LdapException
328     *             If the result code indicates that the request failed for some
329     *             reason.
330     * @throws org.forgerock.i18n.LocalizedIllegalArgumentException
331     *             If {@code name} could not be decoded using the default
332     *             schema.
333     * @throws UnsupportedOperationException
334     *             If this connection does not support delete operations.
335     * @throws IllegalStateException
336     *             If this connection has already been closed, i.e. if
337     *             {@code isClosed() == true}.
338     * @throws NullPointerException
339     *             If {@code name} was {@code null}.
340     */
341    public Result delete(String name) throws LdapException {
342        return delete(newDeleteRequest(name));
343    }
344
345    /**
346     * Deletes the named entry and all of its subordinates from the Directory
347     * Server.
348     * <p>
349     * This method is equivalent to the following code:
350     *
351     * <pre>
352     * {@code
353     * DeleteRequest request = newDeleteRequest(name).addControl(SubtreeDeleteRequestControl.newControl(true)));
354     * connection.delete(request);
355     * }
356     * </pre>
357     *
358     * @param name
359     *            The distinguished name of the subtree base entry to be
360     *            deleted.
361     * @return The result of the operation.
362     * @throws LdapException
363     *             If the result code indicates that the request failed for some
364     *             reason.
365     * @throws org.forgerock.i18n.LocalizedIllegalArgumentException
366     *             If {@code name} could not be decoded using the default
367     *             schema.
368     * @throws UnsupportedOperationException
369     *             If this connection does not support delete operations.
370     * @throws IllegalStateException
371     *             If this connection has already been closed, i.e. if
372     *             {@code isClosed() == true}.
373     * @throws NullPointerException
374     *             If {@code name} was {@code null}.
375     */
376    public Result deleteSubtree(String name) throws LdapException {
377        return delete(newDeleteRequest(name).addControl(SubtreeDeleteRequestControl.newControl(true)));
378    }
379
380    /**
381     * Modifies an entry in the Directory Server using the provided modify
382     * request.
383     *
384     * @param request The modify request.
385     * @return The result of the operation.
386     * @throws LdapException If the result code indicates that the request failed for some
387     * reason.
388     * @throws UnsupportedOperationException If this connection does not support modify operations.
389     * @throws IllegalStateException If this connection has already been closed, i.e. if
390     * {@code isClosed() == true}.
391     * @throws NullPointerException If {@code request} was {@code null}.
392     */
393    public Result modify(ModifyRequest request) throws LdapException {
394        addTransactionIdControl(request);
395        return connection.modify(request);
396    }
397
398    /**
399     * Modifies an entry in the Directory Server using the provided lines of
400     * LDIF.
401     * <p>
402     * This method is equivalent to the following code:
403     *
404     * <pre>
405     * {@code
406     * ModifyRequest request = newModifyRequest(ldifLines);
407     * connection.modify(request);
408     * }
409     * </pre>
410     *
411     * @param ldifLines
412     *            Lines of LDIF containing the a single LDIF modify change
413     *            record.
414     * @return The result of the operation.
415     * @throws LdapException
416     *             If the result code indicates that the request failed for some
417     *             reason.
418     * @throws UnsupportedOperationException
419     *             If this connection does not support modify operations.
420     * @throws org.forgerock.i18n.LocalizedIllegalArgumentException
421     *             If {@code ldifLines} was empty, or contained invalid LDIF, or
422     *             could not be decoded using the default schema.
423     * @throws IllegalStateException
424     *             If this connection has already been closed, i.e. if
425     *             {@code isClosed() == true}.
426     * @throws NullPointerException
427     *             If {@code ldifLines} was {@code null} .
428     */
429    public Result modify(String... ldifLines) throws LdapException {
430        return modify(newModifyRequest(ldifLines));
431    }
432
433    /**
434     * Renames an entry in the Directory Server using the provided modify DN
435     * request.
436     *
437     * @param request The modify DN request.
438     * @return The result of the operation.
439     * @throws LdapException If the result code indicates that the request failed for some
440     * reason.
441     * @throws UnsupportedOperationException If this connection does not support modify DN operations.
442     * @throws IllegalStateException If this connection has already been closed, i.e. if
443     * {@code isClosed() == true}.
444     * @throws NullPointerException If {@code request} was {@code null}.
445     */
446    public Result modifyDN(ModifyDNRequest request) throws LdapException {
447        addTransactionIdControl(request);
448        return connection.modifyDN(request);
449    }
450
451    /**
452     * Renames the named entry in the Directory Server using the provided new
453     * RDN.
454     * <p>
455     * This method is equivalent to the following code:
456     *
457     * <pre>
458     * {@code
459     * ModifyDNRequest request = newModifyDNRequest(name, newRDN);
460     * connection.modifyDN(request);
461     * }
462     * </pre>
463     *
464     * @param name
465     *            The distinguished name of the entry to be renamed.
466     * @param newRDN
467     *            The new RDN of the entry.
468     * @return The result of the operation.
469     * @throws LdapException
470     *             If the result code indicates that the request failed for some
471     *             reason.
472     * @throws org.forgerock.i18n.LocalizedIllegalArgumentException
473     *             If {@code name} or {@code newRDN} could not be decoded using
474     *             the default schema.
475     * @throws UnsupportedOperationException
476     *             If this connection does not support modify DN operations.
477     * @throws IllegalStateException
478     *             If this connection has already been closed, i.e. if
479     *             {@code isClosed() == true}.
480     * @throws NullPointerException
481     *             If {@code name} or {@code newRDN} was {@code null}.
482     */
483    public Result modifyDN(String name, String newRDN) throws LdapException {
484        return modifyDN(newModifyDNRequest(name, newRDN));
485    }
486
487    /**
488     * Reads the named entry from the Directory Server.
489     * <p>
490     * If the requested entry is not returned by the Directory Server then the
491     * request will fail with an {@link org.forgerock.opendj.ldap.EntryNotFoundException}. More
492     * specifically, this method will never return {@code null}.
493     * <p>
494     * This method is equivalent to the following code:
495     *
496     * <pre>
497     * {@code
498     * SearchRequest request =
499     *         new SearchRequest(name, SearchScope.BASE_OBJECT, &quot;(objectClass=*)&quot;, attributeDescriptions);
500     * connection.searchSingleEntry(request);
501     * }
502     * </pre>
503     *
504     * @param name
505     *            The distinguished name of the entry to be read.
506     * @param attributeDescriptions
507     *            The names of the attributes to be included with the entry,
508     *            which may be {@code null} or empty indicating that all user
509     *            attributes should be returned.
510     * @return The single search result entry returned from the search.
511     * @throws LdapException
512     *             If the result code indicates that the request failed for some
513     *             reason.
514     * @throws UnsupportedOperationException
515     *             If this connection does not support search operations.
516     * @throws IllegalStateException
517     *             If this connection has already been closed, i.e. if
518     *             {@code isClosed() == true}.
519     * @throws NullPointerException
520     *             If the {@code name} was {@code null}.
521     */
522    public SearchResultEntry readEntry(DN name, String... attributeDescriptions)
523            throws LdapException {
524        return connection.readEntry(name, attributeDescriptions);
525    }
526
527    /**
528     * Reads the named entry from the Directory Server.
529     * <p>
530     * If the requested entry is not returned by the Directory Server then the
531     * request will fail with an {@link org.forgerock.opendj.ldap.EntryNotFoundException}. More
532     * specifically, this method will never return {@code null}.
533     * <p>
534     * This method is equivalent to the following code:
535     *
536     * <pre>
537     * {@code
538     * SearchRequest request =
539     *         new SearchRequest(name, SearchScope.BASE_OBJECT, &quot;(objectClass=*)&quot;, attributeDescriptions);
540     * connection.searchSingleEntry(request);
541     * }
542     * </pre>
543     *
544     * @param name
545     *            The distinguished name of the entry to be read.
546     * @param attributeDescriptions
547     *            The names of the attributes to be included with the entry.
548     * @return The single search result entry returned from the search.
549     * @throws LdapException
550     *             If the result code indicates that the request failed for some
551     *             reason.
552     * @throws org.forgerock.i18n.LocalizedIllegalArgumentException
553     *             If {@code baseObject} could not be decoded using the default
554     *             schema.
555     * @throws UnsupportedOperationException
556     *             If this connection does not support search operations.
557     * @throws IllegalStateException
558     *             If this connection has already been closed, i.e. if
559     *             {@code isClosed() == true}.
560     * @throws NullPointerException
561     *             If the {@code name} was {@code null}.
562     */
563    public SearchResultEntry readEntry(String name, String... attributeDescriptions)
564            throws LdapException {
565        return connection.readEntry(name, attributeDescriptions);
566    }
567
568    /**
569     * Searches the Directory Server using the provided search parameters. Any
570     * matching entries returned by the search will be exposed through the
571     * returned {@code ConnectionEntryReader}.
572     * <p>
573     * Unless otherwise specified, calling this method is equivalent to:
574     *
575     * <pre>
576     * {@code
577     * ConnectionEntryReader reader = new ConnectionEntryReader(this, request);
578     * }
579     * </pre>
580     *
581     * @param request
582     *            The search request.
583     * @return The result of the operation.
584     * @throws UnsupportedOperationException
585     *             If this connection does not support search operations.
586     * @throws IllegalStateException
587     *             If this connection has already been closed, i.e. if
588     *             {@code isClosed() == true}.
589     * @throws NullPointerException
590     *             If {@code request} or {@code entries} was {@code null}.
591     */
592    public ConnectionEntryReader search(SearchRequest request) {
593        addTransactionIdControl(request);
594        return connection.search(request);
595    }
596
597    /**
598     * Searches the Directory Server using the provided search request. Any
599     * matching entries returned by the search will be added to {@code entries},
600     * even if the final search result indicates that the search failed. Search
601     * result references will be discarded.
602     * <p>
603     * <b>Warning:</b> Usage of this method is discouraged if the search request
604     * is expected to yield a large number of search results since the entire
605     * set of results will be stored in memory, potentially causing an
606     * {@code OutOfMemoryError}.
607     * <p>
608     * This method is equivalent to the following code:
609     *
610     * <pre>
611     * {@code
612     * connection.search(request, entries, null);
613     * }
614     * </pre>
615     *
616     * @param request
617     *            The search request.
618     * @param entries
619     *            The collection to which matching entries should be added.
620     * @return The result of the operation.
621     * @throws LdapException
622     *             If the result code indicates that the request failed for some
623     *             reason.
624     * @throws UnsupportedOperationException
625     *             If this connection does not support search operations.
626     * @throws IllegalStateException
627     *             If this connection has already been closed, i.e. if
628     *             {@code isClosed() == true}.
629     * @throws NullPointerException
630     *             If {@code request} or {@code entries} was {@code null}.
631     */
632    public Result search(SearchRequest request, Collection<? super SearchResultEntry> entries)
633            throws LdapException {
634        addTransactionIdControl(request);
635        return connection.search(request, entries);
636    }
637
638    /**
639     * Searches the Directory Server using the provided search request. Any
640     * matching entries returned by the search will be added to {@code entries},
641     * even if the final search result indicates that the search failed.
642     * Similarly, search result references returned by the search will be added
643     * to {@code references}.
644     * <p>
645     * <b>Warning:</b> Usage of this method is discouraged if the search request
646     * is expected to yield a large number of search results since the entire
647     * set of results will be stored in memory, potentially causing an
648     * {@code OutOfMemoryError}.
649     *
650     * @param request
651     *            The search request.
652     * @param entries
653     *            The collection to which matching entries should be added.
654     * @param references
655     *            The collection to which search result references should be
656     *            added, or {@code null} if references are to be discarded.
657     * @return The result of the operation.
658     * @throws LdapException
659     *             If the result code indicates that the request failed for some
660     *             reason.
661     * @throws UnsupportedOperationException
662     *             If this connection does not support search operations.
663     * @throws IllegalStateException
664     *             If this connection has already been closed, i.e. if
665     *             {@code isClosed() == true}.
666     * @throws NullPointerException
667     *             If {@code request} or {@code entries} was {@code null}.
668     */
669    public Result search(SearchRequest request, Collection<? super SearchResultEntry> entries,
670                         Collection<? super SearchResultReference> references) throws LdapException {
671        addTransactionIdControl(request);
672        return connection.search(request, entries, references);
673    }
674
675    /**
676     * Searches the Directory Server using the provided search parameters. Any
677     * matching entries returned by the search will be exposed through the
678     * {@code EntryReader} interface.
679     * <p>
680     * <b>Warning:</b> When using a queue with an optional capacity bound, the
681     * connection will stop reading responses and wait if necessary for space to
682     * become available.
683     * <p>
684     * This method is equivalent to the following code:
685     *
686     * <pre>
687     * {@code
688     * SearchRequest request = new SearchRequest(baseDN, scope, filter, attributeDescriptions);
689     * connection.search(request, new LinkedBlockingQueue&lt;Response&gt;());
690     * }
691     * </pre>
692     *
693     * @param baseObject
694     *            The distinguished name of the base entry relative to which the
695     *            search is to be performed.
696     * @param scope
697     *            The scope of the search.
698     * @param filter
699     *            The filter that defines the conditions that must be fulfilled
700     *            in order for an entry to be returned.
701     * @param attributeDescriptions
702     *            The names of the attributes to be included with each entry.
703     * @return An entry reader exposing the returned entries.
704     * @throws UnsupportedOperationException
705     *             If this connection does not support search operations.
706     * @throws IllegalStateException
707     *             If this connection has already been closed, i.e. if
708     *             {@code isClosed() == true}.
709     * @throws NullPointerException
710     *             If the {@code baseObject}, {@code scope}, or {@code filter}
711     *             were {@code null}.
712     */
713    public ConnectionEntryReader search(String baseObject, SearchScope scope, String filter,
714                                        String... attributeDescriptions) {
715        return search(newSearchRequest(baseObject, scope, filter, attributeDescriptions));
716    }
717
718    /**
719     * Searches the Directory Server for a single entry using the provided
720     * search request.
721     * <p>
722     * If the requested entry is not returned by the Directory Server then the
723     * request will fail with an {@link org.forgerock.opendj.ldap.EntryNotFoundException}. More
724     * specifically, this method will never return {@code null}. If multiple
725     * matching entries are returned by the Directory Server then the request
726     * will fail with an {@link org.forgerock.opendj.ldap.MultipleEntriesFoundException}.
727     *
728     * @param request
729     *            The search request.
730     * @return The single search result entry returned from the search.
731     * @throws LdapException
732     *             If the result code indicates that the request failed for some
733     *             reason.
734     * @throws UnsupportedOperationException
735     *             If this connection does not support search operations.
736     * @throws IllegalStateException
737     *             If this connection has already been closed, i.e. if
738     *             {@code isClosed() == true}.
739     * @throws NullPointerException
740     *             If the {@code request} was {@code null}.
741     */
742    public SearchResultEntry searchSingleEntry(SearchRequest request) throws LdapException {
743        addTransactionIdControl(request);
744        return connection.searchSingleEntry(request);
745    }
746
747    /**
748     * Searches the Directory Server for a single entry using the provided
749     * search parameters.
750     * <p>
751     * If the requested entry is not returned by the Directory Server then the
752     * request will fail with an {@link org.forgerock.opendj.ldap.EntryNotFoundException}. More
753     * specifically, this method will never return {@code null}. If multiple
754     * matching entries are returned by the Directory Server then the request
755     * will fail with an {@link org.forgerock.opendj.ldap.MultipleEntriesFoundException}.
756     * <p>
757     * This method is equivalent to the following code:
758     *
759     * <pre>
760     * {@code
761     * SearchRequest request = new SearchRequest(baseObject, scope, filter, attributeDescriptions);
762     * connection.searchSingleEntry(request);
763     * }
764     * </pre>
765     *
766     * @param baseObject
767     *            The distinguished name of the base entry relative to which the
768     *            search is to be performed.
769     * @param scope
770     *            The scope of the search.
771     * @param filter
772     *            The filter that defines the conditions that must be fulfilled
773     *            in order for an entry to be returned.
774     * @param attributeDescriptions
775     *            The names of the attributes to be included with each entry.
776     * @return The single search result entry returned from the search.
777     * @throws LdapException
778     *             If the result code indicates that the request failed for some
779     *             reason.
780     * @throws org.forgerock.i18n.LocalizedIllegalArgumentException
781     *             If {@code baseObject} could not be decoded using the default
782     *             schema or if {@code filter} is not a valid LDAP string
783     *             representation of a filter.
784     * @throws UnsupportedOperationException
785     *             If this connection does not support search operations.
786     * @throws IllegalStateException
787     *             If this connection has already been closed, i.e. if
788     *             {@code isClosed() == true}.
789     * @throws NullPointerException
790     *             If the {@code baseObject}, {@code scope}, or {@code filter}
791     *             were {@code null}.
792     */
793    public SearchResultEntry searchSingleEntry(String baseObject, SearchScope scope, String filter,
794                                               String... attributeDescriptions) throws LdapException {
795        return searchSingleEntry(newSearchRequest(baseObject, scope, filter, attributeDescriptions));
796    }
797
798    private void addTransactionIdControl(Request request) {
799        if (rootTransactionId != null && !request.containsControl(TransactionIdControl.OID)) {
800            request.addControl(TransactionIdControl.newControl(rootTransactionId.createSubTransactionId().getValue()));
801        }
802    }
803
804}