BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Creating and Extending Apache Wicket Web Applications

Creating and Extending Apache Wicket Web Applications

Bookmarks

Introduction

Apache Wicket is a powerful, light-weight component-based web application framework with strong separation of presentation and business logic. It enables you to create quality Web 2.0 applications which are easy to test, debug and support. Suppose that you have to extend a Wicket-based application delivered by another team without changing their code or that you have to deliver a modular web application that is easy to extend and customize by other teams. This article describes how to solve this problem without redundant source code, markup and configuration. The goal is achieved by using maven-war-plugin for merging projects, wicketstuff-annotations for dynamic mounting of web pages, Spring Framework as an inversion of control (IoC) container, powered by wicket-spring-annot project and by fine tuning Maven dependencies.

The goal of this article is to show how to design and build a highly modular and extensible Wicket-based web application from scratch. The readers will be guided through all steps of this process, starting from writing an initial Maven POM file and choosing the required dependencies, and finishing the discussion with configuring components, auto-wiring services and mounting web pages.

This article includes two Maven-managed example applications - Warsaw and Global. The first one is a fully configured web application with two simple web pages. The second application uses Warsaw project as a dependency and introduces a service, several new web pages and a modified version of the component copied from the first application. Both web applications are packaged as WAR files and configured to work in Jetty or any other servlet container. They can be easily launched by running mvn jetty:run-war from command line.

Use case

Imagine a situation where you have to create a customized version of the provided web application built on Wicket application framework. For example, you need to add some links to external resources to the main web page header. To implement this feature you could create a new Wicket panel component and add its instance to the desired web page. This may sound simple if it is a feature of the main version of the application. But how to complete this task if you were given access to the source code and resources of an existing application, but not allowed to introduce any functional changes to the application? This problem can be solved in several ways. One of them is discussed further in this article.

Maven WAR plugin

Simple web application written in Java can be distributed as a single WAR file containing compiled classes, JSP and XML files, static web pages and other resources. Maven lets us merge several WAR files using the maven-war-plugin. All we need to do is to set packaging attribute value to WAR in application's pom.xml file and use another WAR artifact as a dependency. In this article we are going to use two example applications - Warsaw (main) and Global (derived, dependent on Warsaw project). Basic version of Warsaw pom.xml file is shown in Listing 1 and Global project's is in Listing 2.

Listing 1: Basic version of the Warsaw project Maven pom.xml file.

<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>com.example.modular</groupId>
  <artifactId>warsaw</artifactId>
  <packaging>war</packaging>
  <version>1.0</version>

  <name>Modular Wicket Warsaw Project</name>

  <dependencies>
    <!-- Warsaw project dependencies -->
  </dependencies>
</project>

Listing 2: Basic version of the Global project Maven pom.xml file with dependency to Warsaw project.

<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>com.example.modular</groupId>
  <artifactId>global</artifactId>
  <packaging>war</packaging>
  <version>1.0</version>

  <name>Modular Wicket Global Project</name>

  <dependencies>
    <dependency>
      <groupId>com.example.modular</groupId>
      <artifactId>warsaw</artifactId>
      <version>1.0</version>
      <type>war</type>
    </dependency>
  </dependencies>

</project>

After compiling and packaging both projects, the contents of resulting WAR files is almost identical (compare warsaw-1.0.war and global-1.0.war), even if Global project has not got any classes nor resources. What is important here, all dependent libraries and configuration are present in both archives.

According to Java specification, we cannot use WAR files on the classpath. In our case it means, that classes defined in Warsaw project cannot be accessed in Global project at compilation time, so we cannot extend them or use them as regular class components. To overcome this, we have to fine tune the maven-war-plugin by overriding one of its default settings, Listing 3 below shows this setting.

Listing 3: Configuration added to the Warsaw project Maven pom.xml file.

<build>
  <plugins>
    <plugin>
      <artifactId>maven-war-plugin</artifactId>
      <configuration>
        <attachClasses>true</attachClasses>
      </configuration>
    </plugin>
  <plugins>
<build>

