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