001    /*
002     * Copyright 2011-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2011-2013 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.listener;
022    
023    
024    
025    import java.net.InetAddress;
026    import java.util.ArrayList;
027    import java.util.Arrays;
028    import java.util.Collection;
029    import java.util.Collections;
030    import java.util.LinkedHashMap;
031    import java.util.List;
032    import java.util.Map;
033    import javax.net.SocketFactory;
034    
035    import com.unboundid.asn1.ASN1OctetString;
036    import com.unboundid.ldap.protocol.AddRequestProtocolOp;
037    import com.unboundid.ldap.protocol.AddResponseProtocolOp;
038    import com.unboundid.ldap.protocol.BindRequestProtocolOp;
039    import com.unboundid.ldap.protocol.BindResponseProtocolOp;
040    import com.unboundid.ldap.protocol.CompareRequestProtocolOp;
041    import com.unboundid.ldap.protocol.CompareResponseProtocolOp;
042    import com.unboundid.ldap.protocol.DeleteRequestProtocolOp;
043    import com.unboundid.ldap.protocol.DeleteResponseProtocolOp;
044    import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp;
045    import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp;
046    import com.unboundid.ldap.protocol.LDAPMessage;
047    import com.unboundid.ldap.protocol.ModifyRequestProtocolOp;
048    import com.unboundid.ldap.protocol.ModifyResponseProtocolOp;
049    import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp;
050    import com.unboundid.ldap.protocol.ModifyDNResponseProtocolOp;
051    import com.unboundid.ldap.protocol.SearchRequestProtocolOp;
052    import com.unboundid.ldap.protocol.SearchResultDoneProtocolOp;
053    import com.unboundid.ldap.sdk.AddRequest;
054    import com.unboundid.ldap.sdk.Attribute;
055    import com.unboundid.ldap.sdk.BindRequest;
056    import com.unboundid.ldap.sdk.BindResult;
057    import com.unboundid.ldap.sdk.CompareRequest;
058    import com.unboundid.ldap.sdk.CompareResult;
059    import com.unboundid.ldap.sdk.Control;
060    import com.unboundid.ldap.sdk.DeleteRequest;
061    import com.unboundid.ldap.sdk.DereferencePolicy;
062    import com.unboundid.ldap.sdk.DN;
063    import com.unboundid.ldap.sdk.Entry;
064    import com.unboundid.ldap.sdk.ExtendedRequest;
065    import com.unboundid.ldap.sdk.ExtendedResult;
066    import com.unboundid.ldap.sdk.Filter;
067    import com.unboundid.ldap.sdk.InternalSDKHelper;
068    import com.unboundid.ldap.sdk.LDAPConnection;
069    import com.unboundid.ldap.sdk.LDAPConnectionOptions;
070    import com.unboundid.ldap.sdk.LDAPConnectionPool;
071    import com.unboundid.ldap.sdk.LDAPException;
072    import com.unboundid.ldap.sdk.LDAPInterface;
073    import com.unboundid.ldap.sdk.LDAPResult;
074    import com.unboundid.ldap.sdk.LDAPSearchException;
075    import com.unboundid.ldap.sdk.Modification;
076    import com.unboundid.ldap.sdk.ModifyRequest;
077    import com.unboundid.ldap.sdk.ModifyDNRequest;
078    import com.unboundid.ldap.sdk.PLAINBindRequest;
079    import com.unboundid.ldap.sdk.ReadOnlyAddRequest;
080    import com.unboundid.ldap.sdk.ReadOnlyCompareRequest;
081    import com.unboundid.ldap.sdk.ReadOnlyDeleteRequest;
082    import com.unboundid.ldap.sdk.ReadOnlyModifyRequest;
083    import com.unboundid.ldap.sdk.ReadOnlyModifyDNRequest;
084    import com.unboundid.ldap.sdk.ReadOnlySearchRequest;
085    import com.unboundid.ldap.sdk.ResultCode;
086    import com.unboundid.ldap.sdk.RootDSE;
087    import com.unboundid.ldap.sdk.SearchRequest;
088    import com.unboundid.ldap.sdk.SearchResult;
089    import com.unboundid.ldap.sdk.SearchResultEntry;
090    import com.unboundid.ldap.sdk.SearchResultListener;
091    import com.unboundid.ldap.sdk.SearchResultReference;
092    import com.unboundid.ldap.sdk.SearchScope;
093    import com.unboundid.ldap.sdk.SimpleBindRequest;
094    import com.unboundid.ldap.sdk.schema.Schema;
095    import com.unboundid.ldif.LDIFException;
096    import com.unboundid.ldif.LDIFReader;
097    import com.unboundid.ldif.LDIFWriter;
098    import com.unboundid.util.ByteStringBuffer;
099    import com.unboundid.util.Debug;
100    import com.unboundid.util.Mutable;
101    import com.unboundid.util.StaticUtils;
102    import com.unboundid.util.ThreadSafety;
103    import com.unboundid.util.ThreadSafetyLevel;
104    import com.unboundid.util.Validator;
105    
106    import static com.unboundid.ldap.listener.ListenerMessages.*;
107    
108    
109    
110    /**
111     * This class provides a utility that may be used to create a simple LDAP server
112     * instance that will hold all of its information in memory.  It is intended to
113     * be very easy to use, particularly as an embeddable server for testing
114     * directory-enabled applications.  It can be easily created, configured,
115     * populated, and shut down with only a few lines of code, and it provides a
116     * number of convenience methods that can be very helpful in writing test cases
117     * that validate the content of the server.
118     * <BR><BR>
119     * Some notes about the capabilities of this server:
120     * <UL>
121     *   <LI>It provides reasonably complete support for add, compare, delete,
122     *       modify, modify DN (including new superior and subtree move/rename),
123     *       search, and unbind operations.</LI>
124     *   <LI>It will accept abandon requests, but will not do anything with
125     *       them.</LI>
126     *   <LI>It provides support for simple bind operations, and for the SASL PLAIN
127     *       mechanism.  It also provides an API that can be used to add support for
128     *       additional SASL mechanisms.</LI>
129     *   <LI>It provides support for the password modify, StartTLS, and "who am I?"
130     *       extended operations, as well as an API that can be used to add support
131     *       for additional types of extended operations.</LI>
132     *   <LI>It provides support for the LDAP assertions, authorization identity,
133     *       don't use copy, manage DSA IT, permissive modify, pre-read, post-read,
134     *       proxied authorization v1 and v2, server-side sort, simple paged
135     *       results, LDAP subentries, subtree delete, and virtual list view request
136     *       controls.</LI>
137     *   <LI>It supports the use of schema (if provided), but it does not currently
138     *       allow updating the schema on the fly.</LI>
139     *   <LI>It has the ability to maintain a log of operations processed, either
140     *       as a simple access log or a more detailed LDAP debug log.</LI>
141     *   <LI>It has the ability to maintain an LDAP-accessible changelog.</LI>
142     *   <LI>It provides an option to generate a number of operational attributes,
143     *       including entryDN, entryUUID, creatorsName, createTimestamp,
144     *       modifiersName, modifyTimestamp, and subschemaSubentry.</LI>
145     *   <LI>It provides support for referential integrity, in which case specified
146     *       attributes whose values are DNs may be updated if the entries they
147     *       reference are deleted or renamed.</LI>
148     *   <LI>It provides methods for importing data from and exporting data to LDIF
149     *       files, and it has the ability to capture a point-in-time snapshot of
150     *       the data (including changelog information) that may be restored at any
151     *       point.</LI>
152     *   <LI>It implements the {@link LDAPInterface} interface, which means that in
153     *       many cases it can be used as a drop-in replacement for an
154     *       {@link LDAPConnection}.</LI>
155     * </UL>
156     * <BR><BR>
157     * In order to create an in-memory directory server instance, you should first
158     * create an {@link InMemoryDirectoryServerConfig} object with the desired
159     * settings.  Then use that configuration object to initialize the directory
160     * server instance, and call the {@link #startListening} method to start
161     * accepting connections from LDAP clients.  The {@link #getConnection} and
162     * {@link #getConnectionPool} methods may be used to obtain connections to the
163     * server and you can also manually create connections using the information
164     * obtained via the {@link #getListenAddress}, {@link #getListenPort}, and
165     * {@link #getClientSocketFactory} methods.  When the server is no longer
166     * needed, the {@link #shutDown} method should be used to stop the server.  Any
167     * number of in-memory directory server instances can be created and running in
168     * a single JVM at any time, and many of the methods provided in this class can
169     * be used without the server running if operations are to be performed using
170     * only method calls rather than via LDAP clients.
171     * <BR><BR>
172     * <H2>Example</H2>
173     * The following example demonstrates the process that can be used to create,
174     * start, and use an in-memory directory server instance:
175     * <PRE>
176     * // Create a base configuration for the server.
177     * InMemoryDirectoryServerConfig config =
178     *      new InMemoryDirectoryServerConfig("dc=example,dc=com");
179     * config.addAdditionalBindCredentials("cn=Directory Manager",
180     *      "password");
181     *
182     * // Create and start the server instance and populate it with an
183     * // initial set of data from the file "/tmp/test.ldif".
184     * InMemoryDirectoryServer server = new InMemoryDirectoryServer(config);
185     * server.importFromLDIF(true, "/tmp/test.ldif");
186     *
187     * // Start the server so it will accept client connections.
188     * server.startListening();
189     *
190     * // Get a connection to the server.
191     * LDAPConnection conn = server.getConnection();
192     *
193     * // Perform various operations in the server....
194     *
195     * // Close the connection.
196     * conn.close();
197     *
198     * // Shut down the server so that it will no longer accept client
199     * // connections, and close all existing connections.
200     * server.shutDown(true);
201     * </PRE>
202     */
203    @Mutable()
204    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
205    public final class InMemoryDirectoryServer
206           implements LDAPInterface
207    {
208      // The in-memory request handler that will be used for the server.
209      private final InMemoryRequestHandler inMemoryHandler;
210    
211      // The set of listeners that have been configured for this server, mapped by
212      // listener name.
213      private final Map<String,LDAPListener> listeners;
214    
215      // The set of configurations for all the LDAP listeners to be used.
216      private final Map<String,LDAPListenerConfig> ldapListenerConfigs;
217    
218      // The set of client socket factories associated with each of the listeners.
219      private final Map<String,SocketFactory> clientSocketFactories;
220    
221      // A read-only representation of the configuration used to create this
222      // in-memory directory server.
223      private final ReadOnlyInMemoryDirectoryServerConfig config;
224    
225    
226    
227      /**
228       * Creates a very simple instance of an in-memory directory server with the
229       * specified set of base DNs.  It will not use a well-defined schema, and will
230       * pick a listen port at random.
231       *
232       * @param  baseDNs  The base DNs to use for the server.  It must not be
233       *                  {@code null} or empty.
234       *
235       * @throws  LDAPException  If a problem occurs while attempting to initialize
236       *                         the server.
237       */
238      public InMemoryDirectoryServer(final String... baseDNs)
239             throws LDAPException
240      {
241        this(new InMemoryDirectoryServerConfig(baseDNs));
242      }
243    
244    
245    
246      /**
247       * Creates a new instance of an in-memory directory server with the provided
248       * configuration.
249       *
250       * @param  cfg  The configuration to use for the server.  It must not be
251       *              {@code null}.
252       *
253       * @throws  LDAPException  If a problem occurs while trying to initialize the
254       *                         directory server with the provided configuration.
255       */
256      public InMemoryDirectoryServer(final InMemoryDirectoryServerConfig cfg)
257             throws LDAPException
258      {
259        Validator.ensureNotNull(cfg);
260    
261        config = new ReadOnlyInMemoryDirectoryServerConfig(cfg);
262        inMemoryHandler = new InMemoryRequestHandler(config);
263    
264        LDAPListenerRequestHandler requestHandler = inMemoryHandler;
265    
266        if (config.getAccessLogHandler() != null)
267        {
268          requestHandler = new AccessLogRequestHandler(config.getAccessLogHandler(),
269               requestHandler);
270        }
271    
272        if (config.getLDAPDebugLogHandler() != null)
273        {
274          requestHandler = new LDAPDebuggerRequestHandler(
275               config.getLDAPDebugLogHandler(), requestHandler);
276        }
277    
278    
279        final List<InMemoryListenerConfig> listenerConfigs =
280             config.getListenerConfigs();
281    
282        listeners = new LinkedHashMap<String,LDAPListener>(listenerConfigs.size());
283        ldapListenerConfigs =
284             new LinkedHashMap<String,LDAPListenerConfig>(listenerConfigs.size());
285        clientSocketFactories =
286             new LinkedHashMap<String,SocketFactory>(listenerConfigs.size());
287    
288        for (final InMemoryListenerConfig c : listenerConfigs)
289        {
290          final String name = StaticUtils.toLowerCase(c.getListenerName());
291    
292          final LDAPListenerRequestHandler listenerRequestHandler;
293          if (c.getStartTLSSocketFactory() == null)
294          {
295            listenerRequestHandler =  requestHandler;
296          }
297          else
298          {
299            listenerRequestHandler =
300                 new StartTLSRequestHandler(c.getStartTLSSocketFactory(),
301                      requestHandler);
302          }
303    
304          final LDAPListenerConfig listenerCfg = new LDAPListenerConfig(
305               c.getListenPort(), listenerRequestHandler);
306          listenerCfg.setExceptionHandler(config.getListenerExceptionHandler());
307          listenerCfg.setListenAddress(c.getListenAddress());
308          listenerCfg.setServerSocketFactory(c.getServerSocketFactory());
309    
310          ldapListenerConfigs.put(name, listenerCfg);
311    
312          if (c.getClientSocketFactory() != null)
313          {
314            clientSocketFactories.put(name, c.getClientSocketFactory());
315          }
316        }
317      }
318    
319    
320    
321      /**
322       * Attempts to start listening for client connections on all configured
323       * listeners.  Any listeners that are already running will be unaffected.
324       *
325       * @throws  LDAPException  If a problem occurs while attempting to create any
326       *                         of the configured listeners.  Even if an exception
327       *                         is thrown, then as many listeners as possible will
328       *                         be started.
329       */
330      public synchronized void startListening()
331             throws LDAPException
332      {
333        final ArrayList<String> messages = new ArrayList<String>(listeners.size());
334    
335        for (final Map.Entry<String,LDAPListenerConfig> cfgEntry :
336             ldapListenerConfigs.entrySet())
337        {
338          final String name = cfgEntry.getKey();
339    
340          if (listeners.containsKey(name))
341          {
342            // This listener is already running.
343            continue;
344          }
345    
346          final LDAPListenerConfig listenerConfig = cfgEntry.getValue();
347          final LDAPListener listener = new LDAPListener(listenerConfig);
348    
349          try
350          {
351            listener.startListening();
352            listenerConfig.setListenPort(listener.getListenPort());
353            listeners.put(name, listener);
354          }
355          catch (final Exception e)
356          {
357            Debug.debugException(e);
358            messages.add(ERR_MEM_DS_START_FAILED.get(name,
359                 StaticUtils.getExceptionMessage(e)));
360          }
361        }
362    
363        if (! messages.isEmpty())
364        {
365          throw new LDAPException(ResultCode.LOCAL_ERROR,
366               StaticUtils.concatenateStrings(messages));
367        }
368      }
369    
370    
371    
372      /**
373       * Attempts to start listening for client connections on the specified
374       * listener.  If the listener is already running, then it will be unaffected.
375       *
376       * @param  listenerName  The name of the listener to be started.  It must not
377       *                       be {@code null}.
378       *
379       * @throws  LDAPException  If a problem occurs while attempting to start the
380       *                         requested listener.
381       */
382      public synchronized void startListening(final String listenerName)
383             throws LDAPException
384      {
385        // If the listener is already running, then there's nothing to do.
386        final String name = StaticUtils .toLowerCase(listenerName);
387        if (listeners.containsKey(name))
388        {
389          return;
390        }
391    
392        // Get the configuration to use for the listener.
393        final LDAPListenerConfig listenerConfig = ldapListenerConfigs.get(name);
394        if (listenerConfig == null)
395        {
396          throw new LDAPException(ResultCode.PARAM_ERROR,
397               ERR_MEM_DS_NO_SUCH_LISTENER.get(listenerName));
398        }
399    
400    
401        final LDAPListener listener = new LDAPListener(listenerConfig);
402    
403        try
404        {
405          listener.startListening();
406          listenerConfig.setListenPort(listener.getListenPort());
407          listeners.put(name, listener);
408        }
409        catch (final Exception e)
410        {
411          Debug.debugException(e);
412          throw new LDAPException(ResultCode.LOCAL_ERROR,
413               ERR_MEM_DS_START_FAILED.get(name,
414                    StaticUtils.getExceptionMessage(e)),
415               e);
416        }
417      }
418    
419    
420    
421      /**
422       * Shuts down all configured listeners.  Any listeners that are already
423       * stopped will be unaffected.
424       *
425       * @param  closeExistingConnections  Indicates whether to close all existing
426       *                                   connections, or merely to stop accepting
427       *                                   new connections.
428       */
429      public synchronized void shutDown(final boolean closeExistingConnections)
430      {
431        for (final LDAPListener l : listeners.values())
432        {
433          try
434          {
435            l.shutDown(closeExistingConnections);
436          }
437          catch (final Exception e)
438          {
439            Debug.debugException(e);
440          }
441        }
442    
443        listeners.clear();
444      }
445    
446    
447    
448      /**
449       * Shuts down the specified listener.  If there is no such listener defined,
450       * or if the specified listener is not running, then no action will be taken.
451       *
452       * @param  listenerName              The name of the listener to be shut down.
453       *                                   It must not be {@code null}.
454       * @param  closeExistingConnections  Indicates whether to close all existing
455       *                                   connections, or merely to stop accepting
456       *                                   new connections.
457       */
458      public synchronized void shutDown(final String listenerName,
459                                        final boolean closeExistingConnections)
460      {
461        final String name = StaticUtils.toLowerCase(listenerName);
462        final LDAPListener listener = listeners.remove(name);
463        if (listener != null)
464        {
465          listener.shutDown(closeExistingConnections);
466        }
467      }
468    
469    
470    
471      /**
472       * Attempts to restart all listeners defined in the server.  All running
473       * listeners will be stopped, and all configured listeners will be started.
474       *
475       * @throws  LDAPException  If a problem occurs while attempting to restart any
476       *                         of the listeners.  Even if an exception is thrown,
477       *                         as many listeners as possible will be started.
478       */
479      public synchronized void restartServer()
480             throws LDAPException
481      {
482        shutDown(true);
483    
484        try
485        {
486          Thread.sleep(100L);
487        }
488        catch (final Exception e)
489        {
490          Debug.debugException(e);
491        }
492    
493        startListening();
494      }
495    
496    
497    
498      /**
499       * Attempts to restart the specified listener.  If it is running, it will be
500       * stopped.  It will then be started.
501       *
502       * @param  listenerName  The name of the listener to be restarted.  It must
503       *                       not be {@code null}.
504       *
505       * @throws  LDAPException  If a problem occurs while attempting to restart the
506       *                         specified listener.
507       */
508      public synchronized void restartListener(final String listenerName)
509             throws LDAPException
510      {
511        shutDown(listenerName, true);
512    
513        try
514        {
515          Thread.sleep(100L);
516        }
517        catch (final Exception e)
518        {
519          Debug.debugException(e);
520        }
521    
522        startListening(listenerName);
523      }
524    
525    
526    
527      /**
528       * Retrieves a read-only representation of the configuration used to create
529       * this in-memory directory server instance.
530       *
531       * @return  A read-only representation of the configuration used to create
532       *          this in-memory directory server instance.
533       */
534      public ReadOnlyInMemoryDirectoryServerConfig getConfig()
535      {
536        return config;
537      }
538    
539    
540    
541      /**
542       * Retrieves the in-memory request handler that is used to perform the real
543       * server processing.
544       *
545       * @return  The in-memory request handler that is used to perform the real
546       *          server processing.
547       */
548      InMemoryRequestHandler getInMemoryRequestHandler()
549      {
550        return inMemoryHandler;
551      }
552    
553    
554    
555      /**
556       * Creates a point-in-time snapshot of the information contained in this
557       * in-memory directory server instance.  It may be restored using the
558       * {@link #restoreSnapshot} method.
559       * <BR><BR>
560       * This method may be used regardless of whether the server is listening for
561       * client connections.
562       *
563       * @return  The snapshot created based on the current content of this
564       *          in-memory directory server instance.
565       */
566      public InMemoryDirectoryServerSnapshot createSnapshot()
567      {
568        return inMemoryHandler.createSnapshot();
569      }
570    
571    
572    
573      /**
574       * Restores the this in-memory directory server instance to match the content
575       * it held at the time the snapshot was created.
576       * <BR><BR>
577       * This method may be used regardless of whether the server is listening for
578       * client connections.
579       *
580       * @param  snapshot  The snapshot to be restored.  It must not be
581       *                   {@code null}.
582       */
583      public void restoreSnapshot(final InMemoryDirectoryServerSnapshot snapshot)
584      {
585        inMemoryHandler.restoreSnapshot(snapshot);
586      }
587    
588    
589    
590      /**
591       * Retrieves the list of base DNs configured for use by the server.
592       *
593       * @return  The list of base DNs configured for use by the server.
594       */
595      public List<DN> getBaseDNs()
596      {
597        return inMemoryHandler.getBaseDNs();
598      }
599    
600    
601    
602      /**
603       * Attempts to establish a client connection to the server.  If multiple
604       * listeners are configured, then it will attempt to establish a connection to
605       * the first configured listener that is running.
606       *
607       * @return  The client connection that has been established.
608       *
609       * @throws  LDAPException  If a problem is encountered while attempting to
610       *                         create the connection.
611       */
612      public LDAPConnection getConnection()
613             throws LDAPException
614      {
615        return getConnection(null, null);
616      }
617    
618    
619    
620      /**
621       * Attempts to establish a client connection to the server.
622       *
623       * @param  options  The connection options to use when creating the
624       *                  connection.  It may be {@code null} if a default set of
625       *                  options should be used.
626       *
627       * @return  The client connection that has been established.
628       *
629       * @throws  LDAPException  If a problem is encountered while attempting to
630       *                         create the connection.
631       */
632      public LDAPConnection getConnection(final LDAPConnectionOptions options)
633             throws LDAPException
634      {
635        return getConnection(null, options);
636      }
637    
638    
639    
640      /**
641       * Attempts to establish a client connection to the specified listener.
642       *
643       * @param  listenerName  The name of the listener to which to establish the
644       *                       connection.  It may be {@code null} if a connection
645       *                       should be established to the first available
646       *                       listener.
647       *
648       * @return  The client connection that has been established.
649       *
650       * @throws  LDAPException  If a problem is encountered while attempting to
651       *                         create the connection.
652       */
653      public LDAPConnection getConnection(final String listenerName)
654             throws LDAPException
655      {
656        return getConnection(listenerName, null);
657      }
658    
659    
660    
661      /**
662       * Attempts to establish a client connection to the specified listener.
663       *
664       * @param  listenerName  The name of the listener to which to establish the
665       *                       connection.  It may be {@code null} if a connection
666       *                       should be established to the first available
667       *                       listener.
668       * @param  options       The set of LDAP connection options to use for the
669       *                       connection that is created.
670       *
671       * @return  The client connection that has been established.
672       *
673       * @throws  LDAPException  If a problem is encountered while attempting to
674       *                         create the connection.
675       */
676      public synchronized LDAPConnection getConnection(final String listenerName,
677                                              final LDAPConnectionOptions options)
678             throws LDAPException
679      {
680        final LDAPListenerConfig listenerConfig;
681        final SocketFactory clientSocketFactory;
682    
683        if (listenerName == null)
684        {
685          final String name = getFirstListenerName();
686          if (name == null)
687          {
688            throw new LDAPException(ResultCode.CONNECT_ERROR,
689                 ERR_MEM_DS_GET_CONNECTION_NO_LISTENERS.get());
690          }
691    
692          listenerConfig      = ldapListenerConfigs.get(name);
693          clientSocketFactory = clientSocketFactories.get(name);
694        }
695        else
696        {
697          final String name = StaticUtils.toLowerCase(listenerName);
698          if (! listeners.containsKey(name))
699          {
700            throw new LDAPException(ResultCode.CONNECT_ERROR,
701                 ERR_MEM_DS_GET_CONNECTION_LISTENER_NOT_RUNNING.get(listenerName));
702          }
703    
704          listenerConfig      = ldapListenerConfigs.get(name);
705          clientSocketFactory = clientSocketFactories.get(name);
706        }
707    
708        String hostAddress;
709        final InetAddress listenAddress = listenerConfig.getListenAddress();
710        if ((listenAddress == null) || (listenAddress.isAnyLocalAddress()))
711        {
712          try
713          {
714            hostAddress = InetAddress.getLocalHost().getHostAddress();
715          }
716          catch (final Exception e)
717          {
718            Debug.debugException(e);
719            hostAddress = "127.0.0.1";
720          }
721        }
722        else
723        {
724          hostAddress = listenAddress.getHostAddress();
725        }
726    
727        return new LDAPConnection(clientSocketFactory, options, hostAddress,
728             listenerConfig.getListenPort());
729      }
730    
731    
732    
733      /**
734       * Attempts to establish a connection pool to the server with the specified
735       * maximum number of connections.
736       *
737       * @param  maxConnections  The maximum number of connections to maintain in
738       *                         the connection pool.  It must be greater than or
739       *                         equal to one.
740       *
741       * @return  The connection pool that has been created.
742       *
743       * @throws  LDAPException  If a problem occurs while attempting to create the
744       *                         connection pool.
745       */
746      public LDAPConnectionPool getConnectionPool(final int maxConnections)
747             throws LDAPException
748      {
749        return getConnectionPool(null, null, 1, maxConnections);
750      }
751    
752    
753    
754      /**
755       * Attempts to establish a connection pool to the server with the provided
756       * settings.
757       *
758       * @param  listenerName        The name of the listener to which the
759       *                             connections should be established.
760       * @param  options             The connection options to use when creating
761       *                             connections for use in the pool.  It may be
762       *                             {@code null} if a default set of options should
763       *                             be used.
764       * @param  initialConnections  The initial number of connections to establish
765       *                             in the connection pool.  It must be greater
766       *                             than or equal to one.
767       * @param  maxConnections      The maximum number of connections to maintain
768       *                             in the connection pool.  It must be greater
769       *                             than or equal to the initial number of
770       *                             connections.
771       *
772       * @return  The connection pool that has been created.
773       *
774       * @throws  LDAPException  If a problem occurs while attempting to create the
775       *                         connection pool.
776       */
777      public LDAPConnectionPool getConnectionPool(final String listenerName,
778                                     final LDAPConnectionOptions options,
779                                     final int initialConnections,
780                                     final int maxConnections)
781             throws LDAPException
782      {
783        final LDAPConnection conn = getConnection(listenerName, options);
784        return new LDAPConnectionPool(conn, initialConnections, maxConnections);
785      }
786    
787    
788    
789      /**
790       * Retrieves the configured listen address for the first active listener, if
791       * defined.
792       *
793       * @return  The configured listen address for the first active listener, or
794       *          {@code null} if that listener does not have an
795       *          explicitly-configured listen address or there are no active
796       *          listeners.
797       */
798      public InetAddress getListenAddress()
799      {
800        return getListenAddress(null);
801      }
802    
803    
804    
805      /**
806       * Retrieves the configured listen address for the specified listener, if
807       * defined.
808       *
809       * @param  listenerName  The name of the listener for which to retrieve the
810       *                       listen address.  It may be {@code null} in order to
811       *                       obtain the listen address for the first active
812       *                       listener.
813       *
814       * @return  The configured listen address for the specified listener, or
815       *          {@code null} if there is no such listener or the listener does not
816       *          have an explicitly-configured listen address.
817       */
818      public synchronized InetAddress getListenAddress(final String listenerName)
819      {
820        final String name;
821        if (listenerName == null)
822        {
823          name = getFirstListenerName();
824        }
825        else
826        {
827          name = StaticUtils.toLowerCase(listenerName);
828        }
829    
830        final LDAPListenerConfig listenerCfg = ldapListenerConfigs.get(name);
831        if (listenerCfg == null)
832        {
833          return null;
834        }
835        else
836        {
837          return listenerCfg.getListenAddress();
838        }
839      }
840    
841    
842    
843      /**
844       * Retrieves the configured listen port for the first active listener.
845       *
846       * @return  The configured listen port for the first active listener, or -1 if
847       *          there are no active listeners.
848       */
849      public int getListenPort()
850      {
851        return getListenPort(null);
852      }
853    
854    
855    
856      /**
857       * Retrieves the configured listen port for the specified listener, if
858       * available.
859       *
860       * @param  listenerName  The name of the listener for which to retrieve the
861       *                       listen port.  It may be {@code null} in order to
862       *                       obtain the listen port for the first active
863       *                       listener.
864       *
865       * @return  The configured listen port for the specified listener, or -1 if
866       *          there is no such listener or the listener is not active.
867       */
868      public synchronized int getListenPort(final String listenerName)
869      {
870        final String name;
871        if (listenerName == null)
872        {
873          name = getFirstListenerName();
874        }
875        else
876        {
877          name = StaticUtils.toLowerCase(listenerName);
878        }
879    
880        final LDAPListener listener = listeners.get(name);
881        if (listener == null)
882        {
883          return -1;
884        }
885        else
886        {
887          return listener.getListenPort();
888        }
889      }
890    
891    
892    
893      /**
894       * Retrieves the configured client socket factory for the first active
895       * listener.
896       *
897       * @return  The configured client socket factory for the first active
898       *          listener, or {@code null} if that listener does not have an
899       *          explicitly-configured socket factory or there are no active
900       *          listeners.
901       */
902      public SocketFactory getClientSocketFactory()
903      {
904        return getClientSocketFactory(null);
905      }
906    
907    
908    
909      /**
910       * Retrieves the configured client socket factory for the specified listener,
911       * if available.
912       *
913       * @param  listenerName  The name of the listener for which to retrieve the
914       *                       client socket factory.  It may be {@code null} in
915       *                       order to obtain the client socket factory for the
916       *                       first active listener.
917       *
918       * @return  The configured client socket factory for the specified listener,
919       *          or {@code null} if there is no such listener or that listener does
920       *          not have an explicitly-configured client socket factory.
921       */
922      public synchronized SocketFactory getClientSocketFactory(
923                                             final String listenerName)
924      {
925        final String name;
926        if (listenerName == null)
927        {
928          name = getFirstListenerName();
929        }
930        else
931        {
932          name = StaticUtils.toLowerCase(listenerName);
933        }
934    
935        return clientSocketFactories.get(name);
936      }
937    
938    
939    
940      /**
941       * Retrieves the name of the first running listener.
942       *
943       * @return  The name of the first running listener, or {@code null} if there
944       *          are no active listeners.
945       */
946      private String getFirstListenerName()
947      {
948        for (final Map.Entry<String,LDAPListenerConfig> e :
949             ldapListenerConfigs.entrySet())
950        {
951          final String name = e.getKey();
952          if (listeners.containsKey(name))
953          {
954            return name;
955          }
956        }
957    
958        return null;
959      }
960    
961    
962    
963      /**
964       * Retrieves the delay in milliseconds that the server should impose before
965       * beginning processing for operations.
966       *
967       * @return  The delay in milliseconds that the server should impose before
968       *          beginning processing for operations, or 0 if there should be no
969       *          delay inserted when processing operations.
970       */
971      public long getProcessingDelayMillis()
972      {
973        return inMemoryHandler.getProcessingDelayMillis();
974      }
975    
976    
977    
978      /**
979       * Specifies the delay in milliseconds that the server should impose before
980       * beginning processing for operations.
981       *
982       * @param  processingDelayMillis  The delay in milliseconds that the server
983       *                                should impose before beginning processing
984       *                                for operations.  A value less than or equal
985       *                                to zero may be used to indicate that there
986       *                                should be no delay.
987       */
988      public void setProcessingDelayMillis(final long processingDelayMillis)
989      {
990        inMemoryHandler.setProcessingDelayMillis(processingDelayMillis);
991      }
992    
993    
994    
995      /**
996       * Retrieves the number of entries currently held in the server.  The count
997       * returned will not include entries which are part of the changelog.
998       * <BR><BR>
999       * This method may be used regardless of whether the server is listening for
1000       * client connections.
1001       *
1002       * @return  The number of entries currently held in the server.
1003       */
1004      public int countEntries()
1005      {
1006        return countEntries(false);
1007      }
1008    
1009    
1010    
1011      /**
1012       * Retrieves the number of entries currently held in the server, optionally
1013       * including those entries which are part of the changelog.
1014       * <BR><BR>
1015       * This method may be used regardless of whether the server is listening for
1016       * client connections.
1017       *
1018       * @param  includeChangeLog  Indicates whether to include entries that are
1019       *                           part of the changelog in the count.
1020       *
1021       * @return  The number of entries currently held in the server.
1022       */
1023      public int countEntries(final boolean includeChangeLog)
1024      {
1025        return inMemoryHandler.countEntries(includeChangeLog);
1026      }
1027    
1028    
1029    
1030      /**
1031       * Retrieves the number of entries currently held in the server whose DN
1032       * matches or is subordinate to the provided base DN.
1033       * <BR><BR>
1034       * This method may be used regardless of whether the server is listening for
1035       * client connections.
1036       *
1037       * @param  baseDN  The base DN to use for the determination.
1038       *
1039       * @return  The number of entries currently held in the server whose DN
1040       *          matches or is subordinate to the provided base DN.
1041       *
1042       * @throws  LDAPException  If the provided string cannot be parsed as a valid
1043       *                         DN.
1044       */
1045      public int countEntriesBelow(final String baseDN)
1046             throws LDAPException
1047      {
1048        return inMemoryHandler.countEntriesBelow(baseDN);
1049      }
1050    
1051    
1052    
1053      /**
1054       * Removes all entries currently held in the server.  If a changelog is
1055       * enabled, then all changelog entries will also be cleared but the base
1056       * "cn=changelog" entry will be retained.
1057       * <BR><BR>
1058       * This method may be used regardless of whether the server is listening for
1059       * client connections.
1060       */
1061      public void clear()
1062      {
1063        inMemoryHandler.clear();
1064      }
1065    
1066    
1067    
1068      /**
1069       * Reads entries from the specified LDIF file and adds them to the server,
1070       * optionally clearing any existing entries before beginning to add the new
1071       * entries.  If an error is encountered while adding entries from LDIF then
1072       * the server will remain populated with the data it held before the import
1073       * attempt (even if the {@code clear} is given with a value of {@code true}).
1074       * <BR><BR>
1075       * This method may be used regardless of whether the server is listening for
1076       * client connections.
1077       *
1078       * @param  clear  Indicates whether to remove all existing entries prior to
1079       *                adding entries read from LDIF.
1080       * @param  path   The path to the LDIF file from which the entries should be
1081       *                read.  It must not be {@code null}.
1082       *
1083       * @return  The number of entries read from LDIF and added to the server.
1084       *
1085       * @throws  LDAPException  If a problem occurs while reading entries or adding
1086       *                         them to the server.
1087       */
1088      public int importFromLDIF(final boolean clear, final String path)
1089             throws LDAPException
1090      {
1091        final LDIFReader reader;
1092        try
1093        {
1094          reader = new LDIFReader(path);
1095        }
1096        catch (final Exception e)
1097        {
1098          Debug.debugException(e);
1099          throw new LDAPException(ResultCode.LOCAL_ERROR,
1100               ERR_MEM_DS_INIT_FROM_LDIF_CANNOT_CREATE_READER.get(path,
1101                    StaticUtils.getExceptionMessage(e)),
1102               e);
1103        }
1104    
1105        return importFromLDIF(clear, reader);
1106      }
1107    
1108    
1109    
1110      /**
1111       * Reads entries from the provided LDIF reader and adds them to the server,
1112       * optionally clearing any existing entries before beginning to add the new
1113       * entries.  If an error is encountered while adding entries from LDIF then
1114       * the server will remain populated with the data it held before the import
1115       * attempt (even if the {@code clear} is given with a value of {@code true}).
1116       * <BR><BR>
1117       * This method may be used regardless of whether the server is listening for
1118       * client connections.
1119       *
1120       * @param  clear   Indicates whether to remove all existing entries prior to
1121       *                 adding entries read from LDIF.
1122       * @param  reader  The LDIF reader to use to obtain the entries to be
1123       *                 imported.
1124       *
1125       * @return  The number of entries read from LDIF and added to the server.
1126       *
1127       * @throws  LDAPException  If a problem occurs while reading entries or adding
1128       *                         them to the server.
1129       */
1130      public int importFromLDIF(final boolean clear, final LDIFReader reader)
1131             throws LDAPException
1132      {
1133        return inMemoryHandler.importFromLDIF(clear, reader);
1134      }
1135    
1136    
1137    
1138      /**
1139       * Writes the current contents of the server in LDIF form to the specified
1140       * file.
1141       * <BR><BR>
1142       * This method may be used regardless of whether the server is listening for
1143       * client connections.
1144       *
1145       * @param  path                   The path of the file to which the LDIF
1146       *                                entries should be written.
1147       * @param  excludeGeneratedAttrs  Indicates whether to exclude automatically
1148       *                                generated operational attributes like
1149       *                                entryUUID, entryDN, creatorsName, etc.
1150       * @param  excludeChangeLog       Indicates whether to exclude entries
1151       *                                contained in the changelog.
1152       *
1153       * @return  The number of entries written to LDIF.
1154       *
1155       * @throws  LDAPException  If a problem occurs while writing entries to LDIF.
1156       */
1157      public int exportToLDIF(final String path,
1158                              final boolean excludeGeneratedAttrs,
1159                              final boolean excludeChangeLog)
1160             throws LDAPException
1161      {
1162        final LDIFWriter ldifWriter;
1163        try
1164        {
1165          ldifWriter = new LDIFWriter(path);
1166        }
1167        catch (final Exception e)
1168        {
1169          Debug.debugException(e);
1170          throw new LDAPException(ResultCode.LOCAL_ERROR,
1171               ERR_MEM_DS_EXPORT_TO_LDIF_CANNOT_CREATE_WRITER.get(path,
1172                    StaticUtils.getExceptionMessage(e)),
1173               e);
1174        }
1175    
1176        return exportToLDIF(ldifWriter, excludeGeneratedAttrs, excludeChangeLog,
1177             true);
1178      }
1179    
1180    
1181    
1182      /**
1183       * Writes the current contents of the server in LDIF form using the provided
1184       * LDIF writer.
1185       * <BR><BR>
1186       * This method may be used regardless of whether the server is listening for
1187       * client connections.
1188       *
1189       * @param  ldifWriter             The LDIF writer to use when writing the
1190       *                                entries.  It must not be {@code null}.
1191       * @param  excludeGeneratedAttrs  Indicates whether to exclude automatically
1192       *                                generated operational attributes like
1193       *                                entryUUID, entryDN, creatorsName, etc.
1194       * @param  excludeChangeLog       Indicates whether to exclude entries
1195       *                                contained in the changelog.
1196       * @param  closeWriter            Indicates whether the LDIF writer should be
1197       *                                closed after all entries have been written.
1198       *
1199       * @return  The number of entries written to LDIF.
1200       *
1201       * @throws  LDAPException  If a problem occurs while writing entries to LDIF.
1202       */
1203      public int exportToLDIF(final LDIFWriter ldifWriter,
1204                              final boolean excludeGeneratedAttrs,
1205                              final boolean excludeChangeLog,
1206                              final boolean closeWriter)
1207             throws LDAPException
1208      {
1209        return inMemoryHandler.exportToLDIF(ldifWriter, excludeGeneratedAttrs,
1210             excludeChangeLog, closeWriter);
1211      }
1212    
1213    
1214    
1215      /**
1216       * {@inheritDoc}
1217       * <BR><BR>
1218       * This method may be used regardless of whether the server is listening for
1219       * client connections.
1220       */
1221      public RootDSE getRootDSE()
1222             throws LDAPException
1223      {
1224        return new RootDSE(inMemoryHandler.getEntry(""));
1225      }
1226    
1227    
1228    
1229      /**
1230       * {@inheritDoc}
1231       * <BR><BR>
1232       * This method may be used regardless of whether the server is listening for
1233       * client connections.
1234       */
1235      public Schema getSchema()
1236             throws LDAPException
1237      {
1238        return inMemoryHandler.getSchema();
1239      }
1240    
1241    
1242    
1243      /**
1244       * {@inheritDoc}
1245       * <BR><BR>
1246       * This method may be used regardless of whether the server is listening for
1247       * client connections.
1248       */
1249      public Schema getSchema(final String entryDN)
1250             throws LDAPException
1251      {
1252        return inMemoryHandler.getSchema();
1253      }
1254    
1255    
1256    
1257      /**
1258       * {@inheritDoc}
1259       * <BR><BR>
1260       * This method may be used regardless of whether the server is listening for
1261       * client connections.
1262       */
1263      public SearchResultEntry getEntry(final String dn)
1264             throws LDAPException
1265      {
1266        return searchForEntry(dn, SearchScope.BASE,
1267             Filter.createPresenceFilter("objectClass"));
1268      }
1269    
1270    
1271    
1272      /**
1273       * {@inheritDoc}
1274       * <BR><BR>
1275       * This method may be used regardless of whether the server is listening for
1276       * client connections, and regardless of whether search operations are
1277       * allowed in the server.
1278       */
1279      public SearchResultEntry getEntry(final String dn, final String... attributes)
1280             throws LDAPException
1281      {
1282        return searchForEntry(dn, SearchScope.BASE,
1283             Filter.createPresenceFilter("objectClass"), attributes);
1284      }
1285    
1286    
1287    
1288      /**
1289       * {@inheritDoc}
1290       * <BR><BR>
1291       * This method may be used regardless of whether the server is listening for
1292       * client connections, and regardless of whether add operations are allowed in
1293       * the server.
1294       */
1295      public LDAPResult add(final String dn, final Attribute... attributes)
1296             throws LDAPException
1297      {
1298        return add(new AddRequest(dn, attributes));
1299      }
1300    
1301    
1302    
1303      /**
1304       * {@inheritDoc}
1305       * <BR><BR>
1306       * This method may be used regardless of whether the server is listening for
1307       * client connections, and regardless of whether add operations are allowed in
1308       * the server.
1309       */
1310      public LDAPResult add(final String dn, final Collection<Attribute> attributes)
1311             throws LDAPException
1312      {
1313        return add(new AddRequest(dn, attributes));
1314      }
1315    
1316    
1317    
1318      /**
1319       * {@inheritDoc}
1320       * <BR><BR>
1321       * This method may be used regardless of whether the server is listening for
1322       * client connections, and regardless of whether add operations are allowed in
1323       * the server.
1324       */
1325      public LDAPResult add(final Entry entry)
1326             throws LDAPException
1327      {
1328        return add(new AddRequest(entry));
1329      }
1330    
1331    
1332    
1333      /**
1334       * {@inheritDoc}
1335       * <BR><BR>
1336       * This method may be used regardless of whether the server is listening for
1337       * client connections, and regardless of whether add operations are allowed in
1338       * the server.
1339       */
1340      public LDAPResult add(final String... ldifLines)
1341             throws LDIFException, LDAPException
1342      {
1343        return add(new AddRequest(ldifLines));
1344      }
1345    
1346    
1347    
1348      /**
1349       * {@inheritDoc}
1350       * <BR><BR>
1351       * This method may be used regardless of whether the server is listening for
1352       * client connections, and regardless of whether add operations are allowed in
1353       * the server.
1354       */
1355      public LDAPResult add(final AddRequest addRequest)
1356             throws LDAPException
1357      {
1358        final ArrayList<Control> requestControlList =
1359             new ArrayList<Control>(addRequest.getControlList());
1360        requestControlList.add(new Control(
1361             InMemoryRequestHandler.OID_INTERNAL_OPERATION_REQUEST_CONTROL, false));
1362    
1363        final LDAPMessage responseMessage = inMemoryHandler.processAddRequest(1,
1364             new AddRequestProtocolOp(addRequest.getDN(),
1365                  addRequest.getAttributes()),
1366             requestControlList);
1367    
1368        final AddResponseProtocolOp addResponse =
1369             responseMessage.getAddResponseProtocolOp();
1370    
1371        final LDAPResult ldapResult = new LDAPResult(responseMessage.getMessageID(),
1372             ResultCode.valueOf(addResponse.getResultCode()),
1373             addResponse.getDiagnosticMessage(), addResponse.getMatchedDN(),
1374             addResponse.getReferralURLs(), responseMessage.getControls());
1375    
1376        switch (addResponse.getResultCode())
1377        {
1378          case ResultCode.SUCCESS_INT_VALUE:
1379          case ResultCode.NO_OPERATION_INT_VALUE:
1380            return ldapResult;
1381          default:
1382            throw new LDAPException(ldapResult);
1383        }
1384      }
1385    
1386    
1387    
1388      /**
1389       * {@inheritDoc}
1390       * <BR><BR>
1391       * This method may be used regardless of whether the server is listening for
1392       * client connections, and regardless of whether add operations are allowed in
1393       * the server.
1394       */
1395      public LDAPResult add(final ReadOnlyAddRequest addRequest)
1396             throws LDAPException
1397      {
1398        return add(addRequest.duplicate());
1399      }
1400    
1401    
1402    
1403      /**
1404       * Attempts to add all of the provided entries to the server.  If a problem is
1405       * encountered while attempting to add any of the provided entries, then the
1406       * server will remain populated with the data it held before this method was
1407       * called.
1408       * <BR><BR>
1409       * This method may be used regardless of whether the server is listening for
1410       * client connections, and regardless of whether add operations are allowed in
1411       * the server.
1412       *
1413       * @param  entries  The entries to be added to the server.
1414       *
1415       * @throws  LDAPException  If a problem is encountered while attempting to add
1416       *                         any of the provided entries.
1417       */
1418      public void addEntries(final Entry... entries)
1419             throws LDAPException
1420      {
1421        addEntries(Arrays.asList(entries));
1422      }
1423    
1424    
1425    
1426      /**
1427       * Attempts to add all of the provided entries to the server.  If a problem is
1428       * encountered while attempting to add any of the provided entries, then the
1429       * server will remain populated with the data it held before this method was
1430       * called.
1431       * <BR><BR>
1432       * This method may be used regardless of whether the server is listening for
1433       * client connections, and regardless of whether add operations are allowed in
1434       * the server.
1435       *
1436       * @param  entries  The entries to be added to the server.
1437       *
1438       * @throws  LDAPException  If a problem is encountered while attempting to add
1439       *                         any of the provided entries.
1440       */
1441      public void addEntries(final List<? extends Entry> entries)
1442             throws LDAPException
1443      {
1444        inMemoryHandler.addEntries(entries);
1445      }
1446    
1447    
1448    
1449      /**
1450       * Attempts to add a set of entries provided in LDIF form in which each
1451       * element of the provided array is a line of the LDIF representation, with
1452       * empty strings as separators between entries (as you would have for blank
1453       * lines in an LDIF file).  If a problem is encountered while attempting to
1454       * add any of the provided entries, then the server will remain populated with
1455       * the data it held before this method was called.
1456       * <BR><BR>
1457       * This method may be used regardless of whether the server is listening for
1458       * client connections, and regardless of whether add operations are allowed in
1459       * the server.
1460       *
1461       * @param  ldifEntryLines  The lines comprising the LDIF representation of the
1462       *                         entries to be added.
1463       *
1464       * @throws  LDAPException  If a problem is encountered while attempting to add
1465       *                         any of the provided entries.
1466       */
1467      public void addEntries(final String... ldifEntryLines)
1468             throws LDAPException
1469      {
1470        final ByteStringBuffer buffer = new ByteStringBuffer();
1471        for (final String line : ldifEntryLines)
1472        {
1473          buffer.append(line);
1474          buffer.append(StaticUtils.EOL_BYTES);
1475        }
1476    
1477        final ArrayList<Entry> entryList = new ArrayList<Entry>(10);
1478        final LDIFReader reader = new LDIFReader(buffer.asInputStream());
1479        while (true)
1480        {
1481          try
1482          {
1483            final Entry entry = reader.readEntry();
1484            if (entry == null)
1485            {
1486              break;
1487            }
1488            else
1489            {
1490              entryList.add(entry);
1491            }
1492          }
1493          catch (final Exception e)
1494          {
1495            Debug.debugException(e);
1496            throw new LDAPException(ResultCode.PARAM_ERROR,
1497                 ERR_MEM_DS_ADD_ENTRIES_LDIF_PARSE_EXCEPTION.get(
1498                      StaticUtils.getExceptionMessage(e)),
1499                 e);
1500          }
1501        }
1502    
1503        addEntries(entryList);
1504      }
1505    
1506    
1507    
1508      /**
1509       * Processes a simple bind request with the provided DN and password.  Note
1510       * that the bind processing will verify that the provided credentials are
1511       * valid, but it will not alter the server in any way.
1512       *
1513       * @param  bindDN    The bind DN for the bind operation.
1514       * @param  password  The password for the simple bind operation.
1515       *
1516       * @return  The result of processing the bind operation.
1517       *
1518       * @throws  LDAPException  If the server rejects the bind request, or if a
1519       *                         problem occurs while sending the request or reading
1520       *                         the response.
1521       */
1522      public BindResult bind(final String bindDN, final String password)
1523             throws LDAPException
1524      {
1525        return bind(new SimpleBindRequest(bindDN, password));
1526      }
1527    
1528    
1529    
1530      /**
1531       * Processes the provided bind request.  Only simple and SASL PLAIN bind
1532       * requests are supported.  Note that the bind processing will verify that the
1533       * provided credentials are valid, but it will not alter the server in any
1534       * way.
1535       *
1536       * @param  bindRequest  The bind request to be processed.  It must not be
1537       *                      {@code null}.
1538       *
1539       * @return  The result of processing the bind operation.
1540       *
1541       * @throws  LDAPException  If the server rejects the bind request, or if a
1542       *                         problem occurs while sending the request or reading
1543       *                         the response.
1544       */
1545      public BindResult bind(final BindRequest bindRequest)
1546             throws LDAPException
1547      {
1548        final ArrayList<Control> requestControlList =
1549             new ArrayList<Control>(bindRequest.getControlList());
1550        requestControlList.add(new Control(
1551             InMemoryRequestHandler.OID_INTERNAL_OPERATION_REQUEST_CONTROL, false));
1552    
1553        final BindRequestProtocolOp bindOp;
1554        if (bindRequest instanceof SimpleBindRequest)
1555        {
1556          final SimpleBindRequest r = (SimpleBindRequest) bindRequest;
1557          bindOp = new BindRequestProtocolOp(r.getBindDN(),
1558               r.getPassword().getValue());
1559        }
1560        else if (bindRequest instanceof PLAINBindRequest)
1561        {
1562          final PLAINBindRequest r = (PLAINBindRequest) bindRequest;
1563    
1564          // Create the byte array that should comprise the credentials.
1565          final byte[] authZIDBytes = StaticUtils.getBytes(r.getAuthorizationID());
1566          final byte[] authNIDBytes = StaticUtils.getBytes(r.getAuthenticationID());
1567          final byte[] passwordBytes = r.getPasswordBytes();
1568    
1569          final byte[] credBytes = new byte[2 + authZIDBytes.length +
1570               authNIDBytes.length + passwordBytes.length];
1571          System.arraycopy(authZIDBytes, 0, credBytes, 0, authZIDBytes.length);
1572    
1573          int pos = authZIDBytes.length + 1;
1574          System.arraycopy(authNIDBytes, 0, credBytes, pos, authNIDBytes.length);
1575    
1576          pos += authNIDBytes.length + 1;
1577          System.arraycopy(passwordBytes, 0, credBytes, pos, passwordBytes.length);
1578    
1579          bindOp = new BindRequestProtocolOp(null, "PLAIN",
1580               new ASN1OctetString(credBytes));
1581        }
1582        else
1583        {
1584          throw new LDAPException(ResultCode.AUTH_METHOD_NOT_SUPPORTED,
1585               ERR_MEM_DS_UNSUPPORTED_BIND_TYPE.get());
1586        }
1587    
1588        final LDAPMessage responseMessage = inMemoryHandler.processBindRequest(1,
1589             bindOp, requestControlList);
1590        final BindResponseProtocolOp bindResponse =
1591             responseMessage.getBindResponseProtocolOp();
1592    
1593        final BindResult bindResult = new BindResult(new LDAPResult(
1594             responseMessage.getMessageID(),
1595             ResultCode.valueOf(bindResponse.getResultCode()),
1596             bindResponse.getDiagnosticMessage(), bindResponse.getMatchedDN(),
1597             bindResponse.getReferralURLs(), responseMessage.getControls()));
1598    
1599        switch (bindResponse.getResultCode())
1600        {
1601          case ResultCode.SUCCESS_INT_VALUE:
1602            return bindResult;
1603          default:
1604            throw new LDAPException(bindResult);
1605        }
1606      }
1607    
1608    
1609    
1610      /**
1611       * {@inheritDoc}
1612       * <BR><BR>
1613       * This method may be used regardless of whether the server is listening for
1614       * client connections, and regardless of whether compare operations are
1615       * allowed in the server.
1616       */
1617      public CompareResult compare(final String dn, final String attributeName,
1618                            final String assertionValue)
1619             throws LDAPException
1620      {
1621        return compare(new CompareRequest(dn, attributeName, assertionValue));
1622      }
1623    
1624    
1625    
1626      /**
1627       * {@inheritDoc}
1628       * <BR><BR>
1629       * This method may be used regardless of whether the server is listening for
1630       * client connections, and regardless of whether compare operations are
1631       * allowed in the server.
1632       */
1633      public CompareResult compare(final CompareRequest compareRequest)
1634             throws LDAPException
1635      {
1636        final ArrayList<Control> requestControlList =
1637             new ArrayList<Control>(compareRequest.getControlList());
1638        requestControlList.add(new Control(
1639             InMemoryRequestHandler.OID_INTERNAL_OPERATION_REQUEST_CONTROL, false));
1640    
1641        final LDAPMessage responseMessage = inMemoryHandler.processCompareRequest(1,
1642             new CompareRequestProtocolOp(compareRequest.getDN(),
1643                  compareRequest.getAttributeName(),
1644                  compareRequest.getRawAssertionValue()),
1645             requestControlList);
1646    
1647        final CompareResponseProtocolOp compareResponse =
1648             responseMessage.getCompareResponseProtocolOp();
1649    
1650        final LDAPResult compareResult = new LDAPResult(
1651             responseMessage.getMessageID(),
1652             ResultCode.valueOf(compareResponse.getResultCode()),
1653             compareResponse.getDiagnosticMessage(), compareResponse.getMatchedDN(),
1654             compareResponse.getReferralURLs(), responseMessage.getControls());
1655    
1656        switch (compareResponse.getResultCode())
1657        {
1658          case ResultCode.COMPARE_TRUE_INT_VALUE:
1659          case ResultCode.COMPARE_FALSE_INT_VALUE:
1660            return new CompareResult(compareResult);
1661          default:
1662            throw new LDAPException(compareResult);
1663        }
1664      }
1665    
1666    
1667    
1668      /**
1669       * {@inheritDoc}
1670       * <BR><BR>
1671       * This method may be used regardless of whether the server is listening for
1672       * client connections, and regardless of whether compare operations are
1673       * allowed in the server.
1674       */
1675      public CompareResult compare(final ReadOnlyCompareRequest compareRequest)
1676             throws LDAPException
1677      {
1678        return compare(compareRequest.duplicate());
1679      }
1680    
1681    
1682    
1683      /**
1684       * {@inheritDoc}
1685       * <BR><BR>
1686       * This method may be used regardless of whether the server is listening for
1687       * client connections, and regardless of whether delete operations are
1688       * allowed in the server.
1689       */
1690      public LDAPResult delete(final String dn)
1691             throws LDAPException
1692      {
1693        return delete(new DeleteRequest(dn));
1694      }
1695    
1696    
1697    
1698      /**
1699       * {@inheritDoc}
1700       * <BR><BR>
1701       * This method may be used regardless of whether the server is listening for
1702       * client connections, and regardless of whether delete operations are
1703       * allowed in the server.
1704       */
1705      public LDAPResult delete(final DeleteRequest deleteRequest)
1706             throws LDAPException
1707      {
1708        final ArrayList<Control> requestControlList =
1709             new ArrayList<Control>(deleteRequest.getControlList());
1710        requestControlList.add(new Control(
1711             InMemoryRequestHandler.OID_INTERNAL_OPERATION_REQUEST_CONTROL, false));
1712    
1713        final LDAPMessage responseMessage = inMemoryHandler.processDeleteRequest(1,
1714             new DeleteRequestProtocolOp(deleteRequest.getDN()),
1715             requestControlList);
1716    
1717        final DeleteResponseProtocolOp deleteResponse =
1718             responseMessage.getDeleteResponseProtocolOp();
1719    
1720        final LDAPResult ldapResult = new LDAPResult(responseMessage.getMessageID(),
1721             ResultCode.valueOf(deleteResponse.getResultCode()),
1722             deleteResponse.getDiagnosticMessage(), deleteResponse.getMatchedDN(),
1723             deleteResponse.getReferralURLs(), responseMessage.getControls());
1724    
1725        switch (deleteResponse.getResultCode())
1726        {
1727          case ResultCode.SUCCESS_INT_VALUE:
1728          case ResultCode.NO_OPERATION_INT_VALUE:
1729            return ldapResult;
1730          default:
1731            throw new LDAPException(ldapResult);
1732        }
1733      }
1734    
1735    
1736    
1737      /**
1738       * {@inheritDoc}
1739       * <BR><BR>
1740       * This method may be used regardless of whether the server is listening for
1741       * client connections, and regardless of whether delete operations are
1742       * allowed in the server.
1743       */
1744      public LDAPResult delete(final ReadOnlyDeleteRequest deleteRequest)
1745             throws LDAPException
1746      {
1747        return delete(deleteRequest.duplicate());
1748      }
1749    
1750    
1751    
1752      /**
1753       * Attempts to delete the specified entry and all entries below it from the
1754       * server.
1755       * <BR><BR>
1756       * This method may be used regardless of whether the server is listening for
1757       * client connections, and regardless of whether compare operations are
1758       * allowed in the server.
1759       *
1760       * @param  baseDN  The DN of the entry to remove, along with all of its
1761       *                 subordinates.
1762       *
1763       * @return  The number of entries removed from the server, or zero if the
1764       *          specified entry was not found.
1765       *
1766       * @throws  LDAPException  If a problem is encountered while attempting to
1767       *                         remove the entries.
1768       */
1769      public int deleteSubtree(final String baseDN)
1770             throws LDAPException
1771      {
1772        return inMemoryHandler.deleteSubtree(baseDN);
1773      }
1774    
1775    
1776    
1777      /**
1778       * Processes an extended request with the provided request OID.  Note that
1779       * because some types of extended operations return unusual result codes under
1780       * "normal" conditions, the server may not always throw an exception for a
1781       * failed extended operation like it does for other types of operations.  It
1782       * will throw an exception under conditions where there appears to be a
1783       * problem with the connection or the server to which the connection is
1784       * established, but there may be many circumstances in which an extended
1785       * operation is not processed correctly but this method does not throw an
1786       * exception.  In the event that no exception is thrown, it is the
1787       * responsibility of the caller to interpret the result to determine whether
1788       * the operation was processed as expected.
1789       * <BR><BR>
1790       * This method may be used regardless of whether the server is listening for
1791       * client connections, and regardless of whether extended operations are
1792       * allowed in the server.
1793       *
1794       * @param  requestOID  The OID for the extended request to process.  It must
1795       *                     not be {@code null}.
1796       *
1797       * @return  The extended result object that provides information about the
1798       *          result of the request processing.  It may or may not indicate that
1799       *          the operation was successful.
1800       *
1801       * @throws  LDAPException  If a problem occurs while sending the request or
1802       *                         reading the response.
1803       */
1804      public ExtendedResult processExtendedOperation(final String requestOID)
1805             throws LDAPException
1806      {
1807        Validator.ensureNotNull(requestOID);
1808    
1809        return processExtendedOperation(new ExtendedRequest(requestOID));
1810      }
1811    
1812    
1813    
1814      /**
1815       * Processes an extended request with the provided request OID and value.
1816       * Note that because some types of extended operations return unusual result
1817       * codes under "normal" conditions, the server may not always throw an
1818       * exception for a failed extended operation like it does for other types of
1819       * operations.  It will throw an exception under conditions where there
1820       * appears to be a problem with the connection or the server to which the
1821       * connection is established, but there may be many circumstances in which an
1822       * extended operation is not processed correctly but this method does not
1823       * throw an exception.  In the event that no exception is thrown, it is the
1824       * responsibility of the caller to interpret the result to determine whether
1825       * the operation was processed as expected.
1826       * <BR><BR>
1827       * This method may be used regardless of whether the server is listening for
1828       * client connections, and regardless of whether extended operations are
1829       * allowed in the server.
1830       *
1831       * @param  requestOID    The OID for the extended request to process.  It must
1832       *                       not be {@code null}.
1833       * @param  requestValue  The encoded value for the extended request to
1834       *                       process.  It may be {@code null} if there does not
1835       *                       need to be a value for the requested operation.
1836       *
1837       * @return  The extended result object that provides information about the
1838       *          result of the request processing.  It may or may not indicate that
1839       *          the operation was successful.
1840       *
1841       * @throws  LDAPException  If a problem occurs while sending the request or
1842       *                         reading the response.
1843       */
1844      public ExtendedResult processExtendedOperation(final String requestOID,
1845                                 final ASN1OctetString requestValue)
1846             throws LDAPException
1847      {
1848        Validator.ensureNotNull(requestOID);
1849    
1850        return processExtendedOperation(new ExtendedRequest(requestOID,
1851             requestValue));
1852      }
1853    
1854    
1855    
1856      /**
1857       * Processes the provided extended request.  Note that because some types of
1858       * extended operations return unusual result codes under "normal" conditions,
1859       * the server may not always throw an exception for a failed extended
1860       * operation like it does for other types of operations.  It will throw an
1861       * exception under conditions where there appears to be a problem with the
1862       * connection or the server to which the connection is established, but there
1863       * may be many circumstances in which an extended operation is not processed
1864       * correctly but this method does not throw an exception.  In the event that
1865       * no exception is thrown, it is the responsibility of the caller to interpret
1866       * the result to determine whether the operation was processed as expected.
1867       * <BR><BR>
1868       * This method may be used regardless of whether the server is listening for
1869       * client connections, and regardless of whether extended operations are
1870       * allowed in the server.
1871       *
1872       * @param  extendedRequest  The extended request to be processed.  It must not
1873       *                          be {@code null}.
1874       *
1875       * @return  The extended result object that provides information about the
1876       *          result of the request processing.  It may or may not indicate that
1877       *          the operation was successful.
1878       *
1879       * @throws  LDAPException  If a problem occurs while sending the request or
1880       *                         reading the response.
1881       */
1882      public ExtendedResult processExtendedOperation(
1883                                   final ExtendedRequest extendedRequest)
1884             throws LDAPException
1885      {
1886        Validator.ensureNotNull(extendedRequest);
1887    
1888        final ArrayList<Control> requestControlList =
1889             new ArrayList<Control>(extendedRequest.getControlList());
1890        requestControlList.add(new Control(
1891             InMemoryRequestHandler.OID_INTERNAL_OPERATION_REQUEST_CONTROL, false));
1892    
1893    
1894        final LDAPMessage responseMessage =
1895             inMemoryHandler.processExtendedRequest(1,
1896                  new ExtendedRequestProtocolOp(extendedRequest.getOID(),
1897                       extendedRequest.getValue()),
1898                  requestControlList);
1899    
1900        final ExtendedResponseProtocolOp extendedResponse =
1901             responseMessage.getExtendedResponseProtocolOp();
1902    
1903        final ResultCode rc = ResultCode.valueOf(extendedResponse.getResultCode());
1904    
1905        final String[] referralURLs;
1906        final List<String> referralURLList = extendedResponse.getReferralURLs();
1907        if ((referralURLList == null) || referralURLList.isEmpty())
1908        {
1909          referralURLs = StaticUtils.NO_STRINGS;
1910        }
1911        else
1912        {
1913          referralURLs = new String[referralURLList.size()];
1914          referralURLList.toArray(referralURLs);
1915        }
1916    
1917        final Control[] responseControls;
1918        final List<Control> controlList = responseMessage.getControls();
1919        if ((controlList == null) || controlList.isEmpty())
1920        {
1921          responseControls = StaticUtils.NO_CONTROLS;
1922        }
1923        else
1924        {
1925          responseControls = new Control[controlList.size()];
1926          controlList.toArray(responseControls);
1927        }
1928    
1929        final ExtendedResult extendedResult = new ExtendedResult(
1930             responseMessage.getMessageID(), rc,
1931             extendedResponse.getDiagnosticMessage(),
1932             extendedResponse.getMatchedDN(), referralURLs,
1933             extendedResponse.getResponseOID(),
1934             extendedResponse.getResponseValue(), responseControls);
1935    
1936        if ((extendedResult.getOID() == null) &&
1937            (extendedResult.getValue() == null))
1938        {
1939          switch (rc.intValue())
1940          {
1941            case ResultCode.OPERATIONS_ERROR_INT_VALUE:
1942            case ResultCode.PROTOCOL_ERROR_INT_VALUE:
1943            case ResultCode.BUSY_INT_VALUE:
1944            case ResultCode.UNAVAILABLE_INT_VALUE:
1945            case ResultCode.OTHER_INT_VALUE:
1946            case ResultCode.SERVER_DOWN_INT_VALUE:
1947            case ResultCode.LOCAL_ERROR_INT_VALUE:
1948            case ResultCode.ENCODING_ERROR_INT_VALUE:
1949            case ResultCode.DECODING_ERROR_INT_VALUE:
1950            case ResultCode.TIMEOUT_INT_VALUE:
1951            case ResultCode.NO_MEMORY_INT_VALUE:
1952            case ResultCode.CONNECT_ERROR_INT_VALUE:
1953              throw new LDAPException(extendedResult);
1954          }
1955        }
1956    
1957        return extendedResult;
1958      }
1959    
1960    
1961    
1962      /**
1963       * {@inheritDoc}
1964       * <BR><BR>
1965       * This method may be used regardless of whether the server is listening for
1966       * client connections, and regardless of whether modify operations are allowed
1967       * in the server.
1968       */
1969      public LDAPResult modify(final String dn, final Modification mod)
1970             throws LDAPException
1971      {
1972        return modify(new ModifyRequest(dn, mod));
1973      }
1974    
1975    
1976    
1977      /**
1978       * {@inheritDoc}
1979       * <BR><BR>
1980       * This method may be used regardless of whether the server is listening for
1981       * client connections, and regardless of whether modify operations are allowed
1982       * in the server.
1983       */
1984      public LDAPResult modify(final String dn, final Modification... mods)
1985             throws LDAPException
1986      {
1987        return modify(new ModifyRequest(dn, mods));
1988      }
1989    
1990    
1991    
1992      /**
1993       * {@inheritDoc}
1994       * <BR><BR>
1995       * This method may be used regardless of whether the server is listening for
1996       * client connections, and regardless of whether modify operations are allowed
1997       * in the server.
1998       */
1999      public LDAPResult modify(final String dn, final List<Modification> mods)
2000             throws LDAPException
2001      {
2002        return modify(new ModifyRequest(dn, mods));
2003      }
2004    
2005    
2006    
2007      /**
2008       * {@inheritDoc}
2009       * <BR><BR>
2010       * This method may be used regardless of whether the server is listening for
2011       * client connections, and regardless of whether modify operations are allowed
2012       * in the server.
2013       */
2014      public LDAPResult modify(final String... ldifModificationLines)
2015             throws LDIFException, LDAPException
2016      {
2017        return modify(new ModifyRequest(ldifModificationLines));
2018      }
2019    
2020    
2021    
2022      /**
2023       * {@inheritDoc}
2024       * <BR><BR>
2025       * This method may be used regardless of whether the server is listening for
2026       * client connections, and regardless of whether modify operations are allowed
2027       * in the server.
2028       */
2029      public LDAPResult modify(final ModifyRequest modifyRequest)
2030             throws LDAPException
2031      {
2032        final ArrayList<Control> requestControlList =
2033             new ArrayList<Control>(modifyRequest.getControlList());
2034        requestControlList.add(new Control(
2035             InMemoryRequestHandler.OID_INTERNAL_OPERATION_REQUEST_CONTROL, false));
2036    
2037        final LDAPMessage responseMessage = inMemoryHandler.processModifyRequest(1,
2038             new ModifyRequestProtocolOp(modifyRequest.getDN(),
2039                  modifyRequest.getModifications()),
2040             requestControlList);
2041    
2042        final ModifyResponseProtocolOp modifyResponse =
2043             responseMessage.getModifyResponseProtocolOp();
2044    
2045        final LDAPResult ldapResult = new LDAPResult(responseMessage.getMessageID(),
2046             ResultCode.valueOf(modifyResponse.getResultCode()),
2047             modifyResponse.getDiagnosticMessage(), modifyResponse.getMatchedDN(),
2048             modifyResponse.getReferralURLs(), responseMessage.getControls());
2049    
2050        switch (modifyResponse.getResultCode())
2051        {
2052          case ResultCode.SUCCESS_INT_VALUE:
2053          case ResultCode.NO_OPERATION_INT_VALUE:
2054            return ldapResult;
2055          default:
2056            throw new LDAPException(ldapResult);
2057        }
2058      }
2059    
2060    
2061    
2062      /**
2063       * {@inheritDoc}
2064       * <BR><BR>
2065       * This method may be used regardless of whether the server is listening for
2066       * client connections, and regardless of whether modify operations are allowed
2067       * in the server.
2068       */
2069      public LDAPResult modify(final ReadOnlyModifyRequest modifyRequest)
2070             throws LDAPException
2071      {
2072        return modify(modifyRequest.duplicate());
2073      }
2074    
2075    
2076    
2077      /**
2078       * {@inheritDoc}
2079       * <BR><BR>
2080       * This method may be used regardless of whether the server is listening for
2081       * client connections, and regardless of whether modify DN operations are
2082       * allowed in the server.
2083       */
2084      public LDAPResult modifyDN(final String dn, final String newRDN,
2085                                 final boolean deleteOldRDN)
2086             throws LDAPException
2087      {
2088        return modifyDN(new ModifyDNRequest(dn, newRDN, deleteOldRDN));
2089      }
2090    
2091    
2092    
2093      /**
2094       * {@inheritDoc}
2095       * <BR><BR>
2096       * This method may be used regardless of whether the server is listening for
2097       * client connections, and regardless of whether modify DN operations are
2098       * allowed in the server.
2099       */
2100      public LDAPResult modifyDN(final String dn, final String newRDN,
2101                                 final boolean deleteOldRDN,
2102                                 final String newSuperiorDN)
2103             throws LDAPException
2104      {
2105        return modifyDN(new ModifyDNRequest(dn, newRDN, deleteOldRDN,
2106             newSuperiorDN));
2107      }
2108    
2109    
2110    
2111      /**
2112       * {@inheritDoc}
2113       * <BR><BR>
2114       * This method may be used regardless of whether the server is listening for
2115       * client connections, and regardless of whether modify DN operations are
2116       * allowed in the server.
2117       */
2118      public LDAPResult modifyDN(final ModifyDNRequest modifyDNRequest)
2119             throws LDAPException
2120      {
2121        final ArrayList<Control> requestControlList =
2122             new ArrayList<Control>(modifyDNRequest.getControlList());
2123        requestControlList.add(new Control(
2124             InMemoryRequestHandler.OID_INTERNAL_OPERATION_REQUEST_CONTROL, false));
2125    
2126        final LDAPMessage responseMessage = inMemoryHandler.processModifyDNRequest(
2127             1, new ModifyDNRequestProtocolOp(modifyDNRequest.getDN(),
2128                  modifyDNRequest.getNewRDN(), modifyDNRequest.deleteOldRDN(),
2129                  modifyDNRequest.getNewSuperiorDN()),
2130             requestControlList);
2131    
2132        final ModifyDNResponseProtocolOp modifyDNResponse =
2133             responseMessage.getModifyDNResponseProtocolOp();
2134    
2135        final LDAPResult ldapResult = new LDAPResult(responseMessage.getMessageID(),
2136             ResultCode.valueOf(modifyDNResponse.getResultCode()),
2137             modifyDNResponse.getDiagnosticMessage(),
2138             modifyDNResponse.getMatchedDN(), modifyDNResponse.getReferralURLs(),
2139             responseMessage.getControls());
2140    
2141        switch (modifyDNResponse.getResultCode())
2142        {
2143          case ResultCode.SUCCESS_INT_VALUE:
2144          case ResultCode.NO_OPERATION_INT_VALUE:
2145            return ldapResult;
2146          default:
2147            throw new LDAPException(ldapResult);
2148        }
2149      }
2150    
2151    
2152    
2153      /**
2154       * {@inheritDoc}
2155       * <BR><BR>
2156       * This method may be used regardless of whether the server is listening for
2157       * client connections, and regardless of whether modify DN operations are
2158       * allowed in the server.
2159       */
2160      public LDAPResult modifyDN(final ReadOnlyModifyDNRequest modifyDNRequest)
2161             throws LDAPException
2162      {
2163        return modifyDN(modifyDNRequest.duplicate());
2164      }
2165    
2166    
2167    
2168      /**
2169       * {@inheritDoc}
2170       * <BR><BR>
2171       * This method may be used regardless of whether the server is listening for
2172       * client connections, and regardless of whether search operations are allowed
2173       * in the server.
2174       */
2175      public SearchResult search(final String baseDN, final SearchScope scope,
2176                                 final String filter, final String... attributes)
2177             throws LDAPSearchException
2178      {
2179        return search(new SearchRequest(baseDN, scope, parseFilter(filter),
2180             attributes));
2181      }
2182    
2183    
2184    
2185      /**
2186       * {@inheritDoc}
2187       * <BR><BR>
2188       * This method may be used regardless of whether the server is listening for
2189       * client connections, and regardless of whether search operations are allowed
2190       * in the server.
2191       */
2192      public SearchResult search(final String baseDN, final SearchScope scope,
2193                                 final Filter filter, final String... attributes)
2194             throws LDAPSearchException
2195      {
2196        return search(new SearchRequest(baseDN, scope, filter, attributes));
2197      }
2198    
2199    
2200    
2201      /**
2202       * {@inheritDoc}
2203       * <BR><BR>
2204       * This method may be used regardless of whether the server is listening for
2205       * client connections, and regardless of whether search operations are allowed
2206       * in the server.
2207       */
2208      public SearchResult search(final SearchResultListener searchResultListener,
2209                                 final String baseDN, final SearchScope scope,
2210                                 final String filter, final String... attributes)
2211             throws LDAPSearchException
2212      {
2213        return search(new SearchRequest(searchResultListener, baseDN, scope,
2214             parseFilter(filter), attributes));
2215      }
2216    
2217    
2218    
2219      /**
2220       * {@inheritDoc}
2221       * <BR><BR>
2222       * This method may be used regardless of whether the server is listening for
2223       * client connections, and regardless of whether search operations are allowed
2224       * in the server.
2225       */
2226      public SearchResult search(final SearchResultListener searchResultListener,
2227                                 final String baseDN, final SearchScope scope,
2228                                 final Filter filter, final String... attributes)
2229             throws LDAPSearchException
2230      {
2231        return search(new SearchRequest(searchResultListener, baseDN, scope,
2232             filter, attributes));
2233      }
2234    
2235    
2236    
2237      /**
2238       * {@inheritDoc}
2239       * <BR><BR>
2240       * This method may be used regardless of whether the server is listening for
2241       * client connections, and regardless of whether search operations are allowed
2242       * in the server.
2243       */
2244      public SearchResult search(final String baseDN, final SearchScope scope,
2245                                 final DereferencePolicy derefPolicy,
2246                                 final int sizeLimit, final int timeLimit,
2247                                 final boolean typesOnly, final String filter,
2248                                 final String... attributes)
2249             throws LDAPSearchException
2250      {
2251        return search(new SearchRequest(baseDN, scope, derefPolicy, sizeLimit,
2252             timeLimit, typesOnly, parseFilter(filter), attributes));
2253      }
2254    
2255    
2256    
2257      /**
2258       * {@inheritDoc}
2259       * <BR><BR>
2260       * This method may be used regardless of whether the server is listening for
2261       * client connections, and regardless of whether search operations are allowed
2262       * in the server.
2263       */
2264      public SearchResult search(final String baseDN, final SearchScope scope,
2265                                 final DereferencePolicy derefPolicy,
2266                                 final int sizeLimit, final int timeLimit,
2267                                 final boolean typesOnly, final Filter filter,
2268                                 final String... attributes)
2269             throws LDAPSearchException
2270      {
2271        return search(new SearchRequest(baseDN, scope, derefPolicy, sizeLimit,
2272             timeLimit, typesOnly, filter, attributes));
2273      }
2274    
2275    
2276    
2277      /**
2278       * {@inheritDoc}
2279       * <BR><BR>
2280       * This method may be used regardless of whether the server is listening for
2281       * client connections, and regardless of whether search operations are allowed
2282       * in the server.
2283       */
2284      public SearchResult search(final SearchResultListener searchResultListener,
2285                                 final String baseDN, final SearchScope scope,
2286                                 final DereferencePolicy derefPolicy,
2287                                 final int sizeLimit, final int timeLimit,
2288                                 final boolean typesOnly, final String filter,
2289                                 final String... attributes)
2290             throws LDAPSearchException
2291      {
2292        return search(new SearchRequest(searchResultListener, baseDN, scope,
2293             derefPolicy, sizeLimit, timeLimit, typesOnly, parseFilter(filter),
2294             attributes));
2295      }
2296    
2297    
2298    
2299      /**
2300       * {@inheritDoc}
2301       * <BR><BR>
2302       * This method may be used regardless of whether the server is listening for
2303       * client connections, and regardless of whether search operations are allowed
2304       * in the server.
2305       */
2306      public SearchResult search(final SearchResultListener searchResultListener,
2307                                 final String baseDN, final SearchScope scope,
2308                                 final DereferencePolicy derefPolicy,
2309                                 final int sizeLimit, final int timeLimit,
2310                                 final boolean typesOnly, final Filter filter,
2311                                 final String... attributes)
2312             throws LDAPSearchException
2313      {
2314        return search(new SearchRequest(searchResultListener, baseDN, scope,
2315             derefPolicy, sizeLimit, timeLimit, typesOnly, filter, attributes));
2316      }
2317    
2318    
2319    
2320      /**
2321       * {@inheritDoc}
2322       * <BR><BR>
2323       * This method may be used regardless of whether the server is listening for
2324       * client connections, and regardless of whether search operations are allowed
2325       * in the server.
2326       */
2327      public SearchResult search(final SearchRequest searchRequest)
2328             throws LDAPSearchException
2329      {
2330        final ArrayList<Control> requestControlList =
2331             new ArrayList<Control>(searchRequest.getControlList());
2332        requestControlList.add(new Control(
2333             InMemoryRequestHandler.OID_INTERNAL_OPERATION_REQUEST_CONTROL, false));
2334    
2335        final List<SearchResultEntry> entryList =
2336             new ArrayList<SearchResultEntry>(10);
2337        final List<SearchResultReference> referenceList =
2338             new ArrayList<SearchResultReference>(10);
2339    
2340        final LDAPMessage responseMessage = inMemoryHandler.processSearchRequest(1,
2341             new SearchRequestProtocolOp(searchRequest.getBaseDN(),
2342                  searchRequest.getScope(), searchRequest.getDereferencePolicy(),
2343                  searchRequest.getSizeLimit(), searchRequest.getTimeLimitSeconds(),
2344                  searchRequest.typesOnly(), searchRequest.getFilter(),
2345                  searchRequest.getAttributeList()),
2346             requestControlList, entryList, referenceList);
2347    
2348    
2349        final List<SearchResultEntry> returnEntryList;
2350        final List<SearchResultReference> returnReferenceList;
2351        final SearchResultListener searchListener =
2352             searchRequest.getSearchResultListener();
2353        if (searchListener == null)
2354        {
2355          returnEntryList = Collections.unmodifiableList(entryList);
2356          returnReferenceList = Collections.unmodifiableList(referenceList);
2357        }
2358        else
2359        {
2360          returnEntryList     = null;
2361          returnReferenceList = null;
2362    
2363          for (final SearchResultEntry e : entryList)
2364          {
2365            searchListener.searchEntryReturned(e);
2366          }
2367    
2368          for (final SearchResultReference r : referenceList)
2369          {
2370            searchListener.searchReferenceReturned(r);
2371          }
2372        }
2373    
2374    
2375        final SearchResultDoneProtocolOp searchDone =
2376             responseMessage.getSearchResultDoneProtocolOp();
2377    
2378        final ResultCode rc = ResultCode.valueOf(searchDone.getResultCode());
2379    
2380        final String[] referralURLs;
2381        final List<String> referralURLList = searchDone.getReferralURLs();
2382        if ((referralURLList == null) || referralURLList.isEmpty())
2383        {
2384          referralURLs = StaticUtils.NO_STRINGS;
2385        }
2386        else
2387        {
2388          referralURLs = new String[referralURLList.size()];
2389          referralURLList.toArray(referralURLs);
2390        }
2391    
2392        final Control[] responseControls;
2393        final List<Control> controlList = responseMessage.getControls();
2394        if ((controlList == null) || controlList.isEmpty())
2395        {
2396          responseControls = StaticUtils.NO_CONTROLS;
2397        }
2398        else
2399        {
2400          responseControls = new Control[controlList.size()];
2401          controlList.toArray(responseControls);
2402        }
2403    
2404        final SearchResult searchResult =new SearchResult(
2405             responseMessage.getMessageID(), rc, searchDone.getDiagnosticMessage(),
2406             searchDone.getMatchedDN(), referralURLs, returnEntryList,
2407             returnReferenceList, entryList.size(), referenceList.size(),
2408             responseControls);
2409    
2410        if (rc == ResultCode.SUCCESS)
2411        {
2412          return searchResult;
2413        }
2414        else
2415        {
2416          throw new LDAPSearchException(searchResult);
2417        }
2418      }
2419    
2420    
2421    
2422      /**
2423       * {@inheritDoc}
2424       * <BR><BR>
2425       * This method may be used regardless of whether the server is listening for
2426       * client connections, and regardless of whether search operations are allowed
2427       * in the server.
2428       */
2429      public SearchResult search(final ReadOnlySearchRequest searchRequest)
2430             throws LDAPSearchException
2431      {
2432        return search(searchRequest.duplicate());
2433      }
2434    
2435    
2436    
2437      /**
2438       * {@inheritDoc}
2439       * <BR><BR>
2440       * This method may be used regardless of whether the server is listening for
2441       * client connections, and regardless of whether search operations are allowed
2442       * in the server.
2443       */
2444      public SearchResultEntry searchForEntry(final String baseDN,
2445                                              final SearchScope scope,
2446                                              final String filter,
2447                                              final String... attributes)
2448             throws LDAPSearchException
2449      {
2450        return searchForEntry(new SearchRequest(baseDN, scope, parseFilter(filter),
2451             attributes));
2452      }
2453    
2454    
2455    
2456      /**
2457       * {@inheritDoc}
2458       * <BR><BR>
2459       * This method may be used regardless of whether the server is listening for
2460       * client connections, and regardless of whether search operations are allowed
2461       * in the server.
2462       */
2463      public SearchResultEntry searchForEntry(final String baseDN,
2464                                              final SearchScope scope,
2465                                              final Filter filter,
2466                                              final String... attributes)
2467             throws LDAPSearchException
2468      {
2469        return searchForEntry(new SearchRequest(baseDN, scope, filter, attributes));
2470      }
2471    
2472    
2473    
2474      /**
2475       * {@inheritDoc}
2476       * <BR><BR>
2477       * This method may be used regardless of whether the server is listening for
2478       * client connections, and regardless of whether search operations are allowed
2479       * in the server.
2480       */
2481      public SearchResultEntry searchForEntry(final String baseDN,
2482                                              final SearchScope scope,
2483                                              final DereferencePolicy derefPolicy,
2484                                              final int timeLimit,
2485                                              final boolean typesOnly,
2486                                              final String filter,
2487                                              final String... attributes)
2488             throws LDAPSearchException
2489      {
2490        return searchForEntry(new SearchRequest(baseDN, scope, derefPolicy, 1,
2491             timeLimit, typesOnly, parseFilter(filter), attributes));
2492      }
2493    
2494    
2495    
2496      /**
2497       * {@inheritDoc}
2498       * <BR><BR>
2499       * This method may be used regardless of whether the server is listening for
2500       * client connections, and regardless of whether search operations are allowed
2501       * in the server.
2502       */
2503      public SearchResultEntry searchForEntry(final String baseDN,
2504                                              final SearchScope scope,
2505                                              final DereferencePolicy derefPolicy,
2506                                              final int timeLimit,
2507                                              final boolean typesOnly,
2508                                              final Filter filter,
2509                                              final String... attributes)
2510             throws LDAPSearchException
2511      {
2512        return searchForEntry(new SearchRequest(baseDN, scope, derefPolicy, 1,
2513             timeLimit, typesOnly, filter, attributes));
2514      }
2515    
2516    
2517    
2518      /**
2519       * {@inheritDoc}
2520       * <BR><BR>
2521       * This method may be used regardless of whether the server is listening for
2522       * client connections, and regardless of whether search operations are allowed
2523       * in the server.
2524       */
2525      public SearchResultEntry searchForEntry(final SearchRequest searchRequest)
2526             throws LDAPSearchException
2527      {
2528        final ArrayList<Control> requestControlList =
2529             new ArrayList<Control>(searchRequest.getControlList());
2530        requestControlList.add(new Control(
2531             InMemoryRequestHandler.OID_INTERNAL_OPERATION_REQUEST_CONTROL, false));
2532    
2533        final SearchRequest r;
2534        if ((searchRequest.getSizeLimit() == 1) &&
2535            (searchRequest.getSearchResultListener() == null))
2536        {
2537          r = searchRequest;
2538        }
2539        else
2540        {
2541          r = new SearchRequest(searchRequest.getBaseDN(), searchRequest.getScope(),
2542               searchRequest.getDereferencePolicy(), 1,
2543               searchRequest.getTimeLimitSeconds(), searchRequest.typesOnly(),
2544               searchRequest.getFilter(), searchRequest.getAttributes());
2545    
2546          r.setFollowReferrals(InternalSDKHelper.followReferralsInternal(r));
2547          r.setResponseTimeoutMillis(searchRequest.getResponseTimeoutMillis(null));
2548          r.setControls(requestControlList);
2549        }
2550    
2551        final SearchResult result;
2552        try
2553        {
2554          result = search(r);
2555        }
2556        catch (final LDAPSearchException lse)
2557        {
2558          Debug.debugException(lse);
2559    
2560          if (lse.getResultCode() == ResultCode.NO_SUCH_OBJECT)
2561          {
2562            return null;
2563          }
2564    
2565          throw lse;
2566        }
2567    
2568        if (result.getEntryCount() == 0)
2569        {
2570          return null;
2571        }
2572        else
2573        {
2574          return result.getSearchEntries().get(0);
2575        }
2576      }
2577    
2578    
2579    
2580      /**
2581       * {@inheritDoc}
2582       * <BR><BR>
2583       * This method may be used regardless of whether the server is listening for
2584       * client connections, and regardless of whether search operations are allowed
2585       * in the server.
2586       */
2587      public SearchResultEntry searchForEntry(
2588                                    final ReadOnlySearchRequest searchRequest)
2589             throws LDAPSearchException
2590      {
2591        return searchForEntry(searchRequest.duplicate());
2592      }
2593    
2594    
2595    
2596      /**
2597       * Parses the provided string as a search filter.
2598       *
2599       * @param  s  The string to be parsed.
2600       *
2601       * @return  The parsed filter.
2602       *
2603       * @throws  LDAPSearchException  If the provided string could not be parsed as
2604       *                               a valid search filter.
2605       */
2606      private static Filter parseFilter(final String s)
2607              throws LDAPSearchException
2608      {
2609        try
2610        {
2611          return Filter.create(s);
2612        }
2613        catch (final LDAPException le)
2614        {
2615          throw new LDAPSearchException(le);
2616        }
2617      }
2618    
2619    
2620    
2621      /**
2622       * Indicates whether the specified entry exists in the server.
2623       * <BR><BR>
2624       * This method may be used regardless of whether the server is listening for
2625       * client connections.
2626       *
2627       * @param  dn  The DN of the entry for which to make the determination.
2628       *
2629       * @return  {@code true} if the entry exists, or {@code false} if not.
2630       *
2631       * @throws  LDAPException  If a problem is encountered while trying to
2632       *                         communicate with the directory server.
2633       */
2634      public boolean entryExists(final String dn)
2635             throws LDAPException
2636      {
2637        return inMemoryHandler.entryExists(dn);
2638      }
2639    
2640    
2641    
2642      /**
2643       * Indicates whether the specified entry exists in the server and matches the
2644       * given filter.
2645       * <BR><BR>
2646       * This method may be used regardless of whether the server is listening for
2647       * client connections.
2648       *
2649       * @param  dn      The DN of the entry for which to make the determination.
2650       * @param  filter  The filter the entry is expected to match.
2651       *
2652       * @return  {@code true} if the entry exists and matches the specified filter,
2653       *          or {@code false} if not.
2654       *
2655       * @throws  LDAPException  If a problem is encountered while trying to
2656       *                         communicate with the directory server.
2657       */
2658      public boolean entryExists(final String dn, final String filter)
2659             throws LDAPException
2660      {
2661        return inMemoryHandler.entryExists(dn, filter);
2662      }
2663    
2664    
2665    
2666      /**
2667       * Indicates whether the specified entry exists in the server.  This will
2668       * return {@code true} only if the target entry exists and contains all values
2669       * for all attributes of the provided entry.  The entry will be allowed to
2670       * have attribute values not included in the provided entry.
2671       * <BR><BR>
2672       * This method may be used regardless of whether the server is listening for
2673       * client connections.
2674       *
2675       * @param  entry  The entry to compare against the directory server.
2676       *
2677       * @return  {@code true} if the entry exists in the server and is a superset
2678       *          of the provided entry, or {@code false} if not.
2679       *
2680       * @throws  LDAPException  If a problem is encountered while trying to
2681       *                         communicate with the directory server.
2682       */
2683      public boolean entryExists(final Entry entry)
2684             throws LDAPException
2685      {
2686        return inMemoryHandler.entryExists(entry);
2687      }
2688    
2689    
2690    
2691      /**
2692       * Ensures that an entry with the provided DN exists in the directory.
2693       * <BR><BR>
2694       * This method may be used regardless of whether the server is listening for
2695       * client connections.
2696       *
2697       * @param  dn  The DN of the entry for which to make the determination.
2698       *
2699       * @throws  LDAPException  If a problem is encountered while trying to
2700       *                         communicate with the directory server.
2701       *
2702       * @throws  AssertionError  If the target entry does not exist.
2703       */
2704      public void assertEntryExists(final String dn)
2705             throws LDAPException, AssertionError
2706      {
2707        inMemoryHandler.assertEntryExists(dn);
2708      }
2709    
2710    
2711    
2712      /**
2713       * Ensures that an entry with the provided DN exists in the directory.
2714       * <BR><BR>
2715       * This method may be used regardless of whether the server is listening for
2716       * client connections.
2717       *
2718       * @param  dn      The DN of the entry for which to make the determination.
2719       * @param  filter  A filter that the target entry must match.
2720       *
2721       * @throws  LDAPException  If a problem is encountered while trying to
2722       *                         communicate with the directory server.
2723       *
2724       * @throws  AssertionError  If the target entry does not exist or does not
2725       *                          match the provided filter.
2726       */
2727      public void assertEntryExists(final String dn, final String filter)
2728             throws LDAPException, AssertionError
2729      {
2730        inMemoryHandler.assertEntryExists(dn, filter);
2731      }
2732    
2733    
2734    
2735      /**
2736       * Ensures that an entry exists in the directory with the same DN and all
2737       * attribute values contained in the provided entry.  The server entry may
2738       * contain additional attributes and/or attribute values not included in the
2739       * provided entry.
2740       * <BR><BR>
2741       * This method may be used regardless of whether the server is listening for
2742       * client connections.
2743       *
2744       * @param  entry  The entry expected to be present in the directory server.
2745       *
2746       * @throws  LDAPException  If a problem is encountered while trying to
2747       *                         communicate with the directory server.
2748       *
2749       * @throws  AssertionError  If the target entry does not exist or does not
2750       *                          match the provided filter.
2751       */
2752      public void assertEntryExists(final Entry entry)
2753             throws LDAPException, AssertionError
2754      {
2755        inMemoryHandler.assertEntryExists(entry);
2756      }
2757    
2758    
2759    
2760      /**
2761       * Retrieves a list containing the DNs of the entries which are missing from
2762       * the directory server.
2763       * <BR><BR>
2764       * This method may be used regardless of whether the server is listening for
2765       * client connections.
2766       *
2767       * @param  dns  The DNs of the entries to try to find in the server.
2768       *
2769       * @return  A list containing all of the provided DNs that were not found in
2770       *          the server, or an empty list if all entries were found.
2771       *
2772       * @throws  LDAPException  If a problem is encountered while trying to
2773       *                         communicate with the directory server.
2774       */
2775      public List<String> getMissingEntryDNs(final String... dns)
2776             throws LDAPException
2777      {
2778        return inMemoryHandler.getMissingEntryDNs(StaticUtils.toList(dns));
2779      }
2780    
2781    
2782    
2783      /**
2784       * Retrieves a list containing the DNs of the entries which are missing from
2785       * the directory server.
2786       * <BR><BR>
2787       * This method may be used regardless of whether the server is listening for
2788       * client connections.
2789       *
2790       * @param  dns  The DNs of the entries to try to find in the server.
2791       *
2792       * @return  A list containing all of the provided DNs that were not found in
2793       *          the server, or an empty list if all entries were found.
2794       *
2795       * @throws  LDAPException  If a problem is encountered while trying to
2796       *                         communicate with the directory server.
2797       */
2798      public List<String> getMissingEntryDNs(final Collection<String> dns)
2799             throws LDAPException
2800      {
2801        return inMemoryHandler.getMissingEntryDNs(dns);
2802      }
2803    
2804    
2805    
2806      /**
2807       * Ensures that all of the entries with the provided DNs exist in the
2808       * directory.
2809       * <BR><BR>
2810       * This method may be used regardless of whether the server is listening for
2811       * client connections.
2812       *
2813       * @param  dns  The DNs of the entries for which to make the determination.
2814       *
2815       * @throws  LDAPException  If a problem is encountered while trying to
2816       *                         communicate with the directory server.
2817       *
2818       * @throws  AssertionError  If any of the target entries does not exist.
2819       */
2820      public void assertEntriesExist(final String... dns)
2821             throws LDAPException, AssertionError
2822      {
2823        inMemoryHandler.assertEntriesExist(StaticUtils.toList(dns));
2824      }
2825    
2826    
2827    
2828      /**
2829       * Ensures that all of the entries with the provided DNs exist in the
2830       * directory.
2831       * <BR><BR>
2832       * This method may be used regardless of whether the server is listening for
2833       * client connections.
2834       *
2835       * @param  dns  The DNs of the entries for which to make the determination.
2836       *
2837       * @throws  LDAPException  If a problem is encountered while trying to
2838       *                         communicate with the directory server.
2839       *
2840       * @throws  AssertionError  If any of the target entries does not exist.
2841       */
2842      public void assertEntriesExist(final Collection<String> dns)
2843             throws LDAPException, AssertionError
2844      {
2845        inMemoryHandler.assertEntriesExist(dns);
2846      }
2847    
2848    
2849    
2850      /**
2851       * Retrieves a list containing all of the named attributes which do not exist
2852       * in the target entry.
2853       * <BR><BR>
2854       * This method may be used regardless of whether the server is listening for
2855       * client connections.
2856       *
2857       * @param  dn              The DN of the entry to examine.
2858       * @param  attributeNames  The names of the attributes expected to be present
2859       *                         in the target entry.
2860       *
2861       * @return  A list containing the names of the attributes which were not
2862       *          present in the target entry, an empty list if all specified
2863       *          attributes were found in the entry, or {@code null} if the target
2864       *          entry does not exist.
2865       *
2866       * @throws  LDAPException  If a problem is encountered while trying to
2867       *                         communicate with the directory server.
2868       */
2869      public List<String> getMissingAttributeNames(final String dn,
2870                                                   final String... attributeNames)
2871             throws LDAPException
2872      {
2873        return inMemoryHandler.getMissingAttributeNames(dn,
2874             StaticUtils.toList(attributeNames));
2875      }
2876    
2877    
2878    
2879      /**
2880       * Retrieves a list containing all of the named attributes which do not exist
2881       * in the target entry.
2882       * <BR><BR>
2883       * This method may be used regardless of whether the server is listening for
2884       * client connections.
2885       *
2886       * @param  dn              The DN of the entry to examine.
2887       * @param  attributeNames  The names of the attributes expected to be present
2888       *                         in the target entry.
2889       *
2890       * @return  A list containing the names of the attributes which were not
2891       *          present in the target entry, an empty list if all specified
2892       *          attributes were found in the entry, or {@code null} if the target
2893       *          entry does not exist.
2894       *
2895       * @throws  LDAPException  If a problem is encountered while trying to
2896       *                         communicate with the directory server.
2897       */
2898      public List<String> getMissingAttributeNames(final String dn,
2899                               final Collection<String> attributeNames)
2900             throws LDAPException
2901      {
2902        return inMemoryHandler.getMissingAttributeNames(dn, attributeNames);
2903      }
2904    
2905    
2906    
2907      /**
2908       * Ensures that the specified entry exists in the directory with all of the
2909       * specified attributes.
2910       * <BR><BR>
2911       * This method may be used regardless of whether the server is listening for
2912       * client connections.
2913       *
2914       * @param  dn              The DN of the entry to examine.
2915       * @param  attributeNames  The names of the attributes that are expected to be
2916       *                         present in the provided entry.
2917       *
2918       * @throws  LDAPException  If a problem is encountered while trying to
2919       *                         communicate with the directory server.
2920       *
2921       * @throws  AssertionError  If the target entry does not exist or does not
2922       *                          contain all of the specified attributes.
2923       */
2924      public void assertAttributeExists(final String dn,
2925                                        final String... attributeNames)
2926            throws LDAPException, AssertionError
2927      {
2928        inMemoryHandler.assertAttributeExists(dn,
2929             StaticUtils.toList(attributeNames));
2930      }
2931    
2932    
2933    
2934      /**
2935       * Ensures that the specified entry exists in the directory with all of the
2936       * specified attributes.
2937       * <BR><BR>
2938       * This method may be used regardless of whether the server is listening for
2939       * client connections.
2940       *
2941       * @param  dn              The DN of the entry to examine.
2942       * @param  attributeNames  The names of the attributes that are expected to be
2943       *                         present in the provided entry.
2944       *
2945       * @throws  LDAPException  If a problem is encountered while trying to
2946       *                         communicate with the directory server.
2947       *
2948       * @throws  AssertionError  If the target entry does not exist or does not
2949       *                          contain all of the specified attributes.
2950       */
2951      public void assertAttributeExists(final String dn,
2952                                        final Collection<String> attributeNames)
2953            throws LDAPException, AssertionError
2954      {
2955        inMemoryHandler.assertAttributeExists(dn, attributeNames);
2956      }
2957    
2958    
2959    
2960      /**
2961       * Retrieves a list of all provided attribute values which are missing from
2962       * the specified entry.
2963       * <BR><BR>
2964       * This method may be used regardless of whether the server is listening for
2965       * client connections.
2966       *
2967       * @param  dn               The DN of the entry to examine.
2968       * @param  attributeName    The attribute expected to be present in the target
2969       *                          entry with the given values.
2970       * @param  attributeValues  The values expected to be present in the target
2971       *                          entry.
2972       *
2973       * @return  A list containing all of the provided values which were not found
2974       *          in the entry, an empty list if all provided attribute values were
2975       *          found, or {@code null} if the target entry does not exist.
2976       *
2977       * @throws  LDAPException  If a problem is encountered while trying to
2978       *                         communicate with the directory server.
2979       */
2980      public List<String> getMissingAttributeValues(final String dn,
2981                                                    final String attributeName,
2982                                                    final String... attributeValues)
2983             throws LDAPException
2984      {
2985        return inMemoryHandler.getMissingAttributeValues(dn, attributeName,
2986             StaticUtils.toList(attributeValues));
2987      }
2988    
2989    
2990    
2991      /**
2992       * Retrieves a list of all provided attribute values which are missing from
2993       * the specified entry.  The target attribute may or may not contain
2994       * additional values.
2995       * <BR><BR>
2996       * This method may be used regardless of whether the server is listening for
2997       * client connections.
2998       *
2999       * @param  dn               The DN of the entry to examine.
3000       * @param  attributeName    The attribute expected to be present in the target
3001       *                          entry with the given values.
3002       * @param  attributeValues  The values expected to be present in the target
3003       *                          entry.
3004       *
3005       * @return  A list containing all of the provided values which were not found
3006       *          in the entry, an empty list if all provided attribute values were
3007       *          found, or {@code null} if the target entry does not exist.
3008       *
3009       * @throws  LDAPException  If a problem is encountered while trying to
3010       *                         communicate with the directory server.
3011       */
3012      public List<String> getMissingAttributeValues(final String dn,
3013                               final String attributeName,
3014                               final Collection<String> attributeValues)
3015           throws LDAPException
3016      {
3017        return inMemoryHandler.getMissingAttributeValues(dn, attributeName,
3018             attributeValues);
3019      }
3020    
3021    
3022    
3023      /**
3024       * Ensures that the specified entry exists in the directory with all of the
3025       * specified values for the given attribute.  The attribute may or may not
3026       * contain additional values.
3027       * <BR><BR>
3028       * This method may be used regardless of whether the server is listening for
3029       * client connections.
3030       *
3031       * @param  dn               The DN of the entry to examine.
3032       * @param  attributeName    The name of the attribute to examine.
3033       * @param  attributeValues  The set of values which must exist for the given
3034       *                          attribute.
3035       *
3036       * @throws  LDAPException  If a problem is encountered while trying to
3037       *                         communicate with the directory server.
3038       *
3039       * @throws  AssertionError  If the target entry does not exist, does not
3040       *                          contain the specified attribute, or that attribute
3041       *                          does not have all of the specified values.
3042       */
3043      public void assertValueExists(final String dn, final String attributeName,
3044                                    final String... attributeValues)
3045            throws LDAPException, AssertionError
3046      {
3047        inMemoryHandler.assertValueExists(dn, attributeName,
3048             StaticUtils.toList(attributeValues));
3049      }
3050    
3051    
3052    
3053      /**
3054       * Ensures that the specified entry exists in the directory with all of the
3055       * specified values for the given attribute.  The attribute may or may not
3056       * contain additional values.
3057       * <BR><BR>
3058       * This method may be used regardless of whether the server is listening for
3059       * client connections.
3060       *
3061       * @param  dn               The DN of the entry to examine.
3062       * @param  attributeName    The name of the attribute to examine.
3063       * @param  attributeValues  The set of values which must exist for the given
3064       *                          attribute.
3065       *
3066       * @throws  LDAPException  If a problem is encountered while trying to
3067       *                         communicate with the directory server.
3068       *
3069       * @throws  AssertionError  If the target entry does not exist, does not
3070       *                          contain the specified attribute, or that attribute
3071       *                          does not have all of the specified values.
3072       */
3073      public void assertValueExists(final String dn, final String attributeName,
3074                                    final Collection<String> attributeValues)
3075            throws LDAPException, AssertionError
3076      {
3077        inMemoryHandler.assertValueExists(dn, attributeName, attributeValues);
3078      }
3079    
3080    
3081    
3082      /**
3083       * Ensures that the specified entry does not exist in the directory.
3084       * <BR><BR>
3085       * This method may be used regardless of whether the server is listening for
3086       * client connections.
3087       *
3088       * @param  dn  The DN of the entry expected to be missing.
3089       *
3090       * @throws  LDAPException  If a problem is encountered while trying to
3091       *                         communicate with the directory server.
3092       *
3093       * @throws  AssertionError  If the target entry is found in the server.
3094       */
3095      public void assertEntryMissing(final String dn)
3096             throws LDAPException, AssertionError
3097      {
3098        inMemoryHandler.assertEntryMissing(dn);
3099      }
3100    
3101    
3102    
3103      /**
3104       * Ensures that the specified entry exists in the directory but does not
3105       * contain any of the specified attributes.
3106       * <BR><BR>
3107       * This method may be used regardless of whether the server is listening for
3108       * client connections.
3109       *
3110       * @param  dn              The DN of the entry expected to be present.
3111       * @param  attributeNames  The names of the attributes expected to be missing
3112       *                         from the entry.
3113       *
3114       * @throws  LDAPException  If a problem is encountered while trying to
3115       *                         communicate with the directory server.
3116       *
3117       * @throws  AssertionError  If the target entry is missing from the server, or
3118       *                          if it contains any of the target attributes.
3119       */
3120      public void assertAttributeMissing(final String dn,
3121                                         final String... attributeNames)
3122             throws LDAPException, AssertionError
3123      {
3124        inMemoryHandler.assertAttributeMissing(dn,
3125             StaticUtils.toList(attributeNames));
3126      }
3127    
3128    
3129    
3130      /**
3131       * Ensures that the specified entry exists in the directory but does not
3132       * contain any of the specified attributes.
3133       * <BR><BR>
3134       * This method may be used regardless of whether the server is listening for
3135       * client connections.
3136       *
3137       * @param  dn              The DN of the entry expected to be present.
3138       * @param  attributeNames  The names of the attributes expected to be missing
3139       *                         from the entry.
3140       *
3141       * @throws  LDAPException  If a problem is encountered while trying to
3142       *                         communicate with the directory server.
3143       *
3144       * @throws  AssertionError  If the target entry is missing from the server, or
3145       *                          if it contains any of the target attributes.
3146       */
3147      public void assertAttributeMissing(final String dn,
3148                                         final Collection<String> attributeNames)
3149             throws LDAPException, AssertionError
3150      {
3151        inMemoryHandler.assertAttributeMissing(dn, attributeNames);
3152      }
3153    
3154    
3155    
3156      /**
3157       * Ensures that the specified entry exists in the directory but does not
3158       * contain any of the specified attribute values.
3159       * <BR><BR>
3160       * This method may be used regardless of whether the server is listening for
3161       * client connections.
3162       *
3163       * @param  dn               The DN of the entry expected to be present.
3164       * @param  attributeName    The name of the attribute to examine.
3165       * @param  attributeValues  The values expected to be missing from the target
3166       *                          entry.
3167       *
3168       * @throws  LDAPException  If a problem is encountered while trying to
3169       *                         communicate with the directory server.
3170       *
3171       * @throws  AssertionError  If the target entry is missing from the server, or
3172       *                          if it contains any of the target attribute values.
3173       */
3174      public void assertValueMissing(final String dn, final String attributeName,
3175                                     final String... attributeValues)
3176             throws LDAPException, AssertionError
3177      {
3178        inMemoryHandler.assertValueMissing(dn, attributeName,
3179             StaticUtils.toList(attributeValues));
3180      }
3181    
3182    
3183    
3184      /**
3185       * Ensures that the specified entry exists in the directory but does not
3186       * contain any of the specified attribute values.
3187       * <BR><BR>
3188       * This method may be used regardless of whether the server is listening for
3189       * client connections.
3190       *
3191       * @param  dn               The DN of the entry expected to be present.
3192       * @param  attributeName    The name of the attribute to examine.
3193       * @param  attributeValues  The values expected to be missing from the target
3194       *                          entry.
3195       *
3196       * @throws  LDAPException  If a problem is encountered while trying to
3197       *                         communicate with the directory server.
3198       *
3199       * @throws  AssertionError  If the target entry is missing from the server, or
3200       *                          if it contains any of the target attribute values.
3201       */
3202      public void assertValueMissing(final String dn, final String attributeName,
3203                                     final Collection<String> attributeValues)
3204             throws LDAPException, AssertionError
3205      {
3206        inMemoryHandler.assertValueMissing(dn, attributeName, attributeValues);
3207      }
3208    }