Enabling attachClasses option results in installation of additional JAR file (warsaw-1.0-classes.jar) along with standard WAR file into the Maven repository. To access this artifact we have to modify dependency list of the Global project, as shown in listing 4.

Listing 4: Modified dependencies of the Global project Maven pom.xml file

<dependencies>
  <dependency>
    <groupId>com.example.modular</groupId>
    <artifactId>warsaw</artifactId>
    <version>1.0</version>
    <type>war</type>
  </dependency>
  <dependency>
    <groupId>com.example.modular</groupId>
    <artifactId>warsaw</artifactId>
    <version>1.0</version>
    <type>jar</type>
    <classifier>classes</classifier>
    <scope>provided</scope>
  </dependency>
</dependencies>

As we can see, Global project uses Warsaw WAR for creating final web archive and its classes packaged as JAR for compilation purposes. By setting classifier property to classes we define which artifact has to be chosen from the repository. Setting scope to provided tells Maven, that this artifact is only needed during compilation and at run-time it will be provided from other source. This "other source" is of course the Warsaw project's WAR artifact that will be merged with this one by the WAR plugin. Now, having dependencies configured properly, we can start building our derived Wicket application.

Introducing Wicket framework

To start your adventure with Apache Wicket it is advised to build and explore Apache Wicket QuickStart application. I also recommend reading Wicket in Action book, if you find this framework useful and interesting. In Wicket framework, web pages can be mounted in init() method of the main application class, which has to inherit from org.apache.wicket.protocol.http.WebApplication. This technique is used very often but it comes with one disadvantage. If this main application class is defined in the base project (Warsaw in this case), then we cannot add new web pages in the dependent applications (Global). Of course, we could once again extend this class in the other project but then we should also modify reference to this class in web.xml file, as seen in listing 5. One of the solutions to this problem is to introduce a system that automatically seeks and mounts Wicket web pages localized in JAR files on the classpath. In the example applications annotation-driven solution from WicketStuff has been used.

Listing 5. Snippet from web.xml file from Wicket QuickStart application.

<filter>
  <filter-name>wicket.base</filter-name>
  <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
  <init-param>
    <param-name>applicationClassName</param-name>
    <param-value>com.example.modular.warsaw.WicketApplication</param-value>
  </init-param>
</filter>

WicketStuff Annotations

This package offers smart mounting mechanism that can simplify the process of adding and registering Wicket web pages by using @MountPath class annotation and AnnotatedMountScanner utility. As seen in listings 6 and 7, configuration is trivial.

Listing 6: Example usage of WicketStuff AnnotatedMountScanner.

// package and imports were here
public class WicketApplication extends WebApplication {

  public Class getHomePage() {
return HomePage.class;
}

@Override
protected void init() {
super.init();
new AnnotatedMountScanner().scanPackage("com.example.modular.**.pages").mount(this);
}
}

Wicketstuff-annotations project uses Spring Framework PathMatchingResourcePatternResolver for finding matching resources in the classpath. In the example above, this resolver seeks any class with @MountPath annotation, that is localized in any package named "pages" inside com.example.modular package.

Listing 7: Example usage of WicketStuff @MountPath annotation.

package com.example.modular.warsaw.pages;

import org.apache.wicket.markup.html.WebPage;
import org.wicketstuff.annotation.mount.MountPath;

@MountPath(path = "warsaw")
public class WarsawPage extends WebPage {
  // we don't need any extra stuff at this point
}

By using this technique, we can define and mount web pages in both Warsaw and Global projects. Maven WAR plugin guarantees that deployed Global project will contain all these classes in the same directory structure as in Warsaw project. AnnotatedMountScanner ensures that web pages will be properly mounted, even if they are in separate packages, for example in com.example.modular.warsaw.pages and com.example.modular.global.pages. But what happens if we create a web page in Global project in com.example.modular.warsaw.pages package? In this situation there is a risk, that this page will accidentally override other page with the same name, already defined in Warsaw project. On the other hand, this behavior can help us solve the problem with modifying Warsaw project's components from the Global project level.

