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