Sunday, January 17, 2010

Bookmark and Share

In his excellent book "Effective Java (2nd edition)" Joshua Bloch describes a variation of the Builder design pattern for instantiating objects with multiple optional attributes.

Sticking to this pattern frees you from providing multiple constructors with the different optional attributes as parameters (hard to maintain and hard to read for clients) or providing setter methods for the optional attributes (require objects to be mutable, can leave objects in inconsistent state).

As Bloch points out, it's a very good idea to check any invariants applying to the object to be created within the builder's build() method. That way it is ensured, that clients can only retrieve valid object instances from the builder.

If you are using the Bean Validation API (JSR 303) to define constraints for your object model, this can be realized by validating these constraints within the build() method.

The following listing shows an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class Customer {

    private long id;
    private String firstName;
    private String lastName;
    private Date birthday;

    private Customer(Builder builder) {

        this.id = builder.id;
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.birthday = builder.birthday;
    }

    public static class Builder {

        private static Validator validator = 
            Validation.buildDefaultValidatorFactory().getValidator();

        private long id;
        private String firstName;
        private String lastName;
        private Date birthday;

        public Builder(long id, String lastName) {
            this.id = id;
            this.lastName = lastName;
        }

        public Builder firstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder birthday(Date birthday) {
            this.birthday = birthday;
            return this;
        }

        public Customer build() throws ConstraintViolationException {

            Customer customer = new Customer(this);
            Set<ConstraintViolation<Customer>> violations = 
                validator.validate(customer);

            if (!violations.isEmpty()) {
                throw new ConstraintViolationException(
                    new HashSet<ConstraintViolation<?>>(violations));
            }

            return customer;
        }
    }

    @Min(1)
    public long getId() {
        return id;
    }

    @Size(min = 3, max = 80)
    public String getFirstName() {
        return firstName;
    }

    @Size(min = 3, max = 80)
    @NotNull
    public String getLastName() {
        return lastName;
    }

    @Past
    public Date getBirthday() {
        return birthday;
    }

}

The listing shows an exemplary model class Customer for which some invariants apply (e.g. a customer's last name must not be null and must be between 3 and 80 characters long). These invariants are expressed using constraint annotations from the Bean Validation API at the getter methods of the Customer class.

The inner class Builder is in charge of creating Customer instances. All mandatory fields – either primitive (e.g. id) or annotated with @NotNull (e.g. lastName) – are part of the builder's constructor. For all optional fields setter methods on the builder are provided.

Within the build() method the newly created Customer instance is validated using the Validator#validate() method. If any constraint violations occur, a ConstraintViolationException is thrown. That way it's impossible to retrieve an invalid Customer instance. The following unit test shows an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CustomerTest {

    @Test
    public void validCustomer() {
        Customer c = 
            new Customer.Builder(1, "Smith")
                .firstName("Bob")
                .birthday(new GregorianCalendar(1970, 3, 10).getTime())
                .build();

        assertNotNull(c);
    }

    @Test
    public void lastNameNullAndBirthdayInFuture() {
        try {
            new Customer.Builder(1, null)
                .birthday(new GregorianCalendar(2020, 3, 10).getTime())
                .build();
            fail("Expected ConstraintViolationException wasn't thrown.");
        }
        catch (ConstraintViolationException e) {
            assertEquals(2, e.getConstraintViolations().size());
        }
    }
}

If there are multiple classes for which you want to provide a builder in the described way, it is useful to extract the validation routine into a base class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class AbstractBuilder<T> {

    private static Validator validator = 
        Validation.buildDefaultValidatorFactory().getValidator();

    protected abstract T buildInternal();

    public T build() throws ConstraintViolationException {

        T object = buildInternal();

        Set<ConstraintViolation<T>> violations = validator.validate(object);
        if (!violations.isEmpty()) {
            throw new ConstraintViolationException(
                new HashSet<ConstraintViolation<?>>(violations));
        }

        return object;
    }
}

Concrete builder classes have to extend AbstractBuilder and must implement the buildInternal() method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
...
public static class Builder extends AbstractBuilder<Customer> {

    private long id;
    private String firstName;
    private String lastName;
    private Date birthday;

