001/*
002 * Copyright 2011-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2011-2017 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.listener;
022
023
024
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.EnumSet;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.LinkedHashMap;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035import java.util.logging.Handler;
036
037import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
038import com.unboundid.ldap.sdk.DN;
039import com.unboundid.ldap.sdk.Entry;
040import com.unboundid.ldap.sdk.LDAPException;
041import com.unboundid.ldap.sdk.OperationType;
042import com.unboundid.ldap.sdk.ReadOnlyEntry;
043import com.unboundid.ldap.sdk.ResultCode;
044import com.unboundid.ldap.sdk.Version;
045import com.unboundid.ldap.sdk.schema.Schema;
046import com.unboundid.util.Mutable;
047import com.unboundid.util.NotExtensible;
048import com.unboundid.util.StaticUtils;
049import com.unboundid.util.ThreadSafety;
050import com.unboundid.util.ThreadSafetyLevel;
051
052import static com.unboundid.ldap.listener.ListenerMessages.*;
053
054
055
056/**
057 * This class provides a simple data structure with information that may be
058 * used to control the behavior of an {@link InMemoryDirectoryServer} instance.
059 * At least one base DN must be specified.  For all other properties, the
060 * following default values will be used unless an alternate configuration is
061 * provided:
062 * <UL>
063 *   <LI>Listeners:  The server will provide a single listener that will use an
064 *       automatically-selected port on all interfaces, which will not use SSL
065 *       or StartTLS.</LI>
066 *   <LI>Allowed Operation Types:  All types of operations will be allowed.</LI>
067 *   <LI>Authentication Required Operation Types:  Authentication will not be
068 *       required for any types of operations.</LI>
069 *   <LI>Schema:  The server will use a schema with a number of standard
070 *       attribute types and object classes.</LI>
071 *   <LI>Additional Bind Credentials:  The server will not have any additional
072 *       bind credentials.</LI>
073 *   <LI>Referential Integrity Attributes:  Referential integrity will not be
074 *       maintained.</LI>
075 *   <LI>Generate Operational Attributes:  The server will automatically
076 *       generate a number of operational attributes.</LI>
077 *   <LI>Extended Operation Handlers:  The server will support the password
078 *       modify extended operation as defined in RFC 3062, the start and end
079 *       transaction extended operations as defined in RFC 5805, and the
080 *       "Who Am I?" extended operation as defined in RFC 4532.</LI>
081 *   <LI>SASL Bind Handlers:  The server will support the SASL PLAIN mechanism
082 *       as defined in RFC 4616.</LI>
083 *   <LI>Max ChangeLog Entries:  The server will not provide an LDAP
084 *       changelog.</LI>
085 *   <LI>Access Log Handler:  The server will not perform any access
086 *       logging.</LI>
087 *   <LI>Code Log Handler:  The server will not perform any code logging.</LI>
088 *   <LI>LDAP Debug Log Handler:  The server will not perform any LDAP debug
089 *       logging.</LI>
090 *   <LI>Listener Exception Handler:  The server will not use a listener
091 *       exception handler.</LI>
092 *   <LI>Maximum Size Limit:  The server will not enforce a maximum search size
093 *       limit.</LI>
094 * </UL>
095 */
096@NotExtensible()
097@Mutable()
098@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
099public class InMemoryDirectoryServerConfig
100{
101  // Indicates whether to enforce the requirement that attribute values comply
102  // with the associated attribute syntax.
103  private boolean enforceAttributeSyntaxCompliance;
104
105  // Indicates whether to enforce the requirement that entries contain exactly
106  // one structural object class.
107  private boolean enforceSingleStructuralObjectClass;
108
109  // Indicates whether to automatically generate operational attributes.
110  private boolean generateOperationalAttributes;
111
112  // Indicates whether the code log should include sample code for processing
113  // the requests.
114  private boolean includeRequestProcessingInCodeLog;
115
116  // The base DNs to use for the LDAP listener.
117  private DN[] baseDNs;
118
119  // The log handler that should be used to record access log messages about
120  // operations processed by the server.
121  private Handler accessLogHandler;
122
123  // The log handler that should be used to record detailed protocol-level
124  // messages about LDAP operations processed by the server.
125  private Handler ldapDebugLogHandler;
126
127  // The maximum number of entries to retain in a generated changelog.
128  private int maxChangeLogEntries;
129
130  // The maximum number of concurrent connections that will be allowed.
131  private int maxConnections;
132
133  // The maximum number of entries that may be returned in any single search
134  // operation.
135  private int maxSizeLimit;
136
137  // The exception handler that should be used for the listener.
138  private LDAPListenerExceptionHandler exceptionHandler;
139
140  // The extended operation handlers that may be used to process extended
141  // operations in the server.
142  private final List<InMemoryExtendedOperationHandler>
143       extendedOperationHandlers;
144
145  // The listener configurations that should be used for accepting connections
146  // to the server.
147  private final List<InMemoryListenerConfig> listenerConfigs;
148
149  // The operation interceptors that should be used with the in-memory directory
150  // server.
151  private final List<InMemoryOperationInterceptor> operationInterceptors;
152
153  // The SASL bind handlers that may be used to process SASL bind requests in
154  // the server.
155  private final List<InMemorySASLBindHandler> saslBindHandlers;
156
157  // The names or OIDs of the attributes for which to maintain equality indexes.
158  private final List<String> equalityIndexAttributes;
159
160  // A set of additional credentials that can be used for binding without
161  // requiring a corresponding entry in the data set.
162  private final Map<DN,byte[]> additionalBindCredentials;
163
164  // The entry to use for the server root DSE.
165  private ReadOnlyEntry rootDSEEntry;
166
167  // The schema to use for the server.
168  private Schema schema;
169
170  // The set of operation types that will be supported by the server.
171  private final Set<OperationType> allowedOperationTypes;
172
173  // The set of operation types for which authentication will be required.
174  private final Set<OperationType> authenticationRequiredOperationTypes;
175
176  // The set of attributes for which referential integrity should be maintained.
177  private final Set<String> referentialIntegrityAttributes;
178
179  // The path to a file that should be written with code that may be used to
180  // issue the requests received by the server.
181  private String codeLogPath;
182
183  // The vendor name to report in the server root DSE.
184  private String vendorName;
185
186  // The vendor version to report in the server root DSE.
187  private String vendorVersion;
188
189
190
191  /**
192   * Creates a new in-memory directory server config object with the provided
193   * set of base DNs.
194   *
195   * @param  baseDNs  The set of base DNs to use for the server.  It must not
196   *                  be {@code null} or empty.
197   *
198   * @throws  LDAPException  If the provided set of base DN strings is null or
199   *                         empty, or if any of the provided base DN strings
200   *                         cannot be parsed as a valid DN.
201   */
202  public InMemoryDirectoryServerConfig(final String... baseDNs)
203         throws LDAPException
204  {
205    this(parseDNs(Schema.getDefaultStandardSchema(), baseDNs));
206  }
207
208
209
210  /**
211   * Creates a new in-memory directory server config object with the default
212   * settings.
213   *
214   * @param  baseDNs  The set of base DNs to use for the server.  It must not
215   *                  be {@code null} or empty.
216   *
217   * @throws  LDAPException  If the provided set of base DNs is null or empty.
218   */
219  public InMemoryDirectoryServerConfig(final DN... baseDNs)
220         throws LDAPException
221  {
222    if ((baseDNs == null) || (baseDNs.length == 0))
223    {
224      throw new LDAPException(ResultCode.PARAM_ERROR,
225           ERR_MEM_DS_CFG_NO_BASE_DNS.get());
226    }
227
228    this.baseDNs = baseDNs;
229
230    listenerConfigs = new ArrayList<InMemoryListenerConfig>(1);
231    listenerConfigs.add(InMemoryListenerConfig.createLDAPConfig("default"));
232
233    additionalBindCredentials            = new LinkedHashMap<DN,byte[]>(1);
234    accessLogHandler                     = null;
235    ldapDebugLogHandler                  = null;
236    enforceAttributeSyntaxCompliance     = true;
237    enforceSingleStructuralObjectClass   = true;
238    generateOperationalAttributes        = true;
239    maxChangeLogEntries                  = 0;
240    maxConnections                       = 0;
241    maxSizeLimit                         = 0;
242    exceptionHandler                     = null;
243    equalityIndexAttributes              = new ArrayList<String>(10);
244    rootDSEEntry                         = null;
245    schema                               = Schema.getDefaultStandardSchema();
246    allowedOperationTypes                = EnumSet.allOf(OperationType.class);
247    authenticationRequiredOperationTypes = EnumSet.noneOf(OperationType.class);
248    referentialIntegrityAttributes       = new HashSet<String>(0);
249    vendorName                           = "UnboundID Corp.";
250    vendorVersion                        = Version.FULL_VERSION_STRING;
251    codeLogPath                          = null;
252    includeRequestProcessingInCodeLog    = false;
253
254    operationInterceptors = new ArrayList<InMemoryOperationInterceptor>(5);
255
256    extendedOperationHandlers =
257         new ArrayList<InMemoryExtendedOperationHandler>(3);
258    extendedOperationHandlers.add(new PasswordModifyExtendedOperationHandler());
259    extendedOperationHandlers.add(new TransactionExtendedOperationHandler());
260    extendedOperationHandlers.add(new WhoAmIExtendedOperationHandler());
261
262    saslBindHandlers = new ArrayList<InMemorySASLBindHandler>(1);
263    saslBindHandlers.add(new PLAINBindHandler());
264  }
265
266
267
268  /**
269   * Creates a new in-memory directory server config object that is a duplicate
270   * of the provided config and may be altered without impacting the state of
271   * the given config object.
272   *
273   * @param  cfg  The in-memory directory server config object for to be
274   *              duplicated.
275   */
276  public InMemoryDirectoryServerConfig(final InMemoryDirectoryServerConfig cfg)
277  {
278    baseDNs = new DN[cfg.baseDNs.length];
279    System.arraycopy(cfg.baseDNs, 0, baseDNs, 0, baseDNs.length);
280
281    listenerConfigs = new ArrayList<InMemoryListenerConfig>(
282         cfg.listenerConfigs);
283
284    operationInterceptors = new ArrayList<InMemoryOperationInterceptor>(
285         cfg.operationInterceptors);
286
287    extendedOperationHandlers = new ArrayList<InMemoryExtendedOperationHandler>(
288         cfg.extendedOperationHandlers);
289
290    saslBindHandlers =
291         new ArrayList<InMemorySASLBindHandler>(cfg.saslBindHandlers);
292
293    additionalBindCredentials =
294         new LinkedHashMap<DN,byte[]>(cfg.additionalBindCredentials);
295
296    referentialIntegrityAttributes =
297         new HashSet<String>(cfg.referentialIntegrityAttributes);
298
299    allowedOperationTypes = EnumSet.noneOf(OperationType.class);
300    allowedOperationTypes.addAll(cfg.allowedOperationTypes);
301
302    authenticationRequiredOperationTypes = EnumSet.noneOf(OperationType.class);
303    authenticationRequiredOperationTypes.addAll(
304         cfg.authenticationRequiredOperationTypes);
305
306    equalityIndexAttributes =
307         new ArrayList<String>(cfg.equalityIndexAttributes);
308
309    enforceAttributeSyntaxCompliance   = cfg.enforceAttributeSyntaxCompliance;
310    enforceSingleStructuralObjectClass = cfg.enforceSingleStructuralObjectClass;
311    generateOperationalAttributes      = cfg.generateOperationalAttributes;
312    accessLogHandler                   = cfg.accessLogHandler;
313    ldapDebugLogHandler                = cfg.ldapDebugLogHandler;
314    maxChangeLogEntries                = cfg.maxChangeLogEntries;
315    maxConnections                     = cfg.maxConnections;
316    maxSizeLimit                       = cfg.maxSizeLimit;
317    exceptionHandler                   = cfg.exceptionHandler;
318    rootDSEEntry                       = cfg.rootDSEEntry;
319    schema                             = cfg.schema;
320    vendorName                         = cfg.vendorName;
321    vendorVersion                      = cfg.vendorVersion;
322    codeLogPath                        = cfg.codeLogPath;
323    includeRequestProcessingInCodeLog  = cfg.includeRequestProcessingInCodeLog;
324  }
325
326
327
328  /**
329   * Retrieves the set of base DNs that should be used for the directory server.
330   *
331   * @return  The set of base DNs that should be used for the directory server.
332   */
333  public DN[] getBaseDNs()
334  {
335    return baseDNs;
336  }
337
338
339
340  /**
341   * Specifies the set of base DNs that should be used for the directory server.
342   *
343   * @param  baseDNs  The set of base DNs that should be used for the directory
344   *                  server.  It must not be {@code null} or empty.
345   *
346   * @throws  LDAPException  If the provided set of base DN strings is null or
347   *                         empty, or if any of the provided base DN strings
348   *                         cannot be parsed as a valid DN.
349   */
350  public void setBaseDNs(final String... baseDNs)
351         throws LDAPException
352  {
353    setBaseDNs(parseDNs(schema, baseDNs));
354  }
355
356
357
358  /**
359   * Specifies the set of base DNs that should be used for the directory server.
360   *
361   * @param  baseDNs  The set of base DNs that should be used for the directory
362   *                  server.  It must not be {@code null} or empty.
363   *
364   * @throws  LDAPException  If the provided set of base DNs is null or empty.
365   */
366  public void setBaseDNs(final DN... baseDNs)
367         throws LDAPException
368  {
369    if ((baseDNs == null) || (baseDNs.length == 0))
370    {
371      throw new LDAPException(ResultCode.PARAM_ERROR,
372           ERR_MEM_DS_CFG_NO_BASE_DNS.get());
373    }
374
375    this.baseDNs = baseDNs;
376  }
377
378
379
380  /**
381   * Retrieves the list of listener configurations that should be used for the
382   * directory server.
383   *
384   * @return  The list of listener configurations that should be used for the
385   *          directory server.
386   */
387  public List<InMemoryListenerConfig> getListenerConfigs()
388  {
389    return listenerConfigs;
390  }
391
392
393
394  /**
395   * Specifies the configurations for all listeners that should be used for the
396   * directory server.
397   *
398   * @param  listenerConfigs  The configurations for all listeners that should
399   *                          be used for the directory server.  It must not be
400   *                          {@code null} or empty, and it must not contain
401   *                          multiple configurations with the same name.
402   *
403   * @throws  LDAPException  If there is a problem with the provided set of
404   *                         listener configurations.
405   */
406  public void setListenerConfigs(
407                   final InMemoryListenerConfig... listenerConfigs)
408         throws LDAPException
409  {
410    setListenerConfigs(StaticUtils.toList(listenerConfigs));
411  }
412
413
414
415  /**
416   * Specifies the configurations for all listeners that should be used for the
417   * directory server.
418   *
419   * @param  listenerConfigs  The configurations for all listeners that should
420   *                          be used for the directory server.  It must not be
421   *                          {@code null} or empty, and it must not contain
422   *                          multiple configurations with the same name.
423   *
424   * @throws  LDAPException  If there is a problem with the provided set of
425   *                         listener configurations.
426   */
427  public void setListenerConfigs(
428                   final Collection<InMemoryListenerConfig> listenerConfigs)
429         throws LDAPException
430  {
431    if ((listenerConfigs == null) || listenerConfigs.isEmpty())
432    {
433      throw new LDAPException(ResultCode.PARAM_ERROR,
434           ERR_MEM_DS_CFG_NO_LISTENERS.get());
435    }
436
437    final HashSet<String> listenerNames =
438         new HashSet<String>(listenerConfigs.size());
439    for (final InMemoryListenerConfig c : listenerConfigs)
440    {
441      final String name = StaticUtils.toLowerCase(c.getListenerName());
442      if (listenerNames.contains(name))
443      {
444        throw new LDAPException(ResultCode.PARAM_ERROR,
445             ERR_MEM_DS_CFG_CONFLICTING_LISTENER_NAMES.get(name));
446      }
447      else
448      {
449        listenerNames.add(name);
450      }
451    }
452
453    this.listenerConfigs.clear();
454    this.listenerConfigs.addAll(listenerConfigs);
455  }
456
457
458
459  /**
460   * Retrieves the set of operation types that will be allowed by the server.
461   * Note that if the server is configured to support StartTLS, then it will be
462   * allowed even if other types of extended operations are not allowed.
463   *
464   * @return  The set of operation types that will be allowed by the server.
465   */
466  public Set<OperationType> getAllowedOperationTypes()
467  {
468    return allowedOperationTypes;
469  }
470
471
472
473  /**
474   * Specifies the set of operation types that will be allowed by the server.
475   * Note that if the server is configured to support StartTLS, then it will be
476   * allowed even if other types of extended operations are not allowed.
477   *
478   * @param  operationTypes  The set of operation types that will be allowed by
479   *                         the server.
480   */
481  public void setAllowedOperationTypes(final OperationType... operationTypes)
482  {
483    allowedOperationTypes.clear();
484    if (operationTypes != null)
485    {
486      allowedOperationTypes.addAll(Arrays.asList(operationTypes));
487    }
488  }
489
490
491
492  /**
493   * Specifies the set of operation types that will be allowed by the server.
494   * Note that if the server is configured to support StartTLS, then it will be
495   * allowed even if other types of extended operations are not allowed.
496   *
497   * @param  operationTypes  The set of operation types that will be allowed by
498   *                         the server.
499   */
500  public void setAllowedOperationTypes(
501                   final Collection<OperationType> operationTypes)
502  {
503    allowedOperationTypes.clear();
504    if (operationTypes != null)
505    {
506      allowedOperationTypes.addAll(operationTypes);
507    }
508  }
509
510
511
512  /**
513   * Retrieves the set of operation types that will only be allowed for
514   * authenticated clients.  Note that authentication will never be required for
515   * bind operations, and if the server is configured to support StartTLS, then
516   * authentication will never be required for StartTLS operations even if it
517   * is required for other types of extended operations.
518   *
519   * @return  The set of operation types that will only be allowed for
520   *          authenticated clients.
521   */
522  public Set<OperationType> getAuthenticationRequiredOperationTypes()
523  {
524    return authenticationRequiredOperationTypes;
525  }
526
527
528
529  /**
530   * Specifies the set of operation types that will only be allowed for
531   * authenticated clients.  Note that authentication will never be required for
532   * bind operations, and if the server is configured to support StartTLS, then
533   * authentication will never be required for StartTLS operations even if it
534   * is required for other types of extended operations.
535   *
536   * @param  operationTypes  The set of operation types that will be allowed for
537   *                         authenticated clients.
538   */
539  public void setAuthenticationRequiredOperationTypes(
540                   final OperationType... operationTypes)
541  {
542    authenticationRequiredOperationTypes.clear();
543    if (operationTypes != null)
544    {
545      authenticationRequiredOperationTypes.addAll(
546           Arrays.asList(operationTypes));
547    }
548  }
549
550
551
552  /**
553   * Specifies the set of operation types that will only be allowed for
554   * authenticated clients.  Note that authentication will never be required for
555   * bind operations, and if the server is configured to support StartTLS, then
556   * authentication will never be required for StartTLS operations even if it
557   * is required for other types of extended operations.
558   *
559   * @param  operationTypes  The set of operation types that will be allowed for
560   *                         authenticated clients.
561   */
562  public void setAuthenticationRequiredOperationTypes(
563                   final Collection<OperationType> operationTypes)
564  {
565    authenticationRequiredOperationTypes.clear();
566    if (operationTypes != null)
567    {
568      authenticationRequiredOperationTypes.addAll(operationTypes);
569    }
570  }
571
572
573
574  /**
575   * Retrieves a map containing DNs and passwords of additional users that will
576   * be allowed to bind to the server, even if their entries do not exist in the
577   * data set.  This can be used to mimic the functionality of special
578   * administrative accounts (e.g., "cn=Directory Manager" in many directories).
579   * The map that is returned may be altered if desired.
580   *
581   * @return  A map containing DNs and passwords of additional users that will
582   *          be allowed to bind to the server, even if their entries do not
583   *          exist in the data set.
584   */
585  public Map<DN,byte[]> getAdditionalBindCredentials()
586  {
587    return additionalBindCredentials;
588  }
589
590
591
592  /**
593   * Adds an additional bind DN and password combination that can be used to
594   * bind to the server, even if the corresponding entry does not exist in the
595   * data set.  This can be used to mimic the functionality of special
596   * administrative accounts (e.g., "cn=Directory Manager" in many directories).
597   * If a password has already been defined for the given DN, then it will be
598   * replaced with the newly-supplied password.
599   *
600   * @param  dn        The bind DN to allow.  It must not be {@code null} or
601   *                   represent the null DN.
602   * @param  password  The password for the provided bind DN.  It must not be
603   *                   {@code null} or empty.
604   *
605   * @throws  LDAPException  If there is a problem with the provided bind DN or
606   *                         password.
607   */
608  public void addAdditionalBindCredentials(final String dn,
609                                           final String password)
610         throws LDAPException
611  {
612    addAdditionalBindCredentials(dn, StaticUtils.getBytes(password));
613  }
614
615
616
617  /**
618   * Adds an additional bind DN and password combination that can be used to
619   * bind to the server, even if the corresponding entry does not exist in the
620   * data set.  This can be used to mimic the functionality of special
621   * administrative accounts (e.g., "cn=Directory Manager" in many directories).
622   * If a password has already been defined for the given DN, then it will be
623   * replaced with the newly-supplied password.
624   *
625   * @param  dn        The bind DN to allow.  It must not be {@code null} or
626   *                   represent the null DN.
627   * @param  password  The password for the provided bind DN.  It must not be
628   *                   {@code null} or empty.
629   *
630   * @throws  LDAPException  If there is a problem with the provided bind DN or
631   *                         password.
632   */
633  public void addAdditionalBindCredentials(final String dn,
634                                           final byte[] password)
635         throws LDAPException
636  {
637    if (dn == null)
638    {
639      throw new LDAPException(ResultCode.PARAM_ERROR,
640           ERR_MEM_DS_CFG_NULL_ADDITIONAL_BIND_DN.get());
641    }
642
643    final DN parsedDN = new DN(dn, schema);
644    if (parsedDN.isNullDN())
645    {
646      throw new LDAPException(ResultCode.PARAM_ERROR,
647           ERR_MEM_DS_CFG_NULL_ADDITIONAL_BIND_DN.get());
648    }
649
650    if ((password == null) || (password.length == 0))
651    {
652      throw new LDAPException(ResultCode.PARAM_ERROR,
653           ERR_MEM_DS_CFG_NULL_ADDITIONAL_BIND_PW.get());
654    }
655
656    additionalBindCredentials.put(parsedDN, password);
657  }
658
659
660
661  /**
662   * Retrieves the object that should be used to handle any errors encountered
663   * while attempting to interact with a client, if defined.
664   *
665   * @return  The object that should be used to handle any errors encountered
666   *          while attempting to interact with a client, or {@code null} if no
667   *          exception handler should be used.
668   */
669  public LDAPListenerExceptionHandler getListenerExceptionHandler()
670  {
671    return exceptionHandler;
672  }
673
674
675
676  /**
677   * Specifies the LDAP listener exception handler that the server should use to
678   * handle any errors encountered while attempting to interact with a client.
679   *
680   * @param  exceptionHandler  The LDAP listener exception handler that the
681   *                           server should use to handle any errors
682   *                           encountered while attempting to interact with a
683   *                           client.  It may be {@code null} if no exception
684   *                           handler should be used.
685   */
686  public void setListenerExceptionHandler(
687                   final LDAPListenerExceptionHandler exceptionHandler)
688  {
689    this.exceptionHandler = exceptionHandler;
690  }
691
692
693
694  /**
695   * Retrieves the schema that should be used by the server, if defined.  If a
696   * schema is defined, then it will be used to validate entries and determine
697   * which matching rules should be used for various types of matching
698   * operations.
699   *
700   * @return  The schema that should be used by the server, or {@code null} if
701   *          no schema should be used.
702   */
703  public Schema getSchema()
704  {
705    return schema;
706  }
707
708
709
710  /**
711   * Specifies the schema that should be used by the server.  If a schema is
712   * defined, then it will be used to validate entries and determine which
713   * matching rules should be used for various types of matching operations.
714   *
715   * @param  schema  The schema that should be used by the server.  It may be
716   *                 {@code null} if no schema should be used.
717   */
718  public void setSchema(final Schema schema)
719  {
720    this.schema = schema;
721  }
722
723
724
725  /**
726   * Indicates whether the server should reject attribute values which violate
727   * the constraints of the associated syntax.  This setting will be ignored if
728   * a {@code null} schema is in place.
729   *
730   * @return  {@code true} if the server should reject attribute values which
731   *          violate the constraints of the associated syntax, or {@code false}
732   *          if not.
733   */
734  public boolean enforceAttributeSyntaxCompliance()
735  {
736    return enforceAttributeSyntaxCompliance;
737  }
738
739
740
741  /**
742   * Specifies whether the server should reject attribute values which violate
743   * the constraints of the associated syntax.  This setting will be ignored if
744   * a {@code null} schema is in place.
745   *
746   * @param  enforceAttributeSyntaxCompliance  Indicates whether the server
747   *                                           should reject attribute values
748   *                                           which violate the constraints of
749   *                                           the associated syntax.
750   */
751  public void setEnforceAttributeSyntaxCompliance(
752                   final boolean enforceAttributeSyntaxCompliance)
753  {
754    this.enforceAttributeSyntaxCompliance = enforceAttributeSyntaxCompliance;
755  }
756
757
758
759  /**
760   * Indicates whether the server should reject entries which do not contain
761   * exactly one structural object class.  This setting will be ignored if a
762   * {@code null} schema is in place.
763   *
764   * @return  {@code true} if the server should reject entries which do not
765   *          contain exactly one structural object class, or {@code false} if
766   *          it should allow entries which do not have any structural class or
767   *          that have multiple structural classes.
768   */
769  public boolean enforceSingleStructuralObjectClass()
770  {
771    return enforceSingleStructuralObjectClass;
772  }
773
774
775
776  /**
777   * Specifies whether the server should reject entries which do not contain
778   * exactly one structural object class.  This setting will be ignored if a
779   * {@code null} schema is in place.
780   *
781   * @param  enforceSingleStructuralObjectClass  Indicates whether the server
782   *                                             should reject entries which do
783   *                                             not contain exactly one
784   *                                             structural object class.
785   */
786  public void setEnforceSingleStructuralObjectClass(
787                   final boolean enforceSingleStructuralObjectClass)
788  {
789    this.enforceSingleStructuralObjectClass =
790         enforceSingleStructuralObjectClass;
791  }
792
793
794
795  /**
796   * Retrieves the log handler that should be used to record access log messages
797   * about operations processed by the server, if any.
798   *
799   * @return  The log handler that should be used to record access log messages
800   *          about operations processed by the server, or {@code null} if no
801   *          access logging should be performed.
802   */
803  public Handler getAccessLogHandler()
804  {
805    return accessLogHandler;
806  }
807
808
809
810  /**
811   * Specifies the log handler that should be used to record access log messages
812   * about operations processed by the server.
813   *
814   * @param  accessLogHandler  The log handler that should be used to record
815   *                           access log messages about operations processed by
816   *                           the server.  It may be {@code null} if no access
817   *                           logging should be performed.
818   */
819  public void setAccessLogHandler(final Handler accessLogHandler)
820  {
821    this.accessLogHandler = accessLogHandler;
822  }
823
824
825
826  /**
827   * Retrieves the log handler that should be used to record detailed messages
828   * about LDAP communication to and from the server, which may be useful for
829   * debugging purposes.
830   *
831   * @return  The log handler that should be used to record detailed
832   *          protocol-level debug messages about LDAP communication to and from
833   *          the server, or {@code null} if no debug logging should be
834   *          performed.
835   */
836  public Handler getLDAPDebugLogHandler()
837  {
838    return ldapDebugLogHandler;
839  }
840
841
842
843  /**
844   * Specifies the log handler that should be used to record detailed messages
845   * about LDAP communication to and from the server, which may be useful for
846   * debugging purposes.
847   *
848   * @param  ldapDebugLogHandler  The log handler that should be used to record
849   *                              detailed messages about LDAP communication to
850   *                              and from the server.  It may be {@code null}
851   *                              if no LDAP debug logging should be performed.
852   */
853  public void setLDAPDebugLogHandler(final Handler ldapDebugLogHandler)
854  {
855    this.ldapDebugLogHandler = ldapDebugLogHandler;
856  }
857
858
859
860  /**
861   * Retrieves the path to a file to be written with generated code that may
862   * be used to construct the requests processed by the server.
863   *
864   * @return  The path to a file to be written with generated code that may be
865   *          used to construct the requests processed by the server, or
866   *          {@code null} if no code log should be written.
867   */
868  public String getCodeLogPath()
869  {
870    return codeLogPath;
871  }
872
873
874
875  /**
876   * Indicates whether the code log should include sample code for processing
877   * the generated requests.  This will only be used if {@link #getCodeLogPath}
878   * returns a non-{@code null} value.
879   *
880   * @return  {@code false} if the code log should only include code that
881   *          corresponds to requests received from clients, or {@code true} if
882   *          the code log should also include sample code for processing the
883   *          generated requests and interpreting the results.
884   */
885  public boolean includeRequestProcessingInCodeLog()
886  {
887    return includeRequestProcessingInCodeLog;
888  }
889
890
891
892  /**
893   * Specifies information about code logging that should be performed by the
894   * server, if any.
895   *
896   * @param  codeLogPath        The path to the file to which a code log should
897   *                            be written.  It may be {@code null} if no code
898   *                            log should be written.
899   * @param  includeProcessing  Indicates whether to include sample code that
900   *                            demonstrates how to process the requests and
901   *                            interpret the results.  This will only be
902   *                            used if the {@code codeLogPath} argument is
903   *                            non-{@code null}.
904   */
905  public void setCodeLogDetails(final String codeLogPath,
906                                final boolean includeProcessing)
907  {
908    this.codeLogPath = codeLogPath;
909    includeRequestProcessingInCodeLog = includeProcessing;
910  }
911
912
913
914  /**
915   * Retrieves a list of the operation interceptors that may be used to
916   * intercept and transform requests before they are processed by the in-memory
917   * directory server, and/or to intercept and transform responses before they
918   * are returned to the client.  The contents of the list may be altered by the
919   * caller.
920   *
921   * @return  An updatable list of the operation interceptors that may be used
922   *          to intercept and transform requests and/or responses.
923   */
924  public List<InMemoryOperationInterceptor> getOperationInterceptors()
925  {
926    return operationInterceptors;
927  }
928
929
930
931  /**
932   * Adds the provided operation interceptor to the list of operation
933   * interceptors that may be used to transform requests before they are
934   * processed by the in-memory directory server, and/or to transform responses
935   * before they are returned to the client.
936   *
937   * @param  interceptor  The operation interceptor that should be invoked in
938   *                      the course of processing requests and responses.
939   */
940  public void addInMemoryOperationInterceptor(
941                   final InMemoryOperationInterceptor interceptor)
942  {
943    operationInterceptors.add(interceptor);
944  }
945
946
947
948  /**
949   * Retrieves a list of the extended operation handlers that may be used to
950   * process extended operations in the server.  The contents of the list may
951   * be altered by the caller.
952   *
953   * @return  An updatable list of the extended operation handlers that may be
954   *          used to process extended operations in the server.
955   */
956  public List<InMemoryExtendedOperationHandler> getExtendedOperationHandlers()
957  {
958    return extendedOperationHandlers;
959  }
960
961
962
963  /**
964   * Adds the provided extended operation handler for use by the server for
965   * processing certain types of extended operations.
966   *
967   * @param  handler  The extended operation handler that should be used by the
968   *                  server for processing certain types of extended
969   *                  operations.
970   */
971  public void addExtendedOperationHandler(
972                   final InMemoryExtendedOperationHandler handler)
973  {
974    extendedOperationHandlers.add(handler);
975  }
976
977
978
979  /**
980   * Retrieves a list of the SASL bind handlers that may be used to process
981   * SASL bind requests in the server.  The contents of the list may be altered
982   * by the caller.
983   *
984   * @return  An updatable list of the SASL bind handlers that may be used to
985   *          process SASL bind requests in the server.
986   */
987  public List<InMemorySASLBindHandler> getSASLBindHandlers()
988  {
989    return saslBindHandlers;
990  }
991
992
993
994  /**
995   * Adds the provided SASL bind handler for use by the server for processing
996   * certain types of SASL bind requests.
997   *
998   * @param  handler  The SASL bind handler that should be used by the server
999   *                  for processing certain types of SASL bind requests.
1000   */
1001  public void addSASLBindHandler(final InMemorySASLBindHandler handler)
1002  {
1003    saslBindHandlers.add(handler);
1004  }
1005
1006
1007
1008  /**
1009   * Indicates whether the server should automatically generate operational
1010   * attributes (including entryDN, entryUUID, creatorsName, createTimestamp,
1011   * modifiersName, modifyTimestamp, and subschemaSubentry) for entries in the
1012   * server.
1013   *
1014   * @return  {@code true} if the server should automatically generate
1015   *          operational attributes for entries in the server, or {@code false}
1016   *          if not.
1017   */
1018  public boolean generateOperationalAttributes()
1019  {
1020    return generateOperationalAttributes;
1021  }
1022
1023
1024
1025  /**
1026   * Specifies whether the server should automatically generate operational
1027   * attributes (including entryDN, entryUUID, creatorsName, createTimestamp,
1028   * modifiersName, modifyTimestamp, and subschemaSubentry) for entries in the
1029   * server.
1030   *
1031   * @param  generateOperationalAttributes  Indicates whether the server should
1032   *                                        automatically generate operational
1033   *                                        attributes for entries in the
1034   *                                        server.
1035   */
1036  public void setGenerateOperationalAttributes(
1037                   final boolean generateOperationalAttributes)
1038  {
1039    this.generateOperationalAttributes = generateOperationalAttributes;
1040  }
1041
1042
1043
1044  /**
1045   * Retrieves the maximum number of changelog entries that the server should
1046   * maintain.
1047   *
1048   * @return  The maximum number of changelog entries that the server should
1049   *          maintain, or 0 if the server should not maintain a changelog.
1050   */
1051  public int getMaxChangeLogEntries()
1052  {
1053    return maxChangeLogEntries;
1054  }
1055
1056
1057
1058  /**
1059   * Specifies the maximum number of changelog entries that the server should
1060   * maintain.  A value less than or equal to zero indicates that the server
1061   * should not attempt to maintain a changelog.
1062   *
1063   * @param  maxChangeLogEntries  The maximum number of changelog entries that
1064   *                              the server should maintain.
1065   */
1066  public void setMaxChangeLogEntries(final int maxChangeLogEntries)
1067  {
1068    if (maxChangeLogEntries < 0)
1069    {
1070      this.maxChangeLogEntries = 0;
1071    }
1072    else
1073    {
1074      this.maxChangeLogEntries = maxChangeLogEntries;
1075    }
1076  }
1077
1078
1079
1080  /**
1081   * Retrieves the maximum number of concurrent connections that the server will
1082   * allow.  If a client tries to establish a new connection while the server
1083   * already has the maximum number of concurrent connections, then the new
1084   * connection will be rejected.  Note that if the server is configured with
1085   * multiple listeners, then each listener will be allowed to have up to this
1086   * number of connections.
1087   *
1088   * @return  The maximum number of concurrent connections that the server will
1089   *          allow, or zero if no limit should be enforced.
1090   */
1091  public int getMaxConnections()
1092  {
1093    return maxConnections;
1094  }
1095
1096
1097
1098  /**
1099   * Specifies the maximum number of concurrent connections that the server will
1100   * allow.  If a client tries to establish a new connection while the server
1101   * already has the maximum number of concurrent connections, then the new
1102   * connection will be rejected.  Note that if the server is configured with
1103   * multiple listeners, then each listener will be allowed to have up to this
1104   * number of connections.
1105   *
1106   * @param  maxConnections  The maximum number of concurrent connections that
1107   *                         the server will allow.  A value that is less than
1108   *                         or equal to zero indicates no limit.
1109   */
1110  public void setMaxConnections(final int maxConnections)
1111  {
1112    if (maxConnections > 0)
1113    {
1114      this.maxConnections = maxConnections;
1115    }
1116    else
1117    {
1118      this.maxConnections = 0;
1119    }
1120  }
1121
1122
1123
1124  /**
1125   * Retrieves the maximum number of entries that the server should return in
1126   * any search operation.
1127   *
1128   * @return  The maximum number of entries that the server should return in any
1129   *          search operation, or zero if no limit should be enforced.
1130   */
1131  public int getMaxSizeLimit()
1132  {
1133    return maxSizeLimit;
1134  }
1135
1136
1137
1138  /**
1139   * Specifies the maximum number of entries that the server should return in
1140   * any search operation.  A value less than or equal to zero indicates that no
1141   * maximum limit should be enforced.
1142   *
1143   * @param  maxSizeLimit  The maximum number of entries that the server should
1144   *                       return in any search operation.
1145   */
1146  public void setMaxSizeLimit(final int maxSizeLimit)
1147  {
1148    if (maxSizeLimit > 0)
1149    {
1150      this.maxSizeLimit = maxSizeLimit;
1151    }
1152    else
1153    {
1154      this.maxSizeLimit = 0;
1155    }
1156  }
1157
1158
1159
1160  /**
1161   * Retrieves a list containing the names or OIDs of the attribute types for
1162   * which to maintain an equality index to improve the performance of certain
1163   * kinds of searches.
1164   *
1165   * @return  A list containing the names or OIDs of the attribute types for
1166   *          which to maintain an equality index to improve the performance of
1167   *          certain kinds of searches, or an empty list if no equality indexes
1168   *          should be created.
1169   */
1170  public List<String> getEqualityIndexAttributes()
1171  {
1172    return equalityIndexAttributes;
1173  }
1174
1175
1176
1177  /**
1178   * Specifies the names or OIDs of the attribute types for which to maintain an
1179   * equality index to improve the performance of certain kinds of searches.
1180   *
1181   * @param  equalityIndexAttributes  The names or OIDs of the attributes for
1182   *                                  which to maintain an equality index to
1183   *                                  improve the performance of certain kinds
1184   *                                  of searches.  It may be {@code null} or
1185   *                                  empty to indicate that no equality indexes
1186   *                                  should be maintained.
1187   */
1188  public void setEqualityIndexAttributes(
1189                   final String... equalityIndexAttributes)
1190  {
1191    setEqualityIndexAttributes(StaticUtils.toList(equalityIndexAttributes));
1192  }
1193
1194
1195
1196  /**
1197   * Specifies the names or OIDs of the attribute types for which to maintain an
1198   * equality index to improve the performance of certain kinds of searches.
1199   *
1200   * @param  equalityIndexAttributes  The names or OIDs of the attributes for
1201   *                                  which to maintain an equality index to
1202   *                                  improve the performance of certain kinds
1203   *                                  of searches.  It may be {@code null} or
1204   *                                  empty to indicate that no equality indexes
1205   *                                  should be maintained.
1206   */
1207  public void setEqualityIndexAttributes(
1208                   final Collection<String> equalityIndexAttributes)
1209  {
1210    this.equalityIndexAttributes.clear();
1211    if (equalityIndexAttributes != null)
1212    {
1213      this.equalityIndexAttributes.addAll(equalityIndexAttributes);
1214    }
1215  }
1216
1217
1218
1219  /**
1220   * Retrieves the names of the attributes for which referential integrity
1221   * should be maintained.  If referential integrity is to be provided and an
1222   * entry is removed, then any other entries containing one of the specified
1223   * attributes with a value equal to the DN of the entry that was removed, then
1224   * that value will also be removed.  Similarly, if an entry is moved or
1225   * renamed, then any references to that entry in one of the specified
1226   * attributes will be updated to reflect the new DN.
1227   *
1228   * @return  The names of the attributes for which referential integrity should
1229   *          be maintained, or an empty set if referential integrity should not
1230   *          be maintained for any attributes.
1231   */
1232  public Set<String> getReferentialIntegrityAttributes()
1233  {
1234    return referentialIntegrityAttributes;
1235  }
1236
1237
1238
1239  /**
1240   * Specifies the names of the attributes for which referential integrity
1241   * should be maintained.  If referential integrity is to be provided and an
1242   * entry is removed, then any other entries containing one of the specified
1243   * attributes with a value equal to the DN of the entry that was removed, then
1244   * that value will also be removed.  Similarly, if an entry is moved or
1245   * renamed, then any references to that entry in one of the specified
1246   * attributes will be updated to reflect the new DN.
1247   *
1248   * @param  referentialIntegrityAttributes  The names of the attributes for
1249   *                                          which referential integrity should
1250   *                                          be maintained.  The values of
1251   *                                          these attributes should be DNs.
1252   *                                          It may be {@code null} or empty if
1253   *                                          referential integrity should not
1254   *                                          be maintained.
1255   */
1256  public void setReferentialIntegrityAttributes(
1257                   final String... referentialIntegrityAttributes)
1258  {
1259    setReferentialIntegrityAttributes(
1260         StaticUtils.toList(referentialIntegrityAttributes));
1261  }
1262
1263
1264
1265  /**
1266   * Specifies the names of the attributes for which referential integrity
1267   * should be maintained.  If referential integrity is to be provided and an
1268   * entry is removed, then any other entries containing one of the specified
1269   * attributes with a value equal to the DN of the entry that was removed, then
1270   * that value will also be removed.  Similarly, if an entry is moved or
1271   * renamed, then any references to that entry in one of the specified
1272   * attributes will be updated to reflect the new DN.
1273   *
1274   * @param  referentialIntegrityAttributes  The names of the attributes for
1275   *                                          which referential integrity should
1276   *                                          be maintained.  The values of
1277   *                                          these attributes should be DNs.
1278   *                                          It may be {@code null} or empty if
1279   *                                          referential integrity should not
1280   *                                          be maintained.
1281   */
1282  public void setReferentialIntegrityAttributes(
1283                   final Collection<String> referentialIntegrityAttributes)
1284  {
1285    this.referentialIntegrityAttributes.clear();
1286    if (referentialIntegrityAttributes != null)
1287    {
1288      this.referentialIntegrityAttributes.addAll(
1289           referentialIntegrityAttributes);
1290    }
1291  }
1292
1293
1294
1295  /**
1296   * Retrieves the vendor name value to report in the server root DSE.
1297   *
1298   * @return  The vendor name value to report in the server root DSE, or
1299   *          {@code null} if no vendor name should appear.
1300   */
1301  public String getVendorName()
1302  {
1303    return vendorName;
1304  }
1305
1306
1307
1308  /**
1309   * Specifies the vendor name value to report in the server root DSE.
1310   *
1311   * @param  vendorName  The vendor name value to report in the server root DSE.
1312   *                     It may be {@code null} if no vendor name should appear.
1313   */
1314  public void setVendorName(final String vendorName)
1315  {
1316    this.vendorName = vendorName;
1317  }
1318
1319
1320
1321  /**
1322   * Retrieves the vendor version value to report in the server root DSE.
1323   *
1324   * @return  The vendor version value to report in the server root DSE, or
1325   *          {@code null} if no vendor version should appear.
1326   */
1327  public String getVendorVersion()
1328  {
1329    return vendorVersion;
1330  }
1331
1332
1333
1334  /**
1335   * Specifies the vendor version value to report in the server root DSE.
1336   *
1337   * @param  vendorVersion  The vendor version value to report in the server
1338   *                        root DSE.  It may be {@code null} if no vendor
1339   *                        version should appear.
1340   */
1341  public void setVendorVersion(final String vendorVersion)
1342  {
1343    this.vendorVersion = vendorVersion;
1344  }
1345
1346
1347
1348  /**
1349   * Retrieves a predefined entry that should always be returned as the
1350   * in-memory directory server's root DSE, if defined.
1351   *
1352   * @return  A predefined entry that should always be returned as the in-memory
1353   *          directory server's root DSE, or {@code null} if the root DSE
1354   *          should be dynamically generated.
1355   */
1356  public ReadOnlyEntry getRootDSEEntry()
1357  {
1358    return rootDSEEntry;
1359  }
1360
1361
1362
1363  /**
1364   * Specifies an entry that should always be returned as the in-memory
1365   * directory server's root DSE.  Note that if a specific root DSE entry is
1366   * provided, then
1367   *
1368   * @param  rootDSEEntry  An entry that should always be returned as the
1369   *                       in-memory directory server's root DSE, or
1370   *                       {@code null} to indicate that the root DSE should be
1371   *                       dynamically generated.
1372   */
1373  public void setRootDSEEntry(final Entry rootDSEEntry)
1374  {
1375    if (rootDSEEntry == null)
1376    {
1377      this.rootDSEEntry = null;
1378      return;
1379    }
1380
1381    final Entry e = rootDSEEntry.duplicate();
1382    e.setDN("");
1383    this.rootDSEEntry = new ReadOnlyEntry(e);
1384  }
1385
1386
1387
1388  /**
1389   * Parses the provided set of strings as DNs.
1390   *
1391   * @param  dnStrings  The array of strings to be parsed as DNs.
1392   * @param  schema     The schema to use to generate the normalized
1393   *                    representations of the DNs, if available.
1394   *
1395   * @return  The array of parsed DNs.
1396   *
1397   * @throws  LDAPException  If any of the provided strings cannot be parsed as
1398   *                         DNs.
1399   */
1400  private static DN[] parseDNs(final Schema schema, final String... dnStrings)
1401          throws LDAPException
1402  {
1403    if (dnStrings == null)
1404    {
1405      return null;
1406    }
1407
1408    final DN[] dns = new DN[dnStrings.length];
1409    for (int i=0; i < dns.length; i++)
1410    {
1411      dns[i] = new DN(dnStrings[i], schema);
1412    }
1413    return dns;
1414  }
1415
1416
1417
1418  /**
1419   * Retrieves a string representation of this in-memory directory server
1420   * configuration.
1421   *
1422   * @return  A string representation of this in-memory directory server
1423   *          configuration.
1424   */
1425  @Override()
1426  public String toString()
1427  {
1428    final StringBuilder buffer = new StringBuilder();
1429    toString(buffer);
1430    return buffer.toString();
1431  }
1432
1433
1434
1435  /**
1436   * Appends a string representation of this in-memory directory server
1437   * configuration to the provided buffer.
1438   *
1439   * @param  buffer  The buffer to which the string representation should be
1440   *                 appended.
1441   */
1442  public void toString(final StringBuilder buffer)
1443  {
1444    buffer.append("InMemoryDirectoryServerConfig(baseDNs={");
1445
1446    for (int i=0; i < baseDNs.length; i++)
1447    {
1448      if (i > 0)
1449      {
1450        buffer.append(", ");
1451      }
1452
1453      buffer.append('\'');
1454      baseDNs[i].toString(buffer);
1455      buffer.append('\'');
1456    }
1457    buffer.append('}');
1458
1459    buffer.append(", listenerConfigs={");
1460
1461    final Iterator<InMemoryListenerConfig> listenerCfgIterator =
1462         listenerConfigs.iterator();
1463    while(listenerCfgIterator.hasNext())
1464    {
1465      listenerCfgIterator.next().toString(buffer);
1466      if (listenerCfgIterator.hasNext())
1467      {
1468        buffer.append(", ");
1469      }
1470    }
1471    buffer.append('}');
1472
1473    buffer.append(", schemaProvided=");
1474    buffer.append((schema != null));
1475    buffer.append(", enforceAttributeSyntaxCompliance=");
1476    buffer.append(enforceAttributeSyntaxCompliance);
1477    buffer.append(", enforceSingleStructuralObjectClass=");
1478    buffer.append(enforceSingleStructuralObjectClass);
1479
1480    if (! additionalBindCredentials.isEmpty())
1481    {
1482      buffer.append(", additionalBindDNs={");
1483
1484      final Iterator<DN> bindDNIterator =
1485           additionalBindCredentials.keySet().iterator();
1486      while (bindDNIterator.hasNext())
1487      {
1488        buffer.append('\'');
1489        bindDNIterator.next().toString(buffer);
1490        buffer.append('\'');
1491        if (bindDNIterator.hasNext())
1492        {
1493          buffer.append(", ");
1494        }
1495      }
1496      buffer.append('}');
1497    }
1498
1499    if (! equalityIndexAttributes.isEmpty())
1500    {
1501      buffer.append(", equalityIndexAttributes={");
1502
1503      final Iterator<String> attrIterator = equalityIndexAttributes.iterator();
1504      while (attrIterator.hasNext())
1505      {
1506        buffer.append('\'');
1507        buffer.append(attrIterator.next());
1508        buffer.append('\'');
1509        if (attrIterator.hasNext())
1510        {
1511          buffer.append(", ");
1512        }
1513      }
1514      buffer.append('}');
1515    }
1516
1517    if (! referentialIntegrityAttributes.isEmpty())
1518    {
1519      buffer.append(", referentialIntegrityAttributes={");
1520
1521      final Iterator<String> attrIterator =
1522           referentialIntegrityAttributes.iterator();
1523      while (attrIterator.hasNext())
1524      {
1525        buffer.append('\'');
1526        buffer.append(attrIterator.next());
1527        buffer.append('\'');
1528        if (attrIterator.hasNext())
1529        {
1530          buffer.append(", ");
1531        }
1532      }
1533      buffer.append('}');
1534    }
1535
1536    buffer.append(", generateOperationalAttributes=");
1537    buffer.append(generateOperationalAttributes);
1538
1539    if (maxChangeLogEntries > 0)
1540    {
1541      buffer.append(", maxChangelogEntries=");
1542      buffer.append(maxChangeLogEntries);
1543    }
1544
1545    buffer.append(", maxConnections=");
1546    buffer.append(maxConnections);
1547    buffer.append(", maxSizeLimit=");
1548    buffer.append(maxSizeLimit);
1549
1550    if (! extendedOperationHandlers.isEmpty())
1551    {
1552      buffer.append(", extendedOperationHandlers={");
1553
1554      final Iterator<InMemoryExtendedOperationHandler>
1555           handlerIterator = extendedOperationHandlers.iterator();
1556      while (handlerIterator.hasNext())
1557      {
1558        buffer.append(handlerIterator.next().toString());
1559        if (handlerIterator.hasNext())
1560        {
1561          buffer.append(", ");
1562        }
1563      }
1564      buffer.append('}');
1565    }
1566
1567    if (! saslBindHandlers.isEmpty())
1568    {
1569      buffer.append(", saslBindHandlers={");
1570
1571      final Iterator<InMemorySASLBindHandler>
1572           handlerIterator = saslBindHandlers.iterator();
1573      while (handlerIterator.hasNext())
1574      {
1575        buffer.append(handlerIterator.next().toString());
1576        if (handlerIterator.hasNext())
1577        {
1578          buffer.append(", ");
1579        }
1580      }
1581      buffer.append('}');
1582    }
1583
1584    if (accessLogHandler != null)
1585    {
1586      buffer.append(", accessLogHandlerClass='");
1587      buffer.append(accessLogHandler.getClass().getName());
1588      buffer.append('\'');
1589    }
1590
1591    if (ldapDebugLogHandler != null)
1592    {
1593      buffer.append(", ldapDebugLogHandlerClass='");
1594      buffer.append(ldapDebugLogHandler.getClass().getName());
1595      buffer.append('\'');
1596    }
1597
1598    if (codeLogPath != null)
1599    {
1600      buffer.append(", codeLogPath='");
1601      buffer.append(codeLogPath);
1602      buffer.append("', includeRequestProcessingInCodeLog=");
1603      buffer.append(includeRequestProcessingInCodeLog);
1604    }
1605
1606    if (exceptionHandler != null)
1607    {
1608      buffer.append(", listenerExceptionHandlerClass='");
1609      buffer.append(exceptionHandler.getClass().getName());
1610      buffer.append('\'');
1611    }
1612
1613    if (vendorName != null)
1614    {
1615      buffer.append(", vendorName='");
1616      buffer.append(vendorName);
1617      buffer.append('\'');
1618    }
1619
1620    if (vendorVersion != null)
1621    {
1622      buffer.append(", vendorVersion='");
1623      buffer.append(vendorVersion);
1624      buffer.append('\'');
1625    }
1626
1627    buffer.append(')');
1628  }
1629}