Wicket in Action

A comprehensive guide for Java developers building Wicket-based web applications

Creating pluggable applications with Wicket and Spring

In the application I am currently working on it is quiet common to have plugins which affect the UI in different ways. Because of Wicket’s component and OO nature it is very easy to build such functionality. In this article we are going to build a simple application that allows the user to create an article and configure how it should be deployed. Out of the box we are going to support a fictional FTP and HTTP POST deployers, each with their own configuration data and editors. We are going to abstract the deployer into a plugin system so new ones can be easily created and added to the application. The sample project provided works with Hibernate, Spring, and Wicket. In the end we will end up with something like the screenshots below. Notice that the UI is different based on which deployer type is selected in the dropdown…



The Data Model

Lets start by looking at the datamodel. The model is made up of two classes: the Article and the DeplployerConfig. The Article holds our main data, while DeployerConfig acts as a base class for the different deployer configurations: (notice JPA annotations are stripped)

1
2
3
4
5
6
7
8
public class Article implements Serializable, Identifiable<Long>
{
    public Long id;
    public String title;
    public String content;
    public Date created;
    public DeployerConfig deployerConfig;
}

and the DeployerConfig base class:

1
2
3
4
5
6
public abstract class DeployerConfig implements Identifiable<Long>, Serializable
{
    public Long id;
    public String pluginId;
    public Article article;
}

The Plugin System

In order to have a working plugin system we need three things:

  • A common interface or base class for plugins to implement to give them structure
  • A way to identify a unique plugin and tie it to the data model
  • A way to discover all plugins

Lets start by defining our plugin interface:

1
2
3
4
5
6
7
public interface DeployerPlugin extends Identifiable<String>
{
    String getName();
    String deploy(Article article);
    Component newEditor(String id, IModel< ? extends DeployerConfig> model);
    DeployerConfig newConfig();
}

Our plugin interface extends Identifiable which means it has a String getId() method which we will use to return a uuid that will identifiy the plugin.

All we are missing is a way to discover all plugins in the system. Since we are using Spring for the purpose of this article we can build a simple registry bean to aid in plugin discovery:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class IdentifiableBeanRegistry<T extends Identifiable<ID>, ID extends Serializable>
        implements
            ApplicationContextAware,
            InitializingBean
{
    private final Class<T> beanType;
 
    private ApplicationContext context;
    private Map<ID, T> entries;
    private List<T> beans;
    private List<ID> uuids;
 
    public IdentifiableBeanRegistry(Class<T> beanType)
    {
        this.beanType = beanType;
    }
 
    public void setApplicationContext(ApplicationContext context) throws BeansException
    {
        this.context = context;
    }
 
    public T getEntry(Serializable id)
    {
        T entry = entries.get(id);
        if (entry == null)
        {
            throw new IllegalStateException();
        }
        return entry;
    }
 
    public List<T> getEntries()
    {
        return beans;
    }
 
    public List<ID> getIds()
    {
        return uuids;
    }
 
    @SuppressWarnings("unchecked")
    public void afterPropertiesSet() throws Exception
    {
        // initialize the registry
        entries = new HashMap<ID, T>();
 
        final Map<String, ? extends T> matches = BeanFactoryUtils.beansOfTypeIncludingAncestors(
            context, beanType);
        for (T entry : matches.values())
        {
            entries.put(entry.getId(), entry);
        }
 
        entries = Collections.unmodifiableMap(entries);
 
        // intialize indexes
        beans = new ArrayList<T>(entries.values());
        beans = Collections.unmodifiableList(beans);
        uuids = new ArrayList<ID>(entries.keySet());
        uuids = Collections.unmodifiableList(uuids);
    }
}

The registry uses spring’s context introspection to lookup all the beans that implement our plugin interface and caches it in the map. The registry is optimized for three basic operations:

  • Listing all available plugins
  • Listing all available plugin uuids
  • Looking up a plugin by its uuid

The UI

