001/*
002 * Copyright 2011-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2011-2017 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.listener;
022
023
024
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.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      final long processingStartTime = System.currentTimeMillis();
2990      sleepBeforeProcessing();
2991
2992      // Look at the time limit for the search request and see if sleeping
2993      // would have caused us to exceed that time limit.  It's extremely
2994      // unlikely that any search in the in-memory directory server would take
2995      // a second or more to complete, and that's the minimum time limit that
2996      // can be requested, so there's no need to check the time limit in most
2997      // cases.  However, someone may want to force a "time limit exceeded"
2998      // response by configuring a delay that is greater than the requested time
2999      // limit, so we should check now to see if that's been exceeded.
3000      final long timeLimitMillis = 1000L * request.getTimeLimit();
3001      if (timeLimitMillis > 0L)
3002      {
3003        final long timeLimitExpirationTime =
3004             processingStartTime + timeLimitMillis;
3005        if (System.currentTimeMillis() >= timeLimitExpirationTime)
3006        {
3007          return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
3008               ResultCode.TIME_LIMIT_EXCEEDED_INT_VALUE, null,
3009               ERR_MEM_HANDLER_TIME_LIMIT_EXCEEDED.get(), null));
3010        }
3011      }
3012
3013      // Process the provided request controls.
3014      final Map<String,Control> controlMap;
3015      try
3016      {
3017        controlMap = RequestControlPreProcessor.processControls(
3018             LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST, controls);
3019      }
3020      catch (final LDAPException le)
3021      {
3022        Debug.debugException(le);
3023        return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
3024             le.getResultCode().intValue(), null, le.getMessage(), null));
3025      }
3026      final ArrayList<Control> responseControls = new ArrayList<Control>(1);
3027
3028
3029      // If this operation type is not allowed, then reject it.
3030      final boolean isInternalOp =
3031           controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
3032      if ((! isInternalOp) &&
3033           (! config.getAllowedOperationTypes().contains(OperationType.SEARCH)))
3034      {
3035        return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
3036             ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
3037             ERR_MEM_HANDLER_SEARCH_NOT_ALLOWED.get(), null));
3038      }
3039
3040
3041      // If this operation type requires authentication, then ensure that the
3042      // client is authenticated.
3043      if ((authenticatedDN.isNullDN() &&
3044           config.getAuthenticationRequiredOperationTypes().contains(
3045                OperationType.SEARCH)))
3046      {
3047        return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
3048             ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
3049             ERR_MEM_HANDLER_SEARCH_REQUIRES_AUTH.get(), null));
3050      }
3051
3052
3053      // Get the parsed base DN.
3054      final DN baseDN;
3055      final Schema schema = schemaRef.get();
3056      try
3057      {
3058        baseDN = new DN(request.getBaseDN(), schema);
3059      }
3060      catch (final LDAPException le)
3061      {
3062        Debug.debugException(le);
3063        return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
3064             ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
3065             ERR_MEM_HANDLER_SEARCH_MALFORMED_BASE.get(request.getBaseDN(),
3066                  le.getMessage()),
3067             null));
3068      }
3069
3070      // See if the search base or one of its superiors is a smart referral.
3071      final boolean hasManageDsaIT = controlMap.containsKey(
3072           ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID);
3073      if (! hasManageDsaIT)
3074      {
3075        final Entry referralEntry = findNearestReferral(baseDN);
3076        if (referralEntry != null)
3077        {
3078          return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
3079               ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
3080               INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
3081               getReferralURLs(baseDN, referralEntry)));
3082        }
3083      }
3084
3085      // Make sure that the base entry exists.  It may be the root DSE or
3086      // subschema subentry.
3087      final Entry baseEntry;
3088      boolean includeChangeLog = true;
3089      if (baseDN.isNullDN())
3090      {
3091        baseEntry = generateRootDSE();
3092        includeChangeLog = false;
3093      }
3094      else if (baseDN.equals(subschemaSubentryDN))
3095      {
3096        baseEntry = subschemaSubentryRef.get();
3097      }
3098      else
3099      {
3100        baseEntry = entryMap.get(baseDN);
3101      }
3102
3103      if (baseEntry == null)
3104      {
3105        return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
3106             ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(baseDN),
3107             ERR_MEM_HANDLER_SEARCH_BASE_DOES_NOT_EXIST.get(
3108                  request.getBaseDN()),
3109             null));
3110      }
3111
3112      // Perform any necessary processing for the assertion and proxied auth
3113      // controls.
3114      try
3115      {
3116        handleAssertionRequestControl(controlMap, baseEntry);
3117        handleProxiedAuthControl(controlMap);
3118      }
3119      catch (final LDAPException le)
3120      {
3121        Debug.debugException(le);
3122        return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
3123             le.getResultCode().intValue(), null, le.getMessage(), null));
3124      }
3125
3126      // Create a temporary list to hold all of the entries to be returned.
3127      // These entries will not have been pared down based on the requested
3128      // attributes.
3129      final List<Entry> fullEntryList = new ArrayList<Entry>(entryMap.size());
3130
3131findEntriesAndRefs:
3132      {
3133        // Check the scope.  If it is a base-level search, then we only need to
3134        // examine the base entry.  Otherwise, we'll have to scan the entire
3135        // entry map.
3136        final Filter filter = request.getFilter();
3137        final SearchScope scope = request.getScope();
3138        final boolean includeSubEntries = ((scope == SearchScope.BASE) ||
3139             controlMap.containsKey(
3140                  SubentriesRequestControl.SUBENTRIES_REQUEST_OID));
3141        if (scope == SearchScope.BASE)
3142        {
3143          try
3144          {
3145            if (filter.matchesEntry(baseEntry, schema))
3146            {
3147              processSearchEntry(baseEntry, includeSubEntries, includeChangeLog,
3148                   hasManageDsaIT, fullEntryList, referenceList);
3149            }
3150          }
3151          catch (final Exception e)
3152          {
3153            Debug.debugException(e);
3154          }
3155
3156          break findEntriesAndRefs;
3157        }
3158
3159        // If the search uses a single-level scope and the base DN is the root
3160        // DSE, then we will only examine the defined base entries for the data
3161        // set.
3162        if ((scope == SearchScope.ONE) && baseDN.isNullDN())
3163        {
3164          for (final DN dn : baseDNs)
3165          {
3166            final Entry e = entryMap.get(dn);
3167            if (e != null)
3168            {
3169              try
3170              {
3171                if (filter.matchesEntry(e, schema))
3172                {
3173                  processSearchEntry(e, includeSubEntries, includeChangeLog,
3174                       hasManageDsaIT, fullEntryList, referenceList);
3175                }
3176              }
3177              catch (final Exception ex)
3178              {
3179                Debug.debugException(ex);
3180              }
3181            }
3182          }
3183
3184          break findEntriesAndRefs;
3185        }
3186
3187
3188        // Try to use indexes to process the request.  If we can't use any
3189        // indexes to get a candidate list, then just iterate over all the
3190        // entries.  It's not necessary to consider the root DSE for non-base
3191        // scopes.
3192        final Set<DN> candidateDNs = indexSearch(filter);
3193        if (candidateDNs == null)
3194        {
3195          for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet())
3196          {
3197            final DN dn = me.getKey();
3198            final Entry entry = me.getValue();
3199            try
3200            {
3201              if (dn.matchesBaseAndScope(baseDN, scope) &&
3202                   filter.matchesEntry(entry, schema))
3203              {
3204                processSearchEntry(entry, includeSubEntries, includeChangeLog,
3205                     hasManageDsaIT, fullEntryList, referenceList);
3206              }
3207            }
3208            catch (final Exception e)
3209            {
3210              Debug.debugException(e);
3211            }
3212          }
3213        }
3214        else
3215        {
3216          for (final DN dn : candidateDNs)
3217          {
3218            try
3219            {
3220              if (! dn.matchesBaseAndScope(baseDN, scope))
3221              {
3222                continue;
3223              }
3224
3225              final Entry entry = entryMap.get(dn);
3226              if (filter.matchesEntry(entry, schema))
3227              {
3228                processSearchEntry(entry, includeSubEntries, includeChangeLog,
3229                     hasManageDsaIT, fullEntryList, referenceList);
3230              }
3231            }
3232            catch (final Exception e)
3233            {
3234              Debug.debugException(e);
3235            }
3236          }
3237        }
3238      }
3239
3240
3241      // If the request included the server-side sort request control, then sort
3242      // the matching entries appropriately.
3243      final ServerSideSortRequestControl sortRequestControl =
3244           (ServerSideSortRequestControl) controlMap.get(
3245                ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID);
3246      if (sortRequestControl != null)
3247      {
3248        final EntrySorter entrySorter = new EntrySorter(false, schema,
3249             sortRequestControl.getSortKeys());
3250        final SortedSet<Entry> sortedEntrySet = entrySorter.sort(fullEntryList);
3251        fullEntryList.clear();
3252        fullEntryList.addAll(sortedEntrySet);
3253
3254        responseControls.add(new ServerSideSortResponseControl(
3255             ResultCode.SUCCESS, null, false));
3256      }
3257
3258
3259      // If the request included the simple paged results control, then handle
3260      // it.
3261      final SimplePagedResultsControl pagedResultsControl =
3262           (SimplePagedResultsControl)
3263                controlMap.get(SimplePagedResultsControl.PAGED_RESULTS_OID);
3264      if (pagedResultsControl != null)
3265      {
3266        final int totalSize = fullEntryList.size();
3267        final int pageSize = pagedResultsControl.getSize();
3268        final ASN1OctetString cookie = pagedResultsControl.getCookie();
3269
3270        final int offset;
3271        if ((cookie == null) || (cookie.getValueLength() == 0))
3272        {
3273          // This is the first request in the series, so start at the beginning
3274          // of the list.
3275          offset = 0;
3276        }
3277        else
3278        {
3279          // The cookie value will simply be an integer representation of the
3280          // offset within the result list at which to start the next batch.
3281          try
3282          {
3283            final ASN1Integer offsetInteger =
3284                 ASN1Integer.decodeAsInteger(cookie.getValue());
3285            offset = offsetInteger.intValue();
3286          }
3287          catch (final Exception e)
3288          {
3289            Debug.debugException(e);
3290            return new LDAPMessage(messageID,
3291                 new SearchResultDoneProtocolOp(
3292                      ResultCode.PROTOCOL_ERROR_INT_VALUE, null,
3293                      ERR_MEM_HANDLER_MALFORMED_PAGED_RESULTS_COOKIE.get(),
3294                      null),
3295                 responseControls);
3296          }
3297        }
3298
3299        // Create an iterator that will be used to remove entries from the
3300        // result set that are outside of the requested page of results.
3301        int pos = 0;
3302        final Iterator<Entry> iterator = fullEntryList.iterator();
3303
3304        // First, remove entries at the beginning of the list until we hit the
3305        // offset.
3306        while (iterator.hasNext() && (pos < offset))
3307        {
3308          iterator.next();
3309          iterator.remove();
3310          pos++;
3311        }
3312
3313        // Next, skip over the entries that should be returned.
3314        int keptEntries = 0;
3315        while (iterator.hasNext() && (keptEntries < pageSize))
3316        {
3317          iterator.next();
3318          pos++;
3319          keptEntries++;
3320        }
3321
3322        // If there are still entries left, then remove them and create a cookie
3323        // to include in the response.  Otherwise, use an empty cookie.
3324        if (iterator.hasNext())
3325        {
3326          responseControls.add(new SimplePagedResultsControl(totalSize,
3327               new ASN1OctetString(new ASN1Integer(pos).encode()), false));
3328          while (iterator.hasNext())
3329          {
3330            iterator.next();
3331            iterator.remove();
3332          }
3333        }
3334        else
3335        {
3336          responseControls.add(new SimplePagedResultsControl(totalSize,
3337               new ASN1OctetString(), false));
3338        }
3339      }
3340
3341
3342      // If the request includes the virtual list view request control, then
3343      // handle it.
3344      final VirtualListViewRequestControl vlvRequest =
3345           (VirtualListViewRequestControl) controlMap.get(
3346                VirtualListViewRequestControl.VIRTUAL_LIST_VIEW_REQUEST_OID);
3347      if (vlvRequest != null)
3348      {
3349        final int totalEntries = fullEntryList.size();
3350        final ASN1OctetString assertionValue = vlvRequest.getAssertionValue();
3351
3352        // Figure out the position of the target entry in the list.
3353        int offset = vlvRequest.getTargetOffset();
3354        if (assertionValue == null)
3355        {
3356          // The offset is one-based, so we need to adjust it for the list's
3357          // zero-based offset.  Also, make sure to put it within the bounds of
3358          // the list.
3359          offset--;
3360          offset = Math.max(0, offset);
3361          offset = Math.min(fullEntryList.size(), offset);
3362        }
3363        else
3364        {
3365          final SortKey primarySortKey = sortRequestControl.getSortKeys()[0];
3366
3367          final Entry testEntry = new Entry("cn=test", schema,
3368               new Attribute(primarySortKey.getAttributeName(),
3369                    assertionValue));
3370
3371          final EntrySorter entrySorter =
3372               new EntrySorter(false, schema, primarySortKey);
3373
3374          offset = fullEntryList.size();
3375          for (int i=0; i < fullEntryList.size(); i++)
3376          {
3377            if (entrySorter.compare(fullEntryList.get(i), testEntry) >= 0)
3378            {
3379              offset = i;
3380              break;
3381            }
3382          }
3383        }
3384
3385        // Get the start and end positions based on the before and after counts.
3386        final int beforeCount = Math.max(0, vlvRequest.getBeforeCount());
3387        final int afterCount  = Math.max(0, vlvRequest.getAfterCount());
3388
3389        final int start = Math.max(0, (offset - beforeCount));
3390        final int end =
3391             Math.min(fullEntryList.size(), (offset + afterCount + 1));
3392
3393        // Create an iterator to use to alter the list so that it only contains
3394        // the appropriate set of entries.
3395        int pos = 0;
3396        final Iterator<Entry> iterator = fullEntryList.iterator();
3397        while (iterator.hasNext())
3398        {
3399          iterator.next();
3400          if ((pos < start) || (pos >= end))
3401          {
3402            iterator.remove();
3403          }
3404          pos++;
3405        }
3406
3407        // Create the appropriate response control.
3408        responseControls.add(new VirtualListViewResponseControl((offset+1),
3409             totalEntries, ResultCode.SUCCESS, null));
3410      }
3411
3412
3413      // Process the set of requested attributes so that we can pare down the
3414      // entries.
3415      final AtomicBoolean allUserAttrs = new AtomicBoolean(false);
3416      final AtomicBoolean allOpAttrs = new AtomicBoolean(false);
3417      final Map<String,List<List<String>>> returnAttrs =
3418           processRequestedAttributes(request.getAttributes(), allUserAttrs,
3419                allOpAttrs);
3420
3421      final int sizeLimit;
3422      if (request.getSizeLimit() > 0)
3423      {
3424        sizeLimit = Math.min(request.getSizeLimit(), maxSizeLimit);
3425      }
3426      else
3427      {
3428        sizeLimit = maxSizeLimit;
3429      }
3430
3431      int entryCount = 0;
3432      for (final Entry e : fullEntryList)
3433      {
3434        entryCount++;
3435        if (entryCount > sizeLimit)
3436        {
3437          return new LDAPMessage(messageID,
3438               new SearchResultDoneProtocolOp(
3439                    ResultCode.SIZE_LIMIT_EXCEEDED_INT_VALUE, null,
3440                    ERR_MEM_HANDLER_SEARCH_SIZE_LIMIT_EXCEEDED.get(), null),
3441               responseControls);
3442        }
3443
3444        final Entry trimmedEntry = trimForRequestedAttributes(e,
3445             allUserAttrs.get(), allOpAttrs.get(), returnAttrs);
3446        if (request.typesOnly())
3447        {
3448          final Entry typesOnlyEntry = new Entry(trimmedEntry.getDN(), schema);
3449          for (final Attribute a : trimmedEntry.getAttributes())
3450          {
3451            typesOnlyEntry.addAttribute(new Attribute(a.getName()));
3452          }
3453          entryList.add(new SearchResultEntry(typesOnlyEntry));
3454        }
3455        else
3456        {
3457          entryList.add(new SearchResultEntry(trimmedEntry));
3458        }
3459      }
3460
3461      return new LDAPMessage(messageID,
3462           new SearchResultDoneProtocolOp(ResultCode.SUCCESS_INT_VALUE, null,
3463                null, null),
3464           responseControls);
3465    }
3466  }
3467
3468
3469
3470  /**
3471   * Performs any necessary index processing to add the provided entry.
3472   *
3473   * @param  entry  The entry that has been added.
3474   */
3475  private void indexAdd(final Entry entry)
3476  {
3477    for (final InMemoryDirectoryServerEqualityAttributeIndex i :
3478         equalityIndexes.values())
3479    {
3480      try
3481      {
3482        i.processAdd(entry);
3483      }
3484      catch (final LDAPException le)
3485      {
3486        Debug.debugException(le);
3487      }
3488    }
3489  }
3490
3491
3492
3493  /**
3494   * Performs any necessary index processing to delete the provided entry.
3495   *
3496   * @param  entry  The entry that has been deleted.
3497   */
3498  private void indexDelete(final Entry entry)
3499  {
3500    for (final InMemoryDirectoryServerEqualityAttributeIndex i :
3501         equalityIndexes.values())
3502    {
3503      try
3504      {
3505        i.processDelete(entry);
3506      }
3507      catch (final LDAPException le)
3508      {
3509        Debug.debugException(le);
3510      }
3511    }
3512  }
3513
3514
3515
3516  /**
3517   * Attempts to use indexes to obtain a candidate list for the provided filter.
3518   *
3519   * @param  filter  The filter to be processed.
3520   *
3521   * @return  The DNs of entries which may match the given filter, or
3522   *          {@code null} if the filter is not indexed.
3523   */
3524  private Set<DN> indexSearch(final Filter filter)
3525  {
3526    switch (filter.getFilterType())
3527    {
3528      case Filter.FILTER_TYPE_AND:
3529        Filter[] comps = filter.getComponents();
3530        if (comps.length == 0)
3531        {
3532          return null;
3533        }
3534        else if (comps.length == 1)
3535        {
3536          return indexSearch(comps[0]);
3537        }
3538        else
3539        {
3540          Set<DN> candidateSet = null;
3541          for (final Filter f : comps)
3542          {
3543            final Set<DN> dnSet = indexSearch(f);
3544            if (dnSet != null)
3545            {
3546              if (candidateSet == null)
3547              {
3548                candidateSet = new TreeSet<DN>(dnSet);
3549              }
3550              else
3551              {
3552                candidateSet.retainAll(dnSet);
3553              }
3554            }
3555          }
3556          return candidateSet;
3557        }
3558
3559      case Filter.FILTER_TYPE_OR:
3560        comps = filter.getComponents();
3561        if (comps.length == 0)
3562        {
3563          return Collections.emptySet();
3564        }
3565        else if (comps.length == 1)
3566        {
3567          return indexSearch(comps[0]);
3568        }
3569        else
3570        {
3571          Set<DN> candidateSet = null;
3572          for (final Filter f : comps)
3573          {
3574            final Set<DN> dnSet = indexSearch(f);
3575            if (dnSet == null)
3576            {
3577              return null;
3578            }
3579
3580            if (candidateSet == null)
3581            {
3582              candidateSet = new TreeSet<DN>(dnSet);
3583            }
3584            else
3585            {
3586              candidateSet.addAll(dnSet);
3587            }
3588          }
3589          return candidateSet;
3590        }
3591
3592      case Filter.FILTER_TYPE_EQUALITY:
3593        final Schema schema = schemaRef.get();
3594        if (schema == null)
3595        {
3596          return null;
3597        }
3598        final AttributeTypeDefinition at =
3599             schema.getAttributeType(filter.getAttributeName());
3600        if (at == null)
3601        {
3602          return null;
3603        }
3604        final InMemoryDirectoryServerEqualityAttributeIndex i =
3605             equalityIndexes.get(at);
3606        if (i == null)
3607        {
3608          return null;
3609        }
3610        try
3611        {
3612          return i.getMatchingEntries(filter.getRawAssertionValue());
3613        }
3614        catch (final Exception e)
3615        {
3616          Debug.debugException(e);
3617          return null;
3618        }
3619
3620      default:
3621        return null;
3622    }
3623  }
3624
3625
3626
3627  /**
3628   * Determines whether the provided set of controls includes a transaction
3629   * specification request control.  If so, then it will verify that it
3630   * references a valid transaction for the client.  If the request is part of a
3631   * valid transaction, then the transaction specification request control will
3632   * be removed and the request will be stashed in the client connection state
3633   * so that it can be retrieved and processed when the transaction is
3634   * committed.
3635   *
3636   * @param  messageID  The message ID for the request to be processed.
3637   * @param  request    The protocol op for the request to be processed.
3638   * @param  controls   The set of controls for the request to be processed.
3639   *
3640   * @return  The transaction ID for the associated transaction, or {@code null}
3641   *          if the request is not part of any transaction.
3642   *
3643   * @throws  LDAPException  If the transaction specification request control is
3644   *                         present but does not refer to a valid transaction
3645   *                         for the associated client connection.
3646   */
3647  @SuppressWarnings("unchecked")
3648  private ASN1OctetString processTransactionRequest(final int messageID,
3649                               final ProtocolOp request,
3650                               final Map<String,Control> controls)
3651          throws LDAPException
3652  {
3653    final TransactionSpecificationRequestControl txnControl =
3654         (TransactionSpecificationRequestControl)
3655         controls.remove(TransactionSpecificationRequestControl.
3656              TRANSACTION_SPECIFICATION_REQUEST_OID);
3657    if (txnControl == null)
3658    {
3659      return null;
3660    }
3661
3662    // See if the client has an active transaction.  If not, then fail.
3663    final ASN1OctetString txnID = txnControl.getTransactionID();
3664    final ObjectPair<ASN1OctetString,List<LDAPMessage>> txnInfo =
3665         (ObjectPair<ASN1OctetString,List<LDAPMessage>>) connectionState.get(
3666              TransactionExtendedOperationHandler.STATE_VARIABLE_TXN_INFO);
3667    if (txnInfo == null)
3668    {
3669      throw new LDAPException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
3670           ERR_MEM_HANDLER_TXN_CONTROL_WITHOUT_TXN.get(txnID.stringValue()));
3671    }
3672
3673
3674    // Make sure that the active transaction has a transaction ID that matches
3675    // the transaction ID from the control.  If not, then abort the existing
3676    // transaction and fail.
3677    final ASN1OctetString existingTxnID = txnInfo.getFirst();
3678    if (! txnID.stringValue().equals(existingTxnID.stringValue()))
3679    {
3680      connectionState.remove(
3681           TransactionExtendedOperationHandler.STATE_VARIABLE_TXN_INFO);
3682      connection.sendUnsolicitedNotification(
3683           new AbortedTransactionExtendedResult(existingTxnID,
3684                ResultCode.CONSTRAINT_VIOLATION,
3685                ERR_MEM_HANDLER_TXN_ABORTED_BY_CONTROL_TXN_ID_MISMATCH.get(
3686                     existingTxnID.stringValue(), txnID.stringValue()),
3687                null, null, null));
3688      throw new LDAPException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
3689           ERR_MEM_HANDLER_TXN_CONTROL_ID_MISMATCH.get(txnID.stringValue(),
3690                existingTxnID.stringValue()));
3691    }
3692
3693
3694    // Stash the request in the transaction state information so that it will
3695    // be processed when the transaction is committed.
3696    txnInfo.getSecond().add(new LDAPMessage(messageID, request,
3697         new ArrayList<Control>(controls.values())));
3698
3699    return txnID;
3700  }
3701
3702
3703
3704  /**
3705   * Sleeps for a period of time (if appropriate) before beginning processing
3706   * for an operation.
3707   */
3708  private void sleepBeforeProcessing()
3709  {
3710    final long delay = processingDelayMillis.get();
3711    if (delay > 0)
3712    {
3713      try
3714      {
3715        Thread.sleep(delay);
3716      }
3717      catch (final Exception e)
3718      {
3719        Debug.debugException(e);
3720
3721        if (e instanceof InterruptedException)
3722        {
3723          Thread.currentThread().interrupt();
3724        }
3725      }
3726    }
3727  }
3728
3729
3730
3731  /**
3732   * Retrieves the number of entries currently held in the server.
3733   *
3734   * @param  includeChangeLog  Indicates whether to include entries that are
3735   *                           part of the changelog in the count.
3736   *
3737   * @return  The number of entries currently held in the server.
3738   */
3739  public int countEntries(final boolean includeChangeLog)
3740  {
3741    synchronized (entryMap)
3742    {
3743      if (includeChangeLog || (maxChangelogEntries == 0))
3744      {
3745        return entryMap.size();
3746      }
3747      else
3748      {
3749        int count = 0;
3750
3751        for (final DN dn : entryMap.keySet())
3752        {
3753          if (! dn.isDescendantOf(changeLogBaseDN, true))
3754          {
3755            count++;
3756          }
3757        }
3758
3759        return count;
3760      }
3761    }
3762  }
3763
3764
3765
3766  /**
3767   * Retrieves the number of entries currently held in the server whose DN
3768   * matches or is subordinate to the provided base DN.
3769   *
3770   * @param  baseDN  The base DN to use for the determination.
3771   *
3772   * @return  The number of entries currently held in the server whose DN
3773   *          matches or is subordinate to the provided base DN.
3774   *
3775   * @throws  LDAPException  If the provided string cannot be parsed as a valid
3776   *                         DN.
3777   */
3778  public int countEntriesBelow(final String baseDN)
3779         throws LDAPException
3780  {
3781    synchronized (entryMap)
3782    {
3783      final DN parsedBaseDN = new DN(baseDN, schemaRef.get());
3784
3785      int count = 0;
3786      for (final DN dn : entryMap.keySet())
3787      {
3788        if (dn.isDescendantOf(parsedBaseDN, true))
3789        {
3790          count++;
3791        }
3792      }
3793
3794      return count;
3795    }
3796  }
3797
3798
3799
3800  /**
3801   * Removes all entries currently held in the server.  If a changelog is
3802   * enabled, then all changelog entries will also be cleared but the base
3803   * "cn=changelog" entry will be retained.
3804   */
3805  public void clear()
3806  {
3807    synchronized (entryMap)
3808    {
3809      restoreSnapshot(initialSnapshot);
3810    }
3811  }
3812
3813
3814
3815  /**
3816   * Reads entries from the provided LDIF reader and adds them to the server,
3817   * optionally clearing any existing entries before beginning to add the new
3818   * entries.  If an error is encountered while adding entries from LDIF then
3819   * the server will remain populated with the data it held before the import
3820   * attempt (even if the {@code clear} is given with a value of {@code true}).
3821   *
3822   * @param  clear       Indicates whether to remove all existing entries prior
3823   *                     to adding entries read from LDIF.
3824   * @param  ldifReader  The LDIF reader to use to obtain the entries to be
3825   *                     imported.
3826   *
3827   * @return  The number of entries read from LDIF and added to the server.
3828   *
3829   * @throws  LDAPException  If a problem occurs while reading entries or adding
3830   *                         them to the server.
3831   */
3832  public int importFromLDIF(final boolean clear, final LDIFReader ldifReader)
3833         throws LDAPException
3834  {
3835    synchronized (entryMap)
3836    {
3837      final InMemoryDirectoryServerSnapshot snapshot = createSnapshot();
3838      boolean restoreSnapshot = true;
3839
3840      try
3841      {
3842        if (clear)
3843        {
3844          restoreSnapshot(initialSnapshot);
3845        }
3846
3847        int entriesAdded = 0;
3848        while (true)
3849        {
3850          final Entry entry;
3851          try
3852          {
3853            entry = ldifReader.readEntry();
3854            if (entry == null)
3855            {
3856              restoreSnapshot = false;
3857              return entriesAdded;
3858            }
3859          }
3860          catch (final LDIFException le)
3861          {
3862            Debug.debugException(le);
3863            throw new LDAPException(ResultCode.LOCAL_ERROR,
3864                 ERR_MEM_HANDLER_INIT_FROM_LDIF_READ_ERROR.get(le.getMessage()),
3865                 le);
3866          }
3867          catch (final Exception e)
3868          {
3869            Debug.debugException(e);
3870            throw new LDAPException(ResultCode.LOCAL_ERROR,
3871                 ERR_MEM_HANDLER_INIT_FROM_LDIF_READ_ERROR.get(
3872                      StaticUtils.getExceptionMessage(e)),
3873                 e);
3874          }
3875
3876          addEntry(entry, true);
3877          entriesAdded++;
3878        }
3879      }
3880      finally
3881      {
3882        try
3883        {
3884          ldifReader.close();
3885        }
3886        catch (final Exception e)
3887        {
3888          Debug.debugException(e);
3889        }
3890
3891        if (restoreSnapshot)
3892        {
3893          restoreSnapshot(snapshot);
3894        }
3895      }
3896    }
3897  }
3898
3899
3900
3901  /**
3902   * Writes all entries contained in the server to LDIF using the provided
3903   * writer.
3904   *
3905   * @param  ldifWriter             The LDIF writer to use when writing the
3906   *                                entries.  It must not be {@code null}.
3907   * @param  excludeGeneratedAttrs  Indicates whether to exclude automatically
3908   *                                generated operational attributes like
3909   *                                entryUUID, entryDN, creatorsName, etc.
3910   * @param  excludeChangeLog       Indicates whether to exclude entries
3911   *                                contained in the changelog.
3912   * @param  closeWriter            Indicates whether the LDIF writer should be
3913   *                                closed after all entries have been written.
3914   *
3915   * @return  The number of entries written to LDIF.
3916   *
3917   * @throws  LDAPException  If a problem is encountered while attempting to
3918   *                         write an entry to LDIF.
3919   */
3920  public int exportToLDIF(final LDIFWriter ldifWriter,
3921                          final boolean excludeGeneratedAttrs,
3922                          final boolean excludeChangeLog,
3923                          final boolean closeWriter)
3924         throws LDAPException
3925  {
3926    synchronized (entryMap)
3927    {
3928      boolean exceptionThrown = false;
3929
3930      try
3931      {
3932        int entriesWritten = 0;
3933
3934        for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet())
3935        {
3936          final DN dn = me.getKey();
3937          if (excludeChangeLog && dn.isDescendantOf(changeLogBaseDN, true))
3938          {
3939            continue;
3940          }
3941
3942          final Entry entry;
3943          if (excludeGeneratedAttrs)
3944          {
3945            entry = me.getValue().duplicate();
3946            entry.removeAttribute("entryDN");
3947            entry.removeAttribute("entryUUID");
3948            entry.removeAttribute("subschemaSubentry");
3949            entry.removeAttribute("creatorsName");
3950            entry.removeAttribute("createTimestamp");
3951            entry.removeAttribute("modifiersName");
3952            entry.removeAttribute("modifyTimestamp");
3953          }
3954          else
3955          {
3956            entry = me.getValue();
3957          }
3958
3959          try
3960          {
3961            ldifWriter.writeEntry(entry);
3962            entriesWritten++;
3963          }
3964          catch (final Exception e)
3965          {
3966            Debug.debugException(e);
3967            exceptionThrown = true;
3968            throw new LDAPException(ResultCode.LOCAL_ERROR,
3969                 ERR_MEM_HANDLER_LDIF_WRITE_ERROR.get(entry.getDN(),
3970                      StaticUtils.getExceptionMessage(e)),
3971                 e);
3972          }
3973        }
3974
3975        return entriesWritten;
3976      }
3977      finally
3978      {
3979        if (closeWriter)
3980        {
3981          try
3982          {
3983            ldifWriter.close();
3984          }
3985          catch (final Exception e)
3986          {
3987            Debug.debugException(e);
3988            if (! exceptionThrown)
3989            {
3990              throw new LDAPException(ResultCode.LOCAL_ERROR,
3991                   ERR_MEM_HANDLER_LDIF_WRITE_CLOSE_ERROR.get(
3992                        StaticUtils.getExceptionMessage(e)),
3993                   e);
3994            }
3995          }
3996        }
3997      }
3998    }
3999  }
4000
4001
4002
4003  /**
4004   * Attempts to add the provided entry to the in-memory data set.  The attempt
4005   * will fail if any of the following conditions is true:
4006   * <UL>
4007   *   <LI>The provided entry has a malformed DN.</LI>
4008   *   <LI>The provided entry has the null DN.</LI>
4009   *   <LI>The provided entry has a DN that is the same as or subordinate to the
4010   *       subschema subentry.</LI>
4011   *   <LI>An entry already exists with the same DN as the entry in the provided
4012   *       request.</LI>
4013   *   <LI>The entry is outside the set of base DNs for the server.</LI>
4014   *   <LI>The entry is below one of the defined base DNs but the immediate
4015   *       parent entry does not exist.</LI>
4016   *   <LI>If a schema was provided, and the entry is not valid according to the
4017   *       constraints of that schema.</LI>
4018   * </UL>
4019   *
4020   * @param  entry                     The entry to be added.  It must not be
4021   *                                   {@code null}.
4022   * @param  ignoreNoUserModification  Indicates whether to ignore constraints
4023   *                                   normally imposed by the
4024   *                                   NO-USER-MODIFICATION element in attribute
4025   *                                   type definitions.
4026   *
4027   * @throws  LDAPException  If a problem occurs while attempting to add the
4028   *                         provided entry.
4029   */
4030  public void addEntry(final Entry entry,
4031                       final boolean ignoreNoUserModification)
4032         throws LDAPException
4033  {
4034    final List<Control> controls;
4035    if (ignoreNoUserModification)
4036    {
4037      controls = new ArrayList<Control>(1);
4038      controls.add(new Control(OID_INTERNAL_OPERATION_REQUEST_CONTROL, false));
4039    }
4040    else
4041    {
4042      controls = Collections.emptyList();
4043    }
4044
4045    final AddRequestProtocolOp addRequest = new AddRequestProtocolOp(
4046         entry.getDN(), new ArrayList<Attribute>(entry.getAttributes()));
4047
4048    final LDAPMessage resultMessage =
4049         processAddRequest(-1, addRequest, controls);
4050
4051    final AddResponseProtocolOp addResponse =
4052         resultMessage.getAddResponseProtocolOp();
4053    if (addResponse.getResultCode() != ResultCode.SUCCESS_INT_VALUE)
4054    {
4055      throw new LDAPException(ResultCode.valueOf(addResponse.getResultCode()),
4056           addResponse.getDiagnosticMessage(), addResponse.getMatchedDN(),
4057           stringListToArray(addResponse.getReferralURLs()));
4058    }
4059  }
4060
4061
4062
4063  /**
4064   * Attempts to add all of the provided entries to the server.  If an error is
4065   * encountered during processing, then the contents of the server will be the
4066   * same as they were before this method was called.
4067   *
4068   * @param  entries  The collection of entries to be added.
4069   *
4070   * @throws  LDAPException  If a problem was encountered while attempting to
4071   *                         add any of the entries to the server.
4072   */
4073  public void addEntries(final List<? extends Entry> entries)
4074         throws LDAPException
4075  {
4076    synchronized (entryMap)
4077    {
4078      final InMemoryDirectoryServerSnapshot snapshot = createSnapshot();
4079      boolean restoreSnapshot = true;
4080
4081      try
4082      {
4083        for (final Entry e : entries)
4084        {
4085          addEntry(e, false);
4086        }
4087        restoreSnapshot = false;
4088      }
4089      finally
4090      {
4091        if (restoreSnapshot)
4092        {
4093          restoreSnapshot(snapshot);
4094        }
4095      }
4096    }
4097  }
4098
4099
4100
4101  /**
4102   * Removes the entry with the specified DN and any subordinate entries it may
4103   * have.
4104   *
4105   * @param  baseDN  The DN of the entry to be deleted.  It must not be
4106   *                 {@code null} or represent the null DN.
4107   *
4108   * @return  The number of entries actually removed, or zero if the specified
4109   *          base DN does not represent an entry in the server.
4110   *
4111   * @throws  LDAPException  If the provided base DN is not a valid DN, or is
4112   *                         the DN of an entry that cannot be deleted (e.g.,
4113   *                         the null DN).
4114   */
4115  public int deleteSubtree(final String baseDN)
4116         throws LDAPException
4117  {
4118    synchronized (entryMap)
4119    {
4120      final DN dn = new DN(baseDN, schemaRef.get());
4121      if (dn.isNullDN())
4122      {
4123        throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
4124             ERR_MEM_HANDLER_DELETE_ROOT_DSE.get());
4125      }
4126
4127      int numDeleted = 0;
4128
4129      final Iterator<Map.Entry<DN,ReadOnlyEntry>> iterator =
4130           entryMap.entrySet().iterator();
4131      while (iterator.hasNext())
4132      {
4133        final Map.Entry<DN,ReadOnlyEntry> e = iterator.next();
4134        if (e.getKey().isDescendantOf(dn, true))
4135        {
4136          iterator.remove();
4137          numDeleted++;
4138        }
4139      }
4140
4141      return numDeleted;
4142    }
4143  }
4144
4145
4146
4147  /**
4148   * Attempts to apply the provided set of modifications to the specified entry.
4149   * The attempt will fail if any of the following conditions is true:
4150   * <UL>
4151   *   <LI>The target DN is malformed.</LI>
4152   *   <LI>The target entry is the root DSE.</LI>
4153   *   <LI>The target entry is the subschema subentry.</LI>
4154   *   <LI>The target entry does not exist.</LI>
4155   *   <LI>Any of the modifications cannot be applied to the entry.</LI>
4156   *   <LI>If a schema was provided, and the entry violates any of the
4157   *       constraints of that schema.</LI>
4158   * </UL>
4159   *
4160   * @param  dn    The DN of the entry to be modified.
4161   * @param  mods  The set of modifications to be applied to the entry.
4162   *
4163   * @throws  LDAPException  If a problem is encountered while attempting to
4164   *                         update the specified entry.
4165   */
4166  public void modifyEntry(final String dn, final List<Modification> mods)
4167         throws LDAPException
4168  {
4169    final ModifyRequestProtocolOp modifyRequest =
4170         new ModifyRequestProtocolOp(dn, mods);
4171
4172    final LDAPMessage resultMessage = processModifyRequest(-1, modifyRequest,
4173         Collections.<Control>emptyList());
4174
4175    final ModifyResponseProtocolOp modifyResponse =
4176         resultMessage.getModifyResponseProtocolOp();
4177    if (modifyResponse.getResultCode() != ResultCode.SUCCESS_INT_VALUE)
4178    {
4179      throw new LDAPException(
4180           ResultCode.valueOf(modifyResponse.getResultCode()),
4181           modifyResponse.getDiagnosticMessage(), modifyResponse.getMatchedDN(),
4182           stringListToArray(modifyResponse.getReferralURLs()));
4183    }
4184  }
4185
4186
4187
4188  /**
4189   * Retrieves a read-only representation the entry with the specified DN, if
4190   * it exists.
4191   *
4192   * @param  dn  The DN of the entry to retrieve.
4193   *
4194   * @return  The requested entry, or {@code null} if no entry exists with the
4195   *          given DN.
4196   *
4197   * @throws  LDAPException  If the provided DN is malformed.
4198   */
4199  public ReadOnlyEntry getEntry(final String dn)
4200         throws LDAPException
4201  {
4202    return getEntry(new DN(dn, schemaRef.get()));
4203  }
4204
4205
4206
4207  /**
4208   * Retrieves a read-only representation the entry with the specified DN, if
4209   * it exists.
4210   *
4211   * @param  dn  The DN of the entry to retrieve.
4212   *
4213   * @return  The requested entry, or {@code null} if no entry exists with the
4214   *          given DN.
4215   */
4216  public ReadOnlyEntry getEntry(final DN dn)
4217  {
4218    synchronized (entryMap)
4219    {
4220      if (dn.isNullDN())
4221      {
4222        return generateRootDSE();
4223      }
4224      else if (dn.equals(subschemaSubentryDN))
4225      {
4226        return subschemaSubentryRef.get();
4227      }
4228      else
4229      {
4230        final Entry e = entryMap.get(dn);
4231        if (e == null)
4232        {
4233          return null;
4234        }
4235        else
4236        {
4237          return new ReadOnlyEntry(e);
4238        }
4239      }
4240    }
4241  }
4242
4243
4244
4245  /**
4246   * Retrieves a list of all entries in the server which match the given
4247   * search criteria.
4248   *
4249   * @param  baseDN  The base DN to use for the search.  It must not be
4250   *                 {@code null}.
4251   * @param  scope   The scope to use for the search.  It must not be
4252   *                 {@code null}.
4253   * @param  filter  The filter to use for the search.  It must not be
4254   *                 {@code null}.
4255   *
4256   * @return  A list of the entries that matched the provided search criteria.
4257   *
4258   * @throws  LDAPException  If a problem is encountered while performing the
4259   *                         search.
4260   */
4261  public List<ReadOnlyEntry> search(final String baseDN,
4262                                    final SearchScope scope,
4263                                    final Filter filter)
4264         throws LDAPException
4265  {
4266    synchronized (entryMap)
4267    {
4268      final DN parsedDN;
4269      final Schema schema = schemaRef.get();
4270      try
4271      {
4272        parsedDN = new DN(baseDN, schema);
4273      }
4274      catch (final LDAPException le)
4275      {
4276        Debug.debugException(le);
4277        throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
4278             ERR_MEM_HANDLER_SEARCH_MALFORMED_BASE.get(baseDN, le.getMessage()),
4279             le);
4280      }
4281
4282      final ReadOnlyEntry baseEntry;
4283      if (parsedDN.isNullDN())
4284      {
4285        baseEntry = generateRootDSE();
4286      }
4287      else if (parsedDN.equals(subschemaSubentryDN))
4288      {
4289        baseEntry = subschemaSubentryRef.get();
4290      }
4291      else
4292      {
4293        final Entry e = entryMap.get(parsedDN);
4294        if (e == null)
4295        {
4296          throw new LDAPException(ResultCode.NO_SUCH_OBJECT,
4297               ERR_MEM_HANDLER_SEARCH_BASE_DOES_NOT_EXIST.get(baseDN),
4298               getMatchedDNString(parsedDN), null);
4299        }
4300
4301        baseEntry = new ReadOnlyEntry(e);
4302      }
4303
4304      if (scope == SearchScope.BASE)
4305      {
4306        final List<ReadOnlyEntry> entryList = new ArrayList<ReadOnlyEntry>(1);
4307
4308        try
4309        {
4310          if (filter.matchesEntry(baseEntry, schema))
4311          {
4312            entryList.add(baseEntry);
4313          }
4314        }
4315        catch (final LDAPException le)
4316        {
4317          Debug.debugException(le);
4318        }
4319
4320        return Collections.unmodifiableList(entryList);
4321      }
4322
4323      if ((scope == SearchScope.ONE) && parsedDN.isNullDN())
4324      {
4325        final List<ReadOnlyEntry> entryList =
4326             new ArrayList<ReadOnlyEntry>(baseDNs.size());
4327
4328        try
4329        {
4330          for (final DN dn : baseDNs)
4331          {
4332            final Entry e = entryMap.get(dn);
4333            if ((e != null) && filter.matchesEntry(e, schema))
4334            {
4335              entryList.add(new ReadOnlyEntry(e));
4336            }
4337          }
4338        }
4339        catch (final LDAPException le)
4340        {
4341          Debug.debugException(le);
4342        }
4343
4344        return Collections.unmodifiableList(entryList);
4345      }
4346
4347      final List<ReadOnlyEntry> entryList = new ArrayList<ReadOnlyEntry>(10);
4348      for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet())
4349      {
4350        final DN dn = me.getKey();
4351        if (dn.matchesBaseAndScope(parsedDN, scope))
4352        {
4353          // We don't want to return changelog entries searches based at the
4354          // root DSE.
4355          if (parsedDN.isNullDN() && dn.isDescendantOf(changeLogBaseDN, true))
4356          {
4357            continue;
4358          }
4359
4360          try
4361          {
4362            final Entry entry = me.getValue();
4363            if (filter.matchesEntry(entry, schema))
4364            {
4365              entryList.add(new ReadOnlyEntry(entry));
4366            }
4367          }
4368          catch (final LDAPException le)
4369          {
4370            Debug.debugException(le);
4371          }
4372        }
4373      }
4374
4375      return Collections.unmodifiableList(entryList);
4376    }
4377  }
4378
4379
4380
4381  /**
4382   * Generates an entry to use as the server root DSE.
4383   *
4384   * @return  The generated root DSE entry.
4385   */
4386  private ReadOnlyEntry generateRootDSE()
4387  {
4388    final ReadOnlyEntry rootDSEFromCfg = config.getRootDSEEntry();
4389    if (rootDSEFromCfg != null)
4390    {
4391      return rootDSEFromCfg;
4392    }
4393
4394    final Entry rootDSEEntry = new Entry(DN.NULL_DN, schemaRef.get());
4395    rootDSEEntry.addAttribute("objectClass", "top", "ds-root-dse");
4396    rootDSEEntry.addAttribute(new Attribute("supportedLDAPVersion",
4397         IntegerMatchingRule.getInstance(), "3"));
4398
4399    final String vendorName = config.getVendorName();
4400    if (vendorName != null)
4401    {
4402      rootDSEEntry.addAttribute("vendorName", vendorName);
4403    }
4404
4405    final String vendorVersion = config.getVendorVersion();
4406    if (vendorVersion != null)
4407    {
4408      rootDSEEntry.addAttribute("vendorVersion", vendorVersion);
4409    }
4410
4411    rootDSEEntry.addAttribute(new Attribute("subschemaSubentry",
4412         DistinguishedNameMatchingRule.getInstance(),
4413         subschemaSubentryDN.toString()));
4414    rootDSEEntry.addAttribute(new Attribute("entryDN",
4415         DistinguishedNameMatchingRule.getInstance(), ""));
4416    rootDSEEntry.addAttribute("entryUUID", UUID.randomUUID().toString());
4417
4418    rootDSEEntry.addAttribute("supportedFeatures",
4419         "1.3.6.1.4.1.4203.1.5.1",  // All operational attributes
4420         "1.3.6.1.4.1.4203.1.5.2",  // Request attributes by object class
4421         "1.3.6.1.4.1.4203.1.5.3",  // LDAP absolute true and false filters
4422         "1.3.6.1.1.14");           // Increment modification type
4423
4424    final TreeSet<String> ctlSet = new TreeSet<String>();
4425
4426    ctlSet.add(AssertionRequestControl.ASSERTION_REQUEST_OID);
4427    ctlSet.add(AuthorizationIdentityRequestControl.
4428         AUTHORIZATION_IDENTITY_REQUEST_OID);
4429    ctlSet.add(DontUseCopyRequestControl.DONT_USE_COPY_REQUEST_OID);
4430    ctlSet.add(ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID);
4431    ctlSet.add(DraftZeilengaLDAPNoOp12RequestControl.NO_OP_REQUEST_OID);
4432    ctlSet.add(PermissiveModifyRequestControl.PERMISSIVE_MODIFY_REQUEST_OID);
4433    ctlSet.add(PostReadRequestControl.POST_READ_REQUEST_OID);
4434    ctlSet.add(PreReadRequestControl.PRE_READ_REQUEST_OID);
4435    ctlSet.add(ProxiedAuthorizationV1RequestControl.
4436         PROXIED_AUTHORIZATION_V1_REQUEST_OID);
4437    ctlSet.add(ProxiedAuthorizationV2RequestControl.
4438         PROXIED_AUTHORIZATION_V2_REQUEST_OID);
4439    ctlSet.add(ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID);
4440    ctlSet.add(SimplePagedResultsControl.PAGED_RESULTS_OID);
4441    ctlSet.add(SubentriesRequestControl.SUBENTRIES_REQUEST_OID);
4442    ctlSet.add(SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID);
4443    ctlSet.add(TransactionSpecificationRequestControl.
4444         TRANSACTION_SPECIFICATION_REQUEST_OID);
4445    ctlSet.add(VirtualListViewRequestControl.VIRTUAL_LIST_VIEW_REQUEST_OID);
4446
4447    final String[] controlOIDs = new String[ctlSet.size()];
4448    rootDSEEntry.addAttribute("supportedControl", ctlSet.toArray(controlOIDs));
4449
4450
4451    if (! extendedRequestHandlers.isEmpty())
4452    {
4453      final String[] oidArray = new String[extendedRequestHandlers.size()];
4454      rootDSEEntry.addAttribute("supportedExtension",
4455           extendedRequestHandlers.keySet().toArray(oidArray));
4456
4457      for (final InMemoryListenerConfig c : config.getListenerConfigs())
4458      {
4459        if (c.getStartTLSSocketFactory() != null)
4460        {
4461          rootDSEEntry.addAttribute("supportedExtension",
4462               StartTLSExtendedRequest.STARTTLS_REQUEST_OID);
4463          break;
4464        }
4465      }
4466    }
4467
4468    if (! saslBindHandlers.isEmpty())
4469    {
4470      final String[] mechanismArray = new String[saslBindHandlers.size()];
4471      rootDSEEntry.addAttribute("supportedSASLMechanisms",
4472           saslBindHandlers.keySet().toArray(mechanismArray));
4473    }
4474
4475    int pos = 0;
4476    final String[] baseDNStrings = new String[baseDNs.size()];
4477    for (final DN baseDN : baseDNs)
4478    {
4479      baseDNStrings[pos++] = baseDN.toString();
4480    }
4481    rootDSEEntry.addAttribute(new Attribute("namingContexts",
4482         DistinguishedNameMatchingRule.getInstance(), baseDNStrings));
4483
4484    if (maxChangelogEntries > 0)
4485    {
4486      rootDSEEntry.addAttribute(new Attribute("changeLog",
4487           DistinguishedNameMatchingRule.getInstance(),
4488           changeLogBaseDN.toString()));
4489      rootDSEEntry.addAttribute(new Attribute("firstChangeNumber",
4490           IntegerMatchingRule.getInstance(), firstChangeNumber.toString()));
4491      rootDSEEntry.addAttribute(new Attribute("lastChangeNumber",
4492           IntegerMatchingRule.getInstance(), lastChangeNumber.toString()));
4493    }
4494
4495    return new ReadOnlyEntry(rootDSEEntry);
4496  }
4497
4498
4499
4500  /**
4501   * Generates a subschema subentry from the provided schema object.
4502   *
4503   * @param  schema  The schema to use to generate the subschema subentry.  It
4504   *                 may be {@code null} if a minimal default entry should be
4505   *                 generated.
4506   *
4507   * @return  The generated subschema subentry.
4508   */
4509  private static ReadOnlyEntry generateSubschemaSubentry(final Schema schema)
4510  {
4511    final Entry e;
4512
4513    if (schema == null)
4514    {
4515      e = new Entry("cn=schema", schema);
4516
4517      e.addAttribute("objectClass", "namedObject", "ldapSubEntry",
4518           "subschema");
4519      e.addAttribute("cn", "schema");
4520    }
4521    else
4522    {
4523      e = schema.getSchemaEntry().duplicate();
4524    }
4525
4526    try
4527    {
4528      e.addAttribute("entryDN", DN.normalize(e.getDN(), schema));
4529    }
4530    catch (final LDAPException le)
4531    {
4532      // This should never happen.
4533      Debug.debugException(le);
4534      e.setAttribute("entryDN", StaticUtils.toLowerCase(e.getDN()));
4535    }
4536
4537
4538    e.addAttribute("entryUUID", UUID.randomUUID().toString());
4539    return new ReadOnlyEntry(e);
4540  }
4541
4542
4543
4544  /**
4545   * Processes the set of requested attributes from the given search request.
4546   *
4547   * @param  attrList      The list of requested attributes to examine.
4548   * @param  allUserAttrs  Indicates whether to return all user attributes.  It
4549   *                       should have an initial value of {@code false}.
4550   * @param  allOpAttrs    Indicates whether to return all operational
4551   *                       attributes.  It should have an initial value of
4552   *                       {@code false}.
4553   *
4554   * @return  A map of specific attribute types to be returned.  The keys of the
4555   *          map will be the lowercase OID and names of the attribute types,
4556   *          and the values will be a list of option sets for the associated
4557   *          attribute type.
4558   */
4559  private Map<String,List<List<String>>> processRequestedAttributes(
4560               final List<String> attrList, final AtomicBoolean allUserAttrs,
4561               final AtomicBoolean allOpAttrs)
4562  {
4563    if (attrList.isEmpty())
4564    {
4565      allUserAttrs.set(true);
4566      return Collections.emptyMap();
4567    }
4568
4569    final Schema schema = schemaRef.get();
4570    final HashMap<String,List<List<String>>> m =
4571         new HashMap<String,List<List<String>>>(attrList.size() * 2);
4572    for (final String s : attrList)
4573    {
4574      if (s.equals("*"))
4575      {
4576        // All user attributes.
4577        allUserAttrs.set(true);
4578      }
4579      else if (s.equals("+"))
4580      {
4581        // All operational attributes.
4582        allOpAttrs.set(true);
4583      }
4584      else if (s.startsWith("@"))
4585      {
4586        // Return attributes by object class.  This can only be supported if a
4587        // schema has been defined.
4588        if (schema != null)
4589        {
4590          final String ocName = s.substring(1);
4591          final ObjectClassDefinition oc = schema.getObjectClass(ocName);
4592          if (oc != null)
4593          {
4594            for (final AttributeTypeDefinition at :
4595                 oc.getRequiredAttributes(schema, true))
4596            {
4597              addAttributeOIDAndNames(at, m, Collections.<String>emptyList());
4598            }
4599            for (final AttributeTypeDefinition at :
4600                 oc.getOptionalAttributes(schema, true))
4601            {
4602              addAttributeOIDAndNames(at, m, Collections.<String>emptyList());
4603            }
4604          }
4605        }
4606      }
4607      else
4608      {
4609        final ObjectPair<String,List<String>> nameWithOptions =
4610             getNameWithOptions(s);
4611        if (nameWithOptions == null)
4612        {
4613          continue;
4614        }
4615
4616        final String name = nameWithOptions.getFirst();
4617        final List<String> options = nameWithOptions.getSecond();
4618
4619        if (schema == null)
4620        {
4621          // Just use the name as provided.
4622          List<List<String>> optionLists = m.get(name);
4623          if (optionLists == null)
4624          {
4625            optionLists = new ArrayList<List<String>>(1);
4626            m.put(name, optionLists);
4627          }
4628          optionLists.add(options);
4629        }
4630        else
4631        {
4632          // If the attribute type is defined in the schema, then use it to get
4633          // all names and the OID.  Otherwise, just use the name as provided.
4634          final AttributeTypeDefinition at = schema.getAttributeType(name);
4635          if (at == null)
4636          {
4637            List<List<String>> optionLists = m.get(name);
4638            if (optionLists == null)
4639            {
4640              optionLists = new ArrayList<List<String>>(1);
4641              m.put(name, optionLists);
4642            }
4643            optionLists.add(options);
4644          }
4645          else
4646          {
4647            addAttributeOIDAndNames(at, m, options);
4648          }
4649        }
4650      }
4651    }
4652
4653    return m;
4654  }
4655
4656
4657
4658  /**
4659   * Parses the provided string into an attribute type and set of options.
4660   *
4661   * @param  s  The string to be parsed.
4662   *
4663   * @return  An {@code ObjectPair} in which the first element is the attribute
4664   *          type name and the second is the list of options (or an empty
4665   *          list if there are no options).  Alternately, a value of
4666   *          {@code null} may be returned if the provided string does not
4667   *          represent a valid attribute type description.
4668   */
4669  private static ObjectPair<String,List<String>> getNameWithOptions(
4670                                                      final String s)
4671  {
4672    if (! Attribute.nameIsValid(s, true))
4673    {
4674      return null;
4675    }
4676
4677    final String l = StaticUtils.toLowerCase(s);
4678
4679    int semicolonPos = l.indexOf(';');
4680    if (semicolonPos < 0)
4681    {
4682      return new ObjectPair<String,List<String>>(l,
4683           Collections.<String>emptyList());
4684    }
4685
4686    final String name = l.substring(0, semicolonPos);
4687    final ArrayList<String> optionList = new ArrayList<String>(1);
4688    while (true)
4689    {
4690      final int nextSemicolonPos = l.indexOf(';', semicolonPos+1);
4691      if (nextSemicolonPos < 0)
4692      {
4693        optionList.add(l.substring(semicolonPos+1));
4694        break;
4695      }
4696      else
4697      {
4698        optionList.add(l.substring(semicolonPos+1, nextSemicolonPos));
4699        semicolonPos = nextSemicolonPos;
4700      }
4701    }
4702
4703    return new ObjectPair<String,List<String>>(name, optionList);
4704  }
4705
4706
4707
4708  /**
4709   * Adds all-lowercase versions of the OID and all names for the provided
4710   * attribute type definition to the given map with the given options.
4711   *
4712   * @param  d  The attribute type definition to process.
4713   * @param  m  The map to which the OID and names should be added.
4714   * @param  o  The array of attribute options to use in the map.  It should be
4715   *            empty if no options are needed, and must not be {@code null}.
4716   */
4717  private void addAttributeOIDAndNames(final AttributeTypeDefinition d,
4718                                       final Map<String,List<List<String>>> m,
4719                                       final List<String> o)
4720  {
4721    if (d == null)
4722    {
4723      return;
4724    }
4725
4726    final String lowerOID = StaticUtils.toLowerCase(d.getOID());
4727    if (lowerOID != null)
4728    {
4729      List<List<String>> l = m.get(lowerOID);
4730      if (l == null)
4731      {
4732        l = new ArrayList<List<String>>(1);
4733        m.put(lowerOID, l);
4734      }
4735
4736      l.add(o);
4737    }
4738
4739    for (final String name : d.getNames())
4740    {
4741      final String lowerName = StaticUtils.toLowerCase(name);
4742      List<List<String>> l = m.get(lowerName);
4743      if (l == null)
4744      {
4745        l = new ArrayList<List<String>>(1);
4746        m.put(lowerName, l);
4747      }
4748
4749      l.add(o);
4750    }
4751
4752    // If a schema is available, then see if the attribute type has any
4753    // subordinate types.  If so, then add them.
4754    final Schema schema = schemaRef.get();
4755    if (schema != null)
4756    {
4757      for (final AttributeTypeDefinition subordinateType :
4758           schema.getSubordinateAttributeTypes(d))
4759      {
4760        addAttributeOIDAndNames(subordinateType, m, o);
4761      }
4762    }
4763  }
4764
4765
4766
4767  /**
4768   * Performs the necessary processing to determine whether the given entry
4769   * should be returned as a search result entry or reference, or if it should
4770   * not be returned at all.
4771   *
4772   * @param  entry              The entry to be processed.
4773   * @param  includeSubEntries  Indicates whether LDAP subentries should be
4774   *                            returned to the client.
4775   * @param  includeChangeLog   Indicates whether entries within the changelog
4776   *                            should be returned to the client.
4777   * @param  hasManageDsaIT     Indicates whether the request includes the
4778   *                            ManageDsaIT control, which can change how smart
4779   *                            referrals should be handled.
4780   * @param  entryList          The list to which the entry should be added if
4781   *                            it should be returned to the client as a search
4782   *                            result entry.
4783   * @param  referenceList      The list that should be updated if the provided
4784   *                            entry represents a smart referral that should be
4785   *                            returned as a search result reference.
4786   */
4787  private void processSearchEntry(final Entry entry,
4788                    final boolean includeSubEntries,
4789                    final boolean includeChangeLog,
4790                    final boolean hasManageDsaIT,
4791                    final List<Entry> entryList,
4792                    final List<SearchResultReference> referenceList)
4793  {
4794    // See if the entry should be suppressed as an LDAP subentry.
4795    if ((! includeSubEntries) &&
4796        (entry.hasObjectClass("ldapSubEntry") ||
4797         entry.hasObjectClass("inheritableLDAPSubEntry")))
4798    {
4799      return;
4800    }
4801
4802    // See if the entry should be suppressed as a changelog entry.
4803    try
4804    {
4805      if ((! includeChangeLog) &&
4806           (entry.getParsedDN().isDescendantOf(changeLogBaseDN, true)))
4807      {
4808        return;
4809      }
4810    }
4811    catch (final Exception e)
4812    {
4813      // This should never happen.
4814      Debug.debugException(e);
4815    }
4816
4817    // See if the entry is a referral and should result in a reference rather
4818    // than an entry.
4819    if ((! hasManageDsaIT) && entry.hasObjectClass("referral") &&
4820        entry.hasAttribute("ref"))
4821    {
4822      referenceList.add(new SearchResultReference(
4823           entry.getAttributeValues("ref"), NO_CONTROLS));
4824      return;
4825    }
4826
4827    entryList.add(entry);
4828  }
4829
4830
4831
4832  /**
4833   * Retrieves a copy of the provided entry that includes only the appropriate
4834   * set of requested attributes.
4835   *
4836   * @param  entry         The entry to be returned.
4837   * @param  allUserAttrs  Indicates whether to return all user attributes.
4838   * @param  allOpAttrs    Indicates whether to return all operational
4839   *                       attributes.
4840   * @param  returnAttrs   A map with information about the specific attribute
4841   *                       types to return.
4842   *
4843   * @return  A copy of the provided entry that includes only the appropriate
4844   *          set of requested attributes.
4845   */
4846  private Entry trimForRequestedAttributes(final Entry entry,
4847                     final boolean allUserAttrs, final boolean allOpAttrs,
4848                     final Map<String,List<List<String>>> returnAttrs)
4849  {
4850    // See if we can return the entry without paring it down.
4851    final Schema schema = schemaRef.get();
4852    if (allUserAttrs)
4853    {
4854      if (allOpAttrs || (schema == null))
4855      {
4856        return entry;
4857      }
4858    }
4859
4860
4861    // If we've gotten here, then we may only need to return a partial entry.
4862    final Entry copy = new Entry(entry.getDN(), schema);
4863
4864    for (final Attribute a : entry.getAttributes())
4865    {
4866      final ObjectPair<String,List<String>> nameWithOptions =
4867           getNameWithOptions(a.getName());
4868      final String name = nameWithOptions.getFirst();
4869      final List<String> options = nameWithOptions.getSecond();
4870
4871      // If there is a schema, then see if it is an operational attribute, since
4872      // that needs to be handled in a manner different from user attributes
4873      if (schema != null)
4874      {
4875        final AttributeTypeDefinition at = schema.getAttributeType(name);
4876        if ((at != null) && at.isOperational())
4877        {
4878          if (allOpAttrs)
4879          {
4880            copy.addAttribute(a);
4881            continue;
4882          }
4883
4884          final List<List<String>> optionLists = returnAttrs.get(name);
4885          if (optionLists == null)
4886          {
4887            continue;
4888          }
4889
4890          for (final List<String> optionList : optionLists)
4891          {
4892            boolean matchAll = true;
4893            for (final String option : optionList)
4894            {
4895              if (! options.contains(option))
4896              {
4897                matchAll = false;
4898                break;
4899              }
4900            }
4901
4902            if (matchAll)
4903            {
4904              copy.addAttribute(a);
4905              break;
4906            }
4907          }
4908          continue;
4909        }
4910      }
4911
4912      // We'll assume that it's a user attribute, and we'll look for an exact
4913      // match on the base name.
4914      if (allUserAttrs)
4915      {
4916        copy.addAttribute(a);
4917        continue;
4918      }
4919
4920      final List<List<String>> optionLists = returnAttrs.get(name);
4921      if (optionLists == null)
4922      {
4923        continue;
4924      }
4925
4926      for (final List<String> optionList : optionLists)
4927      {
4928        boolean matchAll = true;
4929        for (final String option : optionList)
4930        {
4931          if (! options.contains(option))
4932          {
4933            matchAll = false;
4934            break;
4935          }
4936        }
4937
4938        if (matchAll)
4939        {
4940          copy.addAttribute(a);
4941          break;
4942        }
4943      }
4944    }
4945
4946    return copy;
4947  }
4948
4949
4950
4951  /**
4952   * Retrieves the DN of the existing entry which is the closest hierarchical
4953   * match to the provided DN.
4954   *
4955   * @param  dn  The DN for which to retrieve the appropriate matched DN.
4956   *
4957   * @return  The appropriate matched DN value, or {@code null} if there is
4958   *          none.
4959   */
4960  private String getMatchedDNString(final DN dn)
4961  {
4962    DN parentDN = dn.getParent();
4963    while (parentDN != null)
4964    {
4965      if (entryMap.containsKey(parentDN))
4966      {
4967        return parentDN.toString();
4968      }
4969
4970      parentDN = parentDN.getParent();
4971    }
4972
4973    return null;
4974  }
4975
4976
4977
4978  /**
4979   * Converts the provided string list to an array.
4980   *
4981   * @param  l  The possibly null list to be converted.
4982   *
4983   * @return  The string array with the same elements as the given list in the
4984   *          same order, or {@code null} if the given list was null.
4985   */
4986  private static String[] stringListToArray(final List<String> l)
4987  {
4988    if (l == null)
4989    {
4990      return null;
4991    }
4992    else
4993    {
4994      final String[] a = new String[l.size()];
4995      return l.toArray(a);
4996    }
4997  }
4998
4999
5000
5001  /**
5002   * Creates a changelog entry from the information in the provided add request
5003   * and adds it to the server changelog.
5004   *
5005   * @param  addRequest  The add request to use to construct the changelog
5006   *                     entry.
5007   * @param  authzDN     The authorization DN for the change.
5008   */
5009  private void addChangeLogEntry(final AddRequestProtocolOp addRequest,
5010                                 final DN authzDN)
5011  {
5012    // If the changelog is disabled, then don't do anything.
5013    if (maxChangelogEntries <= 0)
5014    {
5015      return;
5016    }
5017
5018    final long changeNumber = lastChangeNumber.incrementAndGet();
5019    final LDIFAddChangeRecord changeRecord = new LDIFAddChangeRecord(
5020         addRequest.getDN(), addRequest.getAttributes());
5021    try
5022    {
5023      addChangeLogEntry(
5024           ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord),
5025           authzDN);
5026    }
5027    catch (final LDAPException le)
5028    {
5029      // This should not happen.
5030      Debug.debugException(le);
5031    }
5032  }
5033
5034
5035
5036  /**
5037   * Creates a changelog entry from the information in the provided delete
5038   * request and adds it to the server changelog.
5039   *
5040   * @param  e        The entry to be deleted.
5041   * @param  authzDN  The authorization DN for the change.
5042   */
5043  private void addDeleteChangeLogEntry(final Entry e, final DN authzDN)
5044  {
5045    // If the changelog is disabled, then don't do anything.
5046    if (maxChangelogEntries <= 0)
5047    {
5048      return;
5049    }
5050
5051    final long changeNumber = lastChangeNumber.incrementAndGet();
5052    final LDIFDeleteChangeRecord changeRecord =
5053         new LDIFDeleteChangeRecord(e.getDN());
5054
5055    // Create the changelog entry.
5056    try
5057    {
5058      final ChangeLogEntry cle = ChangeLogEntry.constructChangeLogEntry(
5059           changeNumber, changeRecord);
5060
5061      // Add a set of deleted entry attributes, which is simply an LDIF-encoded
5062      // representation of the entry, excluding the first line since it contains
5063      // the DN.
5064      final StringBuilder deletedEntryAttrsBuffer = new StringBuilder();
5065      final String[] ldifLines = e.toLDIF(0);
5066      for (int i=1; i < ldifLines.length; i++)
5067      {
5068        deletedEntryAttrsBuffer.append(ldifLines[i]);
5069        deletedEntryAttrsBuffer.append(StaticUtils.EOL);
5070      }
5071
5072      final Entry copy = cle.duplicate();
5073      copy.addAttribute(ChangeLogEntry.ATTR_DELETED_ENTRY_ATTRS,
5074           deletedEntryAttrsBuffer.toString());
5075      addChangeLogEntry(new ChangeLogEntry(copy), authzDN);
5076    }
5077    catch (final LDAPException le)
5078    {
5079      // This should never happen.
5080      Debug.debugException(le);
5081    }
5082  }
5083
5084
5085
5086  /**
5087   * Creates a changelog entry from the information in the provided modify
5088   * request and adds it to the server changelog.
5089   *
5090   * @param  modifyRequest  The modify request to use to construct the changelog
5091   *                        entry.
5092   * @param  authzDN        The authorization DN for the change.
5093   */
5094  private void addChangeLogEntry(final ModifyRequestProtocolOp modifyRequest,
5095                                 final DN authzDN)
5096  {
5097    // If the changelog is disabled, then don't do anything.
5098    if (maxChangelogEntries <= 0)
5099    {
5100      return;
5101    }
5102
5103    final long changeNumber = lastChangeNumber.incrementAndGet();
5104    final LDIFModifyChangeRecord changeRecord =
5105         new LDIFModifyChangeRecord(modifyRequest.getDN(),
5106              modifyRequest.getModifications());
5107    try
5108    {
5109      addChangeLogEntry(
5110           ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord),
5111           authzDN);
5112    }
5113    catch (final LDAPException le)
5114    {
5115      // This should not happen.
5116      Debug.debugException(le);
5117    }
5118  }
5119
5120
5121
5122  /**
5123   * Creates a changelog entry from the information in the provided modify DN
5124   * request and adds it to the server changelog.
5125   *
5126   * @param  modifyDNRequest  The modify DN request to use to construct the
5127   *                          changelog entry.
5128   * @param  authzDN          The authorization DN for the change.
5129   */
5130  private void addChangeLogEntry(
5131                    final ModifyDNRequestProtocolOp modifyDNRequest,
5132                    final DN authzDN)
5133  {
5134    // If the changelog is disabled, then don't do anything.
5135    if (maxChangelogEntries <= 0)
5136    {
5137      return;
5138    }
5139
5140    final long changeNumber = lastChangeNumber.incrementAndGet();
5141    final LDIFModifyDNChangeRecord changeRecord =
5142         new LDIFModifyDNChangeRecord(modifyDNRequest.getDN(),
5143              modifyDNRequest.getNewRDN(), modifyDNRequest.deleteOldRDN(),
5144              modifyDNRequest.getNewSuperiorDN());
5145    try
5146    {
5147      addChangeLogEntry(
5148           ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord),
5149           authzDN);
5150    }
5151    catch (final LDAPException le)
5152    {
5153      // This should not happen.
5154      Debug.debugException(le);
5155    }
5156  }
5157
5158
5159
5160  /**
5161   * Adds the provided changelog entry to the data set, removing an old entry if
5162   * necessary to remain within the maximum allowed number of changes.  This
5163   * must only be called from a synchronized method, and the change number for
5164   * the changelog entry must have been obtained by calling
5165   * {@code lastChangeNumber.incrementAndGet()}.
5166   *
5167   * @param  e        The changelog entry to add to the data set.
5168   * @param  authzDN  The authorization DN for the change.
5169   */
5170  private void addChangeLogEntry(final ChangeLogEntry e, final DN authzDN)
5171  {
5172    // Construct the DN object to use for the entry and put it in the map.
5173    final long changeNumber = e.getChangeNumber();
5174    final Schema schema = schemaRef.get();
5175    final DN dn = new DN(
5176         new RDN("changeNumber", String.valueOf(changeNumber), schema),
5177         changeLogBaseDN);
5178
5179    final Entry entry = e.duplicate();
5180    if (generateOperationalAttributes)
5181    {
5182      final Date d = new Date();
5183      entry.addAttribute(new Attribute("entryDN",
5184           DistinguishedNameMatchingRule.getInstance(),
5185           dn.toNormalizedString()));
5186      entry.addAttribute(new Attribute("entryUUID",
5187           UUID.randomUUID().toString()));
5188      entry.addAttribute(new Attribute("subschemaSubentry",
5189           DistinguishedNameMatchingRule.getInstance(),
5190           subschemaSubentryDN.toString()));
5191      entry.addAttribute(new Attribute("creatorsName",
5192           DistinguishedNameMatchingRule.getInstance(),
5193           authzDN.toString()));
5194      entry.addAttribute(new Attribute("createTimestamp",
5195           GeneralizedTimeMatchingRule.getInstance(),
5196           StaticUtils.encodeGeneralizedTime(d)));
5197      entry.addAttribute(new Attribute("modifiersName",
5198           DistinguishedNameMatchingRule.getInstance(),
5199           authzDN.toString()));
5200      entry.addAttribute(new Attribute("modifyTimestamp",
5201           GeneralizedTimeMatchingRule.getInstance(),
5202           StaticUtils.encodeGeneralizedTime(d)));
5203    }
5204
5205    entryMap.put(dn, new ReadOnlyEntry(entry));
5206    indexAdd(entry);
5207
5208    // Update the first change number and/or trim the changelog if necessary.
5209    final long firstNumber = firstChangeNumber.get();
5210    if (changeNumber == 1L)
5211    {
5212      // It's the first change, so we need to set the first change number.
5213      firstChangeNumber.set(1);
5214    }
5215    else
5216    {
5217      // See if we need to trim an entry.
5218      final long numChangeLogEntries = changeNumber - firstNumber + 1;
5219      if (numChangeLogEntries > maxChangelogEntries)
5220      {
5221        // We need to delete the first changelog entry and increment the
5222        // first change number.
5223        firstChangeNumber.incrementAndGet();
5224        final Entry deletedEntry = entryMap.remove(new DN(
5225             new RDN("changeNumber", String.valueOf(firstNumber), schema),
5226             changeLogBaseDN));
5227        indexDelete(deletedEntry);
5228      }
5229    }
5230  }
5231
5232
5233
5234  /**
5235   * Checks to see if the provided control map includes a proxied authorization
5236   * control (v1 or v2) and if so then attempts to determine the appropriate
5237   * authorization identity to use for the operation.
5238   *
5239   * @param  m  The map of request controls, indexed by OID.
5240   *
5241   * @return  The DN of the authorized user, or the current authentication DN
5242   *          if the control map does not include a proxied authorization
5243   *          request control.
5244   *
5245   * @throws  LDAPException  If a problem is encountered while attempting to
5246   *                         determine the authorization DN.
5247   */
5248  private DN handleProxiedAuthControl(final Map<String,Control> m)
5249          throws LDAPException
5250  {
5251    final ProxiedAuthorizationV1RequestControl p1 =
5252         (ProxiedAuthorizationV1RequestControl) m.get(
5253              ProxiedAuthorizationV1RequestControl.
5254                   PROXIED_AUTHORIZATION_V1_REQUEST_OID);
5255    if (p1 != null)
5256    {
5257      final DN authzDN = new DN(p1.getProxyDN(), schemaRef.get());
5258      if (authzDN.isNullDN() ||
5259          entryMap.containsKey(authzDN) ||
5260          additionalBindCredentials.containsKey(authzDN))
5261      {
5262        return authzDN;
5263      }
5264      else
5265      {
5266        throw new LDAPException(ResultCode.AUTHORIZATION_DENIED,
5267             ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get("dn:" + authzDN.toString()));
5268      }
5269    }
5270
5271    final ProxiedAuthorizationV2RequestControl p2 =
5272         (ProxiedAuthorizationV2RequestControl) m.get(
5273              ProxiedAuthorizationV2RequestControl.
5274                   PROXIED_AUTHORIZATION_V2_REQUEST_OID);
5275    if (p2 != null)
5276    {
5277      return getDNForAuthzID(p2.getAuthorizationID());
5278    }
5279
5280    return authenticatedDN;
5281  }
5282
5283
5284
5285  /**
5286   * Attempts to identify the DN of the user referenced by the provided
5287   * authorization ID string.  It may be "dn:" followed by the target DN, or
5288   * "u:" followed by the value of the uid attribute in the entry.  If it uses
5289   * the "dn:" form, then it may reference the DN of a regular entry or a DN
5290   * in the configured set of additional bind credentials.
5291   *
5292   * @param  authzID  The authorization ID to resolve to a user DN.
5293   *
5294   * @return  The DN identified for the provided authorization ID.
5295   *
5296   * @throws  LDAPException  If a problem prevents resolving the authorization
5297   *                         ID to a user DN.
5298   */
5299  public DN getDNForAuthzID(final String authzID)
5300         throws LDAPException
5301  {
5302    synchronized (entryMap)
5303    {
5304      final String lowerAuthzID = StaticUtils.toLowerCase(authzID);
5305      if (lowerAuthzID.startsWith("dn:"))
5306      {
5307        if (lowerAuthzID.equals("dn:"))
5308        {
5309          return DN.NULL_DN;
5310        }
5311        else
5312        {
5313          final DN dn = new DN(authzID.substring(3), schemaRef.get());
5314          if (entryMap.containsKey(dn) ||
5315               additionalBindCredentials.containsKey(dn))
5316          {
5317            return dn;
5318          }
5319          else
5320          {
5321            throw new LDAPException(ResultCode.AUTHORIZATION_DENIED,
5322                 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID));
5323          }
5324        }
5325      }
5326      else if (lowerAuthzID.startsWith("u:"))
5327      {
5328        final Filter f =
5329             Filter.createEqualityFilter("uid", authzID.substring(2));
5330        final List<ReadOnlyEntry> entryList = search("", SearchScope.SUB, f);
5331        if (entryList.size() == 1)
5332        {
5333          return entryList.get(0).getParsedDN();
5334        }
5335        else
5336        {
5337          throw new LDAPException(ResultCode.AUTHORIZATION_DENIED,
5338               ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID));
5339        }
5340      }
5341      else
5342      {
5343        throw new LDAPException(ResultCode.AUTHORIZATION_DENIED,
5344             ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID));
5345      }
5346    }
5347  }
5348
5349
5350
5351  /**
5352   * Checks to see if the provided control map includes an assertion request
5353   * control, and if so then checks to see whether the provided entry satisfies
5354   * the filter in that control.
5355   *
5356   * @param  m  The map of request controls, indexed by OID.
5357   * @param  e  The entry to examine against the assertion filter.
5358   *
5359   * @throws  LDAPException  If the control map includes an assertion request
5360   *                         control and the provided entry does not match the
5361   *                         filter contained in that control.
5362   */
5363  private static void handleAssertionRequestControl(final Map<String,Control> m,
5364                                                    final Entry e)
5365          throws LDAPException
5366  {
5367    final AssertionRequestControl c = (AssertionRequestControl)
5368         m.get(AssertionRequestControl.ASSERTION_REQUEST_OID);
5369    if (c == null)
5370    {
5371      return;
5372    }
5373
5374    try
5375    {
5376      if (c.getFilter().matchesEntry(e))
5377      {
5378        return;
5379      }
5380    }
5381    catch (final LDAPException le)
5382    {
5383      Debug.debugException(le);
5384    }
5385
5386    // If we've gotten here, then the filter doesn't match.
5387    throw new LDAPException(ResultCode.ASSERTION_FAILED,
5388         ERR_MEM_HANDLER_ASSERTION_CONTROL_NOT_SATISFIED.get());
5389  }
5390
5391
5392
5393  /**
5394   * Checks to see if the provided control map includes a pre-read request
5395   * control, and if so then generates the appropriate response control that
5396   * should be returned to the client.
5397   *
5398   * @param  m  The map of request controls, indexed by OID.
5399   * @param  e  The entry as it appeared before the operation.
5400   *
5401   * @return  The pre-read response control that should be returned to the
5402   *          client, or {@code null} if there is none.
5403   */
5404  private PreReadResponseControl handlePreReadControl(
5405               final Map<String,Control> m, final Entry e)
5406  {
5407    final PreReadRequestControl c = (PreReadRequestControl)
5408         m.get(PreReadRequestControl.PRE_READ_REQUEST_OID);
5409    if (c == null)
5410    {
5411      return null;
5412    }
5413
5414    final AtomicBoolean allUserAttrs = new AtomicBoolean(false);
5415    final AtomicBoolean allOpAttrs = new AtomicBoolean(false);
5416    final Map<String,List<List<String>>> returnAttrs =
5417         processRequestedAttributes(Arrays.asList(c.getAttributes()),
5418              allUserAttrs, allOpAttrs);
5419
5420    final Entry trimmedEntry = trimForRequestedAttributes(e, allUserAttrs.get(),
5421         allOpAttrs.get(), returnAttrs);
5422    return new PreReadResponseControl(new ReadOnlyEntry(trimmedEntry));
5423  }
5424
5425
5426
5427  /**
5428   * Checks to see if the provided control map includes a post-read request
5429   * control, and if so then generates the appropriate response control that
5430   * should be returned to the client.
5431   *
5432   * @param  m  The map of request controls, indexed by OID.
5433   * @param  e  The entry as it appeared before the operation.
5434   *
5435   * @return  The post-read response control that should be returned to the
5436   *          client, or {@code null} if there is none.
5437   */
5438  private PostReadResponseControl handlePostReadControl(
5439               final Map<String,Control> m, final Entry e)
5440  {
5441    final PostReadRequestControl c = (PostReadRequestControl)
5442         m.get(PostReadRequestControl.POST_READ_REQUEST_OID);
5443    if (c == null)
5444    {
5445      return null;
5446    }
5447
5448    final AtomicBoolean allUserAttrs = new AtomicBoolean(false);
5449    final AtomicBoolean allOpAttrs = new AtomicBoolean(false);
5450    final Map<String,List<List<String>>> returnAttrs =
5451         processRequestedAttributes(Arrays.asList(c.getAttributes()),
5452              allUserAttrs, allOpAttrs);
5453
5454    final Entry trimmedEntry = trimForRequestedAttributes(e, allUserAttrs.get(),
5455         allOpAttrs.get(), returnAttrs);
5456    return new PostReadResponseControl(new ReadOnlyEntry(trimmedEntry));
5457  }
5458
5459
5460
5461  /**
5462   * Finds the smart referral entry which is hierarchically nearest the entry
5463   * with the given DN.
5464   *
5465   * @param  dn  The DN for which to find the hierarchically nearest smart
5466   *             referral entry.
5467   *
5468   * @return  The hierarchically nearest smart referral entry for the provided
5469   *          DN, or {@code null} if there are no smart referral entries with
5470   *          the provided DN or any of its ancestors.
5471   */
5472  private Entry findNearestReferral(final DN dn)
5473  {
5474    DN d = dn;
5475    while (true)
5476    {
5477      final Entry e = entryMap.get(d);
5478      if (e == null)
5479      {
5480        d = d.getParent();
5481        if (d == null)
5482        {
5483          return null;
5484        }
5485      }
5486      else if (e.hasObjectClass("referral"))
5487      {
5488        return e;
5489      }
5490      else
5491      {
5492        return null;
5493      }
5494    }
5495  }
5496
5497
5498
5499  /**
5500   * Retrieves the referral URLs that should be used for the provided target DN
5501   * based on the given referral entry.
5502   *
5503   * @param  targetDN       The target DN from the associated operation.
5504   * @param  referralEntry  The entry containing the smart referral.
5505   *
5506   * @return  The referral URLs that should be returned.
5507   */
5508  private static List<String> getReferralURLs(final DN targetDN,
5509                                              final Entry referralEntry)
5510  {
5511    final String[] refs = referralEntry.getAttributeValues("ref");
5512    if (refs == null)
5513    {
5514      return null;
5515    }
5516
5517    final RDN[] retainRDNs;
5518    try
5519    {
5520      // If the target DN equals the referral entry DN, or if it's not
5521      // subordinate to the referral entry, then the URLs should be returned
5522      // as-is.
5523      final DN parsedEntryDN = referralEntry.getParsedDN();
5524      if (targetDN.equals(parsedEntryDN) ||
5525          (! targetDN.isDescendantOf(parsedEntryDN, true)))
5526      {
5527        return Arrays.asList(refs);
5528      }
5529
5530      final RDN[] targetRDNs   = targetDN.getRDNs();
5531      final RDN[] refEntryRDNs = referralEntry.getParsedDN().getRDNs();
5532      retainRDNs = new RDN[targetRDNs.length - refEntryRDNs.length];
5533      System.arraycopy(targetRDNs, 0, retainRDNs, 0, retainRDNs.length);
5534    }
5535    catch (final LDAPException le)
5536    {
5537      Debug.debugException(le);
5538      return Arrays.asList(refs);
5539    }
5540
5541    final List<String> refList = new ArrayList<String>(refs.length);
5542    for (final String ref : refs)
5543    {
5544      try
5545      {
5546        final LDAPURL url = new LDAPURL(ref);
5547        final RDN[] refRDNs = url.getBaseDN().getRDNs();
5548        final RDN[] newRefRDNs = new RDN[retainRDNs.length + refRDNs.length];
5549        System.arraycopy(retainRDNs, 0, newRefRDNs, 0, retainRDNs.length);
5550        System.arraycopy(refRDNs, 0, newRefRDNs, retainRDNs.length,
5551             refRDNs.length);
5552        final DN newBaseDN = new DN(newRefRDNs);
5553
5554        final LDAPURL newURL = new LDAPURL(url.getScheme(), url.getHost(),
5555             url.getPort(), newBaseDN, null, null, null);
5556        refList.add(newURL.toString());
5557      }
5558      catch (final LDAPException le)
5559      {
5560        Debug.debugException(le);
5561        refList.add(ref);
5562      }
5563    }
5564
5565    return refList;
5566  }
5567
5568
5569
5570  /**
5571   * Indicates whether the specified entry exists in the server.
5572   *
5573   * @param  dn  The DN of the entry for which to make the determination.
5574   *
5575   * @return  {@code true} if the entry exists, or {@code false} if not.
5576   *
5577   * @throws  LDAPException  If a problem is encountered while trying to
5578   *                         communicate with the directory server.
5579   */
5580  public boolean entryExists(final String dn)
5581         throws LDAPException
5582  {
5583    return (getEntry(dn) != null);
5584  }
5585
5586
5587
5588  /**
5589   * Indicates whether the specified entry exists in the server and matches the
5590   * given filter.
5591   *
5592   * @param  dn      The DN of the entry for which to make the determination.
5593   * @param  filter  The filter the entry is expected to match.
5594   *
5595   * @return  {@code true} if the entry exists and matches the specified filter,
5596   *          or {@code false} if not.
5597   *
5598   * @throws  LDAPException  If a problem is encountered while trying to
5599   *                         communicate with the directory server.
5600   */
5601  public boolean entryExists(final String dn, final String filter)
5602         throws LDAPException
5603  {
5604    synchronized (entryMap)
5605    {
5606      final Entry e = getEntry(dn);
5607      if (e == null)
5608      {
5609        return false;
5610      }
5611
5612      final Filter f = Filter.create(filter);
5613      try
5614      {
5615        return f.matchesEntry(e, schemaRef.get());
5616      }
5617      catch (final LDAPException le)
5618      {
5619        Debug.debugException(le);
5620        return false;
5621      }
5622    }
5623  }
5624
5625
5626
5627  /**
5628   * Indicates whether the specified entry exists in the server.  This will
5629   * return {@code true} only if the target entry exists and contains all values
5630   * for all attributes of the provided entry.  The entry will be allowed to
5631   * have attribute values not included in the provided entry.
5632   *
5633   * @param  entry  The entry to compare against the directory server.
5634   *
5635   * @return  {@code true} if the entry exists in the server and is a superset
5636   *          of the provided entry, or {@code false} if not.
5637   *
5638   * @throws  LDAPException  If a problem is encountered while trying to
5639   *                         communicate with the directory server.
5640   */
5641  public boolean entryExists(final Entry entry)
5642         throws LDAPException
5643  {
5644    synchronized (entryMap)
5645    {
5646      final Entry e = getEntry(entry.getDN());
5647      if (e == null)
5648      {
5649        return false;
5650      }
5651
5652      for (final Attribute a : entry.getAttributes())
5653      {
5654        for (final byte[] value : a.getValueByteArrays())
5655        {
5656          if (! e.hasAttributeValue(a.getName(), value))
5657          {
5658            return false;
5659          }
5660        }
5661      }
5662
5663      return true;
5664    }
5665  }
5666
5667
5668
5669  /**
5670   * Ensures that an entry with the provided DN exists in the directory.
5671   *
5672   * @param  dn  The DN of the entry for which to make the determination.
5673   *
5674   * @throws  LDAPException  If a problem is encountered while trying to
5675   *                         communicate with the directory server.
5676   *
5677   * @throws  AssertionError  If the target entry does not exist.
5678   */
5679  public void assertEntryExists(final String dn)
5680         throws LDAPException, AssertionError
5681  {
5682    final Entry e = getEntry(dn);
5683    if (e == null)
5684    {
5685      throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5686    }
5687  }
5688
5689
5690
5691  /**
5692   * Ensures that an entry with the provided DN exists in the directory.
5693   *
5694   * @param  dn      The DN of the entry for which to make the determination.
5695   * @param  filter  A filter that the target entry must match.
5696   *
5697   * @throws  LDAPException  If a problem is encountered while trying to
5698   *                         communicate with the directory server.
5699   *
5700   * @throws  AssertionError  If the target entry does not exist or does not
5701   *                          match the provided filter.
5702   */
5703  public void assertEntryExists(final String dn, final String filter)
5704         throws LDAPException, AssertionError
5705  {
5706    synchronized (entryMap)
5707    {
5708      final Entry e = getEntry(dn);
5709      if (e == null)
5710      {
5711        throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5712      }
5713
5714      final Filter f = Filter.create(filter);
5715      try
5716      {
5717        if (! f.matchesEntry(e, schemaRef.get()))
5718        {
5719          throw new AssertionError(
5720               ERR_MEM_HANDLER_TEST_ENTRY_DOES_NOT_MATCH_FILTER.get(dn,
5721                    filter));
5722        }
5723      }
5724      catch (final LDAPException le)
5725      {
5726        Debug.debugException(le);
5727        throw new AssertionError(
5728             ERR_MEM_HANDLER_TEST_ENTRY_DOES_NOT_MATCH_FILTER.get(dn, filter));
5729      }
5730    }
5731  }
5732
5733
5734
5735  /**
5736   * Ensures that an entry exists in the directory with the same DN and all
5737   * attribute values contained in the provided entry.  The server entry may
5738   * contain additional attributes and/or attribute values not included in the
5739   * provided entry.
5740   *
5741   * @param  entry  The entry expected to be present in the directory server.
5742   *
5743   * @throws  LDAPException  If a problem is encountered while trying to
5744   *                         communicate with the directory server.
5745   *
5746   * @throws  AssertionError  If the target entry does not exist or does not
5747   *                          match the provided filter.
5748   */
5749  public void assertEntryExists(final Entry entry)
5750         throws LDAPException, AssertionError
5751  {
5752    synchronized (entryMap)
5753    {
5754      final Entry e = getEntry(entry.getDN());
5755      if (e == null)
5756      {
5757        throw new AssertionError(
5758             ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(entry.getDN()));
5759      }
5760
5761
5762      final Collection<Attribute> attrs = entry.getAttributes();
5763      final List<String> messages = new ArrayList<String>(attrs.size());
5764
5765      final Schema schema = schemaRef.get();
5766      for (final Attribute a : entry.getAttributes())
5767      {
5768        final Filter presFilter = Filter.createPresenceFilter(a.getName());
5769        if (! presFilter.matchesEntry(e, schema))
5770        {
5771          messages.add(ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(entry.getDN(),
5772               a.getName()));
5773          continue;
5774        }
5775
5776        for (final byte[] value : a.getValueByteArrays())
5777        {
5778          final Filter eqFilter = Filter.createEqualityFilter(a.getName(),
5779               value);
5780          if (! eqFilter.matchesEntry(e, schema))
5781          {
5782            messages.add(ERR_MEM_HANDLER_TEST_VALUE_MISSING.get(entry.getDN(),
5783                 a.getName(), StaticUtils.toUTF8String(value)));
5784          }
5785        }
5786      }
5787
5788      if (! messages.isEmpty())
5789      {
5790        throw new AssertionError(StaticUtils.concatenateStrings(messages));
5791      }
5792    }
5793  }
5794
5795
5796
5797  /**
5798   * Retrieves a list containing the DNs of the entries which are missing from
5799   * the directory server.
5800   *
5801   * @param  dns  The DNs of the entries to try to find in the server.
5802   *
5803   * @return  A list containing all of the provided DNs that were not found in
5804   *          the server, or an empty list if all entries were found.
5805   *
5806   * @throws  LDAPException  If a problem is encountered while trying to
5807   *                         communicate with the directory server.
5808   */
5809  public List<String> getMissingEntryDNs(final Collection<String> dns)
5810         throws LDAPException
5811  {
5812    synchronized (entryMap)
5813    {
5814      final List<String> missingDNs = new ArrayList<String>(dns.size());
5815      for (final String dn : dns)
5816      {
5817        final Entry e = getEntry(dn);
5818        if (e == null)
5819        {
5820          missingDNs.add(dn);
5821        }
5822      }
5823
5824      return missingDNs;
5825    }
5826  }
5827
5828
5829
5830  /**
5831   * Ensures that all of the entries with the provided DNs exist in the
5832   * directory.
5833   *
5834   * @param  dns  The DNs of the entries for which to make the determination.
5835   *
5836   * @throws  LDAPException  If a problem is encountered while trying to
5837   *                         communicate with the directory server.
5838   *
5839   * @throws  AssertionError  If any of the target entries does not exist.
5840   */
5841  public void assertEntriesExist(final Collection<String> dns)
5842         throws LDAPException, AssertionError
5843  {
5844    synchronized (entryMap)
5845    {
5846      final List<String> missingDNs = getMissingEntryDNs(dns);
5847      if (missingDNs.isEmpty())
5848      {
5849        return;
5850      }
5851
5852      final List<String> messages = new ArrayList<String>(missingDNs.size());
5853      for (final String dn : missingDNs)
5854      {
5855        messages.add(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5856      }
5857
5858      throw new AssertionError(StaticUtils.concatenateStrings(messages));
5859    }
5860  }
5861
5862
5863
5864  /**
5865   * Retrieves a list containing all of the named attributes which do not exist
5866   * in the target entry.
5867   *
5868   * @param  dn              The DN of the entry to examine.
5869   * @param  attributeNames  The names of the attributes expected to be present
5870   *                         in the target entry.
5871   *
5872   * @return  A list containing the names of the attributes which were not
5873   *          present in the target entry, an empty list if all specified
5874   *          attributes were found in the entry, or {@code null} if the target
5875   *          entry does not exist.
5876   *
5877   * @throws  LDAPException  If a problem is encountered while trying to
5878   *                         communicate with the directory server.
5879   */
5880  public List<String> getMissingAttributeNames(final String dn,
5881                           final Collection<String> attributeNames)
5882         throws LDAPException
5883  {
5884    synchronized (entryMap)
5885    {
5886      final Entry e = getEntry(dn);
5887      if (e == null)
5888      {
5889        return null;
5890      }
5891
5892      final Schema schema = schemaRef.get();
5893      final List<String> missingAttrs =
5894           new ArrayList<String>(attributeNames.size());
5895      for (final String attr : attributeNames)
5896      {
5897        final Filter f = Filter.createPresenceFilter(attr);
5898        if (! f.matchesEntry(e, schema))
5899        {
5900          missingAttrs.add(attr);
5901        }
5902      }
5903
5904      return missingAttrs;
5905    }
5906  }
5907
5908
5909
5910  /**
5911   * Ensures that the specified entry exists in the directory with all of the
5912   * specified attributes.
5913   *
5914   * @param  dn              The DN of the entry to examine.
5915   * @param  attributeNames  The names of the attributes that are expected to be
5916   *                         present in the provided entry.
5917   *
5918   * @throws  LDAPException  If a problem is encountered while trying to
5919   *                         communicate with the directory server.
5920   *
5921   * @throws  AssertionError  If the target entry does not exist or does not
5922   *                          contain all of the specified attributes.
5923   */
5924  public void assertAttributeExists(final String dn,
5925                                    final Collection<String> attributeNames)
5926        throws LDAPException, AssertionError
5927  {
5928    synchronized (entryMap)
5929    {
5930      final List<String> missingAttrs =
5931           getMissingAttributeNames(dn, attributeNames);
5932      if (missingAttrs == null)
5933      {
5934        throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5935      }
5936      else if (missingAttrs.isEmpty())
5937      {
5938        return;
5939      }
5940
5941      final List<String> messages = new ArrayList<String>(missingAttrs.size());
5942      for (final String attr : missingAttrs)
5943      {
5944        messages.add(ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(dn, attr));
5945      }
5946
5947      throw new AssertionError(StaticUtils.concatenateStrings(messages));
5948    }
5949  }
5950
5951
5952
5953  /**
5954   * Retrieves a list of all provided attribute values which are missing from
5955   * the specified entry.  The target attribute may or may not contain
5956   * additional values.
5957   *
5958   * @param  dn               The DN of the entry to examine.
5959   * @param  attributeName    The attribute expected to be present in the target
5960   *                          entry with the given values.
5961   * @param  attributeValues  The values expected to be present in the target
5962   *                          entry.
5963   *
5964   * @return  A list containing all of the provided values which were not found
5965   *          in the entry, an empty list if all provided attribute values were
5966   *          found, or {@code null} if the target entry does not exist.
5967   *
5968   * @throws  LDAPException  If a problem is encountered while trying to
5969   *                         communicate with the directory server.
5970   */
5971  public List<String> getMissingAttributeValues(final String dn,
5972                           final String attributeName,
5973                           final Collection<String> attributeValues)
5974       throws LDAPException
5975  {
5976    synchronized (entryMap)
5977    {
5978      final Entry e = getEntry(dn);
5979      if (e == null)
5980      {
5981        return null;
5982      }
5983
5984      final Schema schema = schemaRef.get();
5985      final List<String> missingValues =
5986           new ArrayList<String>(attributeValues.size());
5987      for (final String value : attributeValues)
5988      {
5989        final Filter f = Filter.createEqualityFilter(attributeName, value);
5990        if (! f.matchesEntry(e, schema))
5991        {
5992          missingValues.add(value);
5993        }
5994      }
5995
5996      return missingValues;
5997    }
5998  }
5999
6000
6001
6002  /**
6003   * Ensures that the specified entry exists in the directory with all of the
6004   * specified values for the given attribute.  The attribute may or may not
6005   * contain additional values.
6006   *
6007   * @param  dn               The DN of the entry to examine.
6008   * @param  attributeName    The name of the attribute to examine.
6009   * @param  attributeValues  The set of values which must exist for the given
6010   *                          attribute.
6011   *
6012   * @throws  LDAPException  If a problem is encountered while trying to
6013   *                         communicate with the directory server.
6014   *
6015   * @throws  AssertionError  If the target entry does not exist, does not
6016   *                          contain the specified attribute, or that attribute
6017   *                          does not have all of the specified values.
6018   */
6019  public void assertValueExists(final String dn,
6020                                final String attributeName,
6021                                final Collection<String> attributeValues)
6022        throws LDAPException, AssertionError
6023  {
6024    synchronized (entryMap)
6025    {
6026      final List<String> missingValues =
6027           getMissingAttributeValues(dn, attributeName, attributeValues);
6028      if (missingValues == null)
6029      {
6030        throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
6031      }
6032      else if (missingValues.isEmpty())
6033      {
6034        return;
6035      }
6036
6037      // See if the attribute exists at all in the entry.
6038      final Entry e = getEntry(dn);
6039      final Filter f = Filter.createPresenceFilter(attributeName);
6040      if (! f.matchesEntry(e,  schemaRef.get()))
6041      {
6042        throw new AssertionError(
6043             ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(dn, attributeName));
6044      }
6045
6046      final List<String> messages = new ArrayList<String>(missingValues.size());
6047      for (final String value : missingValues)
6048      {
6049        messages.add(ERR_MEM_HANDLER_TEST_VALUE_MISSING.get(dn, attributeName,
6050             value));
6051      }
6052
6053      throw new AssertionError(StaticUtils.concatenateStrings(messages));
6054    }
6055  }
6056
6057
6058
6059  /**
6060   * Ensures that the specified entry does not exist in the directory.
6061   *
6062   * @param  dn  The DN of the entry expected to be missing.
6063   *
6064   * @throws  LDAPException  If a problem is encountered while trying to
6065   *                         communicate with the directory server.
6066   *
6067   * @throws  AssertionError  If the target entry is found in the server.
6068   */
6069  public void assertEntryMissing(final String dn)
6070         throws LDAPException, AssertionError
6071  {
6072    final Entry e = getEntry(dn);
6073    if (e != null)
6074    {
6075      throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_EXISTS.get(dn));
6076    }
6077  }
6078
6079
6080
6081  /**
6082   * Ensures that the specified entry exists in the directory but does not
6083   * contain any of the specified attributes.
6084   *
6085   * @param  dn              The DN of the entry expected to be present.
6086   * @param  attributeNames  The names of the attributes expected to be missing
6087   *                         from the entry.
6088   *
6089   * @throws  LDAPException  If a problem is encountered while trying to
6090   *                         communicate with the directory server.
6091   *
6092   * @throws  AssertionError  If the target entry is missing from the server, or
6093   *                          if it contains any of the target attributes.
6094   */
6095  public void assertAttributeMissing(final String dn,
6096                                     final Collection<String> attributeNames)
6097         throws LDAPException, AssertionError
6098  {
6099    synchronized (entryMap)
6100    {
6101      final Entry e = getEntry(dn);
6102      if (e == null)
6103      {
6104        throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
6105      }
6106
6107      final Schema schema = schemaRef.get();
6108      final List<String> messages =
6109           new ArrayList<String>(attributeNames.size());
6110      for (final String name : attributeNames)
6111      {
6112        final Filter f = Filter.createPresenceFilter(name);
6113        if (f.matchesEntry(e, schema))
6114        {
6115          messages.add(ERR_MEM_HANDLER_TEST_ATTR_EXISTS.get(dn, name));
6116        }
6117      }
6118
6119      if (! messages.isEmpty())
6120      {
6121        throw new AssertionError(StaticUtils.concatenateStrings(messages));
6122      }
6123    }
6124  }
6125
6126
6127
6128  /**
6129   * Ensures that the specified entry exists in the directory but does not
6130   * contain any of the specified attribute values.
6131   *
6132   * @param  dn               The DN of the entry expected to be present.
6133   * @param  attributeName    The name of the attribute to examine.
6134   * @param  attributeValues  The values expected to be missing from the target
6135   *                          entry.
6136   *
6137   * @throws  LDAPException  If a problem is encountered while trying to
6138   *                         communicate with the directory server.
6139   *
6140   * @throws  AssertionError  If the target entry is missing from the server, or
6141   *                          if it contains any of the target attribute values.
6142   */
6143  public void assertValueMissing(final String dn,
6144                                 final String attributeName,
6145                                 final Collection<String> attributeValues)
6146         throws LDAPException, AssertionError
6147  {
6148    synchronized (entryMap)
6149    {
6150      final Entry e = getEntry(dn);
6151      if (e == null)
6152      {
6153        throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
6154      }
6155
6156      final Schema schema = schemaRef.get();
6157      final List<String> messages =
6158           new ArrayList<String>(attributeValues.size());
6159      for (final String value : attributeValues)
6160      {
6161        final Filter f = Filter.createEqualityFilter(attributeName, value);
6162        if (f.matchesEntry(e, schema))
6163        {
6164          messages.add(ERR_MEM_HANDLER_TEST_VALUE_EXISTS.get(dn, attributeName,
6165               value));
6166        }
6167      }
6168
6169      if (! messages.isEmpty())
6170      {
6171        throw new AssertionError(StaticUtils.concatenateStrings(messages));
6172      }
6173    }
6174  }
6175}