I found a blog by Nick Wiedenbrueck here which really got me started.
Limitations I've Encountered
- Still some problems with security permissions and app engine white lists
- Some performance problems
Step 1. Add necessary dependencies to maven project.
First thing is to add the necessary dependencies to your maven project. This should pull in everything you need.
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket</artifactId>
<version>1.4.3</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-spring</artifactId>
<version>1.4.3</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-extensions</artifactId>
<version>1.4.3</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-auth-roles</artifactId>
<version>1.4.3</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.4.2</version>
</dependency>
Step 2. Configure Web.xml
All you need to do to add wicket to your web application is to add the wicket filter to your web.xml and point it to your own Wicket Application class.
<filter>
<filter-name>WicketFilter</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>com.agilewombat.blog.MyApplication</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>WicketFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Step 3. Create Wicket Application Class
Your wicket application class will look like a standard wicket application with one main exception. The GAE restricts access to classes in the file io classes, so wickets second level session store can't work and must be disabled (line 10). Also the app engine prevents spawning of new threads so the standard resource modification watcher must be either disable or overridden with a version that doesn't spawn threads (line 17). Additionally the class below adds spring integration and enables wicket ajax features.
public class MyApplication extends AuthenticatedWebApplication {
@Override
public Class<? extends Page> getHomePage() {
return HomePage.class;
}
@Override
protected ISessionStore newSessionStore() {
// disabled second level cache because it relies on File.io classes (incompatible with GAE)
return new HttpSessionStore(this);
}
@Override
protected void init() {
super.init();
addComponentInstantiationListener(new SpringComponentInjector(this));
getResourceSettings().setResourceWatcher(new GaeModificationWatcher());
enableAjaxFeatures();
}
@Override
public RequestCycle newRequestCycle(final Request request, final Response response) {
return new MyWebRequestCycle(this, (WebRequest) request, response);
}
private void enableAjaxFeatures() {
getResourceSettings().setThrowExceptionOnMissingResource(false);
getRequestCycleSettings().addResponseFilter(new AjaxServerAndClientTimeFilter());
getDebugSettings().setAjaxDebugModeEnabled(true);
}
}
Step 4. Create GaeModificationWatcher
The following is what I wrote as an alternative modification watcher to the built in wicket one (so development use only and use at own risk). This one doesn't use threads but will use the poll interval to limit the number of times notifications can be sent.
public class GaeModificationWatcher implements IModificationWatcher {
private static final Logger LOG = LoggerFactory.getLogger(GaeModificationWatcher.class);
ConcurrentHashMap<IModifiable, Set<IChangeListener>> listenersMap = new ConcurrentHashMap<IModifiable, Set<IChangeListener>>();
Duration pollFrequency;
Time lastCheckTime;
Object timeCheckLock = new Object();
@Override
public boolean add(IModifiable modifiable, IChangeListener listener) {
checkResources();
Set<IChangeListener> listeners = listenersMap.putIfAbsent(modifiable, new HashSet<IChangeListener>());
return listeners.add(listener);
}
@Override
public void destroy() {
// do nothing
}
@Override
public Set<IModifiable> getEntries() {
return listenersMap.keySet();
}
@Override
public IModifiable remove(IModifiable modifiable) {
if (listenersMap.remove(modifiable) != null) {
return modifiable;
} else {
return null;
}
}
@Override
public void start(Duration pollFrequency) {
LOG.debug("Starting watcher");
synchronized (timeCheckLock) {
lastCheckTime = Time.now();
this.pollFrequency = pollFrequency;
}
}
public void checkResources() {
Time now = Time.now();
Time timeCheck;
synchronized (timeCheckLock) {
if (lastCheckTime == null) {
return; // not started
}
Time nextTimeCheck = lastCheckTime.add(pollFrequency);
if (nextTimeCheck.after(now)) {
return; // nothing to do, not ready
}
// lets go
timeCheck = this.lastCheckTime;
this.lastCheckTime = now;
}
Set<Entry<IModifiable, Set<IChangeListener>>> entrySet =
new HashSet<Entry<IModifiable, Set<IChangeListener>>>(listenersMap.entrySet());
for (Entry<IModifiable, Set<IChangeListener>> entry : entrySet) {
if (timeCheck.before(entry.getKey().lastModifiedTime())) {
LOG.debug("Found modification, notifying listeners of change");
for (IChangeListener listener : entry.getValue()) {
listener.onChange();
}
}
}
}
}
Step 5. Create MyWebRequestCycle
The MyWebRequestCycle is a custom WebRequestCycle that just ensures that the modification watcher is given a chance to check every request (because we don't have a seperate thread to check.
class MyWebRequestCycle extends WebRequestCycle {
MyWebRequestCycle(final WebApplication application,
final WebRequest request, final Response response) {
super(application, request, response);
}
@Override
protected void onBeginRequest() {
if (getApplication().getConfigurationType().equals(Application.DEVELOPMENT)) {
final GaeModificationWatcher resourceWatcher = (GaeModificationWatcher) getApplication()
.getResourceSettings().getResourceWatcher(true);
resourceWatcher.checkResources();
}
}
}
Step 6. Create Home Page
Finally finish off with the actual Home Page. This needs a Java file and Html file in the same package.
public class HomePage extends WebPage {
public HomePage() {
add(new Label("label", new Model("Hello, World")));
}
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd">
<head>
<title>Home Page</title>
</head>
<body>
<span wicket:id="label"></span>
</body>
</html>
Thats it!