In this tutorial we will show how to upgrade our Java EE 6 EJB based application into a CDI based application using Eclipse and Eclipse Maven plugin.
Contexts and Dependency Injection(CDI) is a new addition to the Java EE specification as of Java EE 6. It provides several benefits that were missing to Java EE developers, such as allowing any JavaBean to be used as a JSF managed bean, including stateless and stateful session beans.
CDI elementary unit is still the Bean. Compared with EJBs, CDI features a different, more flexible kind of Bean: one of the most important differences between the two approaches is that CDI Beans are contextual that s they live in a well defined scope.
In this tutorial we will create a CDI based application using Eclipse and Maven Eclipse plugin.
Start by creating a new Maven Project from the File Menu:
Now move to the Archetype selection screen and enter the maven-archetype-webapp which will produce a standard Java EE Web application project which is suited as well for applications containing EJBs and CDI beans:
Next choose as Artifact Id “javaee6example” which will be our Project name and enter a Package & Group Id for your project:
Ok. Now you should have in your project explorer a basic skeleton of Java EE project:
javaee6example ¦ pom.xml ¦ +---src ¦ +---main ¦ +---java ¦ ¦ ¦ +---resources ¦ ¦ ¦ +---webapp ¦ ¦ ¦ +---WEB-INF ¦ faces-config.xml ¦ web.xml ¦ +---target
The first thing to settle is Maven pom.xml which will contain the required dependencies and the plugins which are necessary to deploy the application on JBoss AS 7.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mastertheboss</groupId> <artifactId>javaee6example</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>Java EE 6 webapp project</name> <description>A starter Java EE 6 webapp project for use on JBoss AS 7, generated from the jboss-javaee6-webapp archetype</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <jboss.home>C:\jboss-as-7.1.1.Final</jboss.home> <maven.build.timestamp.format>yyyyMMdd'T'HHmmss</maven.build.timestamp.format> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.spec</groupId> <artifactId>jboss-javaee-web-6.0</artifactId> <version>2.0.0.Final</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>javax.enterprise</groupId> <artifactId>cdi-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.spec.javax.annotation</groupId> <artifactId>jboss-annotations-api_1.1_spec</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.spec.javax.ws.rs</groupId> <artifactId>jboss-jaxrs-api_1.1_spec</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.0-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.spec.javax.ejb</groupId> <artifactId>jboss-ejb-api_3.1_spec</artifactId> <scope>provided</scope> </dependency> </dependencies> <build> <finalName>${project.artifactId}</finalName> <plugins> <!-- Compiler plugin enforces Java 1.6 compatibility and activates annotation processors --> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.1</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>2.1.1</version> <configuration> <!-- Java EE 6 doesn't require web.xml, Maven needs to catch up! --> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <plugin> <groupId>org.jboss.as.plugins</groupId> <artifactId>jboss-as-maven-plugin</artifactId> <version>7.1.1.Final</version> </plugin> </plugins> </build> <profiles> <profile> <!-- The default profile skips all tests, though you can tune it to run just unit tests based on a custom pattern --> <!-- Seperate profiles are provided for running all tests, including Arquillian tests that execute in the specified container --> <id>default</id> <activation> <activeByDefault>true</activeByDefault> </activation> <build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.4.3</version> <configuration> <skip>true</skip> </configuration> </plugin> </plugins> </build> </profile> </profiles> </project>
Please notice that this pom.xml does not contain dependency versions for the single libraries. The dependency version is resolved using a BOM (Bill of materials) which specifies the exact version for the Java EE stack. See this tutorial for more info about it.
Now we will start adding classes. We will add the following classes:
- SimpleProperty: an Entity Bean for storing our Properties
- Manager: a CDI Bean which acts as a glue between the JSF view and the EJBs
- ServiceBean: a Stateless EJB which will carry on transactional jobs (Inserts, Deletes)
- GenericProducer: a CDI Producer Bean used to instantiate container resources (The Entity Manager)
- Producer: a CDI Producer Bean used as a factory of SimpleProperty for the JSF view
- RepositoryManager: a CDI Bean used to perform queries and populate SimpleProperty objects
So start by adding the class com.mastertheboss.model.SimpleProperty class:
@Entity public class SimpleProperty { @Id @Column(name="id") private String key; private String value; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
Now it’s the turn of the com.mastertheboss.bean.Manager class:
@Model public class Manager { @Inject ServiceBean ejb; @Produces @Named SimpleProperty property; @Inject Producer producer; @PostConstruct public void initNewProperty() { property = new SimpleProperty(); } public void save() { ejb.put(property); initNewProperty(); } public void clear(SimpleProperty property) { ejb.delete(property); } }
Key Point
Notice this class contains a SimpleProperty instance with a @Produces @Named annotation. This means that this field will be used as a factory for an instance of the SimpleProperty, which will be eventually exposed (via @Named annotation) to the JSF EL. The benefit of using this approach is that you won’t need creating boilerplate code for the single fields of SimpleProperty that need
Here’s the turn of the com.mastertheboss.ejb.ServiceBean EJB which will perform some transactional activities such as storing new SimpleProperty and deleting them as well from the DB:
@Stateless public class ServiceBean { @Inject private Event<SimpleProperty> propEventSrc; @Inject private EntityManager em; public void put(SimpleProperty p){ em.persist(p); propEventSrc.fire(p); } public void delete(SimpleProperty p){ Query query = em.createQuery("delete FROM com.sample.model.SimpleProperty p where p.key='"+p.getKey()+"'"); query.executeUpdate(); propEventSrc.fire(p); } }
In this example we use a com.mastertheboss.producer.GenericProducer class to instantiate some typical container resources such as the EntityManager, which can now be safely injected into your beans via the @Inject annotation:
public class GenericProducer { @SuppressWarnings("unused") @Produces @PersistenceContext private EntityManager em; }
The other Producer class namely com.mastertheboss.producer.Producer will be used to Produce an ArrayList of @Named SimpleProperty which can be therefore be exposed to the JSF EL:
@RequestScoped public class Producer { @Inject RepositoryManager db; private List<SimpleProperty> propertyList; public void onMemberListChanged(@Observes(notifyObserver = Reception.IF_EXISTS) final SimpleProperty member) { retrieveAllSeatsOrderedByName(); } @Produces @Named public List<SimpleProperty> getPropertyList() { return propertyList; } public void setProperty(List<SimpleProperty> property) { this.propertyList = propertyList; } @PostConstruct public void retrieveAllSeatsOrderedByName() { propertyList = db.queryCache(); } }
Key Point
Another feature of this Producer class is the Observer pattern. Observers, just like the name suggests, can be used to observe objects. An observer method is notified whenever an object is created, removed, or updated. In our example, it allows refreshing the list of Seats whenever they are needed.
To be precise, in our example, we are using a conditional Observer, which is denoted by the expression notifyObserver = Reception.IF_EXISTS. This means in practice that the observer method is only called if an instance of the component already exists. If not specified, the default option (ALWAYS) will be that the observer method is always called (If an instance doesn’t exist, one will be created).
Finally, the last class we will add is the com.mastertheboss.repository.RepositoryManager class which will use the EntityManager instance to perform a basic query on the SimpleProperty table:
public class RepositoryManager { @Inject private EntityManager em; public List<SimpleProperty> queryCache(){ Query query = em.createQuery("FROM com.mastertheboss.model.SimpleProperty"); List <SimpleProperty> list = query.getResultList(); return list; } }
Handling Persistence
In order to be able to use JPA, you need to add a persistence.xml file into the src\main\resources\META-INF folder of your project:
<persistence version="2.0" 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"> <persistence-unit name="primary"> <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source> <properties> <!-- Properties for Hibernate --> <property name="hibernate.hbm2ddl.auto" value="create-drop" /> <property name="hibernate.show_sql" value="false" /> </properties> </persistence-unit> </persistence>
In this example we’re using the default H2 datasource which is bound into the JNDI java:jboss/datasources/ExampleDS
Now about the JSF view, we will add an index.xhtml page that will capture the SimpleProperty fields in the upper panel and display them in a data table in the lower panel:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:c="http://java.sun.com/jsp/jstl/core"> <h:head> <style type="text/css"> <!-- styles not included for brevity --> </style> </h:head> <h:body> <h2>JSF 2 example on JBoss 7</h2> <h:form id="jsfexample"> <h:panelGrid columns="2" styleClass="default"> <h:outputText value="Enter key:" /> <h:inputText value="#{property.key}" /> <h:outputText value="Enter value:" /> <h:inputText value="#{property.value}" /> <h:commandButton actionListener="#{manager.save}" styleClass="buttons" value="Save key/value" /> <h:messages /> </h:panelGrid> <h:dataTable value="#{propertyList}" var="item" styleClass="table" headerClass="table-header" rowClasses="table-odd-row,table-even-row"> <h:column> <f:facet name="header">Key</f:facet> <h:outputText value="#{item.key}" /> </h:column> <h:column> <f:facet name="header">Value</f:facet> <h:outputText value="#{item.value}" /> </h:column> <h:column> <f:facet name="header">Delete</f:facet> <h:commandButton actionListener="#{manager.clear(item)}" styleClass="buttons" value="Delete" /> </h:column> </h:dataTable> </h:form> </h:body> </html>
Finally, in order to be able to include this page as welcome page, include the following index.jsp into your project:
<html> <head> <meta http-equiv="Refresh" content="0; URL=index.xhtml"> </head> </html>
Running the Project from Eclipse
Once you are done with the Project set up your can deploy it from within your Eclipse Environment. Create a new Maven run configuration and add as Base Directory the Project’s base directory ( you can explore through your projects using the Browse_Workspace button). Next enter the Goals “clean package jboss-as:deploy” which will respectively delete build files, compile & package, deploy on a running JBoss AS instance.
Once done click Apply and Run. This will deploy your javaee6example.war to JBoss AS 7 which can be accessed at http://localhost:8080/javaee6example/
Download the sources: you can access the source code at my fork of JBoss developer framework