    public Builder(long id, String lastName) {
        this.id = id;
        this.lastName = lastName;
    }

    public Builder firstName(String firstName) {
        this.firstName = firstName;
        return this;
    }

    public Builder birthday(Date birthday) {
        this.birthday = birthday;
        return this;
    }

    @Override
    protected Customer buildInternal() {
        return new Customer(this);
    }
}
...

The complete source code for this post can be found in my Git repository over at github.com.

Wednesday, January 6, 2010

Bookmark and Share

Version 2 of the Java Persistence API (JPA) comes with an out-of-the-box integration of the Bean Validation API (BV). If a BV implementation is found in the runtime environment, any BV constraints placed at JPA entities will be validated whenever an entity is written to the database. In the following I'm going to show how the both APIs play together.

Project setup

Assuming you are working with Apache Maven, begin by adding the following repositories to your settings.xml or the pom.xml of your project:

1
2
3
4
5
6
7
8
9
10
...
<repository>
    <id>JBoss Repo</id>
    <url>http://repository.jboss.com/maven2</url>
</repository>
<repository>
    <id>EclipseLink Repo</id>
    <url>http://www.eclipse.org/downloads/download.php?r=1&nf=1&file=/rt/eclipselink/maven.repo</url>
</repository>   
...

In order to use the reference implementations of both APIs (EclipseLink resp. Hibernate Validator) add the following dependencies to you pom.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
...
<!-- JPA API and RI -->
<dependency>
    <groupId>javax.persistence</groupId>
    <artifactId>javax.persistence</artifactId>
    <version>2.0-SNAPSHOT</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>eclipselink</artifactId>
    <version>2.0.0</version>
    <scope>runtime</scope>
</dependency>
...
<!-- Bean Validation API and RI -->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.0.0.GA</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>4.0.2.GA</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.5.6</version>
    <scope>runtime</scope>
</dependency>
...

Instead of EclipseLink you might also use Hibernate 3.5 (currently beta) as JPA provider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
<dependency>
    <groupId>org.hibernate.java-persistence</groupId>
    <artifactId>jpa-api</artifactId>
    <version>2.0-cr-1</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>3.5.0-Beta-2</version>
    <scope>runtime</scope>
</dependency>
...

Finally we need a database to execute some unit tests against it. I recommend using Apache Derby as it provides an in-memory mode, which is perfectly suited for unit tests:

1
2
3
4
5
6
7
8
...
<dependency>
    <groupId>org.apache.derby</groupId>
    <artifactId>derby</artifactId>
    <version>10.5.3.0_1</version>
    <scope>test</scope>
</dependency>
...

The model class

Now let's create a simple model class. It's just a POJO with some annotations stemming from JPA (@Entity, @Id etc.) and some from Bean Validation (@NotNull, @Size):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Entity
@NamedQueries( { @NamedQuery(name = Customer.FIND_ALL_CUSTOMERS, query = "SELECT c FROM Customer c") })
public class Customer {

    public final static String FIND_ALL_CUSTOMERS = "findAllCustomers";

    @Id
    @GeneratedValue
    @NotNull
    private Long id;

    @NotNull
    @Size(min = 3, max = 80)
    private String name;

    private boolean archived;

    public Customer() {

    }

    public Customer(String name) {
        this.name = name;
        archived = false;
    }

    //getters and setters ...

}

Configuration

Next we have to setup the file persistence.xml which defines the persistence unit to be used for our module tests:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<persistence 
    xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
    version="2.0">

    <persistence-unit name="testPU" transaction-type="RESOURCE_LOCAL">

        <class>de.gmorling.jpa2.model.Customer</class>

        <properties>
            <property name="eclipselink.target-database" value="DERBY" />
            <property name="eclipselink.ddl-generation" value="create-tables" />
            <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" />
            <property name="javax.persistence.jdbc.url" value="jdbc:derby:memory:testDB;create=true" />
            <property name="javax.persistence.jdbc.user" value="APP" />
            <property name="javax.persistence.jdbc.password" value="APP" />
        </properties>
    </persistence-unit>
</persistence>

JPA 2 standardizes the property names for driver, user etc. When working with EclipseLink therefore only two provider-specific properties for the target database and the DDL strategy are required.

