opendj-core/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java
@@ -15,6 +15,7 @@ */ package org.forgerock.opendj.ldap; import static org.forgerock.opendj.ldap.AttributeDescription.create; import static org.forgerock.opendj.ldap.Attributes.singletonAttribute; import static org.forgerock.opendj.ldap.Entries.modifyEntry; import static org.forgerock.opendj.ldap.LdapException.newLdapException; @@ -22,10 +23,13 @@ import static org.forgerock.opendj.ldap.responses.Responses.newCompareResult; import static org.forgerock.opendj.ldap.responses.Responses.newResult; import static org.forgerock.opendj.ldap.responses.Responses.newSearchResultEntry; import static org.forgerock.opendj.ldap.schema.CoreSchema.getHasSubordinatesAttributeType; import static org.forgerock.opendj.ldap.schema.CoreSchema.getNumSubordinatesAttributeType; import java.io.IOException; import java.util.Collection; import java.util.NavigableMap; import java.util.SortedSet; import java.util.concurrent.ConcurrentSkipListMap; import org.forgerock.i18n.LocalizedIllegalArgumentException; @@ -91,10 +95,14 @@ * </pre> */ public final class MemoryBackend implements RequestHandler<RequestContext> { private static final AttributeDescription HAS_SUBORDINATES = create(getHasSubordinatesAttributeType()); private static final AttributeDescription NUM_SUBORDINATES = create(getNumSubordinatesAttributeType()); private final DecodeOptions decodeOptions; private final ConcurrentSkipListMap<DN, Entry> entries = new ConcurrentSkipListMap<>(); private final Schema schema; private final Object writeLock = new Object(); private boolean enableVirtualAttributes; /** * Creates a new empty memory backend which will use the default schema. @@ -141,12 +149,24 @@ * or if duplicate entries are detected. */ public MemoryBackend(final Schema schema, final EntryReader reader) throws IOException { this.schema = schema; this.decodeOptions = new DecodeOptions().setSchema(schema); this(schema); load(reader, false); } /** * Indicates whether search responses should include the {@code hasSubordinates} and {@code numSubordinates} * virtual attributes if requested. * * @param enabled * {@code true} if the virtual attributes should be included. * @return This memory backend. */ public MemoryBackend enableVirtualAttributes(final boolean enabled) { this.enableVirtualAttributes = enabled; return this; } /** * Clears the contents of this memory backend so that it does not contain * any entries. * @@ -219,6 +239,65 @@ return entries.values(); } /** * Returns {@code true} if the named entry exists and has at least one child entry. * * @param dn * The name of the entry. * @return {@code true} if the named entry exists and has at least one child entry. */ public boolean hasSubordinates(final String dn) { return hasSubordinates(DN.valueOf(dn, schema)); } /** * Returns {@code true} if the named entry exists and has at least one child entry. * * @param dn * The name of the entry. * @return {@code true} if the named entry exists and has at least one child entry. */ public boolean hasSubordinates(final DN dn) { if (!contains(dn)) { return false; } final DN next = entries.higherKey(dn); return next != null && next.isChildOf(dn); } /** * Returns the number of entries which are immediately subordinate to the named entry, or {@code 0} if the named * entry does not exist. * * @param dn * The name of the entry. * @return The number of entries which are immediately subordinate to the named entry. */ public int numSubordinates(final String dn) { return numSubordinates(DN.valueOf(dn, schema)); } /** * Returns the number of entries which are immediately subordinate to the named entry, or {@code 0} if the named * entry does not exist. * * @param dn * The name of the entry. * @return The number of entries which are immediately subordinate to the named entry. */ public int numSubordinates(final DN dn) { final DN start = dn.child(RDN.minValue()); final DN end = dn.child(RDN.maxValue()); final SortedSet<DN> subtree = entries.keySet().subSet(start, end); int numSubordinates = 0; for (DN subordinate : subtree) { if (subordinate.isChildOf(dn)) { numSubordinates++; } } return numSubordinates; } @Override public void handleAdd(final RequestContext requestContext, final AddRequest request, final IntermediateResponseHandler intermediateResponseHandler, @@ -261,7 +340,7 @@ "non-SIMPLE authentication not supported: " + request.getAuthenticationType()); } entry = getRequiredEntry(null, username); if (!entry.containsAttribute("userPassword", password)) { if (!entry.containsAttribute("userPassword", (Object) password)) { throw newLdapException(ResultCode.INVALID_CREDENTIALS, "Wrong password"); } } @@ -381,8 +460,9 @@ switch (scope.asEnum()) { case BASE_OBJECT: final Entry baseEntry = getRequiredEntry(request, dn); if (matcher.matches(baseEntry).toBoolean()) { sendEntry(attributeFilter, entryHandler, baseEntry); final Entry augmentedEntry = addVirtualAttributesIfNeeded(baseEntry); if (matcher.matches(augmentedEntry).toBoolean()) { sendEntry(attributeFilter, entryHandler, augmentedEntry); } resultHandler.handleResult(newResult(ResultCode.SUCCESS)); break; @@ -406,6 +486,17 @@ } } private Entry addVirtualAttributesIfNeeded(final Entry entry) { if (!enableVirtualAttributes) { return entry; } final Entry augmentedEntry = new LinkedHashMapEntry(entry); final int numSubordinates = numSubordinates(entry.getName()); augmentedEntry.addAttribute(singletonAttribute(NUM_SUBORDINATES, numSubordinates)); augmentedEntry.addAttribute(singletonAttribute(HAS_SUBORDINATES, numSubordinates > 0)); return augmentedEntry; } /** * Returns {@code true} if this memory backend does not contain any entries. * @@ -482,7 +573,7 @@ private void searchWithSubordinates(final RequestContext requestContext, final SearchResultHandler entryHandler, final LdapResultHandler<Result> resultHandler, final DN dn, final Matcher matcher, final AttributeFilter attributeFilter, final int sizeLimit, SearchScope scope, SimplePagedResultsControl pagedResults) throws CancelledResultException, LdapException { SimplePagedResultsControl pagedResults) throws LdapException { final NavigableMap<DN, Entry> subtree = entries.subMap(dn, dn.child(RDN.maxValue())); if (subtree.isEmpty() || !dn.equals(subtree.firstKey())) { throw newLdapException(newResult(ResultCode.NO_SUCH_OBJECT)); @@ -497,7 +588,8 @@ requestContext.checkIfCancelled(false); if (scope.equals(SearchScope.WHOLE_SUBTREE) || entry.getName().isChildOf(dn) || (scope.equals(SearchScope.SUBORDINATES) && !entry.getName().equals(dn))) { if (matcher.matches(entry).toBoolean()) { final Entry augmentedEntry = addVirtualAttributesIfNeeded(entry); if (matcher.matches(augmentedEntry).toBoolean()) { /* * This entry is going to be returned to the client so it * counts towards the size limit and any paging criteria. @@ -514,7 +606,7 @@ } // Send the entry back to the client. if (!sendEntry(attributeFilter, entryHandler, entry)) { if (!sendEntry(attributeFilter, entryHandler, augmentedEntry)) { // Client has disconnected or cancelled. break; } opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CoreSchema.java
@@ -12,7 +12,7 @@ * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2009 Sun Microsystems, Inc. * Portions copyright 2014 ForgeRock AS. * Portions copyright 2014-2016 ForgeRock AS. */ package org.forgerock.opendj.ldap.schema; @@ -275,6 +275,8 @@ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.42"); private static final AttributeType GOVERNING_STRUCTURE_RULE_ATTRIBUTE_TYPE = CoreSchemaImpl.getInstance().getAttributeType("2.5.21.10"); private static final AttributeType HAS_SUBORDINATES_ATTRIBUTE_TYPE = CoreSchemaImpl.getInstance().getAttributeType("2.5.18.9"); private static final AttributeType HOUSE_IDENTIFIER_ATTRIBUTE_TYPE = CoreSchemaImpl.getInstance().getAttributeType("2.5.4.51"); private static final AttributeType INITIALS_ATTRIBUTE_TYPE = @@ -301,6 +303,8 @@ CoreSchemaImpl.getInstance().getAttributeType("2.5.21.7"); private static final AttributeType NAMING_CONTEXTS_ATTRIBUTE_TYPE = CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.5"); private static final AttributeType NUM_SUBORDINATES_ATTRIBUTE_TYPE = CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.453.16.2.103"); private static final AttributeType OBJECT_CLASSES_ATTRIBUTE_TYPE = CoreSchemaImpl.getInstance().getAttributeType("2.5.21.6"); private static final AttributeType OBJECT_CLASS_ATTRIBUTE_TYPE = @@ -1571,6 +1575,16 @@ } /** * Returns a reference to the {@code hasSubordinates} Attribute Type * which has the OID {@code 2.5.18.9}. * * @return A reference to the {@code hasSubordinates} Attribute Type. */ public static AttributeType getHasSubordinatesAttributeType() { return HAS_SUBORDINATES_ATTRIBUTE_TYPE; } /** * Returns a reference to the {@code houseIdentifier} Attribute Type * which has the OID {@code 2.5.4.51}. * @@ -1701,6 +1715,16 @@ } /** * Returns a reference to the {@code numSubordinates} Attribute Type * which has the OID {@code 1.3.6.1.4.1.453.16.2.103}. * * @return A reference to the {@code numSubordinates} Attribute Type. */ public static AttributeType getNumSubordinatesAttributeType() { return NUM_SUBORDINATES_ATTRIBUTE_TYPE; } /** * Returns a reference to the {@code objectClasses} Attribute Type * which has the OID {@code 2.5.21.6}. * opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CoreSchemaImpl.java
@@ -12,7 +12,7 @@ * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2009-2010 Sun Microsystems, Inc. * Portions copyright 2013-2015 ForgeRock AS. * Portions copyright 2013-2016 ForgeRock AS. * Portions copyright 2014 Manuel Gaupp */ package org.forgerock.opendj.ldap.schema; @@ -33,6 +33,7 @@ import java.util.Map.Entry; import java.util.TreeMap; /** Minimal set of LDAP standard schema elements. */ final class CoreSchemaImpl { private static final Map<String, List<String>> X500_ORIGIN = Collections.singletonMap( SCHEMA_PROPERTY_ORIGIN, Collections.singletonList("X.500")); @@ -1099,6 +1100,28 @@ .extraProperties(RFC4512_ORIGIN) .addToSchema(); builder.buildAttributeType("2.5.18.9") .names("hasSubordinates") .equalityMatchingRule(EMR_BOOLEAN_NAME) .syntax(SYNTAX_INTEGER_OID) .singleValue(true) .noUserModification(true) .usage(AttributeUsage.DIRECTORY_OPERATION) .extraProperties(SCHEMA_PROPERTY_ORIGIN, "X.501") .addToSchema(); builder.buildAttributeType("1.3.6.1.4.1.453.16.2.103") .names("numSubordinates") .description("Count of immediate subordinates") .equalityMatchingRule(EMR_INTEGER_NAME) .orderingMatchingRule(OMR_INTEGER_NAME) .syntax(SYNTAX_INTEGER_OID) .singleValue(true) .noUserModification(true) .usage(AttributeUsage.DIRECTORY_OPERATION) .extraProperties(SCHEMA_PROPERTY_ORIGIN, "draft-ietf-boreham-numsubordinates") .addToSchema(); builder.buildAttributeType("2.5.18.10") .names("subschemaSubentry") .equalityMatchingRule(EMR_DN_NAME) opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/GenerateCoreSchema.java
@@ -13,7 +13,7 @@ * * Copyright 2009 Sun Microsystems, Inc. * Portions Copyright 2014 Manuel Gaupp * Portions Copyright 2015 ForgeRock AS. * Portions Copyright 2015-2016 ForgeRock AS. */ package org.forgerock.opendj.ldap.schema; @@ -115,26 +115,21 @@ out.println("// It is automatically generated using GenerateCoreSchema class."); out.println(); out.println("/**"); out.println(" * The OpenDJ SDK core schema contains standard LDAP " + "RFC schema elements. These include:"); out.println(" * The OpenDJ SDK core schema contains standard LDAP RFC schema elements. These include:"); out.println(" * <ul>"); out.println(" * <li><a href=\"http://tools.ietf.org/html/rfc4512\">RFC 4512 -"); out .println(" * Lightweight Directory Access Protocol (LDAP): Directory Information"); out.println(" * Lightweight Directory Access Protocol (LDAP): Directory Information"); out.println(" * Models </a>"); out.println(" * <li><a href=\"http://tools.ietf.org/html/rfc4517\">RFC 4517 -"); out .println(" * Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching"); out.println(" * Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching"); out.println(" * Rules </a>"); out.println(" * <li><a href=\"http://tools.ietf.org/html/rfc4519\">RFC 4519 -"); out.println(" * Lightweight Directory Access Protocol (LDAP): Schema for User"); out.println(" * Applications </a>"); out.println(" * <li><a href=\"http://tools.ietf.org/html/rfc4530\">RFC 4530 -"); out .println(" * Lightweight Directory Access Protocol (LDAP): entryUUID Operational"); out.println(" * Lightweight Directory Access Protocol (LDAP): entryUUID Operational"); out.println(" * Attribute </a>"); out .println(" * <li><a href=\"http://tools.ietf.org/html/rfc3045\">RFC 3045 - Storing"); out.println(" * <li><a href=\"http://tools.ietf.org/html/rfc3045\">RFC 3045 - Storing"); out.println(" * Vendor Information in the LDAP Root DSE </a>"); out.println(" * <li><a href=\"http://tools.ietf.org/html/rfc3112\">RFC 3112 - LDAP"); out.println(" * Authentication Password Schema </a>"); @@ -149,15 +144,13 @@ out.println(" // Core Syntaxes"); for (final Map.Entry<String, Syntax> syntax : syntaxes.entrySet()) { out.println(" private static final Syntax " + syntax.getKey() + " ="); out.println(" CoreSchemaImpl.getInstance().getSyntax(\"" + syntax.getValue().getOID() + "\");"); out.println(" CoreSchemaImpl.getInstance().getSyntax(\"" + syntax.getValue().getOID() + "\");"); } out.println(); out.println(" // Core Matching Rules"); for (final Map.Entry<String, MatchingRule> matchingRule : matchingRules.entrySet()) { out.println(" private static final MatchingRule " + matchingRule.getKey() + " ="); out.println(" private static final MatchingRule " + matchingRule.getKey() + " ="); out.println(" CoreSchemaImpl.getInstance().getMatchingRule(\"" + matchingRule.getValue().getOID() + "\");"); } @@ -165,8 +158,7 @@ out.println(); out.println(" // Core Attribute Types"); for (final Map.Entry<String, AttributeType> attributeType : attributeTypes.entrySet()) { out.println(" private static final AttributeType " + attributeType.getKey() + " ="); out.println(" private static final AttributeType " + attributeType.getKey() + " ="); out.println(" CoreSchemaImpl.getInstance().getAttributeType(\"" + attributeType.getValue().getOID() + "\");"); } @@ -203,11 +195,9 @@ + " Syntax"); out.println(" /**"); out.println(" * Returns a reference to the " + description); out.println(" * which has the OID " + toCodeJavaDoc(syntax.getValue().getOID()) + "."); out.println(" * which has the OID " + toCodeJavaDoc(syntax.getValue().getOID()) + "."); out.println(" *"); out.println(" * @return A reference to the " + description + "."); out.println(" */"); out.println(" public static Syntax get" + toJavaName(syntax.getKey()) + "() {"); out.println(" return " + syntax.getKey() + ";"); @@ -224,7 +214,6 @@ + toCodeJavaDoc(matchingRule.getValue().getOID()) + "."); out.println(" *"); out.println(" * @return A reference to the " + description + " Matching Rule."); out.println(" */"); out.println(" public static MatchingRule get" + toJavaName(matchingRule.getKey()) + "() {"); out.println(" return " + matchingRule.getKey() + ";"); @@ -241,10 +230,8 @@ + toCodeJavaDoc(attributeType.getValue().getOID()) + "."); out.println(" *"); out.println(" * @return A reference to the " + description + " Attribute Type."); out.println(" */"); out.println(" public static AttributeType get" + toJavaName(attributeType.getKey()) + "() {"); out.println(" public static AttributeType get" + toJavaName(attributeType.getKey()) + "() {"); out.println(" return " + attributeType.getKey() + ";"); out.println(" }"); } @@ -259,10 +246,8 @@ + toCodeJavaDoc(objectClass.getValue().getOID()) + "."); out.println(" *"); out.println(" * @return A reference to the " + description + " Object Class."); out.println(" */"); out.println(" public static ObjectClass get" + toJavaName(objectClass.getKey()) + "() {"); out.println(" public static ObjectClass get" + toJavaName(objectClass.getKey()) + "() {"); out.println(" return " + objectClass.getKey() + ";"); out.println(" }"); } opendj-core/src/test/java/org/forgerock/opendj/ldap/MemoryBackendTestCase.java
@@ -590,7 +590,30 @@ getUser1Entry()); } @Test public void testHasSubordinates() throws Exception { final MemoryBackend backend = getMemoryBackend(); assertThat(backend.hasSubordinates("dc=com")).isTrue(); assertThat(backend.hasSubordinates("dc=example,dc=com")).isTrue(); assertThat(backend.hasSubordinates("uid=test1,ou=people,dc=example,dc=com")).isFalse(); // leaf assertThat(backend.hasSubordinates("dc=c,dc=b,dc=a")).isFalse(); // doesn't exist } @Test public void testNumSubordinates() throws Exception { final MemoryBackend backend = getMemoryBackend(); assertThat(backend.numSubordinates("dc=com")).isEqualTo(2); assertThat(backend.numSubordinates("dc=example,dc=com")).isEqualTo(1); assertThat(backend.numSubordinates("ou=people,dc=example,dc=com")).isEqualTo(5); assertThat(backend.numSubordinates("uid=test1,ou=people,dc=example,dc=com")).isEqualTo(0); // leaf assertThat(backend.numSubordinates("dc=c,dc=b,dc=a")).isEqualTo(0); // doesn't exist } private Connection getConnection() throws IOException { return newInternalConnection(getMemoryBackend()); } private MemoryBackend getMemoryBackend() throws IOException { // @formatter:off String[] ldifEntries = new String[] { "dn: dc=com", @@ -659,8 +682,7 @@ }; // @formatter:on numberOfEntriesInBackend = getNumberOfEntries(ldifEntries); final MemoryBackend backend = new MemoryBackend(new LDIFEntryReader(ldifEntries)); return newInternalConnection(backend); return new MemoryBackend(new LDIFEntryReader(ldifEntries)); } private int getNumberOfEntries(String[] ldifEntries) {