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