Sunday, January 10, 2010

Wicket on Google App Engine

As a continuation of the proof of concept application I started looking at what UI framework I could use. JSF/Richfaces was my preferred UI layer as I have a fair bit of experience with it but after a few days working around a number issues to get it working I reached a final road block and came to the conclusion it just can't work on app engine at this stage. There are too many issues with security permissions and non white listed classes when trying to run it on app engine. An alternative framework had to be found. Wicket peaked my interest do to its component based programmatic approach and that others had already demonstrated some success using it on app engine.

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!

1 comment:

  1. It does not work: I'm using the last stable wicket version and appengine 1.3.8

    ReplyDelete