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!