New URL Rewriting API
Since 5.1.0.1, Tapestry has basic support for URL rewriting based on the URLRewriter service. In 5.2.0 this service has been deprecated in favour of LinkTransformer service. The new service is actually a facade around ComponentEventLinkTransformer and PageRenderLinkTransformer services.
The PageRenderLinkTransformer service is responsible for the transformation of the page render requests. The implementation of the service is a chain of PageRenderLinkTransformer.
The ComponentEventLinkTransformer service allows a selective replacement of the default links used to represent a component event request. The implementation of the service is a chain of ComponentEventLinkTransformer. Now let’s see some examples.
Rewriting incoming URLs
Imagine you decided to migrate your existing application to Tapestry. Let’s say you are moving from JavaServer Faces. Because the navigation mechanisms in Tapestry and JSF are different, the URLs of your application will change in a incompatible way. This might annoy some of your users who bookmarked the URLs. After the migration these URLs will not work any more. In order to avoid it, you might rewrite all the incoming URLs that start with /jsf.
When processing an incoming requests Tapestry tries to determine if the request is a page render request. If so, Tapestry creates an instance of PageRenderRequestParameters which represents a page and its activation context. The created instance of PageRenderRequestParameters is then used by the PageRenderRequestHandler service to handle a page render request by activating and then rendering the page.
You can take over the task of creation of PageRenderRequestParameters by contributing a PageRenderLinkTransformer. The following listing shows how to accomplish this. When Tapestry attempts to decode the page render request, it will invoke the decodePageRenderRequest() method of JsfLinkTransformer. Inside this method we check if the request path is for a JSF page. If so, an instance of PageRenderRequestParameters representing the Index page with an empty activation context is returned. This will cause the rendering of the Index page when the /jsf/home.xhtml path is requested.
If the incoming request was not identified as a JSF request, a null value is returned. In this case the default logic for decoding the page render request will be executed.
public class JsfLinkTransformer implements PageRenderLinkTransformer {
public PageRenderRequestParameters decodePageRenderRequest(
Request request) {
String path = request.getPath();
if (path.startsWith("/jsf/home.xhtml")) {
return new PageRenderRequestParameters(
Index.class.getSimpleName(),
new EmptyEventContext(),
false);
}
return null;
}
public Link transformPageRenderLink(
Link defaultLink,
PageRenderRequestParameters parameters) {
return defaultLink;
}
}
The class EmptyEventContext represents an empty activation context as shown in following example.
public class EmptyEventContext implements EventContext {
public int getCount() {
return 0;
}
public <T> T get(Class<T> desiredType, int index) {
return null;
}
public String[] toStrings() {
return new String[0];
}
}
The next listing shows how to contribute an JsfLinkTransformer to the configuration of the PageRenderLinkTransformer service.
public class AppModule {
@Contribute(PageRenderLinkTransformer.class)
@Primary
public static void provideURLRewriting(
OrderedConfiguration<PageRenderLinkTransformer>
configuration) {
configuration.addInstance(
"Faces", JsfLinkTransformer.class);
}
}
Rewriting URLs within an Application
Imagine you want to deprecate a page inside your application in favour of a new page. Because some of your users might have bookmarked the old page you decided to keep it for a while. The URL of the page should still be accessible but all the links inside the application need to be updated. You can either go through all pages of your application to update the page parameter of the PageLink component or you can make a global replacement by contributing to the configuration of the PageRenderLinkTransformer service.
After creating a Link that encapsulates a page render request, Tapestry passes that Link through the PageRenderLinkTransformer chain of command. By contributing your own PageRenderLinkTransformer you can replace a passed link by another one. The following listing shows how to replace a link to MyPage by a link to AnotherPage globally. Inside the transformPageRenderLink() method the base path of the passed link is examined to identify the target page. If the Link represents a URL to MyPage, then a Link to AnotherPage is created and returned. Returning the original Link means that no transformation is required.
public class DeprecatePageLinkTransformer implements PageRenderLinkTransformer {
@Inject
private PageRenderLinkSource pageRenderLinkSource;
public PageRenderRequestParameters decodePageRenderRequest(
Request request) {
return null;
}
public Link transformPageRenderLink(
Link defaultLink,
PageRenderRequestParameters parameters) {
if (defaultLink.getBasePath().contains("/mypage")) {
return this.pageRenderLinkSource
.createPageRenderLink(AnotherPage.class);
}
return defaultLink;
}
}
The contribution in the following listing will cause that all the PageLink linking to MyPage will render a page link to AnotherPage event though the page parameter is set to MyPage.
public class AppModule {
@Contribute(PageRenderLinkTransformer.class)
@Primary
public static void provideURLRewriting(
OrderedConfiguration<PageRenderLinkTransformer>
configuration) {
configuration.addInstance(
"DeprecatePageLink",
DeprecatePageLinkTransformer.class);
}
}
Rewriting Component Event URLs
Finally, the replacement of the default links used to represent a component event request is accomplished by the ComponentEventLinkTransformer service. This transformer follows the same pattern as PageRenderLinkTransformer.
Hi Igor!
Very informative article, thanks for that.
I’ve tried to implement it in an alive application, and as for the URLs rewriting it works greatly, but there is a caveat with default Tapestry actions(I’m sure that is because my incorrect implementation of the idea, because it can’t work like this).
So, I’ve implemented my own PageRenderLinkTransformer as described in the article… You’ve written: “If the incoming request was not identified as a JSF request, a null value is returned. In this case the default logic for decoding the page render request will be executed.”
For my system it goes to the Index page instead of expected default behavior. That’s why when I try to add ordinary ActionLink to the page and want it to behave as defined in the correspondent “OnEvent method”, it goes to the PageRenderLinkTransformer instead of going to the “OnEvent method”, transformer returns null, and it sends me to the Index page. I think there is some rude mistake in my implementation, but I can’t find them.
Could you be so kind to suggest any idea why it behaves like this? I would be very appreciated for your help.
Solved!) The thing was in special apache settings that transformed the urls. Article’s approach works perfectly!)
Sorry for the delay. I was unavailable for couple of days. But looks like you already solved the problem. Great.
DeprecatePageLinkTransformer rewrites a link from one page to another, is there a way to rewrite it to a link that are not a valid page? say:
/my_incoming_link ? thanks.
I think following has a problem, it should return null instead of return defaultLink, otherwise another LinkTransformer can not rewrite its application wide urls as it will not be called:
public Link transformPageRenderLink(
Link defaultLink,
PageRenderRequestParameters parameters) {
if (defaultLink.getBasePath().contains(“/mypage”)) {
return this.pageRenderLinkSource
.createPageRenderLink(AnotherPage.class);
}
return defaultLink; // change to return null;
}