Devoxx 2010 impressions – day 1

Last week I enjoyed 2 great University days at Devoxx. In this blog post i’ll comment on the 2 main talks i’ve visited on monday, i.e. Seam 3 and Spring Roo. Both talks had a lot in common.
In the past both were JEE frameworks which tried to fill a gap in traditional JEE development. The Spring framework simplified for one the whole entity bean fiasco of EJB2 by introducing a model layer based on POJOs and was typically a framework used for integration in the model layer of enterprise development. Springs lessons led to the EJB3 spec in JEE5 and the Managed Bean spec in JEE6.
Seam on the other hand filled the gap of integrating the view and the model layer, making it much easier to couple JSF pages to Session Beans (or their Seam counterparts). Its lessons have led to the CDI spec in JEE6 and (i think) to the EJB Lite spec.
Now, while both were considered a framework in the past, they are now in their latest incarnations both full blown stacks for Java Enterprise Development. And in this they have a lot in common. To name a few:

  • Both have their own (eclipse-)based IDE. For Spring it’s called SpringSource Tool Suite (STS), for Seam we have JBoss Developer Studio;
  • Both have solutions for cross layer concerns, like security, logging, SOAP/REST integration, event handling and AJAX support, to name a few;
  • Both have a solution for building the view layer. Spring has Spring MVC and Seam uses JSF, CDI and facelets;
  • And now both have their own RAD solution. In Spring it’s called SpringRoo, in Seam it’s called SeamForge.

The last 2 components of the stacks are (especially SeamForge) relatively new. Both are shell based and can also be used in Eclipse as a plugin (Of course, their own IDE’s have these plugins installed by default).

I think it’s a good thing that these stacks are emerging. In the end they will provide you with basically everything you need for Enterprise development. And in the end hopefully it will prevent all these hybrid solutions where everyone uses their favorite framework on the job, which hinders maintainability in the long run imho. The SpringSource stack and the JBoss stack are both big players and i honestly believe that commitment to these stacks by the development community will be an ongoing concern for many years to come.
If I had to pick a favorite among the two. I slightly prefer the JBoss stack, because it adheres much more to the JEE specification.

In the next 2 section i’ll summarize some highlights of the 2 talks.

Seam 3: State of the Union

by Pete Muir and Dan Allen

Seam 3 as apposed to the rather monolithic Seam 2 framework is of a modular design. It consists of a set of modules built on top of the core of Seam. This core consists of the reference implementation of CDI, Weld, which currently is supported for the JEE servers JBoss and Glassfish, and for the Web servers Tomcat and Jetty. It is also supported for a standalone JSE environment. Seam 3 is hosted at GitHub and uses Maven for the build lifecycle. There was also talk about the use of Gradle in the near future.

As i sad before. Seam’s 3 approach is more modular than that of its predecessor. Here’s a list of the modules that were presented during the talk:

  • Seam Solder, aka Weld extensions. This is the core of Seam 3 and Pete and Dan stressed that this is the module everyone should use. It provides to name but a few:
    • A lot of familiar Seam 2 annotations (eg. @Requires, @Unwraps);
    • Annotations for Logging;
    • Annotations for Resource handling;
    • Annotations to be used for queries in DAO’s;
  • SeamConfig, which extends the beans.xml configuration file for Seam related configurations in xml-style;
  • SeamInternational, for i18n an l10n support;
  • SeamFaces, which is an enhancement for JSF 2. It contains, to name a few, the familiar (Seam 2) @Conversation scope, support for multiple field validation, composite components and a simple front controller called ViewAction;
  • SeamPersistence, the module for declarative persistence, transactions, manual flushing and the likes;
  • SeamCath for error and event handling;
  • SeamServlet, which provides Servlet enhancements;
  • SeamRest, for REST integration;
  • SeamGWT, for GWT integration.

And already mentioned a big part of the presentation was reserved for a demonstration of the current state of the SeamForge project. It’s the successor to seam-gen. You can use it for RAD. It can generate an entire CRUD application out of the box based on a couple of simple database tables or defined entities. It is shell based and also comes as an IDE (Eclipse) plugin.  I was really impressed by the demo. As with seam-gen, you don’t have to mess with all the configuration files anymore, because you can let SeamForge do this for you. In no time you can generate a fully working demo application for your customers or one to be used as a reference for yourself for development. And SeamForge does this all in Maven style, which is big plus compared to the ANT style seam-gen, if i may say so. I can’t wait to try it all out for myself.

Another big topic was the Arquillian project. It contains for one an embedded container used for testing an enterprise application. Arquillian can be integrated in a testing framework like JUNIT or TestNG. Control of the container is handled via ANT tasks or a Maven plugin.

Extreme Productivity with Spring Roo

by Ben Alex and Stefan Schmidt

Spring Roo is a project within the SpringSource stack comparable to SeamForge. You can generate a CRUD application with it in no time in the Spring style. Like SeamForge, SpringRoo generates a Maven application, it can be controlled via highly intelligent shell commands and also comes with an Eclipse plugin.

It makes heavy use of AspectJ files. For example it puts the getter and setter methods of the generated JPA classes in a seperate AspectJ file per class. This keeps the JPA classes very clean. Another benefit is that the AspectJ classes can be generated (or even incrementally updated) so they can be omitted from the version control system. If you have the AspectJ plugin installed in Eclipse, code completion will work even for the code in the AspectJ files like those getter and setter methods. Of course STS comes with this plugin already installed.

The JPA classes that are generated by SpringRoo contain JPA 2 annotations and a couple of SpringRoo specific annotations.

Removing all of SpringRoo from your application can be done in three simple steps:

  1. Refactor the AspectJ code back in your Java code (can be done via the AspectJ plugin);
  2. Remove The SpringRoo annotations (search/delete);
  3. Remove the SpringRoo jar from you pom.xml.

Another part of the presentation showed the use of the Hades plugin for SpringRoo. With Hades you can generate a generic DAO layer on top of your JPA layer. Very powerful stuff.

The last big part of the presentation showed the view layer generation capabilities of SpringRoo. SpringRoo lets you generate a Spring MVC layer and takes care of a lot of scaffolding for you, again removing you of the burden of getting all those configuration files right. It eg. can take care of:

  • REST support
  • Templating support
  • Themes
  • i18n, l10n
  • Bean validation
  • Security
  • Spring WebFlow

All in all, this was the second time this day i was pretty impressed, and again I can’t wait to play with this stuff.

Advertisements

Setup a local smtp server for JBoss