Substituting components

Using Maven WAR plugin for overriding classes, configuration or other resources in general sounds like a really bad idea. And in most cases it is. However, there are some rare cases where there is no other easy option. If we do a quick search on Wicket web applications' source code, we will find that most of the Wicket components are instantiated using the new keyword. This is a common technique and is considered as a standard practice. So how could we modify behavior of these classes from the derived project side? Probably the first idea that comes into our minds is to use Spring Inversion of Control container for injecting components into particular web pages and try the built-in bean substitution mechanism. It sounds great but on the wicket-spring project wiki web site we read, that "most problems with injecting dependencies from an IOC container come from the following - wicket is an unmanaged framework (and) wicket components and models are often serialized". In short it means that Wicket does not manage the lifecycle of its components and serialization can cause some serious problems, for example during clustering. Even if we find a way of solving this issue, then what about XHTML markup files? Each template file is associated with one specific Java class file. For example /com/example/modular/pages/WarsawPage.html is tightly bound to the com.example.modular.pages.WarsawPage class. To solve this problem we would also need a mechanism to properly handle these associations. For example, it could dynamically substitute and instantiate classes and interact with the Wicket mechanism that is responsible for binding markup files. This mechanism alone could be a good topic for new article, so we will skip it here.

As we see, this issue really complicates the process of extending our application. As I wrote at the beginning of the article, we can try using default override feature of the maven-war-plugin and hope that the base project will not be modified very often.

In Global project we see that maven-war-plugin overrides files from Warsaw project without any confirmation dialogs or warnings. These are plugin's default settings, which are in this case desired. To see how it works in real life, please see attached sample application code.

Back to Spring

Spring is a very powerful and useful framework and we can take advantage of it in Wicket-based applications. Contributors from the wicket-spring project did a great job in providing Spring integration mechanism, that can be easily used in Wicket based web applications. It offers several ways of using Spring IoC container for injecting dependencies into the web components. In the sample applications provided with this article (Warsaw and Global), annotation-based approach has been chosen. Unfortunately, it cannot be used for injecting Wicket components but it handles injection of services into these components.

To take full advantage of this feature, first we need to edit Warsaw application servlet configuration file to use Wicket and Spring. The most interesting parts in listing 8 are definitions of contextConfigLocation with two parameters. First one, WEB-INF/applicationContext.xml, defines main application context, where Wicket WebApplication class bean is defined. In this file we can also define service or DAO beans, that can be used in the application. Second, classpath*:META-INF/example/extra-ctx.xml, states that if there is a file named extra-ctx.xml localized anywhere on the classpath in META-INF/example directory structure, then it is also an application context file with additional Spring beans definitions. What is more important, Warsaw project's servlet configuration file, web.xml, is also used in the Global project, so extra-ctx.xml files will be searched in Warsaw project, Global project and inside any jar file used by any of these applications. This is why Global project is almost free of essential web application configuration files. To show how it works, the Global project introduces an example service named RandomTzService. This service has got only one method, which returns identifier of a randomly chosen time zone. In our application, annotation-based approach is used for injecting Spring beans.

Listing 8: Warsaw application web.xml file snippet.

<context-param>
  <description>Spring Context</description>
  <param-name>contextConfigLocation</param-name>
  <param-value>
    <string>WEB-INF/applicationContext.xml</string>
    <string>classpath*:META-INF/example/extra-ctx.xml</string>
  </param-value>
</context-param>

<servlet>
  <servlet-name>WebClientApplication</servlet-name>
  <servlet-class>org.apache.wicket.protocol.http.WicketServlet</servlet-class>
  <init-param>
    <param-name>applicationFactoryClassName</param-name>
    <param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value>
  </init-param>
</servlet>

Listing 9: Global application extra-ctx.xml file snippet containing localization of annotation services.

<context:annotation-config />
<context:component-scan base-package="com.example.modular.global.service" />

