Configuration‎ > ‎

Properties Manager

Once Seam is up and running you are not restricted to using JBoss's system properties to configure your application (this was described here). Instead we want a flexible way of accessing configuration within your application. We want to be able to access our properties from within EL and also have them injected into our Seam components. We also want to be able to get our configuration from multiple sources and have a way of overriding properties so we can do things like define a default property and then have a user specific property be used (and override) the default one if it is specified.

To start this we need a Seam component that we can look up to get our properties from. This component will not actually retrieve any properties it will merely define the public api that we can use to look up properties. All calls will actually delegate out to other classes to do the lookup.

/**
 * Copyright Software Factory - 2010
 */
package nz.co.softwarefactory.esafensound.configuration;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;

/**
 * Class that is able to look up the user config in a semi-generic way and if no configuration option is present will
 * read a value from the default properties table. Not entirely happy with the way the userconfig is retrieved.
 *
 * @author craig
 */
@SuppressWarnings("serial")
@Name("propertyManager")
@Install(false)
@Scope(ScopeType.SESSION)
public class PropertyManager implements Serializable {

    private List<PropertyResolver> propertyResolvers = new LinkedList<PropertyResolver>();

    @Create
    public void init() {
        // sanity check to ensure we have at least one PropertyResolver
        if (propertyResolvers == null || propertyResolvers.isEmpty()) {
            propertyResolvers = new LinkedList<PropertyResolver>();
            propertyResolvers.add(getDefaultPropertyResolver());
        }
    }

    public Integer getAsInt(final String name) {
        return Integer.parseInt(getAsString(name));
    }

    public String getAsString(final String name) {
        return get(name).toString();
    }

    public Object get(final Object name) {
        Object property = null;
        for (final PropertyResolver propertyResolver : propertyResolvers) {
            property = propertyResolver.getProperty(name);
            if (property != null) {
                break;
            }
        }

        return property;
    }

    public List<PropertyResolver> getPropertyResolvers() {
        return propertyResolvers;
    }

    public void setPropertyResolvers(final List<PropertyResolver> propertyResolvers) {
        this.propertyResolvers = propertyResolvers;
    }

    private PropertyResolver getDefaultPropertyResolver() {
        return new DefaultPropertyResolver();
    }
}

As you can see we delegate our calls out to underlying implementations of the PropertyResolver interface.

/**
 * Copyright Software Factory - 2011
 */
package nz.co.softwarefactory.esafensound.configuration;

import java.io.Serializable;

/**
 * @author craig
 */
public interface PropertyResolver extends Serializable {

    void init();

    /**
     * The actual property lookup.
     *
     * @param key
     *            the key to look up
     * @return the value corresponding to the key or null if no value is found
     */
    Object getProperty(final Object key);

    void destroy();
}

Note: the init and destroy methods are not really needed on this interface.

I have provided a number of default implementations but as you can imagine you could get properties from anywhere (for example LDAP or XML)

The following are the implementations I have written.

/**
 * Copyright Software Factory - 2010
 */
package nz.co.softwarefactory.esafensound.configuration;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * We need a table to store our default values for properties. This object is a simple key/value pair which deals with
 * this.
 *
 * @author craig
 */
@SuppressWarnings("serial")
@Entity
@Table(name = "default_property")
public class DefaultProperty implements Serializable {

    @SuppressWarnings("unused")
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private String value;

    public String getName() {
        return name;
    }

    public void setName(final String name) {
        this.name = name;
    }

    public String getValue() {
        return value;
    }

    public void setValue(final String value) {
        this.value = value;
    }
}


/**
 * Copyright Software Factory - 2011
 */
package nz.co.softwarefactory.esafensound.configuration;

import java.util.HashMap;
import java.util.Map;

import org.jboss.seam.annotations.Create;
import org.jboss.seam.core.Expressions;
import org.jboss.seam.core.Expressions.ValueExpression;
import org.jboss.seam.el.EL;
import org.jboss.seam.el.SeamELResolver;
import org.jboss.seam.util.AnnotatedBeanProperty;

