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