Seam 2.1.0.BETA1 brought Wicket support to Seam, but how do you use this support?
In this first tutorial we will discuss how to create a seam-wicket project, and how to inject Seam components into Wicket components. A follow-up tutorial will go into more depth on orchestrating a Wicket application with Seam.
First, let's set up a new project using seam-gen which will create a skeleton project for us. Although seam-gen is designed for use with JSF, we can easily customize the project it creates to work with Wicket.
$ ./seam setup
Buildfile: build.xml
init:
setup:
[echo] Welcome to seam-gen :-)
[input] Enter your Java project workspace (the directory that contains your Seam projects) [C:/Projects] [C:/Projects]
/workspace
[input] Enter your JBoss home directory [C:/Program Files/jboss-4.2.3.GA] [C:/Program Files/jboss-4.2.3.GA]
/Applications/jboss-4.2.3.GA
[input] Enter the project name [myproject] [myproject]
wicket-tutorial
[echo] Accepted project name as: wicket_tutorial
[input] Do you want to use ICEfaces instead of RichFaces [n] (y, [n])
n
[input] skipping input as property icefaces.home.new has already been set.
[input] Select a RichFaces skin [blueSky] ([blueSky], classic, ruby, wine, deepMarine, emeraldTown, japanCherry, DEFAULT)
ruby
[input] Is this project deployed as an EAR (with EJB components) or a WAR (with no EJB support) [ear] ([ear], war)
war
[input] Enter the Java package name for your session beans [com.mydomain.wicket_tutorial] [com.mydomain.wicket_tutorial]
<ENTER>
[input] Enter the Java package name for your entity beans [com.mydomain.wicket_tutorial] [com.mydomain.wicket_tutorial]
<ENTER>
[input] Enter the Java package name for your test cases [com.mydomain.wicket_tutorial.test] [com.mydomain.wicket_tutorial.test]
<ENTER>
[input] What kind of database are you using? [hsql] ([hsql], mysql, oracle, postgres, mssql, db2, sybase, enterprisedb, h2)
<ENTER>
[input] Enter the Hibernate dialect for your database [org.hibernate.dialect.HSQLDialect] [org.hibernate.dialect.HSQLDialect]
<ENTER>
[input] Enter the filesystem path to the JDBC driver jar [../lib/hsqldb.jar] [../lib/hsqldb.jar]
<ENTER>
[input] Enter JDBC driver class for your database [org.hsqldb.jdbcDriver] [org.hsqldb.jdbcDriver]
<ENTER>
[input] Enter the JDBC URL for your database [jdbc:hsqldb:.] [jdbc:hsqldb:.]
<ENTER>
[input] Enter database username [sa] [sa]
<ENTER>
[input] Enter database password [] []
<ENTER>
[input] Enter the database schema name (it is OK to leave this blank) [] []
<ENTER>
[input] Enter the database catalog name (it is OK to leave this blank) [] []
<ENTER>
[input] Are you working with tables that already exist in the database? [n] (y, [n])
n
[input] Do you want to drop and recreate the database tables and data in import.sql each time you deploy? [n] (y, [n])
y
[propertyfile] Creating new property file: /workspace/seam/seam-gen/build.properties
[echo] Installing JDBC driver jar to JBoss server
[copy] Copying 1 file to /Applications/jboss-4.2.3.GA/server/default/lib
[echo] Type 'seam create-project' to create the new project
BUILD SUCCESSFUL
Total time: 44 seconds
Next, we need to create the project:
$ ./seam create-project
Now, we need to alter the project to support wicket. Lets first remove the JSF specific configuration files: /resources/WEB-INF/pages.xml, /resources/WEB-INF/faces-config.xml, /view/*.xhtml and /view/*.page.xml. Next, we need to edit /resources/WEB-INF/web.xml to remove the JSF servlet, its mapping, and any JSF, Facelets and RichFaces configuration parameter. You should end up with a web.xml like this:
<?xml version="1.0" ?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<listener>
<listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>
<filter>
<filter-name>Seam Filter</filter-name>
<filter-class>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Seam Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>Seam Resource Servlet</servlet-name>
<servlet-class>org.jboss.seam.servlet.SeamResourceServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Seam Resource Servlet</servlet-name>
<url-pattern>/seam/resource/*</url-pattern>
</servlet-mapping>
</web-app>
And finally, we need to add the wicket libraries and seam-wicket integration library to the project. First, copy jboss-seam-wicket.jar from the /lib/ directory of the Seam distribution to your project's /lib/ directory. Then, make sure the wicket libraries get deployed into the archive by first removing jsf-facelets.jar and the various Seam-JSF integration libraries from the file /deployed-jars.list and adding line items for jboss-seam-wicket.jar and wicket.jar. The file should now contain the following list:
antlr-runtime.jar core.jar drools-compiler.jar drools-core.jar janino.jar jboss-el.jar jboss-seam.jar jboss-seam-wicket.jar jboss-seam-debug.jar jbpm-jpdl.jar mvel14.jar slf4j-log4j12.jar slf4j-api.jar wicket.jar
Now would be a good time to update your IDE to reflect the libraries you are using. You'll probably need to edit the classpath of your IDE using the project buildpath screen.
Lets now set up the source directories in your project. We need to deploy our Wicket components with Seam super powers to a special directory /WEB-INF/wicket where Seam can grab hold of them and enhance them. Unfortunately, this gets a little hairy because you need to modify the Ant build script.
Start by creating a new source directory called /src/web which we'll use to store Wicket views. Then, we need to alter the build script to copy these compiled classes to /WEB-INF/wicket, you can add this target:
<target name="compileweb"
depends="init"
description="Compile the Wicket views from java source code">
<mkdir dir="${war.dir}/WEB-INF/wicket"/>
<!-- Compile the Wicket classes -->
<javac classpathref="build.classpath"
destdir="${war.dir}/WEB-INF/wicket"
debug="${javac.debug}"
deprecation="${javac.deprecation}"
nowarn="on">
<src path="src/web" />
</javac>
<!-- Copy the html markup to the same location -->
<copy todir="${war.dir}/WEB-INF/wicket">
<fileset dir="src/web">
<include name="**/*.html"/>
</fileset>
</copy>
</target>
And make sure it is run, by adding compileweb as a dependency for the war target:
<target name="war"
depends="compilemodel,compileactions, compileweb, copyclasses"
description="Build the distribution .war file">
TIP You may want to create a target named compile which depends compilemodel, compileactions, compileweb, copyclasses and then have the war target depend on compile, if this change has not already been made in Seam.
Wicket uses a class to configure the application (similar in nature to /WEB-INF/faces-config.xml for JSF); so before we can deploy the application to the server, we need to create the following class in the com.mydomain.wicket_tutorial.web package under the src/web directory:
public class TutorialApplication extends SeamWebApplication {}
We tell Wicket to use this class by adding a component definition to the Seam component descriptor (i.e., /resources/WEB-INF/components.xml). Start by adding the wicket namespace:
<components xmlns="http://jboss.com/products/seam/components" ... xmlns:wicket="http://jboss.com/products/seam/wicket">
Then configure the Wicket application by adding the following component definition:
<wicket:web-application application-class="com.mydomain.wicket_tutorial.web.TutorialApplication"/>
Now we can get to the real development. Let's first define a home page for Wicket; we'll make a really simple one for now. First, we need some html to layout and style our page:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
</head>
<body>
<div style="font-weight:bold">
<p wicket:id="replace">Wicket will replace this text at runtime.</p>
</div>
</body>
</html>
Each page in Wicket is accompanies by a matching tree of components in Java. The following class pairs with the home page we just created:
public class HomePage extends WebPage {
public HomePage(PageParameters parameters) {
add(new Label("replace", "Hello World!"));
}
}
Finally, we need to tell Wicket to use our home page as the application's start page:
public class TutorialApplication extends SeamWebApplication {
@Override
public Class getHomePage() {
return HomePage.class;
}
...
}
Admittedly, it's taken a bit of time to get a Wicket project set up, but now that we have, we can start adding Seam to the mix.
TIP For those of you who are adventurous, and perhaps comfortable with Ant and Freemarker, you are encouraged to hack the seam-gen templates to create your own Wicket starter application. That way, you never have to repeat these steps again.
Now, let's make the application do something interesting. We'll add a login box to the home page and use the Seam authenticator to authenticate the user. Here's what a typical login form might look like in Wicket:
<div style="border: 1px solid black; width: 400px; padding: 3px">
<form wicket:id="login">
<div>
<label for="username">Username</label>
<input wicket:id="username" />
</div>
<div>
<label for="password">Password</label>
<input wicket:id="password" type="password" />
</div>
<div>
<input type="submit" value="Login"/>
</div>
</form>
</div>
Of course, we also need to build a matching component tree in Java:
public class HomePage extends WebPage {
public HomePage(PageParameters parameters) {
add(new Label("replace", "Hello World!"));
add(new LoginForm("login"));
}
public class LoginForm extends Form {
public LoginForm(String id) {
super(id);
add(new TextField("username"));
add(new PasswordTextField("password"));
}
}
}
Notice how the Wicket tags are acting as slots where the Java code can inject real UI components. But how do we capture the information that the user submits using the Wicket-powered login form? This is an area where Wicket really shines. There is no need to touch the html page. We can just manipulate the Java components to get what we want. First, let's inject the identity Seam component and tell Wicket to call authenticate() when the form is submitted. At this point, we aren't looking at the values the user entered.
public class LoginForm extends Form {
@In Identity identity;
@Logger log log;
public LoginForm(String id) {
super(id);
add(new TextField("username"));
add(new PasswordTextField("password"));
}
protected void onSubmit() {
try {
identity.authenticate();
log.info("Login succeeded");
setResponsePage(HomePage.class);
} catch (LoginException e) {
log.info("Login failed");
error("Login failed");
}
}
}
The onSubmit() callback method will be called by Wicket when the user submits the form. In it, we tell Wicket to redirect us back to the current page if login succeeds, or raise an error if the login fails. Notice how we use exactly the same annotations as you use in a regular Seam application to inject a Seam component, including the @Logger annotation. For more information about Seam's bijection mechanism, see the bijection section of the Seam reference documentation.
All that is left is to bind the input controls to the username and password properties of the built-in Seam component named credentials so that we can access the values the user submits in the login routine. To do this, we inject the credentials component and bind it using the Wicket PropertyModel:
public class LoginForm extends Form {
@In Identity identity;
@In Credentials credentials;
public LoginForm(String id) {
super(id);
add(new TextField("username", new PropertyModel(credentials, "username")));
add(new PasswordTextField("password", new PropertyModel(credentials, "password")));
}
...
}
Now, try logging in with the username admin and take a look at the console - you'll see your login succeeded message printed. (The default authenticator class in a seam-gen 2.1 project requires the username to be admin and the password to be blank).
That concludes our introduction to using Seam with Wicket. In this tutorial we've looked at creating a new Seam project, removing the JSF view layer and replacing it with Wicket, and using injection to access our Seam components from Wicket. In the next tutorial we'll look at controlling conversations using annotations on Wicket components, raising events, and more bijection (including outjection).
Pete,
This is great! Looking forward to following along and the upcoming tutorials around conversations and events!
Chris
Very nice.
One question. Is SSL been supported by the entry in components.xml or should I use the Wicket solution described in last entry of http://cwiki.apache.org/WICKET/how-to-switch-to-ssl-mode.html ?
Looking forward to a article describing conversation boundary in Wicket.
Marcell
Use the wicket solution :-)
This looks really, really promising. But when/how are enhanched the Wicket component by Seam? At build time, with a post-compile ANT task or on-fly by Seam at runtime ?
Anyway great work! Thank you
This is awesome, finally an alternative for Seam. I will use it, I love Wicket and Seam. Now I can just dump JSF. This is how really Web development should be, EASY.
When Seam starts up (it writes to the log all the classes it enhances). An ant task would be nice too - file an issue in the Seam JIRA and we can look at adding it (especially if you write a patch!).
you said : Then, make sure the wicket libraries get deployed into the archive by first removing jsf-facelets.jar and the various Seam-JSF integration libraries from the file /deployed-jars.list and adding line items for jboss-seam-wicket.jar and wicket.jar. The file should now contain the following list.
project generated by seam 2.10 CR1 does not have that file, there are only deployed-jars-war.list and deployed-jars-ear.list, which one should be edited?....
I created a war in the tutorial, not an ear. But if you create an ear, you should add the Wicket jars to the war.
Hi Pete! Thx for the great setup description. I followede the above but with the exception that I created a EAR project. I am working with seam-2.1.1GA and when a try to deploy the application I get some error when the war file deploys.
Partial stacktrace of deployment ... normal deploy sequence...
09:41:15,193 INFO [Contexts] starting up: org.jboss.seam.security.facesSecurityEvents 09:41:15,193 INFO [Initialization] done initializing Seam 09:41:15,209 INFO [SeamFilter] Initializing filter: org.jboss.seam.web.wicketFilter 09:41:15,240 ERROR [[/seam_wicket]] Exception starting filter Seam Filter java.lang.RuntimeException: exception invoking: unrwap Caused by: java.lang.reflect.InvocationTargetException ...lines ommited ... 143 more Caused by: java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory at org.apache.wicket.protocol.http.WicketFilter.<clinit>(WicketFilter.java:75) at org.jboss.seam.wicket.web.WicketFilterInstantiator.unrwap(WicketFilterInstantiator.java:47) ... 149 more Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1358) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1204) at java.lang.ClassLoader.loadClassInternal(Unknown Source) ... 151 more 09:41:15,256 ERROR [StandardContext] Error filterStartI test the seam-wicket. But how to change the wicket development to deployment in components.xml? Thanks!
Is it possible to use the @Name seam annotation to define a seam component (bean in the war) and then inject that bean using the @In into a wicket component? I have an ear with an ejb and war. I can inject seam beans from the ejb into wicket components in the war just fine. I cannot name a bean in the war though and inject it into another bean in the war. I created the seam.properties file in the WEB-INF/classes folder, but no luck. Any ideas?
I was able to take the classes I was trying to inject using the @In annotation out of the web project and put them in their own j2ee utility project. After doing that, seam picked up the classes and made them available in the war. This came about because I wanted to use seam's bijection to inject some helper classes in some wicket components. I assumed I could put them in the war project directly. I still don't know if that is possible. But now I have an ear with an ejb, war, and utility jar and it seams :) to work well. Thanks for your article. -David.
It seems now suddenly I get an error message when running my tests. It says:
But I am not running wicket. Even if I add this to components.xml it still shows the same error message. Why?
I found it out.
Solution here