directory-api mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Emmanuel L├ęcharny <elecha...@gmail.com>
Subject Immutable SyntexCheckers
Date Fri, 17 Mar 2017 10:00:44 GMT
Hi guys,


last week-end, I worked on adding a way to get a static instance of
SyntaxCheckers, instead of having to create a new instance every now and
then. As most of those SC aren't going to be modified during the
lifetime of an application, it makes a lot fo sense to have it
constructed once, and only once.


First let me summarize their lifecycle.


SC (SyntaxChecker) are associated with a LDAP Syntax, and are helper
classes used to check that a value is correct, accordingly to the
AttributeType syntax. We have more or less a hundred of such objects.
Most of them are defined in the code base, and will never change, but a
few can take a parameter, like teh TelephoneNumber that can use a regexp
as a matcher.

We wanted to be able to 'inject' some new SC dynamiclaly in the server,
by using a Java class that implements a new checker (this actually
works, but it's not really use as of today). The whole idea was to make
it dead simple - sort of - to extend the Schema, adding application
driven Schema elements. Bottom line, the mechanism is in place.


Now, when do we use those SC ? When we need to check a value. In this
case, we know about the value' AttributeType, and this AT knwos about
its Syntax, which is associated with a SC. The instance are created when
we load the schema at startup, as we store the FQCN of each SC in the
Schema :


dn: m-oid=1.3.6.1.4.1.1466.115.121.1.7,ou=syntaxCheckers,cn=system,ou=schema
m-oid: 1.3.6.1.4.1.1466.115.121.1.7
m-fqcn:
org.apache.directory.api.ldap.model.schema.syntaxCheckers.BooleanSyntaxChecker
objectclass: metaSyntaxChecker
objectclass: metaTop
objectclass: top
creatorsname: uid=admin,ou=system

The relation between a Ldap Syntax and its associated SC is the OID :
they have the same. So when we load the LdapSyntax, we class load its
associated SC. As a matter of fact, LdapSyntax are just descriptive,
when SC are executable.


That being said, there is not much of a reason to have the SC being
mutable. Even for the very few ones that can use a configuration - like
a regexp -, once created, they don't change.


So I changed the existing SC code by adding a static INSTANCE, which is
a reference to the instanciated SC. You don't need anymore to create a
new instance when you need it, you just pull the INSTANCE.


That caused some issue in some tests, that were creating new instances
of a SC, but with a different OID. Using INSTANCE was breaking the
tests, because those tests were expecting a different OID. Actually, the
test was faulty, we should have created a totally new SC instead of
redefining and existing one, but well, that life and it raised a
concern. And this concern was even more important in the tests using teh
TelephoneNumber SC, because you can inject new regexp into it...

So we fixed the tests, and now, it's time to think about a better
solution, where the SC are completely immutable.


The idea is to use internal Builders :


public class BooleanSyntaxChecker extends SyntaxChecker
{

    /**
     * A static instance of BooleanSyntaxChecker
     */
    public static final BooleanSyntaxChecker INSTANCE = new
BooleanSyntaxChecker( SchemaConstants.BOOLEAN_SYNTAX );

    /** A static instance of the builder */
    private static final Builder BUILDER_INSTANCE = new Builder();

    /**
     * A static Builder for this class
     */
    public static class Builder
    {
        /** The BooleanSyntaxChecker OID */
        private String oid = SchemaConstants.BOOLEAN_SYNTAX;
       
        /**
         * The Builder constructor
         */
        private Builder()
        {
            // Nothing to do
        }
       
       
        /**
         * Set the SyntaxChecker's OID
         *
         * @param oid The OID
         * @return The Builder's Instance
         */
        public Builder setOid( String oid )
        {
            this.oid = oid;
           
            return this;
        }
       
       
        /**
         * Create a new instance of BooleanSyntaxChecker
         * @return A new instance of BooleanSyntaxChecker
         */
        public BooleanSyntaxChecker build()
        {
            return new BooleanSyntaxChecker( oid );
        }
    }
   
    /**
     * Creates a new instance of BooleanSyntaxChecker.
     */
    private BooleanSyntaxChecker( String oid )
    {
        super( oid );
    }

   
    /**
     * @return An instance of the Builder for this class
     */
    public static Builder builder()
    {
        return BUILDER_INSTANCE;
    }

The constructor is now private, so you can't instanciate the class. What
you have to do is either to get the static INSTANCE, and you get an
immutable and static version of the class, or you 'build' a completely
new instance using code like :


    BooleanSyntaxChecker checker = BooleanSyntaxChecker.builder().build();

(which is actually the same as the static INSTANCE, but as a different
object), or :

    BooleanSyntaxChecker checker =
BooleanSyntaxChecker.builder().setOid( "1.2.3.4" ).build();

if you want a completely different SC, with a different OID? that does
not collide with the existing statically instanciated SC.


The only requirement is that each SC has its own builder (we can't put
this code in the abstract SC, because it's abstract...)


For the RegexpSyntaxChecker, this gives :


/**
 * A SyntaxChecker implemented using Perl5 regular expressions to constrain
 * values.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory
Project</a>
 */
@SuppressWarnings("serial")
public class RegexSyntaxChecker extends SyntaxChecker
{
    /** A logger for this class */
    private static final Logger LOG = LoggerFactory.getLogger(
RegexSyntaxChecker.class );

    /** the set of regular expressions */
    private String[] expressions;
   
    /** A static instance of the builder */
    private static final Builder BUILDER_INSTANCE = new Builder();
   
    /**
     * A static Builder for this class
     */
    public static class Builder
    {
        /** The RegexSyntaxChecker OID, which is undefined by default */
        private String oid;
       
        /** the set of regular expressions */
        private String[] expressions;
       
        /**
         * The Builder constructor
         */
        private Builder()
        {
            // Nothing to do
        }
       
       
        /**
         * Set the SyntaxChecker's OID
         *
         * @param oid The OID
         * @return The Builder's Instance
         */
        public Builder setOid( String oid )
        {
            this.oid = oid;
           
            return this;
        }


        /**
         * Add a list of regexp to be applied by this SyntaxChecker
         *
         * @param expressions The regexp list to add
         */
        public Builder setExpressions( String[] expressions )
        {
            if ( ( expressions != null ) && ( expressions.length > 0 ) )
            {
                this.expressions = new String[expressions.length];
                System.arraycopy( expressions, 0, this.expressions, 0,
expressions.length );
            }
           
            return this;
        }
       
       
        /**
         * Create a new instance of RegexSyntaxChecker
         * @return A new instance of RegexSyntaxChecker
         */
        public RegexSyntaxChecker build()
        {
            return new RegexSyntaxChecker( oid, expressions );
        }
    }

   
    /**
     * Creates a Syntax validator for a specific Syntax using Perl5 matching
     * rules for validation.
     *
     * @param oid the oid of the Syntax values checked
     * @param matchExprArray the array of matching expressions
     */
    private RegexSyntaxChecker( String oid, String[] matchExprArray )
    {
        super( oid );

        this.expressions = matchExprArray;
    }

   
    /**
     * @return An instance of the Builder for this class
     */
    public static Builder builder()
    {
        return BUILDER_INSTANCE;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isValidSyntax( Object value )
    {
        String str;

        if ( value instanceof String )
        {
            str = ( String ) value;

            for ( String regexp : expressions )
            {
                if ( !str.matches( regexp ) )
                {
                    LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID,
value ) );
                   
                    return false;
                }
            }
        }

        LOG.debug( I18n.msg( I18n.MSG_04489_SYNTAX_VALID, value ) );

        return true;
    }


    /**
     * Get the list of regexp stored into this SyntaxChecker
     *
     * @return AN array containing all the stored regexp
     */
    public String[] getExpressions()
    {
        if ( expressions == null )
        {
            return null;
        }
       
        String[] exprs = new String[expressions.length];
        System.arraycopy( expressions, 0, exprs, 0, expressions.length );
       
        return exprs;
    }
}


and using it looks like :



    RegexpSyntaxChecker checker = RegexpSyntaxChecker.builder().setOid(
"1.2.3.4" ).setExpressions( "*.*", "\\.*", "\\n" ).build();


Thoughts ?


-- 
Emmanuel Lecharny

Symas.com
directory.apache.org


Mime
View raw message