Apache Wicket on Google App Engine for Java

Holy smokes, that was easy. I\’ve got a basic Wicket app running on Google App Engine in under 2 minutes.

3 small traps for the unwary. First of all, you need to enable sessions in your appengine config file.

    true

Secondly, add the following line into your WebApplication\’s init() method:

    @Override
    protected void init() {
        super.init();
        
        //remove thread monitoring from resource watcher
        this.getResourceSettings().setResourcePollFrequency(null);
    }

Thirdly, override the newSessionStore() method to return HttpSessionStore, because the default second level session store uses java.io.File, which Google App Engine doesn\’t allow:

    @Override
    protected ISessionStore newSessionStore()
    {   
        return new HttpSessionStore(this);
//      return new SecondLevelCacheSessionStore(this, new InMemoryPageStore());
    }

That\’s because Google App Engine doesn\’t want you spawning threads. Obvious enough.

So that\’s it! You\’re in a Wicket-land of infinite scalability…

(I\’m sure there\’s more to it but I was excited…)

See my stupid test here: http://transitplatform.appspot.com/

Sending HTML Email with Wicket part II: Converting links

In my previous post, I showed how you can use Wicket\’s HTML rendering engine to render HTML emails by faking a request/response cycle.

In this post, I\’ll show you how to use an IVisitor to change image and anchor URLs to be absolute instead of relative. This is absolutely essential in order to make your HTML email work – otherwise all your images can\’t be found, and your links point to your own mail server.

The trick is to use Wicket\’s IVisitor to add a TransformerBehaviour to all the Images and Links that uses a regex to transform the URL after render but before the page is returned.

The code for the IVisitor is below:

private final class RelativeToAbsoluteUrlVisitor implements IVisitor {
        private final String requestPath;
        private Pattern urlPattern;
        private Class<? extends Component> componentClass;

        private RelativeToAbsoluteUrlVisitor(String requestPath, Class<? extends Component> componentClass, String attributeName) {
            this.requestPath = requestPath;
            this.componentClass = componentClass;
            urlPattern = Pattern.compile(attributeName+"="(.*?)"");
        }

        public Object component(Component component) {
            //if this component is of the specified class, update the URL attribute to be absolute instead of relative
            if(componentClass.isInstance(component)) {
                component.add(new AbstractTransformerBehavior() {

                    @Override
                    public CharSequence transform(Component component,
                            CharSequence output) throws Exception {
                        log.warn("Transforming component output: "+output);

                        Matcher m = urlPattern.matcher(output);

                        if(m.find()){
                            String attributeValue = m.group(1);
                            int start = m.start(1);
                            int end = m.end(1);

                            //convert relative to absolute URL
                            String absolutePath = RequestUtils.toAbsolutePath(requestPath, attributeValue);

                            log.warn("Got absolute path \'"+absolutePath+"\' from relative path \'"+attributeValue+"\'");

                            //construct a new string with the absolute URL
                            String strOutput = String.valueOf(output);
                            String finalOutput = strOutput.substring(0, start)+absolutePath+strOutput.substring(end);

                            log.warn("Returning updated component: \'"+finalOutput+"\'");

                            return finalOutput;
                        }

                        return output;
                    }});
            }
            return IVisitor.CONTINUE_TRAVERSAL;
        }
    }

Then we override the onBeforeRender() routine to traverse the component hierarchy and add this behaviour to the appropriate elements. Note that I haven\’t shown how you get the current absolute request URL, as in my system this is proprietary. There\’s plenty of example code floating around on how to do that, anyway.

    protected void onBeforeRender() {
        super.onBeforeRender();

        final String requestPath = MyCustomWebRequestCycle.get().getCurrentUrlAsString();

        IVisitor imageVisitor = new RelativeToAbsoluteUrlVisitor(requestPath, Image.class, "src");
        IVisitor anchorVisitor = new RelativeToAbsoluteUrlVisitor(requestPath, Link.class, "href");

        visitChildren(Image.class, imageVisitor);
        visitChildren(Link.class, anchorVisitor);
    }

So there you have it! All the bits and pieces to create HTML email with Wicket. There\’s one more catch though: You have to generate these emails in the same process as the Wicket Application. Calling Application.get() outside of the main process results in an error. In my system, I get around this by generating the HTML email source every time the user saves my Newsletter bean, which means that when it\’s finally sent (in the background), it just sends the pre-generated HTML. Easy!


	