/**
 * An {@link PropertyResolver} is responsible for looking up a datasource for the given key. Datasources can really be
 * anything - Java Properties files, XML files, Database tables, LDAP etc etc. An implementation is free to cache
 * results of lookups, load all properties in the init method or do a lookup for each request.
 *
 * @author craig
 */
@SuppressWarnings("serial")
public class UserPropertyResolver implements PropertyResolver {

    private AnnotatedBeanProperty<UserConfiguration> userConfiguration;

    /**
     * Class which contains the @UserConfiguration annotation.
     */
    private Class<?> configurationWrapperClass;

    private ValueExpression<String> expression;

    /**
     * @see nz.co.softwarefactory.esafensound.configuration.PropertyResolver#init()
     */
    @Override
    @Create
    public void init() {
        userConfiguration = new AnnotatedBeanProperty<UserConfiguration>(configurationWrapperClass,
                UserConfiguration.class);
    }

    /**
     * @see nz.co.softwarefactory.esafensound.configuration.PropertyResolver#getProperty(java.lang.Object)
     */
    @Override
    public Object getProperty(final Object key) {
        return getUserConfiguration().get(key);
    }

    @SuppressWarnings("unchecked")
    Map<String, String> getUserConfiguration() {
        final Object configurationWrapper = getConfigurationWrapper();
        if (configurationWrapper == null) {
            return new HashMap<String, String>();
        }
        return (Map<String, String>) userConfiguration.getValue(configurationWrapper);
    }

    /**
     * @return
     */
    private Object getConfigurationWrapper() {
        if (expression == null) {
            return null;
        }
        return expression.getValue();
    }

    /**
     * @see nz.co.softwarefactory.esafensound.configuration.PropertyResolver#destroy()
     */
    @Override
    public void destroy() {
        // do nothing
    }

    public Class<?> getConfigurationWrapperClass() {
        return configurationWrapperClass;
    }

    public void setConfigurationWrapperClass(final Class<?> configurationWrapperClass) {
        this.configurationWrapperClass = configurationWrapperClass;
    }

    public ValueExpression<String> getExpression() {
        return expression;
    }

    public void setExpression(ValueExpression<String> expression) {
        this.expression = expression;
    }
}



/**
 * Copyright Software Factory - 2011
 */
package nz.co.softwarefactory.esafensound.configuration;

import java.util.List;

import javax.persistence.EntityManager;

import org.jboss.seam.Component;
import org.jboss.seam.annotations.Create;

/**
 * @author craig
 */
@SuppressWarnings("serial")
public class DefaultPropertyResolver implements PropertyResolver {

    private List<String> keys;

    /**
     * @see nz.co.softwarefactory.esafensound.configuration.PropertyResolver#init()
     */
    @SuppressWarnings("unchecked")
    @Override
    @Create
    public void init() {
        keys = getEntityManager().createQuery("SELECT p.name FROM DefaultProperty p").getResultList();
    }

    /**
     * @param name
     * @return
     * @see DefaultProperty
     */
    public Object getProperty(final Object name) {
        try {
            if (keys.contains(name)) {
                return ((DefaultProperty) getEntityManager().createQuery(
                        "FROM DefaultProperty dp WHERE dp.name = :name").setParameter("name", name).getSingleResult())
                        .getValue();
            }
        }
        catch (final Exception e) {
            // TODO log this???
        }
        return null;
    }

    /**
     * @see nz.co.softwarefactory.esafensound.configuration.PropertyResolver#destroy()
     */
    @Override
    public void destroy() {
    }

    private EntityManager getEntityManager() {
        return (EntityManager) Component.getInstance("unrestrictedEntityManager", true);
    }
}


/**
 * Copyright Software Factory - 2010
 */
package nz.co.softwarefactory.esafensound.configuration;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * We need a table to store our default values for properties. This object is a simple key/value pair which deals with
 * this.
 *
 * @author craig
 */
@SuppressWarnings("serial")
@Entity
@Table(name = "default_property")
public class DefaultProperty implements Serializable {

    @SuppressWarnings("unused")
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private String value;

    public String getName() {
        return name;
    }

    public void setName(final String name) {
        this.name = name;
    }

