The Seam Maven Refimpl project is a Maven multi-module multi-environment project template for Seam using EJB and JSF that is archived in an ear-archive. A super-POM, (which inherits from the Seam root POM) contains library dependencies and plugin management. The parent project manages the EAR, EJB and WAR modules while the Codecaus Properties Maven Plugin is used to manage a multi-environment setup.
Due to problems booting the JBoss Micro Container with Maven, we have choosen to use the Apache OpenEJB embedded container to run Seam (and EJB-3.x) integration tests. The seam-refimpl project template comes preconfigured with the Apache OpenEJB embedded container for running tests. The main inspiration for using Apace OpenEJB is the post, Using OpenEJB for integration testing, on SeamFramework.org. A lot of code is copied from this post, so a zillion thanks to Stephan B and Oliver Thierry :-)
This tutorial outlines the necessary steps to get a Mavenized Seam project up and running on a JBoss Application Server (JBoss-4.2.3.AS or Jboss-5.1.x.AS) and how to effectively test your Seam components using the Apache OpenEJB embedded container.
You should have a basic understanding of JBoss Seam (EJB3, JPA, JSF, EL, SeamTest), TestNG, the JBoss application server, Maven and a DBMS of your choice, e.g. MySql5, to follow this tutorial.
The shortcuts are listed here so we don't have to type the tedious file paths over and over again in this document.
${JBOSS_HOME}
- refers to Jboss installation directory, e.g.
${JBOSS_CONFIG}
- refers to the configuration directory e.g.
${JBOSS_DEPLOY}
- refers to the JBoss deploy directory, e.g.
${TRUNK}
- the project root directory, i.e. seam-maven-refimpl
${SEAM_REFIMPL}
- the directory where the reference project, i.e. seam-refimpl,
is installed on your computer
${SEAM_TUTORIAL}
- the directory where the tutorial project, i.e. seam-tutorial,
is installed on your computer
jboss-app.xml
dependent on the targeted JBoss
Application Server. This works fine with Maven 2.2.1, but fails in Maven 3. The latest
Maven3.0-beta1 does not evaluate external properties during the
"ear:generate-application-xml"
goal and you will get the following error message:[ERROR] Failed to execute goal org.apache.maven.plugins:maven-ear-plugin:2.4.2:generate-application-xml (default-generate-application-xml) on project mvn3-problem-ear: Failed to initialize JBoss configuration: Invalid JBoss configuration, version[${as.version}] is not supported. -> [Help 1]The bug is reported in JIRA: http://jira.codehaus.org/browse/MNG-2971.
jboss-4.2.3.GA.jdk6.zip
is the correct build for jdk-6.Install the required software.
In the tutorial we'll use Tortoise SVN to check out the project.
C:/dev/projects/seam-maven-refimpl
TortoiseSVN > Repo-browser
from the
menu.
https://seam-maven-refimpl.googlecode.com/svn/trunk/
http://seam-maven-refimpl.googlecode.com/svn/trunk/
trunk
and select
checkout...
from the menu.
mvn clean install
no.knowit.seam:root
pom which all projects in this trunk uses as its
root pom.
no.knowit.seam:seam-utils
modules.
${SEAM_REFIMPL}
and run: mvn clean install
to build the
seam-refimpl project.
${SEAM_REFIMPL}/ear/target/seam-refimpl-ear.ear
file to the
${JBOSS_DEPLOY}
directory.
${JBOSS_HOME}/bin
. If you're using a Unix platform, execute the command:./run.sh
run
Movie List
menu.
Create movie
button to create a new movie.
Save
.
Done
to return to the Movie List view.
${JBOSS_HOME}/bin
using a command shell and type: shutdown -S
or press Ctrl+C from the command shell that started the server.
seam-refimpl-ear.ear
file from the ${JBOSS_DEPLOY}
directory.
seam-refimpl
Project as a Template for Your Own ProjectThis section explains how to use the seam-refimpl project as a template for your own project.
${TRUNK}/seam-refimpl
directory and select
TortoiseSVN > Repo-browser
from the menu.
seam-refimpl
directory and select
export...
from the popup menu.
Export
dialog window select Export directory
and click OK
<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> <parent> <groupId>no.knowit.seam</groupId> <artifactId>root</artifactId> <version>2.2.0-SNAPSHOT</version> </parent> <groupId>com.yourcompany.seam</groupId> <artifactId>seam-tutorial</artifactId> <version>0.1-SNAPSHOT</version> <name>${project.artifactId} : ${project.version}</name> <description>The Seam Tutorial parent POM</description> <!-- <url>http://code.google.com/p/seam-maven-refimpl/</url> --> <packaging>pom</packaging> <modules> <module>ejb</module> <module>war</module> <module>ear</module> </modules> <properties> <env>dev</env> <!-- The default setting if no profile is specified --> <app>tutorial</app> <app.context>seam-tutorial</app.context> <!-- hint: same value as artifactId --> <app.web.context>${app.context}</app.web.context> <app.display.name>Seam Tutorial</app.display.name> <app.description>The Seam Tutorial uses seam-refimpl as a project template</app.description> <app.distributable>false</app.distributable> <app.jndi.pattern>${app.context}-ear/#{ejbName}/local</app.jndi.pattern> <!-- JBoss JNDI Pattern --> <app.jndi.pattern.test>/#{ejbName}/Local</app.jndi.pattern.test> <!-- OpenEJB JNDI Pattern --> <!-- Properties for production are located in src/main/filters/filter-prod.properties Properties to be modified by developer are located in src/main/filters/filter-dev.properties Copy filter-prod.properties to filter-dev.properties and add filter-dev.properties to svn:ignore Type e.g "mvn -Penv-dev" to activate the development profile. --> </properties> . <!-- <scm> <connection>scm:svn:http://seam-maven-refimpl.googlecode.com/svn/trunk</connection> <developerConnection>scm:svn:https://seam-maven-refimpl.googlecode.com/svn/trunk</developerConnection> <url>http://code.google.com/p/seam-maven-refimpl/source/browse/#svn/trunk</url> </scm> --> . </project>
<project . <parent> <groupId>com.yourcompany.seam</groupId> <artifactId>seam-tutorial</artifactId> <version>0.1-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <artifactId>seam-tutorial-ear</artifactId> <name>${project.artifactId} : ${project.version}</name> <description>The Seam Tutorial EAR module</description> <packaging>ear</packaging> . </project>
<project . <parent> <groupId>com.yourcompany.seam</groupId> <artifactId>seam-tutorial</artifactId> <version>0.1-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <artifactId>seam-tutorial-ejb</artifactId> <name>${project.artifactId} : ${project.version}</name> <description>The Seam Tutorial EJB module</description> <packaging>ejb</packaging> . </project>
<project . <parent> <groupId>com.yourcompany.seam</groupId> <artifactId>seam-tutorial</artifactId> <version>0.1-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <artifactId>seam-tutorial-war</artifactId> <name>${project.artifactId} : ${project.version}</name> <description>The Seam Tutorial WAR module</description> <packaging>war</packaging> . </project>
${SEAM_TUTORIAL}/ear/src/main/resources/seam-refimpl-ds.xml
to seam-tutorial-ds.xml
(by default the datasource file name starts with the
${app.context}
property value).
mvn clean install
to verify that the project builds.
A software development lifecycle consists of several phases that each requires its own project configurations - which can make moving a project through all the different phases a challenge. For instance, your development phase may require you to connect to a local database, but your integration test environment database won't be local. And your test database will certainly differ from your production database. Also, being able to deploy to multiple environments is a standard requirement for business applications. It is also natural that each developer want to install the project and it's required utilities, like the application server, to directories of hers/his personal taste. To achieve this we will use a combination of property files and profiles.
To activate development mode:
filter-prod.properties
to filter-dev.properties
:${env}
, which in our case will
come from the activated profile or from the default ${env}
setting in the parent
POM if neither the dev
nor the prod
profile is activated. ${env}
property value is dev
when we activate the
development profile - so we'll name the property file filter-dev.properties
.
${SEAM_TUTORIAL}/src/main/filters/filter-prod.properties
to
${SEAM_TUTORIAL}/src/main/filters/filter-dev.properties
(add the
filter-dev.properties
file to svn:ignore if your project is under version control).
filter-dev.properties
:filter-dev.properties
file in
your favourite editor and make the following changes:
jdk.debug
to true.jdk.optimize
to false.app.debug
to true.app.unpack.modules
to true.as.home
property to the directory where you installed JBoss AS.jdk.debug =true jdk.optimize =false app.debug =true app.unpack.modules=true as.home =C:/dev/server/jboss-4.2.3.GA-jdk6
activeByDefault
tag from the env-prod
profile:env-prod
profile is activated by default, unless another profile in the same
pom is activated, when you execute a Maven command. To activate the env-dev
profile
you could type mvn install -Penv-dev
- but the most
convenient is to remove the activeByDefault
tag from the
env-prod
profile.activeByDefault
tag from the
env-prod
profile:
<profile> <id>env-dev</id> <properties> <env>dev</env> </properties> </profile> <profile> <id>env-prod</id>With this profile configuration the<activation><activeByDefault>true</activeByDefault></activation><properties> <env>prod</env> </properties> </profile>
env
property value is set in the parent POM
and will be used in cases where the env-dev
or env-prod
profile is not
specified - which is more convenient since most of your build cycles are connected to coding,
compiling, unit tests and deployment to your local app server.
mvn clean install -Pexplode
${JBOSS_DEPLOY}
directory should now contain the exploded EAR project:
mvn compile -Pexplode
, and verify that incremental hot deployment of WARs and
EARs works. Check
Chapter 2.1 and
chapter 2.8 in the Seam reference documentation and the
application server documentation for hot deployment issues.
no.knowit.seam.example.action
from ejb/src/test/java/no
directory.
no.knowit.seam.example.action
and no.knowit.seam.example.model
from ejb/src/main/java/no
directory.
no.knowit.seam.example.security.Authenticator
,
no.knowit.seam.example.security.AuthenticatorBean
and
no.knowit.seam.example.action.AuthenticatorTest
.
ejb/src/main/resources/import.sql
.
ejb/testng.xml
.
ejb/pom.xml
accordingly, e.g:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId><configuration><suiteXmlFiles><suiteXmlFile>testng.xml</suiteXmlFile></suiteXmlFiles></configuration></plugin>
war/src/main/webapp/view/example
directory.
war/src/main/webapp/layout/menu.xhtml
remove:
<!-- @newMenuItem@ --> <s:link view="/view/example/MovieList.xhtml" value="Movie List" includePageParams="false" propagation="none"/>
war/src/main/webapp/layout/template.xhtml
,
eg:
<title>Seam Torial</title>
Filters are Maven's way of externalizing property replacement into easily portable and configurable properties files. The basic idea of filtering is that you set up a named properties file and specify one or more filesets to apply the substitutions in the properties file. For example:
<filters> <filter>src/main/filters/filter-${env}.properties</filter> </filters>
Maven filter properties are ony visible to resources and can not be accessed from the POM. This is where the Properties Maven Plugin makes our life a little easier when dealing with properties that we need to access inside our POM. It provides goals to read and write properties from and to files, and also to set system properties. It's main use-case is loading properties from files instead of declaring them in pom.xml, something that comes in handy when dealing with different environments. The plugin read properties during the "initialize" phase and the properties are then accessible from the pom. The Properties Maven Plugin is not inheritable from the parent POM and and you must therefore configure it in each sub POM. For example:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>properties-maven-plugin</artifactId> <executions> <execution> <configuration> <files> <file>../src/main/filters/filter-${env}.properties</file> </files> </configuration> </execution> </executions> </plugin>
The filter properties file has a variable portion ${env}
, which in
our case will come from the activated profile or from the default setting for ${env} in the parent
POM if no profile is specified. When resources are filtered, the project replaces
all matching property names with their corresponding values, taking the filter list into account.
The resources to be filtered are defined by the "resources" build element. For example:
<resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources>
The filtering take place during the process-resources phase and the filtered resource is then copied to the target directory.
ds.jndi.name =refimplDatabase ds.database.name =refimpl_db ds.server.name = ds.driver.class =org.hsqldb.jdbcDriver ds.connection.url=jdbc:hsqldb:mem:${ds.database.name} ds.user.name =sa ds.password =
ear/src/main/resources/seam-tutorial-ds.xml
, are:
<datasources> <local-tx-datasource> <jndi-name>${ds.jndi.name}</jndi-name> <use-java-context>true</use-java-context> <connection-url>${ds.connection.url}</connection-url> <driver-class>${ds.driver.class}</driver-class> <user-name>${ds.user.name}</user-name> <password>${ds.password}</password> </local-tx-datasource> </datasources>
ear/target/seam-tutorial-ear-0.1-SNAPSHOT/seam-refimpl-ds.xml
:
<datasources> <local-tx-datasource> <jndi-name>refimplDatabase</jndi-name> <use-java-context>true</use-java-context> <connection-url>jdbc:hsqldb:mem:refimpl_db</connection-url> <driver-class>org.hsqldb.jdbcDriver</driver-class> <user-name>sa</user-name> <password></password> </local-tx-datasource> </datasources>
The filters properties file with reference to the resource files keeping the corresponding placeholders:
The resource files, e.g. persistence.xml
, are well documented, so we will not go
into details about the properties usage here.
Alternative ways to organize properties can be found here: Maven: Introduction to Build Profiles , Sonatype: Chapter 9. Properties and Resource Filtering
The area of development that has routinely slowed down Java EE (and Seam) developers most often is testing. Before the embedded EJB container era you had to write teststs, redeploy your application, restart the application server and then run your tests inside the application server. It's just slow and boring to do it that way and as a result projects tend to end up with no automated (integration)tests at all. Now, with the advent of embedded EJB3 containers - which boots super fast, need a minimum of configuration and integrates nicely with Maven - there are noe excuses for not writing tests. Writing tests are simply fun again.
Due to problems booting the JBoss Micro Container with Maven, we have choosen to use the Apache OpenEJB embedded container to test Seam (and EJB-3.x) components. The seam-refimpl project template comes preconfigured with the Apache OpenEJB embedded container for running tests. The main inspiration for using Apace OpenEJB is the post, Using OpenEJB for integration testing, on SeamFramework.org. A lot of code is copied from this post, so a zillion thanks to Stephan B and Oliver Thierry :-)
The classic difference between integration and unit testing is that unit tests run in isolation, or near-isolation. Integration tests, on the other hand, test the whole stack. With an embedded container at hand, another and maybe better way is to group tests by how fast they execute; unit test runs fast and integration tests tend to run slowly. Unit tests are normally executed inside our favourite IDE and running as many tests as possible from IDE should definitively increase our productivity.
As programmers we want to have good code quality, which means good unit test test coverage.
EJB 3 and Seam components are just annotated classes - so it is straight forward to unit-test them
the "traditional" way. However, you will have to "emulate" the Dependency Injection, boot the
EntityManager and manage transactions by yourself , someting that will cost you few additional
lines of boilerplate code. With Apache OpenEJB, which
boots the entire container in less than one second, we can move a lot of our traditional
integration tests to unit tests and avoid the useless boilerplate code. The
documentation and the
examples of OpenEJB are really neat and
enable an easy jump-start. All the Apache OpenEJB examples are automated tests that run with a
simple mvn clean install
.
OpenEJB provide an own JNDI-SPI implementation, and plugs so naturally as JNDI-provider.
The settings are factored out into the
ejb/src/test/resources-openejb/jndi.properties
file. You will get from the lookup a
fully-featured EJB 3/3.1 container with injected dependencies and persistence.
The filter properties file has a section with properties dedicated to bootstrapping the OpenEJB embedded container:
# Properties for running tests ds.test =h2 ds.database.name.test =refimpl_db ds.server.name.test = ds.driver.class.test =org.h2.Driver ds.connection.url.test =jdbc:h2:mem:${ds.database.name.test} ds.user.name.test =sa ds.password.test = ds.hibernate.dialect.test =org.hibernate.dialect.H2Dialect pu.hibernate.dialect.test =${ds.hibernate.dialect.test} pu.hibernate.hbm2ddl.auto.test =create-drop pu.hibernate.default_batch_fetch_size.test =16 pu.hibernate.show_sql.test =true pu.hibernate.transaction.manager_lookup_class.test=org.apache.openejb.hibernate.TransactionManagerLookup
The jndi.properties
file has the corresponding placeholders:
You can find more information about OpenEJB embedded configuration here: OpenEJB: Embedded Configuration
In addition to the jndi.properties
file OpenEJB need it's own version of
components.xml
and web.xml
. These files are placed in the
ejb/src/test/resources-openejb
directory. If you need to share the OpenEJB spesific
resource files between different modules, just move the ejb/src/test/resources-openejb
directory to the parent project and modify POMs accordingly, e.g:
<testResources> <testResource> <directory>src/test/resources</directory> <filtering>true</filtering> </testResource> <testResource> <directory>../src/test/resources-openejb</directory> <filtering>true</filtering> </testResource> </testResources>
I you haven't done so yet, read trough Chapter 37. Testing Seam applications in the Seam reference manual.
If you only have EJB components to test you could inherit your test class from
no.knowit.openejb.mock.OpenEjbTest
which provides Base OpenEJB test functionality for
TestNG tests. If you don't want to use TestNG then follow e.g.
these tutorials:
How To Unit-Test EJB 3 ...in 0.8 Seconds [source code included] and
Effective Unit Testing EJB 3.0 with OpenEJB.
Seam tests should inherit from no.knowit.seam.openejb.mock.SeamOpenEjbTest
which provides base Seam test functionality for TestNG tests running in the OpenEJB embedded
container. The class is a copy of the org.jboss.seam.mock.SeamTest
- the only
difference is of cource that the no.knowit.seam.openejb.mock.SeamOpenEjbTest
class
set up the fixture needed to boot the OpenEJB embedded container.
The SeamOpenEjbTest
(equivalent to SeamTest
) class lets you write
scripted tests, in a simulated JSF environment. The role of a scripted test is to reproduce the
interaction between the view and the Seam components. In other words, you get to pretend you are
the JSF implementation! This approach tests everything except the view.
You'll find plenty of tests for the Seam example applications which demonstrate more complex cases.
To configure TestNG refer to e.g.:
To finish the basic steps of this tutorial we will clean the project and completly removed it from the application server.
unexplode
ProfileThe unexplode
profile only works if you have activated the dev
profile.
${JBOSS_HOME}/bin
using a command shell and type: shutdown -S
or press Ctrl+C from the command shell that started the server.
mvn clean -Penv-dev,unexplode
${JBOSS_HOME}/bin
using a command shell and type: shutdown -S
or press Ctrl+C from the command shell that started the server.
mvn clean
seam-tutorial-ear.ear
directory (or file), from ${JBOSS_DEPLOY}
.
Also delete the corresponding datasource file,seam-tutorial-ds.xml
, form
${JBOSS_DEPLOY}
if you have deployed the datasource file as a
separate service.This completes the getting started tutorial.
At some point in time you will want to access data in a database other than the default Hypersonic
database provided with the JBoss Application Server and you will require some mechanism to do this.
The *-ds.xml
file located in ear/src/main/resources
defines a data source,
which is used to access data in a database. These steps explains how to use MySql5 as your data
source.
${JBOSS_CONFIG}/lib
(delete any other
mysql-connector-java-*-bin.jar file if exist).
"tutorial_db"
using the MySql
Administrator GUI.
CREATE DATABASE tutorial_db CHARACTER SET utf8 COLLATE utf8_general_ci;
filter-prod.properties
or
filter-dev.properties
, e.g.:
# MySQL5 #ds =mysql #ds.jndi.name =tutorialDatabase #ds.database.name =tutorial_db #ds.server.name =localhost:3306 #ds.driver.class =com.mysql.jdbc.Driver #ds.connection.url =jdbc:mysql://${ds.server.name}/${ds.database.name}?characterEncoding=UTF-8 #ds.user.name =root #ds.password = #ds.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect #ds.xml =${app.context}-ds.xml
mvn clean install -Pexplode
*-ds.xml
file during packaging of the
project.
You can deploy the *-ds.xml
data source file in two ways: embedded
in the ear project or as a separate service deployed to the
${JBOSS_DEPLOY}
directory. The default for this build is to embed the
*-ds.xml
data source in the ear project.
If you want to deploy the *-ds.xml
data source as a separate service then open the
ear pom and make the changes emphasized in blue.
<profiles> <profile> .. <id>explode</id> .. <build> <plugins> <plugin> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>package</id> <phase>package</phase> <configuration> <tasks> <property name="deploy-path" value="${as.deploy}/${app.context}-ear.ear" /> <echo>Exploding to ${deploy-path}</echo> <delete file="${deploy-path}" quiet="true" /> <mkdir dir="${deploy-path}" /> <sync todir="${deploy-path}" verbose="true" > <fileset dir="${project.build.directory}/${project.build.finalName}" /> </sync> <!-- Uncomment if you want to deploy the *-ds.xml file to appserver deploy directory --> <copy todir="${as.deploy}" file="${project.build.directory}/${app.ds.xml}" verbose="true" /> </tasks> </configuration> .. </profile> .. .. </profiles>
copy-app-ds-xml
:
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>copy-app-ds-xml</id> .. <configuration> <!-- Comment out this tag if you have deployed the *-ds.xml file to appserver deploy directory <outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory> --> <!-- Uncomment this tag if you have deployed the *-ds.xml file to appserver deploy directory --> <outputDirectory>${project.build.directory}</outputDirectory> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <includes> <include>${app.ds.xml}</include> </includes> </resource> </resources> </configuration> </execution> </executions> </plugin> ..
.. <!-- Maven EAR plugin --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-ear-plugin</artifactId> <configuration> .. <jboss> <!-- To trigger the generation of the jboss-app.xml file you need to ... --> <version>${as.version}</version> <loader-repository> <!-- seam.jboss.org is a Seam-specific scoped classloader. It acts as ... --> seam.jboss.org:loader=${app.context}-ear </loader-repository> <!-- Comment out this tag if you have deployed the *-ds.xml file to appserver deploy directory <data-sources> <data-source>${app.ds.xml}</data-source> </data-sources> --> </jboss> .. </configuration> </plugin> ..
For those who had a hard time configuring "their own" log4j.properties or log4j.xml within their application on JBoss, see: My Wiki: How do i use my own log4j properties or xml file in JBoss and JBoss Community: How do i use my own log4j properties or xml file in JBoss.
When your application starts up it will print all components being created (Seam and your own) and the context they are placed in. Validate that your new component is displayed as well.
Seam has a debug page showing useful information. To use it, add the following dependency to your war
pom.xml
:
<dependency> <groupId>org.jboss.seam</groupId> <artifactId>jboss-seam-debug</artifactId> <version>${jboss.seam.version}</version> </dependency>
In filter-dev.properties
make shure that the jdk.debug
and
jdk.debug
property values are true
:
jdk.debug =true jdk.optimize =false jdk.source =1.6 jdk.target =1.6 app.debug =${jdk.debug} app.unpack.modules=true
Open your browser and point to the following URL to see the debug page. http://localhost:8080/seam-tutorial/debug.seam
Some files and directories, like user spesific settings, generated files and IDE settings should not be under revision control. This are the files found so far using Eclipse with the m2eclipse and JBoss Tools plugin. There will be more IDE spesific files to add as other IDEs are put to use. IDE spesific files should be added to the global svn ignore!