opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/MatchingRuleUse.java
@@ -22,16 +22,23 @@ * * * Copyright 2009 Sun Microsystems, Inc. * Portions copyright 2015 ForgeRock AS */ package org.forgerock.opendj.ldap.schema; import static com.forgerock.opendj.ldap.CoreMessages.ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_ATTR1; import static com.forgerock.opendj.ldap.CoreMessages.ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_MATCHING_RULE1; import static java.util.Arrays.*; import static org.forgerock.opendj.ldap.schema.SchemaUtils.*; import static com.forgerock.opendj.ldap.CoreMessages.*; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -45,6 +52,202 @@ * attribute types that may be used for a given matching rule. */ public final class MatchingRuleUse extends SchemaElement { /** A fluent API for incrementally constructing matching rule uses. */ public static final class Builder extends SchemaElementBuilder<Builder> { private String oid; private final List<String> names = new LinkedList<String>(); private boolean isObsolete; private final Set<String> attributeOIDs = new LinkedHashSet<String>(); Builder(MatchingRuleUse mru, SchemaBuilder builder) { super(builder, mru); this.oid = mru.oid; this.names.addAll(mru.names); this.isObsolete = mru.isObsolete; this.attributeOIDs.addAll(mru.attributeOIDs); } Builder(final String oid, final SchemaBuilder builder) { super(builder); this.oid = oid; } /** * Adds this matching rule use definition to the schema, throwing a * {@code ConflictingSchemaElementException} if there is an existing * matching rule definition with the same numeric OID. * * @return The parent schema builder. * @throws ConflictingSchemaElementException * If there is an existing matching rule use definition with * the same numeric OID. */ public SchemaBuilder addToSchema() { return getSchemaBuilder().addMatchingRuleUse(new MatchingRuleUse(this), false); } /** * Adds this matching rule use definition to the schema overwriting any * existing matching rule use definition with the same numeric OID. * * @return The parent schema builder. */ public SchemaBuilder addToSchemaOverwrite() { return getSchemaBuilder().addMatchingRuleUse(new MatchingRuleUse(this), true); } /** * Adds the provided list of attribute types to the list of attribute * type the matching rule applies to. * * @param attributeOIDs * The list of attribute type numeric OIDs. * @return This builder. */ public Builder attributes(Collection<String> attributeOIDs) { this.attributeOIDs.addAll(attributeOIDs); return this; } /** * Adds the provided list of attribute types to the list of attribute * type the matching rule applies to. * * @param attributeOIDs * The list of attribute type numeric OIDs. * @return This builder. */ public Builder attributes(String... attributeOIDs) { this.attributeOIDs.addAll(asList(attributeOIDs)); return this; } @Override public Builder description(final String description) { return description0(description); } @Override public Builder extraProperties(final Map<String, List<String>> extraProperties) { return extraProperties0(extraProperties); } @Override public Builder extraProperties(final String extensionName, final String... extensionValues) { return extraProperties0(extensionName, extensionValues); } @Override Builder getThis() { return this; } /** * Adds the provided user friendly names. * * @param names * The user friendly names. * @return This builder. */ public Builder names(final Collection<String> names) { this.names.addAll(names); return this; } /** * Adds the provided user friendly names. * * @param names * The user friendly names. * @return This builder. */ public Builder names(final String... names) { return names(asList(names)); } /** * Specifies whether this schema element is obsolete. * * @param isObsolete * {@code true} if this schema element is obsolete * (default is {@code false}). * @return This builder. */ public Builder obsolete(final boolean isObsolete) { this.isObsolete = isObsolete; return this; } /** * Sets the numeric OID which uniquely identifies this matching rule use * definition. * * @param oid * The numeric OID. * @return This builder. */ public Builder oid(final String oid) { this.oid = oid; return this; } /** * Removes all attribute types the matching rule applies to. * * @return This builder. */ public Builder removeAllAttributes() { this.attributeOIDs.clear(); return this; } @Override public Builder removeAllExtraProperties() { return removeAllExtraProperties0(); } /** * Removes all user defined names. * * @return This builder. */ public Builder removeAllNames() { this.names.clear(); return this; } /** * Removes the provided attribute type. * * @param attributeOID * The attribute type OID to be removed. * @return This builder. */ public Builder removeAttribute(String attributeOID) { this.attributeOIDs.remove(attributeOID); return this; } @Override public Builder removeExtraProperty(String extensionName, String... extensionValues) { return removeExtraProperty0(extensionName, extensionValues); } /** * Removes the provided user defined name. * * @param name * The user defined name to be removed. * @return This builder. */ public Builder removeName(String name) { this.names.remove(name); return this; } } /** * The OID of the matching rule associated with this matching rule * use definition. @@ -66,16 +269,14 @@ private MatchingRule matchingRule; private Set<AttributeType> attributes = Collections.emptySet(); MatchingRuleUse(final String oid, final List<String> names, final String description, final boolean obsolete, final Set<String> attributeOIDs, final Map<String, List<String>> extraProperties, final String definition) { super(description, extraProperties, definition); private MatchingRuleUse(final Builder builder) { super(builder); Reject.ifNull(builder.oid); Reject.ifNull(oid, names, attributeOIDs); this.oid = oid; this.names = names; this.isObsolete = obsolete; this.attributeOIDs = attributeOIDs; this.oid = builder.oid; this.names = unmodifiableCopyOfList(builder.names); this.isObsolete = builder.isObsolete; this.attributeOIDs = unmodifiableCopyOfSet(builder.attributeOIDs); } /** @@ -218,11 +419,6 @@ return isObsolete; } MatchingRuleUse duplicate() { return new MatchingRuleUse(oid, names, getDescription(), isObsolete, attributeOIDs, getExtraProperties(), toString()); } @Override void toStringContent(final StringBuilder buffer) { buffer.append(oid); opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
@@ -820,13 +820,9 @@ reader.skipWhitespaces(); // The next set of characters must be the OID. final String oid = readOID(reader, allowsMalformedNamesAndOptions()); List<String> names = Collections.emptyList(); String description = "".intern(); boolean isObsolete = false; final MatchingRuleUse.Builder useBuilder = buildMatchingRuleUse(readOID(reader, allowsMalformedNamesAndOptions())); Set<String> attributes = null; Map<String, List<String>> extraProperties = Collections.emptyMap(); // At this point, we should have a pretty specific syntax that // describes what may come next, but some of the components are @@ -843,17 +839,16 @@ // No more tokens. break; } else if ("name".equalsIgnoreCase(tokenName)) { names = readNameDescriptors(reader, allowsMalformedNamesAndOptions()); useBuilder.names(readNameDescriptors(reader, allowsMalformedNamesAndOptions())); } else if ("desc".equalsIgnoreCase(tokenName)) { // This specifies the description for the attribute type. It // is an arbitrary string of characters enclosed in single // quotes. description = readQuotedString(reader); useBuilder.description(readQuotedString(reader)); } else if ("obsolete".equalsIgnoreCase(tokenName)) { // This indicates whether the attribute type should be // considered obsolete. We do not need to do any more // parsing for this token. isObsolete = true; // considered obsolete. useBuilder.obsolete(true); } else if ("applies".equalsIgnoreCase(tokenName)) { attributes = readOIDs(reader, allowsMalformedNamesAndOptions()); } else if (tokenName.matches("^X-[A-Za-z_-]+$")) { @@ -862,10 +857,7 @@ // or an open parenthesis followed by one or more values in // single quotes separated by spaces followed by a close // parenthesis. if (extraProperties.isEmpty()) { extraProperties = new HashMap<String, List<String>>(); } extraProperties.put(tokenName, readExtensions(reader)); useBuilder.extraProperties(tokenName, readExtensions(reader)); } else { throw new LocalizedIllegalArgumentException( ERR_ATTR_SYNTAX_MRUSE_ILLEGAL_TOKEN1.get(definition, tokenName)); @@ -876,21 +868,13 @@ if (attributes == null || attributes.size() == 0) { throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_MRUSE_NO_ATTR.get(definition)); } useBuilder.attributes(attributes); if (!extraProperties.isEmpty()) { extraProperties = Collections.unmodifiableMap(extraProperties); } final MatchingRuleUse use = new MatchingRuleUse(oid, names, description, isObsolete, attributes, extraProperties, definition); addMatchingRuleUse(use, overwrite); return overwrite ? useBuilder.addToSchemaOverwrite() : useBuilder.addToSchema(); } catch (final DecodeException e) { final LocalizableMessage msg = ERR_ATTR_SYNTAX_MRUSE_INVALID1.get(definition, e.getMessageObject()); final LocalizableMessage msg = ERR_ATTR_SYNTAX_MRUSE_INVALID1.get(definition, e.getMessageObject()); throw new LocalizedIllegalArgumentException(msg, e.getCause()); } return this; } /** @@ -949,41 +933,23 @@ } /** * Adds the provided matching rule use definition to this schema builder. * Returns a builder which can be used for incrementally constructing a new * matching rule use before adding it to the schema. Example usage: * * <pre> * SchemaBuilder builder = ...; * builder.buildMatchingRuleUse("matchingrule-oid") * .name("matching rule use name") * .addToSchema(); * </pre> * * @param oid * The OID of the matching rule use definition. * @param names * The user-friendly names of the matching rule use definition. * @param description * The description of the matching rule use definition. * @param obsolete * {@code true} if the matching rule use definition is obsolete, * otherwise {@code false}. * @param attributeOIDs * The list of attribute types the matching rule applies to. * @param extraProperties * A map containing additional properties associated with the * matching rule use definition. * @param overwrite * {@code true} if any existing matching rule use with the same * OID should be overwritten. * @return A reference to this schema builder. * @throws ConflictingSchemaElementException * If {@code overwrite} was {@code false} and a conflicting * schema element was found. * The OID of the matching rule definition. * @return A builder to continue building the MatchingRuleUse. */ SchemaBuilder addMatchingRuleUse(final String oid, final List<String> names, final String description, final boolean obsolete, final Set<String> attributeOIDs, final Map<String, List<String>> extraProperties, final boolean overwrite) { public MatchingRuleUse.Builder buildMatchingRuleUse(final String oid) { lazyInitBuilder(); final MatchingRuleUse use = new MatchingRuleUse(oid, unmodifiableCopyOfList(names), description, obsolete, unmodifiableCopyOfSet(attributeOIDs), unmodifiableCopyOfExtraProperties(extraProperties), null); addMatchingRuleUse(use, overwrite); return this; return new MatchingRuleUse.Builder(oid, this); } /** @@ -1254,6 +1220,21 @@ } /** * Returns a matching rule use builder whose fields are initialized to the * values of the provided matching rule use object. This method should be used when * duplicating matching rule uses from external schemas or when modifying * existing matching rule uses. * * @param matchingRuleUse * The matching rule use source. * @return A builder to continue building the MatchingRuleUse. */ public MatchingRuleUse.Builder buildMatchingRuleUse(final MatchingRuleUse matchingRuleUse) { lazyInitBuilder(); return new MatchingRuleUse.Builder(matchingRuleUse, this); } /** * Returns a name form builder whose fields are initialized to the * values of the provided name form. This method should be used when * duplicating name forms from external schemas or when modifying @@ -2258,7 +2239,7 @@ return this; } private void addMatchingRuleUse(final MatchingRuleUse use, final boolean overwrite) { SchemaBuilder addMatchingRuleUse(final MatchingRuleUse use, final boolean overwrite) { MatchingRuleUse conflictingUse; if (numericOID2MatchingRuleUses.containsKey(use.getMatchingRuleOID())) { conflictingUse = numericOID2MatchingRuleUses.get(use.getMatchingRuleOID()); @@ -2285,6 +2266,8 @@ uses.add(use); } } return this; } SchemaBuilder addMatchingRule(final MatchingRule rule, final boolean overwrite) { @@ -2402,7 +2385,7 @@ } for (final MatchingRuleUse matchingRuleUse : schema.getMatchingRuleUses()) { addMatchingRuleUse(matchingRuleUse.duplicate(), overwrite); addMatchingRuleUse(matchingRuleUse, overwrite); } for (final AttributeType attributeType : schema.getAttributeTypes()) { opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/MatchingRuleUseBuilderTestCase.java
New file @@ -0,0 +1,157 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2015 ForgeRock AS. */ package org.forgerock.opendj.ldap.schema; import static java.util.Collections.*; import static org.fest.assertions.Assertions.*; import static org.fest.assertions.MapAssert.*; import static org.forgerock.opendj.ldap.schema.SchemaConstants.*; import org.testng.annotations.Test; public class MatchingRuleUseBuilderTestCase extends AbstractSchemaTestCase { @Test public void testValidMatchingRuleUse() { final Schema schema = new SchemaBuilder(Schema.getCoreSchema()) .buildMatchingRuleUse(EMR_CASE_EXACT_OID) .names("Matching rule use test") .description("Matching rule use description") .attributes("2.5.4.40", "2.5.4.52", "2.5.4.53") .extraProperties("property name", "property value") .addToSchema() .toSchema(); assertThat(schema.getWarnings()).isEmpty(); final MatchingRuleUse mru = schema.getMatchingRuleUse(EMR_CASE_EXACT_OID); assertThat(mru).isNotNull(); assertThat(mru.getMatchingRuleOID()).isEqualTo(EMR_CASE_EXACT_OID); assertThat(mru.getNames()).containsOnly("Matching rule use test"); assertThat(mru.getDescription()).isEqualTo("Matching rule use description"); assertThat(mru.getAttributes()).containsOnly(schema.getAttributeType("2.5.4.40"), schema.getAttributeType("2.5.4.52"), schema.getAttributeType("2.5.4.53")); assertThat(mru.getExtraProperties()).includes(entry("property name", singletonList("property value"))); assertThat(mru.isObsolete()).isFalse(); } @Test public void testCopyConstructor() { final SchemaBuilder builder = new SchemaBuilder(Schema.getCoreSchema()) .buildMatchingRuleUse(EMR_BIT_STRING_OID) .description("Matching rule use description") .names("Matching rule use test") .attributes("2.5.4.40") .extraProperties("property name", "property value") .addToSchema(); final Schema schema = builder.toSchema(); assertThat(schema.getWarnings()).isEmpty(); final Schema schemaCopy = builder.buildMatchingRuleUse(schema.getMatchingRuleUse(EMR_BIT_STRING_OID)) .oid(EMR_OCTET_STRING_OID) .names("Matching rule use test copy") .attributes("2.5.4.53") .addToSchema() .toSchema(); assertThat(schemaCopy.getWarnings()).isEmpty(); final MatchingRuleUse mru = schemaCopy.getMatchingRuleUse(EMR_OCTET_STRING_OID); assertThat(mru).isNotNull(); assertThat(mru.getMatchingRuleOID()).isEqualTo(EMR_OCTET_STRING_OID); assertThat(mru.getNames()).containsOnly("Matching rule use test", "Matching rule use test copy"); assertThat(mru.getDescription()).isEqualTo("Matching rule use description"); assertThat(mru.getAttributes()).containsOnly(schema.getAttributeType("2.5.4.40"), schema.getAttributeType("2.5.4.53")); assertThat(mru.getExtraProperties()).includes(entry("property name", singletonList("property value"))); assertThat(mru.isObsolete()).isFalse(); } @Test(expectedExceptions = ConflictingSchemaElementException.class) public void testBuilderDoesNotAllowOverwrite() throws Exception { final SchemaBuilder builder = new SchemaBuilder(Schema.getCoreSchema()) .buildMatchingRuleUse(EMR_BIT_STRING_OID) .names("Matching rule use test") .attributes("2.5.4.40") .addToSchema(); builder.buildMatchingRuleUse(EMR_BIT_STRING_OID) .addToSchema() .toSchema(); } @Test(expectedExceptions = NullPointerException.class) public void testBuilderDoesNotAllowNullMatchingRuleOID() throws Exception { new SchemaBuilder(Schema.getCoreSchema()) .buildMatchingRuleUse((String) null) .addToSchema(); } @Test public void testBuilderRemoveAll() throws Exception { final MatchingRuleUse.Builder builder = new SchemaBuilder(Schema.getCoreSchema()) .buildMatchingRuleUse(EMR_BIT_STRING_OID) .description("Matching rule use description") .names("Matching rule use test") .attributes("2.5.4.40", "2.5.4.52") .extraProperties("property name", "property value"); final Schema schema = builder.removeAllNames() .removeAllAttributes() .removeAllExtraProperties() .addToSchema() .toSchema(); assertThat(schema.getWarnings()).isEmpty(); final MatchingRuleUse mru = schema.getMatchingRuleUse(EMR_BIT_STRING_OID); assertThat(mru.getNames()).isEmpty(); assertThat(mru.getAttributes()).isEmpty(); assertThat(mru.getExtraProperties()).isEmpty(); } @Test public void testBuilderRemove() throws Exception { final MatchingRuleUse.Builder builder = new SchemaBuilder(Schema.getCoreSchema()) .buildMatchingRuleUse(EMR_OCTET_STRING_OID) .description("Matching rule use description") .names("Matching rule use test", "I should not be in the schema") .attributes("2.5.4.52", "I should not be in the schema") .extraProperties("property name", "property value"); final Schema schema = builder.removeName("I should not be in the schema") .removeAttribute("I should not be in the schema") .removeExtraProperty("property name") .addToSchema() .toSchema(); assertThat(schema.getWarnings()).isEmpty(); final MatchingRuleUse mru = schema.getMatchingRuleUse(EMR_OCTET_STRING_OID); assertThat(mru.getNames()).containsOnly("Matching rule use test"); assertThat(mru.getAttributes()).containsOnly(schema.getAttributeType("2.5.4.52")); assertThat(mru.getExtraProperties()).isEmpty(); } }