Implementation of RandomTzService has been placed in com.example.modular.global.service package and annotated with Spring's @Service annotation. This way it can be automatically found, according to Listing 9 definition. To use this service in an application, all we need to do is to use Spring's autowire mechanism and annotate fields where this bean should be injected with @Autowired annotation. First of all, in web application class we have to add a component instantiation listener that is responsible for injecting dependencies into Wicket components. It sounds complicated but wicket-spring-annot project provides SpringComponentInjector class that does all this work. Listing 10 shows this code.

Listing 10: Warsaw project main application class with example usage of Spring bean injection mechanism and annotation-based web page scanner.

// package and imports were here
import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
import org.wicketstuff.annotation.scan.AnnotatedMountScanner;

public class WicketApplication extends WebApplication {

    @Override
    protected void init() {
        super.init();
        addComponentInstantiationListener(new SpringComponentInjector(this));
        new AnnotatedMountScanner().scanPackage("com.example.modular.**.pages").mount(this);
    }

    // other methods
}

To inject a service into Wicket component class, we have to annotate a field of correct type with the @SpringBean annotation, also defined in wicket-spring-annot project. When this component is instantiated, SpringComponentInjector seeks for fields with this annotation and injects dependencies where needed. We do not have to worry about serialization of the injected dependencies because they are represented as serialized proxies and it is done automatically. While using this approach we have to remember that this mechanism supports only accessor-based injection, constructor argument based injection is not supported.

Time for AJAX

Now, when we have all major building blocks set, we can start enhancing our application with Web 2.0 components. Wicket framework has good native support for AJAX, although this technology is optional. By default, Wicket prefers traditional requests but adding AJAX support to new and existing components is easy and painless. It is even possible to create dynamic web pages without writing a single line of JavaScript code. The user can still use regular js scripts and add JavaScript function calls to individual Wicket components. This, as well as dynamically adding CSS to pages, is done in a programmatic way from the Java code.

Wicket framework comes with a set of reusable AJAX behaviors and components. Simplest example is an AjaxFallbackLink. This link can be also used in web browser with disabled or without JavaScript support. In such case, by clicking a link, whole page is reloaded. As we see in listing 11, creating a traditional link is quite simple. This example class is a Wicket panel with a link, that we can click, and a label that shows number of times this link has been clicked.

Listing 11: Wicket panel with a classic link.

// package was here
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.PropertyModel;

public class ClassicApproach extends Panel {

    private int clickCount = 0;

    public ClassicApproach(String pId) {
        super(pId);

        add(new Label("clickLabel", new PropertyModel(this, "clickCount")));

add(new Link("link") {
@Override
public void onClick() {
clickCount++;
}
});
}

}

When user clicks this link, whole web page is reloaded. In many cases we would like to perform this operation in the background and update or change only a part of the page. This can be easily done with AJAX, as seen in Listing 12.

Listing 12: Wicket panel with an AJAX link.

// package was here
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.PropertyModel;

public class AjaxApproach extends Panel {

    private int clickCount = 0;

    public AjaxApproach(String pId) {
        super(pId);

        final Label label = new Label("clickLabel", new PropertyModel(this, "clickCount"));
label.setOutputMarkupId(true);
add(label);

add(new AjaxFallbackLink("link") {
@Override
public void onClick(AjaxRequestTarget pTarget) {
clickCount++;
if (pTarget != null) {
pTarget.addComponent(label);
}

}
});
}

}

While comparing these two approaches we see that only a few things were changed. In AJAX link's onClick method we receive an AjaxRequestTarget object. It is used to identify components that will be updated in the AJAX request. If we are using a web browser without JavaScript support, then AjaxRequestTarget object will be null. Besides that, source code is almost identical and XHTML markup seen in Listing 13 can be used in both cases. With only few lines of Java code we got a working web page component that can be updated without needing to refresh the whole page. Thanks to built-in Wicket AJAX engine we did not have to write a single block of JavaScript code or manually link a JavaScript library. All essential JavaScript functions were added in the background automatically.

Listing 13 below shows a Wicket label wrapped into a HTML span tag. If web browser supports JavaScript, then only content of this tag will be updated.

