Java EE 6 is out and it indeed offers an interesting stack of technologies. So in today’s tutorial we are going to build a small sample web application that builds on this stack using Enterprise JavaBeans, Java Persistence API, Bean Validation, CDI and finally Java Server Faces and PrimeFaces.
The application we’re going to develop is a simple blog app that allows us to create new articles, list them and – finally delete them. We’re also covering some additional topics like JSF navigation, i18n, Ajax-enabled components and the deployment on the GlassFish application server.
Prerequisites
We do not need anything exotic for this tutorial .. just a JDK, Maven and a GlassFish. I’ve chosen the last one because of its status as Java EE 6 reference implementation but inbetween JBoss released a new version that supports the Java EE 6 Web Profile so perhaps it is also worth a look …
Our Technology Stack
We’re going to cover a lot of specifications so here is a brief overview of technologies used in the following tutorial:
Build management: Maven, Maven EJB Plugin and others ..
Application Server: GlassFish 3 (running on my Ubuntu machine)
Persistence: Java Persistence API as abstraction layer, TopLink/EclipseLink as persistence provider, JavaDB/Derby as concrete underlying RDBMS
Inversion of Control/Dependency Injection: CDI specs using Weld
Validation: Bean Validation / JSR 303
Presentation Tier: Java Server Faces 2 / Mojarra and PrimeFaces 2.2
Middle Tier: Enterprise Java Beans 3.1
Project Setup using Maven Archetypes
We’re too lazy to create a project skeleton and add dependencies by hand so we’re using an archetype to create our project..
-
Create a new Maven project using the webapp-javaee6 (org.codehaus.mojo.archetype:webapp-javaee6) archetype with your favourite IDE and Maven plugin or via console
mvn archetype:generate [INFO] Scanning for projects... [..] 254: remote -> webapp-javaee6 (Archetype for a web application using Java EE 6.) [..] Choose version: 1: 1.0 2: 1.0.1 3: 1.0.2 4: 1.1 5: 1.2 6: 1.3 Choose a number: 6: 6 Define value for property 'groupId': : com.hascode.tutorial.jee6 Define value for property 'artifactId': : jee6-blog-tutorial Define value for property 'version': 1.0-SNAPSHOT: 0.0.1 Define value for property 'package': com.hascode.tutorial.jee6: com.hascode.tutorial.jee6.blog [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------
-
Add the following dependencies and repositories – your pom.xml should look like this one
<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>com.hascode.tutorial.jee6</groupId> <artifactId>jee6-blog-tutorial</artifactId> <version>0.0.1</version> <packaging>war</packaging> <name>hasCode.com Java EE 6 Blog Tutorial</name> <properties> <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> <version>6.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.0.0.GA</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>4.0.2.GA</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derby</artifactId> <version>10.6.1.0</version> </dependency> <dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>javax.faces</groupId> <artifactId>jsf-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> <compilerArguments> <endorseddirs>${endorsed.dir}</endorseddirs> </compilerArguments> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.1</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.1</version> <executions> <execution> <phase>validate</phase> <goals> <goal>copy</goal> </goals> <configuration> <outputDirectory>${endorsed.dir}</outputDirectory> <silent>true</silent> <artifactItems> <artifactItem> <groupId>javax</groupId> <artifactId>javaee-endorsed-api</artifactId> <version>6.0</version> <type>jar</type> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-ejb-plugin</artifactId> <configuration> <ejbVersion>3.1</ejbVersion> <archive> <manifest> <addClasspath>true</addClasspath> </manifest> </archive> </configuration> </plugin> </plugins> <finalName>jee6-blog-tutorial</finalName> </build> <repositories> <repository> <id>maven2-repository.dev.java.net</id> <name>Java.net Repository for Maven</name> <url>http://download.java.net/maven/2</url> </repository> <repository> <id>JBoss repository</id> <url>http://repository.jboss.com/maven2/</url> </repository> <repository> <id>EclipseLink Repo</id> <url>http://www.eclipse.org/downloads/download.php?r=1&nf=1&file=/rt/eclipselink/maven.repo</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>primefaces-repo</id> <name>Prime Technology Maven Repository</name> <url>http://repository.primefaces.org</url> <layout>default</layout> </repository> </repositories> </project>
-
Remove the created index.jsp from src/main/webapp and add the directories for resources, WEB-INF and META-INF
mkdir -p src/main/resources mkdir -p src/main/webapp/WEB-INF mkdir -p src/main/resources/META-INF
-
Create an empty file named beans.xml in src/main/webapp/WEB-INF – we need it for CDI .. you’re asking why? Gavin King kindly gives an explanation.
touch src/main/webapp/WEB-INF/beans.xml
Entity creation using the Java Persistence API 2 / JSR-317
We want to save our blog entries in the persistence layer .. what we’re going to store is a title, the author, the date the entry was created and of course some text content.
-
First create a new class named BlogEntry in the package com.hascode.tutorial.jee6.blog.entity
package com.hascode.tutorial.jee6.blog.entity; import java.io.Serializable; import java.util.Date; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Lob; import javax.persistence.PrePersist; import javax.persistence.Temporal; import javax.persistence.TemporalType; @Entity public class BlogEntry implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; private String title; @Lob private String content; private String author; @Temporal(TemporalType.TIMESTAMP) private Date created = new Date(); @PrePersist private void onCreate() { created = new Date(); } /** * @param id * the id to set */ public void setId(Long id) { this.id = id; } /** * @return the title */ public String getTitle() { return title; } /** * @param title * the title to set */ public void setTitle(String title) { this.title = title; } /** * @return the content */ public String getContent() { return content; } /** * @param content * the content to set */ public void setContent(String content) { this.content = content; } /** * @return the author */ public String getAuthor() { return author; } /** * @param author * the author to set */ public void setAuthor(String author) { this.author = author; } /** * @return the created */ public Date getCreated() { return created; } /** * @param created * the created to set */ public void setCreated(Date created) { this.created = created; } /** * @return the id */ public Long getId() { return id; } }
So what have we done here?
-
The minimal set of annotations we need is @Entity and @Id - we have added those to the entity class and to the field id
-
We don’t want to set our primary key by hand so we’re adding the @GeneratedValue annotation to our id
-
The text from a blog entry might take some space so we’re predicting that with the @LoB annotation
-
We want to save the creation date of a blog entry as a timestamp and not a date – that’s why we add @Temporal(TemporalType.TIMESTAMP)
-
To set the creation date on our first persist we’re using @PrePersist
Now we need to define a persistence unit ..
-
First we create a new xml file named persistence.xml in src/main/resources/META-INF
-
We’re using EclipseLink/TopLink as PersistenceProvider
-
Because we’re lazy we’re going to use the GlassFish embedded database “default” via JNDI “jdbc/default”.SCREENSHOT
-
Out persistence unit is named “defaultPersistenceUnit“
-
We definitely want container managed transactions/CTM here that’s why we choose JTA as transaction-type
-
At last we want EclipseLink to create the tables for our entity so the value for the property eclipselink.ddl-generation is “create-tables“. You might want to change this for another persistence unit for integration tests later.
-
Finally the persistence.xml should look like this one
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.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_1_0.xsd"> <persistence-unit name="defaultPersistenceUnit" transaction-type="JTA"> <provider>oracle.toplink.essentials.PersistenceProvider</provider> <jta-data-source>jdbc/__default</jta-data-source> <class>com.hascode.tutorial.jee6.blog.entity.BlogEntry</class> <properties> <property name="eclipselink.ddl-generation" value="create-tables" /> </properties> </persistence-unit> </persistence>
Validation using Bean Validation / JSR-303
Now that we have defined our entities and persistence unit we should add some validation rules not to allow to save invalid blog entries.
For a closer look and more detailed information about bean validation and jsr-303 take a look at my article: “https://www.hascode.com/2010/12/bean-validation-with-jsr-303-and-hibernate-validator/[Bean Validation with JSR-303 and Hibernate Validator]”
The rules that we’re going to define are ..
-
The title must not be null and its length must be between 10 and 100 characters
-
The content must not be null and its length must be between 300 and 4000 characters
-
The author must not be null and its length must be between 10 and 40 characters
-
The creation date, created should lie in the past
-
Applying some annotations from the bean validation API like @NotNull, @Size, @Past our BlogEntry’s fields look like this
package com.hascode.tutorial.jee6.blog.entity; import java.io.Serializable; import java.util.Date; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Lob; import javax.persistence.PrePersist; import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.validation.constraints.NotNull; import javax.validation.constraints.Past; import javax.validation.constraints.Size; @Entity public class BlogEntry implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; @NotNull @Size(min = 10, max = 100) private String title; @Lob @NotNull @Size(min = 300, max = 4000) private String content; @NotNull @Size(min = 10, max = 40) private String author; @Past @Temporal(TemporalType.TIMESTAMP) private Date created = new Date(); @PrePersist private void onCreate() { created = new Date(); } /** * @param id * the id to set */ public void setId(Long id) { this.id = id; } /** * @return the title */ public String getTitle() { return title; } /** * @param title * the title to set */ public void setTitle(String title) { this.title = title; } /** * @return the content */ public String getContent() { return content; } /** * @param content * the content to set */ public void setContent(String content) { this.content = content; } /** * @return the author */ public String getAuthor() { return author; } /** * @param author * the author to set */ public void setAuthor(String author) { this.author = author; } /** * @return the created */ public Date getCreated() { return created; } /** * @param created * the created to set */ public void setCreated(Date created) { this.created = created; } /** * @return the id */ public Long getId() { return id; } }
The middle tier using Enterprise Java Beans / EJB 3.1 – JSR-318
We’re going to use a stateless session bean to provide methods to save, delete and list our blog entry entities. We don’t define any local or remote interfaces to keep it simple here.
-
Create a new package named com.hascode.tutorial.jee6.blog.ejb and a class named BlogEntryEJB
package com.hascode.tutorial.jee6.blog.ejb; import java.util.ArrayList; import java.util.List; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import com.hascode.tutorial.jee6.blog.entity.BlogEntry; @Stateless public class BlogEntryEJB { @PersistenceContext(unitName = "defaultPersistenceUnit") private EntityManager em; public BlogEntry saveBlogEntry(BlogEntry blogEntry) { em.persist(blogEntry); return blogEntry; } public List<BlogEntry> findBlogEntries() { final Query query = em.createQuery("SELECT b FROM BlogEntry b ORDER BY b.created DESC"); List<BlogEntry> entries = query.getResultList(); if (entries == null) { entries = new ArrayList<BlogEntry>(); } return entries; } public void deleteBlogEntry(BlogEntry blogEntry) { blogEntry = em.merge(blogEntry); em.remove(blogEntry); } }
-
The unit name that we’re using in @PersistenceContext should correspond to the defined named in our persistence.xml
Creating the presentation layer using Java Server Faces 2 and PrimeFaces
First we’re creating some facelets and decorators .. to keep this example simple we’re only creating a view to create new blog entries and a view that lists existing entries.
-
First we’re creating a decorator template named _decorator.xhtml in src/main/webapp
-
There are three fields in the decorator that may be overridden by our concrete views: the title, the heading and the body:
<!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:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"> <h:head> <title><ui:insert name="title">hasCode.com - Java EE 6 Blog Tutorial</ui:insert></title> <link rel="stylesheet" type="text/css" href="css/style.css" /> </h:head> <h:body> <p:panel> <h:panelGrid columns="2" cellpadding="10"> <img src="image/logo.png" width="100" height="100" alt="hasCode.com logo"/> <h:panelGroup> <h1><ui:insert name="heading">Java EE 6 Tutorial - Blog Application</ui:insert></h1> </h:panelGroup> </h:panelGrid> <ui:insert name="body">Welcome to the tutorial .. you should never see this content ;)</ui:insert> </p:panel> </h:body> </html>
-
The ugly logo image is saved in src/main/webapp/image, the cascading stylesheets in src/main/webapp/css - this is my style.css
h1 { font-size: 34px; } .errorMsg { background-color:#D97C7C; color:#A30000; display:block; padding:5px; }
-
Now we want to display available blog entries this is my view list.xhtml
<!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:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"> <ui:composition template="/_decorator.xhtml"> <ui:define name="title"> <h:outputText value="hasCode.com - Java EE 6 Blog Tutorial - Blog entries overview" /> </ui:define> <ui:define name="heading"> <h:outputText value="Blog entries overview" /> </ui:define> <ui:define name="body"> <h:form> <h:commandButton title="Create new article" value="Create new article" action="create" /> <br /> <hr /> <br /> <p:panel header="#{blogEntryBean.getBlogEntries().size()} Blog entries available" toggleable="true" closable="true" toggleSpeed="500"> <ui:repeat value="#{blogEntryBean.getBlogEntries()}" var="entry"> <h:panelGrid columns="2" cellpadding="10"> <f:facet name="header"> <h:outputText value="#{entry.title}" /> </f:facet> <h:outputText value="Author: #{entry.author}" /> <h:panelGroup id="pGroup"> <h:outputText value="#{entry.content}" /> <p:contextMenu for="pGroup"> <p:menuitem value="Delete" actionListener="#{blogEntryBean.delete(entry)}" update="@form" /> </p:contextMenu> </h:panelGroup> <f:facet name="footer"> <h:outputText value="Created: #{entry.created}" /> </f:facet> </h:panelGrid> <hr /> </ui:repeat> </p:panel> </h:form> </ui:define> </ui:composition> </html>
-
We’re filling the decorator using ui:composition and ui:repeat to iterate over a list of available blog entries
-
PrimeFaces gives us nice panels and ajax-delete via context menu, nice is the @form expression that forces the form to update its content
-
We should adjust our web.xml in src/main/webapp/WEB-INF/ to load JSF and display list.xhtml per default
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <display-name>jee6-blog-tutorial</display-name> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.xhtml</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>/list.xhtml</welcome-file> </welcome-file-list> </web-app>
-
Now we need a ManagedBean to link our views to the middle tier so we’re creating a class named BlogEntryBean in com.hascode.tutorial.jee6.blog.controller
package com.hascode.tutorial.jee6.blog.controller; import java.util.List; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; import com.hascode.tutorial.jee6.blog.ejb.BlogEntryEJB; import com.hascode.tutorial.jee6.blog.entity.BlogEntry; @Named(value = "blogEntryBean") @RequestScoped public class BlogEntryBean { @Inject private BlogEntryEJB blogEntryEJB; private BlogEntry blogEntry = new BlogEntry(); /** * @return the blogEntries */ public List<BlogEntry> getBlogEntries() { return blogEntryEJB.findBlogEntries(); } /** * @return the blogEntry */ public BlogEntry getBlogEntry() { return blogEntry; } /** * @param blogEntry * the blogEntry to set */ public void setBlogEntry(BlogEntry blogEntry) { this.blogEntry = blogEntry; } public String saveBlogEntry() { blogEntryEJB.saveBlogEntry(blogEntry); return "success"; } public void delete(BlogEntry blogEntry) { blogEntryEJB.deleteBlogEntry(blogEntry); } }
-
Please note that we’re using CDI for dependency injection here – that means @Named instead of @ManagedBean, @Inject instead of @EJB and javax.enterprise.context.RequestScoped instead of javax.faces.bean.RequestScoped!!
-
In the next step we should add some i18n and move our messages and text content to a resource bundle so create a____directory src/main/resources/com/hascode/tutorial/jee6/blog and a new file named messages.properties in this directory.
mkdir -p src/main/resources/com/hascode/tutorial/jee6/blog
-
Now we’ve got to register this resource bundle – we’re doing this via declaration in the JSF configuration file. Create a new file named faces-config.xml in src/main/webapp/WEB-INF and declare the resource bundle – in this case we’ve registered the bundle for variable named i18n
<?xml version="1.0" encoding="UTF-8"?> <faces-config version="2.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xi="http://www.w3.org/2001/XInclude" 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"> <application> <resource-bundle> <base-name>com.hascode.tutorial.jee6.blog.messages</base-name> <var>i18n</var> </resource-bundle> </application> </faces-config>
-
Now we’re able to replace our messages via
#{i18n.thekeyfromthebundle}
-
Finally out messages.properties looks like this
listTitle=hasCode.com - Java EE 6 Blog Tutorial - Blog entries overview listHeading=Blog entries overview
-
Our update facelet list.xhtml now looks like this
<!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:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"> <ui:composition template="/_decorator.xhtml"> <ui:define name="title"> <h:outputText value="#{i18n.listTitle}" /> </ui:define> <ui:define name="heading"> <h:outputText value="#{i18n.listHeading}" /> </ui:define> <ui:define name="body"> <h:form> <h:commandButton title="#{i18n.newArticle}" value="#{i18n.newArticle}" action="create" /> <br /> <hr /> <br /> <p:panel header="#{blogEntryBean.getBlogEntries().size()} #{i18n.amountEntries}" toggleable="true" closable="true" toggleSpeed="500"> <ui:repeat value="#{blogEntryBean.getBlogEntries()}" var="entry"> <h:panelGrid columns="2" cellpadding="10"> <f:facet name="header"> <h:outputText value="#{entry.title}" /> </f:facet> <h:outputText value="#{i18n.author}: #{entry.author}" /> <h:panelGroup id="pGroup"> <h:outputText value="#{entry.content}" /> <p:contextMenu for="pGroup"> <p:menuitem value="#{i18n.delete}" actionListener="#{blogEntryBean.delete(entry)}" update="@form" /> </p:contextMenu> </h:panelGroup> <f:facet name="footer"> <h:outputText value="#{i18n.created}: #{entry.created}" /> </f:facet> </h:panelGrid> <hr /> </ui:repeat> </p:panel> </h:form> </ui:define> </ui:composition> </html>
Adding JSF Navigation Rules
We could have managed our user actions completely using nice PrimeFaces AJAX-enriched components or JSF’s native AJAX API but to demonstrate JSF’s navigation rules we’re going to define a new view with a form to create a new blog entry. If the blog entry has been successfully saved using this form we want to redirect our user to the entries-list-overview and the URL should change to avoid posting duplicate content by refreshing in the browser.
-
First we’re creating the new view reusing our decorator .. add a new template named create.xhtml in src/main/webapp
<!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:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"> <ui:composition template="/_decorator.xhtml"> <ui:define name="title">#{i18n.createTitle}</ui:define> <ui:define name="heading">#{i18n.createHeading}</ui:define> <ui:define name="body"> <h:form> <p:panel id="createPnl" header="#{i18n.newArticle}" toggleable="true" closable="false" toggleSpeed="500" onCloseUpdate="growl" closeSpeed="2000" onToggleUpdate="growl" widgetVar="panel"> <h:panelGrid columns="3" cellpadding="10"> <label>#{i18n.title}</label> <h:inputText label="Title" id="lblTitle" value="#{blogEntryBean.blogEntry.title}" required="true" /> <h:message for="lblTitle" class="errorMsg"/> <label>#{i18n.author}</label> <h:inputText label="Author" id="lblAuthor" value="#{blogEntryBean.blogEntry.author}" required="true" /> <h:message for="lblAuthor" class="errorMsg"/> <label>#{i18n.content}</label> <h:inputTextarea label="Content" id="lblContent" value="#{blogEntryBean.blogEntry.content}" required="true" /> <h:message for="lblContent" class="errorMsg"/> <h:commandButton title="#{i18n.save}" value="#{i18n.save}" action="#{blogEntryBean.saveBlogEntry()}" /> </h:panelGrid> </p:panel> </h:form> </ui:define> </ui:composition> </html>
-
We need to update our internationalized messages in our messages.properties
listTitle=hasCode.com - Java EE 6 Blog Tutorial - Blog entries overview listHeading=Blog entries overview newArticle=Create new blog entry amountEntries=Blog entries available author=Author options=Options delete=Delete created=Created createTitle=hasCode.com - Java EE 6 Blog Tutorial - Create a new blog entry createHeading=Create a new blog entry title=Title content=Content save=Save article
-
Add navigation rules to your faces-config.xml in src/main/webapp/WEB-INF .. mine now looks like this one
<?xml version="1.0" encoding="UTF-8"?> <faces-config version="2.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xi="http://www.w3.org/2001/XInclude" 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"> <application> <resource-bundle> <base-name>com.hascode.tutorial.jee6.blog.messages</base-name> <var>i18n</var> </resource-bundle> </application> <navigation-rule> <from-view-id>/list.xhtml</from-view-id> <navigation-case> <from-outcome>create</from-outcome> <to-view-id>/create.xhtml</to-view-id> </navigation-case> </navigation-rule> <navigation-rule> <from-view-id>/create.xhtml</from-view-id> <navigation-case> <from-outcome>success</from-outcome> <to-view-id>/list.xhtml</to-view-id> <redirect/> </navigation-case> </navigation-rule> </faces-config>
-
The redirect-Tag in the navigation-case forces the a redirect to list.xhtml
GlassFish Configuration
The GlassFish setup is quite easy we simply need a new domain and the embedded database because we’re too lazy to connect or create an external database ..
-
Start the GlassFish console via
asadmin
-
Create a new domain named blog-domain
asadmin> create-domain blog-domain Enter admin user name [Enter to accept default "admin" / no password]> Using port 4848 for Admin. Using default port 8080 for HTTP Instance. Using default port 7676 for JMS. Using default port 3700 for IIOP. Using default port 8181 for HTTP_SSL. Using default port 3820 for IIOP_SSL. Using default port 3920 for IIOP_MUTUALAUTH. Using default port 8686 for JMX_ADMIN. Using default port 6666 for OSGI_SHELL. Distinguished Name of the self-signed X.509 Server Certificate is: [CN=yourworkstationname,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US] No domain initializers found, bypassing customization step Domain blog-domain created. Domain blog-domain admin port is 4848. Domain blog-domain allows admin login as user "admin" with no password. Command create-domain executed successfully.
-
Start the created domain
asadmin> start-domain blog-domain Waiting for DAS to start ................................. Started domain: blog-domain Domain location: /somepath/app/glassfishv3/glassfish/domains/blog-domain Log file: /somepath/app/glassfishv3/glassfish/domains/blog-domain/logs/server.log Admin port for the domain: 4848 Command start-domain executed successfully.
-
Start the embedded Derby/JavaDB database
asadmin> start-database Starting database in Network Server mode on host 0.0.0.0 and port 1527. [..] ------------------------------------------------------ Starting database in the background. Log redirected to /somepath/derby.log. Command start-database executed successfully.
-
Visit the administration console in your browser at http://localhost:4848/ and enjoy ;)
Deploying and Running the Application
-
First build the war file for deployment using
mvn package
-
Ensure that your GlassFish server is running, the database is started and the domain “blog-domain” is created and started
-
Log into the GlassFish administration console at http://localhost:4848/ with login “admin” and empty password if you haven’t defined one in the domain creation process
-
Click on “Applications” and deploy the application via file upload as shown as in the following screenshots
Figure 2. GlassFish Application Dep loyment Step 1Figure 3. GlassFish Application Deployment Step 2Figure 4. GlassFish Application Deployment Step 3GlassFish Application Deployment Step 4 -
Now click on "Launch" and visit your application at this location: http://localhost:8080/blog-tutorial
Download Sources
Troubleshooting
-
“Exception while preparing the app : org.hibernate.AnnotationException: No identifier missing @Id attribute“: You’ve forgotten to specify an identifying property for your entity class .. e.g.: @Id private Long id;
-
“Exception [EclipseLink-4002] (Eclipse Persistence Services – 2.0.1.v20100213-r6600): org.eclipse.persistence.exceptions.DatabaseException Internal Exception: java.sql.SQLNonTransientConnectionException: No current connection. java.sql.SQLNonTransientConnectionException: No current connection. org.apache.derby.client.am.SqlException: No current connection.“: In this tutorial we’re lazy and rely on GlassFish’s embedded database. To use this database it must be started first as this
asadmin asadmin> start-database Starting database in Network Server mode on host 0.0.0.0 and port 1527.
-
“[#|2011-01-16T17:47:47.364+0100|WARNING|glassfish3.0.1|javax.enterprise.system.container.web.com.sun.enterprise.web|_ThreadID=30;_ThreadName=Thread-1;|StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception javax.el.PropertyNotFoundExceptionor: CDI does not work somehow“: You need to put an empty text file named beans.xml in src/main/webapp/WEB-INF – strange but necessary ;) For more information take a look at Gavin King’s blog article: “http://relation.to/Bloggers/WhyIsBeansxmlRequiredInCDI[Why is beans.xml required in CDI?]“
-
“Caused by: java.lang.IllegalArgumentException: Entity must be managed to call remove: com.hascode.tutorial.jee6.blog.entity.BlogEntry@184340e, try merging the detached and try the remove again. at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.performRemove(UnitOfWorkImpl.java:3539)“: This is the standard detached object problem .. in JPA there are four possible object states .. new, removed, managed and of course detached .. the last one has a persistent identity that no longer is associated with a persistence context. EntityManager’s merge method is what helps you to “reattach” the bean .. look at this sample code
public void theMethod(XXX entityBean) { entityBean = em.merge(entityBean); em.remove(entityBean); }
-
“PrimeFaces is not defined“: There was an issue with some menu components in PrimeFaces 2.2.RC1, Cagatay Civici has already fixed this one in version 2.2.RC2 and above.
-
“Duplicate menuitem in contextmenu“: Again Cagatay Civici saved the day and fixed this issue, update to version 2.2-SNAPSHOT or above.
-
“Plugin execution not covered by lifecycle configuration: org.apache.maven.plugins:maven-dependency-plugin:2.1:copy (execution: default, phase: validate)” – It’s the m2eclipse plugin .. why does this error occur? They’re explaining it in this article .. for now add the following code to your pom.xml and update your project configuration
<pluginManagement> <plugins> <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.--> <plugin> <groupId>org.eclipse.m2e</groupId> <artifactId>lifecycle-mapping</artifactId> <version>1.0.0</version> <configuration> <lifecycleMappingMetadata> <pluginExecutions> <pluginExecution> <pluginExecutionFilter> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <versionRange>[2.1,)</versionRange> <goals> <goal>copy</goal> </goals> </pluginExecutionFilter> <action> <ignore></ignore> </action> </pluginExecution> </pluginExecutions> </lifecycleMappingMetadata> </configuration> </plugin> </plugins> </pluginManagement>
Additional Articles: Testing with Arquillian
If you’re interested in testing your Java EE application , please feel free to have a look at the following tutorials of mine covering the Arquillian framework:
-
Arquillian Tutorial: Writing Java EE 6 Integration Tests and more..
-
Arquillian Transaction Extension: Transaction Rollback for your Java EE Integration Tests
-
Java EE: Setting up and Testing Form-Based JDBC Authentication with Arquillian and Maven
-
Marrying Java EE and BDD with Cucumber, Arquillian and Cukespace
Article Updates
-
2018-06-01: Embedded YouTube video removed (GDPR/DSGVO).
-
2015-03-21: Links to my Arquillian articles added, formatting fixed.