Render a Wicket page to a string for HTML email

Something that\’s very desirable to do in Apache Wicket is create HTML emails using Wicket\’s brilliant component-oriented markup.

I\’ve been working on this problem on and off for ages — it\’s tricky because of teh way that markup rendering is so deeply tied to the requestcycle, which in turn is deeply dependent on the httpservletrequest — with good reason, too. That\’s where Wicket gets its autoconfiguring magic from!

So in order to use Wicket to create HTML emails, we need to fake the request/response cycle. I wrote this convenient method that renders a bookmarkable page (pageclass + pageparameters) to a string:

protected String renderPage(Class<? extends Page> pageClass, PageParameters pageParameters) {

        //get the servlet context
        WebApplication application = (WebApplication) WebApplication.get();

        ServletContext context = application.getServletContext();

        //fake a request/response cycle
        MockHttpSession servletSession = new MockHttpSession(context);
        servletSession.setTemporary(true);

        MockHttpServletRequest servletRequest = new MockHttpServletRequest(
                application, servletSession, context);
        MockHttpServletResponse servletResponse = new MockHttpServletResponse(
                servletRequest);

        //initialize request and response
        servletRequest.initialize();
        servletResponse.initialize();

        WebRequest webRequest = new WebRequest(servletRequest);

        BufferedWebResponse webResponse = new BufferedWebResponse(servletResponse);
        webResponse.setAjax(true);

        WebRequestCycle requestCycle = new WebRequestCycle(
                application, webRequest, webResponse);

        requestCycle.setRequestTarget(new BookmarkablePageRequestTarget(pageClass, pageParameters));

        try {
            requestCycle.request();

            log.warn("Response after request: "+webResponse.toString());

            if (requestCycle.wasHandled() == false) {
                requestCycle.setRequestTarget(new WebErrorCodeResponseTarget(
                        HttpServletResponse.SC_NOT_FOUND));
            }
            requestCycle.detach();

        } finally {
            requestCycle.getResponse().close();
        }

        return webResponse.toString();
    }

One other thing that\’s desirable to do is change all relative links in the email to absolute URLs — something that Wicket makes super-easy, if you know how. That will be the subject of my next post.

Jackrabbit, Wicket, Tomcat, Maven2… hell.

What follows is lessons learned migrating to the potentially magnificent Maven2 for dependency management.

Put <scope>provided</scope> on Tomcat shared resources in your pom.xml

If you deploy jars as a shared resource on Tomcat (i.e. put the jars in common/lib) then be sure to add the <scope>provided</scope> to those dependencies in your project\’s pom.xml. Otherwise, you\’ll get absolutely daft class-cast errors on shared resources like:


2007-10-21 12:42:19,425 ERROR 0-SNAPSHOT] - Servlet /myExample2-1.0-SNAPSHOT threw load() exception
java.lang.ClassCastException: org.apache.jackrabbit.core.jndi.BindableRepository cannot be cast to org.apache.jackrabbit.core.jndi.BindableRepository

Hahahahahahaha I think I want to kill myself. The problem is that Tomcat\’s shared libraries are loaded by a different classloader than your web-app\’s shared libraries (which is nice in a way, because it means you can use different versions of log4j or whatever).

So the lesson here is: Anything you want created by Tomcat and loaded by name (e.g. “jcr/repository”), be sure to exclude from your WEB-INF/lib when you deploy.

You can load the same shared resource by name for all apps

Deploying a Maven2-enabled app using Codehaus Mojo is a breeze… unless you want to deploy a context with it. And a context is the only way to load up named shared resources like a Jackrabbit repository. The solution?

$TOMCAT_HOME/conf/Catalina/$HOSTNAME/context.xml.shared

The contents are loaded for all contexts. Brilliant.

Class blahblah violates loader constraints

Oh no. This was awful. For me it was:


2007-10-21 13:16:26,331 ERROR 0-SNAPSHOT] - Exception starting filter DataServlet
java.lang.LinkageError: Class org/slf4j/ILoggerFactory violates loader constraints

I needed to scour the dependencies that Maven was loading into my webapp automatically and explicitly label them as provided.