Listing 13: Mark-up code that displays number of times the link, classic or AJAX, has been clicked.

<a href="#" wicket:id="link">This link</a> has been clicked <span wicket:id="clickLabel">[link label]</span> times.

To see more examples of Wicket AJAX support please refer to time zone panels in Global Project. One of them uses AJAX behavior that is responsible for periodical updates of a given component. As we see in listing 14, adding such a behavior to Wicket components is trivial.

Listing 14: AJAX behavior responsible for periodical updates of an associated component.

someWicketComponent.add(new AjaxSelfUpdatingTimerBehavior(Duration.seconds(1)));

There are several great examples of Wicket AJAX components found in Wicket Library's examples section. These mini-applications are deployed so we don't have to download them in order to find what they exactly do. Java source code and XHTML markup files are also included.

Testing Wicket components

One of the important features of a modern Java web framework is the ability to unit-test the presentation layer without having to deploy the web application into a container. Wicket supports the unit testing using a WicketTester helper class, that works directly on the presentation classes. In comparison to protocol level testing frameworks, like JWebUnit or HtmlUnit, we get full control over the pages and individual components, which we can isolate and unit-test outside the servlet container. This approach makes test-driven design (TDD) with functional tests possible, fast and reliable.

Creating and running unit tests with WicketTester is fast and simple, as shown below.

Listing 15: Simple Wicket unit test using JUnit and WicketTester.

// package and imports were here
import junit.framework.TestCase;

import org.apache.wicket.util.tester.WicketTester;

public class MyHomePageTest extends TestCase {

    public void testRenderPage() {
        WicketTester tester = new WicketTester();
        tester.startPage(MyHomePage.class);
        tester.assertRenderedPage(MyHomePage.class);
    }

}

In this example WicketTester creates a dummy web application for testing purposes. In real applications it is more likely that we will need to operate on a concrete instance of a Wicket application class. For example, in Warsaw and Global projects we heavily depend on Spring for mounting web pages and registering services. Therefore we can use a base test class for all Wicket-related unit tests.

Listing 16: Base test class for Wicket unit tests of Warsaw and Global projects.

package com.example.modular;

import junit.framework.TestCase;

import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
import org.apache.wicket.spring.injection.annot.test.AnnotApplicationContextMock;
import org.apache.wicket.util.tester.WicketTester;
import org.springframework.context.ApplicationContext;

import com.example.modular.warsaw.WicketApplication;

public abstract class BaseTestCase extends TestCase {

    protected WicketTester tester;

    protected ApplicationContext mockAppCtx;

    protected WicketApplication application;

    @Override
    public void setUp() {
        mockAppCtx = new AnnotApplicationContextMock();
        application = new WicketApplication();
        SpringComponentInjector sci = new SpringComponentInjector(application, mockAppCtx);
        application.setSpringComponentInjector(sci);
        tester = new WicketTester(application);
    }

}

In Listing 16 we see, that mock application context is created as well as the standard Wicket application instance. When everything is set up we can start testing page components. WicketTester provides methods for clicking links, filling and submitting forms, checking for labels or any other components and many more. Most of these methods take component ID as first parameter. In case of embedded components, colon character is used to separate each of them.

Listing 17: Example usage of WicketTester helper methods.

// open page for testing
tester.startPage(InfoPage.class);

// check if page is rendered
tester.assertRenderedPage(InfoPage.class);

// check class of a given component
tester.assertComponent("leftPanel", LeftTextPanel.class);

// check if label is properly displayed
tester.assertLabel("leftPanel:header", "My info page");

// get Wicket component by name
Component someLink = tester.getComponentFromLastRenderedPage("someLink");

// click a link
tester.clickLink("leftPanel:redirectLink");

// check if page has changed
tester.assertRenderedPage(AfterRedirectPage.class);

Wicket also provides an utility class, called FormTester, dedicated for testing forms. This class lets us programmatically set values of text fields, check boxes and radio buttons and even select values in drop-down boxes. Simulation of file upload action as well as regular form submission is also possible.