Using Hibernate the properties would look like the following (at the time of writing this post the standard properties don't seem to be supported yet by Hibernate):

1
2
3
4
5
6
7
8
...
<property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />
<property name="hibernate.hbm2ddl.auto" value="create" />
<property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="hibernate.connection.username" value="APP" />
<property name="hibernate.connection.password" value="APP" />
<property name="hibernate.connection.url" value="jdbc:derby:memory:testDB;create=true" />
...

Note that not a single line of configuration is required to enable the Bean Validation integration, it's working out of the box. If JPA detects a BV implementation on the classpath, it will use it by default to validate any BV constraints, whenever an entity is persisted or updated.

And action ...

To try that, let's write a simple unit test for our model class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class CustomerTest {

    private static EntityManagerFactory emf;

    private EntityManager em;

    @BeforeClass
    public static void createEntityManagerFactory() {
        emf = Persistence.createEntityManagerFactory("testPU");
    }

    @AfterClass
    public static void closeEntityManagerFactory() {
        emf.close();
    }

    @Before
    public void beginTransaction() {
        em = emf.createEntityManager();
        em.getTransaction().begin();
    }

    @After
    public void rollbackTransaction() {

        if (em.getTransaction().isActive())
            em.getTransaction().rollback();

        if (em.isOpen())
            em.close();
    }

    @Test
    public void nameTooShort() {

        try {
            Customer customer = new Customer("Bo");
            em.persist(customer);
            fail("Expected ConstraintViolationException wasn't thrown.");
        } 
        catch (ConstraintViolationException e) {
            assertEquals(1, e.getConstraintViolations().size());
            ConstraintViolation<?> violation = 
                e.getConstraintViolations().iterator().next();

            assertEquals("name", violation.getPropertyPath().toString());
            assertEquals(
                Size.class, 
                violation.getConstraintDescriptor().getAnnotation().annotationType());
        }
    }

    @Test
    public void validCustomer() {

        Customer customer = new Customer("Bob");
        em.persist(customer);

        Query query = em.createNamedQuery(Customer.FIND_ALL_CUSTOMERS);

        assertEquals(1, query.getResultList().size());
    }
}

The first four methods are just for setting up resp. closing down an entity manager factory for our persistence unit as well as the entity manager to be used.

In the test method nameTooShort() a ConstraintViolationException is raised, as the @Size constraint of the "name" attribute isn't fulfilled. The method validCustomer() on the other hand shows how a valid Customer instance is persisted and successfully retrieved later on.

Specifying validation groups

By default the constraint annotations of the Default validation group are validated upon inserting a new entity. The same holds true when an existing entity is updated, while no validation takes place by default if an entity is deleted.

If needed this behavior can be overridden by specifying the following properties in the persistence.xml (where values must be comma separated lists of fully-qualified class names of the groups to be validated):

1
2
3
javax.persistence.validation.group.pre-persist
javax.persistence.validation.group.pre-update
javax.persistence.validation.group.pre-remove

Let's assume for example that when a customer account is closed the concerned record from the Customer table is moved to some kind of archive storage. For all migrated records the "archived" flag should be set to true. Using a constraint annotation we would like to ensure now, that only archived Customer records are allowed to be deleted.

To do so we first define a new validation group by declaring an empty interface:

1
public interface DeletionAttributes {}

Now we add an @AssertTrue constraint to the "archived" attribute, which is part of this validation group:

1
2
3
4
5
6
7
public class Customer {

    ...
    @AssertTrue(groups = DeletionAttributes.class)
    private boolean archived;
    ...
}

Then we specify that constraints of the DeletionAttributes group shall be evaluated upon entity removal:

1
2
3
...
<property name="javax.persistence.validation.group.pre-remove" value="de.gmorling.jpa2.model.DeletionAttributes" />
...

As an unit test shows, a ConstraintViolationException will be raised now, if we try to delete a non-archived customer:

1
2
3
4
5
6
7
8
...
@Test(expected = ConstraintViolationException.class)
public void nonArchivedCustomerDeleted() {

    Customer customer = new Customer("Bob");
    em.persist(customer);
    em.remove(customer);
}...

But everything will be fine, if an archived customer gets deleted:

