001/*
002 * Copyright 2011-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2011-2014 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.Collections;
029import java.util.Date;
030import java.util.HashMap;
031import java.util.Iterator;
032import java.util.LinkedHashMap;
033import java.util.LinkedHashSet;
034import java.util.List;
035import java.util.Map;
036import java.util.Set;
037import java.util.SortedSet;
038import java.util.TreeMap;
039import java.util.TreeSet;
040import java.util.UUID;
041import java.util.concurrent.atomic.AtomicBoolean;
042import java.util.concurrent.atomic.AtomicLong;
043import java.util.concurrent.atomic.AtomicReference;
044
045import com.unboundid.asn1.ASN1Integer;
046import com.unboundid.asn1.ASN1OctetString;
047import com.unboundid.ldap.protocol.AddRequestProtocolOp;
048import com.unboundid.ldap.protocol.AddResponseProtocolOp;
049import com.unboundid.ldap.protocol.BindRequestProtocolOp;
050import com.unboundid.ldap.protocol.BindResponseProtocolOp;
051import com.unboundid.ldap.protocol.CompareRequestProtocolOp;
052import com.unboundid.ldap.protocol.CompareResponseProtocolOp;
053import com.unboundid.ldap.protocol.DeleteRequestProtocolOp;
054import com.unboundid.ldap.protocol.DeleteResponseProtocolOp;
055import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp;
056import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp;
057import com.unboundid.ldap.protocol.LDAPMessage;
058import com.unboundid.ldap.protocol.ModifyRequestProtocolOp;
059import com.unboundid.ldap.protocol.ModifyResponseProtocolOp;
060import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp;
061import com.unboundid.ldap.protocol.ModifyDNResponseProtocolOp;
062import com.unboundid.ldap.protocol.ProtocolOp;
063import com.unboundid.ldap.protocol.SearchRequestProtocolOp;
064import com.unboundid.ldap.protocol.SearchResultDoneProtocolOp;
065import com.unboundid.ldap.matchingrules.DistinguishedNameMatchingRule;
066import com.unboundid.ldap.matchingrules.GeneralizedTimeMatchingRule;
067import com.unboundid.ldap.matchingrules.IntegerMatchingRule;
068import com.unboundid.ldap.matchingrules.MatchingRule;
069import com.unboundid.ldap.matchingrules.OctetStringMatchingRule;
070import com.unboundid.ldap.protocol.SearchResultReferenceProtocolOp;
071import com.unboundid.ldap.sdk.Attribute;
072import com.unboundid.ldap.sdk.BindResult;
073import com.unboundid.ldap.sdk.ChangeLogEntry;
074import com.unboundid.ldap.sdk.Control;
075import com.unboundid.ldap.sdk.DN;
076import com.unboundid.ldap.sdk.Entry;
077import com.unboundid.ldap.sdk.EntrySorter;
078import com.unboundid.ldap.sdk.ExtendedRequest;
079import com.unboundid.ldap.sdk.ExtendedResult;
080import com.unboundid.ldap.sdk.Filter;
081import com.unboundid.ldap.sdk.LDAPException;
082import com.unboundid.ldap.sdk.LDAPURL;
083import com.unboundid.ldap.sdk.Modification;
084import com.unboundid.ldap.sdk.ModificationType;
085import com.unboundid.ldap.sdk.OperationType;
086import com.unboundid.ldap.sdk.RDN;
087import com.unboundid.ldap.sdk.ReadOnlyEntry;
088import com.unboundid.ldap.sdk.ResultCode;
089import com.unboundid.ldap.sdk.SearchResultEntry;
090import com.unboundid.ldap.sdk.SearchResultReference;
091import com.unboundid.ldap.sdk.SearchScope;
092import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
093import com.unboundid.ldap.sdk.schema.DITContentRuleDefinition;
094import com.unboundid.ldap.sdk.schema.DITStructureRuleDefinition;
095import com.unboundid.ldap.sdk.schema.EntryValidator;
096import com.unboundid.ldap.sdk.schema.MatchingRuleUseDefinition;
097import com.unboundid.ldap.sdk.schema.NameFormDefinition;
098import com.unboundid.ldap.sdk.schema.ObjectClassDefinition;
099import com.unboundid.ldap.sdk.schema.Schema;
100import com.unboundid.ldap.sdk.controls.AssertionRequestControl;
101import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl;
102import com.unboundid.ldap.sdk.controls.AuthorizationIdentityResponseControl;
103import com.unboundid.ldap.sdk.controls.DontUseCopyRequestControl;
104import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl;
105import com.unboundid.ldap.sdk.controls.PermissiveModifyRequestControl;
106import com.unboundid.ldap.sdk.controls.PostReadRequestControl;
107import com.unboundid.ldap.sdk.controls.PostReadResponseControl;
108import com.unboundid.ldap.sdk.controls.PreReadRequestControl;
109import com.unboundid.ldap.sdk.controls.PreReadResponseControl;
110import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl;
111import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl;
112import com.unboundid.ldap.sdk.controls.ServerSideSortRequestControl;
113import com.unboundid.ldap.sdk.controls.ServerSideSortResponseControl;
114import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl;
115import com.unboundid.ldap.sdk.controls.SortKey;
116import com.unboundid.ldap.sdk.controls.SubentriesRequestControl;
117import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl;
118import com.unboundid.ldap.sdk.controls.TransactionSpecificationRequestControl;
119import com.unboundid.ldap.sdk.controls.VirtualListViewRequestControl;
120import com.unboundid.ldap.sdk.controls.VirtualListViewResponseControl;
121import com.unboundid.ldap.sdk.extensions.AbortedTransactionExtendedResult;
122import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
123import com.unboundid.ldif.LDIFAddChangeRecord;
124import com.unboundid.ldif.LDIFDeleteChangeRecord;
125import com.unboundid.ldif.LDIFException;
126import com.unboundid.ldif.LDIFModifyChangeRecord;
127import com.unboundid.ldif.LDIFModifyDNChangeRecord;
128import com.unboundid.ldif.LDIFReader;
129import com.unboundid.ldif.LDIFWriter;
130import com.unboundid.util.Debug;
131import com.unboundid.util.Mutable;
132import com.unboundid.util.ObjectPair;
133import com.unboundid.util.StaticUtils;
134import com.unboundid.util.ThreadSafety;
135import com.unboundid.util.ThreadSafetyLevel;
136
137import static com.unboundid.ldap.listener.ListenerMessages.*;
138
139
140
141/**
142 * This class provides an implementation of an LDAP request handler that can be
143 * used to store entries in memory and process operations on those entries.
144 * It is primarily intended for use in creating a simple embeddable directory
145 * server that can be used for testing purposes.  It performs only very basic
146 * validation, and is not intended to be a fully standards-compliant server.
147 */
148@Mutable()
149@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
150public final class InMemoryRequestHandler
151       extends LDAPListenerRequestHandler
152{
153  /**
154   * A pre-allocated array containing no controls.
155   */
156  private static final Control[] NO_CONTROLS = new Control[0];
157
158
159
160  /**
161   * The OID for a proprietary control that can be used to indicate that the
162   * associated operation should be considered an internal operation that was
163   * requested by a method call in the in-memory directory server class rather
164   * than from an LDAP client.  It may be used to bypass certain restrictions
165   * that might otherwise be enforced (e.g., allowed operation types, write
166   * access to NO-USER-MODIFICATION attributes, etc.).
167   */
168  static final String OID_INTERNAL_OPERATION_REQUEST_CONTROL =
169       "1.3.6.1.4.1.30221.2.5.18";
170
171
172
173  // The change number for the first changelog entry in the server.
174  private final AtomicLong firstChangeNumber;
175
176  // The change number for the last changelog entry in the server.
177  private final AtomicLong lastChangeNumber;
178
179  // A delay (in milliseconds) to insert before processing operations.
180  private final AtomicLong processingDelayMillis;
181
182  // The reference to the entry validator that will be used for schema checking,
183  // if appropriate.
184  private final AtomicReference<EntryValidator> entryValidatorRef;
185
186  // The entry to use as the subschema subentry.
187  private final AtomicReference<ReadOnlyEntry> subschemaSubentryRef;
188
189  // The reference to the schema that will be used for this request handler.
190  private final AtomicReference<Schema> schemaRef;
191
192  // Indicates whether to generate operational attributes for writes.
193  private final boolean generateOperationalAttributes;
194
195  // The DN of the currently-authenticated user for the associated connection.
196  private DN authenticatedDN;
197
198  // The base DN for the server changelog.
199  private final DN changeLogBaseDN;
200
201  // The DN of the subschema subentry.
202  private final DN subschemaSubentryDN;
203
204  // The configuration used to create this request handler.
205  private final InMemoryDirectoryServerConfig config;
206
207  // A snapshot containing the server content as it initially appeared.  It
208  // will not contain any user data, but may contain a changelog base entry.
209  private final InMemoryDirectoryServerSnapshot initialSnapshot;
210
211  // The maximum number of changelog entries to maintain.
212  private final int maxChangelogEntries;
213
214  // The maximum number of entries to return from any single search.
215  private final int maxSizeLimit;
216
217  // The client connection for this request handler instance.
218  private final LDAPListenerClientConnection connection;
219
220  // The set of equality indexes defined for the server.
221  private final Map<AttributeTypeDefinition,
222     InMemoryDirectoryServerEqualityAttributeIndex> equalityIndexes;
223
224  // An additional set of credentials that may be used for bind operations.
225  private final Map<DN,byte[]> additionalBindCredentials;
226
227  // A map of the available extended operation handlers by request OID.
228  private final Map<String,InMemoryExtendedOperationHandler>
229       extendedRequestHandlers;
230
231  // A map of the available SASL bind handlers by mechanism name.
232  private final Map<String,InMemorySASLBindHandler> saslBindHandlers;
233
234  // A map of state information specific to the associated connection.
235  private final Map<String,Object> connectionState;
236
237  // The set of base DNs for the server.
238  private final Set<DN> baseDNs;
239
240  // The set of referential integrity attributes for the server.
241  private final Set<String> referentialIntegrityAttributes;
242
243  // The map of entries currently held in the server.
244  private final TreeMap<DN,ReadOnlyEntry> entryMap;
245
246
247
248  /**
249   * Creates a new instance of this request handler with an initially-empty
250   * data set.
251   *
252   * @param  config  The configuration that should be used for the in-memory
253   *                 directory server.
254   *
255   * @throws  LDAPException  If there is a problem with the provided
256   *                         configuration.
257   */
258  public InMemoryRequestHandler(final InMemoryDirectoryServerConfig config)
259         throws LDAPException
260  {
261    this.config = config;
262
263    schemaRef            = new AtomicReference<Schema>();
264    entryValidatorRef    = new AtomicReference<EntryValidator>();
265    subschemaSubentryRef = new AtomicReference<ReadOnlyEntry>();
266
267    final Schema schema = config.getSchema();
268    schemaRef.set(schema);
269    if (schema != null)
270    {
271      final EntryValidator entryValidator = new EntryValidator(schema);
272      entryValidatorRef.set(entryValidator);
273      entryValidator.setCheckAttributeSyntax(
274           config.enforceAttributeSyntaxCompliance());
275      entryValidator.setCheckStructuralObjectClasses(
276           config.enforceSingleStructuralObjectClass());
277    }
278
279    final DN[] baseDNArray = config.getBaseDNs();
280    if ((baseDNArray == null) || (baseDNArray.length == 0))
281    {
282      throw new LDAPException(ResultCode.PARAM_ERROR,
283           ERR_MEM_HANDLER_NO_BASE_DNS.get());
284    }
285
286    entryMap = new TreeMap<DN,ReadOnlyEntry>();
287
288    final LinkedHashSet<DN> baseDNSet =
289         new LinkedHashSet<DN>(Arrays.asList(baseDNArray));
290    if (baseDNSet.contains(DN.NULL_DN))
291    {
292      throw new LDAPException(ResultCode.PARAM_ERROR,
293           ERR_MEM_HANDLER_NULL_BASE_DN.get());
294    }
295
296    changeLogBaseDN = new DN("cn=changelog", schema);
297    if (baseDNSet.contains(changeLogBaseDN))
298    {
299      throw new LDAPException(ResultCode.PARAM_ERROR,
300           ERR_MEM_HANDLER_CHANGELOG_BASE_DN.get());
301    }
302
303    maxChangelogEntries = config.getMaxChangeLogEntries();
304
305    if (config.getMaxSizeLimit() <= 0)
306    {
307      maxSizeLimit = Integer.MAX_VALUE;
308    }
309    else
310    {
311      maxSizeLimit = config.getMaxSizeLimit();
312    }
313
314    final TreeMap<String,InMemoryExtendedOperationHandler> extOpHandlers =
315         new TreeMap<String,InMemoryExtendedOperationHandler>();
316    for (final InMemoryExtendedOperationHandler h :
317         config.getExtendedOperationHandlers())
318    {
319      for (final String oid : h.getSupportedExtendedRequestOIDs())
320      {
321        if (extOpHandlers.containsKey(oid))
322        {
323          throw new LDAPException(ResultCode.PARAM_ERROR,
324               ERR_MEM_HANDLER_EXTENDED_REQUEST_HANDLER_CONFLICT.get(oid));
325        }
326        else
327        {
328          extOpHandlers.put(oid, h);
329        }
330      }
331    }
332    extendedRequestHandlers = Collections.unmodifiableMap(extOpHandlers);
333
334    final TreeMap<String,InMemorySASLBindHandler> saslHandlers =
335         new TreeMap<String,InMemorySASLBindHandler>();
336    for (final InMemorySASLBindHandler h : config.getSASLBindHandlers())
337    {
338      final String mech = h.getSASLMechanismName();
339      if (saslHandlers.containsKey(mech))
340      {
341        throw new LDAPException(ResultCode.PARAM_ERROR,
342             ERR_MEM_HANDLER_SASL_BIND_HANDLER_CONFLICT.get(mech));
343      }
344      else
345      {
346        saslHandlers.put(mech, h);
347      }
348    }
349    saslBindHandlers = Collections.unmodifiableMap(saslHandlers);
350
351    additionalBindCredentials = Collections.unmodifiableMap(
352         config.getAdditionalBindCredentials());
353
354    final List<String> eqIndexAttrs = config.getEqualityIndexAttributes();
355    equalityIndexes = new HashMap<AttributeTypeDefinition,
356         InMemoryDirectoryServerEqualityAttributeIndex>(eqIndexAttrs.size());
357    for (final String s : eqIndexAttrs)
358    {
359      final InMemoryDirectoryServerEqualityAttributeIndex i =
360           new InMemoryDirectoryServerEqualityAttributeIndex(s, schema);
361      equalityIndexes.put(i.getAttributeType(), i);
362    }
363
364    referentialIntegrityAttributes = Collections.unmodifiableSet(
365         config.getReferentialIntegrityAttributes());
366
367    baseDNs = Collections.unmodifiableSet(baseDNSet);
368    generateOperationalAttributes = config.generateOperationalAttributes();
369    authenticatedDN               = new DN("cn=Internal Root User", schema);
370    connection                    = null;
371    connectionState               = Collections.emptyMap();
372    firstChangeNumber             = new AtomicLong(0L);
373    lastChangeNumber              = new AtomicLong(0L);
374    processingDelayMillis         = new AtomicLong(0L);
375
376    final ReadOnlyEntry subschemaSubentry = generateSubschemaSubentry(schema);
377    subschemaSubentryRef.set(subschemaSubentry);
378    subschemaSubentryDN = subschemaSubentry.getParsedDN();
379
380    if (baseDNs.contains(subschemaSubentryDN))
381    {
382      throw new LDAPException(ResultCode.PARAM_ERROR,
383           ERR_MEM_HANDLER_SCHEMA_BASE_DN.get());
384    }
385
386    if (maxChangelogEntries > 0)
387    {
388      baseDNSet.add(changeLogBaseDN);
389
390      final ReadOnlyEntry changeLogBaseEntry = new ReadOnlyEntry(
391           changeLogBaseDN, schema,
392           new Attribute("objectClass", "top", "namedObject"),
393           new Attribute("cn", "changelog"),
394           new Attribute("entryDN",
395                DistinguishedNameMatchingRule.getInstance(),
396                "cn=changelog"),
397           new Attribute("entryUUID", UUID.randomUUID().toString()),
398           new Attribute("creatorsName",
399                DistinguishedNameMatchingRule.getInstance(),
400                DN.NULL_DN.toString()),
401           new Attribute("createTimestamp",
402                GeneralizedTimeMatchingRule.getInstance(),
403                StaticUtils.encodeGeneralizedTime(new Date())),
404           new Attribute("modifiersName",
405                DistinguishedNameMatchingRule.getInstance(),
406                DN.NULL_DN.toString()),
407           new Attribute("modifyTimestamp",
408                GeneralizedTimeMatchingRule.getInstance(),
409                StaticUtils.encodeGeneralizedTime(new Date())),
410           new Attribute("subschemaSubentry",
411                DistinguishedNameMatchingRule.getInstance(),
412                subschemaSubentryDN.toString()));
413      entryMap.put(changeLogBaseDN, changeLogBaseEntry);
414      indexAdd(changeLogBaseEntry);
415    }
416
417    initialSnapshot = createSnapshot();
418  }
419
420
421
422  /**
423   * Creates a new instance of this request handler that will use the provided
424   * entry map object.
425   *
426   * @param  parent      The parent request handler instance.
427   * @param  connection  The client connection for this instance.
428   */
429  private InMemoryRequestHandler(final InMemoryRequestHandler parent,
430               final LDAPListenerClientConnection connection)
431  {
432    this.connection = connection;
433
434    authenticatedDN = DN.NULL_DN;
435    connectionState = new LinkedHashMap<String,Object>(0);
436
437    config                         = parent.config;
438    generateOperationalAttributes  = parent.generateOperationalAttributes;
439    additionalBindCredentials      = parent.additionalBindCredentials;
440    baseDNs                        = parent.baseDNs;
441    changeLogBaseDN                = parent.changeLogBaseDN;
442    firstChangeNumber              = parent.firstChangeNumber;
443    lastChangeNumber               = parent.lastChangeNumber;
444    processingDelayMillis          = parent.processingDelayMillis;
445    maxChangelogEntries            = parent.maxChangelogEntries;
446    maxSizeLimit                   = parent.maxSizeLimit;
447    equalityIndexes                = parent.equalityIndexes;
448    referentialIntegrityAttributes = parent.referentialIntegrityAttributes;
449    entryMap                       = parent.entryMap;
450    entryValidatorRef              = parent.entryValidatorRef;
451    extendedRequestHandlers        = parent.extendedRequestHandlers;
452    saslBindHandlers               = parent.saslBindHandlers;
453    schemaRef                      = parent.schemaRef;
454    subschemaSubentryRef           = parent.subschemaSubentryRef;
455    subschemaSubentryDN            = parent.subschemaSubentryDN;
456    initialSnapshot                = parent.initialSnapshot;
457  }
458
459
460
461  /**
462   * Creates a new instance of this request handler that will be used to process
463   * requests read by the provided connection.
464   *
465   * @param  connection  The connection with which this request handler instance
466   *                     will be associated.
467   *
468   * @return  The request handler instance that will be used for the provided
469   *          connection.
470   *
471   * @throws  LDAPException  If the connection should not be accepted.
472   */
473  @Override()
474  public InMemoryRequestHandler newInstance(
475              final LDAPListenerClientConnection connection)
476         throws LDAPException
477  {
478    return new InMemoryRequestHandler(this, connection);
479  }
480
481
482
483  /**
484   * Creates a point-in-time snapshot of the information contained in this
485   * in-memory request handler.  If desired, it may be restored using the
486   * {@link #restoreSnapshot} method.
487   *
488   * @return  The snapshot created based on the current content of this
489   *          in-memory request handler.
490   */
491  public synchronized InMemoryDirectoryServerSnapshot createSnapshot()
492  {
493    return new InMemoryDirectoryServerSnapshot(entryMap,
494         firstChangeNumber.get(), lastChangeNumber.get());
495  }
496
497
498
499  /**
500   * Updates the content of this in-memory request handler to match what it was
501   * at the time the snapshot was created.
502   *
503   * @param  snapshot  The snapshot to be restored.  It must not be
504   *                   {@code null}.
505   */
506  public synchronized void restoreSnapshot(
507                                final InMemoryDirectoryServerSnapshot snapshot)
508  {
509    entryMap.clear();
510    entryMap.putAll(snapshot.getEntryMap());
511
512    for (final InMemoryDirectoryServerEqualityAttributeIndex i :
513         equalityIndexes.values())
514    {
515      i.clear();
516      for (final Entry e : entryMap.values())
517      {
518        try
519        {
520          i.processAdd(e);
521        }
522        catch (final Exception ex)
523        {
524          Debug.debugException(ex);
525        }
526      }
527    }
528
529    firstChangeNumber.set(snapshot.getFirstChangeNumber());
530    lastChangeNumber.set(snapshot.getLastChangeNumber());
531  }
532
533
534
535  /**
536   * Retrieves the schema that will be used by the server, if any.
537   *
538   * @return  The schema that will be used by the server, or {@code null} if
539   *          none has been configured.
540   */
541  public Schema getSchema()
542  {
543    return schemaRef.get();
544  }
545
546
547
548  /**
549   * Retrieves a list of the base DNs configured for use by the server.
550   *
551   * @return  A list of the base DNs configured for use by the server.
552   */
553  public List<DN> getBaseDNs()
554  {
555    return Collections.unmodifiableList(new ArrayList<DN>(baseDNs));
556  }
557
558
559
560  /**
561   * Retrieves the client connection associated with this request handler
562   * instance.
563   *
564   * @return  The client connection associated with this request handler
565   *          instance, or {@code null} if this instance is not associated with
566   *          any client connection.
567   */
568  public synchronized LDAPListenerClientConnection getClientConnection()
569  {
570    return connection;
571  }
572
573
574
575  /**
576   * Retrieves the DN of the user currently authenticated on the connection
577   * associated with this request handler instance.
578   *
579   * @return  The DN of the user currently authenticated on the connection
580   *          associated with this request handler instance, or
581   *          {@code DN#NULL_DN} if the connection is unauthenticated or is
582   *          authenticated as the anonymous user.
583   */
584  public synchronized DN getAuthenticatedDN()
585  {
586    return authenticatedDN;
587  }
588
589
590
591  /**
592   * Sets the DN of the user currently authenticated on the connection
593   * associated with this request handler instance.
594   *
595   * @param  authenticatedDN  The DN of the user currently authenticated on the
596   *                          connection associated with this request handler.
597   *                          It may be {@code null} or {@link DN#NULL_DN} to
598   *                          indicate that the connection is unauthenticated.
599   */
600  public synchronized void setAuthenticatedDN(final DN authenticatedDN)
601  {
602    if (authenticatedDN == null)
603    {
604      this.authenticatedDN = DN.NULL_DN;
605    }
606    else
607    {
608      this.authenticatedDN = authenticatedDN;
609    }
610  }
611
612
613
614  /**
615   * Retrieves an unmodifiable map containing the defined set of additional bind
616   * credentials, mapped from bind DN to password bytes.
617   *
618   * @return  An unmodifiable map containing the defined set of additional bind
619   *          credentials, or an empty map if no additional credentials have
620   *          been defined.
621   */
622  public Map<DN,byte[]> getAdditionalBindCredentials()
623  {
624    return additionalBindCredentials;
625  }
626
627
628
629  /**
630   * Retrieves the password for the given DN from the set of additional bind
631   * credentials.
632   *
633   * @param  dn  The DN for which to retrieve the corresponding password.
634   *
635   * @return  The password bytes for the given DN, or {@code null} if the
636   *          additional bind credentials does not include information for the
637   *          provided DN.
638   */
639  public byte[] getAdditionalBindCredentials(final DN dn)
640  {
641    return additionalBindCredentials.get(dn);
642  }
643
644
645
646  /**
647   * Retrieves a map that may be used to hold state information specific to the
648   * connection associated with this request handler instance.  It may be
649   * queried and updated if necessary to store state information that may be
650   * needed at multiple different times in the life of a connection (e.g., when
651   * processing a multi-stage SASL bind).
652   *
653   * @return  An updatable map that may be used to hold state information
654   *          specific to the connection associated with this request handler
655   *          instance.
656   */
657  public synchronized Map<String,Object> getConnectionState()
658  {
659    return connectionState;
660  }
661
662
663
664  /**
665   * Retrieves the delay in milliseconds that the server should impose before
666   * beginning processing for operations.
667   *
668   * @return  The delay in milliseconds that the server should impose before
669   *          beginning processing for operations, or 0 if there should be no
670   *          delay inserted when processing operations.
671   */
672  public long getProcessingDelayMillis()
673  {
674    return processingDelayMillis.get();
675  }
676
677
678
679  /**
680   * Specifies the delay in milliseconds that the server should impose before
681   * beginning processing for operations.
682   *
683   * @param  processingDelayMillis  The delay in milliseconds that the server
684   *                                should impose before beginning processing
685   *                                for operations.  A value less than or equal
686   *                                to zero may be used to indicate that there
687   *                                should be no delay.
688   */
689  public void setProcessingDelayMillis(final long processingDelayMillis)
690  {
691    if (processingDelayMillis > 0)
692    {
693      this.processingDelayMillis.set(processingDelayMillis);
694    }
695    else
696    {
697      this.processingDelayMillis.set(0L);
698    }
699  }
700
701
702
703  /**
704   * Attempts to add an entry to the in-memory data set.  The attempt will fail
705   * if any of the following conditions is true:
706   * <UL>
707   *   <LI>There is a problem with any of the request controls.</LI>
708   *   <LI>The provided entry has a malformed DN.</LI>
709   *   <LI>The provided entry has the null DN.</LI>
710   *   <LI>The provided entry has a DN that is the same as or subordinate to the
711   *       subschema subentry.</LI>
712   *   <LI>The provided entry has a DN that is the same as or subordinate to the
713   *       changelog base entry.</LI>
714   *   <LI>An entry already exists with the same DN as the entry in the provided
715   *       request.</LI>
716   *   <LI>The entry is outside the set of base DNs for the server.</LI>
717   *   <LI>The entry is below one of the defined base DNs but the immediate
718   *       parent entry does not exist.</LI>
719   *   <LI>If a schema was provided, and the entry is not valid according to the
720   *       constraints of that schema.</LI>
721   * </UL>
722   *
723   * @param  messageID  The message ID of the LDAP message containing the add
724   *                    request.
725   * @param  request    The add request that was included in the LDAP message
726   *                    that was received.
727   * @param  controls   The set of controls included in the LDAP message.  It
728   *                    may be empty if there were no controls, but will not be
729   *                    {@code null}.
730   *
731   * @return  The {@link LDAPMessage} containing the response to send to the
732   *          client.  The protocol op in the {@code LDAPMessage} must be an
733   *          {@code AddResponseProtocolOp}.
734   */
735  @Override()
736  public synchronized LDAPMessage processAddRequest(final int messageID,
737                                       final AddRequestProtocolOp request,
738                                       final List<Control> controls)
739  {
740    // Sleep before processing, if appropriate.
741    sleepBeforeProcessing();
742
743    // Process the provided request controls.
744    final Map<String,Control> controlMap;
745    try
746    {
747      controlMap = RequestControlPreProcessor.processControls(
748           LDAPMessage.PROTOCOL_OP_TYPE_ADD_REQUEST, controls);
749    }
750    catch (final LDAPException le)
751    {
752      Debug.debugException(le);
753      return new LDAPMessage(messageID, new AddResponseProtocolOp(
754           le.getResultCode().intValue(), null, le.getMessage(), null));
755    }
756    final ArrayList<Control> responseControls = new ArrayList<Control>(1);
757
758
759    // If this operation type is not allowed, then reject it.
760    final boolean isInternalOp =
761         controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
762    if ((! isInternalOp) &&
763        (! config.getAllowedOperationTypes().contains(OperationType.ADD)))
764    {
765      return new LDAPMessage(messageID, new AddResponseProtocolOp(
766           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
767           ERR_MEM_HANDLER_ADD_NOT_ALLOWED.get(), null));
768    }
769
770
771    // If this operation type requires authentication, then ensure that the
772    // client is authenticated.
773    if ((authenticatedDN.isNullDN() &&
774        config.getAuthenticationRequiredOperationTypes().contains(
775             OperationType.ADD)))
776    {
777      return new LDAPMessage(messageID, new AddResponseProtocolOp(
778           ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
779           ERR_MEM_HANDLER_ADD_REQUIRES_AUTH.get(), null));
780    }
781
782
783    // See if this add request is part of a transaction.  If so, then perform
784    // appropriate processing for it and return success immediately without
785    // actually doing any further processing.
786    try
787    {
788      final ASN1OctetString txnID =
789           processTransactionRequest(messageID, request, controlMap);
790      if (txnID != null)
791      {
792        return new LDAPMessage(messageID, new AddResponseProtocolOp(
793             ResultCode.SUCCESS_INT_VALUE, null,
794             INFO_MEM_HANDLER_OP_IN_TXN.get(txnID.stringValue()), null));
795      }
796    }
797    catch (final LDAPException le)
798    {
799      Debug.debugException(le);
800      return new LDAPMessage(messageID,
801           new AddResponseProtocolOp(le.getResultCode().intValue(),
802                le.getMatchedDN(), le.getDiagnosticMessage(),
803                StaticUtils.toList(le.getReferralURLs())),
804           le.getResponseControls());
805    }
806
807
808    // Get the entry to be added.  If a schema was provided, then make sure the
809    // attributes are created with the appropriate matching rules.
810    final Entry entry;
811    final Schema schema = schemaRef.get();
812    if (schema == null)
813    {
814      entry = new Entry(request.getDN(), request.getAttributes());
815    }
816    else
817    {
818      final List<Attribute> providedAttrs = request.getAttributes();
819      final List<Attribute> newAttrs =
820           new ArrayList<Attribute>(providedAttrs.size());
821      for (final Attribute a : providedAttrs)
822      {
823        final String baseName = a.getBaseName();
824        final MatchingRule matchingRule =
825             MatchingRule.selectEqualityMatchingRule(baseName, schema);
826        newAttrs.add(new Attribute(a.getName(), matchingRule,
827             a.getRawValues()));
828      }
829
830      entry = new Entry(request.getDN(), schema, newAttrs);
831    }
832
833    // Make sure that the DN is valid.
834    final DN dn;
835    try
836    {
837      dn = entry.getParsedDN();
838    }
839    catch (final LDAPException le)
840    {
841      Debug.debugException(le);
842      return new LDAPMessage(messageID, new AddResponseProtocolOp(
843           ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
844           ERR_MEM_HANDLER_ADD_MALFORMED_DN.get(request.getDN(),
845                le.getMessage()),
846           null));
847    }
848
849    // See if the DN is the null DN, the schema entry DN, or a changelog entry.
850    if (dn.isNullDN())
851    {
852      return new LDAPMessage(messageID, new AddResponseProtocolOp(
853           ResultCode.ENTRY_ALREADY_EXISTS_INT_VALUE, null,
854           ERR_MEM_HANDLER_ADD_ROOT_DSE.get(), null));
855    }
856    else if (dn.isDescendantOf(subschemaSubentryDN, true))
857    {
858      return new LDAPMessage(messageID, new AddResponseProtocolOp(
859           ResultCode.ENTRY_ALREADY_EXISTS_INT_VALUE, null,
860           ERR_MEM_HANDLER_ADD_SCHEMA.get(subschemaSubentryDN.toString()),
861           null));
862    }
863    else if (dn.isDescendantOf(changeLogBaseDN, true))
864    {
865      return new LDAPMessage(messageID, new AddResponseProtocolOp(
866           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
867           ERR_MEM_HANDLER_ADD_CHANGELOG.get(changeLogBaseDN.toString()),
868           null));
869    }
870
871    // See if there is a referral at or above the target entry.
872    if (! controlMap.containsKey(
873               ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID))
874    {
875      final Entry referralEntry = findNearestReferral(dn);
876      if (referralEntry != null)
877      {
878        return new LDAPMessage(messageID, new AddResponseProtocolOp(
879             ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
880             INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
881             getReferralURLs(dn, referralEntry)));
882      }
883    }
884
885    // See if another entry exists with the same DN.
886    if (entryMap.containsKey(dn))
887    {
888      return new LDAPMessage(messageID, new AddResponseProtocolOp(
889           ResultCode.ENTRY_ALREADY_EXISTS_INT_VALUE, null,
890           ERR_MEM_HANDLER_ADD_ALREADY_EXISTS.get(request.getDN()), null));
891    }
892
893    // Make sure that all RDN attribute values are present in the entry.
894    final RDN      rdn           = dn.getRDN();
895    final String[] rdnAttrNames  = rdn.getAttributeNames();
896    final byte[][] rdnAttrValues = rdn.getByteArrayAttributeValues();
897    for (int i=0; i < rdnAttrNames.length; i++)
898    {
899      final MatchingRule matchingRule =
900           MatchingRule.selectEqualityMatchingRule(rdnAttrNames[i], schema);
901      entry.addAttribute(new Attribute(rdnAttrNames[i], matchingRule,
902           rdnAttrValues[i]));
903    }
904
905    // Make sure that all superior object classes are present in the entry.
906    if (schema != null)
907    {
908      final String[] objectClasses = entry.getObjectClassValues();
909      if (objectClasses != null)
910      {
911        final LinkedHashMap<String,String> ocMap =
912             new LinkedHashMap<String,String>(objectClasses.length);
913        for (final String ocName : objectClasses)
914        {
915          final ObjectClassDefinition oc = schema.getObjectClass(ocName);
916          if (oc == null)
917          {
918            ocMap.put(StaticUtils.toLowerCase(ocName), ocName);
919          }
920          else
921          {
922            ocMap.put(StaticUtils.toLowerCase(oc.getNameOrOID()), ocName);
923            for (final ObjectClassDefinition supClass :
924                 oc.getSuperiorClasses(schema, true))
925            {
926              ocMap.put(StaticUtils.toLowerCase(supClass.getNameOrOID()),
927                   supClass.getNameOrOID());
928            }
929          }
930        }
931
932        final String[] newObjectClasses = new String[ocMap.size()];
933        ocMap.values().toArray(newObjectClasses);
934        entry.setAttribute("objectClass", newObjectClasses);
935      }
936    }
937
938    // If a schema was provided, then make sure the entry complies with it.
939    // Also make sure that there are no attributes marked with
940    // NO-USER-MODIFICATION.
941    final EntryValidator entryValidator = entryValidatorRef.get();
942    if (entryValidator != null)
943    {
944      final ArrayList<String> invalidReasons =
945           new ArrayList<String>(1);
946      if (! entryValidator.entryIsValid(entry, invalidReasons))
947      {
948        return new LDAPMessage(messageID, new AddResponseProtocolOp(
949             ResultCode.OBJECT_CLASS_VIOLATION_INT_VALUE, null,
950             ERR_MEM_HANDLER_ADD_VIOLATES_SCHEMA.get(request.getDN(),
951                  StaticUtils.concatenateStrings(invalidReasons)), null));
952      }
953
954      if (! isInternalOp)
955      {
956        for (final Attribute a : entry.getAttributes())
957        {
958          final AttributeTypeDefinition at =
959               schema.getAttributeType(a.getBaseName());
960          if ((at != null) && at.isNoUserModification())
961          {
962            return new LDAPMessage(messageID, new AddResponseProtocolOp(
963                 ResultCode.CONSTRAINT_VIOLATION_INT_VALUE, null,
964                 ERR_MEM_HANDLER_ADD_CONTAINS_NO_USER_MOD.get(request.getDN(),
965                      a.getName()), null));
966          }
967        }
968      }
969    }
970
971    // If the entry contains a proxied authorization control, then process it.
972    final DN authzDN;
973    try
974    {
975      authzDN = handleProxiedAuthControl(controlMap);
976    }
977    catch (final LDAPException le)
978    {
979      Debug.debugException(le);
980      return new LDAPMessage(messageID, new AddResponseProtocolOp(
981           le.getResultCode().intValue(), null, le.getMessage(), null));
982    }
983
984    // Add a number of operational attributes to the entry.
985    if (generateOperationalAttributes)
986    {
987      final Date d = new Date();
988      if (! entry.hasAttribute("entryDN"))
989      {
990        entry.addAttribute(new Attribute("entryDN",
991             DistinguishedNameMatchingRule.getInstance(),
992             dn.toNormalizedString()));
993      }
994      if (! entry.hasAttribute("entryUUID"))
995      {
996        entry.addAttribute(new Attribute("entryUUID",
997             UUID.randomUUID().toString()));
998      }
999      if (! entry.hasAttribute("subschemaSubentry"))
1000      {
1001        entry.addAttribute(new Attribute("subschemaSubentry",
1002             DistinguishedNameMatchingRule.getInstance(),
1003             subschemaSubentryDN.toString()));
1004      }
1005      if (! entry.hasAttribute("creatorsName"))
1006      {
1007        entry.addAttribute(new Attribute("creatorsName",
1008             DistinguishedNameMatchingRule.getInstance(),
1009             authzDN.toString()));
1010      }
1011      if (! entry.hasAttribute("createTimestamp"))
1012      {
1013        entry.addAttribute(new Attribute("createTimestamp",
1014             GeneralizedTimeMatchingRule.getInstance(),
1015             StaticUtils.encodeGeneralizedTime(d)));
1016      }
1017      if (! entry.hasAttribute("modifiersName"))
1018      {
1019        entry.addAttribute(new Attribute("modifiersName",
1020             DistinguishedNameMatchingRule.getInstance(),
1021             authzDN.toString()));
1022      }
1023      if (! entry.hasAttribute("modifyTimestamp"))
1024      {
1025        entry.addAttribute(new Attribute("modifyTimestamp",
1026             GeneralizedTimeMatchingRule.getInstance(),
1027             StaticUtils.encodeGeneralizedTime(d)));
1028      }
1029    }
1030
1031    // If the request includes the assertion request control, then check it now.
1032    try
1033    {
1034      handleAssertionRequestControl(controlMap, entry);
1035    }
1036    catch (final LDAPException le)
1037    {
1038      Debug.debugException(le);
1039      return new LDAPMessage(messageID, new AddResponseProtocolOp(
1040           le.getResultCode().intValue(), null, le.getMessage(), null));
1041    }
1042
1043    // If the request includes the post-read request control, then create the
1044    // appropriate response control.
1045    final PostReadResponseControl postReadResponse =
1046         handlePostReadControl(controlMap, entry);
1047    if (postReadResponse != null)
1048    {
1049      responseControls.add(postReadResponse);
1050    }
1051
1052    // See if the entry DN is one of the defined base DNs.  If so, then we can
1053    // add the entry.
1054    if (baseDNs.contains(dn))
1055    {
1056      entryMap.put(dn, new ReadOnlyEntry(entry));
1057      indexAdd(entry);
1058      addChangeLogEntry(request, authzDN);
1059      return new LDAPMessage(messageID,
1060           new AddResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, null,
1061                null),
1062           responseControls);
1063    }
1064
1065    // See if the parent entry exists.  If so, then we can add the entry.
1066    final DN parentDN = dn.getParent();
1067    if ((parentDN != null) && entryMap.containsKey(parentDN))
1068    {
1069      entryMap.put(dn, new ReadOnlyEntry(entry));
1070      indexAdd(entry);
1071      addChangeLogEntry(request, authzDN);
1072      return new LDAPMessage(messageID,
1073           new AddResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, null,
1074                null),
1075           responseControls);
1076    }
1077
1078    // The add attempt must fail.
1079    return new LDAPMessage(messageID, new AddResponseProtocolOp(
1080         ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn),
1081         ERR_MEM_HANDLER_ADD_MISSING_PARENT.get(request.getDN(),
1082              dn.getParentString()),
1083         null));
1084  }
1085
1086
1087
1088  /**
1089   * Attempts to process the provided bind request.  The attempt will fail if
1090   * any of the following conditions is true:
1091   * <UL>
1092   *   <LI>There is a problem with any of the request controls.</LI>
1093   *   <LI>The bind request is not a simple bind request.</LI>
1094   *   <LI>The bind request contains a malformed bind DN.</LI>
1095   *   <LI>The bind DN is not the null DN and is not the DN of any entry in the
1096   *       data set.</LI>
1097   *   <LI>The bind password is empty and the bind DN is not the null DN.</LI>
1098   *   <LI>The target user does not have a userPassword value that matches the
1099   *       provided bind password.</LI>
1100   * </UL>
1101   *
1102   * @param  messageID  The message ID of the LDAP message containing the bind
1103   *                    request.
1104   * @param  request    The bind request that was included in the LDAP message
1105   *                    that was received.
1106   * @param  controls   The set of controls included in the LDAP message.  It
1107   *                    may be empty if there were no controls, but will not be
1108   *                    {@code null}.
1109   *
1110   * @return  The {@link LDAPMessage} containing the response to send to the
1111   *          client.  The protocol op in the {@code LDAPMessage} must be a
1112   *          {@code BindResponseProtocolOp}.
1113   */
1114  @Override()
1115  public synchronized LDAPMessage processBindRequest(final int messageID,
1116                                       final BindRequestProtocolOp request,
1117                                       final List<Control> controls)
1118  {
1119    // Sleep before processing, if appropriate.
1120    sleepBeforeProcessing();
1121
1122    // If this operation type is not allowed, then reject it.
1123    if (! config.getAllowedOperationTypes().contains(OperationType.BIND))
1124    {
1125      return new LDAPMessage(messageID, new BindResponseProtocolOp(
1126           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1127           ERR_MEM_HANDLER_BIND_NOT_ALLOWED.get(), null, null));
1128    }
1129
1130
1131    authenticatedDN = DN.NULL_DN;
1132
1133
1134    // If this operation type requires authentication and it is a simple bind
1135    // request , then ensure that the request includes credentials.
1136    if ((authenticatedDN.isNullDN() &&
1137        config.getAuthenticationRequiredOperationTypes().contains(
1138             OperationType.BIND)))
1139    {
1140      if ((request.getCredentialsType() ==
1141           BindRequestProtocolOp.CRED_TYPE_SIMPLE) &&
1142           ((request.getSimplePassword() == null) ||
1143                request.getSimplePassword().getValueLength() == 0))
1144      {
1145        return new LDAPMessage(messageID, new BindResponseProtocolOp(
1146             ResultCode.INVALID_CREDENTIALS_INT_VALUE, null,
1147             ERR_MEM_HANDLER_BIND_REQUIRES_AUTH.get(), null, null));
1148      }
1149    }
1150
1151
1152    // Get the parsed bind DN.
1153    final DN bindDN;
1154    try
1155    {
1156      bindDN = new DN(request.getBindDN(), schemaRef.get());
1157    }
1158    catch (final LDAPException le)
1159    {
1160      Debug.debugException(le);
1161      return new LDAPMessage(messageID, new BindResponseProtocolOp(
1162           ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
1163           ERR_MEM_HANDLER_BIND_MALFORMED_DN.get(request.getBindDN(),
1164                le.getMessage()),
1165           null, null));
1166    }
1167
1168    // If the bind request is for a SASL bind, then see if there is a SASL
1169    // mechanism handler that can be used to process it.
1170    if (request.getCredentialsType() == BindRequestProtocolOp.CRED_TYPE_SASL)
1171    {
1172      final String mechanism = request.getSASLMechanism();
1173      final InMemorySASLBindHandler handler = saslBindHandlers.get(mechanism);
1174      if (handler == null)
1175      {
1176        return new LDAPMessage(messageID, new BindResponseProtocolOp(
1177             ResultCode.AUTH_METHOD_NOT_SUPPORTED_INT_VALUE, null,
1178             ERR_MEM_HANDLER_SASL_MECH_NOT_SUPPORTED.get(mechanism), null,
1179             null));
1180      }
1181
1182      try
1183      {
1184        final BindResult bindResult = handler.processSASLBind(this, messageID,
1185             bindDN, request.getSASLCredentials(), controls);
1186
1187        // If the SASL bind was successful but the connection is
1188        // unauthenticated, then see if we allow that.
1189        if ((bindResult.getResultCode() == ResultCode.SUCCESS) &&
1190            (authenticatedDN == DN.NULL_DN) &&
1191            config.getAuthenticationRequiredOperationTypes().contains(
1192                 OperationType.BIND))
1193        {
1194          return new LDAPMessage(messageID, new BindResponseProtocolOp(
1195               ResultCode.INVALID_CREDENTIALS_INT_VALUE, null,
1196               ERR_MEM_HANDLER_BIND_REQUIRES_AUTH.get(), null, null));
1197        }
1198
1199        return new LDAPMessage(messageID, new BindResponseProtocolOp(
1200             bindResult.getResultCode().intValue(),
1201             bindResult.getMatchedDN(), bindResult.getDiagnosticMessage(),
1202             Arrays.asList(bindResult.getReferralURLs()),
1203             bindResult.getServerSASLCredentials()),
1204             Arrays.asList(bindResult.getResponseControls()));
1205      }
1206      catch (final Exception e)
1207      {
1208        Debug.debugException(e);
1209        return new LDAPMessage(messageID, new BindResponseProtocolOp(
1210             ResultCode.OTHER_INT_VALUE, null,
1211             ERR_MEM_HANDLER_SASL_BIND_FAILURE.get(
1212                  StaticUtils.getExceptionMessage(e)),
1213             null, null));
1214      }
1215    }
1216
1217    // If we've gotten here, then the bind must use simple authentication.
1218    // Process the provided request controls.
1219    final Map<String,Control> controlMap;
1220    try
1221    {
1222      controlMap = RequestControlPreProcessor.processControls(
1223           LDAPMessage.PROTOCOL_OP_TYPE_BIND_REQUEST, controls);
1224    }
1225    catch (final LDAPException le)
1226    {
1227      Debug.debugException(le);
1228      return new LDAPMessage(messageID, new BindResponseProtocolOp(
1229           le.getResultCode().intValue(), null, le.getMessage(), null, null));
1230    }
1231    final ArrayList<Control> responseControls = new ArrayList<Control>(1);
1232
1233    // If the bind DN is the null DN, then the bind will be considered
1234    // successful as long as the password is also empty.
1235    final ASN1OctetString bindPassword = request.getSimplePassword();
1236    if (bindDN.isNullDN())
1237    {
1238      if (bindPassword.getValueLength() == 0)
1239      {
1240        if (controlMap.containsKey(AuthorizationIdentityRequestControl.
1241             AUTHORIZATION_IDENTITY_REQUEST_OID))
1242        {
1243          responseControls.add(new AuthorizationIdentityResponseControl(""));
1244        }
1245        return new LDAPMessage(messageID,
1246             new BindResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null,
1247                  null, null, null),
1248             responseControls);
1249      }
1250      else
1251      {
1252        return new LDAPMessage(messageID, new BindResponseProtocolOp(
1253             ResultCode.INVALID_CREDENTIALS_INT_VALUE,
1254             getMatchedDNString(bindDN),
1255             ERR_MEM_HANDLER_BIND_WRONG_PASSWORD.get(request.getBindDN()), null,
1256             null));
1257      }
1258    }
1259
1260    // If the bind DN is not null and the password is empty, then reject the
1261    // request.
1262    if ((! bindDN.isNullDN()) && (bindPassword.getValueLength() == 0))
1263    {
1264      return new LDAPMessage(messageID, new BindResponseProtocolOp(
1265           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1266           ERR_MEM_HANDLER_BIND_SIMPLE_DN_WITHOUT_PASSWORD.get(), null, null));
1267    }
1268
1269    // See if the bind DN is in the set of additional bind credentials.  If so,
1270    // then use the password there.
1271    final byte[] additionalCreds = additionalBindCredentials.get(bindDN);
1272    if (additionalCreds != null)
1273    {
1274      if (Arrays.equals(additionalCreds, bindPassword.getValue()))
1275      {
1276        authenticatedDN = bindDN;
1277        if (controlMap.containsKey(AuthorizationIdentityRequestControl.
1278             AUTHORIZATION_IDENTITY_REQUEST_OID))
1279        {
1280          responseControls.add(new AuthorizationIdentityResponseControl(
1281               "dn:" + bindDN.toString()));
1282        }
1283        return new LDAPMessage(messageID,
1284             new BindResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null,
1285                  null, null, null),
1286             responseControls);
1287      }
1288      else
1289      {
1290        return new LDAPMessage(messageID, new BindResponseProtocolOp(
1291             ResultCode.INVALID_CREDENTIALS_INT_VALUE,
1292             getMatchedDNString(bindDN),
1293             ERR_MEM_HANDLER_BIND_WRONG_PASSWORD.get(request.getBindDN()), null,
1294             null));
1295      }
1296    }
1297
1298    // If the target user doesn't exist, then reject the request.
1299    final Entry userEntry = entryMap.get(bindDN);
1300    if (userEntry == null)
1301    {
1302      return new LDAPMessage(messageID, new BindResponseProtocolOp(
1303           ResultCode.INVALID_CREDENTIALS_INT_VALUE, getMatchedDNString(bindDN),
1304           ERR_MEM_HANDLER_BIND_NO_SUCH_USER.get(request.getBindDN()), null,
1305           null));
1306    }
1307
1308    // If the user entry has a userPassword value that matches the provided
1309    // password, then the bind will be successful.  Otherwise, it will fail.
1310    if (userEntry.hasAttributeValue("userPassword", bindPassword.getValue(),
1311             OctetStringMatchingRule.getInstance()))
1312    {
1313      authenticatedDN = bindDN;
1314      if (controlMap.containsKey(AuthorizationIdentityRequestControl.
1315           AUTHORIZATION_IDENTITY_REQUEST_OID))
1316      {
1317        responseControls.add(new AuthorizationIdentityResponseControl(
1318             "dn:" + bindDN.toString()));
1319      }
1320      return new LDAPMessage(messageID,
1321           new BindResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, null,
1322                null, null),
1323           responseControls);
1324    }
1325    else
1326    {
1327      return new LDAPMessage(messageID, new BindResponseProtocolOp(
1328           ResultCode.INVALID_CREDENTIALS_INT_VALUE, getMatchedDNString(bindDN),
1329           ERR_MEM_HANDLER_BIND_WRONG_PASSWORD.get(request.getBindDN()), null,
1330           null));
1331    }
1332  }
1333
1334
1335
1336  /**
1337   * Attempts to process the provided compare request.  The attempt will fail if
1338   * any of the following conditions is true:
1339   * <UL>
1340   *   <LI>There is a problem with any of the request controls.</LI>
1341   *   <LI>The compare request contains a malformed target DN.</LI>
1342   *   <LI>The target entry does not exist.</LI>
1343   * </UL>
1344   *
1345   * @param  messageID  The message ID of the LDAP message containing the
1346   *                    compare request.
1347   * @param  request    The compare request that was included in the LDAP
1348   *                    message that was received.
1349   * @param  controls   The set of controls included in the LDAP message.  It
1350   *                    may be empty if there were no controls, but will not be
1351   *                    {@code null}.
1352   *
1353   * @return  The {@link LDAPMessage} containing the response to send to the
1354   *          client.  The protocol op in the {@code LDAPMessage} must be a
1355   *          {@code CompareResponseProtocolOp}.
1356   */
1357  @Override()
1358  public synchronized LDAPMessage processCompareRequest(final int messageID,
1359                                       final CompareRequestProtocolOp request,
1360                                       final List<Control> controls)
1361  {
1362    // Sleep before processing, if appropriate.
1363    sleepBeforeProcessing();
1364
1365    // Process the provided request controls.
1366    final Map<String,Control> controlMap;
1367    try
1368    {
1369      controlMap = RequestControlPreProcessor.processControls(
1370           LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST, controls);
1371    }
1372    catch (final LDAPException le)
1373    {
1374      Debug.debugException(le);
1375      return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1376           le.getResultCode().intValue(), null, le.getMessage(), null));
1377    }
1378    final ArrayList<Control> responseControls = new ArrayList<Control>(1);
1379
1380
1381    // If this operation type is not allowed, then reject it.
1382    final boolean isInternalOp =
1383         controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
1384    if ((! isInternalOp) &&
1385        (! config.getAllowedOperationTypes().contains(OperationType.COMPARE)))
1386    {
1387      return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1388           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1389           ERR_MEM_HANDLER_COMPARE_NOT_ALLOWED.get(), null));
1390    }
1391
1392
1393    // If this operation type requires authentication, then ensure that the
1394    // client is authenticated.
1395    if ((authenticatedDN.isNullDN() &&
1396        config.getAuthenticationRequiredOperationTypes().contains(
1397             OperationType.COMPARE)))
1398    {
1399      return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1400           ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
1401           ERR_MEM_HANDLER_COMPARE_REQUIRES_AUTH.get(), null));
1402    }
1403
1404
1405    // Get the parsed target DN.
1406    final DN dn;
1407    try
1408    {
1409      dn = new DN(request.getDN(), schemaRef.get());
1410    }
1411    catch (final LDAPException le)
1412    {
1413      Debug.debugException(le);
1414      return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1415           ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
1416           ERR_MEM_HANDLER_COMPARE_MALFORMED_DN.get(request.getDN(),
1417                le.getMessage()),
1418           null));
1419    }
1420
1421    // See if the target entry or one of its superiors is a smart referral.
1422    if (! controlMap.containsKey(
1423               ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID))
1424    {
1425      final Entry referralEntry = findNearestReferral(dn);
1426      if (referralEntry != null)
1427      {
1428        return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1429             ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
1430             INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
1431             getReferralURLs(dn, referralEntry)));
1432      }
1433    }
1434
1435    // Get the target entry (optionally checking for the root DSE or subschema
1436    // subentry).  If it does not exist, then fail.
1437    final Entry entry;
1438    if (dn.isNullDN())
1439    {
1440      entry = generateRootDSE();
1441    }
1442    else if (dn.equals(subschemaSubentryDN))
1443    {
1444      entry = subschemaSubentryRef.get();
1445    }
1446    else
1447    {
1448      entry = entryMap.get(dn);
1449    }
1450    if (entry == null)
1451    {
1452      return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1453           ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn),
1454           ERR_MEM_HANDLER_COMPARE_NO_SUCH_ENTRY.get(request.getDN()), null));
1455    }
1456
1457    // If the request includes an assertion or proxied authorization control,
1458    // then perform the appropriate processing.
1459    try
1460    {
1461      handleAssertionRequestControl(controlMap, entry);
1462      handleProxiedAuthControl(controlMap);
1463    }
1464    catch (final LDAPException le)
1465    {
1466      Debug.debugException(le);
1467      return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1468           le.getResultCode().intValue(), null, le.getMessage(), null));
1469    }
1470
1471    // See if the entry contains the assertion value.
1472    final int resultCode;
1473    if (entry.hasAttributeValue(request.getAttributeName(),
1474             request.getAssertionValue().getValue()))
1475    {
1476      resultCode = ResultCode.COMPARE_TRUE_INT_VALUE;
1477    }
1478    else
1479    {
1480      resultCode = ResultCode.COMPARE_FALSE_INT_VALUE;
1481    }
1482    return new LDAPMessage(messageID,
1483         new CompareResponseProtocolOp(resultCode, null, null, null),
1484         responseControls);
1485  }
1486
1487
1488
1489  /**
1490   * Attempts to process the provided delete request.  The attempt will fail if
1491   * any of the following conditions is true:
1492   * <UL>
1493   *   <LI>There is a problem with any of the request controls.</LI>
1494   *   <LI>The delete request contains a malformed target DN.</LI>
1495   *   <LI>The target entry is the root DSE.</LI>
1496   *   <LI>The target entry is the subschema subentry.</LI>
1497   *   <LI>The target entry is at or below the changelog base entry.</LI>
1498   *   <LI>The target entry does not exist.</LI>
1499   *   <LI>The target entry has one or more subordinate entries.</LI>
1500   * </UL>
1501   *
1502   * @param  messageID  The message ID of the LDAP message containing the delete
1503   *                    request.
1504   * @param  request    The delete request that was included in the LDAP message
1505   *                    that was received.
1506   * @param  controls   The set of controls included in the LDAP message.  It
1507   *                    may be empty if there were no controls, but will not be
1508   *                    {@code null}.
1509   *
1510   * @return  The {@link LDAPMessage} containing the response to send to the
1511   *          client.  The protocol op in the {@code LDAPMessage} must be a
1512   *          {@code DeleteResponseProtocolOp}.
1513   */
1514  @Override()
1515  public synchronized LDAPMessage processDeleteRequest(final int messageID,
1516                                       final DeleteRequestProtocolOp request,
1517                                       final List<Control> controls)
1518  {
1519    // Sleep before processing, if appropriate.
1520    sleepBeforeProcessing();
1521
1522    // Process the provided request controls.
1523    final Map<String,Control> controlMap;
1524    try
1525    {
1526      controlMap = RequestControlPreProcessor.processControls(
1527           LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST, controls);
1528    }
1529    catch (final LDAPException le)
1530    {
1531      Debug.debugException(le);
1532      return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1533           le.getResultCode().intValue(), null, le.getMessage(), null));
1534    }
1535    final ArrayList<Control> responseControls = new ArrayList<Control>(1);
1536
1537
1538    // If this operation type is not allowed, then reject it.
1539    final boolean isInternalOp =
1540         controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
1541    if ((! isInternalOp) &&
1542        (! config.getAllowedOperationTypes().contains(OperationType.DELETE)))
1543    {
1544      return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1545           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1546           ERR_MEM_HANDLER_DELETE_NOT_ALLOWED.get(), null));
1547    }
1548
1549
1550    // If this operation type requires authentication, then ensure that the
1551    // client is authenticated.
1552    if ((authenticatedDN.isNullDN() &&
1553        config.getAuthenticationRequiredOperationTypes().contains(
1554             OperationType.DELETE)))
1555    {
1556      return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1557           ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
1558           ERR_MEM_HANDLER_DELETE_REQUIRES_AUTH.get(), null));
1559    }
1560
1561
1562    // See if this delete request is part of a transaction.  If so, then perform
1563    // appropriate processing for it and return success immediately without
1564    // actually doing any further processing.
1565    try
1566    {
1567      final ASN1OctetString txnID =
1568           processTransactionRequest(messageID, request, controlMap);
1569      if (txnID != null)
1570      {
1571        return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1572             ResultCode.SUCCESS_INT_VALUE, null,
1573             INFO_MEM_HANDLER_OP_IN_TXN.get(txnID.stringValue()), null));
1574      }
1575    }
1576    catch (final LDAPException le)
1577    {
1578      Debug.debugException(le);
1579      return new LDAPMessage(messageID,
1580           new DeleteResponseProtocolOp(le.getResultCode().intValue(),
1581                le.getMatchedDN(), le.getDiagnosticMessage(),
1582                StaticUtils.toList(le.getReferralURLs())),
1583           le.getResponseControls());
1584    }
1585
1586
1587    // Get the parsed target DN.
1588    final DN dn;
1589    try
1590    {
1591      dn = new DN(request.getDN(), schemaRef.get());
1592    }
1593    catch (final LDAPException le)
1594    {
1595      Debug.debugException(le);
1596      return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1597           ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
1598           ERR_MEM_HANDLER_DELETE_MALFORMED_DN.get(request.getDN(),
1599                le.getMessage()),
1600           null));
1601    }
1602
1603    // See if the target entry or one of its superiors is a smart referral.
1604    if (! controlMap.containsKey(
1605               ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID))
1606    {
1607      final Entry referralEntry = findNearestReferral(dn);
1608      if (referralEntry != null)
1609      {
1610        return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1611             ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
1612             INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
1613             getReferralURLs(dn, referralEntry)));
1614      }
1615    }
1616
1617    // Make sure the target entry isn't the root DSE or schema, or a changelog
1618    // entry.
1619    if (dn.isNullDN())
1620    {
1621      return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1622           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1623           ERR_MEM_HANDLER_DELETE_ROOT_DSE.get(), null));
1624    }
1625    else if (dn.equals(subschemaSubentryDN))
1626    {
1627      return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1628           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1629           ERR_MEM_HANDLER_DELETE_SCHEMA.get(subschemaSubentryDN.toString()),
1630           null));
1631    }
1632    else if (dn.isDescendantOf(changeLogBaseDN, true))
1633    {
1634      return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1635           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1636           ERR_MEM_HANDLER_DELETE_CHANGELOG.get(request.getDN()), null));
1637    }
1638
1639    // Get the target entry.  If it does not exist, then fail.
1640    final Entry entry = entryMap.get(dn);
1641    if (entry == null)
1642    {
1643      return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1644           ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn),
1645           ERR_MEM_HANDLER_DELETE_NO_SUCH_ENTRY.get(request.getDN()), null));
1646    }
1647
1648    // Create a list with the DN of the target entry, and all the DNs of its
1649    // subordinates.  If the entry has subordinates and the subtree delete
1650    // control was not provided, then fail.
1651    final ArrayList<DN> subordinateDNs = new ArrayList<DN>(entryMap.size());
1652    for (final DN mapEntryDN : entryMap.keySet())
1653    {
1654      if (mapEntryDN.isDescendantOf(dn, false))
1655      {
1656        subordinateDNs.add(mapEntryDN);
1657      }
1658    }
1659
1660    if ((! subordinateDNs.isEmpty()) &&
1661        (! controlMap.containsKey(
1662               SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID)))
1663    {
1664      return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1665           ResultCode.NOT_ALLOWED_ON_NONLEAF_INT_VALUE, null,
1666           ERR_MEM_HANDLER_DELETE_HAS_SUBORDINATES.get(request.getDN()),
1667           null));
1668    }
1669
1670    // Handle the necessary processing for the assertion, pre-read, and proxied
1671    // auth controls.
1672    final DN authzDN;
1673    try
1674    {
1675      handleAssertionRequestControl(controlMap, entry);
1676
1677      final PreReadResponseControl preReadResponse =
1678           handlePreReadControl(controlMap, entry);
1679      if (preReadResponse != null)
1680      {
1681        responseControls.add(preReadResponse);
1682      }
1683
1684      authzDN = handleProxiedAuthControl(controlMap);
1685    }
1686    catch (final LDAPException le)
1687    {
1688      Debug.debugException(le);
1689      return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1690           le.getResultCode().intValue(), null, le.getMessage(), null));
1691    }
1692
1693    // At this point, the entry will be removed.  However, if this will be a
1694    // subtree delete, then we want to delete all of its subordinates first so
1695    // that the changelog will show the deletes in the appropriate order.
1696    for (int i=(subordinateDNs.size() - 1); i >= 0; i--)
1697    {
1698      final DN subordinateDN = subordinateDNs.get(i);
1699      final Entry subEntry = entryMap.remove(subordinateDN);
1700      indexDelete(subEntry);
1701      addDeleteChangeLogEntry(subEntry, authzDN);
1702      handleReferentialIntegrityDelete(subordinateDN);
1703    }
1704
1705    // Finally, remove the target entry and create a changelog entry for it.
1706    entryMap.remove(dn);
1707    indexDelete(entry);
1708    addDeleteChangeLogEntry(entry, authzDN);
1709    handleReferentialIntegrityDelete(dn);
1710
1711    return new LDAPMessage(messageID,
1712         new DeleteResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, null,
1713              null),
1714         responseControls);
1715  }
1716
1717
1718
1719  /**
1720   * Handles any appropriate referential integrity processing for a delete
1721   * operation.
1722   *
1723   * @param  dn  The DN of the entry that has been deleted.
1724   */
1725  private void handleReferentialIntegrityDelete(final DN dn)
1726  {
1727    if (referentialIntegrityAttributes.isEmpty())
1728    {
1729      return;
1730    }
1731
1732    final ArrayList<DN> entryDNs = new ArrayList<DN>(entryMap.keySet());
1733    for (final DN mapDN : entryDNs)
1734    {
1735      final ReadOnlyEntry e = entryMap.get(mapDN);
1736
1737      boolean referenceFound = false;
1738      final Schema schema = schemaRef.get();
1739      for (final String attrName : referentialIntegrityAttributes)
1740      {
1741        final Attribute a = e.getAttribute(attrName, schema);
1742        if ((a != null) &&
1743            a.hasValue(dn.toNormalizedString(),
1744                 DistinguishedNameMatchingRule.getInstance()))
1745        {
1746          referenceFound = true;
1747          break;
1748        }
1749      }
1750
1751      if (referenceFound)
1752      {
1753        final Entry copy = e.duplicate();
1754        for (final String attrName : referentialIntegrityAttributes)
1755        {
1756          copy.removeAttributeValue(attrName, dn.toNormalizedString(),
1757               DistinguishedNameMatchingRule.getInstance());
1758        }
1759        entryMap.put(mapDN, new ReadOnlyEntry(copy));
1760        indexDelete(e);
1761        indexAdd(copy);
1762      }
1763    }
1764  }
1765
1766
1767
1768  /**
1769   * Attempts to process the provided extended request, if an extended operation
1770   * handler is defined for the given request OID.
1771   *
1772   * @param  messageID  The message ID of the LDAP message containing the
1773   *                    extended request.
1774   * @param  request    The extended request that was included in the LDAP
1775   *                    message that was received.
1776   * @param  controls   The set of controls included in the LDAP message.  It
1777   *                    may be empty if there were no controls, but will not be
1778   *                    {@code null}.
1779   *
1780   * @return  The {@link LDAPMessage} containing the response to send to the
1781   *          client.  The protocol op in the {@code LDAPMessage} must be an
1782   *          {@code ExtendedResponseProtocolOp}.
1783   */
1784  @Override()
1785  public synchronized LDAPMessage processExtendedRequest(final int messageID,
1786                                       final ExtendedRequestProtocolOp request,
1787                                       final List<Control> controls)
1788  {
1789    // Sleep before processing, if appropriate.
1790    sleepBeforeProcessing();
1791
1792    boolean isInternalOp = false;
1793    for (final Control c : controls)
1794    {
1795      if (c.getOID().equals(OID_INTERNAL_OPERATION_REQUEST_CONTROL))
1796      {
1797        isInternalOp = true;
1798        break;
1799      }
1800    }
1801
1802
1803    // If this operation type is not allowed, then reject it.
1804    if ((! isInternalOp) &&
1805        (! config.getAllowedOperationTypes().contains(OperationType.EXTENDED)))
1806    {
1807      return new LDAPMessage(messageID, new ExtendedResponseProtocolOp(
1808           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1809           ERR_MEM_HANDLER_EXTENDED_NOT_ALLOWED.get(), null, null, null));
1810    }
1811
1812
1813    // If this operation type requires authentication, then ensure that the
1814    // client is authenticated.
1815    if ((authenticatedDN.isNullDN() &&
1816        config.getAuthenticationRequiredOperationTypes().contains(
1817             OperationType.EXTENDED)))
1818    {
1819      return new LDAPMessage(messageID, new ExtendedResponseProtocolOp(
1820           ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
1821           ERR_MEM_HANDLER_EXTENDED_REQUIRES_AUTH.get(), null, null, null));
1822    }
1823
1824
1825    final String oid = request.getOID();
1826    final InMemoryExtendedOperationHandler handler =
1827         extendedRequestHandlers.get(oid);
1828    if (handler == null)
1829    {
1830      return new LDAPMessage(messageID, new ExtendedResponseProtocolOp(
1831           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1832           ERR_MEM_HANDLER_EXTENDED_OP_NOT_SUPPORTED.get(oid), null, null,
1833           null));
1834    }
1835
1836    try
1837    {
1838      final Control[] controlArray = new Control[controls.size()];
1839      controls.toArray(controlArray);
1840
1841      final ExtendedRequest extendedRequest = new ExtendedRequest(oid,
1842           request.getValue(), controlArray);
1843
1844      final ExtendedResult extendedResult =
1845           handler.processExtendedOperation(this, messageID, extendedRequest);
1846
1847      return new LDAPMessage(messageID,
1848           new ExtendedResponseProtocolOp(
1849                extendedResult.getResultCode().intValue(),
1850                extendedResult.getMatchedDN(),
1851                extendedResult.getDiagnosticMessage(),
1852                Arrays.asList(extendedResult.getReferralURLs()),
1853                extendedResult.getOID(), extendedResult.getValue()),
1854           extendedResult.getResponseControls());
1855    }
1856    catch (final Exception e)
1857    {
1858      Debug.debugException(e);
1859
1860      return new LDAPMessage(messageID, new ExtendedResponseProtocolOp(
1861           ResultCode.OTHER_INT_VALUE, null,
1862           ERR_MEM_HANDLER_EXTENDED_OP_FAILURE.get(
1863                StaticUtils.getExceptionMessage(e)),
1864           null, null, null));
1865    }
1866  }
1867
1868
1869
1870  /**
1871   * Attempts to process the provided modify request.  The attempt will fail if
1872   * any of the following conditions is true:
1873   * <UL>
1874   *   <LI>There is a problem with any of the request controls.</LI>
1875   *   <LI>The modify request contains a malformed target DN.</LI>
1876   *   <LI>The target entry is the root DSE.</LI>
1877   *   <LI>The target entry is the subschema subentry.</LI>
1878   *   <LI>The target entry does not exist.</LI>
1879   *   <LI>Any of the modifications cannot be applied to the entry.</LI>
1880   *   <LI>If a schema was provided, and the entry violates any of the
1881   *       constraints of that schema.</LI>
1882   * </UL>
1883   *
1884   * @param  messageID  The message ID of the LDAP message containing the modify
1885   *                    request.
1886   * @param  request    The modify request that was included in the LDAP message
1887   *                    that was received.
1888   * @param  controls   The set of controls included in the LDAP message.  It
1889   *                    may be empty if there were no controls, but will not be
1890   *                    {@code null}.
1891   *
1892   * @return  The {@link LDAPMessage} containing the response to send to the
1893   *          client.  The protocol op in the {@code LDAPMessage} must be an
1894   *          {@code ModifyResponseProtocolOp}.
1895   */
1896  @Override()
1897  public synchronized LDAPMessage processModifyRequest(final int messageID,
1898                                       final ModifyRequestProtocolOp request,
1899                                       final List<Control> controls)
1900  {
1901    // Sleep before processing, if appropriate.
1902    sleepBeforeProcessing();
1903
1904    // Process the provided request controls.
1905    final Map<String,Control> controlMap;
1906    try
1907    {
1908      controlMap = RequestControlPreProcessor.processControls(
1909           LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST, controls);
1910    }
1911    catch (final LDAPException le)
1912    {
1913      Debug.debugException(le);
1914      return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1915           le.getResultCode().intValue(), null, le.getMessage(), null));
1916    }
1917    final ArrayList<Control> responseControls = new ArrayList<Control>(1);
1918
1919
1920    // If this operation type is not allowed, then reject it.
1921    final boolean isInternalOp =
1922         controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
1923    if ((! isInternalOp) &&
1924        (! config.getAllowedOperationTypes().contains(OperationType.MODIFY)))
1925    {
1926      return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1927           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1928           ERR_MEM_HANDLER_MODIFY_NOT_ALLOWED.get(), null));
1929    }
1930
1931
1932    // If this operation type requires authentication, then ensure that the
1933    // client is authenticated.
1934    if ((authenticatedDN.isNullDN() &&
1935        config.getAuthenticationRequiredOperationTypes().contains(
1936             OperationType.MODIFY)))
1937    {
1938      return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1939           ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
1940           ERR_MEM_HANDLER_MODIFY_REQUIRES_AUTH.get(), null));
1941    }
1942
1943
1944    // See if this modify request is part of a transaction.  If so, then perform
1945    // appropriate processing for it and return success immediately without
1946    // actually doing any further processing.
1947    try
1948    {
1949      final ASN1OctetString txnID =
1950           processTransactionRequest(messageID, request, controlMap);
1951      if (txnID != null)
1952      {
1953        return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1954             ResultCode.SUCCESS_INT_VALUE, null,
1955             INFO_MEM_HANDLER_OP_IN_TXN.get(txnID.stringValue()), null));
1956      }
1957    }
1958    catch (final LDAPException le)
1959    {
1960      Debug.debugException(le);
1961      return new LDAPMessage(messageID,
1962           new ModifyResponseProtocolOp(le.getResultCode().intValue(),
1963                le.getMatchedDN(), le.getDiagnosticMessage(),
1964                StaticUtils.toList(le.getReferralURLs())),
1965           le.getResponseControls());
1966    }
1967
1968
1969    // Get the parsed target DN.
1970    final DN dn;
1971    final Schema schema = schemaRef.get();
1972    try
1973    {
1974      dn = new DN(request.getDN(), schema);
1975    }
1976    catch (final LDAPException le)
1977    {
1978      Debug.debugException(le);
1979      return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1980           ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
1981           ERR_MEM_HANDLER_MOD_MALFORMED_DN.get(request.getDN(),
1982                le.getMessage()),
1983           null));
1984    }
1985
1986    // See if the target entry or one of its superiors is a smart referral.
1987    if (! controlMap.containsKey(
1988               ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID))
1989    {
1990      final Entry referralEntry = findNearestReferral(dn);
1991      if (referralEntry != null)
1992      {
1993        return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1994             ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
1995             INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
1996             getReferralURLs(dn, referralEntry)));
1997      }
1998    }
1999
2000    // See if the target entry is the root DSE, the subschema subentry, or a
2001    // changelog entry.
2002    if (dn.isNullDN())
2003    {
2004      return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
2005           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2006           ERR_MEM_HANDLER_MOD_ROOT_DSE.get(), null));
2007    }
2008    else if (dn.equals(subschemaSubentryDN))
2009    {
2010      try
2011      {
2012        validateSchemaMods(request);
2013      }
2014      catch (final LDAPException le)
2015      {
2016        return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
2017             le.getResultCode().intValue(), le.getMatchedDN(), le.getMessage(),
2018             null));
2019      }
2020    }
2021    else if (dn.isDescendantOf(changeLogBaseDN, true))
2022    {
2023      return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
2024           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2025           ERR_MEM_HANDLER_MOD_CHANGELOG.get(request.getDN()), null));
2026    }
2027
2028    // Get the target entry.  If it does not exist, then fail.
2029    Entry entry = entryMap.get(dn);
2030    if (entry == null)
2031    {
2032      if (dn.equals(subschemaSubentryDN))
2033      {
2034        entry = subschemaSubentryRef.get().duplicate();
2035      }
2036      else
2037      {
2038        return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
2039             ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn),
2040             ERR_MEM_HANDLER_MOD_NO_SUCH_ENTRY.get(request.getDN()), null));
2041      }
2042    }
2043
2044
2045    // Attempt to apply the modifications to the entry.  If successful, then a
2046    // copy of the entry will be returned with the modifications applied.
2047    final Entry modifiedEntry;
2048    try
2049    {
2050      modifiedEntry = Entry.applyModifications(entry,
2051           controlMap.containsKey(PermissiveModifyRequestControl.
2052                PERMISSIVE_MODIFY_REQUEST_OID),
2053           request.getModifications());
2054    }
2055    catch (final LDAPException le)
2056    {
2057      Debug.debugException(le);
2058      return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
2059           le.getResultCode().intValue(), null,
2060           ERR_MEM_HANDLER_MOD_FAILED.get(request.getDN(), le.getMessage()),
2061           null));
2062    }
2063
2064    // If a schema was provided, use it to validate the resulting entry.  Also,
2065    // ensure that no NO-USER-MODIFICATION attributes were targeted.
2066    final EntryValidator entryValidator = entryValidatorRef.get();
2067    if (entryValidator != null)
2068    {
2069      final ArrayList<String> invalidReasons = new ArrayList<String>(1);
2070      if (! entryValidator.entryIsValid(modifiedEntry, invalidReasons))
2071      {
2072        return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
2073             ResultCode.OBJECT_CLASS_VIOLATION_INT_VALUE, null,
2074             ERR_MEM_HANDLER_MOD_VIOLATES_SCHEMA.get(request.getDN(),
2075                  StaticUtils.concatenateStrings(invalidReasons)),
2076             null));
2077      }
2078
2079      for (final Modification m : request.getModifications())
2080      {
2081        final Attribute a = m.getAttribute();
2082        final String baseName = a.getBaseName();
2083        final AttributeTypeDefinition at = schema.getAttributeType(baseName);
2084        if ((! isInternalOp) && (at != null) && at.isNoUserModification())
2085        {
2086          return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
2087               ResultCode.CONSTRAINT_VIOLATION_INT_VALUE, null,
2088               ERR_MEM_HANDLER_MOD_NO_USER_MOD.get(request.getDN(),
2089                    a.getName()), null));
2090        }
2091      }
2092    }
2093
2094
2095    // Perform the appropriate processing for the assertion and proxied
2096    // authorization controls.
2097    // Perform the appropriate processing for the assertion, pre-read,
2098    // post-read, and proxied authorization controls.
2099    final DN authzDN;
2100    try
2101    {
2102      handleAssertionRequestControl(controlMap, entry);
2103
2104      authzDN = handleProxiedAuthControl(controlMap);
2105    }
2106    catch (final LDAPException le)
2107    {
2108      Debug.debugException(le);
2109      return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
2110           le.getResultCode().intValue(), null, le.getMessage(), null));
2111    }
2112
2113    // Update modifiersName and modifyTimestamp.
2114    if (generateOperationalAttributes)
2115    {
2116      modifiedEntry.setAttribute(new Attribute("modifiersName",
2117           DistinguishedNameMatchingRule.getInstance(),
2118           authzDN.toString()));
2119      modifiedEntry.setAttribute(new Attribute("modifyTimestamp",
2120           GeneralizedTimeMatchingRule.getInstance(),
2121           StaticUtils.encodeGeneralizedTime(new Date())));
2122    }
2123
2124    // Perform the appropriate processing for the pre-read and post-read
2125    // controls.
2126    final PreReadResponseControl preReadResponse =
2127         handlePreReadControl(controlMap, entry);
2128    if (preReadResponse != null)
2129    {
2130      responseControls.add(preReadResponse);
2131    }
2132
2133    final PostReadResponseControl postReadResponse =
2134         handlePostReadControl(controlMap, modifiedEntry);
2135    if (postReadResponse != null)
2136    {
2137      responseControls.add(postReadResponse);
2138    }
2139
2140
2141    // Replace the entry in the map and return a success result.
2142    if (dn.equals(subschemaSubentryDN))
2143    {
2144      final Schema newSchema = new Schema(modifiedEntry);
2145      subschemaSubentryRef.set(new ReadOnlyEntry(modifiedEntry));
2146      schemaRef.set(newSchema);
2147      entryValidatorRef.set(new EntryValidator(newSchema));
2148    }
2149    else
2150    {
2151      entryMap.put(dn, new ReadOnlyEntry(modifiedEntry));
2152      indexDelete(entry);
2153      indexAdd(modifiedEntry);
2154    }
2155    addChangeLogEntry(request, authzDN);
2156    return new LDAPMessage(messageID,
2157         new ModifyResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, null,
2158              null),
2159         responseControls);
2160  }
2161
2162
2163
2164  /**
2165   * Validates a modify request targeting the server schema.  Modifications to
2166   * attribute syntaxes and matching rules will not be allowed.  Modifications
2167   * to other schema elements will only be allowed for add and delete
2168   * modification types, and adds will only be allowed with a valid syntax.
2169   *
2170   * @param  request  The modify request to validate.
2171   *
2172   * @throws  LDAPException  If a problem is encountered.
2173   */
2174  private void validateSchemaMods(final ModifyRequestProtocolOp request)
2175          throws LDAPException
2176  {
2177    // If there is no schema, then we won't allow modifications at all.
2178    if (schemaRef.get() == null)
2179    {
2180      throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2181           ERR_MEM_HANDLER_MOD_SCHEMA.get(subschemaSubentryDN.toString()));
2182    }
2183
2184
2185    for (final Modification m : request.getModifications())
2186    {
2187      // If the modification targets attribute syntaxes or matching rules, then
2188      // reject it.
2189      final String attrName = m.getAttributeName();
2190      if (attrName.equalsIgnoreCase(Schema.ATTR_ATTRIBUTE_SYNTAX) ||
2191           attrName.equalsIgnoreCase(Schema.ATTR_MATCHING_RULE))
2192      {
2193        throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2194             ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_ATTR.get(attrName));
2195      }
2196      else if (attrName.equalsIgnoreCase(Schema.ATTR_ATTRIBUTE_TYPE))
2197      {
2198        if (m.getModificationType() == ModificationType.ADD)
2199        {
2200          for (final String value : m.getValues())
2201          {
2202            new AttributeTypeDefinition(value);
2203          }
2204        }
2205        else if (m.getModificationType() != ModificationType.DELETE)
2206        {
2207          throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2208               ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get(
2209                    m.getModificationType().getName(), attrName));
2210        }
2211      }
2212      else if (attrName.equalsIgnoreCase(Schema.ATTR_OBJECT_CLASS))
2213      {
2214        if (m.getModificationType() == ModificationType.ADD)
2215        {
2216          for (final String value : m.getValues())
2217          {
2218            new ObjectClassDefinition(value);
2219          }
2220        }
2221        else if (m.getModificationType() != ModificationType.DELETE)
2222        {
2223          throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2224               ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get(
2225                    m.getModificationType().getName(), attrName));
2226        }
2227      }
2228      else if (attrName.equalsIgnoreCase(Schema.ATTR_NAME_FORM))
2229      {
2230        if (m.getModificationType() == ModificationType.ADD)
2231        {
2232          for (final String value : m.getValues())
2233          {
2234            new NameFormDefinition(value);
2235          }
2236        }
2237        else if (m.getModificationType() != ModificationType.DELETE)
2238        {
2239          throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2240               ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get(
2241                    m.getModificationType().getName(), attrName));
2242        }
2243      }
2244      else if (attrName.equalsIgnoreCase(Schema.ATTR_DIT_CONTENT_RULE))
2245      {
2246        if (m.getModificationType() == ModificationType.ADD)
2247        {
2248          for (final String value : m.getValues())
2249          {
2250            new DITContentRuleDefinition(value);
2251          }
2252        }
2253        else if (m.getModificationType() != ModificationType.DELETE)
2254        {
2255          throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2256               ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get(
2257                    m.getModificationType().getName(), attrName));
2258        }
2259      }
2260      else if (attrName.equalsIgnoreCase(Schema.ATTR_DIT_STRUCTURE_RULE))
2261      {
2262        if (m.getModificationType() == ModificationType.ADD)
2263        {
2264          for (final String value : m.getValues())
2265          {
2266            new DITStructureRuleDefinition(value);
2267          }
2268        }
2269        else if (m.getModificationType() != ModificationType.DELETE)
2270        {
2271          throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2272               ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get(
2273                    m.getModificationType().getName(), attrName));
2274        }
2275      }
2276      else if (attrName.equalsIgnoreCase(Schema.ATTR_MATCHING_RULE_USE))
2277      {
2278        if (m.getModificationType() == ModificationType.ADD)
2279        {
2280          for (final String value : m.getValues())
2281          {
2282            new MatchingRuleUseDefinition(value);
2283          }
2284        }
2285        else if (m.getModificationType() != ModificationType.DELETE)
2286        {
2287          throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2288               ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get(
2289                    m.getModificationType().getName(), attrName));
2290        }
2291      }
2292    }
2293  }
2294
2295
2296
2297  /**
2298   * Attempts to process the provided modify DN request.  The attempt will fail
2299   * if any of the following conditions is true:
2300   * <UL>
2301   *   <LI>There is a problem with any of the request controls.</LI>
2302   *   <LI>The modify DN request contains a malformed target DN, new RDN, or
2303   *       new superior DN.</LI>
2304   *   <LI>The original or new DN is that of the root DSE.</LI>
2305   *   <LI>The original or new DN is that of the subschema subentry.</LI>
2306   *   <LI>The new DN of the entry would conflict with the DN of an existing
2307   *       entry.</LI>
2308   *   <LI>The new DN of the entry would exist outside the set of defined
2309   *       base DNs.</LI>
2310   *   <LI>The new DN of the entry is not a defined base DN and does not exist
2311   *       immediately below an existing entry.</LI>
2312   * </UL>
2313   *
2314   * @param  messageID  The message ID of the LDAP message containing the modify
2315   *                    DN request.
2316   * @param  request    The modify DN request that was included in the LDAP
2317   *                    message that was received.
2318   * @param  controls   The set of controls included in the LDAP message.  It
2319   *                    may be empty if there were no controls, but will not be
2320   *                    {@code null}.
2321   *
2322   * @return  The {@link LDAPMessage} containing the response to send to the
2323   *          client.  The protocol op in the {@code LDAPMessage} must be an
2324   *          {@code ModifyDNResponseProtocolOp}.
2325   */
2326  @Override()
2327  public synchronized LDAPMessage processModifyDNRequest(final int messageID,
2328                                       final ModifyDNRequestProtocolOp request,
2329                                       final List<Control> controls)
2330  {
2331    // Sleep before processing, if appropriate.
2332    sleepBeforeProcessing();
2333
2334    // Process the provided request controls.
2335    final Map<String,Control> controlMap;
2336    try
2337    {
2338      controlMap = RequestControlPreProcessor.processControls(
2339           LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST, controls);
2340    }
2341    catch (final LDAPException le)
2342    {
2343      Debug.debugException(le);
2344      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2345           le.getResultCode().intValue(), null, le.getMessage(), null));
2346    }
2347    final ArrayList<Control> responseControls = new ArrayList<Control>(1);
2348
2349
2350    // If this operation type is not allowed, then reject it.
2351    final boolean isInternalOp =
2352         controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
2353    if ((! isInternalOp) &&
2354        (! config.getAllowedOperationTypes().contains(OperationType.MODIFY_DN)))
2355    {
2356      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2357           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2358           ERR_MEM_HANDLER_MODIFY_DN_NOT_ALLOWED.get(), null));
2359    }
2360
2361
2362    // If this operation type requires authentication, then ensure that the
2363    // client is authenticated.
2364    if ((authenticatedDN.isNullDN() &&
2365        config.getAuthenticationRequiredOperationTypes().contains(
2366             OperationType.MODIFY_DN)))
2367    {
2368      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2369           ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
2370           ERR_MEM_HANDLER_MODIFY_DN_REQUIRES_AUTH.get(), null));
2371    }
2372
2373
2374    // See if this modify DN request is part of a transaction.  If so, then
2375    // perform appropriate processing for it and return success immediately
2376    // without actually doing any further processing.
2377    try
2378    {
2379      final ASN1OctetString txnID =
2380           processTransactionRequest(messageID, request, controlMap);
2381      if (txnID != null)
2382      {
2383        return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2384             ResultCode.SUCCESS_INT_VALUE, null,
2385             INFO_MEM_HANDLER_OP_IN_TXN.get(txnID.stringValue()), null));
2386      }
2387    }
2388    catch (final LDAPException le)
2389    {
2390      Debug.debugException(le);
2391      return new LDAPMessage(messageID,
2392           new ModifyDNResponseProtocolOp(le.getResultCode().intValue(),
2393                le.getMatchedDN(), le.getDiagnosticMessage(),
2394                StaticUtils.toList(le.getReferralURLs())),
2395           le.getResponseControls());
2396    }
2397
2398
2399    // Get the parsed target DN, new RDN, and new superior DN values.
2400    final DN dn;
2401    final Schema schema = schemaRef.get();
2402    try
2403    {
2404      dn = new DN(request.getDN(), schema);
2405    }
2406    catch (final LDAPException le)
2407    {
2408      Debug.debugException(le);
2409      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2410           ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
2411           ERR_MEM_HANDLER_MOD_DN_MALFORMED_DN.get(request.getDN(),
2412                le.getMessage()),
2413           null));
2414    }
2415
2416    final RDN newRDN;
2417    try
2418    {
2419      newRDN = new RDN(request.getNewRDN(), schema);
2420    }
2421    catch (final LDAPException le)
2422    {
2423      Debug.debugException(le);
2424      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2425           ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
2426           ERR_MEM_HANDLER_MOD_DN_MALFORMED_NEW_RDN.get(request.getDN(),
2427                request.getNewRDN(), le.getMessage()),
2428           null));
2429    }
2430
2431    final DN newSuperiorDN;
2432    final String newSuperiorString = request.getNewSuperiorDN();
2433    if (newSuperiorString == null)
2434    {
2435      newSuperiorDN = null;
2436    }
2437    else
2438    {
2439      try
2440      {
2441        newSuperiorDN = new DN(newSuperiorString, schema);
2442      }
2443      catch (final LDAPException le)
2444      {
2445        Debug.debugException(le);
2446        return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2447             ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
2448             ERR_MEM_HANDLER_MOD_DN_MALFORMED_NEW_SUPERIOR.get(request.getDN(),
2449                  request.getNewSuperiorDN(), le.getMessage()),
2450             null));
2451      }
2452    }
2453
2454    // See if the target entry or one of its superiors is a smart referral.
2455    if (! controlMap.containsKey(
2456               ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID))
2457    {
2458      final Entry referralEntry = findNearestReferral(dn);
2459      if (referralEntry != null)
2460      {
2461        return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2462             ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
2463             INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
2464             getReferralURLs(dn, referralEntry)));
2465      }
2466    }
2467
2468    // See if the target is the root DSE, the subschema subentry, or a changelog
2469    // entry.
2470    if (dn.isNullDN())
2471    {
2472      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2473           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2474           ERR_MEM_HANDLER_MOD_DN_ROOT_DSE.get(), null));
2475    }
2476    else if (dn.equals(subschemaSubentryDN))
2477    {
2478      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2479           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2480           ERR_MEM_HANDLER_MOD_DN_SOURCE_IS_SCHEMA.get(), null));
2481    }
2482    else if (dn.isDescendantOf(changeLogBaseDN, true))
2483    {
2484      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2485           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2486           ERR_MEM_HANDLER_MOD_DN_SOURCE_IS_CHANGELOG.get(), null));
2487    }
2488
2489    // Construct the new DN.
2490    final DN newDN;
2491    if (newSuperiorDN == null)
2492    {
2493      final DN originalParent = dn.getParent();
2494      if (originalParent == null)
2495      {
2496        newDN = new DN(newRDN);
2497      }
2498      else
2499      {
2500        newDN = new DN(newRDN, originalParent);
2501      }
2502    }
2503    else
2504    {
2505      newDN = new DN(newRDN, newSuperiorDN);
2506    }
2507
2508    // If the new DN matches the old DN, then fail.
2509    if (newDN.equals(dn))
2510    {
2511      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2512           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2513           ERR_MEM_HANDLER_MOD_DN_NEW_DN_SAME_AS_OLD.get(request.getDN()),
2514           null));
2515    }
2516
2517    // If the new DN is below a smart referral, then fail.
2518    if (! controlMap.containsKey(
2519               ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID))
2520    {
2521      final Entry referralEntry = findNearestReferral(newDN);
2522      if (referralEntry != null)
2523      {
2524        return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2525             ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, referralEntry.getDN(),
2526             ERR_MEM_HANDLER_MOD_DN_NEW_DN_BELOW_REFERRAL.get(request.getDN(),
2527                  referralEntry.getDN().toString(), newDN.toString()),
2528             null));
2529      }
2530    }
2531
2532    // If the target entry doesn't exist, then fail.
2533    final Entry originalEntry = entryMap.get(dn);
2534    if (originalEntry == null)
2535    {
2536      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2537           ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn),
2538           ERR_MEM_HANDLER_MOD_DN_NO_SUCH_ENTRY.get(request.getDN()), null));
2539    }
2540
2541    // If the new DN matches the subschema subentry DN, then fail.
2542    if (newDN.equals(subschemaSubentryDN))
2543    {
2544      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2545           ResultCode.ENTRY_ALREADY_EXISTS_INT_VALUE, null,
2546           ERR_MEM_HANDLER_MOD_DN_TARGET_IS_SCHEMA.get(request.getDN(),
2547                newDN.toString()),
2548           null));
2549    }
2550
2551    // If the new DN is at or below the changelog base DN, then fail.
2552    if (newDN.isDescendantOf(changeLogBaseDN, true))
2553    {
2554      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2555           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2556           ERR_MEM_HANDLER_MOD_DN_TARGET_IS_CHANGELOG.get(request.getDN(),
2557                newDN.toString()),
2558           null));
2559    }
2560
2561    // If the new DN already exists, then fail.
2562    if (entryMap.containsKey(newDN))
2563    {
2564      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2565           ResultCode.ENTRY_ALREADY_EXISTS_INT_VALUE, null,
2566           ERR_MEM_HANDLER_MOD_DN_TARGET_ALREADY_EXISTS.get(request.getDN(),
2567                newDN.toString()),
2568           null));
2569    }
2570
2571    // If the new DN is not a base DN and its parent does not exist, then fail.
2572    if (baseDNs.contains(newDN))
2573    {
2574      // The modify DN can be processed.
2575    }
2576    else
2577    {
2578      final DN newParent = newDN.getParent();
2579      if ((newParent != null) && entryMap.containsKey(newParent))
2580      {
2581        // The modify DN can be processed.
2582      }
2583      else
2584      {
2585        return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2586             ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(newDN),
2587             ERR_MEM_HANDLER_MOD_DN_PARENT_DOESNT_EXIST.get(request.getDN(),
2588                  newDN.toString()),
2589             null));
2590      }
2591    }
2592
2593    // Create a copy of the entry and update it to reflect the new DN (with
2594    // attribute value changes).
2595    final RDN originalRDN = dn.getRDN();
2596    final Entry updatedEntry = originalEntry.duplicate();
2597    updatedEntry.setDN(newDN);
2598    if (request.deleteOldRDN() && (! newRDN.equals(originalRDN)))
2599    {
2600      final String[] oldRDNNames  = originalRDN.getAttributeNames();
2601      final byte[][] oldRDNValues = originalRDN.getByteArrayAttributeValues();
2602      for (int i=0; i < oldRDNNames.length; i++)
2603      {
2604        updatedEntry.removeAttributeValue(oldRDNNames[i], oldRDNValues[i]);
2605      }
2606
2607      final String[] newRDNNames  = newRDN.getAttributeNames();
2608      final byte[][] newRDNValues = newRDN.getByteArrayAttributeValues();
2609      for (int i=0; i < newRDNNames.length; i++)
2610      {
2611        final MatchingRule matchingRule =
2612             MatchingRule.selectEqualityMatchingRule(newRDNNames[i], schema);
2613        updatedEntry.addAttribute(new Attribute(newRDNNames[i], matchingRule,
2614             newRDNValues[i]));
2615      }
2616    }
2617
2618    // If a schema was provided, then make sure the updated entry conforms to
2619    // the schema.  Also, reject the attempt if any of the new RDN attributes
2620    // is marked with NO-USER-MODIFICATION.
2621    final EntryValidator entryValidator = entryValidatorRef.get();
2622    if (entryValidator != null)
2623    {
2624      final ArrayList<String> invalidReasons = new ArrayList<String>(1);
2625      if (! entryValidator.entryIsValid(updatedEntry, invalidReasons))
2626      {
2627        return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2628             ResultCode.OBJECT_CLASS_VIOLATION_INT_VALUE, null,
2629             ERR_MEM_HANDLER_MOD_DN_VIOLATES_SCHEMA.get(request.getDN(),
2630                  StaticUtils.concatenateStrings(invalidReasons)),
2631             null));
2632      }
2633
2634      final String[] oldRDNNames = originalRDN.getAttributeNames();
2635      for (int i=0; i < oldRDNNames.length; i++)
2636      {
2637        final String name = oldRDNNames[i];
2638        final AttributeTypeDefinition at = schema.getAttributeType(name);
2639        if ((! isInternalOp) && (at != null) && at.isNoUserModification())
2640        {
2641          final byte[] value = originalRDN.getByteArrayAttributeValues()[i];
2642          if (! updatedEntry.hasAttributeValue(name, value))
2643          {
2644            return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2645                 ResultCode.CONSTRAINT_VIOLATION_INT_VALUE, null,
2646                 ERR_MEM_HANDLER_MOD_DN_NO_USER_MOD.get(request.getDN(),
2647                      name), null));
2648          }
2649        }
2650      }
2651
2652      final String[] newRDNNames = newRDN.getAttributeNames();
2653      for (int i=0; i < newRDNNames.length; i++)
2654      {
2655        final String name = newRDNNames[i];
2656        final AttributeTypeDefinition at = schema.getAttributeType(name);
2657        if ((! isInternalOp) && (at != null) && at.isNoUserModification())
2658        {
2659          final byte[] value = newRDN.getByteArrayAttributeValues()[i];
2660          if (! originalEntry.hasAttributeValue(name, value))
2661          {
2662            return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2663                 ResultCode.CONSTRAINT_VIOLATION_INT_VALUE, null,
2664                 ERR_MEM_HANDLER_MOD_DN_NO_USER_MOD.get(request.getDN(),
2665                      name), null));
2666          }
2667        }
2668      }
2669    }
2670
2671    // Perform the appropriate processing for the assertion and proxied
2672    // authorization controls
2673    final DN authzDN;
2674    try
2675    {
2676      handleAssertionRequestControl(controlMap, originalEntry);
2677
2678      authzDN = handleProxiedAuthControl(controlMap);
2679    }
2680    catch (final LDAPException le)
2681    {
2682      Debug.debugException(le);
2683      return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2684           le.getResultCode().intValue(), null, le.getMessage(), null));
2685    }
2686
2687    // Update the modifiersName, modifyTimestamp, and entryDN operational
2688    // attributes.
2689    if (generateOperationalAttributes)
2690    {
2691      updatedEntry.setAttribute(new Attribute("modifiersName",
2692           DistinguishedNameMatchingRule.getInstance(),
2693           authzDN.toString()));
2694      updatedEntry.setAttribute(new Attribute("modifyTimestamp",
2695           GeneralizedTimeMatchingRule.getInstance(),
2696           StaticUtils.encodeGeneralizedTime(new Date())));
2697      updatedEntry.setAttribute(new Attribute("entryDN",
2698           DistinguishedNameMatchingRule.getInstance(),
2699           newDN.toNormalizedString()));
2700    }
2701
2702    // Perform the appropriate processing for the pre-read and post-read
2703    // controls.
2704    final PreReadResponseControl preReadResponse =
2705         handlePreReadControl(controlMap, originalEntry);
2706    if (preReadResponse != null)
2707    {
2708      responseControls.add(preReadResponse);
2709    }
2710
2711    final PostReadResponseControl postReadResponse =
2712         handlePostReadControl(controlMap, updatedEntry);
2713    if (postReadResponse != null)
2714    {
2715      responseControls.add(postReadResponse);
2716    }
2717
2718    // Remove the old entry and add the new one.
2719    entryMap.remove(dn);
2720    entryMap.put(newDN, new ReadOnlyEntry(updatedEntry));
2721    indexDelete(originalEntry);
2722    indexAdd(updatedEntry);
2723
2724    // If the target entry had any subordinates, then rename them as well.
2725    final RDN[] oldDNComps = dn.getRDNs();
2726    final RDN[] newDNComps = newDN.getRDNs();
2727    final Set<DN> dnSet = new LinkedHashSet<DN>(entryMap.keySet());
2728    for (final DN mapEntryDN : dnSet)
2729    {
2730      if (mapEntryDN.isDescendantOf(dn, false))
2731      {
2732        final Entry o = entryMap.remove(mapEntryDN);
2733        final Entry e = o.duplicate();
2734
2735        final RDN[] oldMapEntryComps = mapEntryDN.getRDNs();
2736        final int compsToSave = oldMapEntryComps.length - oldDNComps.length ;
2737
2738        final RDN[] newMapEntryComps = new RDN[compsToSave + newDNComps.length];
2739        System.arraycopy(oldMapEntryComps, 0, newMapEntryComps, 0,
2740             compsToSave);
2741        System.arraycopy(newDNComps, 0, newMapEntryComps, compsToSave,
2742             newDNComps.length);
2743
2744        final DN newMapEntryDN = new DN(newMapEntryComps);
2745        e.setDN(newMapEntryDN);
2746        if (generateOperationalAttributes)
2747        {
2748          e.setAttribute(new Attribute("entryDN",
2749               DistinguishedNameMatchingRule.getInstance(),
2750               newMapEntryDN.toNormalizedString()));
2751        }
2752        entryMap.put(newMapEntryDN, new ReadOnlyEntry(e));
2753        indexDelete(o);
2754        indexAdd(e);
2755        handleReferentialIntegrityModifyDN(mapEntryDN, newMapEntryDN);
2756      }
2757    }
2758
2759    addChangeLogEntry(request, authzDN);
2760    handleReferentialIntegrityModifyDN(dn, newDN);
2761    return new LDAPMessage(messageID,
2762         new ModifyDNResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null,
2763              null, null),
2764         responseControls);
2765  }
2766
2767
2768
2769  /**
2770   * Handles any appropriate referential integrity processing for a modify DN
2771   * operation.
2772   *
2773   * @param  oldDN  The old DN for the entry.
2774   * @param  newDN  The new DN for the entry.
2775   */
2776  private void handleReferentialIntegrityModifyDN(final DN oldDN,
2777                                                  final DN newDN)
2778  {
2779    if (referentialIntegrityAttributes.isEmpty())
2780    {
2781      return;
2782    }
2783
2784    final ArrayList<DN> entryDNs = new ArrayList<DN>(entryMap.keySet());
2785    for (final DN mapDN : entryDNs)
2786    {
2787      final ReadOnlyEntry e = entryMap.get(mapDN);
2788
2789      boolean referenceFound = false;
2790      final Schema schema = schemaRef.get();
2791      for (final String attrName : referentialIntegrityAttributes)
2792      {
2793        final Attribute a = e.getAttribute(attrName, schema);
2794        if ((a != null) &&
2795            a.hasValue(oldDN.toNormalizedString(),
2796                 DistinguishedNameMatchingRule.getInstance()))
2797        {
2798          referenceFound = true;
2799          break;
2800        }
2801      }
2802
2803      if (referenceFound)
2804      {
2805        final Entry copy = e.duplicate();
2806        for (final String attrName : referentialIntegrityAttributes)
2807        {
2808          if (copy.removeAttributeValue(attrName, oldDN.toNormalizedString(),
2809                   DistinguishedNameMatchingRule.getInstance()))
2810          {
2811            copy.addAttribute(attrName, newDN.toString());
2812          }
2813        }
2814        entryMap.put(mapDN, new ReadOnlyEntry(copy));
2815        indexDelete(e);
2816        indexAdd(copy);
2817      }
2818    }
2819  }
2820
2821
2822
2823  /**
2824   * Attempts to process the provided search request.  The attempt will fail
2825   * if any of the following conditions is true:
2826   * <UL>
2827   *   <LI>There is a problem with any of the request controls.</LI>
2828   *   <LI>The modify DN request contains a malformed target DN, new RDN, or
2829   *       new superior DN.</LI>
2830   *   <LI>The new DN of the entry would conflict with the DN of an existing
2831   *       entry.</LI>
2832   *   <LI>The new DN of the entry would exist outside the set of defined
2833   *       base DNs.</LI>
2834   *   <LI>The new DN of the entry is not a defined base DN and does not exist
2835   *       immediately below an existing entry.</LI>
2836   * </UL>
2837   *
2838   * @param  messageID  The message ID of the LDAP message containing the search
2839   *                    request.
2840   * @param  request    The search request that was included in the LDAP message
2841   *                    that was received.
2842   * @param  controls   The set of controls included in the LDAP message.  It
2843   *                    may be empty if there were no controls, but will not be
2844   *                    {@code null}.
2845   *
2846   * @return  The {@link LDAPMessage} containing the response to send to the
2847   *          client.  The protocol op in the {@code LDAPMessage} must be an
2848   *          {@code SearchResultDoneProtocolOp}.
2849   */
2850  @Override()
2851  public synchronized LDAPMessage processSearchRequest(final int messageID,
2852                                       final SearchRequestProtocolOp request,
2853                                       final List<Control> controls)
2854  {
2855    final List<SearchResultEntry> entryList =
2856         new ArrayList<SearchResultEntry>(entryMap.size());
2857    final List<SearchResultReference> referenceList =
2858         new ArrayList<SearchResultReference>(entryMap.size());
2859
2860    final LDAPMessage returnMessage = processSearchRequest(messageID, request,
2861         controls, entryList, referenceList);
2862
2863    for (final SearchResultEntry e : entryList)
2864    {
2865      try
2866      {
2867        connection.sendSearchResultEntry(messageID, e, e.getControls());
2868      }
2869      catch (final LDAPException le)
2870      {
2871        Debug.debugException(le);
2872        return new LDAPMessage(messageID,
2873             new SearchResultDoneProtocolOp(le.getResultCode().intValue(),
2874                  le.getMatchedDN(), le.getDiagnosticMessage(),
2875                  StaticUtils.toList(le.getReferralURLs())),
2876             le.getResponseControls());
2877      }
2878    }
2879
2880    for (final SearchResultReference r : referenceList)
2881    {
2882      try
2883      {
2884        connection.sendSearchResultReference(messageID,
2885             new SearchResultReferenceProtocolOp(
2886                  StaticUtils.toList(r.getReferralURLs())),
2887             r.getControls());
2888      }
2889      catch (final LDAPException le)
2890      {
2891        Debug.debugException(le);
2892        return new LDAPMessage(messageID,
2893             new SearchResultDoneProtocolOp(le.getResultCode().intValue(),
2894                  le.getMatchedDN(), le.getDiagnosticMessage(),
2895                  StaticUtils.toList(le.getReferralURLs())),
2896             le.getResponseControls());
2897      }
2898    }
2899
2900    return returnMessage;
2901  }
2902
2903
2904
2905  /**
2906   * Attempts to process the provided search request.  The attempt will fail
2907   * if any of the following conditions is true:
2908   * <UL>
2909   *   <LI>There is a problem with any of the request controls.</LI>
2910   *   <LI>The modify DN request contains a malformed target DN, new RDN, or
2911   *       new superior DN.</LI>
2912   *   <LI>The new DN of the entry would conflict with the DN of an existing
2913   *       entry.</LI>
2914   *   <LI>The new DN of the entry would exist outside the set of defined
2915   *       base DNs.</LI>
2916   *   <LI>The new DN of the entry is not a defined base DN and does not exist
2917   *       immediately below an existing entry.</LI>
2918   * </UL>
2919   *
2920   * @param  messageID      The message ID of the LDAP message containing the
2921   *                        search request.
2922   * @param  request        The search request that was included in the LDAP
2923   *                        message that was received.
2924   * @param  controls       The set of controls included in the LDAP message.
2925   *                        It may be empty if there were no controls, but will
2926   *                        not be {@code null}.
2927   * @param  entryList      A list to which to add search result entries
2928   *                        intended for return to the client.  It must not be
2929   *                        {@code null}.
2930   * @param  referenceList  A list to which to add search result references
2931   *                        intended for return to the client.  It must not be
2932   *                        {@code null}.
2933   *
2934   * @return  The {@link LDAPMessage} containing the response to send to the
2935   *          client.  The protocol op in the {@code LDAPMessage} must be an
2936   *          {@code SearchResultDoneProtocolOp}.
2937   */
2938  synchronized LDAPMessage processSearchRequest(final int messageID,
2939                                final SearchRequestProtocolOp request,
2940                                final List<Control> controls,
2941                                final List<SearchResultEntry> entryList,
2942                                final List<SearchResultReference> referenceList)
2943  {
2944    // Sleep before processing, if appropriate.
2945    sleepBeforeProcessing();
2946
2947    // Process the provided request controls.
2948    final Map<String,Control> controlMap;
2949    try
2950    {
2951      controlMap = RequestControlPreProcessor.processControls(
2952           LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST, controls);
2953    }
2954    catch (final LDAPException le)
2955    {
2956      Debug.debugException(le);
2957      return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
2958           le.getResultCode().intValue(), null, le.getMessage(), null));
2959    }
2960    final ArrayList<Control> responseControls = new ArrayList<Control>(1);
2961
2962
2963    // If this operation type is not allowed, then reject it.
2964    final boolean isInternalOp =
2965         controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
2966    if ((! isInternalOp) &&
2967        (! config.getAllowedOperationTypes().contains(OperationType.SEARCH)))
2968    {
2969      return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
2970           ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2971           ERR_MEM_HANDLER_SEARCH_NOT_ALLOWED.get(), null));
2972    }
2973
2974
2975    // If this operation type requires authentication, then ensure that the
2976    // client is authenticated.
2977    if ((authenticatedDN.isNullDN() &&
2978        config.getAuthenticationRequiredOperationTypes().contains(
2979             OperationType.SEARCH)))
2980    {
2981      return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
2982           ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
2983           ERR_MEM_HANDLER_SEARCH_REQUIRES_AUTH.get(), null));
2984    }
2985
2986
2987    // Get the parsed base DN.
2988    final DN baseDN;
2989    final Schema schema = schemaRef.get();
2990    try
2991    {
2992      baseDN = new DN(request.getBaseDN(), schema);
2993    }
2994    catch (final LDAPException le)
2995    {
2996      Debug.debugException(le);
2997      return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
2998           ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
2999           ERR_MEM_HANDLER_SEARCH_MALFORMED_BASE.get(request.getBaseDN(),
3000                le.getMessage()),
3001                null));
3002    }
3003
3004    // See if the search base or one of its superiors is a smart referral.
3005    final boolean hasManageDsaIT = controlMap.containsKey(
3006         ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID);
3007    if (! hasManageDsaIT)
3008    {
3009      final Entry referralEntry = findNearestReferral(baseDN);
3010      if (referralEntry != null)
3011      {
3012        return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
3013             ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
3014             INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
3015             getReferralURLs(baseDN, referralEntry)));
3016      }
3017    }
3018
3019    // Make sure that the base entry exists.  It may be the root DSE or
3020    // subschema subentry.
3021    final Entry baseEntry;
3022    boolean includeChangeLog = true;
3023    if (baseDN.isNullDN())
3024    {
3025      baseEntry = generateRootDSE();
3026      includeChangeLog = false;
3027    }
3028    else if (baseDN.equals(subschemaSubentryDN))
3029    {
3030      baseEntry = subschemaSubentryRef.get();
3031    }
3032    else
3033    {
3034      baseEntry = entryMap.get(baseDN);
3035    }
3036
3037    if (baseEntry == null)
3038    {
3039      return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
3040           ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(baseDN),
3041           ERR_MEM_HANDLER_SEARCH_BASE_DOES_NOT_EXIST.get(request.getBaseDN()),
3042           null));
3043    }
3044
3045    // Perform any necessary processing for the assertion and proxied auth
3046    // controls.
3047    try
3048    {
3049      handleAssertionRequestControl(controlMap, baseEntry);
3050      handleProxiedAuthControl(controlMap);
3051    }
3052    catch (final LDAPException le)
3053    {
3054      Debug.debugException(le);
3055      return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
3056           le.getResultCode().intValue(), null, le.getMessage(), null));
3057    }
3058
3059    // Create a temporary list to hold all of the entries to be returned.  These
3060    // entries will not have been pared down based on the requested attributes.
3061    final List<Entry> fullEntryList = new ArrayList<Entry>(entryMap.size());
3062
3063findEntriesAndRefs:
3064    {
3065      // Check the scope.  If it is a base-level search, then we only need to
3066      // examine the base entry.  Otherwise, we'll have to scan the entire entry
3067      // map.
3068      final Filter filter = request.getFilter();
3069      final SearchScope scope = request.getScope();
3070      final boolean includeSubEntries = ((scope == SearchScope.BASE) ||
3071           controlMap.containsKey(
3072                SubentriesRequestControl.SUBENTRIES_REQUEST_OID));
3073      if (scope == SearchScope.BASE)
3074      {
3075        try
3076        {
3077          if (filter.matchesEntry(baseEntry, schema))
3078          {
3079            processSearchEntry(baseEntry, includeSubEntries, includeChangeLog,
3080                 hasManageDsaIT, fullEntryList, referenceList);
3081          }
3082        }
3083        catch (final Exception e)
3084        {
3085          Debug.debugException(e);
3086        }
3087
3088        break findEntriesAndRefs;
3089      }
3090
3091      // If the search uses a single-level scope and the base DN is the root
3092      // DSE, then we will only examine the defined base entries for the data
3093      // set.
3094      if ((scope == SearchScope.ONE) && baseDN.isNullDN())
3095      {
3096        for (final DN dn : baseDNs)
3097        {
3098          final Entry e = entryMap.get(dn);
3099          if (e != null)
3100          {
3101            try
3102            {
3103              if (filter.matchesEntry(e, schema))
3104              {
3105                processSearchEntry(e, includeSubEntries, includeChangeLog,
3106                     hasManageDsaIT, fullEntryList, referenceList);
3107              }
3108            }
3109            catch (final Exception ex)
3110            {
3111              Debug.debugException(ex);
3112            }
3113          }
3114        }
3115
3116        break findEntriesAndRefs;
3117      }
3118
3119
3120      // Try to use indexes to process the request.  If we can't use any
3121      // indexes to get a candidate list, then just iterate over all the
3122      // entries.  It's not necessary to consider the root DSE for non-base
3123      // scopes.
3124      final Set<DN> candidateDNs = indexSearch(filter);
3125      if (candidateDNs == null)
3126      {
3127        for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet())
3128        {
3129          final DN dn = me.getKey();
3130          final Entry entry = me.getValue();
3131          try
3132          {
3133            if (dn.matchesBaseAndScope(baseDN, scope) &&
3134                filter.matchesEntry(entry, schema))
3135            {
3136              processSearchEntry(entry, includeSubEntries, includeChangeLog,
3137                   hasManageDsaIT, fullEntryList, referenceList);
3138            }
3139          }
3140          catch (final Exception e)
3141          {
3142            Debug.debugException(e);
3143          }
3144        }
3145      }
3146      else
3147      {
3148        for (final DN dn : candidateDNs)
3149        {
3150          try
3151          {
3152            if (! dn.matchesBaseAndScope(baseDN, scope))
3153            {
3154              continue;
3155            }
3156
3157            final Entry entry = entryMap.get(dn);
3158            if (filter.matchesEntry(entry, schema))
3159            {
3160              processSearchEntry(entry, includeSubEntries, includeChangeLog,
3161                   hasManageDsaIT, fullEntryList, referenceList);
3162            }
3163          }
3164          catch (final Exception e)
3165          {
3166            Debug.debugException(e);
3167          }
3168        }
3169      }
3170    }
3171
3172
3173    // If the request included the server-side sort request control, then sort
3174    // the matching entries appropriately.
3175    final ServerSideSortRequestControl sortRequestControl =
3176         (ServerSideSortRequestControl) controlMap.get(
3177              ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID);
3178    if (sortRequestControl != null)
3179    {
3180      final EntrySorter entrySorter = new EntrySorter(false, schema,
3181           sortRequestControl.getSortKeys());
3182      final SortedSet<Entry> sortedEntrySet = entrySorter.sort(fullEntryList);
3183      fullEntryList.clear();
3184      fullEntryList.addAll(sortedEntrySet);
3185
3186      responseControls.add(new ServerSideSortResponseControl(ResultCode.SUCCESS,
3187           null, false));
3188    }
3189
3190
3191    // If the request included the simple paged results control, then handle it.
3192    final SimplePagedResultsControl pagedResultsControl =
3193         (SimplePagedResultsControl)
3194         controlMap.get(SimplePagedResultsControl.PAGED_RESULTS_OID);
3195    if (pagedResultsControl != null)
3196    {
3197      final int totalSize = fullEntryList.size();
3198      final int pageSize = pagedResultsControl.getSize();
3199      final ASN1OctetString cookie = pagedResultsControl.getCookie();
3200
3201      final int offset;
3202      if ((cookie == null) || (cookie.getValueLength() == 0))
3203      {
3204        // This is the first request in the series, so start at the beginning of
3205        // the list.
3206        offset = 0;
3207      }
3208      else
3209      {
3210        // The cookie value will simply be an integer representation of the
3211        // offset within the result list at which to start the next batch.
3212        try
3213        {
3214          final ASN1Integer offsetInteger =
3215               ASN1Integer.decodeAsInteger(cookie.getValue());
3216          offset = offsetInteger.intValue();
3217        }
3218        catch (final Exception e)
3219        {
3220          Debug.debugException(e);
3221          return new LDAPMessage(messageID,
3222               new SearchResultDoneProtocolOp(
3223                    ResultCode.PROTOCOL_ERROR_INT_VALUE, null,
3224                    ERR_MEM_HANDLER_MALFORMED_PAGED_RESULTS_COOKIE.get(), null),
3225               responseControls);
3226        }
3227      }
3228
3229      // Create an iterator that will be used to remove entries from the result
3230      // set that are outside of the requested page of results.
3231      int pos = 0;
3232      final Iterator<Entry> iterator = fullEntryList.iterator();
3233
3234      // First, remove entries at the beginning of the list until we hit the
3235      // offset.
3236      while (iterator.hasNext() && (pos < offset))
3237      {
3238        iterator.next();
3239        iterator.remove();
3240        pos++;
3241      }
3242
3243      // Next, skip over the entries that should be returned.
3244      int keptEntries = 0;
3245      while (iterator.hasNext() && (keptEntries < pageSize))
3246      {
3247        iterator.next();
3248        pos++;
3249        keptEntries++;
3250      }
3251
3252      // If there are still entries left, then remove them and create a cookie
3253      // to include in the response.  Otherwise, use an empty cookie.
3254      if (iterator.hasNext())
3255      {
3256        responseControls.add(new SimplePagedResultsControl(totalSize,
3257             new ASN1OctetString(new ASN1Integer(pos).encode()), false));
3258        while (iterator.hasNext())
3259        {
3260          iterator.next();
3261          iterator.remove();
3262        }
3263      }
3264      else
3265      {
3266        responseControls.add(new SimplePagedResultsControl(totalSize,
3267             new ASN1OctetString(), false));
3268      }
3269    }
3270
3271
3272    // If the request includes the virtual list view request control, then
3273    // handle it.
3274    final VirtualListViewRequestControl vlvRequest =
3275         (VirtualListViewRequestControl) controlMap.get(
3276              VirtualListViewRequestControl.VIRTUAL_LIST_VIEW_REQUEST_OID);
3277    if (vlvRequest != null)
3278    {
3279      final int totalEntries = fullEntryList.size();
3280      final ASN1OctetString assertionValue = vlvRequest.getAssertionValue();
3281
3282      // Figure out the position of the target entry in the list.
3283      int offset = vlvRequest.getTargetOffset();
3284      if (assertionValue == null)
3285      {
3286        // The offset is one-based, so we need to adjust it for the list's
3287        // zero-based offset.  Also, make sure to put it within the bounds of
3288        // the list.
3289        offset--;
3290        offset = Math.max(0, offset);
3291        offset = Math.min(fullEntryList.size(), offset);
3292      }
3293      else
3294      {
3295        final SortKey primarySortKey = sortRequestControl.getSortKeys()[0];
3296
3297        final Entry testEntry = new Entry("cn=test", schema,
3298             new Attribute(primarySortKey.getAttributeName(), assertionValue));
3299
3300        final EntrySorter entrySorter =
3301             new EntrySorter(false, schema, primarySortKey);
3302
3303        offset = fullEntryList.size();
3304        for (int i=0; i < fullEntryList.size(); i++)
3305        {
3306          if (entrySorter.compare(fullEntryList.get(i), testEntry) >= 0)
3307          {
3308            offset = i;
3309            break;
3310          }
3311        }
3312      }
3313
3314      // Get the start and end positions based on the before and after counts.
3315      final int beforeCount = Math.max(0, vlvRequest.getBeforeCount());
3316      final int afterCount  = Math.max(0, vlvRequest.getAfterCount());
3317
3318      final int start = Math.max(0, (offset - beforeCount));
3319      final int end = Math.min(fullEntryList.size(), (offset + afterCount + 1));
3320
3321      // Create an iterator to use to alter the list so that it only contains
3322      // the appropriate set of entries.
3323      int pos = 0;
3324      final Iterator<Entry> iterator = fullEntryList.iterator();
3325      while (iterator.hasNext())
3326      {
3327        iterator.next();
3328        if ((pos < start) || (pos >= end))
3329        {
3330          iterator.remove();
3331        }
3332        pos++;
3333      }
3334
3335      // Create the appropriate response control.
3336      responseControls.add(new VirtualListViewResponseControl((offset+1),
3337           totalEntries, ResultCode.SUCCESS, null));
3338    }
3339
3340
3341    // Process the set of requested attributes so that we can pare down the
3342    // entries.
3343    final AtomicBoolean allUserAttrs = new AtomicBoolean(false);
3344    final AtomicBoolean allOpAttrs = new AtomicBoolean(false);
3345    final Map<String,List<List<String>>> returnAttrs =
3346         processRequestedAttributes(request.getAttributes(), allUserAttrs,
3347              allOpAttrs);
3348
3349    final int sizeLimit;
3350    if (request.getSizeLimit() > 0)
3351    {
3352      sizeLimit = Math.min(request.getSizeLimit(), maxSizeLimit);
3353    }
3354    else
3355    {
3356      sizeLimit = maxSizeLimit;
3357    }
3358
3359    int entryCount = 0;
3360    for (final Entry e : fullEntryList)
3361    {
3362      entryCount++;
3363      if (entryCount > sizeLimit)
3364      {
3365        return new LDAPMessage(messageID,
3366             new SearchResultDoneProtocolOp(
3367                  ResultCode.SIZE_LIMIT_EXCEEDED_INT_VALUE, null,
3368                  ERR_MEM_HANDLER_SEARCH_SIZE_LIMIT_EXCEEDED.get(), null),
3369             responseControls);
3370      }
3371
3372      final Entry trimmedEntry = trimForRequestedAttributes(e,
3373           allUserAttrs.get(), allOpAttrs.get(), returnAttrs);
3374      if (request.typesOnly())
3375      {
3376        final Entry typesOnlyEntry = new Entry(trimmedEntry.getDN(), schema);
3377        for (final Attribute a : trimmedEntry.getAttributes())
3378        {
3379          typesOnlyEntry.addAttribute(new Attribute(a.getName()));
3380        }
3381        entryList.add(new SearchResultEntry(typesOnlyEntry));
3382      }
3383      else
3384      {
3385        entryList.add(new SearchResultEntry(trimmedEntry));
3386      }
3387    }
3388
3389    return new LDAPMessage(messageID,
3390         new SearchResultDoneProtocolOp(ResultCode.SUCCESS_INT_VALUE, null,
3391              null, null),
3392         responseControls);
3393  }
3394
3395
3396
3397  /**
3398   * Performs any necessary index processing to add the provided entry.
3399   *
3400   * @param  entry  The entry that has been added.
3401   */
3402  private void indexAdd(final Entry entry)
3403  {
3404    for (final InMemoryDirectoryServerEqualityAttributeIndex i :
3405         equalityIndexes.values())
3406    {
3407      try
3408      {
3409        i.processAdd(entry);
3410      }
3411      catch (final LDAPException le)
3412      {
3413        Debug.debugException(le);
3414      }
3415    }
3416  }
3417
3418
3419
3420  /**
3421   * Performs any necessary index processing to delete the provided entry.
3422   *
3423   * @param  entry  The entry that has been deleted.
3424   */
3425  private void indexDelete(final Entry entry)
3426  {
3427    for (final InMemoryDirectoryServerEqualityAttributeIndex i :
3428         equalityIndexes.values())
3429    {
3430      try
3431      {
3432        i.processDelete(entry);
3433      }
3434      catch (final LDAPException le)
3435      {
3436        Debug.debugException(le);
3437      }
3438    }
3439  }
3440
3441
3442
3443  /**
3444   * Attempts to use indexes to obtain a candidate list for the provided filter.
3445   *
3446   * @param  filter  The filter to be processed.
3447   *
3448   * @return  The DNs of entries which may match the given filter, or
3449   *          {@code null} if the filter is not indexed.
3450   */
3451  private Set<DN> indexSearch(final Filter filter)
3452  {
3453    switch (filter.getFilterType())
3454    {
3455      case Filter.FILTER_TYPE_AND:
3456        Filter[] comps = filter.getComponents();
3457        if (comps.length == 0)
3458        {
3459          return null;
3460        }
3461        else if (comps.length == 1)
3462        {
3463          return indexSearch(comps[0]);
3464        }
3465        else
3466        {
3467          Set<DN> candidateSet = null;
3468          for (final Filter f : comps)
3469          {
3470            final Set<DN> dnSet = indexSearch(f);
3471            if (dnSet != null)
3472            {
3473              if (candidateSet == null)
3474              {
3475                candidateSet = new TreeSet<DN>(dnSet);
3476              }
3477              else
3478              {
3479                candidateSet.retainAll(dnSet);
3480              }
3481            }
3482          }
3483          return candidateSet;
3484        }
3485
3486      case Filter.FILTER_TYPE_OR:
3487        comps = filter.getComponents();
3488        if (comps.length == 0)
3489        {
3490          return Collections.emptySet();
3491        }
3492        else if (comps.length == 1)
3493        {
3494          return indexSearch(comps[0]);
3495        }
3496        else
3497        {
3498          Set<DN> candidateSet = null;
3499          for (final Filter f : comps)
3500          {
3501            final Set<DN> dnSet = indexSearch(f);
3502            if (dnSet == null)
3503            {
3504              return null;
3505            }
3506
3507            if (candidateSet == null)
3508            {
3509              candidateSet = new TreeSet<DN>(dnSet);
3510            }
3511            else
3512            {
3513              candidateSet.addAll(dnSet);
3514            }
3515          }
3516          return candidateSet;
3517        }
3518
3519      case Filter.FILTER_TYPE_EQUALITY:
3520        final Schema schema = schemaRef.get();
3521        if (schema == null)
3522        {
3523          return null;
3524        }
3525        final AttributeTypeDefinition at =
3526             schema.getAttributeType(filter.getAttributeName());
3527        if (at == null)
3528        {
3529          return null;
3530        }
3531        final InMemoryDirectoryServerEqualityAttributeIndex i =
3532             equalityIndexes.get(at);
3533        if (i == null)
3534        {
3535          return null;
3536        }
3537        try
3538        {
3539          return i.getMatchingEntries(filter.getRawAssertionValue());
3540        }
3541        catch (final Exception e)
3542        {
3543          Debug.debugException(e);
3544          return null;
3545        }
3546
3547      default:
3548        return null;
3549    }
3550  }
3551
3552
3553
3554  /**
3555   * Determines whether the provided set of controls includes a transaction
3556   * specification request control.  If so, then it will verify that it
3557   * references a valid transaction for the client.  If the request is part of a
3558   * valid transaction, then the transaction specification request control will
3559   * be removed and the request will be stashed in the client connection state
3560   * so that it can be retrieved and processed when the transaction is
3561   * committed.
3562   *
3563   * @param  messageID  The message ID for the request to be processed.
3564   * @param  request    The protocol op for the request to be processed.
3565   * @param  controls   The set of controls for the request to be processed.
3566   *
3567   * @return  The transaction ID for the associated transaction, or {@code null}
3568   *          if the request is not part of any transaction.
3569   *
3570   * @throws  LDAPException  If the transaction specification request control is
3571   *                         present but does not refer to a valid transaction
3572   *                         for the associated client connection.
3573   */
3574  @SuppressWarnings("unchecked")
3575  private ASN1OctetString processTransactionRequest(final int messageID,
3576                               final ProtocolOp request,
3577                               final Map<String,Control> controls)
3578          throws LDAPException
3579  {
3580    final TransactionSpecificationRequestControl txnControl =
3581         (TransactionSpecificationRequestControl)
3582         controls.remove(TransactionSpecificationRequestControl.
3583              TRANSACTION_SPECIFICATION_REQUEST_OID);
3584    if (txnControl == null)
3585    {
3586      return null;
3587    }
3588
3589    // See if the client has an active transaction.  If not, then fail.
3590    final ASN1OctetString txnID = txnControl.getTransactionID();
3591    final ObjectPair<ASN1OctetString,List<LDAPMessage>> txnInfo =
3592         (ObjectPair<ASN1OctetString,List<LDAPMessage>>) connectionState.get(
3593              TransactionExtendedOperationHandler.STATE_VARIABLE_TXN_INFO);
3594    if (txnInfo == null)
3595    {
3596      throw new LDAPException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
3597           ERR_MEM_HANDLER_TXN_CONTROL_WITHOUT_TXN.get(txnID.stringValue()));
3598    }
3599
3600
3601    // Make sure that the active transaction has a transaction ID that matches
3602    // the transaction ID from the control.  If not, then abort the existing
3603    // transaction and fail.
3604    final ASN1OctetString existingTxnID = txnInfo.getFirst();
3605    if (! txnID.stringValue().equals(existingTxnID.stringValue()))
3606    {
3607      connectionState.remove(
3608           TransactionExtendedOperationHandler.STATE_VARIABLE_TXN_INFO);
3609      connection.sendUnsolicitedNotification(
3610           new AbortedTransactionExtendedResult(existingTxnID,
3611                ResultCode.CONSTRAINT_VIOLATION,
3612                ERR_MEM_HANDLER_TXN_ABORTED_BY_CONTROL_TXN_ID_MISMATCH.get(
3613                     existingTxnID.stringValue(), txnID.stringValue()),
3614                null, null, null));
3615      throw new LDAPException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
3616           ERR_MEM_HANDLER_TXN_CONTROL_ID_MISMATCH.get(txnID.stringValue(),
3617                existingTxnID.stringValue()));
3618    }
3619
3620
3621    // Stash the request in the transaction state information so that it will
3622    // be processed when the transaction is committed.
3623    txnInfo.getSecond().add(new LDAPMessage(messageID, request,
3624         new ArrayList<Control>(controls.values())));
3625
3626    return txnID;
3627  }
3628
3629
3630
3631  /**
3632   * Sleeps for a period of time (if appropriate) before beginning processing
3633   * for an operation.
3634   */
3635  private void sleepBeforeProcessing()
3636  {
3637    final long delay = processingDelayMillis.get();
3638    if (delay > 0)
3639    {
3640      try
3641      {
3642        Thread.sleep(delay);
3643      }
3644      catch (final Exception e)
3645      {
3646        Debug.debugException(e);
3647      }
3648    }
3649  }
3650
3651
3652
3653  /**
3654   * Retrieves the number of entries currently held in the server.
3655   *
3656   * @param  includeChangeLog  Indicates whether to include entries that are
3657   *                           part of the changelog in the count.
3658   *
3659   * @return  The number of entries currently held in the server.
3660   */
3661  public synchronized int countEntries(final boolean includeChangeLog)
3662  {
3663    if (includeChangeLog || (maxChangelogEntries == 0))
3664    {
3665      return entryMap.size();
3666    }
3667    else
3668    {
3669      int count = 0;
3670
3671      for (final DN dn : entryMap.keySet())
3672      {
3673        if (! dn.isDescendantOf(changeLogBaseDN, true))
3674        {
3675          count++;
3676        }
3677      }
3678
3679      return count;
3680    }
3681  }
3682
3683
3684
3685  /**
3686   * Retrieves the number of entries currently held in the server whose DN
3687   * matches or is subordinate to the provided base DN.
3688   *
3689   * @param  baseDN  The base DN to use for the determination.
3690   *
3691   * @return  The number of entries currently held in the server whose DN
3692   *          matches or is subordinate to the provided base DN.
3693   *
3694   * @throws  LDAPException  If the provided string cannot be parsed as a valid
3695   *                         DN.
3696   */
3697  public synchronized int countEntriesBelow(final String baseDN)
3698         throws LDAPException
3699  {
3700    final DN parsedBaseDN = new DN(baseDN, schemaRef.get());
3701
3702    int count = 0;
3703    for (final DN dn : entryMap.keySet())
3704    {
3705      if (dn.isDescendantOf(parsedBaseDN, true))
3706      {
3707        count++;
3708      }
3709    }
3710
3711    return count;
3712  }
3713
3714
3715
3716  /**
3717   * Removes all entries currently held in the server.  If a changelog is
3718   * enabled, then all changelog entries will also be cleared but the base
3719   * "cn=changelog" entry will be retained.
3720   */
3721  public synchronized void clear()
3722  {
3723    restoreSnapshot(initialSnapshot);
3724  }
3725
3726
3727
3728  /**
3729   * Reads entries from the provided LDIF reader and adds them to the server,
3730   * optionally clearing any existing entries before beginning to add the new
3731   * entries.  If an error is encountered while adding entries from LDIF then
3732   * the server will remain populated with the data it held before the import
3733   * attempt (even if the {@code clear} is given with a value of {@code true}).
3734   *
3735   * @param  clear       Indicates whether to remove all existing entries prior
3736   *                     to adding entries read from LDIF.
3737   * @param  ldifReader  The LDIF reader to use to obtain the entries to be
3738   *                     imported.
3739   *
3740   * @return  The number of entries read from LDIF and added to the server.
3741   *
3742   * @throws  LDAPException  If a problem occurs while reading entries or adding
3743   *                         them to the server.
3744   */
3745  public synchronized int importFromLDIF(final boolean clear,
3746                                         final LDIFReader ldifReader)
3747         throws LDAPException
3748  {
3749    final InMemoryDirectoryServerSnapshot snapshot = createSnapshot();
3750    boolean restoreSnapshot = true;
3751
3752    try
3753    {
3754      if (clear)
3755      {
3756        restoreSnapshot(initialSnapshot);
3757      }
3758
3759      int entriesAdded = 0;
3760      while (true)
3761      {
3762        final Entry entry;
3763        try
3764        {
3765          entry = ldifReader.readEntry();
3766          if (entry == null)
3767          {
3768            restoreSnapshot = false;
3769            return entriesAdded;
3770          }
3771        }
3772        catch (final LDIFException le)
3773        {
3774          Debug.debugException(le);
3775          throw new LDAPException(ResultCode.LOCAL_ERROR,
3776               ERR_MEM_HANDLER_INIT_FROM_LDIF_READ_ERROR.get(le.getMessage()),
3777               le);
3778        }
3779        catch (final Exception e)
3780        {
3781          Debug.debugException(e);
3782          throw new LDAPException(ResultCode.LOCAL_ERROR,
3783               ERR_MEM_HANDLER_INIT_FROM_LDIF_READ_ERROR.get(
3784                    StaticUtils.getExceptionMessage(e)),
3785               e);
3786        }
3787
3788        addEntry(entry, true);
3789        entriesAdded++;
3790      }
3791    }
3792    finally
3793    {
3794      try
3795      {
3796        ldifReader.close();
3797      }
3798      catch (final Exception e)
3799      {
3800        Debug.debugException(e);
3801      }
3802
3803      if (restoreSnapshot)
3804      {
3805        restoreSnapshot(snapshot);
3806      }
3807    }
3808  }
3809
3810
3811
3812  /**
3813   * Writes all entries contained in the server to LDIF using the provided
3814   * writer.
3815   *
3816   * @param  ldifWriter             The LDIF writer to use when writing the
3817   *                                entries.  It must not be {@code null}.
3818   * @param  excludeGeneratedAttrs  Indicates whether to exclude automatically
3819   *                                generated operational attributes like
3820   *                                entryUUID, entryDN, creatorsName, etc.
3821   * @param  excludeChangeLog       Indicates whether to exclude entries
3822   *                                contained in the changelog.
3823   * @param  closeWriter            Indicates whether the LDIF writer should be
3824   *                                closed after all entries have been written.
3825   *
3826   * @return  The number of entries written to LDIF.
3827   *
3828   * @throws  LDAPException  If a problem is encountered while attempting to
3829   *                         write an entry to LDIF.
3830   */
3831  public synchronized int exportToLDIF(final LDIFWriter ldifWriter,
3832                                       final boolean excludeGeneratedAttrs,
3833                                       final boolean excludeChangeLog,
3834                                       final boolean closeWriter)
3835         throws LDAPException
3836  {
3837    boolean exceptionThrown = false;
3838
3839    try
3840    {
3841      int entriesWritten = 0;
3842
3843      for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet())
3844      {
3845        final DN dn = me.getKey();
3846        if (excludeChangeLog && dn.isDescendantOf(changeLogBaseDN, true))
3847        {
3848          continue;
3849        }
3850
3851        final Entry entry;
3852        if (excludeGeneratedAttrs)
3853        {
3854          entry = me.getValue().duplicate();
3855          entry.removeAttribute("entryDN");
3856          entry.removeAttribute("entryUUID");
3857          entry.removeAttribute("subschemaSubentry");
3858          entry.removeAttribute("creatorsName");
3859          entry.removeAttribute("createTimestamp");
3860          entry.removeAttribute("modifiersName");
3861          entry.removeAttribute("modifyTimestamp");
3862        }
3863        else
3864        {
3865          entry = me.getValue();
3866        }
3867
3868        try
3869        {
3870          ldifWriter.writeEntry(entry);
3871          entriesWritten++;
3872        }
3873        catch (final Exception e)
3874        {
3875          Debug.debugException(e);
3876          exceptionThrown = true;
3877          throw new LDAPException(ResultCode.LOCAL_ERROR,
3878               ERR_MEM_HANDLER_LDIF_WRITE_ERROR.get(entry.getDN(),
3879                    StaticUtils.getExceptionMessage(e)),
3880               e);
3881        }
3882      }
3883
3884      return entriesWritten;
3885    }
3886    finally
3887    {
3888      if (closeWriter)
3889      {
3890        try
3891        {
3892          ldifWriter.close();
3893        }
3894        catch (final Exception e)
3895        {
3896          Debug.debugException(e);
3897          if (! exceptionThrown)
3898          {
3899            throw new LDAPException(ResultCode.LOCAL_ERROR,
3900                 ERR_MEM_HANDLER_LDIF_WRITE_CLOSE_ERROR.get(
3901                      StaticUtils.getExceptionMessage(e)),
3902                 e);
3903          }
3904        }
3905      }
3906    }
3907  }
3908
3909
3910
3911  /**
3912   * Attempts to add the provided entry to the in-memory data set.  The attempt
3913   * will fail if any of the following conditions is true:
3914   * <UL>
3915   *   <LI>The provided entry has a malformed DN.</LI>
3916   *   <LI>The provided entry has the null DN.</LI>
3917   *   <LI>The provided entry has a DN that is the same as or subordinate to the
3918   *       subschema subentry.</LI>
3919   *   <LI>An entry already exists with the same DN as the entry in the provided
3920   *       request.</LI>
3921   *   <LI>The entry is outside the set of base DNs for the server.</LI>
3922   *   <LI>The entry is below one of the defined base DNs but the immediate
3923   *       parent entry does not exist.</LI>
3924   *   <LI>If a schema was provided, and the entry is not valid according to the
3925   *       constraints of that schema.</LI>
3926   * </UL>
3927   *
3928   * @param  entry                     The entry to be added.  It must not be
3929   *                                   {@code null}.
3930   * @param  ignoreNoUserModification  Indicates whether to ignore constraints
3931   *                                   normally imposed by the
3932   *                                   NO-USER-MODIFICATION element in attribute
3933   *                                   type definitions.
3934   *
3935   * @throws  LDAPException  If a problem occurs while attempting to add the
3936   *                         provided entry.
3937   */
3938  public void addEntry(final Entry entry,
3939                       final boolean ignoreNoUserModification)
3940         throws LDAPException
3941  {
3942    final List<Control> controls;
3943    if (ignoreNoUserModification)
3944    {
3945      controls = new ArrayList<Control>(1);
3946      controls.add(new Control(OID_INTERNAL_OPERATION_REQUEST_CONTROL, false));
3947    }
3948    else
3949    {
3950      controls = Collections.emptyList();
3951    }
3952
3953    final AddRequestProtocolOp addRequest = new AddRequestProtocolOp(
3954         entry.getDN(), new ArrayList<Attribute>(entry.getAttributes()));
3955
3956    final LDAPMessage resultMessage =
3957         processAddRequest(-1, addRequest, controls);
3958
3959    final AddResponseProtocolOp addResponse =
3960         resultMessage.getAddResponseProtocolOp();
3961    if (addResponse.getResultCode() != ResultCode.SUCCESS_INT_VALUE)
3962    {
3963      throw new LDAPException(ResultCode.valueOf(addResponse.getResultCode()),
3964           addResponse.getDiagnosticMessage(), addResponse.getMatchedDN(),
3965           stringListToArray(addResponse.getReferralURLs()));
3966    }
3967  }
3968
3969
3970
3971  /**
3972   * Attempts to add all of the provided entries to the server.  If an error is
3973   * encountered during processing, then the contents of the server will be the
3974   * same as they were before this method was called.
3975   *
3976   * @param  entries  The collection of entries to be added.
3977   *
3978   * @throws  LDAPException  If a problem was encountered while attempting to
3979   *                         add any of the entries to the server.
3980   */
3981  public synchronized void addEntries(final List<? extends Entry> entries)
3982         throws LDAPException
3983  {
3984    final InMemoryDirectoryServerSnapshot snapshot = createSnapshot();
3985    boolean restoreSnapshot = true;
3986
3987    try
3988    {
3989      for (final Entry e : entries)
3990      {
3991        addEntry(e, false);
3992      }
3993      restoreSnapshot = false;
3994    }
3995    finally
3996    {
3997      if (restoreSnapshot)
3998      {
3999        restoreSnapshot(snapshot);
4000      }
4001    }
4002  }
4003
4004
4005
4006  /**
4007   * Removes the entry with the specified DN and any subordinate entries it may
4008   * have.
4009   *
4010   * @param  baseDN  The DN of the entry to be deleted.  It must not be
4011   *                 {@code null} or represent the null DN.
4012   *
4013   * @return  The number of entries actually removed, or zero if the specified
4014   *          base DN does not represent an entry in the server.
4015   *
4016   * @throws  LDAPException  If the provided base DN is not a valid DN, or is
4017   *                         the DN of an entry that cannot be deleted (e.g.,
4018   *                         the null DN).
4019   */
4020  public synchronized int deleteSubtree(final String baseDN)
4021         throws LDAPException
4022  {
4023    final DN dn = new DN(baseDN, schemaRef.get());
4024    if (dn.isNullDN())
4025    {
4026      throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
4027           ERR_MEM_HANDLER_DELETE_ROOT_DSE.get());
4028    }
4029
4030    int numDeleted = 0;
4031
4032    final Iterator<Map.Entry<DN,ReadOnlyEntry>> iterator =
4033         entryMap.entrySet().iterator();
4034    while (iterator.hasNext())
4035    {
4036      final Map.Entry<DN,ReadOnlyEntry> e = iterator.next();
4037      if (e.getKey().isDescendantOf(dn, true))
4038      {
4039        iterator.remove();
4040        numDeleted++;
4041      }
4042    }
4043
4044    return numDeleted;
4045  }
4046
4047
4048
4049  /**
4050   * Attempts to apply the provided set of modifications to the specified entry.
4051   * The attempt will fail if any of the following conditions is true:
4052   * <UL>
4053   *   <LI>The target DN is malformed.</LI>
4054   *   <LI>The target entry is the root DSE.</LI>
4055   *   <LI>The target entry is the subschema subentry.</LI>
4056   *   <LI>The target entry does not exist.</LI>
4057   *   <LI>Any of the modifications cannot be applied to the entry.</LI>
4058   *   <LI>If a schema was provided, and the entry violates any of the
4059   *       constraints of that schema.</LI>
4060   * </UL>
4061   *
4062   * @param  dn    The DN of the entry to be modified.
4063   * @param  mods  The set of modifications to be applied to the entry.
4064   *
4065   * @throws  LDAPException  If a problem is encountered while attempting to
4066   *                         update the specified entry.
4067   */
4068  public void modifyEntry(final String dn, final List<Modification> mods)
4069         throws LDAPException
4070  {
4071    final ModifyRequestProtocolOp modifyRequest =
4072         new ModifyRequestProtocolOp(dn, mods);
4073
4074    final LDAPMessage resultMessage = processModifyRequest(-1, modifyRequest,
4075         Collections.<Control>emptyList());
4076
4077    final ModifyResponseProtocolOp modifyResponse =
4078         resultMessage.getModifyResponseProtocolOp();
4079    if (modifyResponse.getResultCode() != ResultCode.SUCCESS_INT_VALUE)
4080    {
4081      throw new LDAPException(
4082           ResultCode.valueOf(modifyResponse.getResultCode()),
4083           modifyResponse.getDiagnosticMessage(), modifyResponse.getMatchedDN(),
4084           stringListToArray(modifyResponse.getReferralURLs()));
4085    }
4086  }
4087
4088
4089
4090  /**
4091   * Retrieves a read-only representation the entry with the specified DN, if
4092   * it exists.
4093   *
4094   * @param  dn  The DN of the entry to retrieve.
4095   *
4096   * @return  The requested entry, or {@code null} if no entry exists with the
4097   *          given DN.
4098   *
4099   * @throws  LDAPException  If the provided DN is malformed.
4100   */
4101  public synchronized ReadOnlyEntry getEntry(final String dn)
4102         throws LDAPException
4103  {
4104    return getEntry(new DN(dn, schemaRef.get()));
4105  }
4106
4107
4108
4109  /**
4110   * Retrieves a read-only representation the entry with the specified DN, if
4111   * it exists.
4112   *
4113   * @param  dn  The DN of the entry to retrieve.
4114   *
4115   * @return  The requested entry, or {@code null} if no entry exists with the
4116   *          given DN.
4117   */
4118  public synchronized ReadOnlyEntry getEntry(final DN dn)
4119  {
4120    if (dn.isNullDN())
4121    {
4122      return generateRootDSE();
4123    }
4124    else if (dn.equals(subschemaSubentryDN))
4125    {
4126      return subschemaSubentryRef.get();
4127    }
4128    else
4129    {
4130      final Entry e = entryMap.get(dn);
4131      if (e == null)
4132      {
4133        return null;
4134      }
4135      else
4136      {
4137        return new ReadOnlyEntry(e);
4138      }
4139    }
4140  }
4141
4142
4143
4144  /**
4145   * Retrieves a list of all entries in the server which match the given
4146   * search criteria.
4147   *
4148   * @param  baseDN  The base DN to use for the search.  It must not be
4149   *                 {@code null}.
4150   * @param  scope   The scope to use for the search.  It must not be
4151   *                 {@code null}.
4152   * @param  filter  The filter to use for the search.  It must not be
4153   *                 {@code null}.
4154   *
4155   * @return  A list of the entries that matched the provided search criteria.
4156   *
4157   * @throws  LDAPException  If a problem is encountered while performing the
4158   *                         search.
4159   */
4160  public synchronized List<ReadOnlyEntry> search(final String baseDN,
4161                                                 final SearchScope scope,
4162                                                 final Filter filter)
4163         throws LDAPException
4164  {
4165    final DN parsedDN;
4166    final Schema schema = schemaRef.get();
4167    try
4168    {
4169      parsedDN = new DN(baseDN, schema);
4170    }
4171    catch (final LDAPException le)
4172    {
4173      Debug.debugException(le);
4174      throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
4175           ERR_MEM_HANDLER_SEARCH_MALFORMED_BASE.get(baseDN, le.getMessage()),
4176           le);
4177    }
4178
4179    final ReadOnlyEntry baseEntry;
4180    if (parsedDN.isNullDN())
4181    {
4182      baseEntry = generateRootDSE();
4183    }
4184    else if (parsedDN.equals(subschemaSubentryDN))
4185    {
4186      baseEntry = subschemaSubentryRef.get();
4187    }
4188    else
4189    {
4190      final Entry e = entryMap.get(parsedDN);
4191      if (e == null)
4192      {
4193        throw new LDAPException(ResultCode.NO_SUCH_OBJECT,
4194             ERR_MEM_HANDLER_SEARCH_BASE_DOES_NOT_EXIST.get(baseDN),
4195             getMatchedDNString(parsedDN), null);
4196      }
4197
4198      baseEntry = new ReadOnlyEntry(e);
4199    }
4200
4201    if (scope == SearchScope.BASE)
4202    {
4203      final List<ReadOnlyEntry> entryList = new ArrayList<ReadOnlyEntry>(1);
4204
4205      try
4206      {
4207        if (filter.matchesEntry(baseEntry, schema))
4208        {
4209          entryList.add(baseEntry);
4210        }
4211      }
4212      catch (final LDAPException le)
4213      {
4214        Debug.debugException(le);
4215      }
4216
4217      return Collections.unmodifiableList(entryList);
4218    }
4219
4220    if ((scope == SearchScope.ONE) && parsedDN.isNullDN())
4221    {
4222      final List<ReadOnlyEntry> entryList =
4223           new ArrayList<ReadOnlyEntry>(baseDNs.size());
4224
4225      try
4226      {
4227        for (final DN dn : baseDNs)
4228        {
4229          final Entry e = entryMap.get(dn);
4230          if ((e != null) && filter.matchesEntry(e, schema))
4231          {
4232            entryList.add(new ReadOnlyEntry(e));
4233          }
4234        }
4235      }
4236      catch (final LDAPException le)
4237      {
4238        Debug.debugException(le);
4239      }
4240
4241      return Collections.unmodifiableList(entryList);
4242    }
4243
4244    final List<ReadOnlyEntry> entryList = new ArrayList<ReadOnlyEntry>(10);
4245    for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet())
4246    {
4247      final DN dn = me.getKey();
4248      if (dn.matchesBaseAndScope(parsedDN, scope))
4249      {
4250        // We don't want to return changelog entries searches based at the
4251        // root DSE.
4252        if (parsedDN.isNullDN() && dn.isDescendantOf(changeLogBaseDN, true))
4253        {
4254          continue;
4255        }
4256
4257        try
4258        {
4259          final Entry entry = me.getValue();
4260          if (filter.matchesEntry(entry, schema))
4261          {
4262            entryList.add(new ReadOnlyEntry(entry));
4263          }
4264        }
4265        catch (final LDAPException le)
4266        {
4267          Debug.debugException(le);
4268        }
4269      }
4270    }
4271
4272    return Collections.unmodifiableList(entryList);
4273  }
4274
4275
4276
4277  /**
4278   * Generates an entry to use as the server root DSE.
4279   *
4280   * @return  The generated root DSE entry.
4281   */
4282  private ReadOnlyEntry generateRootDSE()
4283  {
4284    final Entry rootDSEEntry = new Entry(DN.NULL_DN, schemaRef.get());
4285    rootDSEEntry.addAttribute("objectClass", "top", "ds-root-dse");
4286    rootDSEEntry.addAttribute(new Attribute("supportedLDAPVersion",
4287         IntegerMatchingRule.getInstance(), "3"));
4288
4289    final String vendorName = config.getVendorName();
4290    if (vendorName != null)
4291    {
4292      rootDSEEntry.addAttribute("vendorName", vendorName);
4293    }
4294
4295    final String vendorVersion = config.getVendorVersion();
4296    if (vendorVersion != null)
4297    {
4298      rootDSEEntry.addAttribute("vendorVersion", vendorVersion);
4299    }
4300
4301    rootDSEEntry.addAttribute(new Attribute("subschemaSubentry",
4302         DistinguishedNameMatchingRule.getInstance(),
4303         subschemaSubentryDN.toString()));
4304    rootDSEEntry.addAttribute(new Attribute("entryDN",
4305         DistinguishedNameMatchingRule.getInstance(), ""));
4306    rootDSEEntry.addAttribute("entryUUID", UUID.randomUUID().toString());
4307
4308    rootDSEEntry.addAttribute("supportedFeatures",
4309         "1.3.6.1.4.1.4203.1.5.1",  // All operational attributes
4310         "1.3.6.1.4.1.4203.1.5.2",  // Request attributes by object class
4311         "1.3.6.1.4.1.4203.1.5.3",  // LDAP absolute true and false filters
4312         "1.3.6.1.1.14");           // Increment modification type
4313
4314    final TreeSet<String> ctlSet = new TreeSet<String>();
4315
4316    ctlSet.add(AssertionRequestControl.ASSERTION_REQUEST_OID);
4317    ctlSet.add(AuthorizationIdentityRequestControl.
4318         AUTHORIZATION_IDENTITY_REQUEST_OID);
4319    ctlSet.add(DontUseCopyRequestControl.DONT_USE_COPY_REQUEST_OID);
4320    ctlSet.add(ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID);
4321    ctlSet.add(PermissiveModifyRequestControl.PERMISSIVE_MODIFY_REQUEST_OID);
4322    ctlSet.add(PostReadRequestControl.POST_READ_REQUEST_OID);
4323    ctlSet.add(PreReadRequestControl.PRE_READ_REQUEST_OID);
4324    ctlSet.add(ProxiedAuthorizationV1RequestControl.
4325         PROXIED_AUTHORIZATION_V1_REQUEST_OID);
4326    ctlSet.add(ProxiedAuthorizationV2RequestControl.
4327         PROXIED_AUTHORIZATION_V2_REQUEST_OID);
4328    ctlSet.add(ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID);
4329    ctlSet.add(SimplePagedResultsControl.PAGED_RESULTS_OID);
4330    ctlSet.add(SubentriesRequestControl.SUBENTRIES_REQUEST_OID);
4331    ctlSet.add(SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID);
4332    ctlSet.add(TransactionSpecificationRequestControl.
4333         TRANSACTION_SPECIFICATION_REQUEST_OID);
4334    ctlSet.add(VirtualListViewRequestControl.VIRTUAL_LIST_VIEW_REQUEST_OID);
4335
4336    final String[] controlOIDs = new String[ctlSet.size()];
4337    rootDSEEntry.addAttribute("supportedControl", ctlSet.toArray(controlOIDs));
4338
4339
4340    if (! extendedRequestHandlers.isEmpty())
4341    {
4342      final String[] oidArray = new String[extendedRequestHandlers.size()];
4343      rootDSEEntry.addAttribute("supportedExtension",
4344           extendedRequestHandlers.keySet().toArray(oidArray));
4345
4346      for (final InMemoryListenerConfig c : config.getListenerConfigs())
4347      {
4348        if (c.getStartTLSSocketFactory() != null)
4349        {
4350          rootDSEEntry.addAttribute("supportedExtension",
4351               StartTLSExtendedRequest.STARTTLS_REQUEST_OID);
4352          break;
4353        }
4354      }
4355    }
4356
4357    if (! saslBindHandlers.isEmpty())
4358    {
4359      final String[] mechanismArray = new String[saslBindHandlers.size()];
4360      rootDSEEntry.addAttribute("supportedSASLMechanisms",
4361           saslBindHandlers.keySet().toArray(mechanismArray));
4362    }
4363
4364    int pos = 0;
4365    final String[] baseDNStrings = new String[baseDNs.size()];
4366    for (final DN baseDN : baseDNs)
4367    {
4368      baseDNStrings[pos++] = baseDN.toString();
4369    }
4370    rootDSEEntry.addAttribute(new Attribute("namingContexts",
4371         DistinguishedNameMatchingRule.getInstance(), baseDNStrings));
4372
4373    if (maxChangelogEntries > 0)
4374    {
4375      rootDSEEntry.addAttribute(new Attribute("changeLog",
4376           DistinguishedNameMatchingRule.getInstance(),
4377           changeLogBaseDN.toString()));
4378      rootDSEEntry.addAttribute(new Attribute("firstChangeNumber",
4379           IntegerMatchingRule.getInstance(), firstChangeNumber.toString()));
4380      rootDSEEntry.addAttribute(new Attribute("lastChangeNumber",
4381           IntegerMatchingRule.getInstance(), lastChangeNumber.toString()));
4382    }
4383
4384    return new ReadOnlyEntry(rootDSEEntry);
4385  }
4386
4387
4388
4389  /**
4390   * Generates a subschema subentry from the provided schema object.
4391   *
4392   * @param  schema  The schema to use to generate the subschema subentry.  It
4393   *                 may be {@code null} if a minimal default entry should be
4394   *                 generated.
4395   *
4396   * @return  The generated subschema subentry.
4397   */
4398  private static ReadOnlyEntry generateSubschemaSubentry(final Schema schema)
4399  {
4400    final Entry e;
4401
4402    if (schema == null)
4403    {
4404      e = new Entry("cn=schema", schema);
4405
4406      e.addAttribute("objectClass", "namedObject", "ldapSubEntry",
4407           "subschema");
4408      e.addAttribute("cn", "schema");
4409    }
4410    else
4411    {
4412      e = schema.getSchemaEntry().duplicate();
4413    }
4414
4415    try
4416    {
4417      e.addAttribute("entryDN", DN.normalize(e.getDN(), schema));
4418    }
4419    catch (final LDAPException le)
4420    {
4421      // This should never happen.
4422      Debug.debugException(le);
4423      e.setAttribute("entryDN", StaticUtils.toLowerCase(e.getDN()));
4424    }
4425
4426
4427    e.addAttribute("entryUUID", UUID.randomUUID().toString());
4428    return new ReadOnlyEntry(e);
4429  }
4430
4431
4432
4433  /**
4434   * Processes the set of requested attributes from the given search request.
4435   *
4436   * @param  attrList      The list of requested attributes to examine.
4437   * @param  allUserAttrs  Indicates whether to return all user attributes.  It
4438   *                       should have an initial value of {@code false}.
4439   * @param  allOpAttrs    Indicates whether to return all operational
4440   *                       attributes.  It should have an initial value of
4441   *                       {@code false}.
4442   *
4443   * @return  A map of specific attribute types to be returned.  The keys of the
4444   *          map will be the lowercase OID and names of the attribute types,
4445   *          and the values will be a list of option sets for the associated
4446   *          attribute type.
4447   */
4448  private Map<String,List<List<String>>> processRequestedAttributes(
4449               final List<String> attrList, final AtomicBoolean allUserAttrs,
4450               final AtomicBoolean allOpAttrs)
4451  {
4452    if (attrList.isEmpty())
4453    {
4454      allUserAttrs.set(true);
4455      return Collections.emptyMap();
4456    }
4457
4458    final Schema schema = schemaRef.get();
4459    final HashMap<String,List<List<String>>> m =
4460         new HashMap<String,List<List<String>>>(attrList.size() * 2);
4461    for (final String s : attrList)
4462    {
4463      if (s.equals("*"))
4464      {
4465        // All user attributes.
4466        allUserAttrs.set(true);
4467      }
4468      else if (s.equals("+"))
4469      {
4470        // All operational attributes.
4471        allOpAttrs.set(true);
4472      }
4473      else if (s.startsWith("@"))
4474      {
4475        // Return attributes by object class.  This can only be supported if a
4476        // schema has been defined.
4477        if (schema != null)
4478        {
4479          final String ocName = s.substring(1);
4480          final ObjectClassDefinition oc = schema.getObjectClass(ocName);
4481          if (oc != null)
4482          {
4483            for (final AttributeTypeDefinition at :
4484                 oc.getRequiredAttributes(schema, true))
4485            {
4486              addAttributeOIDAndNames(at, m, Collections.<String>emptyList());
4487            }
4488            for (final AttributeTypeDefinition at :
4489                 oc.getOptionalAttributes(schema, true))
4490            {
4491              addAttributeOIDAndNames(at, m, Collections.<String>emptyList());
4492            }
4493          }
4494        }
4495      }
4496      else
4497      {
4498        final ObjectPair<String,List<String>> nameWithOptions =
4499             getNameWithOptions(s);
4500        if (nameWithOptions == null)
4501        {
4502          continue;
4503        }
4504
4505        final String name = nameWithOptions.getFirst();
4506        final List<String> options = nameWithOptions.getSecond();
4507
4508        if (schema == null)
4509        {
4510          // Just use the name as provided.
4511          List<List<String>> optionLists = m.get(name);
4512          if (optionLists == null)
4513          {
4514            optionLists = new ArrayList<List<String>>(1);
4515            m.put(name, optionLists);
4516          }
4517          optionLists.add(options);
4518        }
4519        else
4520        {
4521          // If the attribute type is defined in the schema, then use it to get
4522          // all names and the OID.  Otherwise, just use the name as provided.
4523          final AttributeTypeDefinition at = schema.getAttributeType(name);
4524          if (at == null)
4525          {
4526            List<List<String>> optionLists = m.get(name);
4527            if (optionLists == null)
4528            {
4529              optionLists = new ArrayList<List<String>>(1);
4530              m.put(name, optionLists);
4531            }
4532            optionLists.add(options);
4533          }
4534          else
4535          {
4536            addAttributeOIDAndNames(at, m, options);
4537          }
4538        }
4539      }
4540    }
4541
4542    return m;
4543  }
4544
4545
4546
4547  /**
4548   * Parses the provided string into an attribute type and set of options.
4549   *
4550   * @param  s  The string to be parsed.
4551   *
4552   * @return  An {@code ObjectPair} in which the first element is the attribute
4553   *          type name and the second is the list of options (or an empty
4554   *          list if there are no options).  Alternately, a value of
4555   *          {@code null} may be returned if the provided string does not
4556   *          represent a valid attribute type description.
4557   */
4558  private static ObjectPair<String,List<String>> getNameWithOptions(
4559                                                      final String s)
4560  {
4561    if (! Attribute.nameIsValid(s, true))
4562    {
4563      return null;
4564    }
4565
4566    final String l = StaticUtils.toLowerCase(s);
4567
4568    int semicolonPos = l.indexOf(';');
4569    if (semicolonPos < 0)
4570    {
4571      return new ObjectPair<String,List<String>>(l,
4572           Collections.<String>emptyList());
4573    }
4574
4575    final String name = l.substring(0, semicolonPos);
4576    final ArrayList<String> optionList = new ArrayList<String>(1);
4577    while (true)
4578    {
4579      final int nextSemicolonPos = l.indexOf(';', semicolonPos+1);
4580      if (nextSemicolonPos < 0)
4581      {
4582        optionList.add(l.substring(semicolonPos+1));
4583        break;
4584      }
4585      else
4586      {
4587        optionList.add(l.substring(semicolonPos+1, nextSemicolonPos));
4588        semicolonPos = nextSemicolonPos;
4589      }
4590    }
4591
4592    return new ObjectPair<String,List<String>>(name, optionList);
4593  }
4594
4595
4596
4597  /**
4598   * Adds all-lowercase versions of the OID and all names for the provided
4599   * attribute type definition to the given map with the given options.
4600   *
4601   * @param  d  The attribute type definition to process.
4602   * @param  m  The map to which the OID and names should be added.
4603   * @param  o  The array of attribute options to use in the map.  It should be
4604   *            empty if no options are needed, and must not be {@code null}.
4605   */
4606  private void addAttributeOIDAndNames(final AttributeTypeDefinition d,
4607                                       final Map<String,List<List<String>>> m,
4608                                       final List<String> o)
4609  {
4610    if (d == null)
4611    {
4612      return;
4613    }
4614
4615    final String lowerOID = StaticUtils.toLowerCase(d.getOID());
4616    if (lowerOID != null)
4617    {
4618      List<List<String>> l = m.get(lowerOID);
4619      if (l == null)
4620      {
4621        l = new ArrayList<List<String>>(1);
4622        m.put(lowerOID, l);
4623      }
4624
4625      l.add(o);
4626    }
4627
4628    for (final String name : d.getNames())
4629    {
4630      final String lowerName = StaticUtils.toLowerCase(name);
4631      List<List<String>> l = m.get(lowerName);
4632      if (l == null)
4633      {
4634        l = new ArrayList<List<String>>(1);
4635        m.put(lowerName, l);
4636      }
4637
4638      l.add(o);
4639    }
4640
4641    // If a schema is available, then see if the attribute type has any
4642    // subordinate types.  If so, then add them.
4643    final Schema schema = schemaRef.get();
4644    if (schema != null)
4645    {
4646      for (final AttributeTypeDefinition subordinateType :
4647           schema.getSubordinateAttributeTypes(d))
4648      {
4649        addAttributeOIDAndNames(subordinateType, m, o);
4650      }
4651    }
4652  }
4653
4654
4655
4656  /**
4657   * Performs the necessary processing to determine whether the given entry
4658   * should be returned as a search result entry or reference, or if it should
4659   * not be returned at all.
4660   *
4661   * @param  entry              The entry to be processed.
4662   * @param  includeSubEntries  Indicates whether LDAP subentries should be
4663   *                            returned to the client.
4664   * @param  includeChangeLog   Indicates whether entries within the changelog
4665   *                            should be returned to the client.
4666   * @param  hasManageDsaIT     Indicates whether the request includes the
4667   *                            ManageDsaIT control, which can change how smart
4668   *                            referrals should be handled.
4669   * @param  entryList          The list to which the entry should be added if
4670   *                            it should be returned to the client as a search
4671   *                            result entry.
4672   * @param  referenceList      The list that should be updated if the provided
4673   *                            entry represents a smart referral that should be
4674   *                            returned as a search result reference.
4675   */
4676  private void processSearchEntry(final Entry entry,
4677                    final boolean includeSubEntries,
4678                    final boolean includeChangeLog,
4679                    final boolean hasManageDsaIT,
4680                    final List<Entry> entryList,
4681                    final List<SearchResultReference> referenceList)
4682  {
4683    // See if the entry should be suppressed as an LDAP subentry.
4684    if ((! includeSubEntries) &&
4685        (entry.hasObjectClass("ldapSubEntry") ||
4686         entry.hasObjectClass("inheritableLDAPSubEntry")))
4687    {
4688      return;
4689    }
4690
4691    // See if the entry should be suppressed as a changelog entry.
4692    try
4693    {
4694      if ((! includeChangeLog) &&
4695           (entry.getParsedDN().isDescendantOf(changeLogBaseDN, true)))
4696      {
4697        return;
4698      }
4699    }
4700    catch (final Exception e)
4701    {
4702      // This should never happen.
4703      Debug.debugException(e);
4704    }
4705
4706    // See if the entry is a referral and should result in a reference rather
4707    // than an entry.
4708    if ((! hasManageDsaIT) && entry.hasObjectClass("referral") &&
4709        entry.hasAttribute("ref"))
4710    {
4711      referenceList.add(new SearchResultReference(
4712           entry.getAttributeValues("ref"), NO_CONTROLS));
4713      return;
4714    }
4715
4716    entryList.add(entry);
4717  }
4718
4719
4720
4721  /**
4722   * Retrieves a copy of the provided entry that includes only the appropriate
4723   * set of requested attributes.
4724   *
4725   * @param  entry         The entry to be returned.
4726   * @param  allUserAttrs  Indicates whether to return all user attributes.
4727   * @param  allOpAttrs    Indicates whether to return all operational
4728   *                       attributes.
4729   * @param  returnAttrs   A map with information about the specific attribute
4730   *                       types to return.
4731   *
4732   * @return  A copy of the provided entry that includes only the appropriate
4733   *          set of requested attributes.
4734   */
4735  private Entry trimForRequestedAttributes(final Entry entry,
4736                     final boolean allUserAttrs, final boolean allOpAttrs,
4737                     final Map<String,List<List<String>>> returnAttrs)
4738  {
4739    // See if we can return the entry without paring it down.
4740    final Schema schema = schemaRef.get();
4741    if (allUserAttrs)
4742    {
4743      if (allOpAttrs || (schema == null))
4744      {
4745        return entry;
4746      }
4747    }
4748
4749
4750    // If we've gotten here, then we may only need to return a partial entry.
4751    final Entry copy = new Entry(entry.getDN(), schema);
4752
4753    for (final Attribute a : entry.getAttributes())
4754    {
4755      final ObjectPair<String,List<String>> nameWithOptions =
4756           getNameWithOptions(a.getName());
4757      final String name = nameWithOptions.getFirst();
4758      final List<String> options = nameWithOptions.getSecond();
4759
4760      // If there is a schema, then see if it is an operational attribute, since
4761      // that needs to be handled in a manner different from user attributes
4762      if (schema != null)
4763      {
4764        final AttributeTypeDefinition at = schema.getAttributeType(name);
4765        if ((at != null) && at.isOperational())
4766        {
4767          if (allOpAttrs)
4768          {
4769            copy.addAttribute(a);
4770            continue;
4771          }
4772
4773          final List<List<String>> optionLists = returnAttrs.get(name);
4774          if (optionLists == null)
4775          {
4776            continue;
4777          }
4778
4779          for (final List<String> optionList : optionLists)
4780          {
4781            boolean matchAll = true;
4782            for (final String option : optionList)
4783            {
4784              if (! options.contains(option))
4785              {
4786                matchAll = false;
4787                break;
4788              }
4789            }
4790
4791            if (matchAll)
4792            {
4793              copy.addAttribute(a);
4794              break;
4795            }
4796          }
4797          continue;
4798        }
4799      }
4800
4801      // We'll assume that it's a user attribute, and we'll look for an exact
4802      // match on the base name.
4803      if (allUserAttrs)
4804      {
4805        copy.addAttribute(a);
4806        continue;
4807      }
4808
4809      final List<List<String>> optionLists = returnAttrs.get(name);
4810      if (optionLists == null)
4811      {
4812        continue;
4813      }
4814
4815      for (final List<String> optionList : optionLists)
4816      {
4817        boolean matchAll = true;
4818        for (final String option : optionList)
4819        {
4820          if (! options.contains(option))
4821          {
4822            matchAll = false;
4823            break;
4824          }
4825        }
4826
4827        if (matchAll)
4828        {
4829          copy.addAttribute(a);
4830          break;
4831        }
4832      }
4833    }
4834
4835    return copy;
4836  }
4837
4838
4839
4840  /**
4841   * Retrieves the DN of the existing entry which is the closest hierarchical
4842   * match to the provided DN.
4843   *
4844   * @param  dn  The DN for which to retrieve the appropriate matched DN.
4845   *
4846   * @return  The appropriate matched DN value, or {@code null} if there is
4847   *          none.
4848   */
4849  private String getMatchedDNString(final DN dn)
4850  {
4851    DN parentDN = dn.getParent();
4852    while (parentDN != null)
4853    {
4854      if (entryMap.containsKey(parentDN))
4855      {
4856        return parentDN.toString();
4857      }
4858
4859      parentDN = parentDN.getParent();
4860    }
4861
4862    return null;
4863  }
4864
4865
4866
4867  /**
4868   * Converts the provided string list to an array.
4869   *
4870   * @param  l  The possibly null list to be converted.
4871   *
4872   * @return  The string array with the same elements as the given list in the
4873   *          same order, or {@code null} if the given list was null.
4874   */
4875  private static String[] stringListToArray(final List<String> l)
4876  {
4877    if (l == null)
4878    {
4879      return null;
4880    }
4881    else
4882    {
4883      final String[] a = new String[l.size()];
4884      return l.toArray(a);
4885    }
4886  }
4887
4888
4889
4890  /**
4891   * Creates a changelog entry from the information in the provided add request
4892   * and adds it to the server changelog.
4893   *
4894   * @param  addRequest  The add request to use to construct the changelog
4895   *                     entry.
4896   * @param  authzDN     The authorization DN for the change.
4897   */
4898  private void addChangeLogEntry(final AddRequestProtocolOp addRequest,
4899                                 final DN authzDN)
4900  {
4901    // If the changelog is disabled, then don't do anything.
4902    if (maxChangelogEntries <= 0)
4903    {
4904      return;
4905    }
4906
4907    final long changeNumber = lastChangeNumber.incrementAndGet();
4908    final LDIFAddChangeRecord changeRecord = new LDIFAddChangeRecord(
4909         addRequest.getDN(), addRequest.getAttributes());
4910    try
4911    {
4912      addChangeLogEntry(
4913           ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord),
4914           authzDN);
4915    }
4916    catch (final LDAPException le)
4917    {
4918      // This should not happen.
4919      Debug.debugException(le);
4920    }
4921  }
4922
4923
4924
4925  /**
4926   * Creates a changelog entry from the information in the provided delete
4927   * request and adds it to the server changelog.
4928   *
4929   * @param  e        The entry to be deleted.
4930   * @param  authzDN  The authorization DN for the change.
4931   */
4932  private void addDeleteChangeLogEntry(final Entry e, final DN authzDN)
4933  {
4934    // If the changelog is disabled, then don't do anything.
4935    if (maxChangelogEntries <= 0)
4936    {
4937      return;
4938    }
4939
4940    final long changeNumber = lastChangeNumber.incrementAndGet();
4941    final LDIFDeleteChangeRecord changeRecord =
4942         new LDIFDeleteChangeRecord(e.getDN());
4943
4944    // Create the changelog entry.
4945    try
4946    {
4947      final ChangeLogEntry cle = ChangeLogEntry.constructChangeLogEntry(
4948           changeNumber, changeRecord);
4949
4950      // Add a set of deleted entry attributes, which is simply an LDIF-encoded
4951      // representation of the entry, excluding the first line since it contains
4952      // the DN.
4953      final StringBuilder deletedEntryAttrsBuffer = new StringBuilder();
4954      final String[] ldifLines = e.toLDIF(0);
4955      for (int i=1; i < ldifLines.length; i++)
4956      {
4957        deletedEntryAttrsBuffer.append(ldifLines[i]);
4958        deletedEntryAttrsBuffer.append(StaticUtils.EOL);
4959      }
4960
4961      final Entry copy = cle.duplicate();
4962      copy.addAttribute(ChangeLogEntry.ATTR_DELETED_ENTRY_ATTRS,
4963           deletedEntryAttrsBuffer.toString());
4964      addChangeLogEntry(new ChangeLogEntry(copy), authzDN);
4965    }
4966    catch (final LDAPException le)
4967    {
4968      // This should never happen.
4969      Debug.debugException(le);
4970    }
4971  }
4972
4973
4974
4975  /**
4976   * Creates a changelog entry from the information in the provided modify
4977   * request and adds it to the server changelog.
4978   *
4979   * @param  modifyRequest  The modify request to use to construct the changelog
4980   *                        entry.
4981   * @param  authzDN        The authorization DN for the change.
4982   */
4983  private void addChangeLogEntry(final ModifyRequestProtocolOp modifyRequest,
4984                                 final DN authzDN)
4985  {
4986    // If the changelog is disabled, then don't do anything.
4987    if (maxChangelogEntries <= 0)
4988    {
4989      return;
4990    }
4991
4992    final long changeNumber = lastChangeNumber.incrementAndGet();
4993    final LDIFModifyChangeRecord changeRecord =
4994         new LDIFModifyChangeRecord(modifyRequest.getDN(),
4995              modifyRequest.getModifications());
4996    try
4997    {
4998      addChangeLogEntry(
4999           ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord),
5000           authzDN);
5001    }
5002    catch (final LDAPException le)
5003    {
5004      // This should not happen.
5005      Debug.debugException(le);
5006    }
5007  }
5008
5009
5010
5011  /**
5012   * Creates a changelog entry from the information in the provided modify DN
5013   * request and adds it to the server changelog.
5014   *
5015   * @param  modifyDNRequest  The modify DN request to use to construct the
5016   *                          changelog entry.
5017   * @param  authzDN          The authorization DN for the change.
5018   */
5019  private void addChangeLogEntry(
5020                    final ModifyDNRequestProtocolOp modifyDNRequest,
5021                    final DN authzDN)
5022  {
5023    // If the changelog is disabled, then don't do anything.
5024    if (maxChangelogEntries <= 0)
5025    {
5026      return;
5027    }
5028
5029    final long changeNumber = lastChangeNumber.incrementAndGet();
5030    final LDIFModifyDNChangeRecord changeRecord =
5031         new LDIFModifyDNChangeRecord(modifyDNRequest.getDN(),
5032              modifyDNRequest.getNewRDN(), modifyDNRequest.deleteOldRDN(),
5033              modifyDNRequest.getNewSuperiorDN());
5034    try
5035    {
5036      addChangeLogEntry(
5037           ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord),
5038           authzDN);
5039    }
5040    catch (final LDAPException le)
5041    {
5042      // This should not happen.
5043      Debug.debugException(le);
5044    }
5045  }
5046
5047
5048
5049  /**
5050   * Adds the provided changelog entry to the data set, removing an old entry if
5051   * necessary to remain within the maximum allowed number of changes.  This
5052   * must only be called from a synchronized method, and the change number for
5053   * the changelog entry must have been obtained by calling
5054   * {@code lastChangeNumber.incrementAndGet()}.
5055   *
5056   * @param  e        The changelog entry to add to the data set.
5057   * @param  authzDN  The authorization DN for the change.
5058   */
5059  private void addChangeLogEntry(final ChangeLogEntry e, final DN authzDN)
5060  {
5061    // Construct the DN object to use for the entry and put it in the map.
5062    final long changeNumber = e.getChangeNumber();
5063    final Schema schema = schemaRef.get();
5064    final DN dn = new DN(
5065         new RDN("changeNumber", String.valueOf(changeNumber), schema),
5066         changeLogBaseDN);
5067
5068    final Entry entry = e.duplicate();
5069    if (generateOperationalAttributes)
5070    {
5071      final Date d = new Date();
5072      entry.addAttribute(new Attribute("entryDN",
5073           DistinguishedNameMatchingRule.getInstance(),
5074           dn.toNormalizedString()));
5075      entry.addAttribute(new Attribute("entryUUID",
5076           UUID.randomUUID().toString()));
5077      entry.addAttribute(new Attribute("subschemaSubentry",
5078           DistinguishedNameMatchingRule.getInstance(),
5079           subschemaSubentryDN.toString()));
5080      entry.addAttribute(new Attribute("creatorsName",
5081           DistinguishedNameMatchingRule.getInstance(),
5082           authzDN.toString()));
5083      entry.addAttribute(new Attribute("createTimestamp",
5084           GeneralizedTimeMatchingRule.getInstance(),
5085           StaticUtils.encodeGeneralizedTime(d)));
5086      entry.addAttribute(new Attribute("modifiersName",
5087           DistinguishedNameMatchingRule.getInstance(),
5088           authzDN.toString()));
5089      entry.addAttribute(new Attribute("modifyTimestamp",
5090           GeneralizedTimeMatchingRule.getInstance(),
5091           StaticUtils.encodeGeneralizedTime(d)));
5092    }
5093
5094    entryMap.put(dn, new ReadOnlyEntry(entry));
5095    indexAdd(entry);
5096
5097    // Update the first change number and/or trim the changelog if necessary.
5098    final long firstNumber = firstChangeNumber.get();
5099    if (changeNumber == 1L)
5100    {
5101      // It's the first change, so we need to set the first change number.
5102      firstChangeNumber.set(1);
5103    }
5104    else
5105    {
5106      // See if we need to trim an entry.
5107      final long numChangeLogEntries = changeNumber - firstNumber + 1;
5108      if (numChangeLogEntries > maxChangelogEntries)
5109      {
5110        // We need to delete the first changelog entry and increment the
5111        // first change number.
5112        firstChangeNumber.incrementAndGet();
5113        final Entry deletedEntry = entryMap.remove(new DN(
5114             new RDN("changeNumber", String.valueOf(firstNumber), schema),
5115             changeLogBaseDN));
5116        indexDelete(deletedEntry);
5117      }
5118    }
5119  }
5120
5121
5122
5123  /**
5124   * Checks to see if the provided control map includes a proxied authorization
5125   * control (v1 or v2) and if so then attempts to determine the appropriate
5126   * authorization identity to use for the operation.
5127   *
5128   * @param  m  The map of request controls, indexed by OID.
5129   *
5130   * @return  The DN of the authorized user, or the current authentication DN
5131   *          if the control map does not include a proxied authorization
5132   *          request control.
5133   *
5134   * @throws  LDAPException  If a problem is encountered while attempting to
5135   *                         determine the authorization DN.
5136   */
5137  private DN handleProxiedAuthControl(final Map<String,Control> m)
5138          throws LDAPException
5139  {
5140    final ProxiedAuthorizationV1RequestControl p1 =
5141         (ProxiedAuthorizationV1RequestControl) m.get(
5142              ProxiedAuthorizationV1RequestControl.
5143                   PROXIED_AUTHORIZATION_V1_REQUEST_OID);
5144    if (p1 != null)
5145    {
5146      final DN authzDN = new DN(p1.getProxyDN(), schemaRef.get());
5147      if (authzDN.isNullDN() ||
5148          entryMap.containsKey(authzDN) ||
5149          additionalBindCredentials.containsKey(authzDN))
5150      {
5151        return authzDN;
5152      }
5153      else
5154      {
5155        throw new LDAPException(ResultCode.AUTHORIZATION_DENIED,
5156             ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get("dn:" + authzDN.toString()));
5157      }
5158    }
5159
5160    final ProxiedAuthorizationV2RequestControl p2 =
5161         (ProxiedAuthorizationV2RequestControl) m.get(
5162              ProxiedAuthorizationV2RequestControl.
5163                   PROXIED_AUTHORIZATION_V2_REQUEST_OID);
5164    if (p2 != null)
5165    {
5166      return getDNForAuthzID(p2.getAuthorizationID());
5167    }
5168
5169    return authenticatedDN;
5170  }
5171
5172
5173
5174  /**
5175   * Attempts to identify the DN of the user referenced by the provided
5176   * authorization ID string.  It may be "dn:" followed by the target DN, or
5177   * "u:" followed by the value of the uid attribute in the entry.  If it uses
5178   * the "dn:" form, then it may reference the DN of a regular entry or a DN
5179   * in the configured set of additional bind credentials.
5180   *
5181   * @param  authzID  The authorization ID to resolve to a user DN.
5182   *
5183   * @return  The DN identified for the provided authorization ID.
5184   *
5185   * @throws  LDAPException  If a problem prevents resolving the authorization
5186   *                         ID to a user DN.
5187   */
5188  public synchronized DN getDNForAuthzID(final String authzID)
5189         throws LDAPException
5190  {
5191    final String lowerAuthzID = StaticUtils.toLowerCase(authzID);
5192    if (lowerAuthzID.startsWith("dn:"))
5193    {
5194      if (lowerAuthzID.equals("dn:"))
5195      {
5196        return DN.NULL_DN;
5197      }
5198      else
5199      {
5200        final DN dn = new DN(authzID.substring(3), schemaRef.get());
5201        if (entryMap.containsKey(dn) ||
5202            additionalBindCredentials.containsKey(dn))
5203        {
5204          return dn;
5205        }
5206        else
5207        {
5208          throw new LDAPException(ResultCode.AUTHORIZATION_DENIED,
5209               ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID));
5210        }
5211      }
5212    }
5213    else if (lowerAuthzID.startsWith("u:"))
5214    {
5215      final Filter f =
5216           Filter.createEqualityFilter("uid", authzID.substring(2));
5217      final List<ReadOnlyEntry> entryList = search("", SearchScope.SUB, f);
5218      if (entryList.size() == 1)
5219      {
5220        return entryList.get(0).getParsedDN();
5221      }
5222      else
5223      {
5224        throw new LDAPException(ResultCode.AUTHORIZATION_DENIED,
5225             ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID));
5226      }
5227    }
5228    else
5229    {
5230      throw new LDAPException(ResultCode.AUTHORIZATION_DENIED,
5231           ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID));
5232    }
5233  }
5234
5235
5236
5237  /**
5238   * Checks to see if the provided control map includes an assertion request
5239   * control, and if so then checks to see whether the provided entry satisfies
5240   * the filter in that control.
5241   *
5242   * @param  m  The map of request controls, indexed by OID.
5243   * @param  e  The entry to examine against the assertion filter.
5244   *
5245   * @throws  LDAPException  If the control map includes an assertion request
5246   *                         control and the provided entry does not match the
5247   *                         filter contained in that control.
5248   */
5249  private static void handleAssertionRequestControl(final Map<String,Control> m,
5250                                                    final Entry e)
5251          throws LDAPException
5252  {
5253    final AssertionRequestControl c = (AssertionRequestControl)
5254         m.get(AssertionRequestControl.ASSERTION_REQUEST_OID);
5255    if (c == null)
5256    {
5257      return;
5258    }
5259
5260    try
5261    {
5262      if (c.getFilter().matchesEntry(e))
5263      {
5264        return;
5265      }
5266    }
5267    catch (final LDAPException le)
5268    {
5269      Debug.debugException(le);
5270    }
5271
5272    // If we've gotten here, then the filter doesn't match.
5273    throw new LDAPException(ResultCode.ASSERTION_FAILED,
5274         ERR_MEM_HANDLER_ASSERTION_CONTROL_NOT_SATISFIED.get());
5275  }
5276
5277
5278
5279  /**
5280   * Checks to see if the provided control map includes a pre-read request
5281   * control, and if so then generates the appropriate response control that
5282   * should be returned to the client.
5283   *
5284   * @param  m  The map of request controls, indexed by OID.
5285   * @param  e  The entry as it appeared before the operation.
5286   *
5287   * @return  The pre-read response control that should be returned to the
5288   *          client, or {@code null} if there is none.
5289   */
5290  private PreReadResponseControl handlePreReadControl(
5291               final Map<String,Control> m, final Entry e)
5292  {
5293    final PreReadRequestControl c = (PreReadRequestControl)
5294         m.get(PreReadRequestControl.PRE_READ_REQUEST_OID);
5295    if (c == null)
5296    {
5297      return null;
5298    }
5299
5300    final AtomicBoolean allUserAttrs = new AtomicBoolean(false);
5301    final AtomicBoolean allOpAttrs = new AtomicBoolean(false);
5302    final Map<String,List<List<String>>> returnAttrs =
5303         processRequestedAttributes(Arrays.asList(c.getAttributes()),
5304              allUserAttrs, allOpAttrs);
5305
5306    final Entry trimmedEntry = trimForRequestedAttributes(e, allUserAttrs.get(),
5307         allOpAttrs.get(), returnAttrs);
5308    return new PreReadResponseControl(new ReadOnlyEntry(trimmedEntry));
5309  }
5310
5311
5312
5313  /**
5314   * Checks to see if the provided control map includes a post-read request
5315   * control, and if so then generates the appropriate response control that
5316   * should be returned to the client.
5317   *
5318   * @param  m  The map of request controls, indexed by OID.
5319   * @param  e  The entry as it appeared before the operation.
5320   *
5321   * @return  The post-read response control that should be returned to the
5322   *          client, or {@code null} if there is none.
5323   */
5324  private PostReadResponseControl handlePostReadControl(
5325               final Map<String,Control> m, final Entry e)
5326  {
5327    final PostReadRequestControl c = (PostReadRequestControl)
5328         m.get(PostReadRequestControl.POST_READ_REQUEST_OID);
5329    if (c == null)
5330    {
5331      return null;
5332    }
5333
5334    final AtomicBoolean allUserAttrs = new AtomicBoolean(false);
5335    final AtomicBoolean allOpAttrs = new AtomicBoolean(false);
5336    final Map<String,List<List<String>>> returnAttrs =
5337         processRequestedAttributes(Arrays.asList(c.getAttributes()),
5338              allUserAttrs, allOpAttrs);
5339
5340    final Entry trimmedEntry = trimForRequestedAttributes(e, allUserAttrs.get(),
5341         allOpAttrs.get(), returnAttrs);
5342    return new PostReadResponseControl(new ReadOnlyEntry(trimmedEntry));
5343  }
5344
5345
5346
5347  /**
5348   * Finds the smart referral entry which is hierarchically nearest the entry
5349   * with the given DN.
5350   *
5351   * @param  dn  The DN for which to find the hierarchically nearest smart
5352   *             referral entry.
5353   *
5354   * @return  The hierarchically nearest smart referral entry for the provided
5355   *          DN, or {@code null} if there are no smart referral entries with
5356   *          the provided DN or any of its ancestors.
5357   */
5358  private Entry findNearestReferral(final DN dn)
5359  {
5360    DN d = dn;
5361    while (true)
5362    {
5363      final Entry e = entryMap.get(d);
5364      if (e == null)
5365      {
5366        d = d.getParent();
5367        if (d == null)
5368        {
5369          return null;
5370        }
5371      }
5372      else if (e.hasObjectClass("referral"))
5373      {
5374        return e;
5375      }
5376      else
5377      {
5378        return null;
5379      }
5380    }
5381  }
5382
5383
5384
5385  /**
5386   * Retrieves the referral URLs that should be used for the provided target DN
5387   * based on the given referral entry.
5388   *
5389   * @param  targetDN       The target DN from the associated operation.
5390   * @param  referralEntry  The entry containing the smart referral.
5391   *
5392   * @return  The referral URLs that should be returned.
5393   */
5394  private static List<String> getReferralURLs(final DN targetDN,
5395                                              final Entry referralEntry)
5396  {
5397    final String[] refs = referralEntry.getAttributeValues("ref");
5398    if (refs == null)
5399    {
5400      return null;
5401    }
5402
5403    final RDN[] retainRDNs;
5404    try
5405    {
5406      // If the target DN equals the referral entry DN, or if it's not
5407      // subordinate to the referral entry, then the URLs should be returned
5408      // as-is.
5409      final DN parsedEntryDN = referralEntry.getParsedDN();
5410      if (targetDN.equals(parsedEntryDN) ||
5411          (! targetDN.isDescendantOf(parsedEntryDN, true)))
5412      {
5413        return Arrays.asList(refs);
5414      }
5415
5416      final RDN[] targetRDNs   = targetDN.getRDNs();
5417      final RDN[] refEntryRDNs = referralEntry.getParsedDN().getRDNs();
5418      retainRDNs = new RDN[targetRDNs.length - refEntryRDNs.length];
5419      System.arraycopy(targetRDNs, 0, retainRDNs, 0, retainRDNs.length);
5420    }
5421    catch (final LDAPException le)
5422    {
5423      Debug.debugException(le);
5424      return Arrays.asList(refs);
5425    }
5426
5427    final List<String> refList = new ArrayList<String>(refs.length);
5428    for (final String ref : refs)
5429    {
5430      try
5431      {
5432        final LDAPURL url = new LDAPURL(ref);
5433        final RDN[] refRDNs = url.getBaseDN().getRDNs();
5434        final RDN[] newRefRDNs = new RDN[retainRDNs.length + refRDNs.length];
5435        System.arraycopy(retainRDNs, 0, newRefRDNs, 0, retainRDNs.length);
5436        System.arraycopy(refRDNs, 0, newRefRDNs, retainRDNs.length,
5437             refRDNs.length);
5438        final DN newBaseDN = new DN(newRefRDNs);
5439
5440        final LDAPURL newURL = new LDAPURL(url.getScheme(), url.getHost(),
5441             url.getPort(), newBaseDN, null, null, null);
5442        refList.add(newURL.toString());
5443      }
5444      catch (final LDAPException le)
5445      {
5446        Debug.debugException(le);
5447        refList.add(ref);
5448      }
5449    }
5450
5451    return refList;
5452  }
5453
5454
5455
5456  /**
5457   * Indicates whether the specified entry exists in the server.
5458   *
5459   * @param  dn  The DN of the entry for which to make the determination.
5460   *
5461   * @return  {@code true} if the entry exists, or {@code false} if not.
5462   *
5463   * @throws  LDAPException  If a problem is encountered while trying to
5464   *                         communicate with the directory server.
5465   */
5466  public synchronized boolean entryExists(final String dn)
5467         throws LDAPException
5468  {
5469    return (getEntry(dn) != null);
5470  }
5471
5472
5473
5474  /**
5475   * Indicates whether the specified entry exists in the server and matches the
5476   * given filter.
5477   *
5478   * @param  dn      The DN of the entry for which to make the determination.
5479   * @param  filter  The filter the entry is expected to match.
5480   *
5481   * @return  {@code true} if the entry exists and matches the specified filter,
5482   *          or {@code false} if not.
5483   *
5484   * @throws  LDAPException  If a problem is encountered while trying to
5485   *                         communicate with the directory server.
5486   */
5487  public synchronized boolean entryExists(final String dn, final String filter)
5488         throws LDAPException
5489  {
5490    final Entry e = getEntry(dn);
5491    if (e == null)
5492    {
5493      return false;
5494    }
5495
5496    final Filter f = Filter.create(filter);
5497    try
5498    {
5499      return f.matchesEntry(e, schemaRef.get());
5500    }
5501    catch (final LDAPException le)
5502    {
5503      Debug.debugException(le);
5504      return false;
5505    }
5506  }
5507
5508
5509
5510  /**
5511   * Indicates whether the specified entry exists in the server.  This will
5512   * return {@code true} only if the target entry exists and contains all values
5513   * for all attributes of the provided entry.  The entry will be allowed to
5514   * have attribute values not included in the provided entry.
5515   *
5516   * @param  entry  The entry to compare against the directory server.
5517   *
5518   * @return  {@code true} if the entry exists in the server and is a superset
5519   *          of the provided entry, or {@code false} if not.
5520   *
5521   * @throws  LDAPException  If a problem is encountered while trying to
5522   *                         communicate with the directory server.
5523   */
5524  public synchronized boolean entryExists(final Entry entry)
5525         throws LDAPException
5526  {
5527    final Entry e = getEntry(entry.getDN());
5528    if (e == null)
5529    {
5530      return false;
5531    }
5532
5533    for (final Attribute a : entry.getAttributes())
5534    {
5535      for (final byte[] value : a.getValueByteArrays())
5536      {
5537        if (! e.hasAttributeValue(a.getName(), value))
5538        {
5539          return false;
5540        }
5541      }
5542    }
5543
5544    return true;
5545  }
5546
5547
5548
5549  /**
5550   * Ensures that an entry with the provided DN exists in the directory.
5551   *
5552   * @param  dn  The DN of the entry for which to make the determination.
5553   *
5554   * @throws  LDAPException  If a problem is encountered while trying to
5555   *                         communicate with the directory server.
5556   *
5557   * @throws  AssertionError  If the target entry does not exist.
5558   */
5559  public synchronized void assertEntryExists(final String dn)
5560         throws LDAPException, AssertionError
5561  {
5562    final Entry e = getEntry(dn);
5563    if (e == null)
5564    {
5565      throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5566    }
5567  }
5568
5569
5570
5571  /**
5572   * Ensures that an entry with the provided DN exists in the directory.
5573   *
5574   * @param  dn      The DN of the entry for which to make the determination.
5575   * @param  filter  A filter that the target entry must match.
5576   *
5577   * @throws  LDAPException  If a problem is encountered while trying to
5578   *                         communicate with the directory server.
5579   *
5580   * @throws  AssertionError  If the target entry does not exist or does not
5581   *                          match the provided filter.
5582   */
5583  public synchronized void assertEntryExists(final String dn,
5584                                             final String filter)
5585         throws LDAPException, AssertionError
5586  {
5587    final Entry e = getEntry(dn);
5588    if (e == null)
5589    {
5590      throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5591    }
5592
5593    final Filter f = Filter.create(filter);
5594    try
5595    {
5596      if (! f.matchesEntry(e, schemaRef.get()))
5597      {
5598        throw new AssertionError(
5599             ERR_MEM_HANDLER_TEST_ENTRY_DOES_NOT_MATCH_FILTER.get(dn, filter));
5600      }
5601    }
5602    catch (final LDAPException le)
5603    {
5604      Debug.debugException(le);
5605      throw new AssertionError(
5606           ERR_MEM_HANDLER_TEST_ENTRY_DOES_NOT_MATCH_FILTER.get(dn, filter));
5607    }
5608  }
5609
5610
5611
5612  /**
5613   * Ensures that an entry exists in the directory with the same DN and all
5614   * attribute values contained in the provided entry.  The server entry may
5615   * contain additional attributes and/or attribute values not included in the
5616   * provided entry.
5617   *
5618   * @param  entry  The entry expected to be present in the directory server.
5619   *
5620   * @throws  LDAPException  If a problem is encountered while trying to
5621   *                         communicate with the directory server.
5622   *
5623   * @throws  AssertionError  If the target entry does not exist or does not
5624   *                          match the provided filter.
5625   */
5626  public synchronized void assertEntryExists(final Entry entry)
5627         throws LDAPException, AssertionError
5628  {
5629    final Entry e = getEntry(entry.getDN());
5630    if (e == null)
5631    {
5632      throw new AssertionError(
5633           ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(entry.getDN()));
5634    }
5635
5636
5637    final Collection<Attribute> attrs = entry.getAttributes();
5638    final List<String> messages = new ArrayList<String>(attrs.size());
5639
5640    final Schema schema = schemaRef.get();
5641    for (final Attribute a : entry.getAttributes())
5642    {
5643      final Filter presFilter = Filter.createPresenceFilter(a.getName());
5644      if (! presFilter.matchesEntry(e, schema))
5645      {
5646        messages.add(ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(entry.getDN(),
5647             a.getName()));
5648        continue;
5649      }
5650
5651      for (final byte[] value : a.getValueByteArrays())
5652      {
5653        final Filter eqFilter = Filter.createEqualityFilter(a.getName(), value);
5654        if (! eqFilter.matchesEntry(e, schema))
5655        {
5656          messages.add(ERR_MEM_HANDLER_TEST_VALUE_MISSING.get(entry.getDN(),
5657               a.getName(), StaticUtils.toUTF8String(value)));
5658        }
5659      }
5660    }
5661
5662    if (! messages.isEmpty())
5663    {
5664      throw new AssertionError(StaticUtils.concatenateStrings(messages));
5665    }
5666  }
5667
5668
5669
5670  /**
5671   * Retrieves a list containing the DNs of the entries which are missing from
5672   * the directory server.
5673   *
5674   * @param  dns  The DNs of the entries to try to find in the server.
5675   *
5676   * @return  A list containing all of the provided DNs that were not found in
5677   *          the server, or an empty list if all entries were found.
5678   *
5679   * @throws  LDAPException  If a problem is encountered while trying to
5680   *                         communicate with the directory server.
5681   */
5682  public synchronized List<String> getMissingEntryDNs(
5683                                        final Collection<String> dns)
5684         throws LDAPException
5685  {
5686    final List<String> missingDNs = new ArrayList<String>(dns.size());
5687    for (final String dn : dns)
5688    {
5689      final Entry e = getEntry(dn);
5690      if (e == null)
5691      {
5692        missingDNs.add(dn);
5693      }
5694    }
5695
5696    return missingDNs;
5697  }
5698
5699
5700
5701  /**
5702   * Ensures that all of the entries with the provided DNs exist in the
5703   * directory.
5704   *
5705   * @param  dns  The DNs of the entries for which to make the determination.
5706   *
5707   * @throws  LDAPException  If a problem is encountered while trying to
5708   *                         communicate with the directory server.
5709   *
5710   * @throws  AssertionError  If any of the target entries does not exist.
5711   */
5712  public synchronized void assertEntriesExist(final Collection<String> dns)
5713         throws LDAPException, AssertionError
5714  {
5715    final List<String> missingDNs = getMissingEntryDNs(dns);
5716    if (missingDNs.isEmpty())
5717    {
5718      return;
5719    }
5720
5721    final List<String> messages = new ArrayList<String>(missingDNs.size());
5722    for (final String dn : missingDNs)
5723    {
5724      messages.add(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5725    }
5726
5727    throw new AssertionError(StaticUtils.concatenateStrings(messages));
5728  }
5729
5730
5731
5732  /**
5733   * Retrieves a list containing all of the named attributes which do not exist
5734   * in the target entry.
5735   *
5736   * @param  dn              The DN of the entry to examine.
5737   * @param  attributeNames  The names of the attributes expected to be present
5738   *                         in the target entry.
5739   *
5740   * @return  A list containing the names of the attributes which were not
5741   *          present in the target entry, an empty list if all specified
5742   *          attributes were found in the entry, or {@code null} if the target
5743   *          entry does not exist.
5744   *
5745   * @throws  LDAPException  If a problem is encountered while trying to
5746   *                         communicate with the directory server.
5747   */
5748  public synchronized List<String> getMissingAttributeNames(final String dn,
5749                                        final Collection<String> attributeNames)
5750         throws LDAPException
5751  {
5752    final Entry e = getEntry(dn);
5753    if (e == null)
5754    {
5755      return null;
5756    }
5757
5758    final Schema schema = schemaRef.get();
5759    final List<String> missingAttrs =
5760         new ArrayList<String>(attributeNames.size());
5761    for (final String attr : attributeNames)
5762    {
5763      final Filter f = Filter.createPresenceFilter(attr);
5764      if (! f.matchesEntry(e, schema))
5765      {
5766        missingAttrs.add(attr);
5767      }
5768    }
5769
5770    return missingAttrs;
5771  }
5772
5773
5774
5775  /**
5776   * Ensures that the specified entry exists in the directory with all of the
5777   * specified attributes.
5778   *
5779   * @param  dn              The DN of the entry to examine.
5780   * @param  attributeNames  The names of the attributes that are expected to be
5781   *                         present in the provided entry.
5782   *
5783   * @throws  LDAPException  If a problem is encountered while trying to
5784   *                         communicate with the directory server.
5785   *
5786   * @throws  AssertionError  If the target entry does not exist or does not
5787   *                          contain all of the specified attributes.
5788   */
5789  public synchronized void assertAttributeExists(final String dn,
5790                                final Collection<String> attributeNames)
5791        throws LDAPException, AssertionError
5792  {
5793    final List<String> missingAttrs =
5794         getMissingAttributeNames(dn, attributeNames);
5795    if (missingAttrs == null)
5796    {
5797      throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5798    }
5799    else if (missingAttrs.isEmpty())
5800    {
5801      return;
5802    }
5803
5804    final List<String> messages = new ArrayList<String>(missingAttrs.size());
5805    for (final String attr : missingAttrs)
5806    {
5807      messages.add(ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(dn, attr));
5808    }
5809
5810    throw new AssertionError(StaticUtils.concatenateStrings(messages));
5811  }
5812
5813
5814
5815  /**
5816   * Retrieves a list of all provided attribute values which are missing from
5817   * the specified entry.  The target attribute may or may not contain
5818   * additional values.
5819   *
5820   * @param  dn               The DN of the entry to examine.
5821   * @param  attributeName    The attribute expected to be present in the target
5822   *                          entry with the given values.
5823   * @param  attributeValues  The values expected to be present in the target
5824   *                          entry.
5825   *
5826   * @return  A list containing all of the provided values which were not found
5827   *          in the entry, an empty list if all provided attribute values were
5828   *          found, or {@code null} if the target entry does not exist.
5829   *
5830   * @throws  LDAPException  If a problem is encountered while trying to
5831   *                         communicate with the directory server.
5832   */
5833  public synchronized List<String> getMissingAttributeValues(final String dn,
5834                           final String attributeName,
5835                           final Collection<String> attributeValues)
5836       throws LDAPException
5837  {
5838    final Entry e = getEntry(dn);
5839    if (e == null)
5840    {
5841      return null;
5842    }
5843
5844    final Schema schema = schemaRef.get();
5845    final List<String> missingValues =
5846         new ArrayList<String>(attributeValues.size());
5847    for (final String value : attributeValues)
5848    {
5849      final Filter f = Filter.createEqualityFilter(attributeName, value);
5850      if (! f.matchesEntry(e, schema))
5851      {
5852        missingValues.add(value);
5853      }
5854    }
5855
5856    return missingValues;
5857  }
5858
5859
5860
5861  /**
5862   * Ensures that the specified entry exists in the directory with all of the
5863   * specified values for the given attribute.  The attribute may or may not
5864   * contain additional values.
5865   *
5866   * @param  dn               The DN of the entry to examine.
5867   * @param  attributeName    The name of the attribute to examine.
5868   * @param  attributeValues  The set of values which must exist for the given
5869   *                          attribute.
5870   *
5871   * @throws  LDAPException  If a problem is encountered while trying to
5872   *                         communicate with the directory server.
5873   *
5874   * @throws  AssertionError  If the target entry does not exist, does not
5875   *                          contain the specified attribute, or that attribute
5876   *                          does not have all of the specified values.
5877   */
5878  public synchronized void assertValueExists(final String dn,
5879                                final String attributeName,
5880                                final Collection<String> attributeValues)
5881        throws LDAPException, AssertionError
5882  {
5883    final List<String> missingValues =
5884         getMissingAttributeValues(dn, attributeName, attributeValues);
5885    if (missingValues == null)
5886    {
5887      throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5888    }
5889    else if (missingValues.isEmpty())
5890    {
5891      return;
5892    }
5893
5894    // See if the attribute exists at all in the entry.
5895    final Entry e = getEntry(dn);
5896    final Filter f = Filter.createPresenceFilter(attributeName);
5897    if (! f.matchesEntry(e,  schemaRef.get()))
5898    {
5899      throw new AssertionError(
5900           ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(dn, attributeName));
5901    }
5902
5903    final List<String> messages = new ArrayList<String>(missingValues.size());
5904    for (final String value : missingValues)
5905    {
5906      messages.add(ERR_MEM_HANDLER_TEST_VALUE_MISSING.get(dn, attributeName,
5907           value));
5908    }
5909
5910    throw new AssertionError(StaticUtils.concatenateStrings(messages));
5911  }
5912
5913
5914
5915  /**
5916   * Ensures that the specified entry does not exist in the directory.
5917   *
5918   * @param  dn  The DN of the entry expected to be missing.
5919   *
5920   * @throws  LDAPException  If a problem is encountered while trying to
5921   *                         communicate with the directory server.
5922   *
5923   * @throws  AssertionError  If the target entry is found in the server.
5924   */
5925  public synchronized void assertEntryMissing(final String dn)
5926         throws LDAPException, AssertionError
5927  {
5928    final Entry e = getEntry(dn);
5929    if (e != null)
5930    {
5931      throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_EXISTS.get(dn));
5932    }
5933  }
5934
5935
5936
5937  /**
5938   * Ensures that the specified entry exists in the directory but does not
5939   * contain any of the specified attributes.
5940   *
5941   * @param  dn              The DN of the entry expected to be present.
5942   * @param  attributeNames  The names of the attributes expected to be missing
5943   *                         from the entry.
5944   *
5945   * @throws  LDAPException  If a problem is encountered while trying to
5946   *                         communicate with the directory server.
5947   *
5948   * @throws  AssertionError  If the target entry is missing from the server, or
5949   *                          if it contains any of the target attributes.
5950   */
5951  public synchronized void assertAttributeMissing(final String dn,
5952                                final Collection<String> attributeNames)
5953         throws LDAPException, AssertionError
5954  {
5955    final Entry e = getEntry(dn);
5956    if (e == null)
5957    {
5958      throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5959    }
5960
5961    final Schema schema = schemaRef.get();
5962    final List<String> messages = new ArrayList<String>(attributeNames.size());
5963    for (final String name : attributeNames)
5964    {
5965      final Filter f = Filter.createPresenceFilter(name);
5966      if (f.matchesEntry(e, schema))
5967      {
5968        messages.add(ERR_MEM_HANDLER_TEST_ATTR_EXISTS.get(dn, name));
5969      }
5970    }
5971
5972    if (! messages.isEmpty())
5973    {
5974      throw new AssertionError(StaticUtils.concatenateStrings(messages));
5975    }
5976  }
5977
5978
5979
5980  /**
5981   * Ensures that the specified entry exists in the directory but does not
5982   * contain any of the specified attribute values.
5983   *
5984   * @param  dn               The DN of the entry expected to be present.
5985   * @param  attributeName    The name of the attribute to examine.
5986   * @param  attributeValues  The values expected to be missing from the target
5987   *                          entry.
5988   *
5989   * @throws  LDAPException  If a problem is encountered while trying to
5990   *                         communicate with the directory server.
5991   *
5992   * @throws  AssertionError  If the target entry is missing from the server, or
5993   *                          if it contains any of the target attribute values.
5994   */
5995  public synchronized void assertValueMissing(final String dn,
5996                                final String attributeName,
5997                                final Collection<String> attributeValues)
5998         throws LDAPException, AssertionError
5999  {
6000    final Entry e = getEntry(dn);
6001    if (e == null)
6002    {
6003      throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
6004    }
6005
6006    final Schema schema = schemaRef.get();
6007    final List<String> messages = new ArrayList<String>(attributeValues.size());
6008    for (final String value : attributeValues)
6009    {
6010      final Filter f = Filter.createEqualityFilter(attributeName, value);
6011      if (f.matchesEntry(e, schema))
6012      {
6013        messages.add(ERR_MEM_HANDLER_TEST_VALUE_EXISTS.get(dn, attributeName,
6014             value));
6015      }
6016    }
6017
6018    if (! messages.isEmpty())
6019    {
6020      throw new AssertionError(StaticUtils.concatenateStrings(messages));
6021    }
6022  }
6023}