Listing 18: Example usage of FormTester helper methods.

// open page for testing
tester.startPage(MyFormPage.class);
tester.assertComponent("form", TheForm.class);

// check status label
tester.assertLabel("status", "Please fill the form");

// prepare form tester
FormTester formTester = tester.newFormTester("form");

// check are name and age fields empty
assertEquals("", formTester.getTextComponentValue("name"));
assertEquals("", formTester.getTextComponentValue("age"));

// fill name and age fields
formTester.setValue("name", "Bob");
formTester.setValue("age", "30");

// submit form
formTester.submit("submitButton");

// check if status label has changed
tester.assertLabel("status", "Thank you for filling the form");

Testing the form validation can also be done in the unit test (Listing 19 shows the form validation test code).

Listing 19: Using WicketTester and FormTester for testing results of empty form submission.

// check if there are no error messages
tester.assertNoErrorMessage();

// reset name and age fields
formTester.setValue("name", "");
formTester.setValue("age", "");

// submit empty form
formTester.submit("submitButton");

// check if proper error messages are shown
tester.assertErrorMessages(new String[] {"Field 'name' is required.", "Field 'age' is required."});

One of the interesting features of Wicket test utilities is the possibility of testing individual components, isolated from a web page. Imagine this example situation, where web page consists of several panels, e.g. leftPanel, centerPanel, header and footer. Each of these components is represented by an instance of a different class. On page level we can test if every panel is visible and if the communication between them is handled properly. If the same component is used on several web pages, then we should test it independently. In such case we can use WicketTester#startPanel(Panel) method, as seen in Listing 20.

Listing 20: Testing of isolated panel using WicketTester helper methods.

// create panel for testing
tester.startPanel(new TestPanelSource(){
  public Panel getTestPanel(String pId) {
    return new CenterPanel(pId);
  }
});

// check if header label is displayed properly
tester.assertLabel("panel:header", "This is center panel");

By using Wicket's TestPanelSource class we can lazily initialize a given panel for testing purposes. After performing this operation we can start testing its specific behavior. We have to remember that this component is instantiated with panel as component ID, so for accessing its child components we have to use panel: as ID prefix.

Testing Wicket web pages and components is not a difficult task. The framework provides utility classes that can be used for testing content rendering, navigation and data flow. Of course writing unit tests for rich components requires some experience, time and effort but it is an important part of application development. In test-driven development automated tests should be written in the first place, in comparison to manual tests performed by team members.

We can find some useful tips on unit testing on Apache Wiki page. Also Wicket in Action book contains a section about testing, which can be used as a good starting point for this topic.

Choosing Apache Wicket

With all this information provided, you should try Wicket framework on your own. In this article, for the sake of simplicity, several major topics like component models and security were omitted. Fortunately there are several introduction-to-Wicket articles and other guides on-line. It is always good to know the popular practices among Wicket users.

First of all, a good understanding of object oriented programming is crucial. In every Wicket application source code you will find anonymous inner classes. This technique helps in agile development, without the need to create many concrete classes that will be used only in one component. On the other hand, when they are deeply nested, then it might look really confusing. Especially when we get an exception caused by a class named SomeClass$2$2$1. We can also use encapsulation and inheritance to add new behavior to existing pages and components. Without a solid background on object oriented design, we can quickly get lost in a world of objects, relations and classes.

As we know, Wicket is a component-based framework. Every component is represented by a Java class and a group of files with the same name but different file extension. These are markup, CSS and message resources. By default, all these files are placed in the same directory structure. If we are working on an application that is rich in specialized components and contains several web pages, then we might feel overwhelmed by the number of files we have to manage. There is no easy solution to this issue, but it is a good practice to keep Java sources separated from other resources. This also helps with the separation of application logic from presentation logic, because whole page logic is contained only in the form of Java classes.

If we take a look at Wicket's API, then we will see, that it is a vast set of different classes and methods. This framework comes with a wide set of ready to use components, behaviors and utilities but in many cases this is the main cause of Wicket's steep learning curve. This is completely different when compared to Struts or JSF, where you can find dozens of books about these technologies. If you are interested in this topic, you can find some good presentations about comparing different Java web frameworks on Matt Raible's home page. They are one or two years old but most of the information included there is still useful and valid.

