Inspired by features of Grails or the Spring Data JPA project, my plan was to learn more about CDI extensions by implementing a proof of concept on such a DAO framework based on CDI. CDI is part of Java EE 6 and, already by itself a powerful addition to the platform programming model, provides SPIs to extend it even further. A prominent sample is the Seam framework, which in its latest version contains a lot of those extensions. Just drop them in your classpath and they are ready for use. Impressive enough to learn more about the technology.
While I was getting my hands dirty it seemed to me the result is useful enough to share it here - and also of course to demonstrate the power and easiness of creating CDI extensions. In this article I’ll give you a quick start on CDI extensions as well as (hopefully) an idea on how a portable framework might look like based on this technology. All code presented here is available on GitHub.
The DAO Framework
Some common ingredients of a DAO framework are captured in the code snippet below:
@Dao
public interface SimpleDao extends EntityDao<Simple, Long> {
Simple findByNameAndEnabled(String name, Boolean enabled);
@Query(named=Simple.BY_NAME)
List<Simple> findByNamedQuery(String name);
@Query(named=Simple.BY_NAME)
List<Simple> findByNamedQueryRestricted(String name,
@MaxResults int max, @FirstResult int first);
@Query(named=Simple.BY_ID)
Simple findByNamedQueryNamedParams(
@QueryParam("id") Long id,
@QueryParam("enabled") Boolean enabled);
@Query("select s from Simple s where s.name = ?1")
Simple findByQueryString(String name);
}
Typically a DAO framework has a common interface concrete DAOs can extend from. Using generics here allows to have a standard set of methods like saving or retrieving all entities of a specific type, and can also be used during query generation. Usually this is nothing that cannot be done easily with an entity manager. But once you have injected a DAO in your service class - do you really want to inject the entity manager as well? In order to keep code leaner, a DAO base interface should provide this kind of methods and of course implement them all automagically - nothing you would like to rewrite again and again.
Some other features are shown in the method declarations above. Automatic query generation out of method names and parameters as GORM method expressions do, or creating queries based on annotation meta data and parameters will often allow just leaving those easy cases to the query generator and keep the code lean to focus on the complex ones.
The CDI Approach
One way to implement such a framework is over a CDI extension. CDI allows extensions to listen to various lifecycle events:
- Before CDI starts discovering beans.
- While it processes annotated types, injection targets, producers, beans and observers.
- And when it finishes with both discovery and validation.
As in our case we are dealing with plain interfaces, the easiest approach is to simply annotate the interface and then listen for annotation processing events. The sample above shows a Dao annotation on the interface, but this would be placed on the extended AbstractEntityDao interface so developers won’t have to worry about it.
So on the extension we listen for annotated types, check if it is our Dao annotation and register a proxy bean which implements the annotated type. Registering the extension a matter of two things:
1. Implementing the extension class and listen for the Dao annotation.
public class QueryExtension implements Extension {
<X> void processAnnotatedType(@Observes ProcessAnnotatedType<X> event, BeanManager beanManager) {
// all the required information on the type is found in the event
// the bean manager is used to register the proxy
}
}
2. Register the extension class as a service provider in the appropriate file (META-INF/services/javax.enterprise.inject.spi.Extension).
Registering the proxy with the bean manager is slightly more work, but luckily someone has already done that. If you work with CDI extensions, make sure to include Seam Solder - the Swiss army knife for CDI developers. Solder has built-in support for so called service handlers, where you annotate an abstract type with a reference to the handler class. The documentation use case looks probably kind of familiar ;-) All our extension will have to do is to override the handler lookup - and we’re done with registering the proxy! The reason we don't use the ServiceHandlerExtension directly is to separate the handler reference from the annotation, and that we can have a chance to e.g. validate and process further meta data in the extension class.
public class QueryExtension extends ServiceHandlerExtension {
@Override
protected <X> Class<?> getHandlerClass(ProcessAnnotatedType<X> event) {
if (event.getAnnotatedType().isAnnotationPresent(Dao.class)) {
return QueryHandler.class;
}
return null;
}
}
public class QueryHandler {
@Inject
private Instance<EntityManager> entityManager;
@AroundInvoke
public Object handle(InvocationContext ctx) throws Exception {
...
}
}
The handler class simply has to provide a public method annotated with @AroundInvoke, where you get all the required information to build up your query dynamically. Note that in the handler class you will also be able to use CDI services like injection.
As a framework user, all you will have to do is to drop the JAR in the classpath and annotate the interface. Look mom, no XML! Well almost, you still need an (empty) beans.xml somewhere to activate all the CDI magic.
Testing the Extension
Arquillian is a relatively new testing framework which allows you to create dynamic deployment units and run them in a container. A great feature for an extension as you can easily test it live in a unit test without leaving the IDE! This looks like the following:
@RunWith(Arquillian.class)
public class QueryHandlerTest {
@Deployment
public static Archive<?> deployment() {
return ShrinkWrap.create(WebArchive.class, "test.war")
.addClasses(QueryExtension.class)
.addAsWebInfResource("test-persistence.xml", ArchivePaths.create("classes/META-INF/persistence.xml"))
.addAsWebInfResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"))
.addAsWebInfResource("glassfish-resources.xml")
.addClasses(SimpleDao.class)
.addPackage(Simple.class.getPackage());
}
@Inject
private SimpleDao dao;
@Produces
@PersistenceContext
private EntityManager entityManager;
@Test
public void shouldCreateQueryByMethodName() {
// given
final String name = "testCreateQueryByMethodName";
createSimple(name);
// when
Simple result = dao.findByNameAndEnabled(name, Boolean.TRUE);
// then
Assert.assertNotNull(result);
Assert.assertEquals(name, result.getName());
}
}
This creates a stripped down web archive deployment, in this sample for an embedded GlassFish. The test class itself is registered as a bean in the test and can therefore use injections (CDI or container injections, see the @PersistenceContext). Resources like persistence units can be added on demand as we see in the deployment method (persistence.xml for the persistence unit, glassfish-resources.xml contains a data source definition).
The container gets started and we can see our injected proxy in action. In the sample above we then create some test data and see if our generated query fetches the right data back.
Conclusion
Of course this article is a very simplified version of the whole setup - especially the Arquillian setup required some trial and error as the framework is still in Alpha (if anybody finds out how to create the data source without deploying the glassfish-resources.xml into a web archive - let me know).
Check out the project source on GitHub to get started. The module structure might look a little complicate but it follows common Seam module structure separating API (in our case the client annotations) from implementation (the extension code). Documentation is still "basic" but looking at the unit tests might give an indication on the usage.
Once the project setup is done, things get extremely productive though. Seam Solder provides a rich tool set you can use to create an extension, Arquillian lets you immediately test your code inside a container. The result is an easy to reuse and easy to distribute framework you will probably get back to in many of your following Java EE 6 projects.