In my Seam application I needed a local smtp server for testing the email facilities Seam offers. After some research on the internet I came up with a configuration which uses Ubuntu’s Mail Transfer Agent postfix with Gmail as the so called smarthost. Postfix will act as an intermediate smtp server which will dispatch the mails it receives to Gmail‘s smtp server.
This blog will summarize the steps needed for this setup.

Postfix installation and configuration

First, let’s install postfix.

sudo apt-get install postfix

Next, add additional configuration.

sudo dpkg-reconfigure postfix

Choose the following settings. For settings not listed below, you can keep the default settings.

  • General type of mail configuration: Internet with smarthost
  • SMTP relay host: smtp.gmail.com:587
  • Internet protocols to use: all

Next add settings necessary for authentication against the Gmail smtp server.

sudo postconf -e 'smtp_use_tls = yes'
sudo postconf -e 'smtp_sasl_auth_enable = yes'
sudo postconf -e 'smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd'
sudo postconf -e 'smtp_sasl_security_options = noanonymous'
sudo postconf -e 'smtp_sasl_tls_security_options = noanonymous'

Next add the password file /etc/postfix/sasl_passwd containing the username and password of your Gmail account.

smtp.gmail.com username@gmail.com:PASSWORD

Now, hash the password file (this will create the hashed sasl_passwd.db file), throw away the original file and restart the postfix daemon.

sudo postmap /etc/postfix/sasl_passwd
sudo rm /etc/postfix/sasl_passwd
sudo /etc/init.d/postfix restart

Testing postfix

You can now test the postfix configuration via telnet.

telnet localhost 25
>mail from:noreply@gmail.com
>rcpt to:username@gmail.com
>data
>Testmail
>.
>quit

This should send a small email to username@gmail.com.

Troubleshooting

If the mail didn’t arrive, you can check a couple of things:
Check if it’s in the postfix queue:

sudo mailq

Flush the queue:

sudo postfix -f

Check the log file:

sudo tail /var/log/mail.log

JBoss configuration

Next we’re gonna add the mail server configuration to JBoss, so it van be accessed via JNDI. Edit the file $JBOSS_HOME/server/default/deploy/mail-service.xml.

<?xml version="1.0" encoding="UTF-8"?>
<server>
  <mbean code="org.jboss.mail.MailService"
         name="jboss:service=Mail">
    <attribute name="JNDIName">java:/Mail</attribute>
    <attribute name="Configuration">
      <configuration>
        <!-- Change to your mail server prototocol -->
        <property name="mail.transport.protocol" value="smtp"/>
        <!-- Change to the SMTP gateway server -->
        <property name="mail.smtp.host" value="localhost"/>
        <!-- The mail server port -->
        <property name="mail.smtp.port" value="25"/>
        <!-- Change to the address mail will be from  -->
        <property name="mail.from" value="noreply@gmail.com"/>
        <!-- Enable debugging output from the javamail classes -->
        <property name="mail.debug" value="false"/>
      </configuration>
    </attribute>
    <depends>jboss:service=Naming</depends>
  </mbean>
</server>

Seam configuration

Finally we need to configure seam so it can communicate with our new smtp server. This is done in the component descriptor components.xml.

<?xml version="1.0" encoding="UTF-8"?>
<components
  xmlns="http://jboss.com/products/seam/components"
  xmlns:mail="http://jboss.com/products/seam/mail"
  xsi:schemaLocation="http://jboss.com/products/seam/mail http://jboss.com/products/seam/mail-2.2.xsd
    http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd">
  ....
  <mail:mail-session session-jndi-name="java:/Mail" />
  ....
</components>

If you’re also using jBPM for business processes within your seam application and need email facilities, you can configure the smtp server in the jBPM configuration file.

<?xml version="1.0" encoding="UTF-8"?>
<jbpm-configuration>
  ....
  <string name="jbpm.mail.smtp.host" value="localhost"/>
  <string name="jbpm.mail.from.address" value="noreply@open18.org"/>
</jbpm-configuration>

Autosuggesting in Seam

Today I implemented an autosuggest textfield in a search page. Although technically not a Seam but a Richfaces feature, I baked it into a default Seam page. I added it to a default Seam search page backed up by an EntityQuery<E> subclass. Below you’ll find a screenshot of the Search Form containing the autosuggest field. It’s a search form for querying the collection of golfers in the database.

Autosuggest Search Form

Let’s take a walk through the most important code to get this example working.

Model classes

The golfer data in the database is contained in 2 tables MEMBER and GOLFER. These are mapped to 2 JPA classes called org.open18.model.Member and org.open18.model.Golfer respectively.

org.open18.model.Member

package org.open18.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "MEMBER", uniqueConstraints = { @UniqueConstraint(columnNames = "username") })
public abstract class Member implements Serializable {
	private Long id;
	private String userName;

	@Id
	@GeneratedValue
	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	@Column(name = "username", nullable = false)
	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

}

org.open18.model.Golfer

package org.open18.model;

package org.open18.model;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.jboss.seam.annotations.Name;

@Entity
@PrimaryKeyJoinColumn(name = "MEMBER_ID")
@Table(name = "GOLFER")
@Name("golfer")
public class Golfer extends Member {
	private String firstName;
	private String lastName;
	private Date dateJoined;

	@Column(name = "last_name", nullable = false)
	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Column(name = "first_name", nullable = false)
	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	@Transient
	public String getName() {
		return (firstName + ' ' + lastName);
	}

	@Temporal(TemporalType.TIMESTAMP)
	@Column(name = "joined", nullable = false, updatable = false)
	public Date getDateJoined() {
		return dateJoined;
	}

	public void setDateJoined(Date dateJoined) {
		this.dateJoined = dateJoined;
	}

}

Action class

The page is backed up by the action class org.open18.action.GolferList. This class contains an action method for providing the data for the autosuggest field.

org.open18.action.GolferList

package org.open18.action;

import java.util.Arrays;
import java.util.List;

import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.framework.EntityQuery;
import org.open18.model.Golfer;

@Name("golferList")
public class GolferList extends EntityQuery<Golfer> {
	private static final String EJBQL = "select golfer from Golfer golfer";

	private static final String[] RESTRICTIONS = { "lower(golfer.lastName) like lower(concat(#{golferList.golfer.lastName},'%'))", };

	private Golfer golfer = new Golfer();

	public GolferList() {
		setEjbql(EJBQL);
		setRestrictionExpressionStrings(Arrays.asList(RESTRICTIONS));
		setMaxResults(25);
	}

	public Golfer getGolfer() {
		return golfer;
	}