    public String getValue() {
        return value;
    }

    public void setValue(final String value) {
        this.value = value;
    }
}

Ok thats all the code to get our PropertyManager component up and running. Now we just need to configure it in our components.xml

To do this I added the following package-info.java to the package where the above classes live.

/**
 * Copyright Software Factory - 2011
 */
@Namespace(value = "http://esafensound.com/configuration")
package nz.co.softwarefactory.esafensound.configuration;

import org.jboss.seam.annotations.Namespace;

This allows me to define the namespace xmlns:configuration="http://esafensound.com/configuration" in components.xml

 <configuration:property-manager>
        <configuration:property-resolvers>
            <value>#{userPropertyResolver}</value>
            <value>#{organizationPropertyResolver}</value>
            <value>#{systemPropertyResolver}</value>
            <value>#{defaultPropertyResolver}</value>
        </configuration:property-resolvers>
    </configuration:property-manager>

    <configuration:user-property-resolver name="userPropertyResolver" configuration-wrapper-class="nz.co.softwarefactory.esafensound.model.User" expression="#{currentUserNeverNull}" />

    <configuration:user-property-resolver name="organizationPropertyResolver" configuration-wrapper-class="nz.co.softwarefactory.esafensound.model.organization.Organization" expression="#{credentials.organization}" />

    <configuration:default-property-resolver name="defaultPropertyResolver" />

    <configuration:jboss-properties-file-resolver name="systemPropertyResolver" />

Ok so basically the above just defines the PropertyResolvers we want to use then wires them all into our PropertyManager.

Now the Property Manager should be working and accessible like this:

propertyManager.get('myproperty');

This is not as convenient as it could be. Next we will define an interceptor to allow us to inject properties directly into Seam components.

Property Inteceptor

First we define the annotation we will use to mark our fields in our components. This will work just like the @In annotation.

/**
 * Copyright Software Factory - 2010
 */
package nz.co.softwarefactory.esafensound.configuration;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * Used to signify that a field is a user property and should be injected using the {@link PropertyInterceptor}.
 * Analogous to @In
 *
 * @author craig
 */
@Target( { FIELD })
@Documented
@Retention(RUNTIME)
@Inherited
public @interface Property {

    String name() default "";
}

Next we need to mark our Seam components for interception. The interceptor could also be defined in your components.xml to be used as a default interceptor on all components. I chose to just annotate the classes I wanted to use the interceptor on.

/**
 * Copyright Software Factory - 2010
 */
package nz.co.softwarefactory.esafensound.configuration;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.jboss.seam.annotations.intercept.Interceptors;

/**
 * Seam requires that the @Interceptors annotation be used inside another annotation so here it is. Mark your Seam
 * component for this and the {@link PropertyInterceptor} will scan you class injecting any fields that are marked with
 *
 * @Property. <i>Note: I had wanted to do away with this annotation and make the Property Interceptor a default
 *            interceptor but this will do for now.</i>
 * @author craig
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Interceptors(PropertyInterceptor.class)
public @interface ManagedProperties {
}

Finally we create the actual interceptor class.

/**
 * Copyright Software Factory - 2010
 */
package nz.co.softwarefactory.esafensound.configuration;

import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.List;

import org.jboss.seam.Component;
import org.jboss.seam.annotations.intercept.AroundInvoke;
import org.jboss.seam.annotations.intercept.Interceptor;
import org.jboss.seam.core.BijectionInterceptor;
import org.jboss.seam.intercept.AbstractInterceptor;
import org.jboss.seam.intercept.InvocationContext;

/**
 * Seam Interceptor for injecting user properties. This works in a similar way to the {@link BijectionInterceptor}
 *
 * @author craig
 */
@SuppressWarnings("serial")
@Interceptor(stateless = true)
public class PropertyInterceptor extends AbstractInterceptor {

