Seamless Wicket

Posted by    |      

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).


Back to top