Tapestry URL Rewriter 2.0.0 released
Posted by Thiago H. de Paula Figueiredo at .
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 URLRewriterRule
s.
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 Request
s, calling the URLRewriteRule
s 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:
- It's implemented as advice on the
decodePageRenderRequest(Request)
anddecodeComponentEventRequest(Request)
methods ofComponentEventLinkEncoder
. This causes the two following shortcomings. - Point 1 above prevents implementations of
ComponentEventLinkTransformer
andPageRenderLinkTransformer
(the interfaces that should be implemented by URL rewriting code) of using the methods above to parseRequest
URLs, because it would cause an infinite loop. Here's thread in the mailing list about this specific problem. - 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.
- 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
- Handling page and event URLs is done by implement different interfaces (
PageRenderLinkTransformer
andComponentEventLinkTransformer
), each one with different methods with different return types (respectively,PageRenderRequestParameters decodePageRenderRequest(Request request)
andComponentEventRequestParameters decodeComponentEventRequest(Request request)
). In the Tapestry URL rewriter API, implementing a single interface in a single method covers both page and event URLs. - It's harder to implement rewritings that involve domain names. For example,
user.domain.com
todomain.com/view/user
. TheLink
interface has no methods that deal with that, while URL rewriting API'sSimpleRequestWrapper
has a construtor that receives the new domain name. - 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;
}
}