Moved to Turku in -95 to study Computer Science at the Åbo Akademi University and might someday even graduate. Perhaps. I'm currently involved with Seam/Weld, time permitting. and I also use Seam for projects at work in Affecto Finland.
| Recent Entries |
|
11. Jan 2011
|
||
|
27. Jun 2010
|
||
|
23. Jun 2010
|
||
|
21. Jun 2010
|
||
|
07. Jun 2010
|
||
|
06. Jun 2010
|
||
|
05. Jun 2010
|
In this third part we'll be hooking up JPA 2 with the static metamodel generator, Bean Validation and Envers
JPA
Let's get persistent. When we're talking persistence, we need a persistence.xml so let's make a folder META-INF src/main/resources and create one there
<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="Greetings"> <jta-data-source>java:/DefaultDS</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" /> <property name="hibernate.hbm2ddl.auto" value="create-drop" /> <property name="hibernate.show-sql" value="true" /> </properties> </persistence-unit> </persistence>
We're using the standard HSQL DefaultDS that comes with JBoss AS 6. If you want to be really hip, google around for @DataSourceDefinition which is a new kid on the block in EE 6 (haven't tried if AS 6 supports it yet, though)
Next, let's expand our model from Strings to a Greeting entity. Create a
package com.acme.greetings;
import static javax.persistence.TemporalType.TIMESTAMP;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Entity
public class Greeting
{
@Id
@GeneratedValue
int id;
String text;
@Temporal(TIMESTAMP)
Date created = new Date();
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getText()
{
return text;
}
public void setText(String text)
{
this.text = text;
}
}
change the GreetingBean to
package com.acme.greetings;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.inject.Named;
import org.icefaces.application.PushRenderer;
@ApplicationScoped
@Named
public class GreetingBean implements Serializable
{
Greeting greeting = new Greeting();
List<Greeting> greetings = new ArrayList<Greeting>();
@Inject
@Added
Event<Greeting> greetingAddedEvent;
@Inject
GreetingArchiver greetingArchiver;
@PostConstruct
public void init()
{
greetings = greetingArchiver.loadGreetings();
}
public void addGreeting()
{
greetings.add(greeting);
greetingAddedEvent.fire(greeting);
greeting = new Greeting();
PushRenderer.render("greetings");
}
public Greeting getGreeting()
{
return greeting;
}
public void setGreeting(Greeting greeting)
{
this.greeting = greeting;
}
public List<Greeting> getGreetings()
{
PushRenderer.addCurrentSession("greetings");
return greetings;
}
public void setGreetings(List<Greeting> greetings)
{
this.greetings = greetings;
}
}
We have also injected an event that is fired when comments are added:
@Inject @Added Event<Greeting> greetingAddedEvent;
so we need a qualifier called Added:
package com.acme.greetings;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
@Qualifier
@Retention(RUNTIME)
@Target( { METHOD, FIELD, PARAMETER, TYPE })
public @interface Added
{
}
and greetings.xhtml to
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ice="http://www.icesoft.com/icefaces/component"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>
Greetings
</title>
</h:head>
<h:body>
<h:form>
<ice:inputText value="#{greetingBean.greeting.text}" effect="#{greetingBean.appear}"/>
<h:commandButton value="Add" action="#{greetingBean.addGreeting}" />
<h:dataTable value="#{greetingBean.greetings}" var="greeting">
<h:column>
<h:outputText value="#{greeting.text}"/>
</h:column>
</h:dataTable>
</h:form>
</h:body>
</html>
(we changed the order of the table and the input fields as it was getting annoying to have the field and button move down as we add comments)
Of course since we are firing events it would be nice if someone is actually listening. Let's create a GreetingArchvier:
package com.acme.greetings;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@ApplicationScoped
public class GreetingArchiver
{
@PersistenceContext
EntityManager db;
@Inject
UserTransaction userTransaction;
public void saveGreeting(@Observes @Added Greeting greeting)
{
try
{
userTransaction.begin();
db.joinTransaction();
db.persist(greeting);
db.flush();
userTransaction.commit();
}
catch (Exception e)
{
e.printStackTrace();
// The recommended way of dealing with exceptions, right?
}
}
public List<Greeting> loadGreetings()
{
return db.createQuery("from Greeting").getResultList();
}
}
That observes @Added Greetings and store them to the database. Notice also the loadGreetings() method that GreetingBean calls in it's @PostConstruct to populate itself with old comments. Well, with create-drop in our persistence.xml there won't be much to load but let's fix that later.
JPA 2
That's all nice but we're trying to be livin' on the edge so let's bring in JPA and typesafe queries and with those we better have some static metamodel generator, otherwise the attributes will quickly become a burden. There is Eclipse integration available (google around) but if you're doing automated maven-based builds, you're going to need this anyway. Since both Eclipse and Maven are involved in building, be prepared for some chicken-egg-project-cleaning-and-refreshing in Eclipse from time to time when adding new entities. Anyways, open up pom.xml and add some plugin repositories:
<pluginRepositories> <pluginRepository> <id>jfrog</id> <url>http://repo.jfrog.org/artifactory/plugins-releases/</url> </pluginRepository> <pluginRepository> <id>maven plugins</id> <url>http://maven-annotation-plugin.googlecode.com/svn/trunk/mavenrepo/</url> </pluginRepository> </pluginRepositories>
The maven-compiler-plugin will need an argument to not process annotations automagically once we slap jpamodelgen on the classpath
<configuration> <source>1.6</source> <target>1.6</target> <compilerArgument>-proc:none</compilerArgument> </configuration>
and the job should be taken over by our new build-plugins:
<plugin> <groupId>org.bsc.maven</groupId> <artifactId>maven-processor-plugin</artifactId> <version>1.3.5</version> <executions> <execution> <id>process</id> <goals> <goal>process</goal> </goals> <phase>generate-sources</phase> <configuration> <outputDirectory>target/metamodel</outputDirectory> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <version>1.0.0.Final</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <version>1.3</version> <executions> <execution> <id>add-source</id> <phase>generate-sources</phase> <goals> <goal>add-source</goal> </goals> <configuration> <sources> <source>target/metamodel</source> </sources> </configuration> </execution> </executions> </plugin>
Dan Allen thinks this is a lot of configuration for this task, I'll have to remember to ask if he ever got his simple, elegant solution to place the artifacts in usable places ;-)
Run mvn eclipse:eclipse to have the target/metamodel added to eclipse and do the project level Maven, refresh project configuration.
Run the maven build and you should see the Greeting_ class appear in target/metamodel and in the WAR structure. Now let's bring it into use:
First we add EntityManger/EntityManagerFactory producers (the recommeded CDI way of wrapping them)
package com.acme.greetings;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnit;
public class DBFactory
{
@Produces @GreetingDB @PersistenceContext EntityManager entityManager;
@Produces @GreetingDB @PersistenceUnit EntityManagerFactory entityManagerFactory;
}
We also need a qualifier for that
package com.acme.greetings;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
@Qualifier
@Retention(RUNTIME)
@Target( { METHOD, FIELD, PARAMETER, TYPE })
public @interface GreetingDB
{
}
Finally, let's modify or GreetingArchiver:
package com.acme.greetings;
import java.util.Date;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Root;
import javax.transaction.UserTransaction;
@ApplicationScoped
public class GreetingArchiver
{
@Inject
@GreetingDB
EntityManager db;
@Inject
UserTransaction userTransaction;
CriteriaQuery<Greeting> loadQuery;
ParameterExpression<Date> timestampParam;
@Inject
public void initQuery(@GreetingDB EntityManagerFactory emf)
{
CriteriaBuilder cb = emf.getCriteriaBuilder();
timestampParam = cb.parameter(Date.class);
loadQuery = cb.createQuery(Greeting.class);
Root<Greeting> greeting = loadQuery.from(Greeting.class);
loadQuery.select(greeting);
loadQuery.where(cb.greaterThan(greeting.get(Greeting_.created), timestampParam));
}
public void saveGreeting(@Observes @Added Greeting greeting)
{
try
{
userTransaction.begin();
db.joinTransaction();
db.persist(greeting);
db.flush();
userTransaction.commit();
}
catch (Exception e)
{
e.printStackTrace();
// The recommended way of dealing with exceptions, right?
}
}
public List<Greeting> loadGreetings()
{
Date tenMinutesAgo = new Date();
tenMinutesAgo.setTime(tenMinutesAgo.getTime() - 10 * 60 * 1000);
return db.createQuery(loadQuery).setParameter(timestampParam, tenMinutesAgo).getResultList();
}
}
Bean Validation
Adding Bean Validation is a breeze, just stick the annotations on the entity fields in Greeting:
@Size(min = 1, max = 50) String text;
and attach a message to the input field in greetings.xhtml:
<ice:inputText id="feedback" value="#{greetingBean.greeting.text}" effect="#{greetingBean.appear}"/>
<h:message for="feedback" />
I tried placing a @NotNull on text but it still failed on submit because the values came in as empty string (might be this is the designed behavior) so I used min = 1 instead.
Envers
If you would like to have auditing on your entities, you need to add the Envers dep to pom.xml
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> <version>3.5.1-Final</version> <scope>provided</scope> </dependency>
I cheated a little and marked it provided
as it was pulling in a lot of deps. I downloaded the envers.jar and dropped it in the server
common lib with it's other hibernate buddy-jar:s. After that we can stick an annotation on the entity
@Entity @Audited public class Greeting
Last but not least to enjoy automatic data auditing we need to add the Envers listeners to persistence.xml
<property name="hibernate.ejb.event.post-insert" value="org.hibernate.ejb.event.EJB3PostInsertEventListener,org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.post-update" value="org.hibernate.ejb.event.EJB3PostUpdateEventListener,org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.post-delete" value="org.hibernate.ejb.event.EJB3PostDeleteEventListener,org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.pre-collection-update" value="org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.pre-collection-remove" value="org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.post-collection-recreate" value="org.hibernate.envers.event.AuditEventListener" />
This concludes part II, next time we'll be looking at EJB:s and MDB:s in more details. And perhaps abandoning our WAR-only utopia for now. Guess I'll have to learn about the maven EJB plugin. Hints for good tutorials accepted.
PS. Have you checked the size of the WAR file after you've taken all these technologies into use? Around 320k. And of those ~85% are the ICEfaces libs (the only external deps)
This is part II of my series on how to set up a Java EE 6 application and stuff as many technologies into a simple application that can ever fit. And then some.
ICEfaces
Now let's pull in ICEfaces. The guys at RedHat/JBoss have a RichFaces fetisch so it's only fair that the competition gets some attention, too. And it increases my chances of getting a t-shirt in the ICEfaces blogging competition. Oh yeah, and it's a nice framework in general.
Now since we're living on the edge, lets pull in their 2.0 alpha3 version. Unfortunately, the new component set didn't make A3 so it's a bit vanilla at this stage. Anyway, let's add their snapshots repository to our pom.xml
<repositories> <repository> <id>ICEfaces snapshots</id> <url>http://anonsvn.icefaces.org/repo/maven2/snapshots/</url> </repository> </repositories>
and add a version property
<icefaces.version>2.0-A3</icefaces.version>
and add the deps
<dependency>
<groupId>org.icefaces</groupId>
<artifactId>icefaces</artifactId>
<version>${icefaces.version}</version>
<exclusions>
<exclusion>
<groupId>javax.faces</groupId>
<artifactId>jsf-impl</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.icefaces</groupId>
<artifactId>icepush</artifactId>
<version>${icefaces.version}</version>
</dependency>
Notice how we exclude the jsf-impl? We're using an appserver for crying out loud! No need to throw stuff like that in, it's already provided.
Next let's change out GreetingBean to actually do something more useful
package com.acme.greetings;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import org.icefaces.application.PushRenderer;
@ApplicationScoped
@Named
public class GreetingBean implements Serializable
{
String greeting;
List<String> greetings = new ArrayList<String>();
public void addGreeting()
{
greetings.add(greeting);
greeting = "";
PushRenderer.render("greetings");
}
public String getGreeting()
{
return greeting;
}
public void setGreeting(String greeting)
{
this.greeting = greeting;
}
public List<String> getGreetings()
{
PushRenderer.addCurrentSession("greetings");
return greetings;
}
public void setGreetings(List<String> greetings)
{
this.greetings = greetings;
}
}
We've changed the GreetingBean to become @ApplicationScoped, that is, common for everyone so we have a sort of a chatroom. In getGreetings() we register the PushRenderer for our session and in addGreeting we trigged a render to all the currently active clients. This mechanism allows for DOM-changed to be pushed to the clients withoug need for active polling or browser refresh. Neat, huh?
Registering the PushRenderer sessions in a getter like this is a bit of a hack that stems from the fact that we have this application-scoped Grand Unified Bean that is initialized only once (for the first user triggering it) while the sessions need to be registered for each client on their FacesContext (I actually got this wrong the first time). No worries, we will fix this in the next part where we refactor it into GreetingServer and GreetingClient which have more appropriate scopes. Writing applications is a constant flow of refactorings and improvements, remember?
Let's for now forget about such trivialities as concurrent access to the list etc (we're moving it to a concurrency-controlled @Singleton later on). We're in a happy place...
Fixing the broken test is left as a exercise for the reader.
Now let's change our greetings.xhtml to use the new backing bean
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>
Greetings
</title>
</h:head>
<h:body>
<h:form>
<h:dataTable value="#{greetingBean.greetings}" var="greeting">
<h:column>
<h:outputText value="#{greeting}"/>
</h:column>
</h:dataTable>
<h:inputText value="#{greetingBean.greeting}"/>
<h:commandButton value="Add" action="#{greetingBean.addGreeting}" />
</h:form>
</h:body>
</html>
Repack. Redploy. Re...what...the...$CURSE$ is a ZipException and what does it want from me? Oops. Known bug in M3, let's work around it
When the going gets tough, the tough get going. Locate icefaces-2.0-A3.jar from your local repository. Extract it's faces-config.xml, replace your own faces-config.xml with it (it was empty, anyways) and finally rip out the entire faces-config.xml from the icefaces jar (picture the heart scene from Indiana Jones and the Temple of Doom).
I could have said move faces-config.xml from the icefaces jar to the application WEB-INF
but that sounded a lot more cool.
Repack. Redeploy. Rejoyce. Business as usual.
Point your browser at http://localhost:8080/Greetings-1.0.0-SNAPSHOT/faces/greetings.xhtml and add some text. Now comes the nice part -
open another browser (another brand) or use the incognito mode (you know the mode where we can surf... gifts to our loved ones without
leaving traces in the browsing history) or the IE new session
to open another session to the application. This is important because
browsers commonly share session and cookies between tabs so we want to make sure there's no cheating involved here. Tap in some comment
in the other application and they should become visible immediately. Ta-daa...
ICEfaces 2
Now lets bring in some ice components. Wait, didn't you just say they were slipped from alpha3? Yes, the new ones but you are still able to use the components from the 1.8-series by adding some compatibility lib. Let's add the deps to pom.xml
<dependency>
<groupId>org.icefaces</groupId>
<artifactId>icefaces-compat</artifactId>
<version>${icefaces.version}</version>
</dependency>
<dependency>
<groupId>org.icefaces</groupId>
<artifactId>icefaces-comps-compat</artifactId>
<version>${icefaces.version}</version>
</dependency>
And yes, to avoid the dreaded ZipException when deploying the application, you have to move the contents from the faces-config.xml files in the jars in your local repository into your applications own faces-config.xml before assembling and deploying the application.
Next, lets' modify the greetings.xml to add the namespace
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:ice="http://www.icesoft.com/icefaces/component" xmlns:ui="http://java.sun.com/jsf/facelets">
and let's change the input field to the iced version that supports effects
<ice:inputText value="#{greetingBean.greeting.text}" effect="#{greetingBean.appear}"/>
and finally we add an effect-producer to GreetingBean
@Produces
@RequestScoped
public Effect getAppear()
{
return new Appear();
}
Bling-bling!
Some URL cleaning
As I mentioned before, the URL isn't that user-friendly, let's see what we can do about that. Let's start with the http listener port.
We have two options - read the documentation or search all xml files that contain the string 8080
. Let's go with the second option
as I know that's how you do stuff usually anyway. bindings-jboss-beans.xml in server\default\conf\bindingservice.beans\META-INF sounds
promising. s/8080/80.
Next - the welcome file. Edit web.xml and add
<welcome-file-list> <welcome-file>faces/welcome.xhtml</welcome-file> </welcome-file-list>
(Hey! It's actually working! I was just getting to the part where I would complain about having to add a redirecting index.html)
Next - the web context root. Sure, it could be fixed by changing the name of the war as they are in synch if not otherwise stated but let's state otherwise. There is no standard way of doing this(?) so let's do the vendor specific thingie instead. Add jboss-web.xml next to web.xml containing
<jboss-web>
<context-root>Greetings</context-root>
</jboss-web>
Now we have gone from
http://localhost:8080/Greetings-1.0.0-SNAPSHOT/faces/greetings.xhtml
to
http://localhost/Greetings
A note on testing
As you expand your application to bring in new dependencies (such as ICEfaces) always keep in mind that testing those classes also means you have to provide the dependencies in the Arquillian deployment. So be sure to add the dependent jars, the view of the world the Arquillian testing archives have is what you pack in them manually (apart from what the appserver already provides).
This concludes part II, in the next part I'll get to JPA 2 and the static metamodel generator conf.
The goal of this blog post is to walk you through an Java EE 6 application from a simple, static
web page until we have a full blown stack that consist of the stuff in the list below. I'm calling this
stack Summer
because after a long, hard winter Spring may be nice but boy, wait until Summer kicks in ;-)
- CDI (Weld)
- JSF 2 (facelets, ICEfaces 2)
- JPA 2 (Hibernate, Envers)
- EJB 3.1 (no-local-view, asynchronous, singletons, scheduling)
- Bean Validation (Hibernate Validator)
- JMS (MDB)
- JAX-RS (RESTEasy)
- JAX-WS
- Arqullian (incontainer-AS6)
We will pack all this in a single WAR. Just because we can (spoiler: in part IV). Noticed that apart from the component and testing frameworks, they are all standards? That's a lot of stuff. Fortunately, the appserver already provides most of the stuff so you're app will still be reasonably small.
As for the environment I'm using
- Eclipse (Galileo SR2)
- JBoss 6.0 M3
- Maven 3 (beta1)
- Sun JDK 6
- m2eclipse 0.10
This will not be your typical blog post where everything goes well - we will hit bugs. There will be curses, blood and guts and drama and
we will do workarounds and rewrites as we move along. Pretty much the same as your average day as a software developer probably looks like.
I'm also no expert in the technologies I use here so there are probably things that could be done better. Consider this more of a write-down
of my experiences in EE6-land that will probably mirror what others are going through. I will also not point you to links or additional information,
I assume that if I say RESTEasy
, you can google up more information if you are interested.
And I almost forgot: don't panic.
In the beginning: Project setup
So, lets start things off - go and download the stuff mentioned in the environment if you don't already have it. I'm not going to insult your intelligense by walking you through that (remind me to insult it later). Besides, it's pretty straightforward.
Let's make a new Maven project (File -> New -> Project... -> Maven -> Maven Project.
We skip the archetype selection and just make a simple project with group id com.acme
, artifact id Greetings
of version 1.0.0-SNAPSHOT
packed as a WAR. Now finish the wizard and now you should have a nice, perfect project. It will never be this perfect
again as our next step is adding code to it.
Maven tip-of-the-day for Windows users. Google up on how you change the path to your local repo as it might be somewhere under Documents And Settings
which has two effects: classpath gets huge and there could be problems due to the spaces. Change it to something like c:\java\m2repo
The first thing we notice that m2eclipse has J2SE-1.4 as default. How 2002. Besides, that will make using annotations impossible so lets change that. Edit the pom.xml and throw in
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.1</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build>
Save and right-click the project root and go Maven -> Update Project Configuration. Aah, that's better
JSF
Let's wake up JSF. We create a folder WEB-INF in src/main/webapp and throw in a web.xml because no web app is complete without it (enforced by the maven war plugin). OK, actually this can be configured in the plugin but let's keep the web.xml since we'll need it later.
<?xml version="1.0" encoding="ISO-8859-1"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"/>
and an empty faces-config.xml next to it
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
version="2.0"/>
and in webapp we add a greeting.xhtml like
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head> <title> Greetings </title> </h:head> <h:body> <h:outputText value="Hello world"/> </h:body> </html>
Will it blend? I mean, will it deploy? Do a mvn clean package
and you should have a Greetings-1.0.0-SNAPSHOT in your projects target directory.
Throw it into the AS server/default/deploy directory and start up the server and go to
http://localhost:8080/Greetings-1.0.0-SNAPSHOT/faces/greetings.xhtml
The url is not pretty, but the server port, web context root, welcome files and JSF mappings can all be tuned later, let's focus on technologies and dependencies for now. But wait - at which point did we define the JSF servlet and mappings in web.xml? We didn't. It's automagic for JSF-enabled applications.
EJB and CDI
Next step is bringing in some backing beans, let's outsource our greeting. We make a stateless EJB and use it in CDI
package com.acme.greetings;
@Stateful
@Model
public class GreetingBean
{
public String getGreeting()
{
return "Hello world";
}
}
The @Stateful defines a stateful session EJB (3.1 since it's a POJO) and the @Model is a CDI stereotype that is @RequestScoped and @Named
(which means the lifecycle is bound to a single HTTP request and it has a name that can be referenced in EL and defaults to greetingBean
in this case). But we have a problem - the annotations don't resolve to anything. So we need to pick them up from somewhere(tm). Fortunately
we can have all the APIs picked up for us by adding the following to our pom.xml
<dependencies> <dependency> <groupId>org.jboss.spec</groupId> <artifactId>jboss-javaee-6.0</artifactId> <version>1.0.0.Beta4</version> <type>pom</type> <scope>provided</scope> </dependency> </dependencies>
Sun Java API artifacts are a bit amusing since getting hold of them can be a bit tricky. First they publish them in the JSR:s and then they treat them like they're top secret. Fortunately Glassfish and now JBoss have started making them available in their repositories (although under their own artifact names, but still)...
We also need to make sure we have set up the JBoss repositories for this according to http://community.jboss.org/wiki/MavenGettingStarted-Users.
Have a look at what happened in the projects Maven Dependencies
. Good. Now close it and back away. It's getting hairy in there so better trust
Maven to keep track of the deps from now on.
The imports should now be available in our bean so we import
import javax.ejb.Stateful; import javax.enterprise.inject.Model;
and EL-hook the bean up with
<h:body>
<h:outputText value="#{greetingBean.greeting}"/>
</h:body>
in greetings.xhtml.
Just as no web application is complete without web.xml, no CDI application is complete without beans.xml. Let's add it to WEB-INF
<?xml version="1.0" encoding="ISO-8859-1"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd" />
Package and redeploy. We get a warning about encoding when compiling so lets add this to our pom.xml
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties>
Back to http://localhost:8080/Greetings-1.0.0-SNAPSHOT/faces/greetings.xhtml SUCCESS! No. Wait. Huge stack trace hits you for 300 points of damage. Let's back up on our EJB, there are still some issues with 3.1 style EJBs in WAR-only-packaging on AS 6 M3. Remove the @Stateful annotation and it becomes a normal CDI managed POJO. Repackage. Redploy. Recoyce.
Testing
Testing is hip nowadays so let's bring in Arquillian. Arquillian is the latest and greatest in EE testing (embedded or incontainer).
Start using it now. In a year or so when everone else catch up you can go I've been using it since Alpha
. Add the following property to pom.xml:
<arquillian.version>1.0.0.Alpha2</arquillian.version>
and these deps
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-junit</artifactId>
<version>${arquillian.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
and this profile
<profiles> <profile> <id>jbossas-local-60</id> <dependencies> <dependency> <groupId>org.jboss.arquillian.container</groupId> <artifactId>arquillian-jbossas-local-60</artifactId> <version>1.0.0.Alpha2</version> </dependency> <dependency> <groupId>org.jboss.jbossas</groupId> <artifactId>jboss-server-manager</artifactId> <version>1.0.3.GA</version> </dependency> <dependency> <groupId>org.jboss.jbossas</groupId> <artifactId>jboss-as-client</artifactId> <version>6.0.0.20100429-M3</version> <type>pom</type> </dependency> </dependencies> </profile> </profiles>
Maven will probably now download the entire internet for you.
Let's write our first test and place it in the test source folder:
package com.acme.greetings.test;
import javax.inject.Inject;
import org.jboss.arquillian.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.impl.base.asset.ByteArrayAsset;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.acme.greetings.GreetingBean;
@RunWith(Arquillian.class)
public class GreetingTest
{
@Inject
GreetingBean greetingBean;
@Deployment
public static JavaArchive createTestArchive()
{
return ShrinkWrap.create("test.jar", JavaArchive.class).addClass(
GreetingBean.class).addManifestResource(
new ByteArrayAsset("<beans/>".getBytes()),
ArchivePaths.create("beans.xml"));
}
@Test
public void testInjection()
{
Assert.assertEquals("Hello World", greetingBean.getGreeting());
}
}
and then we try it out with mvn test -Pjbossas-local-60
. If we have the AS running we can save some time, otherwise the manager will start it
automagically. Setting the JBOSS_HOME env helps. What happens here is we use Shrinkwrap to create a deployment which consist of our GreetingBean
and an empty beans.xml file (for CDI) and the bean is then injected for use in our tests.
This concludes Part I. In part II we will set up ICEfaces and expand our application and in part III we'll set up JPA. Part IV is for MDB and EJB and part V for adding JAX-RS and JAX-WS for importing and exporting stuff.
|
|
|
Showing 6 to 8 of 8 blog entries |