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