	@SuppressWarnings("unchecked")
	@Transactional
	public List<Golfer> autocomplete(Object suggest) {

		String pref = (String) suggest + "%";

		List<Golfer> golferSuggestList;
		golferSuggestList = getEntityManager()
				.createQuery(
						"select golfer from Golfer golfer where golfer.lastName like :golfer")
				.setParameter("golfer", pref).getResultList();

		return golferSuggestList;

	}
}

Page descriptor

The page descriptor contains page parameters for storing some search and navigation criteria.

golferList.page.xml

<?xml version="1.0" encoding="UTF-8"?>
<page xmlns="http://jboss.com/products/seam/pages" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.2.xsd">

  <param name="from" />

  <param name="lastName" value="#{golferList.golfer.lastName}" />

</page>

Page

Finally the page itself. After the code, I’ll explain some of the elements/attributes on the page.

GolferList.xhtml

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
  xmlns:s="http://jboss.com/products/seam/taglib"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:a="http://richfaces.org/a4j"
  xmlns:rich="http://richfaces.org/rich"
  template="layout/template.xhtml">

  <ui:define name="body">

    <h:form id="courseSearch" styleClass="edit">

      <rich:simpleTogglePanel label="Course Search Filter"
        switchType="ajax">

        <s:decorate template="layout/display.xhtml">
          <ui:define name="label">Last Name</ui:define>
          <h:inputText id="lastName"
            value="#{golferList.golfer.lastName}">
          </h:inputText>
          <rich:suggestionbox ajaxSingle="true" for="lastName"
            var="_golfer" minChars="2"
            suggestionAction="#{golferList.autocomplete}">
            <h:column>
              <h:outputText value="#{_golfer.lastName}" />
            </h:column>
            <a:support event="onselect" reRender="golferSearchResults" />
          </rich:suggestionbox>
        </s:decorate>

      </rich:simpleTogglePanel>

      <div class="actionButtons">
        <h:commandButton id="search"
          value="Search" action="/golferList.xhtml">
        </h:commandButton> <s:button
          id="reset" value="Reset" includePageParams="false" />
      </div>
    </h:form>

    <rich:panel id="golferSearchResults">
      <f:facet name="header">Golfers</f:facet>

      <div class="results">
        <h:outputText
          value="There are no golfers registered."
          rendered="#{empty golferList.resultList}" />
        <rich:dataTable
          id="golferList" var="_golfer" value="#{golferList.resultList}"
          rendered="#{not empty golferList.resultList}">
          <h:column>
            <f:facet name="header">Username</f:facet>
            <s:link id="view" value="#{_golfer.userName}"
              view="/profile.xhtml" propagation="none">
              <f:param name="golferId" value="#{_golfer.id}" />
            </s:link>
          </h:column>
          <h:column>
            <f:facet name="header">Name</f:facet>
            #{_golfer.name}
          </h:column>
          <h:column>
            <f:facet name="header">Date Joined</f:facet>
            <h:outputText value="#{_golfer.dateJoined}">
              <s:convertDateTime pattern="MMMM dd, yyyy" />
            </h:outputText>
          </h:column>
        </rich:dataTable>
      </div>

    </rich:panel>

  </ui:define>

