the magnificent java ee 7 in wildfly-o-rama
DESCRIPTION
My Hands on for Java EE 7 You can grab the code on github : https://github.com/antoinesd/magnificent-java-ee7TRANSCRIPT
The Magnificent Java EE 7 in Wildfly-O-Rama
Antoine Sabot-Durand Java EE Expert
Senior Software Developer @ Red Hat @antoine_sd
Antoine Sabot-DurandSenior Software Developer at Red Hat
Architect and Tech consultant
16 years in IT
Java & OSS :
CDI co-spec lead
CDI community development
Agorava technical leader
@antoine_sd
What’s in there ?
Short Java EE 7 Intro
Java EE History
Java EE 7 main features
Java EE 7 Content
WildFly
WildFly Roadmap
Java EE 7 in action
Java EE Introduction
Java EE History
1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013
Java EE History
1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011
Java Professional Edition
Mai 1998
2012 2013
Java EE History
1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011
Java Professional Edition
Mai 1998
J2EE 1.2 12/12/1999
2012 2013
Java EE History
1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011
Java Professional Edition
Mai 1998
J2EE 1.2 12/12/1999
J2EE 1.3 09/24/2001
2012 2013
Java EE History
1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011
Java Professional Edition
Mai 1998
J2EE 1.2 12/12/1999
J2EE 1.3 09/24/2001
J2EE 1.4 11/11/2003
2012 2013
Java EE History
1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011
Java Professional Edition
Mai 1998
J2EE 1.2 12/12/1999
J2EE 1.3 09/24/2001
J2EE 1.4 11/11/2003
Java EE 5 05/11/2006
2012 2013
Java EE History
1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011
Java Professional Edition
Mai 1998
J2EE 1.2 12/12/1999
J2EE 1.3 09/24/2001
J2EE 1.4 11/11/2003
Java EE 5 05/11/2006
Java EE 6 12/10/2009
2012 2013
Java EE History
1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011
Java Professional Edition
Mai 1998
J2EE 1.2 12/12/1999
J2EE 1.3 09/24/2001
J2EE 1.4 11/11/2003
Java EE 5 05/11/2006
Java EE 6 12/10/2009
2012 2013
Java EE 7 06/22/2013
WebSocket client/server endpoints
Batch Applications
JSON Processing
Concurrency Utilities
Simplified JMS API
@Transactional and @TransactionScoped
JAX-RS Client API
Pervasive CDI
More annotated POJOs
Faces Flow
Java EE 7 Main Features
Java EE 7 JSR
EJB 3.2
Servlet 3.1
CDI Extensions
Bea
n Va
lidat
ion
1.1
Batch 1.0
Web Fragments
JCA 1.7JMS 2.0JPA 2.1
Managed Beans 1.0
Concurrency 1.0Common Annotations 1.1
Interceptors 1.2, JTA 1.2CDI 1.1
JSF 2.2,JSP 2.3,EL 3.0
JAX-RS 2.0, JAX-WS 2.2 JSON 1.0 WebSocket
1.0
Java EE 7 JSR
EJB 3.2
Servlet 3.1
CDI Extensions
Bea
n Va
lidat
ion
1.1
Batch 1.0
Web Fragments
JCA 1.7JMS 2.0JPA 2.1
Managed Beans 1.0
Concurrency 1.0Common Annotations 1.1
Interceptors 1.2, JTA 1.2CDI 1.1
JSF 2.2,JSP 2.3,EL 3.0
JAX-RS 2.0, JAX-WS 2.2 JSON 1.0 WebSocket
1.0
Java EE 7 JSR
EJB 3.2
Servlet 3.1
CDI Extensions
Bea
n Va
lidat
ion
1.1
Batch 1.0
Web Fragments
JCA 1.7JMS 2.0JPA 2.1
Managed Beans 1.0
Concurrency 1.0Common Annotations 1.1
Interceptors 1.2, JTA 1.2CDI 1.1
JSF 2.2,JSP 2.3,EL 3.0
JAX-RS 2.0, JAX-WS 2.2 JSON 1.0 WebSocket
1.0
Java EE 7 JSR
EJB 3.2
Servlet 3.1
CDI Extensions
Bea
n Va
lidat
ion
1.1
Batch 1.0
Web Fragments
JCA 1.7JMS 2.0JPA 2.1
Managed Beans 1.0
Concurrency 1.0Common Annotations 1.1
Interceptors 1.2, JTA 1.2CDI 1.1
JSF 2.2,JSP 2.3,EL 3.0
JAX-RS 2.0, JAX-WS 2.2 JSON 1.0 WebSocket
1.0
Java EE 7 JSR
EJB 3.2
Servlet 3.1
CDI Extensions
Bea
n Va
lidat
ion
1.1
Batch 1.0
Web Fragments
JCA 1.7JMS 2.0JPA 2.1
Managed Beans 1.0
Concurrency 1.0Common Annotations 1.1
Interceptors 1.2, JTA 1.2CDI 1.1
JSF 2.2,JSP 2.3,EL 3.0
JAX-RS 2.0, JAX-WS 2.2 JSON 1.0 WebSocket
1.0
Java EE 7 JSR
EJB 3.2
Servlet 3.1
CDI Extensions
Bea
n Va
lidat
ion
1.1
Batch 1.0
Web Fragments
JCA 1.7JMS 2.0JPA 2.1
Managed Beans 1.0
Concurrency 1.0Common Annotations 1.1
Interceptors 1.2, JTA 1.2CDI 1.1
JSF 2.2,JSP 2.3,EL 3.0
JAX-RS 2.0, JAX-WS 2.2 JSON 1.0 WebSocket
1.0
Java EE 7 JSR
EJB 3.2
Servlet 3.1
CDI Extensions
Bea
n Va
lidat
ion
1.1
Batch 1.0
Web Fragments
JCA 1.7JMS 2.0JPA 2.1
Managed Beans 1.0
Concurrency 1.0Common Annotations 1.1
Interceptors 1.2, JTA 1.2CDI 1.1
JSF 2.2,JSP 2.3,EL 3.0
JAX-RS 2.0, JAX-WS 2.2 JSON 1.0 WebSocket
1.0
Java EE 7 JSR
EJB 3.2
Servlet 3.1
CDI Extensions
Bea
n Va
lidat
ion
1.1
Batch 1.0
Web Fragments
JCA 1.7JMS 2.0JPA 2.1
Managed Beans 1.0
Concurrency 1.0Common Annotations 1.1
Interceptors 1.2, JTA 1.2CDI 1.1
JSF 2.2,JSP 2.3,EL 3.0
JAX-RS 2.0, JAX-WS 2.2 JSON 1.0 WebSocket
1.0
Java EE 7 JSR
EJB 3.2
Servlet 3.1
CDI Extensions
Bea
n Va
lidat
ion
1.1
Batch 1.0
Web Fragments
JCA 1.7JMS 2.0JPA 2.1
Managed Beans 1.0
Concurrency 1.0Common Annotations 1.1
Interceptors 1.2, JTA 1.2CDI 1.1
JSF 2.2,JSP 2.3,EL 3.0
JAX-RS 2.0, JAX-WS 2.2 JSON 1.0 WebSocket
1.0
Java EE 7 JSR
EJB 3.2
Servlet 3.1
CDI Extensions
Bea
n Va
lidat
ion
1.1
Batch 1.0
Web Fragments
JCA 1.7JMS 2.0JPA 2.1
Managed Beans 1.0
Concurrency 1.0Common Annotations 1.1
Interceptors 1.2, JTA 1.2CDI 1.1
JSF 2.2,JSP 2.3,EL 3.0
JAX-RS 2.0, JAX-WS 2.2 JSON 1.0 WebSocket
1.0
Previously named JBoss Application Server
Named change to better differentiate Community from Supported product
Support Java EE 7
Fast, Lightweight, Manageable
Developer Friendly
Open Source
JBoss WildFly
Alpha 1 - May 2013
Alpha 2 - June 2013 (Java EE 7 released on the 22)
Alpha 3 - July 2013
Alpha 4 - August 2013
Beta 1 - October 2013
CR1 - December 2013
Final - Mar/Apr 2014
That’s 8/9 months after EE 7 release (better than 2 years for AS 7)
WildFly Roadmap
Let’s get some action !
Welcome to Chat-e-Chat-oChat-e-Chat-o is a Startup which develop a SaaS chat service
We raised fund from VC to create their first release
CTO decided to use Java EE 7 to develop the service
As the main developer, you have the task to implement all the feature asked by marketing
Events and name in this HOL are totally fictive
Situation and use cases are simplified
This is your story...
At the beginningAfter reading some doc. You created the first chat demo
It contains 4 files
pom.xml : Maven configuration file
ChatEndPoint.java : A Java class corresponding to a websocket Endpoint
index.html : Home Page of the Service
websocket.js : a little JS lib to exchange with the server
This 4 files are enough to start the application server with a (very) basic chat service
pom.xml 1/2
<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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.lab.javaee</groupId> <artifactId>demo-chat</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>demo-chat</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> </dependencies>
pom.xml 2/2<build> <finalName>${project.name}</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.0</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-maven-plugin</artifactId> <version>1.0.0.Beta1</version> <configuration> <version>8.0.0.CR1</version> </configuration> </plugin> </plugins></build></project>
index.html<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>WebSocket Chat</title> </head> <body> <h1>Chat!</h1> <div style="text-align: center;"> <form action=""> <table> <tr> <td> Users<br/> <textarea readonly="true" rows="6" cols="20" id="userField"></textarea> </td> <td> Chat Log<br/> <textarea readonly="true" rows="6" cols="50" id="chatlogField"></textarea> </td> </tr> <tr> <td colspan="2"> <input id="textField" name="name" value="Duke" type="text"><br> <input onclick="join();" value="Join" type="button"> <input onclick="send_message();" value="Chat" type="button"> </td> </tr> </table> </form> </div> <div id="output"></div> <script language="javascript" type="text/javascript" src="websocket.js"></script> </body> </html>
websocket.jsvar wsUri = "ws://" + document.location.host + "/demo-chat/websocket"; var websocket = new WebSocket(wsUri); var username; var output = document.getElementById("output"); websocket.onopen = function (evt) {onOpen(evt)}; websocket.onmessage = function (evt) {onMessage(evt)}; websocket.onerror = function (evt) {onError(evt)}; !function join() { username = textField.value; websocket.send(username + " joined"); } !function send_message() {websocket.send(username + ": " + textField.value);} !function onOpen() {writeToScreen("Connected to " + wsUri);} !function onMessage(evt) { console.log("onMessage"); writeToScreen("RECEIVED: " + evt.data); if (evt.data.indexOf("joined") != -1) { userField.innerHTML += evt.data.substring(0, evt.data.indexOf(" joined")) + "\n"; } else { chatlogField.innerHTML += evt.data + "\n"; } } !function onError(evt) {writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);} !function writeToScreen(message) {output.innerHTML += message + "<br>";}
ChatEndPoint.javaimport javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.*; !@ServerEndpoint("/websocket") public class ChatEndpoint { private static final Set<Session> peers = Collections.synchronizedSet(new HashSet<Session>()); @OnOpen public void onOpen(Session peer) { peers.add(peer); } @OnClose public void onClose(Session peer) { peers.remove(peer); } @OnMessage public void message(String message, Session client) throws IOException, EncodeException { for (Session peer : peers) { peer.getAsyncRemote().sendText(message); } }}
Files organization & launching
Files must be organized like this way
To launch the app
In your shell, go to the directory containing the pom.xml file
and type :
mvn clean package wildfly:run!
To test the app browse to :
http://localhost:8080/demo-chat
Step 1 : Create a Chat ServiceAs we will add functionalities to our application, we want to separate future business logic from the Websocket endpoint
That’s why we decide to create a ChatService classes to deal with chat business logic
To implement this you’ll have to :
Activate CDI
Create ChatService CDI Bean
Remove all business logic from ChatEndpoint to put it in ChatService
Git Shortcut
git checkout step_1
beans.xml!
Activate CDI
To activate CDI you only have to create an empty file named beans.xml in folder src/main/webapp/WEB-INF
ChatService.java
@ApplicationScopedpublic class ChatService { private final Set<Session> peers = Collections.synchronizedSet(new HashSet<Session>()); public boolean add(Session session) { return peers.add(session); } public boolean remove(Object o) { return peers.remove(o); } public void processMessage(String message) { for (Session peer : peers) { peer.getAsyncRemote().sendText(message); } }}
ChatEndPoint.java
@ServerEndpoint("/websocket") public class ChatEndpoint { @Inject private ChatService service; @OnOpen public void onOpen(Session peer) { service.add(peer); } @OnClose public void onClose(Session peer) { service.remove(peer); } @OnMessage public void message(String message, Session client) throws IOException, EncodeException { service.processMessage(message); }}
Step 2 : Keyword detectionOur main partner is the Poodle website.They want to embed our service but they need a solution to detect keywords in chat to feed their advertising platform
You decide to build a prototype of this feature with CDI built-in observer pattern
To implement this you’ll have to :
Modify ChatService class by :
Injecting an Event generator in the endpoint
Modifying the message() method to fire the event
Create an observer bean for the event
Git Shortcut
git checkout step_2
ChatService.javaimport javax.enterprise.event.Event; import javax.inject.Inject; ...!@ApplicationScopedpublic class ChatService { ... @Inject private Event<String> events; ... public void processMessage(String message) { if (message.toLowerCase().indexOf("world") > -1) events.fire(message); for (Session peer : peers) { peer.getAsyncRemote().sendText(message); } } }
Bean with Observer
import javax.enterprise.event.Observes; public class MessageObserver { public void observesWorldMessages(@Observes String msg) { System.out.println("Keyword was trapped : " + msg); } }
Keyword detection resultIn your shell, go to the directory containing the pom.xml file
and type : mvn clean package wildfly:run!
To test the app browse to : http://localhost:8080/demo-chat
When you type a message with «world» inside an event is fired and an alert is written on the console
Step 3 : More decoupling with AOPOk, our system to detect certain keywords works nicely with some kind of decoupling thanks to CDI events
But the team is not happy to have this code directly into the service. If other filtering needs occur, we’ll have a big if cascade in processMessage() method
So you decide to use AOP and interceptor to externalize event firing from your main code :
Extract interface from ChatService to allow use of Decorator
Create a decorator to track Ad Word in the ChatService processMessage() method
Move the event generator to the Decorator
Git Shortcut
git checkout step_3
Extract Interface from ChatService
public interface ChatService { boolean add(Session session); boolean remove(Object o); void processMessage(String message);}
@ApplicationScopedpublic class ChatServiceImpl implements ChatService {!… !}
Create the PoodleAdWord Decorator@Decorator@Priority(Interceptor.Priority.APPLICATION) public abstract class PoddleAddWordDecorator implements ChatService { @Inject @Delegate private ChatService delegateService; private final List<String> adWords = new ArrayList<String>() {{ add("world"); add("duck"); add("cartman"); }}; @Inject private Event<String> events; // This should be moved from ChatServiceImpl class @Override public void processMessage(String message) { String lmessage = message.toLowerCase(); for (String s : adWords) { if (lmessage.indexOf(s) > -1) { events.fire(s); } } delegateService.processMessage(message); }}
Final version of ChatServiceImpl
@ApplicationScopedpublic class ChatServiceImpl implements ChatService { private final Set<Session> peers = Collections.synchronizedSet(new HashSet<Session>()); @Override public boolean add(Session session) { return peers.add(session); } @Override public boolean remove(Object o) { return peers.remove(o); } @Override public void processMessage(String message) { for (Session peer : peers) { peer.getAsyncRemote().sendText(message); } }}
More decoupling resultOur business code doesn’t contain cross cuting concern anymore
We’re ready to add other filters with this pattern
To test, type : mvn clean package wildfly:run!
To test the app browse to : http://localhost:8080/demo-chat
When you type a message containing an ad word (i.e. duck) an event is fired and an alert is written on the console
Step 4 : Please be politeThe famous Mapple company is also very interested in our chat platform
But they have a strict policy regarding bad words. So they need a solution to catch bad words, get notification and replace them by good ones.
As Poodle pay us more we want to give priority to their ad filter to be sure that «bad» keywords are still trapped for them
You know how to build this filter
Create Qualifiers to differentiate Ad Word events from Bad Word events
Build a new decorator to test messages content and correct impolite words
Configure decorator priority to have the Poodle one in first
Change existing event generator to add qualifiers top them
Git Shortcut
git checkout step_4
Create the AdWord qualifier
import javax.inject.Qualifier; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME;!@Qualifier@Documented@Retention(RUNTIME) public @interface AdWord { }
Create the BadWord qualifier
import javax.inject.Qualifier; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME;!@Qualifier@Documented@Retention(RUNTIME) public @interface BadWord { }
Create the Mapple Decorator@Decorator@Priority(Interceptor.Priority.APPLICATION + 10) public abstract class MapplePoliteDecorator implements ChatService { static final Map<String, String> DICTIONARY = new HashMap<String, String>() {{ put("fuck", "duck"); put("crap", "trap"); put("idiots", "world"); put("cartman", "Stan"); }}; @Inject @Delegate private ChatService delegateService; @Inject @BadWord private Event<String> events; @Override public void processMessage(String message) { String lmessage = message.toLowerCase(); String res = message; for (String word : DICTIONARY.keySet()) if (lmessage.indexOf(word) > -1) { res = res.replaceAll("(?i)" + word, DICTIONARY.get(word)); events.fire(word); } delegateService.processMessage(res); }}
Change existing code to introduce AdWord qualifier
@Decorator@Priority(Interceptor.Priority.APPLICATION) public abstract class PoddleAddWordDecorator implements ChatService {… @Inject @AdWord private Event<String> events; … }
public class MessageObserver { public void observesAdWords(@Observes @AdWord String word) { System.out.println("Ad word trapped : " + word); } public void observesbadWords(@Observes @BadWord String word) { System.out.println("Bad word trapped : " + word); }}
Be polite resultWe created a new Decorator that change message content after the 1st decorator did its job.
We’ll feed those dictionaries with a database later
To test type : mvn clean package wildfly:run!
To test the app browse to : http://localhost:8080/demo-chat
When you type a message containing a bad word inside, message is changed, an event is fired and an alert is written on the console.
If the bad word is also an ad word. The Addword is still track thanks to priority
Step 5 : Intercept to logOur code is nice with all this Decorators, but we’d like to have an easy way to trace what code is called without modifying it
For that we’ll need another kind of AOP : an interceptor to log information
Create an interceptor binding
Create the Log interceptor
Use it in our code
Git Shortcut
git checkout step_5
Create the Interceptor Binding
import javax.interceptor.InterceptorBinding; import java.lang.annotation.ElementType;import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target; @InterceptorBinding@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME) public @interface Log { }
Create the interceptor
@Interceptor@Log@Priority(Interceptor.Priority.APPLICATION) public class LoggerInterceptor { @AroundInvoke public Object manageTransaction(InvocationContext ctx) throws Exception { System.out.println("*** Before " + ctx.getMethod().toString()); Object res = ctx.proceed(); System.out.println("*** After " + ctx.getMethod().toString()); return res; }}
Using the Interceptor
@ApplicationScoped@Logpublic class ChatServiceImpl implements ChatService {… }
Log Interceptor resultWe created an interceptor and its binding to activate logging by annotation
To test type : mvn clean package wildfly:run!
To test the app browse to : http://localhost:8080/demo-chat
All calls on ChatServiceImpl will be logged to the console
Step 6 : Plastic surgeryLet’s face the cruel truth our UI is totally ugly!
As we decided to use Java EE stack, we give a try to JSF for our front
Out of the box JSF doesn’t provide rich component, we’re going to use Primefaces to produce a nice UI proposal
So, in this step, we’ll develop a JSF chat page to enhance the user experience
Git Shortcut
git checkout step_6
Adding PrimeFaces to the POM
<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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> … <dependencies> … <dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> <version>4.0</version> <scope>runtime</scope> </dependency> </dependencies> … </project>
The JSF Page<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>WebSocket Chat</title> </h:head> <h:body> <p:layout style="width:1024px;height:600px;margin-left: auto;margin-right: auto;"> <p:layoutUnit position="center" header="Chat-e-Chat-o"> <div style="text-align: center;"> <h:form id="chat"> <table> <tr> <td> Users<br/> <p:inputTextarea readonly="true" rows="6" cols="20" id="userField"/> </td> <td> Chat Log<br/> <p:inputTextarea readonly="true" rows="6" cols="50" id="chatlogField"/> </td> </tr> <tr> <td colspan="2"> <p:inputText id="textField" name="name" value="Duke"/><br/> <p:commandButton onclick="join();" value="Join" id="join"/> <p:commandButton onclick="send_message();" value="Chat" id="chat"/> </td> </tr> </table> </h:form> </div> <div id="output"></div> <script language="javascript" type="text/javascript" src="websocket.js"></script> </p:layoutUnit> </p:layout> </h:body> </html>
var wsUri = "ws://" + document.location.host + "/demo-chat/websocket"; !... !var userField = document.getElementById("chat:userField"); var chatlogField = document.getElementById("chat:chatlogField"); var textField = document.getElementById("chat:textField"); !... function join() { username = textField.value; websocket.send(username + " joined"); document.getElementById("chat:join").disabled = true; }
Modification of websocket.js
JSF generates different component id so we have to adapt the code
We also choose to disable the «join» button after usage to give focus to chat button
New content for index.html
<html> <head> <meta http-equiv="Refresh" content="0; URL=index.jsf"> </head> </html>
Plastic surgery resultWe got a better UI and user experience. Ok there’s still work to do, but we have the POC here ;)
To test type : mvn clean package wildfly:run!
To test the app browse to : http://localhost:8080/demo-chat
Beautiful isn’t it ?
Step 7 : A bit of structureSo we get message and dispatch them to all people, but we don’t do anything of them
It could be nice to receive structured message in JSON format and create our own object from it
In this step we’re gone :
Change client side js to generate JSON structure with username and message
Create a Message class to contain the java version of this JSON structure
Change the ChatService bean to deserialize JSON message with the new JSONP specification
Git Shortcut
git checkout step_7
Websocket.js modification
function send_message() { var msg = new Object(); msg.user = username; msg.content = textField.value; websocket.send(JSON.stringify(msg)); } ...function onMessage(evt) { console.log("onMessage : " + evt.data); writeToScreen("RECEIVED: " + evt.data); if (evt.data.indexOf("joined") != -1) { userField.innerHTML += evt.data.substring(0, evt.data.indexOf(" joined")) + "\n"; } else { var msg = JSON.parse(evt.data) chatlogField.innerHTML += msg.content + " said " + msg.user + "\n"; }
New Message Class
public class Message { private String user; private String content; public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
ChatService processMessage modification
public void processMessage(String message) { System.out.println(message); JsonReader reader = Json.createReader(new StringReader(message)); try { JsonObject msgObj = reader.readObject(); Message msg = new Message(); msg.setUser(msgObj.getString("user")); msg.setContent(msgObj.getString("content")); System.out.println("Message from " + msg.getUser() + " : " + msg.getContent()); } catch (JsonParsingException e) { System.out.println("Message is not in JSON format"); } finally { reader.close(); } for (Session peer : peers) { peer.getAsyncRemote().sendText(message); }}
Structure resultWe got now a data structure for all the messages. We can use it to provide other service (history, search, etc...)
To test type : mvn clean package wildfly:run!
To test the app browse to : http://localhost:8080/demo-chat
Nothing changed... But look at the console...
Step 8 : Save our messagesNow we have messages in a nice Pojo. What about persist them to provide new services
In this step we’re gone :
Add a JPA configuration
Turn our Pojo into a JPA entity
Create a service to handle message
Git Shortcut
git checkout step_8
Persistence.xml in META-INF
<?xml version="1.0" encoding="UTF-8"?><persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_1.xsd" version="2.1"> <persistence-unit name="chatPU" transaction-type="JTA"> <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="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> </properties> </persistence-unit> </persistence>
public class ConfigPersistence { @PersistenceContext @Produces private EntityManager em; }
ConfigPersistence.java
@Entity@Vetoedpublic class Message { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(name = "USERNAME") private String user; private String content; ... ! public Long getId() { return id; } public void setId(Long id) { this.id = id; } }
Message.java modification
We transform the class in an entity.
Note the @Vetoed CDI annotation that exclude the class from being a bean
We should also change the column name of user. Because user is a reserved name in SQL
ChatService modificationpublic class ChatServiceImpl implements ChatService {… @Inject EntityManager em; … @Override public void persistMessage(Message msg) { em.persist(msg); }… @Override @Transactional public void processMessage(String message) { System.out.println(message); JsonReader reader = Json.createReader(new StringReader(message)); try { JsonObject msgObj = reader.readObject(); Message msg = new Message(); msg.setUser(msgObj.getString("user")); msg.setContent(msgObj.getString("content")); System.out.println("Message from " + msg.getUser() + " : " + msg.getContent()); persistMessage(msg); } catch (JsonParsingException e) { System.out.println("Message is not in JSON format"); } finally { reader.close(); } for (Session peer : peers) { peer.getAsyncRemote().sendText(message); } }}
Save our messages resultWe saved our messages to provide future services (history, search, etc...)
To test type : mvn clean package wildfly:run!
To test the app browse to : http://localhost:8080/demo-chat
Nothing changed... But look at the console...
Step 9 : Message in a bottlePoodle plan to provide an appliance to send ad to our application
This appliance uses JMS to receive information asynchronously
So we decide to build a POC to test Messaging feature in Java EE 7
Create a message queue and a Sender Service
Modify PoodleAdWord Decorator to use the sender Bean
Create a MDB to mock appliance side and listen to our messages
Git Shortcut
git checkout step_9
Message queue and SenderServiceimport javax.annotation.Resource; import javax.inject.Inject; import javax.jms.JMSContext;import javax.jms.JMSDestinationDefinition; import javax.jms.Queue;@JMSDestinationDefinition(name = "java:global/jms/myQueue", resourceAdapter = "jmsra", interfaceName = "javax.jms.Queue", destinationName = "classicQueue", description = "My Sync Queue") public class JmsSenderService { @Resource(mappedName = "java:global/jms/myQueue") private Queue myQueue; @Inject private JMSContext jmsContext; public void sendMessage(String message) { jmsContext.createProducer().send(myQueue, message); }}
PoodleAdWord decorator modification
public abstract class PoddleAddWordDecorator implements ChatService { … @Inject JmsSenderService jms; … @Override public void processMessage(String message) { String lmessage = message.toLowerCase(); for (String s : adWords) { if (lmessage.indexOf(s) > -1) { jms.sendMessage(s); } } delegateService.processMessage(message); }}
The Message Driven Beanimport org.lab.javaee.chat.AdWord; import javax.ejb.ActivationConfigProperty; import javax.ejb.MessageDriven; import javax.enterprise.event.Event;import javax.inject.Inject; import javax.jms.JMSException;import javax.jms.Message;import javax.jms.MessageListener;@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "java:global/jms/myQueue"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), }) public class PoodleAdListener implements MessageListener { @Inject @AdWord private Event<String> adWordEvents; @Override public void onMessage(Message msg) { try { System.out.println("**** Poodle MDB receiving Ad Word thru jms : " + msg.getJMSMessageID()); adWordEvents.fire(msg.getBody(String.class)); } catch (JMSException e) { throw new RuntimeException("Something nasty happened", e); } }}
Message in a bottle resultWe put a messaging system in place with two classes and two annotations
To test type : mvn clean package wildfly:run!
To test the app browse to : http://localhost:8080/demo-chat
Enter an ad word and check the console
Questions