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.