    /*
     * (non-Javadoc)
     * @see org.jboss.seam.intercept.OptimizedInterceptor#aroundInvoke(org.jboss. seam.intercept.InvocationContext)
     */
    @Override
    @AroundInvoke
    public Object aroundInvoke(final InvocationContext invocationContext) throws Exception {
        for (final Field field : getPropertyFields()) {
            final Object target = invocationContext.getTarget();
            if (field.get(target) == null) {
                field.setAccessible(true);
                field.set(target, getPropertyManager().get(getUserPropertyName(field)));
            }
        }

        return invocationContext.proceed();
    }

    /*
     * (non-Javadoc)
     * @see org.jboss.seam.intercept.OptimizedInterceptor#isInterceptorEnabled()
     */
    @Override
    public boolean isInterceptorEnabled() {
        return !getPropertyFields().isEmpty();
    }

    private String getUserPropertyName(final Field field) {
        final Property p = field.getAnnotation(Property.class);

        if (p.name() == null || p.name().isEmpty()) {
            return field.getName();
        }
        return p.name();
    }

    private List<Field> getPropertyFields() {
        Class<?> clazz = getComponent().getBeanClass();
        final List<Field> fields = new LinkedList<Field>();
        for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
            for (final Field field : clazz.getDeclaredFields()) {
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                if (field.isAnnotationPresent(Property.class)) {
                    fields.add(field);
                }
            }
        }

        return fields;
    }

    private PropertyManager getPropertyManager() {
        final PropertyManager instance = (PropertyManager) Component.getInstance("propertyManager", true);
        if (instance == null) {
            throw new IllegalStateException("Property Manager not installed. Please configure in components.xml");
        }
        return instance;
    }

}

Now we can do this:

@ManagedProperties
@Name("mycomponent")
public class MyComponent {

    //This will look for a property "myproperty" and inject it if found.
    @Property
    private String myproperty;

    //This will look for a property "my_property" and inject that.
    @Property(value="my_property")
    private String anotherProperty;

}

This is cool but we might also want to access our properties in EL

EL Resolver

/**
 * Copyright Software Factory - 2011
 */
package nz.co.softwarefactory.esafensound.configuration;

import javax.el.ELContext;

import org.jboss.seam.Component;
import org.jboss.seam.el.SeamELResolver;

/**
 * Extends the {@link SeamELResolver}. Will attempt to look up the {@link PropertyManager} component to resolve the
 * expression from that if {@link SeamELResolver#getValue(ELContext, Object, Object)} returns null.
 * <p>
 * To make use of this EL Resolver you need to add it to your <code>faces-config.xml</code>.
 * <p/>
 *
 * <pre>
 * {@code
 * <application>
 *     <el-resolver>nz.co.softwarefactory.esafensound.configuration.external.PropertyELResolver</el-resolver>
 * </application>}
 * </pre>
 *
 * @author craig
 */
public class PropertyELResolver extends SeamELResolver {

    private static ThreadLocal<String> recursionDetection = new ThreadLocal<String>();

    /**
     * @see org.jboss.seam.el.SeamELResolver#getValue(javax.el.ELContext, java.lang.Object, java.lang.Object)
     */
    @Override
    public Object getValue(final ELContext context, final Object base, final Object property) {
        Object value = super.getValue(context, base, property);
        // only search for matching properties if the call to super did not find anything and if base is null
        // If base is not null then we are looking for a field on a bean so theres no point checking properties for a
        // match
        if (value == null && base == null && property != null) {
            if (recursionDetection.get() == null) {
                recursionDetection.set(property.toString());
                try {
                    value = getEnvironment().get(property.toString());
                    if (value != null) {
                        context.setPropertyResolved(true);
                    }
                }
                finally {
                    recursionDetection.set(null);
                }
            }
            else {
                System.out.println("Oops check your config as you have some recursion problems");
            }
        }

        return value;
    }

    /**
     * @return
     */
    private PropertyManager getEnvironment() {
        return (PropertyManager) Component.getInstance(PropertyManager.class, true);
    }
}

To use the above EL Resolver we need to define it in our faces-config.xml

<application>
     <el-resolver>
        nz.co.softwarefactory.esafensound.configuration.PropertyELResolver
    </el-resolver>

</application>

Now anywhere you can put EL (components.xml, xhtml etc) you can now look up properties

#{myproperty}





Comments