mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

sin
21.51.2009 c543bd8a63f66262b7511ba5f90962a497bf0adf
issue# 4120:Provide an implementation for enumeration syntax
7 files modified
702 ■■■■■ changed files
opends/src/messages/messages/backend.properties 4 ●●●● patch | view | raw | blame | history
opends/src/messages/messages/schema.properties 6 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/SchemaBackend.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/schema/LDAPSyntaxDescriptionSyntax.java 448 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/schema/SchemaConstants.java 14 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Schema.java 1 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/schema/LDAPSyntaxTest.java 227 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/backend.properties
@@ -1145,8 +1145,8 @@
MILD_ERR_SCHEMA_MODIFY_REMOVE_NO_SUCH_LSD_416=Unable to remove ldap syntax \
 description %s from the server schema because no such ldap syntax \
 description  is defined
MILD_ERR_ATTR_SYNTAX_INVALID_SUBSTITUTION_SYNTAX_417=The provided value "%s" \
 could not be parsed as a substitution syntax because its OID %s corresponds \
MILD_ERR_ATTR_SYNTAX_INVALID_LDAP_SYNTAX_417=The provided value "%s" \
 could not be parsed as an ldap syntax because its OID %s corresponds \
 to an attribute syntax that is already implemented
MILD_ERR_SCHEMA_MODIFY_CANNOT_DECODE_LDAP_SYNTAX_418=An error occurred while \
 attempting to decode the ldapsyntax description "%s":  %s
opends/src/messages/messages/schema.properties
@@ -1009,3 +1009,9 @@
MILD_WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_PATTERN_309=The provided value \
  "%s" could not be parsed as a regex syntax because the provided regex \
 pattern "%s" is invalid
MILD_WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_INVALID_VALUE_310=The provided value \
  "%s" cannot be parsed because it is not allowed by enumeration syntax \
  with OID "%s"
MILD_WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_DUPLICATE_VALUE_311=The provided value \
  "%s" cannot be parsed as an enumeration syntax  because it contains a \
  duplicate value "%s" at position %d
opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -3318,7 +3318,7 @@
    // We allow only unimplemented syntaxes to be substituted.
    if(schema.getSyntax(oid) !=null)
    {
      Message message = ERR_ATTR_SYNTAX_INVALID_SUBSTITUTION_SYNTAX.get(
      Message message = ERR_ATTR_SYNTAX_INVALID_LDAP_SYNTAX.get(
              ldapSyntaxDesc.getDefinition(),oid);
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                                     message);
opends/src/server/org/opends/server/schema/LDAPSyntaxDescriptionSyntax.java
@@ -29,27 +29,35 @@
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.regex.Pattern;
import org.opends.server.admin.std.server.AttributeSyntaxCfg;
import org.opends.server.api.ApproximateMatchingRule;
import org.opends.server.api.AttributeSyntax;
import org.opends.server.api.EqualityMatchingRule;
import org.opends.server.api.OrderingMatchingRule;
import org.opends.server.api.SubstringMatchingRule;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.messages.Message;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.loggers.ErrorLogger.*;
import org.opends.server.types.*;
import static org.opends.messages.SchemaMessages.*;
import org.opends.messages.MessageBuilder;
import static org.opends.server.schema.SchemaConstants.*;
import static org.opends.server.util.StaticUtils.*;
import org.opends.server.api.AbstractMatchingRule;
import org.opends.server.api.OrderingMatchingRule;
import org.opends.server.config.ConfigException;
import org.opends.server.types.ByteSequence;
import org.opends.server.types.ByteString;
import org.opends.server.types.DirectoryException;
import static org.opends.server.schema.SchemaConstants.*;
import static org.opends.server.schema.StringPrepProfile.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.messages.SchemaMessages.*;
/**
 * This class defines the LDAP syntax description syntax, which is used to
@@ -499,7 +507,7 @@
    String description = descriptionBuffer.toString();
    StringBuilder extBuffer = new StringBuilder();
    LDAPSyntaxDescriptionSyntax syntax = null;
    char c = '\u0000';
    pos = readTokenName(valueStr, extBuffer, pos);
    String lowerTokenName = toLowerCase(extBuffer.toString());
@@ -522,8 +530,8 @@
    {
      StringBuilder regexBuffer = new StringBuilder();
      pos = readQuotedString(valueStr, regexBuffer, pos);
      String regex = regexBuffer.toString();
      if(regex == null)
      String regex = regexBuffer.toString().trim();
      if(regex.length() == 0)
      {
        Message message = WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_NO_PATTERN.get(
               valueStr);
@@ -544,6 +552,41 @@
                message);
      }
    }
    else if(lowerTokenName.equals("x-enum"))
    {
      // The next character must be the opening parenthesis
      if ((c = valueStr.charAt(pos++)) != '(')
      {
        Message message =
                ERR_ATTR_SYNTAX_ATTRSYNTAX_EXPECTED_OPEN_PARENTHESIS.get(
                        valueStr, pos, String.valueOf(c));
         throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                message);
      }
      LinkedList<ByteSequence> entries = new LinkedList<ByteSequence>();
      while(true)
      {
        if ((c=valueStr.charAt(pos)) == ')')
        {
          pos++;
          break;
        }
        StringBuilder buffer = new StringBuilder();
        pos = readQuotedString(valueStr, buffer, pos);
        ByteString entry = ByteString.valueOf(buffer.toString());
        if(entries.contains(entry))
        {
          Message message =
                WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_DUPLICATE_VALUE.get(
                        valueStr, entry.toString(),pos);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                message);
        }
        entries.add(entry);
      }
      syntax = new EnumSyntax(entries, description, oid);
    }
    else
    {
      Message message = WARN_ATTR_SYNTAX_LDAPSYNTAX_UNKNOWN_EXT.get(
@@ -552,16 +595,14 @@
              message);
    }
    char c = valueStr.charAt(pos);
    while ((pos < length) && (c == ' '))
    while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
    {
      pos++;
    }
    // The next character must be the closing parenthesis and there should not
    // be anything after it (except maybe some spaces).
    if ((c = valueStr.charAt(pos++)) != ')')
    if (pos >= length || (c = valueStr.charAt(pos++)) != ')')
    {
      Message message =
@@ -889,6 +930,13 @@
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                                         message);
          }
          //Clean up any space after this.
          while ((pos < valueStr.length()) &&
                  ((c = valueStr.charAt(pos)) == ' '))
          {
            pos++;
          }
          if(valueStr.charAt(pos) == ')')
              break;
      }
@@ -1232,4 +1280,374 @@
      return approximateMatchingRule;
    }
  }
  /**
   * This class provides an enumeration-based mechanism where a new syntax
   * and its corresponding matching rules can be created on-the-fly. An enum
   * syntax is an LDAPSyntaxDescriptionSyntax with X-PATTERN extension.
   */
  private static class EnumSyntax extends
          LDAPSyntaxDescriptionSyntax
  {
    //Set of read-only enum entries.
    LinkedList<ByteSequence> entries;
    // The description of this syntax.
    private String description;
    //The oid of this syntax.
    private String oid;
    //The equality matching rule.
    private EqualityMatchingRule equalityMatchingRule;
    //The substring matching rule.
    private SubstringMatchingRule substringMatchingRule;
    //The ordering matching rule.
    private OrderingMatchingRule orderingMatchingRule;
    //The approximate matching rule.
    private ApproximateMatchingRule approximateMatchingRule;
    //Creates a new instance of this syntax.
    private EnumSyntax(LinkedList<ByteSequence> entries,
            String description,
            String oid)
    {
      super();
      this.entries = entries;
      this.description = description;
      this.oid = oid;
    }
     /**
     * {@inheritDoc}
     */
     @Override
    public String getSyntaxName()
    {
      // There is no name for a enum syntax.
      return null;
    }
    /**
     * {@inheritDoc}
     */
     @Override
    public String getOID()
    {
      return oid;
    }
    /**
     * {@inheritDoc}
     */
     @Override
    public String getDescription()
    {
      return description;
    }
     /**
      * {@inheritDoc}
      */
    @Override
    public void finalizeSyntax()
    {
      DirectoryServer.deregisterMatchingRule(orderingMatchingRule);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean valueIsAcceptable(ByteSequence value,
                                     MessageBuilder invalidReason)
    {
      //The value is acceptable if it belongs to the set.
      boolean isAllowed = entries.contains(value);
      if(!isAllowed)
      {
        Message message = WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_INVALID_VALUE.get(
                value.toString(),oid);
        invalidReason.append(message);
      }
      return isAllowed;
    }
    /**
     * Retrieves the default equality matching rule that will be used for
     * attributes with this syntax.
     *
     * @return  The default equality matching rule that will be used for
     *          attributes with this syntax, or <CODE>null</CODE> if equality
     *          matches will not be allowed for this type by default.
     */
    @Override
    public EqualityMatchingRule getEqualityMatchingRule()
    {
      if(equalityMatchingRule == null)
      {
        //This has already been verified.
        equalityMatchingRule =
                DirectoryServer.getEqualityMatchingRule(EMR_CASE_IGNORE_OID);
      }
      return equalityMatchingRule;
    }
    /**
     * Retrieves the default ordering matching rule that will be used for
     * attributes with this syntax.
     *
     * @return  The default ordering matching rule that will be used for
     *          attributes with this syntax, or <CODE>null</CODE> if ordering
     *          matches will not be allowed for this type by default.
     */
    @Override
    public OrderingMatchingRule getOrderingMatchingRule()
    {
      if(orderingMatchingRule == null)
      {
        orderingMatchingRule = new EnumOrderingMatchingRule(this, oid);
        try
        {
          DirectoryServer.registerMatchingRule(orderingMatchingRule, false);
        }
        catch(DirectoryException de)
        {
          logError(de.getMessageObject());
        }
      }
      return orderingMatchingRule;
    }
    /**
     * Retrieves the default substring matching rule that will be used for
     * attributes with this syntax.
     *
     * @return  The default substring matching rule that will be used for
     *          attributes with this syntax, or <CODE>null</CODE> if substring
     *          matches will not be allowed for this type by default.
     */
    @Override
    public SubstringMatchingRule getSubstringMatchingRule()
    {
      if(substringMatchingRule == null)
      {
        substringMatchingRule =
                DirectoryServer.getSubstringMatchingRule(SMR_CASE_IGNORE_OID);
      }
      return substringMatchingRule;
    }
    /**
     * Retrieves the default approximate matching rule that will be used for
     * attributes with this syntax.
     *
     * @return  The default approximate matching rule that will be used for
     *          attributes with this syntax, or <CODE>null</CODE> if approximate
     *          matches will not be allowed for this type by default.
     */
    @Override
    public ApproximateMatchingRule getApproximateMatchingRule()
    {
      if(approximateMatchingRule == null)
      {
        approximateMatchingRule =
                DirectoryServer.getApproximateMatchingRule(
                                    AMR_DOUBLE_METAPHONE_OID);
      }
      return approximateMatchingRule;
    }
    //Returns the associated data structure containing the enum
    //values.
    private LinkedList<ByteSequence> getEnumValues()
    {
      return entries;
    }
    /**
      * Implementation of an Enum Ordering matching rule.
      */
    private final class EnumOrderingMatchingRule
       extends AbstractMatchingRule
       implements OrderingMatchingRule
    {
      //The enumeration syntax instance.
      private EnumSyntax syntax;
      //The oid of the matching rule.
      private String oid;
      //The name of the matching rule.
      private String name;
      static final long serialVersionUID = -2624642267131703408L;
      /**
       * Creates a new instance.
       */
      private EnumOrderingMatchingRule(EnumSyntax syntax,String oid)
      {
        super();
        this.syntax = syntax;
        this.oid = OMR_OID_GENERIC_ENUM + "." + oid;
        this.name = OMR_GENERIC_ENUM_NAME + oid;
      }
      /**
      * {@inheritDoc}
      */
      public int compare(byte[] arg0, byte[] arg1)
      {
        return compareValues(ByteString.wrap(arg0),ByteString.wrap(arg1));
      }
      /**
      * {@inheritDoc}
      */
      public int compareValues(ByteSequence value1, ByteSequence value2)
      {
        LinkedList<ByteSequence> enumValues = syntax.getEnumValues();
        return enumValues.indexOf(value1) - enumValues.indexOf(value2);
      }
      /**
       * {@inheritDoc}
       */
      @Override
      public String getName()
      {
        return name;
      }
       /**
       * {@inheritDoc}
       */
      @Override
      public Collection<String> getAllNames()
      {
        return Collections.singleton(getName());
      }
       /**
       * {@inheritDoc}
       */
      @Override
      public String getOID()
      {
        return oid;
      }
      /**
       * {@inheritDoc}
       */
      @Override
      public String getDescription()
      {
        return null;
      }
      /**
       * {@inheritDoc}
       */
      @Override
      public String getSyntaxOID()
      {
        return SYNTAX_DIRECTORY_STRING_OID;
      }
      /**
       * {@inheritDoc}
       */
      @Override
      public ByteString normalizeValue(ByteSequence value)
              throws DirectoryException
      {
        StringBuilder buffer = new StringBuilder();
        prepareUnicode(buffer, value, TRIM, CASE_FOLD);
        int bufferLength = buffer.length();
        if (bufferLength == 0)
        {
          if (value.length() > 0)
          {
            // This should only happen if the value is composed entirely
            // of spaces. In that case, the normalized value is a single space.
            return SINGLE_SPACE_VALUE;
          }
          else
          {
            // The value is empty, so it is already normalized.
            return ByteString.empty();
          }
        }
        // Replace any consecutive spaces with a single space.
        for (int pos = bufferLength-1; pos > 0; pos--)
        {
          if (buffer.charAt(pos) == ' ')
          {
            if (buffer.charAt(pos-1) == ' ')
            {
              buffer.delete(pos, pos+1);
            }
          }
        }
        return ByteString.valueOf(buffer.toString());
      }
    }
  }
}
opends/src/server/org/opends/server/schema/SchemaConstants.java
@@ -667,6 +667,20 @@
  /**
   * The name for the enumOrderingMatch ordering matching rule.
   */
  public static final String OMR_GENERIC_ENUM_NAME = "enumOrderingMatch";
  /**
   * The oid for the generic enum syntax ordering matching rule.
   */
  public static final String OMR_OID_GENERIC_ENUM="1.3.6.1.4.1.26027.1.4.8";
  /**
   * The name for the caseExactSubstringsMatch substring matching rule.
   */
  public static final String SMR_CASE_EXACT_NAME = "caseExactSubstringsMatch";
opends/src/server/org/opends/server/types/Schema.java
@@ -1041,6 +1041,7 @@
      {
        //Get rid of this from the virtual ldapsyntaxes.
        deregisterSyntax(syntax.getLdapSyntaxDescriptionSyntax());
        syntax.getLdapSyntaxDescriptionSyntax().finalizeSyntax();
      }
      catch (Exception e)
      {
opends/tests/unit-tests-testng/src/server/org/opends/server/schema/LDAPSyntaxTest.java
@@ -40,6 +40,7 @@
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.DN;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
@@ -361,6 +362,7 @@
    try
    {
      addRegexSyntax();
      TestCaseUtils.initializeTestBackend(true);
      //This addition should go through.
      TestCaseUtils.addEntry(
        "dn: cn=test,o=test",
@@ -403,6 +405,178 @@
    /**
    * Tests whether it is possible to add values after an enum syntax
    * has been added.
    *
    * @throws java.lang.Exception
    */
  @Test()
  public void testEnumSyntaxAddValues() throws Exception
  {
    try
    {
      addEnumSyntax();
      TestCaseUtils.initializeTestBackend(true);
      //This addition should fail because it doesn't match the pattern.
      Entry entry = TestCaseUtils.makeEntry(
      "dn: cn=syntax-test,o=test",
      "objectclass: person",
      "objectclass: testOC",
      "cn: syntax-test",
      "sn: xyz",
      "test-attr-enum: arbit-day");
      InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation = conn.processAdd(entry.getDN(),
                                     entry.getObjectClasses(),
                                     entry.getUserAttributes(),
                                     entry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(),
            ResultCode.INVALID_ATTRIBUTE_SYNTAX);
      //This addition should go through.
      TestCaseUtils.addEntry(
        "dn: cn=syntax-test,o=test",
        "objectclass: person",
        "objectclass: testOC",
        "cn: syntax-test",
        "sn: xyz",
        "test-attr-enum: sunday");
    }
    finally
    {
      deleteEnumSyntax();
    }
  }
  /**
   * Tests the equality-based search using enum syntax.
   *
   * @throws java.lang.Exception
   */
  @Test()
  public void testEnumSyntaxEqualitySearch() throws Exception
  {
    try
    {
      addEnumSyntax();
      //This addition should go through.
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: cn=test,o=test",
        "objectclass: person",
        "objectclass: testOC",
        "cn: test",
        "sn: xyz",
        "test-attr-enum: wednesday");
      InternalClientConnection conn =
      InternalClientConnection.getRootConnection();
      InternalSearchOperation searchOperation =
           new InternalSearchOperation(
                conn,
                InternalClientConnection.nextOperationID(),
                InternalClientConnection.nextMessageID(),
                null,
                ByteString.valueOf("cn=test,o=test"),
                SearchScope.WHOLE_SUBTREE,
                DereferencePolicy.NEVER_DEREF_ALIASES,
                Integer.MAX_VALUE,
                Integer.MAX_VALUE,
                false,
                LDAPFilter.decode("test-attr-enum=wednesday"),
                null, null);
      searchOperation.run();
      assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
      List<SearchResultEntry> entries = searchOperation.getSearchEntries();
      SearchResultEntry e = entries.get(0);
      //An entry must be returned.
      assertNotNull(e);
    }
    finally
    {
      deleteEnumSyntax();
    }
  }
  /**
   * Tests the ordering-based search using enum syntax.
   *
   * @throws java.lang.Exception
   */
  @Test()
  public void testEnumSyntaxOrderingSearch() throws Exception
  {
    try
    {
      addEnumSyntax();
      TestCaseUtils.initializeTestBackend(true);
      //This addition should go through.
      TestCaseUtils.addEntries(
        "dn: cn=test1,o=test",
        "objectclass: person",
        "objectclass: testOC",
        "cn: test1",
        "sn: xyz",
        "test-attr-enum: sunday",
        "",
        "dn: cn=test2,o=test",
        "objectclass: person",
        "objectclass: testOC",
        "cn: test2",
        "sn: xyz",
        "test-attr-enum: monday",
        "",
        "dn: cn=test3,o=test",
        "objectclass: person",
        "objectclass: testOC",
        "cn: test3",
        "sn: xyz",
        "test-attr-enum: tuesday");
      InternalClientConnection conn =
      InternalClientConnection.getRootConnection();
      InternalSearchOperation searchOperation =
           new InternalSearchOperation(
                conn,
                InternalClientConnection.nextOperationID(),
                InternalClientConnection.nextMessageID(),
                null,
                ByteString.valueOf("o=test"),
                SearchScope.WHOLE_SUBTREE,
                DereferencePolicy.NEVER_DEREF_ALIASES,
                Integer.MAX_VALUE,
                Integer.MAX_VALUE,
                false,
                LDAPFilter.decode("test-attr-enum>=tuesday"),
                null, null);
      searchOperation.run();
      assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
      List<SearchResultEntry> entries = searchOperation.getSearchEntries();
      SearchResultEntry e = entries.get(0);
      //An entry must be returned.
      assertNotNull(e);
      assertTrue(e.getDN().equals(DN.decode("cn=test1,o=test")));
    }
    finally
    {
      deleteEnumSyntax();
    }
  }
  //Parses the OID from the syntax defitions.
  private String getOIDFromLdapSyntax(String valueStr)
  {
@@ -515,4 +689,57 @@
    assertTrue(resultCode==0);
  }
     //Adds an enum syntax to the schema.
  private void addEnumSyntax() throws Exception
  {
    //Add the enum syntax.
    int resultCode = TestCaseUtils.applyModifications(true,
    "dn: cn=schema",
    "changetype: modify",
    "add: ldapsyntaxes",
    "ldapSyntaxes: ( 3.3.3  DESC 'Day Of The Week'  " +
            "X-ENUM  ( 'monday' 'tuesday'   'wednesday'  'thursday'  'friday'  'saturday' 'sunday') )");
    assertTrue(resultCode==0);
    resultCode = TestCaseUtils.applyModifications(true,
          "dn: cn=schema",
          "changetype: modify",
          "add: attributetypes",
          "attributetypes: ( test-attr-oid NAME 'test-attr-enum' SYNTAX 3.3.3 )",
          "-",
          "add: objectclasses",
          "objectclasses: ( oc-oid NAME 'testOC' SUP top AUXILIARY MUST test-attr-enum)"
        );
    assertTrue(resultCode == 0);
  }
  //Deletes the enum syntax from the schema.
  private void deleteEnumSyntax() throws Exception
  {
    int resultCode = TestCaseUtils.applyModifications(true,
      "dn: cn=schema",
      "changetype: modify",
      "delete: objectclasses",
      "objectclasses: ( oc-oid NAME 'testOC' SUP top AUXILIARY MUST test-attr-enum)",
      "-",
      "delete: attributetypes",
      "attributetypes: ( test-attr-oid NAME 'test-attr-enum' SYNTAX 3.3.3 )"
    );
    assertTrue(resultCode==0);
    resultCode = TestCaseUtils.applyModifications(true,
    "dn: cn=schema",
    "changetype: modify",
    "delete: ldapsyntaxes",
    "ldapSyntaxes: ( 3.3.3  DESC 'Day Of The Week'  " +
            "X-ENUM  ( 'monday' 'tuesday'   'wednesday'  'thursday'  'friday'  'saturday' 'sunday') )");
    assertTrue(resultCode==0);
  }
}