Integrating Metro with Tapestry

First, we create a class Webservice for contribution web services to a RequestFilter:

package org.apache.tapestry.webservice;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.xml.ws.handler.Handler;

public class Webservice
{
	private final Class webserviceClass;

	private final Object service;

	private final String name;

	private final String urlPattern;

	@SuppressWarnings("rawtypes")
	private final List handlerChain;

	@SuppressWarnings("rawtypes")
	public Webservice(Class webserviceClass, Object service, String name, String urlPattern, Handler... handlerChain)
	{
		super();
		this.webserviceClass = webserviceClass;
		this.service = service;
		this.name = name;
		this.urlPattern = urlPattern;

		if(handlerChain == null)
			this.handlerChain = Collections.emptyList();
		else
			this.handlerChain = Arrays.asList(handlerChain);
	}

	public Class getWebserviceClass()
	{
		return webserviceClass;
	}

	public Object getService()
	{
		return service;
	}

	public String getName()
	{
		return name;
	}

	public String getUrlPattern()
	{
		return urlPattern;
	}

	@SuppressWarnings("rawtypes")
	public List getHandlerChain()
	{
		return handlerChain;
	}
}

Next, we create a HttpServletRequestFilter for handling web service requests and forwarding them tho Metro / JAX-WS:

package org.apache.tapestry.webservice;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.ws.handler.Handler;

import org.apache.tapestry5.services.ApplicationGlobals;
import org.apache.tapestry5.services.HttpServletRequestFilter;
import org.apache.tapestry5.services.HttpServletRequestHandler;

import com.sun.xml.ws.api.BindingID;
import com.sun.xml.ws.api.WSBinding;
import com.sun.xml.ws.api.server.Container;
import com.sun.xml.ws.api.server.WSEndpoint;
import com.sun.xml.ws.binding.BindingImpl;
import com.sun.xml.ws.server.EndpointFactory;
import com.sun.xml.ws.transport.http.DeploymentDescriptorParser.AdapterFactory;
import com.sun.xml.ws.transport.http.servlet.ServletAdapter;
import com.sun.xml.ws.transport.http.servlet.ServletAdapterList;
import com.sun.xml.ws.transport.http.servlet.WSServletDelegate;

public class JaxWsHttpServletRequestFilter implements HttpServletRequestFilter
{
	private final ServletContext servletContext;

	private final WSServletDelegate wsServletDelegate;

	private final List urlPatterns;

	public JaxWsHttpServletRequestFilter(final List webservices,
		final ApplicationGlobals applicationGlobals) throws MalformedURLException
	{
		this.servletContext = applicationGlobals.getServletContext();

		Container servletContainer = new ServletContainer(servletContext);
		AdapterFactory adapterFactory = new ServletAdapterList();
		urlPatterns = new ArrayList();

		List adapters = new ArrayList();
		for (Webservice webservice : webservices)
		{
			WSBinding binding = BindingImpl.create(BindingID.parse(webservice.getWebserviceClass()));
			@SuppressWarnings("rawtypes")
			List handlerChain = binding.getHandlerChain();
			handlerChain.addAll(webservice.getHandlerChain());
			binding.setHandlerChain(handlerChain);

			@SuppressWarnings("rawtypes")
			WSEndpoint endpoint = EndpointFactory.createEndpoint(
				webservice.getWebserviceClass(),
				false,
				new ServiceInvoker(webservice.getService()),
				null,
				null,
				servletContainer,
				binding,
				null,
				null,
				null,
				true);
			adapters.add(adapterFactory.createAdapter(webservice.getName(), webservice.getUrlPattern(), endpoint));
			urlPatterns.add(webservice.getUrlPattern());
		}
		wsServletDelegate = new WSServletDelegate(adapters, servletContext);
	}

	public boolean service(HttpServletRequest request, HttpServletResponse response, HttpServletRequestHandler handler)
		throws IOException
	{
		String path = request.getServletPath();
		String pathInfo = request.getPathInfo();

		if (pathInfo != null)
			path += pathInfo;

		for (String urlPattern : urlPatterns)
		{
			if (path.startsWith(urlPattern))
			{
				try
				{
					wsServletDelegate.doPost(request, response, servletContext);
					return true;
				}
				catch (ServletException e)
				{
					return false;
				}
			}
		}

		// Not a match, so let it go.

		return handler.service(request, response);
	}
}

In the WebServiceModule we add the JaxWsHttpServletRequestFilter:

public final class WebserviceModule
{
	public static void contributeHttpServletRequestHandler(
		OrderedConfiguration configuration,
		@InjectService("JaxWsHttpServletRequestFilter") HttpServletRequestFilter jaxWsHttpServletRequestFilter)
	{
		configuration.addInstance("jaxws", JaxWsHttpServletRequestFilter.class, "after:*");
	}
}

Now you can add your web service contribution to your module:

public static void contributeJaxWsHttpServletRequestFilter(final OrderedConfiguration configuration, final MyWebservice myWebservice)
{
	configuration.add("MyWebservice", new Webservice(MyWebservice.class, myWebservice,
		"MyWebservice", "/ws/MyWebservice", new SOAPLoggingHandler(
			GWS_IMPORT_ORDER_LOGGER_NAME)));
}

Restoring deleted trunk or branch in Subversion

If you ever accidentally delete a branch (or even the trunk) or any other directory in Subversion  don’t panic it can be restored.

The Subversion book mentions a technique with checking out the current version and reverse merging the change in “Undoing Changes”. But this is very slow for really big repositories and it’s a bit complicated – I thought there should be an easier way and in fact, there is.

svn copy  {repoUrl}/trunk@{repoVersionBeforeDelete} {repoUrl}/trunk

E.g. if you deleted trunk in your repo http://myhost/svn in commit 1234 (so the last version with existing trunk was 1233) you restore your trunk with:

svn copy http://myhost/svn@1233 http://myhost/svn

Voila – that’s it: your trunk is ready for new commits.

Tapestry 5.1 in the Cloud

Finally, I’ve got Tapestry 5.1 working in Google App Engine (GAE): http://tapestry-test.appspot.com/ (this is the Tapestry 5.1 integration test application)

It was some kind of a way…

The main problem was the Stax parser and the dependency to the Woodstox lib (see Tapestry in the Cloud and Tapestry 5.1 Woodstox).

First, I tried to use a standard Stax parser instead of Woodstox2. I tried this because we had some issues with running Tapestry in Appservers and having to add -Djavax.xml.stream.XMLInputFactory=com.ctc.wstx.stax.WstxInputFactory. This didn’t seem to work because Stax is kind of a mess and no implementation can hande DTDs.

The solution was to replace the Stax with a Sax parser. This was some work, but the parser works in is now in test at our company.

Then I decided to try to check GAE again and voila – it works. I’m happy.