1
2
3
4
5
6
7
8
9
10
11
12
13
...
@Test
public void archivedCustomerDeleted() {

    Customer customer = new Customer("Bob");
    em.persist(customer);

    customer.setArchived(true);
    em.remove(customer);
    assertTrue(
        em.createNamedQuery(Customer.FIND_ALL_CUSTOMERS).getResultList().isEmpty());
}
...

Specifying the validation groups to be evaluated allows a very fine-grained configuration of the constraint checking process. But there might also be situations where you don't want any validation to take place at all. In that case the validation can be turned off completely by setting the validation mode to "NONE" within your persistence.xml:

1
2
3
4
5
6
...
<persistence-unit name="testPU" transaction-type="RESOURCE_LOCAL">
    <validation-mode>NONE</validation-mode> 
    ...
</persistence-unit>
...

Conclusion

As this post shows, JPA and BV API are integrated very well. Validating your entities before they are written to the database is a very powerful tool for improving data quality, as it ensures that all your persisted data adheres to the constraints of your object model.

I really recommend you to try it out yourself and of course I would be very happy on any feedback.

Sunday, January 3, 2010

Bookmark and Share

2009 was my first complete year of blogging, so I thought it might be the right time to present some statistics of the last 12 months:

  • 17,738 visits total
  • 25,394 page views total
  • best month: March (1,958 visits)
  • best day: March, 13 th (335 visits due to the Adam Bien effect ;-))

These were the most visited posts:

The visitors came from 118 different countries, the top 5 being:

  • United States: 26.18%
  • Germany: 14.95%
  • United Kingdom: 7.00%
  • France 4.71%
  • India: 3.00%

Thank you all for reading and commenting. Stay tuned :-)

Thursday, December 31, 2009

Bookmark and Share

Jetty is widely appreciated as low-footprint and lightning-fast web container. That makes it also a perfect fit to be used as development runtime, where short turnaround times are an absolut must.

I used to work with the Jetty Maven plugin, which supports hot-deploying applications after any code changes while Jetty is running. This works pretty well, but comes at the cost that your session data is lost during re-deployment and depending on your application's complexity it also can take some seconds to get it up running again.

So I tried another option: running Jetty in embedded mode. For that purpose Jetty provides a comprehensive API, which we can use to write a simple main class that starts up a Jetty instance serving the application we are currently developing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class JettyRunner {

    public static void main(String[] args) throws Exception {

        Server server = new Server();

        Connector connector = new SelectChannelConnector();
        connector.setPort(8080);
        connector.setHost("127.0.0.1");
        server.addConnector(connector);

        WebAppContext wac = new WebAppContext();
        wac.setContextPath("/myapp");
        wac.setBaseResource(
            new ResourceCollection(
                new String[] {"./src/main/webapp"}));

        server.setHandler(wac);
        server.setStopAtShutdown(true);
        server.start();
        server.join();
    }
}

The code is pretty much straight forward: first a connector with port and hostname is configured, then a context for the application is created and registered with the server, which finally is started up.

When we launch this class in debugging mode from within our IDE, code changes are immediately applied to the running application without the need to re-deploy or restart it due to Java's hot code replacement mechanism.

Furthermore all changes to the files under "src/main/webapp" are instantly visible, as the files are served directly from there.

The reduced number of required restarts/redeployments greatly improves development productivity. Basically restarting is only required when modifying deployment descriptors (such as web.xml) or when class signatures are modified (e.g. by adding new methods).

Running JSF applications

This works like a charm for basic servlets, but things are getting a bit more complicated, when JSF comes into play.

The reason is that JSF searches for all classes carrying any of the JSF annotations (such as @ManagedBean) when starting up. This scan takes place in the directory "META-INF/classes". As this folder doesn't exist in our case, no single managed bean will be detected.

How can this problem be solved? First of all the output folder of the application must be added to the WebAppContext's resource collection. In case of a Maven-based project we have to add the "target" directory:

1
2
3
4
5
...
wac.setBaseResource(
    new ResourceCollection(
        new String[] { "./src/main/webapp", "./target" }));
...

But the problem remains that JSF searches class files in "WEB-INF/classes", although in our case they are located under "classes".

