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:d...@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