Tapestry URL Rewriter 2.0.0 released

  1. tapestry
  2. url-rewriting
  3. linktransformer
  4. java
  5. www
  6. syntax-highlight

A new version of Tapestry URL Rewriter, 2.0.0, was just released. As always, Apache Public License 2.0 and sources at https://github.com/thiagohp/tapestry-url-rewriter. It is not backward-compatible with 1.0.0 and can be used with Tapestry 5.2 and later because the outgoing URL rewriting support was completely removed.

Before talking about the 2.0.0 release, let's talk about its history.

The history of the Tapestry URL rewriting API, both inside tapestry-core and outside

The Tapestry URL Rewriting API was originally written by me and added in Tapestry 5.1.0.1 and later improved by Robert Zeigler. Not everybody was happy: some people considered it difficult to use for rewriting outgoing URLs (the ones created by Tapestry itself). I agree with that.

When I first wrote it, I've made a decision that I later regretted and eventually caused the deprecation of the URL rewriting API: the user code for rewriting incoming (requested) and outgoing (generated) URLs would be the same. That's my mistake and I apologize for that. Incoming URLs are rewritten by adding a RequestFilter into the RequestHandler Tapestry pipeline and using it to change the Request object passed to the MasterDispatcher, which is the Tapestry pipeline which ultimately responds to HTTP requests. This is surprisingly simple code, both in the URL rewriting support code and in the implementation of URLRewriterRules.

Now, the hard bits. The outgoing URL rewriter part, on the other hand, works by decorating the createComponentEventLink() and createPageRenderLink() methods of the ComponentEventLinkEncoder service. The old URL rewriting tried to provide an uniform API for both incoming and outgoing URLs by transforming the outgoing ones, which are represented by Link objects, into Requests, calling the URLRewriteRules and then transforming it back into a Link. This was cumbersome in both the backing code inside Tapestry and in URLRewriterRule implementations.

For Tapestry 5.2, Igor Drobiazko created a new API, named LinkTransformer, to replace the URL rewriting API, which was deprecated and then removed in T5.3. This blog post describes and shows how to use it.

I still had a personal project which used the URL rewriting API heavily, so I copied it from the T5.1.0.5 sources, adapted it to work with Tapestry 5.2 and created a separate package, named Tapestry URL Rewriter, with the sources on GitHub and JARs on the Maven Central Repository so other people who used it in T5.1 could upgrade their projects to T5.2 or later.

Comparing the two APIs

LinkTransformer was originally created because the URL rewriting API was hard to rewrite outgoing URLs (Link instances) and it's quite successful at that.

On the other hand, it has some shortcomings at the incoming URL part when compared to the URL rewriting API:

  1. It's implemented as advice on the decodePageRenderRequest(Request) and decodeComponentEventRequest(Request) methods of ComponentEventLinkEncoder. This causes the two following shortcomings.
  2. Point 1 above prevents implementations of ComponentEventLinkTransformer and PageRenderLinkTransformer (the interfaces that should be implemented by URL rewriting code) of using the methods above to parse Request URLs, because it would cause an infinite loop. Here's thread in the mailing list about this specific problem.
  3. Point 1 above means LinkTransformer happens late in the Tapestry request processing pipeline. This means:
    • You can't use it to rewrite an incoming URL with a period on it, which looks like a Tapestry event URL, into a page one. For example, for dealing with legacy URLs, rewriting /page.html into /page. Here's a thread in the mailing list about this specific problem.
    • You can't use it to rewrite requests for assets (files) handled by Tapestry. This can be useful for dealing with renamed or moved files.
  4. Handling page and event URLs is done by implement different interfaces (PageRenderLinkTransformer and ComponentEventLinkTransformer), each one with different methods with different return types (respectively, PageRenderRequestParameters decodePageRenderRequest(Request request) and ComponentEventRequestParameters decodeComponentEventRequest(Request request)). In the Tapestry URL rewriter API, implementing a single interface in a single method covers both page and event URLs.
  5. It's harder to implement rewritings that involve domain names. For example, user.domain.com to domain.com/view/user. The Link interface has no methods that deal with that, while URL rewriting API's SimpleRequestWrapper has a construtor that receives the new domain name.
  6. It's not possible to use regular expressions to express the rewritings, while in the URL Rewriter API it's quite easy.

Tapestry URL Rewriter 2.0.0

While writing Eloquentia, the blog engine powering this very blog, I've tried to use the LinkTransformer API for the first time. I noticed that it's really much better than the URL Rewriting API for outgoing links, but it couldn't handle the incoming URL rewriting I needed. My conclusion: I should use the better tool for each scenario, so I created the 2.0.0 release of the API by removing all the outgoing URL rewriting and leave that to LinkTransformer. Best of two worlds.

To use Tapestry URL Rewriter 2.0, just add this to your project's pom.xml:

<dependency>
    <groupId>br.com.arsmachina</groupId>
    <artifactId>tapestry-url-rewriter</artifactId>
    <version>2.0.0</version>
</dependency>
		

Or, if you use something else for handling your dependencies, use the information above and convert it to your favorite tool's syntax.

Usage example

The following example comes from Eloquentia itself and the comments describe what it does.

			
/**
 * URL rewriter rule that handles subdomains: 
 * xxx.domain.com -> domain.com/tag/xxx, where xxx is a {@link Tag}
 * with the subdomain boolean property set to true. The the base domain is taken
 * from the {@link SymbolConstants#HOSTNAME} symbol. In the example above, the symbol value is
 * domain.com. 
 * 
 * @author Thiago H. de Paula Figueiredo (http://machina.com.br/thiago)
 */
public class SubdomainURLRewriterRule implements URLRewriterRule {
	
	final private TagController tagController;
	
	final private String hostname;
	
	/**
	 * Single constructor of this class.
	 * @param tagController a {@link TagController}.
	 * @param hostname the hostname used by the server running Eloquentia.
	 */
	public SubdomainURLRewriterRule(
			final TagController tagController, 
			@Inject @Symbol(SymbolConstants.HOSTNAME) final String hostname) {
		assert tagController != null;
		assert hostname != null;
		this.tagController = tagController;
		this.hostname = "." + hostname;
	}

	public Request process(Request request) {
		
		final String serverName = request.getServerName();
		final String path = request.getPath();
		
		// this rewriting should only happen when a request to
		// tagName + '.' + hostname is done.
		if (path.equals("/") && serverName.endsWith(hostname)) {
			
			final String tagName = serverName.substring(0, serverName.indexOf('.'));
			final Tag tag = tagController.findByName(tagName);
			
			// the tag must exist and have it marked as subdomain for the rewriting to happen.
			if (tag != null && tag.isSubdomain()) {
				request = new SimpleRequestWrapper(request, hostname, "/tag/" + tagName);
			}
			
		}
		
		return request;
		
	}

}
			
		

Comments: 0

You need to be logged in to post a comment.