We could modify the destination of class files (by setting Maven's property ${project.build.outputDirectory}, but I wouldn't really recommend this. Diverging from Maven's conventions makes your project harder to understand for other developers and as rumors go there still are Maven plugins around that directly access "target/classes" instead of referencing said property.

Resource aliases

I studied Jetty's API for a while and came across the concept of resource aliases which provides a neat solution to our problem. To define an alias for "WEB-INF/classes" just do the following:

1
2
3
4
...
WebAppContext wac = new WebAppContext();
wac.setResourceAlias("/WEB-INF/classes/", "/classes/");
...

Unfortunately this only works for single resources. While everything is fine when the folder "WEB-INF/classes" itself is accessed, the alias won't be applied when accessing resources contained within this folder, e.g. "WEB-INF/classes/MyManagedBean.class".

To tackle this problem I extended Jetty's WebAppContext to provide a means of pattern based alias resolution. The only overridden method is getResourceAlias():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class AliasEnhancedWebAppContext extends WebAppContext {

    @Override
    public String getResourceAlias(String alias) {

        @SuppressWarnings("unchecked")
        Map<String, String> resourceAliases = 
            (Map<String, String>) getResourceAliases();

        if (resourceAliases == null) {
            return null;
        }

        for (Entry<String, String> oneAlias : 
            resourceAliases.entrySet()) {

            if (alias.startsWith(oneAlias.getKey())) {
                return alias.replace(
                    oneAlias.getKey(), oneAlias.getValue());
            }
        }

        return null;
    }
}

Whenever an alias for a given resource is looked up, it is checked whether the resource name starts with one of the registered aliases. If that's the case, the aliased part will be replaced. Having registered the alias from the previous listing, e.g. "WEB-INF/classes/MyManagedBean.class" will be aliased with "classes/MyManagedBean.class".

It's certainly not perfect (e.g. nested replacements are not considered), but I would regard it good enough for being used in the scenario at hand. So let's look how all this fits together:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class JettyRunner {

    public static void main(String[] args) throws Exception {

        Server server = new Server();

        Connector connector = new SelectChannelConnector();
        connector.setPort(8080);
        connector.setHost("127.0.0.1");
        server.addConnector(connector);

        WebAppContext wac = new AliasEnhancedWebAppContext();
        wac.setContextPath("/myapp");
        wac.setBaseResource(
            new ResourceCollection(
                new String[] {"./src/main/webapp", "./target"}));
        wac.setResourceAlias("/WEB-INF/classes/", "/classes/");

        server.setHandler(wac);
        server.setStopAtShutdown(true);
        server.start();
        server.join();
    }
}

That way we are finally able to run a JSF application on an embedded Jetty instance, providing us with a very efficient way of development.

EL libraries

Unfortunately my happiness was short lasting as I ran into another problem: Using method parameters within EL expressions gave me parsing errors wrapped into a javax.faces.view.facelets.TagAttributeException.

The reason is that method invocations with parameters are a feature of Unified EL version 2.2 which is leveraged by JSF 2. As others already pointed out, you need to replace the EL JARs (API and impl) of you web container with the new ones.

Using Maven this can be done by adding the following dependencies to your project's pom.xml (if required, add the java.net Maven repository to your settings.xml first):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>el-api</artifactId>
    <version>2.2</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.glassfish.web</groupId>
    <artifactId>el-impl</artifactId>
    <version>2.2</version>
    <scope>runtime</scope>
</dependency>
...

When working with the JSF 2 reference implementation Mojarra furthermore the expression factory to be used must be set to the one from the new EL implementation by specifying the following context parameter within your web.xml:

1
2
3
4
5
6
...
<context-param>
  <param-name>com.sun.faces.expressionFactory</param-name>
  <param-value>com.sun.el.ExpressionFactoryImpl</param-value>
</context-param>
...

Having done that, you are now able to use parametrized method invocations within EL expressions, which is especially useful when performing actions on the rows of a data table.

Saturday, November 21, 2009

Bookmark and Share

In a recent post I presented a generic DTO implementation which allows a type-safe way of accessing its contents.

The presented approach reduces the efforts of writing a lot of dedicated DTOs but comes at the cost that it is not obvious, which attributes are allowed to be put into or can be retrieved from a given DTO instance. Therefore methods using such a generic DTO as parameter or return value define a rather loose contract.