Armed with all this we are now ready to build the user interface. First we begin with a simple page to edit an Article:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public abstract class EditArticlePage extends BasePage
{
    public EditArticlePage(IModel<Article> article)
    {
        add(new FeedbackPanel("feedback"));
        Form<Article> form = new TransactionalForm<Article>("form",
            new CompoundPropertyModel<Article>(article))
        {
            @Override
            protected void onSubmit()
            {
                onSave(getModelObject());
            }
        };
        add(form);
        form.add(new TextField<String>("title"));
        form.add(new TextArea<String>("content"));
        form.add(new Link<Void>("cancel")
        {
            @Override
            public void onClick()
            {
                onCancel();
            }
        });
    }
}

Now we need a way to let the user select which deployer to use for this article and present selected deployer’s interface. For this we build a simple panel which will contain a dropdown that lets the user select a deployer and a swappable panel into which we will put selected deployer’s editor component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class DeployerPluginSelector extends Panel
{
    @SpringBean
    private DeployersRegistry registry;
 
    public DeployerPluginSelector(String id, final IModel<Article> article)
    {
        super(id);
        add(new WebMarkupContainer("editor"));
        add(new PluginPicker("picker")
        {
            @Override
            protected void onSelectionChanged(String pluginId)
            {
                DeployerPlugin plugin = registry.getEntry(pluginId);
                article.getObject().setDeployerConfig(plugin.newConfig());
 
                DeployerPluginSelector.this.replace(plugin.newEditor("editor",
                    new PropertyModel<DeployerConfig>(article, "deployerConfig")));
            }
        });
    }
 
    private static abstract class PluginPicker extends DropDownChoice<String>
    {
        @SpringBean
        private DeployersRegistry registry;
 
        public PluginPicker(String id)
        {
            super(id);
            setRequired(true);
            setModel(new Model<String>());
            setChoices(new LoadableDetachableModel<List< ? extends String>>()
            {
                @Override
                protected List< ? extends String> load()
                {
                    return registry.getIds();
                }
            });
            setChoiceRenderer(new IChoiceRenderer<String>()
            {
                public Object getDisplayValue(String object)
                {
                    return registry.getEntry(object).getName();
                }
 
                public String getIdValue(String object, int index)
                {
                    return object;
                }
            });
        }
 
        @Override
        protected boolean wantOnSelectionChangedNotifications()
        {
            return true;
        }
 
        @Override
        protected abstract void onSelectionChanged(String pluginId);
    }
 
}

A key part of what this picker does is create a new DeployerConfig based on selected plugin and binds it to the article, this way the plugin’s editor component gets the expected model object.

Implementing a Plugin

All thats left is to implement a couple of plugins and drop them into Spring context where they can be discovered by our registry. Lets implement a fictional HTTP POST plugin whose only configuration parameter is the URL to which it will POST the article. The plugin implementation consists of three parts:

  • Implementation of the DeployerPlugin interface
  • Implementation of data structure the plugin will use to store its configuration
  • A Wicket component used to edit the configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class HttpPostDeployerPlugin implements DeployerPlugin
{
    public static final String ID = "plugin.deployer.httppost";
    public String getName()
    {
        return "Http Post";
    }
    public String getId()
    {
        return ID;
    }
    public Component newEditor(String id, IModel< ? extends DeployerConfig> model)
    {
        return new HttpPostDeployerEditor(id, model);
    }
    public DeployerConfig newConfig()
    {
        return new HttpPostDeployerConfig();
    }
    public String deploy(Article article)
    {
        HttpPostDeployerConfig config = (HttpPostDeployerConfig)article.deployerConfig;
        return "Deploying article: " + article.title + " to http://" + config.url;
    }
}
1
2
3
4
5
6
7
8
9
public class HttpPostDeployerConfig extends DeployerConfig
{
    public HttpPostDeployerConfig()
    {
        pluginId = FtpDeployerPlugin.ID;
    }
 
    public String url;
}
1
2
3
4
5
6
7
8
public class HttpPostDeployerEditor extends Panel
{
    public HttpPostDeployerEditor(String id, IModel< ? extends DeployerConfig> model)
    {
        super(id, new CompoundPropertyModel<DeployerConfig>(model));
        add(new TextField<String>("url").setRequired(true));
    }
}

This is an example of a pretty functional plugin system in a web application. Thanks to Wicket it was pretty easy to create a UI that supports dynamic contributions from plugins. Oh yeah, it would be nice to see the article deployed using our plugin. Here is an implementation of Link that can do this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static class DeployArticleLink extends Link<Article>
{
    @SpringBean
    private DeployersRegistry registry;
 
    private DeployArticleLink(String id, IModel<Article> model)
    {
        super(id, model);
    }
 
    @Override
    public void onClick()
    {
        Article article = getModelObject();
        DeployerPlugin plugin = registry.getEntry(article.deployerConfig.pluginId);
        info(plugin.deploy(article));
    }
}

The fully functional example of this system implemented with Hibernate, Spring, and Wicket can be found below. It is a standard Wicket quickstart setup complete with Start class that can be used to launch the application from within an IDE.

plugin-article.zip

4 Comments »

  1. Very cool.
    Though you could clean up your IdentifiableBeanRegistry class by implementing BeanPostProcessor rather than InitializingBean.

    Using IntializingBean.afterPropertiesSet you’re forced to go search the BeanFactory after the fact. By implementing BeanPostProcessor.postProcessAfterInitialization Spring will hand each bean to you as it creates them and you can simply use beanType.isAssignableFrom(bean.getClass()) as they come in.

    Have a look at the BeanFactory lifecycle…
    http://static.springframework.org/spring/docs/2.0.x/api/org/springframework/beans/factory/BeanFactory.html

    Comment by Ray Krueger — October 7, 2008 @ 1:10 pm

  2. Great article! It solves a problem that’s been nagging me for a while - how to write UI plugs for corresponding measuring and control hw devices. Much appreciated.

    Comment by Per Ejeklint — October 7, 2008 @ 4:04 pm

  3. Nice article, but I think it would be a little better if osgi was used instead of rolling your own plugin registry.

    Shouldn’t be that hard now that spring supports osgi.
    http://www.springframework.org/osgi

    - J

    Comment by Jonathan Doklovic — October 7, 2008 @ 4:34 pm

  4. @Ray: While BeanPostProcessors are definitely slicker they also come with shortcommings for this particular usecase. Beans that use AOP-autoproxying cannot be processed by postprocessors, so those plugins will not be registered. Also, when you create proxies or apply any kind of aop (eg @Transactional on a bean) both the real class and the cglib generated subclass are viewed by postprocessors so you would have to filter for duplicates. I find that my way, while not as elegant, simply works.

    @Per: Good to hear, enjoy.

    @Jonathan: This kind of thing can be done with any IOC container that allows introspection. The point of this article was not really to glue Wicket to Spring, but more to demonstrate the pattern. In this whole article there is only one Spring-related class - the registry. I had a template Wicket/Spring project setup and so it was easy for me to base this article on that. Of course, feel free to rewrite the attached code using OSGi and I will be happy to append it to the article.

    Comment by ivaynberg — October 7, 2008 @ 6:45 pm

RSS feed for comments on this post. TrackBack URL

Leave a comment

With this book, Wicket will become the greatest territory the Dutch have settled since Manhattan.

Nathan Hamblen
Senior Software Engineer, Teachscape Inc.

This is the complete and authoritative guide to Wicket, written and reviewed by the core members of the Apache Wicket team. If there's anything you want to know about Wicket, you are sure to find it in this book.

Jonathan Locke
Founder and Architect of Apache Wicket, Foreword Wicket in Action

Without question, Wicket in Action... is the be-all and end-all when it comes to Wicket.

Geertjan Wielenga, Wicket Netbeans Plugin Author

The tutorial and conversational tone of the writing makes the book very approachable.

Nick Heudecker
System Mobile

Loved the sample application—it tied everything together.

Phil Hanna
Senior Software Developer, SAS Institute

The essential guide for learning and using Wicket.

Erik van Oosten
Lead programmer and Project Manager, JTeam

Finally, the Web Framework of web frameworks, Apache Wicket, now has a bible of its own.

Per Ejeklint
Senior Software Architect, Heimore group

Wicket is an innovative evolution of the MVC programming with simple roots, but without a primer such as this, it can be more challenging than it needs to be.

Brian Topping
Founder, Bill2 Inc.

Wicket In Action glues the areas of web development with Apache Wicket together and gives a great overview of Apache Wicket...it will make a great compendium.

Nino Martinez Wael
Java Specialist, Jayway Denmark