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