In the following I'm going to show a way to solve this problem. The basic idea is to introduce the concept of "attribute groups". Each attribute belongs to such a group, and each GenericDTO instance is created for exactly one attribute group.

Let's first define a marker interface AttributeGroup from which all concrete attribute groups will derive:

1
2
3
public interface AttributeGroup {

}

The Attribute class doesn't differ much from the version introduced in the first post. It's just parametrized with the type of group to which a given attribute belongs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Attribute<G extends AttributeGroup, T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;

    public Attribute(String name) {

        if (name == null) {
            throw new IllegalArgumentException();
        }

        this.name = name;
    }

    public static <F extends AttributeGroup, S> Attribute<F, S> getInstance(
            String name) {
        return new Attribute<F, S>(name);
    }

    @Override
    public String toString() {
        return name;
    }

    // equals(), hashCode() ...
}

As in the previous post Attribute instances are declared as constants on interfaces, e.g. PersonAttributes. But now each of these interfaces extends AttributeGroup, while the Attribute instances are parametrized with the attribute group:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface PersonAttributes extends AttributeGroup {

    public static final Attribute<PersonAttributes, String> FIRST_NAME = 
        Attribute.getInstance("FIRST_NAME");

    public static final Attribute<PersonAttributes, Integer> AGE = 
        Attribute.getInstance("AGE");
}

...

public class OrderAttributes implements AttributeGroup {

    public static final Attribute<OrderAttributes, Long> 
        TOTAL_PRICE = Attribute.getInstance("TOTAL_PRICE");
}

Also the GenericDTO class now has a type parameter specifying for which attribute group a given instance is declared:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class GenericDTO<G extends AttributeGroup> implements Serializable {

    private static final long serialVersionUID = 1L;

    private Class<G> attributeGroup;

    private Map<Attribute<G, ?>, Object> attributes = new LinkedHashMap<Attribute<G, ?>, Object>();

    private GenericDTO(Class<G> attributeGroup) {
        this.attributeGroup = attributeGroup;
    }

    public static <B extends AttributeGroup> GenericDTO<B> getInstance(
            Class<B> clazz) {
        return new GenericDTO<B>(clazz);
    }

    public <T> GenericDTO<G> set(Attribute<G, T> identifier, T value) {

        attributes.put(identifier, value);

        return this;
    }

    public <T> T get(Attribute<G, T> identifier) {

        @SuppressWarnings("unchecked")
        T theValue = (T) attributes.get(identifier);

        return theValue;
    }

    public <T> T remove(Attribute<G, T> identifier) {

        @SuppressWarnings("unchecked")
        T theValue = (T) attributes.remove(identifier);

        return theValue;
    }

    public void clear() {
        attributes.clear();
    }

    public int size() {
        return attributes.size();
    }

    public Set<Attribute<G, ?>> getAttributes() {
        return attributes.keySet();
    }

    public boolean contains(Attribute<G, ?> identifier) {

        return attributes.containsKey(identifier);
    }

    @Override
    public String toString() {
        return attributeGroup.getSimpleName() + " [" + attributes + "]";
    }

    // equals(), hashCode() ...
}

That way it is now clear at compile time which attributes are supported by a given GenericDTO instance.

When for instance having a GenericDTO instance at hand, only attributes of the PersonAttributes group can be put into this DTO or can be retrieved from it. Trying to insert attributes of another group will yield in a compiler error:

1
2
3
4
5
6
7
8
9
...
GenericDTO dto = GenericDTO.getInstance(PersonAttributes.class);

//that's fine
dto.set(PersonAttributes.FIRST_NAME, "Bob").set(PersonAttributes.AGE, 28);

//compiler error
dto.set(OrderAttributes.TOTAL_PRICE, 9990);
...

By introducing the group concept Attribute declaration is a bit more verbose than in the first version, but real compile-time safety and more explicit contracts should be worth the effort, which is still way below than having to implement dedicated DTOs with getters and setters for each attribute as well as proper equals()-, hashCode()- and toString()-Methods.

What do you think – is a generic DTO implemented that way a good idea? As always I'm looking forward to receiving your feedback.