Posts Tagged ‘generics’

A GenericFactory with Spring Expressions

Friday, January 29th, 2010

Spring 3.0 is out for a while now and they introduced one of the features I missed when I left the .Net world: Spring Expressions. Spring.Net has them for a few years now (I would guess 4 or so..) and the reason why I missed them is that they are extremely powerful. You can evaluate almost anything with them. For more info and a good introduction, check out this link:

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html

So what do I mean by a ‘GenericFactory’? A GenericFactory is a factory that you can configure to your needs. How much have you been writing the same code over and over again? Given a certain object, if that rule applies, return that object, but if that rule applies, return the other object,… It probably sounds familiar. Well, the GenericFactory will solve that problem. The code is written once and we configure our rule with Spring Expressions and return Spring beans. Let’s see how it works:

package com.idevelop.util;

import java.util.List;

/**
 * Generic factory implementation that returns the required object
 * using spring expressions.
 *
 * @author Steven Willems
 */
public class GenericFactory<INPUT, OUTPUT> {

    private List<GenericFactoryRule<OUTPUT>> factoryConditions;

    /**
     * Get the appropriate object depending on the input object.
     *
     * @param input The object used for evaluating which object to return.
     * @return The appropriate object or null if no expression matched.
     */
    public OUTPUT getObject(INPUT input) {
        for (GenericFactoryRule<OUTPUT> factoryCondition : factoryConditions) {
            if (factoryCondition.evaluate(input)) {
                return factoryCondition.getBean();
            }
        }
        return null;
    }

    public void setFactoryConditions(List<GenericFactoryRule<OUTPUT>> factoryConditions) {
        this.factoryConditions = factoryConditions;
    }
}

The GenericFactory has a list of GenericFactoryRule objects that we will evaluate against the input object. The first rule that evaluates to true will return the linked Spring bean. If no rule evaluates to true, null is returned. I used a List here with dedicated rule objects to make sure the order of configuration is respected when I start evaluating the rules. That would not have been the case if I used a Hashtable with a String and Bean as key-value pair. And it’s better code to let the rule evaluate itself. A drawback as we’ll see in a minute is that the spring config file is a bit heavier. But that’s something I can live with.
Let’s take a look at the GenericFactoryRule class:

package com.idevelop.util;

import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

/**
 * A rule that will be evaluated in the {@link com.idevelop.util.GenericFactory} and
 * will return an instance if it evaluates to true.
 *
 * @author Steven Willems
 */
public class GenericFactoryRule<T> {

    private String rule;

    private T bean;

    public boolean evaluate(Object input) {
        SpelExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = new StandardEvaluationContext(input);
        Expression expression = parser.parseExpression(rule);
        return expression.getValue(context, Boolean.class);
    }

    public T getBean() {
        return bean;
    }

    public void setRule(String rule) {
        this.rule = rule;
    }

    public void setBean(T bean) {
        this.bean = bean;
    }
}

Now, let’s take a look at how we use it and how the Spring config file looks like:

package com.idevelop.util;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.expression.EvaluationException;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.*;