Summary

If we search the Internet for web application frameworks built on Java platform, we will probably find at least a dozen that can satisfy most of our needs. Which of them should we choose is very often a matter of the web application requirements and our personal liking. As shown in this article, Wicket is a well supported framework with many useful and easy to use extensions. Building highly modular application is only a matter of a basic setup of two or three components. Please refer to the provided sample application source code to see how it all works together in real life. All you need installed is a JDK version 1.5 or higher and Maven 2. Enjoy modular Wicket.

About the author

Krzysztof Smigielski currently works as a Software Developer for ConSol* Consulting & Solutions company in Poland. Graduated from Jagiellonian University, Poland, with a Master Degree in Computer Physics, he has been working on numerical and performance aspects of Java since 2005 and on the server side since 2007.

Appendix

Injecting services

This appendix has been written to explain the usage of Spring autowire mechanism, described in Back to Spring section. We can easily introduce additional Spring context files to Warsaw-based application. These files will be automatically processed, if their name and path matches classpath*:META-INF/example/extra-ctx.xml pattern. This feature is not clearly visible in case of Global project, because this project is physically merged with Warsaw project. To show how this mechanism works in case of Warsaw-independent project, application named Appendix has been created. It consists of the AppendixService, its implementation and a context file, extra-ctx.xml, that is located in /src/main/resources/META-INF/example directory. To build this application we need to extract modular-appendix.zip archive and execute mvn clean install Maven command. As a result, artifact named modular-appendix-1.0.jar will be installed in the local Maven repository.

To see how it works in a Warsaw-based project, we need to add a new dependency to its pom.xml file, as shown in Listing 21.

Listing 21: Warsaw-based project's pom.xml file snippet.

<dependencies>
  <!-- ... ->
  <dependency>
    <groupId>com.example.modular</groupId>
    <artifactId>modular-appendix</artifactId>
    <version>1.0</version>
    <type>jar</type>
  </dependency>
  <!-- ... ->
</dependencies>

When we start the web application with mvn jetty:run-war command, we can see in the server logs that a new bean has been found and instantiated - see Listing 22.

Listing 22: Formatted Global project logs, with modular-appendix dependency added.

DEBUG PathMatchingResourcePatternResolver
  Looking for matching resources in jar file
  [file:/modular-global/target/work/webapp/WEB-INF/lib/modular-appendix-1.0.jar]

DEBUG PathMatchingResourcePatternResolver
  Resolved location pattern
  [classpath*:com/example/modular/appendix/service/**/*.class] to resources
  [URL[jar:file:/modular-global/target/work/webapp/WEB-INF/lib/modular-appendix-1.0.jar
  /com/example/modular/appendix/service/AppendixServiceImpl.class]]

DEBUG XmlBeanDefinitionReader
  Loaded 2 bean definitions from location pattern
  [classpath*:META-INF/example/extra-ctx.xml]
...

DEBUG DefaultListableBeanFactory
  Creating shared instance of singleton bean 'appendixServiceImpl'

DEBUG DefaultListableBeanFactory
  Creating instance of bean 'appendixServiceImpl'

DEBUG DefaultListableBeanFactory
  Eagerly caching bean 'appendixServiceImpl' to allow for resolving
  potential circular references

DEBUG DefaultListableBeanFactory
  Finished creating instance of bean 'appendixServiceImpl'

To see details of implementation, please refer to the Appendix project source code (modular-global.zip, modular-warsaw.zip and modular-appendix.zip).

References

Rate this Article

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

  • First

    by bruce b,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    My compliments to the Chef! The code built and deployed on the first go, which is pretty rare.

    On the issue of using a war as a dependency, I've found it best to keep the modules with a war artifact as thin as possible. For wicket, even the html files can be packaged in a jar, allowing the modular wicket panels to be re-used easily across web apps.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT