Smart Tech for a better Web

Using Apache Cayenne with Apache Wicket

von

I have recently figured out how cool Apache Cayenne is. It is easy to use and has many benefits compared to the king of ORMs Hibernate. While I was setting up my Apache Wicket project I thought about the best way of integrating it. In short, the easiest solution is to use the Spring features of wicket and create a SpringBean with Cayennes data context. And here is the long version. My examples use the current Wicket trunk version which will be released as Wicket 1.5 somewhere in future.

First you'll need to activate the Spring features in Wicket. This is not necessary as you will see below, but it makes your life much more easier. This is shown in many tutorials out there, so I'll keep it short. In your applications init() do:

getComponentInstantiationListeners().add(new SpringComponentInjector(this));

Then you'll need to create a applicationContext.xml file in your src/main/webapp/WEB-INF directory. This is used by Spring to create all your Spring-beans. I have already added the SpringBean for Cayenne:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
  <bean id="cayenneConnector" class="de.grobmeier.persistence.CayenneConnector" />
</beans>

For the sake of completeness, Wicket 1.5 uses Spring 2.5.6. Please refer to this docs if you need more instructions.

My connector class (feel free to give it a better name) connects to Cayenne. Make sure you have saved cayenne.xml, your cayenne map and your drivers xml in src/main/resources. Please don't forget to add your JDBC driver to your pom.xml and/or to your classpath.

import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.access.DataContext;

public class CayenneConnector {

    public CayenneConnector() {}

    public ObjectContext getObjectContext() {
        return BaseContext.getThreadObjectContext();
    }
}

As you can see, I have used a ObjectContext which is stored by thread. This is necessary in an multithreaded environment. You'll need to set this up as a new filter, which is provided by Cayenne.

NOTE: The original docs are not available no more.

Open your web.xml and write below the wicket filter:

<filter>
   <filter-name>CayenneFilter</filter-name>
   <filter-class>org.apache.cayenne.conf.WebApplicationContextFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>CayenneFilter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

The Spring bean can be injected into all other bean which need database access. Of course you can get rid of Spring and call the BaseContext.getThreadObjectContext(); yourself, but Dependency Injection is the better way to go.

Then you are ready - here is a sample DataProvider.

public class UserDataProvider implements IDataProvider<User> {
    @SpringBean(name = "cayenneConnector")
    private CayenneConnector connector;

I would like to return all my stored users therefore the name is UserDataProvider. You'll need to tell Wicket to inject a SpringBean to this object - this can be done with the @SpringBean annotation.

    public UserDataProvider() {
        Injector.get().inject(this);
    }

Unfortunately you'll need to add the above call to the constructor. Wicket needs to know the classes to inject. I would be more cool if Wicket would look up all the SpringBean annotations by default. However, it works.

@SuppressWarnings("unchecked")
public Iterator<? extends User> iterator(int first, int count) {
    SelectQuery query = new SelectQuery(User.class);
    query.setFetchLimit(count);
    query.setFetchOffset(first);
    List<User> result = connector.getObjectContext().performQuery(query);
    return result.iterator();
}

Here we go - i prepared a SelectQuery and used the paging values from Wicket. The connector returns the ObjectContext, the query is done and an Iterator on the results is returned. The SelectQuery could be used as a member variable to avoid multiple instances.

public IModel<User> model(User object) {
    return new LoadableDetachableModel<User>(object) {
        @Override
        protected User load() {
            // object has already been loaded through the constructor
            return this.getObject();
        }
    };
}

This is not very nice, but will do the trick. I need to return a Wicket Model filled with my User. I have choosen the LoadableDetachableModel. It's being filled at construction level. Therefore my load() method has nothing to do. You could do it in another way, for example you could move the complete Select-code into the load() method. This is the original intention. In my case this is enough and more flexible.

public int size() {
    CountQuery<User> query = new CountQuery<User>(User.class);
    Map row = (Map) connector.getObjectContext().performQuery(query).get(0);
    return Integer.parseInt(row.get("C").toString());
}

Please have in mind, Wicket will first call the size() method and cancel the process if the is 0 returned. I have created my own CountQuery to perform a SELECT COUNT(). This is one of the less "not so nice things" with Cayenne. You'll need to use the SQLTemplate and write a few lines code to create a count(). However, thanks to generics this has become trivial.

Thats it- with the model builder gui from Cayenne not only this integration is a trivial task. Modelling your application was never more fun.

Tags: #Apache Cayenne #Apache Wicket #Java #Open Source

Newsletter

ABMELDEN

BLOG-POST TEILEN

When you load these comments, you'll be connected to Disqus. Privacy Statement.