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