/**
 * Test class for {@link com.idevelop.util.GenericFactory}.
 *
 * @author Steven Willems
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TestGenericFactory {

    @Resource(name = "goodFactory")
    private GenericFactory<TestGenericFactory, TestObject> goodFactory;

    @Resource(name = "badFactory")
    private GenericFactory<TestGenericFactory, TestObject> badFactory;

    private int testValue;

    @Test
    public void testExpectNull() {
        testValue = 0;
        TestObject object = goodFactory.getObject(this);
        assertNull(object);
    }

    @Test
    public void testEvaluateFirstExpression() {
        testValue = 1;
        TestObject object = goodFactory.getObject(this);
        assertNotNull(object);
        assertEquals("1", object.getField());
    }

    @Test
    public void testEvaluateLastExpression() {
        testValue = 4;
        TestObject object = goodFactory.getObject(this);
        assertNotNull(object);
        assertEquals("4", object.getField());
    }

    @Test(expected = EvaluationException.class)
    public void testBadExpression() {
        testValue = -1;
        TestObject object = goodFactory.getObject(this);
        assertNull(object);

        badFactory.getObject(this);
    }

    public int getTestValue() {
        return testValue;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="goodFactory" class="com.idevelop.util.GenericFactory">
        <property name="factoryConditions">
            <list>
                <bean class="com.idevelop.util.GenericFactoryRule">
                    <property name="rule" value="testValue == 1"/>
                    <property name="bean" ref="instance1"/>
                </bean>
                <bean class="com.idevelop.util.GenericFactoryRule">
                    <property name="rule" value="testValue == 2"/>
                    <property name="bean" ref="instance2"/>
                </bean>
                <bean class="com.idevelop.util.GenericFactoryRule">
                    <property name="rule" value="testValue == 3"/>
                    <property name="bean" ref="instance3"/>
                </bean>
                <bean class="com.idevelop.util.GenericFactoryRule">
                    <property name="rule" value="testValue == 4"/>
                    <property name="bean" ref="instance4"/>
                </bean>
            </list>
        </property>
    </bean>

    <bean id="badFactory" class="com.idevelop.util.GenericFactory">
        <property name="factoryConditions">
            <list>
                <bean class="com.idevelop.util.GenericFactoryRule">
                    <property name="rule" value="testValue == 1"/>
                    <property name="bean" ref="instance1"/>
                </bean>
                <bean class="com.idevelop.util.GenericFactoryRule">
                    <property name="rule" value="testValue == 2"/>
                    <property name="bean" ref="instance2"/>
                </bean>
                <bean class="com.idevelop.util.GenericFactoryRule">
                    <!-- this expression will throw an EvaluationException -->
                    <property name="rule" value="testValue * 2"/>
                    <property name="bean" ref="instance3"/>
                </bean>
                <bean class="com.idevelop.util.GenericFactoryRule">
                    <property name="rule" value="testValue == 4"/>
                    <property name="bean" ref="instance4"/>
                </bean>
            </list>
        </property>
    </bean>

    <bean id="instance1" class="com.idevelop.util.TestObject">
        <constructor-arg value="1"/>
    </bean>

    <bean id="instance2" class="com.idevelop.util.TestObject">
        <constructor-arg value="2"/>
    </bean>

    <bean id="instance3" class="com.idevelop.util.TestObject">
        <constructor-arg value="3"/>
    </bean>

    <bean id="instance4" class="com.idevelop.util.TestObject">
        <constructor-arg value="4"/>
    </bean>

</beans>

And the stupid test class I used..

package com.idevelop.util;

/**
 * @author Steven Willems
 */
public class TestObject {
    private String field;

    public TestObject(String s) {
        field = s;
    }

    public String getField() {
        return field;
    }
}

And that’s all !

Java Generics

Wednesday, January 13th, 2010

Why doesn’t the following compile ? AND, how do you solve it ?

public interface GenericDao<T, PK extends Serializable> {

    T save(final T instance);

    T find(final PK id);

    void delete(final T persistentObject);
}
public interface GenericHibernateDao<T, PK extends Serializable> extends GenericDao<T, PK> {

    Query createNamedQuery(String namedQuery);
}
public class GenericHibernateDaoImpl<T, PK extends Serializable> extends HibernateDaoSupport implements GenericHibernateDao<T, PK> {

    public T save(final T instance) {
        getHibernateTemplate().saveOrUpdate(instance);
        return instance;
    }

    @SuppressWarnings({"unchecked"})
    public T find(final PK id) {
        return getHibernateTemplate().get(getInstanceType(), id);
    }

    public void delete(final T persistentObject) {
        getHibernateTemplate().delete(persistentObject);
    }

    public final Query createNamedQuery(final String namedQuery) {
        return getSession().getNamedQuery(namedQuery);
    }

    @SuppressWarnings({"unchecked"})
    private Class<T> getInstanceType() {
        return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }
}
public interface UserDao<T extends User> extends GenericHibernateDao<T, String> {
}
public class UserDaoImpl extends GenericHibernateDaoImpl<UserImpl, String> implements UserDao<UserImpl> {
}

Then the following line results in an compile error: incompatible types, found java.lang.Object, required: User

User fromDb = dao.find("some@email.com");