</ui:composition>
  • layout/template.xhtml, layout/display.xhtml: these are standard facelets generated by seam-gen;
  • rich:suggestionbox: this element will take care of the suggestion data for the input textfield
  • for=”lastName”: points to the if of the h:inputText component for which the autosuggest data will be shown;
  • minChars=”2″: After 2 characters are entered the suggestion list will appear
  • suggestionAction=”#{golferList.autocomplete}”: Calls the autocomplete action method on the GolferList action class, which will return a list of golfers;
  • h:column: The result will be shown as one column and will contain a suggestion list of the golfers last names via the h:outputText component;
  • a:support: This element takes care of rerendering the golferSearchResults panel when a user has selected a last name in the suggestion list.
  • Performance note

    Hitting the database everytime a user is rumbling in the autosuggest field is probably not the best solution. As an alternative you could create a page scoped factory which captures the autosuggest data in a List<Golfer> variable. This factory could then be injected into the golferList component. In this case the autosuggest query will only be executed once per page visit. See the scriptlets below.

    components.xml

    	....
    	<framework:entity-query name="golferSuggestQuery"
    		ejbql="select golfer from Golfer golfer" >
    	</framework:entity-query>
    	<factory name="golferSuggest" value="#{golferSuggestQuery.resultList}" scope="page" />
    	....
    

    org.open18.action.GolferList

    @Name("golferList")
    public class GolferList extends EntityQuery<Golfer> {
    	@In(create=true,value="golferSuggest")
    	List<Golfer> golferSuggest;
    	....
    	public List<Golfer> autocomplete(Object suggest) {
    		String pref = (String) suggest;
    
    		List<Golfer> golferSuggestList = new ArrayList<Golfer>(0);
    
    		for (Golfer g : golferSuggest) {
    			if (g.getLastName().startsWith(pref)) {
    				golferSuggestList.add(g);
    			}
    		}
    
    		return golferSuggestList;
    	}
    	....
    }
    

    Starting a jBPM pageflow

    This week i struggled a bit to get a jBPM pageflow running from my seam-gen application. It’s one of the examples in the Seam in Action book from Manning. The goal is to kick off a wizard containing a couple of screens for entering a new entity (in this case a golf course) in the database. As the wizard adds a new record in the database, one of the requirements in the application is that the user doing this is logged in.

    There are basically 2 ways to accomplish this. You can start the pageflow in the first page of the wizard during its Render Response phase by making use of a Seam factory. Your other option is to kick off the pageflow from an action method. The second method seems the more natural to me and is the one that gave me some headaches. I’ll shortly describe both the options and provide a simple solution to the issues regarding the second option.

    Example

    A golf facility can contain multiple golf courses. To add a golf course to a facility you can launch a course wizard which is linked to a jBPM pageflow.

    Launching the course wizard from the Facility page.

    In this flow we have a facility page /Facility.xhtml which is bound to the Seam component facilityHome and the first page of the course wizard /coursewizard/BasicCourseInfo.xhtml which is bound to the Seam components courseWizard and course (which contains the actual course being added). The pageflow is defined in the jPDL file courseWizard-pageflow.jpdl.xml.

    Adding a course to the database requires a user that’s logged in. This restriction is applied in the general page descriptor pages.xml by the following XML stanza

    <page view-id="/coursewizard/*" login-required="true"/>
    

    Starting the wizard on its first page

    In this option, the Add Course… button on the facility page contains navigation to the first page of the wizard and passes the id of the current facility:

    <s:button id="courseWizard" value="Add course..."
     propagation="none" view="/coursewizard/BasicCourseInfo.xhtml">
    	<f:param name="facilityId" value="#{facilityHome.instance.id}" />
    </s:button>
    

    The input components on the /coursewizard/BasicCourseInfo.xhtml page are all bound to a conversation scoped Seam component named course. This component is created by a factory in the courseWizard component. The facility is weaved in by a request parameter:

    @Name("courseWizard")
    @Scope(ScopeType.CONVERSATION)
    public class CourseWizard implements Serializable {
        @In
        protected EntityManager entityManager;
        @RequestParameter
        protected Long facilityId;
        @Out
        protected Course course;
        ....
    
        @Begin(pageflow = "Course Wizard", flushMode = FlushModeType.MANUAL)
        @Factory("course")
        public void initCourse() {
            course = new Course();
            course.setFacility(entityManager.find(Facility.class, facilityId));
            // setup some defaults
            course.setNumHoles(18);
            course.setFairways("BENT");
            course.setGreens("BENT");
        }
        ....
    }
    

    Finally the pageflow is started in the jPDL file

    <?xml version="1.0" encoding="UTF-8"?>
    <pageflow-definition xmlns="http://jboss.com/products/seam/pageflow"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://jboss.com/products/seam/pageflow http://jboss.com/products/seam/pageflow-2.0.xsd"
    	name="Course Wizard">
    	<start-page name="basicCourseInfo" view-id="/coursewizard/basicCourseInfo.xhtml"
    		no-conversation-view-id="/CourseList.xhtml">
    		<redirect />
    		<transition name="cancel" to="cancel" />
    		<transition name="next" to="description" />
    	</start-page>
    	....
    </pageflow-definition>
    

    And that’s all there is to it. You click on the Add course… button, the login screen appears (if you’re not logged in) and after successfully logging into the application the first page of the course wizard appears.

    Starting the wizard from an action method

    Now for this second option the Add course… button will be coupled to an action method which will launch the pageflow.
    Once again the code behind the button, which now contains the action method but doesn’t contain navigation anymore. The facilityid parameter isn’t needed anymore as it’s weaved in before the wizard is launched.

    <s:button id="courseWizard" value="Add course..."
    	action="#{courseWizard.addCourse}" propagation="none">
    </s:button>
    

    Now the action method will take care of launching the pageflow. It also weaves the facility into the course to be created.

    
    @Name("courseWizard")
    @Scope(ScopeType.CONVERSATION)
    public class CourseWizard implements Serializable {
    
    	@In
    	protected EntityManager entityManager;
    	@RequestParameter
    	protected Long facilityId;
    	@Out
    	protected Course course;
    	....
    
    	@Begin(join = true, pageflow = "Course Wizard", flushMode = FlushModeType.MANUAL)
    	public void addCourse() {
    		course = new Course();
    		course.setFacility(entityManager.find(Facility.class, facilityId));
    		// setup some defaults
    		course.setNumHoles(18);
    		course.setFairways("BENT");
    		course.setGreens("BENT");
    	}
    	....
    }
    

    Because you start the pageflow from an action method, you have to define a start-state element in your jPDL file as opposed to the start-page element in the previous example.

    <?xml version="1.0" encoding="UTF-8"?>
    <pageflow-definition xmlns="http://jboss.com/products/seam/pageflow"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://jboss.com/products/seam/pageflow http://jboss.com/products/seam/pageflow-2.0.xsd"
    	name="Course Wizard">
    	<start-state>
    		<transition to="basicCourseInfo" />
    	</start-state>
    
    	<page name="basicCourseInfo" view-id="/coursewizard/basicCourseInfo.xhtml"
    		no-conversation-view-id="/CourseList.xhtml">
    		<redirect />
    		<transition name="cancel" to="cancel" />
    		<transition name="next" to="description" />
    	</page>
    	....
    </pageflow-definition>
    

    Now, when clicking the Add course… button when you’re not logged in yet, the login screen will NOT be displayed. Instead you’ll get the following error when using firefox

    The page isn't redirecting properly
    Firefox has detected that the server is redirecting the request for
    this address in a way that will never complete.
    

    Redirect error

    If  firebug is enabled, you can see that the system gets into some sort of redirect loop. I tried a lot of things to fix this issue and eventually stumbled upon a solution which is really very simple. You just have to make sure that the user is logged in before the pageflow gets called. This is done by securing the action method which calls the pageflow. Just add a Restrict annotation.

    	@Begin(join = true, pageflow = "Course Wizard", flushMode = FlushModeType.MANUAL)
    	@Restrict("#{identity.loggedIn}")
    	public void addCourse() {
    		course = new Course();
    		course.setFacility(entityManager.find(Facility.class, facilityId));
    		// setup some defaults
    		course.setNumHoles(18);
    		course.setFairways("BENT");
    		course.setGreens("BENT");
    	}
    

    And that’s it. Now the solution with the action method will behave exactly the same as the solution without it. You click the Add course… button, the login screen appears and after successfully logging in, the first page of the course wizard appears.

    Seam-gen and groovy

    I recently added a groovy action class to my seam-gen project. After a while eclipse’s auto-build kept giving me these misleading ant errors in the groovy.compilemodel task:

    groovyc doesn't support the "srcdir" attribute
    

    It took me a while to fix this issue. When i ran the compile target manually there was no problem, so eventually i figured it had to do with the explode launcher that was generated by seam-gen. You need to let it run in a separate JVM.
    First, right-click on the project and select “Properties”.

    Properties for open18

    Next, select “Builders” and Edit the explode builder.


    Edit Configuration

    Edit configuration

    Finally, click the “JRE” tab en select “Separate JRE” as the runtime JRE.

    After this the auto-build error should be gone.

    Adding Maven to seam-gen

    Recently I changed a seam-gen generated project into a maven project. It took me a while to get it all up and running. In this blog I’ll try to summarize all the steps needed to convert a seam-gen app (the open18 app I created in the previous blogs) into a full blown Maven project called open18_mvn2. I took the liberty to stretch the Maven pom to support deployment to glassfish as well as jboss.

    1. Prerequisites

    Above is the list with prerequisites. Most of them are taken care of when you’ve followed all the steps in the previous blog series “Seam-gen on glassfish”.

    • You’ve created the seam-gen project open18;
    • You’ll have a mysql database up and running in which resides the open18 catalog with application data.
    • You’ll have a glassfish installation available in ~/opt/glassfishv2;
      • The necessary hibernate and slf4j/log4j libraries and the mysql-connector-java-5.1.12-bin.jar file are all available in the <glassfish_home>/domains/domain1/lib/ext directory. This can be done via the ant task gf-prepare of the seam-gen app;
      • A connection pool (open18Pool) together with a datasource (open18Datasource) for connecting to the mysql open18 catalog is available in the glassfish server. This datasource can be installed on the server via the ant task gf-deploy-datasource of the seam-gen app;
    • For this blog I added a jboss server installation and put it in ~/opt/jboss-5.1.0.GA;
      • See the next section for installation details;
      • The mysql driver mysql-connector-java-5.1.12-bin.jar is available in the <jboss_home>/server/default/lib directory;
      • A datasource file open18-ds.xml is available in the jboss server directory <jboss_home>/server/default/deploy for connecting to the mysql open18 catalog. This datasource can be installed on the server via the ant task datasource of the seam-gen app.

    2. Installing JBoss server

    • First download the latest JBoss 5 release here;
    • Unzip the zipfile into your ~/opt folder;
    • For hot deployment to work you need to alter the file <jboss_home>/server/default/conf/bootstrap/profile.xml. Uncomment the following line
              <value>WEB-INF/dev</value>
    
    • To prevent any memory issues from occurring make sure you start jboss with the following vm arguments (you can do this by adding the jboss server to your Netbeans servers):
    -Xms128m -Xmx512m -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -XX:+UseConcMarkSweepGC -XX:+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=512m -Xverify:none
    

    3. Project structure

    Next we’ll setup the maven project structure, wherein we will copy the necessary files from the seam-gen app.

    open18_mvn2 project structure

    • src
      • main
        • hot: Folder for the hot deployable sources (i.e. the action classes). In the seam-gen app this is the src/hot folder;
        • java: Folder for the ordinary java sources (eg. the model classes). In the seam-gen app this is the src/main folder;
        • resources: All the resource files. In the seam-gen app this is the resources folder without the WEB-INF folder. Of all the multiple environment files, I only added the -dev files and renamed them by dropping the –dev extension. For example, i’ve renamed the components-dev.properties to components.properties.;
        • webap: The web-app folder containing all the view files. In the seam-gen app this is the view folder combined with the resources/WEB-INF folder;
      • test
        • bootstrap: The bootstrap files needed for testing with jboss-embedded. In the seam-gen app this is the bootstrap folder;
        • java: Folder for the test classes. In the seam-gen app this is the src/test folder;
        • resources: Resource files for testing. In the seam-gen app these are the resources/META-INF/persistence-test.xml, resources/components-test.properties and resources/import-test.sql files. I’ve renamed them by dropping off the -test extension.

    4. pom.xml

    This is the pom file with which it’ll be possible to eg. test and explode the application to glassfish and jboss. I’ve highlighted the lines which I will comment upon in this section.

    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    
        <modelVersion>4.0.0</modelVersion>
        <groupId>org.open18</groupId>
        <artifactId>open18_mvn2</artifactId>
        <packaging>war</packaging>
        <version>1.0-SNAPSHOT</version>
        <name>open18_mvn2 JEE5 Webapp</name>
        <url>http://maven.apache.org</url>
    
        <repositories>
            <repository>
                <id>jboss</id>
                <name>JBoss Release Repository</name>
                <url>http://repository.jboss.org/maven2</url>
            </repository>
        </repositories>
    
        <pluginRepositories>
            <pluginRepository>
                <id>ctpjava</id>
                <name>CTP Public Repository</name>
                <url>http://ctpjava.googlecode.com/svn/trunk/repository</url>
            </pluginRepository>
            <pluginRepository>
                <id>repository.jboss.org</id>
                <name>JBoss Repository</name>
                <url>http://repository.jboss.org/maven2</url>
            </pluginRepository>
            <pluginRepository>
                <id>maven.java.net</id>
                <name>Java.net Maven2 Repository</name>
                <url>http://download.java.net/maven/2</url>
            </pluginRepository>
        </pluginRepositories>
    
        <dependencies>
    
            <!-- *************** Build Dependencies *************** -->
    
            <dependency>
                <groupId>org.jboss.seam.embedded</groupId>
                <artifactId>hibernate-all</artifactId>
                <version>${jboss.embedded.version}</version>
                <scope>provided</scope>
            </dependency>
    
            <!-- *************** Test Dependencies *************** -->
    
            <dependency>
                <groupId>org.testng</groupId>
                <artifactId>testng</artifactId>
                <version>5.8</version>
                <classifier>jdk15</classifier>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>junit</groupId>
                        <artifactId>junit</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.jboss.seam.embedded</groupId>
                <artifactId>jboss-embedded-all</artifactId>
                <version>${jboss.embedded.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.jboss.seam.embedded</groupId>
                <artifactId>jboss-embedded-api</artifactId>
                <version>${jboss.embedded.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.jboss.seam.embedded</groupId>
                <artifactId>thirdparty-all</artifactId>
                <version>${jboss.embedded.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>javax.faces</groupId>
                <artifactId>jsf-api</artifactId>
                <version>${javax.faces.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>javax.faces</groupId>
                <artifactId>jsf-impl</artifactId>
                <version>${javax.faces.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>javax.el</groupId>
                <artifactId>el-api</artifactId>
                <version>1.2</version>
                <scope>provided</scope>
            </dependency>
    
            <!-- *************** Seam Dependencies *************** -->
    
            <dependency>
                <groupId>org.jboss.seam</groupId>
                <artifactId>jboss-seam</artifactId>
                <version>${jboss.seam.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>javax.el</groupId>
                        <artifactId>el-api</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.jboss.seam</groupId>
                <artifactId>jboss-seam-ui</artifactId>
                <version>${jboss.seam.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>javax.el</groupId>
                        <artifactId>el-api</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>org.jboss.seam</groupId>
                        <artifactId>jboss-seam-jul</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>commons-beanutils</groupId>
                        <artifactId>commons-beanutils</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.jboss.seam</groupId>
                <artifactId>jboss-seam-ioc</artifactId>
                <version>${jboss.seam.version}</version>
            </dependency>
            <dependency>
                <groupId>org.jboss.seam</groupId>
                <artifactId>jboss-seam-debug</artifactId>
                <version>${jboss.seam.version}</version>
            </dependency>
            <dependency>
                <groupId>org.jboss.seam</groupId>
                <artifactId>jboss-seam-mail</artifactId>
                <version>${jboss.seam.version}</version>
            </dependency>
            <dependency>
                <groupId>org.jboss.seam</groupId>
                <artifactId>jboss-seam-remoting</artifactId>
                <version>${jboss.seam.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>dom4j</groupId>
                        <artifactId>dom4j</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.jboss.seam</groupId>
                <artifactId>jboss-seam-pdf</artifactId>
                <version>${jboss.seam.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>com.lowagie</groupId>
                        <artifactId>itext</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>com.lowagie</groupId>
                        <artifactId>itext-rtf</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
            <!-- *************** RichFaces Dependency *************** -->
    
            <dependency>
                <groupId>org.richfaces.ui</groupId>
                <artifactId>richfaces-ui</artifactId>
                <version>${jboss.richfaces.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>commons-collections</groupId>
                        <artifactId>commons-collections</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>commons-logging</groupId>
                        <artifactId>commons-logging</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.richfaces.framework</groupId>
                <artifactId>richfaces-api</artifactId>
                <version>${jboss.richfaces.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>commons-collections</groupId>
                        <artifactId>commons-collections</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>commons-logging</groupId>
                        <artifactId>commons-logging</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.richfaces.framework</groupId>
                <artifactId>richfaces-impl</artifactId>
                <version>${jboss.richfaces.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>commons-logging</groupId>
                        <artifactId>commons-logging</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.richfaces.samples</groupId>
                <artifactId>glassX</artifactId>
                <version>${jboss.richfaces.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>javax.servlet</groupId>
                        <artifactId>jstl</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>nekohtml</groupId>
                        <artifactId>nekohtml</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
            <!-- *************** Drools / jBPM Dependency *************** -->
    
            <dependency>
                <groupId>org.drools</groupId>
                <artifactId>drools-compiler</artifactId>
                <version>${drools.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>xerces</groupId>
                        <artifactId>xercesImpl</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>xml-apis</groupId>
                        <artifactId>xml-apis</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>janino</groupId>
                        <artifactId>janino</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.jbpm</groupId>
                <artifactId>jbpm-jpdl</artifactId>
                <version>${jboss.jbpm-jpdl.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>commons-logging</groupId>
                        <artifactId>commons-logging</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
            <!-- *************** Facelets Dependency *************** -->
    
            <dependency>
                <groupId>com.sun.facelets</groupId>
                <artifactId>jsf-facelets</artifactId>
                <version>1.1.15</version>
            </dependency>
        </dependencies>
    
        <build>
            <finalName>${project.artifactId}</finalName>
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>META-INF/orm.xml</include>
                        <include>META-INF/persistence.xml</include>
                        <include>messages*.properties</include>
                        <include>seam.properties</include>
                        <include>components.properties</include>
                        <include>security.drl</include>
                    </includes>
                </resource>
            </resources>
            <testResources>
                <testResource>
                    <directory>src/test/resources</directory>
                </testResource>
                <testResource>
                    <directory>src/test/bootstrap</directory>
                </testResource>
                <testResource>
                    <directory>src/main/webapp</directory>
                </testResource>
            </testResources>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>${java.source.version}</source>
                        <target>${java.source.version}</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>build-helper-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>add-source</id>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>add-source</goal>
                            </goals>
                            <configuration>
                                <sources>
                                    <source>${java.source.hotdeploy}</source>
                                </sources>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <childDelegation>true</childDelegation>
                        <useSystemClassLoader>true</useSystemClassLoader>
                        <argLine>
                            -Dsun.lang.ClassLoader.allowArraySyntax=true
                        </argLine>
                    </configuration>
                </plugin>
                <plugin>
                    <!-- run with 'mvn cli:execute-phase' and use 'hot' -->
                    <groupId>org.twdata.maven</groupId>
                    <artifactId>maven-cli-plugin</artifactId>
                    <configuration>
                        <userAliases>
                            <hot>hotdeploy:exploded -o -Pjboss.local</hot>
                        </userAliases>
                    </configuration>
                </plugin>
                <plugin>
                    <artifactId>maven-antrun-plugin</artifactId>
                    <version>1.3</version>
                    <executions>
                        <execution>
                            <id>copy-test-persistence</id>
                            <phase>process-test-resources</phase>
                            <configuration>
                                <tasks>
                                    <!--backup the "proper" persistence.xml-->
                                    <copy file="${project.build.outputDirectory}/META-INF/persistence.xml" tofile="${project.build.outputDirectory}/META-INF/persistence.xml.bck"/>
                                    <!--replace the "build" persistence.xml with the "test" version-->
                                    <copy file="${project.build.testOutputDirectory}/META-INF/persistence.xml" tofile="${project.build.outputDirectory}/META-INF/persistence.xml" overwrite="true"/>
                                </tasks>
                            </configuration>
                            <goals>
                                <goal>run</goal>
                            </goals>
                        </execution>
                        <execution>
                            <id>restore-persistence</id>
                            <phase>prepare-package</phase>
                            <configuration>
                                <tasks>
                                    <!--restore the "build" persistence.xml-->
                                    <move file="${project.build.outputDirectory}/META-INF/persistence.xml.bck" tofile="${project.build.outputDirectory}/META-INF/persistence.xml" overwrite="true"/>
                                </tasks>
                            </configuration>
                            <goals>
                                <goal>run</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
        <profiles>
            <profile>
                <id>jboss.local</id>
                <build>
                    <defaultGoal>hotdeploy:exploded</defaultGoal>
                    <plugins>
                        <plugin>
                            <groupId>com.ctp.seam.maven</groupId>
                            <artifactId>maven-hotdeploy-plugin</artifactId>
                            <version>0.3.1</version>
                            <configuration>
                                <source>${java.source.version}</source>
                                <target>${java.source.version}</target>
                                <sourceDirectory>${java.source.hotdeploy}</sourceDirectory>
                                <deployDirectory>
                                    ${directory.deploy.jboss}/${build.finalName}.${project.packaging}
                                </deployDirectory>
                            </configuration>
                        </plugin>
                        <plugin>
                            <groupId>org.apache.maven.plugins</groupId>
                            <artifactId>maven-clean-plugin</artifactId>
                            <configuration>
                                <filesets>
                                    <fileset>
                                        <directory>${directory.deploy.jboss}</directory>
                                        <includes>
                                            <include>**/${build.finalName}.${project.packaging}*</include>
                                        </includes>
                                        <followSymlinks>false</followSymlinks>
                                    </fileset>
                                </filesets>
                            </configuration>
                        </plugin>
                    </plugins>
                </build>
                <properties>
                    <directory.deploy.jboss>~/Programming/opt/jboss-5.1.0.GA/server/default/deploy</directory.deploy.jboss>
                </properties>
            </profile>
    
            <profile>
                <id>glassfish.local</id>
                <build>
                    <plugins>
                        <plugin>
                            <groupId>org.apache.maven.plugins</groupId>
                            <artifactId>maven-war-plugin</artifactId>
                            <executions>
                                <execution>
                                    <id>Prepare WAR</id>
                                    <phase>prepare-package</phase>
                                    <goals>
                                        <goal>exploded</goal>
                                    </goals>
                                </execution>
                            </executions>
                        </plugin>
                        <plugin>
                            <groupId>org.codehaus.mojo</groupId>
                            <artifactId>exec-maven-plugin</artifactId>
                            <version>1.1</version>
                            <executions>
                                <execution>
                                    <id>explode to glassfish</id>
                                    <phase>prepare-package</phase>
                                    <goals>
                                        <goal>exec</goal>
                                    </goals>
                                    <configuration>
                                        <executable>${glassfish.home}/bin/asadmin</executable>
                                        <arguments>
                                            <argument>deploy</argument>
                                            <argument>${project.build.directory}/${project.build.finalName}</argument>
                                        </arguments>
                                    </configuration>
                                </execution>
                                <execution>
                                    <id>clean and unexplode</id>
                                    <phase>clean</phase>
                                    <goals>
                                        <goal>exec</goal>
                                    </goals>
                                    <configuration>
                                        <executable>${glassfish.home}/bin/asadmin</executable>
                                        <arguments>
                                            <argument>undeploy</argument>
                                            <argument>${project.build.finalName}</argument>
                                        </arguments>
                                    </configuration>
                                </execution>
                            </executions>
                        </plugin>
                    </plugins>
                </build>
                <properties>
                    <glassfish.home>~/Programming/opt/glassfishv2</glassfish.home>
                </properties>
            </profile>
        </profiles>
    
        <properties>
            <java.source.version>1.5</java.source.version>
            <java.source.hotdeploy>${basedir}/src/main/hot</java.source.hotdeploy>
            <jboss.seam.version>2.2.1.CR1</jboss.seam.version>
            <jboss.richfaces.version>3.3.3.CR1</jboss.richfaces.version>
            <jboss.jbpm-jpdl.version>3.2.3</jboss.jbpm-jpdl.version>
            <jboss.embedded.version>beta3.SP12</jboss.embedded.version>
            <drools.version>5.0.1</drools.version>
            <javax.faces.version>1.2_13</javax.faces.version>
        </properties>
    
    </project>
    

    line 292-302: testResources

    For testing purposes, you need

    • the webapp directory: this includes for one the facelets and page desciptors;
    • the test resources directory: this  will contain files like persistence.xml and components.properties;
    • the test bootstrap directory: this will contain the bootstrap files needed for testing with jboss embedded server.

    line 330-340: maven-surefire-plugin

    Make sure to add this plugin configuration, or else the tests will surely fail.

    line 351-384: maven-antrun-plugin

    This is a little workaround. JBoss embedded unfortunately doesn’t see the persistence.xml file in the target/test-classes/META-INF directory. It has to be in the target/classes/META-INF directory. To work around this issue, we’ll make a backup of the persistence.xml in the classes tree and replace it with the test-classes version. After the testing phase we’ll put the backed up persistence.xml file back in place.

    line 390: jboss.local profile

    Profile for hot deployment and undeployment to jboss.

    line 425: directory.deploy.jboss

    Make sure to point this to the domain folder of your jboss installation.

    line 430: glassfish.local profile

    Profile for hot deployment and undeployment to glassfish.

    line 484: glassfish.home

    Make sure to point this to your glassfish installation.

    At this point your project in Netbeans should look like the picture below

    open_mvn2 project in Netbeans

    5. Test class example

    To check if testing (and especially persistence against the Hypersonic database) works correctly I’ve added the test class org.open18.test.CreateNewFacilityActionTest, which adds a new facility. Note that you have to be logged in first to be able to make any permanent changes:

    package org.open18.test;
    
    import java.util.List;
    
    import javax.faces.application.FacesMessage;
    
    import org.jboss.seam.faces.FacesMessages;
    import org.jboss.seam.mock.SeamTest;
    import org.testng.annotations.Test;
    
    public class CreateNewFacilityActionTest extends SeamTest {
    
        @Test(groups = { "level.integration", "speed.slow" })
        public void createNewFacilityTest() throws Exception {
    
            new FacesRequest("/login.xhtml") {
    
                @Override
                protected void updateModelValues() {
    
                    setValue("#{credentials.username}", "admin");
                }
    
                @Override
                protected void invokeApplication() {
                    Object outcome = invokeMethod("#{identity.login}");
                    assert outcome != null && outcome.equals("loggedIn");
                }
    
                @Override
                protected void renderResponse() throws Exception {
                       List<FacesMessage> messages = FacesMessages.instance()
                            .getCurrentGlobalMessages();
                    assert messages.size() == 1;
                    assert messages.get(0).getSeverity().equals(
                            FacesMessage.SEVERITY_INFO);
                    assert messages.get(0).getSummary().contains("admin");
                }
            }.run();
    
            new FacesRequest("/FacilityEdit.xhtml") {
    
                @Override
                protected void updateModelValues() {
                    setValue("#{facilityHome.instance.name}", "Eindhoven golf");
                    setValue("#{facilityHome.instance.type}", "PUBLIC");
                }
    
                @Override
                protected void invokeApplication() {
                    Object outcome = invokeMethod("#{facilityHome.persist}");
                    assert outcome != null && outcome.equals("persisted");
                }
    
                @Override
                protected void renderResponse() throws Exception {
                    List<FacesMessage> messages = FacesMessages.instance()
                            .getCurrentGlobalMessages();
                    assert messages.size() == 1;
                    assert messages.get(0).getSeverity().equals(
                            FacesMessage.SEVERITY_INFO);
                    assert(messages.get(0).getSummary().contains("Successfully created"));
    
                }
            }.run();
        }
    }
    

    This is the test suite file FacilityIntegrationActionTest.xml that goes along with it:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
    
    <suite name="RegisterAction Tests" verbose="2" parallel="false">
        <test name="RegisterAction Test">
            <classes>
                <class name="org.open18.test.RegisterGolferIntegrationActionTest" />
            </classes>
        </test>
        <test name="CreateNewFacility Test">
            <classes>
                <class name="org.open18.test.CreateNewFacilityActionTest" />
            </classes>
        </test>
    </suite>
    

    6. Other adjustments

    For simplicity I didn’t bother to convert the @ant-variable@ replacement in the component.properties file to Maven, so I just altered the file and put the correct hard-coded values in it.

    src/main/resources/components.properties

    jndiPattern=open18/#{ejbName}/local
    debug=true
    seamBootstrapsPu=false
    seamEmfRef=#{null}
    puJndiName=java:comp/env/open18/pu
    

    There are also a couple of files that need to be adjusted for the test goal to work.

    src/test/resources/META-INF/persistence.xml

    Make sure the following hibernate properties are set with the values shown below

             <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
             <property name="jboss.entity.manager.factory.jndi.name"
                    value="java:/entityManager"/>
    

    bootstrap/deploy/hsqldb-ds.xml

    Because the generated JPA classes all contain a catalog named “OPEN18” you need to make sure that when Hibernate is going to create the tables in the Hypersonic database you’re connected to a schema by the same name. If you don’t do this, you’ll get a bunch of “invalid schema name OPEN18” messages when running the maven test goal. Add the following line below the displayed comment

          <!-- sql to call when connection is created -->
            <new-connection-sql>CREATE SCHEMA OPEN18 AUTHORIZATION DBA</new-connection-sql>
    

    7. Running the goals

    Now you can test your Maven seam-gen app.

    • Test (Netbeans context menu) will run the maven test goal;
    • Run (Netbeans context menu) and selecting a server will deploy the application;
    • Clean (Netbeans context menu) will run the maven clean goal. When you’ve set one of the profiles (via the Set Configuration context menu) it will also undeploy the application from the server;
    • Profile jboss.local (via Custom goals context menu) will hot deploy the application to jboss;
    • Goal prepare-package with profile glassfish.local (via Custom goals context menu) will hot deploy the application to glassfish.

    Seam-gen on Glassfish – part III

    In this last part we’re gonna create the seam-gen project, import it in NetBeans, deploy it and at the end we have a running application.

    1. Create the project

    Creating the skeleton of the seam-gen project is done by issuing the following seam-gen command

    $ ./seam create-project

    If you stuck to the defaults there will be a project directory called open18 in your home directory.

    2. Generate the JPA classes

    Next we’ll generate the JPA classes from the tables in the open18 database into the new project. Again this is done by a seam-gen command

    $ ./seam generate

    3. Import in NetBeans

    Now let’s import the project into NetBeans. It should look like the picture below

    open18 project

    The build targets for the NetBeans project are coupled to the default (i.e. JBoss) deploy targets. Let’s change them to their Glassfish counterparts. Right click on the open18 project and choose properties. Change the targets so they’ll look like this

    open 18 build properties

    4. Change build-glassfish.xml

    This is the build file that contains all the Glassfish specific Ant targets. One thing it does, is copying all the Hibernate files to the lib/ext folder of the Glassfish domain (Glassfish uses EclipseLink as its default JPA provider).

    We’re gonna make 2 changes to this file.

    First we’re gonna make sure that the slf4j/log4j libraries also get copied to the Glassfish domain. If you omit this step, the application won’t deploy properly. Add the following xml stanza to the gf-deploy-hibernate target in the glassfish-build.xml file so that the copy task which copies the libraries to Glassfish resembles the example below.

            <copy todir="${glassfish.domains.dir}/${glassfish.domain}/lib/ext" overwrite="true">
                <fileset dir="${basedir}/lib">
                    ....
                    <!-- Include slf4j/log4j JARs below -->
                    <include name="jcl-over-slf4j.jar"/>
                    <include name="slf4j-api.jar"/>
                    <include name="slf4j-log4j12.jar"/>
                    <include name="log4j.jar"/>
                </fileset>
            </copy>
    

    Next let’s fix a small bug in the deployment of the libraries to a Glassfish v2 server. If you don’t change this the asm.jar, asm-attrs.jar, cglib.jar and cglib-nodep.jar won’t be copied into the Glassfish domain.

    Alter the lines in the gf-deploy-hibernate target that check whether the Glassfish server is a version 2  or 3.

    <condition property="glassfish.v3" value="true" else="false">
        <available file="${glassfish.home}/glassfish" type="dir"/>
    </condition>
    <condition property="glassfish.domains.dir" value="${glassfish.home}/glassfish/domains" else="${glassfish.home}/domains">
        <istrue value="${glassfish.v3}"/>
    </condition>
    

    5. Prepare Glassfish for Hibernate

    Now let’s copy all those library files to the Glassfish domain. Right click the build.xml file in the NetBeans project and choose Run Target > gf-prepare.

    One last thing that needs to be taken care of, is fixing a bug in the Seam code regarding Hibernate and mysql. If you don’t do this, the facelet TeeList.xhtml (which corresponds with the Tee List menu item in the open18 application) will give you the following error message

    /TeeList.xhtml: javax.persistence.PersistenceException: org.hibernate.exception.DataException: could not execute query

    A nice workaround is provided here. In this workaround the flawed Seam component org.jboss.seam.persistence.persistenceProvider is overriden with the following Seam component (which has a higher precedence)

    package org.open18.persistence;
    
    import javax.annotation.PostConstruct;
    
    import org.jboss.seam.ScopeType;
    import org.jboss.seam.annotations.Install;
    import org.jboss.seam.annotations.Name;
    import org.jboss.seam.annotations.Scope;
    import org.jboss.seam.annotations.intercept.BypassInterceptors;
    import org.jboss.seam.log.Log;
    import org.jboss.seam.log.Logging;
    import org.jboss.seam.persistence.HibernatePersistenceProvider;
    
    /**
     * Addresses problem with HibernatePersistenceProvider by explicitly
     * calling HibernatePersistenceProvider.init() from
     * a PostConstruct method that actually gets called on postconstruct.
     *
     */
    @Name("org.jboss.seam.persistence.persistenceProvider")
    @Scope(ScopeType.STATELESS)
    @BypassInterceptors
    @Install(classDependencies={"org.hibernate.Session", "javax.persistence.EntityManager"})
    public class MoBetterHibernatePersistenceProvider extends HibernatePersistenceProvider {
    
        private static Log log = Logging.getLog(MoBetterHibernatePersistenceProvider.class);
    
        @PostConstruct
        public void init()
        {
           log.debug("MoBetterPersistenceProvider init");
           super.init();
        }
    
    }
    

    6. Deploy the application

    We’re almost there. Let’s deploy the application.

    • Right click the build.xml file and choose Run Target > gf-start to startup the Glassfish server;
    • Right click the open18 project and choose Clean and Build.

    7. Run the application

    Finally! You should be able to play with the open18 seam-gen app which runs on the following url: http://localhost:8080/open18.

    %d bloggers like this: