« Back

Introducing Friendly URL Routes

July 11, 2010 By Connor Mckay

Standard portlet URLs are anything but succinct.

http://www.liferay.com/web/michael.young/blog?p_p_id=33&p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&p_p_col_id=column-2&p_p_col_count=1&_33_struts_action=%2Fblogs%2Fview_entry&_33_entryId=5215369

Much of this information is unnecessary, and by including only the critical pieces in the path a radical transformation occurs.

http://www.liferay.com/web/michael.young/blog/-/blogs/5215369

This URL consists of two pieces separated by a /-/. The first half identifies the page as usual. The second half first identifies the portlet it refers to with a fragment ("blogs" in this case) known as the friendly URL mapping. Everything after the mapping can be used by the portlet however it chooses. The blogs portlet simply appends the post id.

This simple change, known as friendly URL mapping, has huge benefits to usability, by allowing users to quickly scan the URL and know where they are on a site. It also reduces tampering by hiding the implementation details and actual parameter names.

Unfortunately, before Liferay 6, adding friendly URL mapping to a portlet required subclassing BaseFriendlyURLMapper and writing complicated string manipulation code. Not so with Liferay 6.0.3, thanks to an ingenious system borrowed from Ruby on Rails called "URL Routing".

Friendly URL Routes

A route is a pattern for a URL. It can include named fragments that will automatically be parsed from the URL into the parameter map, as well static content. In Liferay, a friendly URL route looks like this:

/{instanceId}/view/{folderId:\d+}/{name}

Each section of the pattern surrounded by brackets is a fragment. Each fragment contains a name and an optional regular expression to specify the format of the fragment, separated from the name by a colon. When a URL is parsed, each fragment will be extracted from the URL into an entry in the parameter map with the same name. For instance, the URL:

/5b21f/view/25/test

Would be parsed into the parameter map:

instanceId=5b21f&folderId=25&name=test

The real power of this system is that it also works in reverse. Every URL that can be parsed by a route can also be generated from the resulting parameter map.

To understand how this system works in practice we will be taking a detailed look into the friendly URL routing for the document library display portlet.

liferay-portlet.xml

Here is an excerpt of /portal-web/docroot/WEB-INF/liferay-portlet.xml/

<portlet>
    <portlet-name>110</portlet-name>
    <struts-path>document_library_display</struts-path>
    <friendly-url-mapper-class>com.liferay.portal.kernel.portlet.DefaultFriendlyURLMapper</friendly-url-mapper-class>
    <friendly-url-mapping>document_library_display</friendly-url-mapping>
    <friendly-url-routes>com/liferay/portlet/documentlibrary/document-library-display-friendly-url-routes.xml</friendly-url-routes>
    <instanceable>true</instanceable>
</portlet>

The three entries to notice are "friendly-url-mapper-class", "friendly-url-mapping", and "friendly-url-routes".

DefaultFriendlyURLMapper

The largest change in how friendly URL mapping works in Liferay 6.0.3 is this class. In 99% of portlets, you will simply use this class and an accompanying routes xml file, and never write a single line of Java.

Note: In previous versions of Liferay, a unique friendly URL mapper class was required for each portlet so that the URL mapping could be specified. In Liferay 6, several new methods have been added to the FriendlyURLMapper interface to allow this mapping to be set dynamically at runtime, eliminating this requirement.

document-library-display-friendly-url-routes.xml

The full content of this file is shown below.

<?xml version="1.0"?>
<!DOCTYPE routes PUBLIC "-//Liferay//DTD Friendly URL Routes 6.0.0//EN" "http://www.liferay.com/dtd/liferay-friendly-url-routes_6_0_0.dtd">

<routes>
    <route>
        <pattern>/{instanceId}</pattern>
        <implicit-parameter name="folderId">0</implicit-parameter>
        <implicit-parameter name="struts_action">/document_library_display/view</implicit-parameter>
    </route>
    <route>
        <pattern>/{instanceId}/view/{folderId:\d+}</pattern>
        <implicit-parameter name="struts_action">/document_library_display/view</implicit-parameter>
    </route>
    <route>
        <pattern>/{instanceId}/view/{folderId:\d+}/{name}</pattern>
        <implicit-parameter name="struts_action">/document_library_display/view_file_entry</implicit-parameter>
    </route>
</routes>

This single file, which only takes about 10 minutes to write, is all that's required to add industrial strength friendly URL mapping to the document library display portlet.

The format of this file is fairly self-explanatory. All the routes for a portlet are listed in the order they should be matched against a URL, which is the same order Liferay will attempt to use them when constructing a new friendly URL from a parameter map.

The string inside <pattern>...</pattern> is the URL pattern described above. <implicit-parameter>'s serve two roles. When a URL is parsed, the implicit parameters for the matching route will be copied onto the parameter map. When a URL is built, the implicit parameters for a route must match the portlet URL parameters before that route will be used.

In the example above, if a portlet URL is created with the struts_action parameter set to "/document_library_display/view", the second route will be used. If struts_action is set to "/document_library_display/view_file_entry", the third route will be used.

If you are familiar with the usual contents of portlet URLs in Liferay, you will know that they usually contain at least the following parameters:

  • p_p_id
  • p_p_col_id
  • p_p_col_pos
  • p_p_col_count
  • p_p_lifecycle
  • p_p_state
  • p_p_mode

DefaultFriendlyURLMapper automatically hides all of these, and only displays them if they are set to a value other than the default. For instance, p_p_mode will be hidden if it is set to "normal", but will be shown if set to "maximized". This system makes your URLs much cleaner without breaking functionality.

Generated Parameters

Up to this point, parameters from the portlet URL have only been directly mapped to fragments of the friendly URL path. Using generated parameters, much more complex mappings are possible. Examine the route definition below:

<route>
    <pattern>/{jspPageName}</pattern>
    <generated-parameter name="jspPage">/{jspPageName}.jsp</generated-parameter>
</route>

In this example, a portlet URL is created with the jspPage parameter set to "/view_profile.jsp". When the friendly URL is generated, the jspPage parameter is automatically parsed using the pattern specified in the generated-parameter option, and the jspPageName "virtual parameter" is set for use in the friendly URL pattern. The resulting friendly URL will be "/view_profile".

When this friendly URL is recognized, the opposite process takes place. First, jspPageName is parsed from the URL. The router then constructs the jspPage parameter using its pattern string and the jspPageName. When the parameters are passed to the portlet, only jspPage is set, making the mapping process entirely transparent.

Advanced Features

Routes support two advanced features that make them even more flexible. The ignored-parameter option forces a parameter to never be shown in the query string. The overridden-parameter options always sets a parameter to the given value when a URL is parsed, but does not set any requirements on the contents of the portlet URL parameters when a URL is generated.

The example below uses these two options together to ensure that p_p_lifecycle is never placed in the query string, and that it will always be set to 1 (action phase) when the URL is parsed.

<route>
    <pattern>/rss</pattern>
    <ignored-parameter name="p_p_lifecycle" />
    <overridden-parameter name="p_p_lifecycle">1</overridden-parameter>
</route>

The difference between this setup and using a single implicit-parameter is subtle. The route above will be used to build a URL even if p_p_lifecycle is set to 0, whereas an implicit-parameter would require it to be 1.

When should you use ignored parameters or overridden parameters instead of implicit parameters? Generally, ignored and overridden parameters should only be used to support legacy applications. Best practice dictates that you always set the portlet URL parameters exactly as you want them to be received later, without depending on the friendly URL mapper to override them. For new portlets, you should simply remove irrelevant parameters from your portlet URLs, and set important parameters to the proper values.

Instanceable Portlets

Before Liferay 6, it was very difficult to add friendly URL mapping to an instanceable portlet (a portlet that can be added multiple times to the same page). With friendly URL routes, it is now trivial, in fact you've already seen how to do it earlier in this article.

DefaultFriendlyURLMapper automatically populates two special parameters for instanceable portlets: p_p_id and instanceId. As you can see in the routes.xml file for document library display above, simply insert one of these two parameters as a fragment in each route, and the rest is handled for you automatically.

Showing 25 Comments

Rob Sonke
7/12/10 12:41 AM

Very cool feature. I always disliked the long and ugly urls with for most of the time useless params.

Alexander Chow
7/12/10 1:47 AM

Hey Connor, nice improvement of the friendly URL mapper. Much easier than programmatically writing out all of those mapper implementations!

Wilson Man
7/12/10 5:49 AM

very nice feature! and great (and clear) post!

Jonas Yuan
7/12/10 7:15 AM

Hi Connor, nice improvement of the friendly URL mapper.Thanks.

Ivan Cheung
7/12/10 8:00 AM

nice job Connor

Gregory Amerson
7/12/10 8:06 AM

Killer feature! I can't wait for liferay.com to get this enhancement emoticon

Julio Camarero
7/13/10 1:04 AM

Really nice Connor! this makes things really simpler emoticon

Suhail Ahmed
7/13/10 10:01 PM

I liked it, Simple is the best

Sampsa Sohlman
7/25/10 12:36 PM

This is useful feature. Basically every portlet needs to have FriendlyURLMapper and this reduces the work significantly.

Are you going back port this to 5.2 EE also?

Connor Mckay
7/26/10 9:58 AM

Hi Sampsa, I'm afraid there are no plans to back port this feature because it requires changes to several APIs that could break backwards compatibility.

Dana Oredson
7/29/10 8:07 AM

Will document library start using this by default soon? Currently, it is difficult to hook up Web Trends or similar web analytics in front of liferay, as the document library links use the non-friendly urls, so web trends doesn't understand how many times a document has been downloaded (the parameters are portlet-specific)

Dana Oredson
7/29/10 8:13 AM

Umm, I think I spoke too soon. I have LR 6.0.4 running and the links to documents are friendly, now. Snap, this is huge!
http://localhost:8082/documents/10154/10729/character_sheet.pdf

Babu Dobwal
11/10/10 3:32 AM

Hi,
I have create portlet using Spring(3.0) Annotations and i have deployed it in liferay server(6.0.5), It is working fine.

Now i need to do configuration for friendly url(for my portlet).
Is I need to follow same document for configuration the same?
Please reply.

Babu Dobwal
11/10/10 3:51 AM

I have followed the same document for configuration friendly url for my portlet(my portlet build using Spring 3.0 Annotations).
But it is not showing friendly url.

Please reply if any body have any idea ?....

Connor Mckay
11/10/10 11:26 PM

Hi Babu,

I am not familiar with Spring 3.0 Annotations, but I did just try modifying the Liferay's sample-spring-portlet to use friendly url routing, and it worked flawlessly. Perhaps you could post an example of a URL that you are trying to map, along with the route definition you would like it to map to?

Thanks,
Connor McKay

Babu Dobwal
11/18/10 9:40 PM

Hi Connor,
Thanks for reply. I did some mistake, That why friendly url was not coming.
But now i am getting some other problem. My portlet have two state like View & Edit. So now I am not able to go in Portlet Edit mode. When ever I am trying to go in Edit mode of my portlet, The portlet getting refreshed but still it is showing View Mode screen only(but now friendly url is coming). This problem only coming after configuration the friendly URL for this portlet.
I thing I am making mistake in writing url pattern in xml. Can I get any help on this.

MyPortlet Default URL in View state is:

http://www.mandovimaruti.com:8080/new-vehicles?p_p_id=vdbasedoninstallbaseidp­ortlet_WAR_vdbasedoninstallbaseidportlet_INSTANCE_bOM3&p_p_lifecycle=0&p_p_state­=normal&p_p_mode=view&p_p_col_id=column-1&p_p_col_count=2

Friendly url expected in Edit State is:

http://localhost:8080/new-vehicles/-/vdbasedoninstallbaseidportlet/bOM3/view

MyPo­rtlet Default URL in Edit state is:
http://www.mandovimaruti.com:8080/new-vehicles?p_p_id=vdbasedoninstallbaseidp­ortlet_WAR_vdbasedoninstallbaseidportlet_INSTANCE_bOM3&p_p_lifecycle=0&p_p_state­=normal&p_p_mode=edit&p_p_col_id=column-1&p_p_col_count=2


Friendly url expected in Edit State is:
http://localhost:8080/new-vehicles/-/vdbasedoninstallbaseidportlet/bOM3/edit


The liferay-portlet.xml file content like:-

….........................................
…...............................­..........
<friendly-url-mapper-class>com.liferay.portal.kernel.portlet.DefaultFr­iendlyURLMapper</friendly-url-mapper-class>
<friendly-url-mapping>vdbasedonins­tallbaseidportlet</friendly-url-mapping>
<friendly-url-routes>com/indy/portlet­/vehicle/inventory/vdbasedoninstallbaseidportlet-friendly-url-routes.xml</friend­ly-url-routes>

…...............................
…................................

T­hanks & Regards
Babu Dobwal

Babu Dobwal
11/18/10 9:43 PM

Plz consider "www.mandovimaruti.com" instead of "localhost:8080" in above reply.

MyPortlet Default URL in View state is:

http://www.mandovimaruti.com:8080/new-vehicles?p_p_id=vdbasedoninstallbaseidp­ortlet_WAR_vdbasedoninstallbaseidportlet_INSTANCE_bOM3&p_p_lifecycle=0&p_p_state­=normal&p_p_mode=view&p_p_col_id=column-1&p_p_col_count=2

Friendly url expected in Edit State is:

http://www.mandovimaruti.com:8080/new-vehicles/-/vdbasedoninstallbaseidportle­t/bOM3/view

MyPortlet Default URL in Edit state is:
http://www.mandovimaruti.com:8080/new-vehicles?p_p_id=vdbasedoninstallbaseidp­ortlet_WAR_vdbasedoninstallbaseidportlet_INSTANCE_bOM3&p_p_lifecycle=0&p_p_state­=normal&p_p_mode=edit&p_p_col_id=column-1&p_p_col_count=2


Friendly url expected in Edit State is:
http://www.mandovimaruti.com:8080/new-vehicles/-/vdbasedoninstallbaseidportle­t/bOM3/edit

yaragad from Spain
4/28/11 7:33 AM

How can I Modify document-library-display-friendly-url-routes.xml ?? Because its inside a jar...

I want to add ignored-params.

Connor Mckay
5/5/11 9:07 AM

I believe you will need to use an ext-plugin to do this, as there is currently no way to replace routes definitions from within a hook (although this may change in the future). Take a look at this documentation: http://www.liferay.com/documentation/liferay-portal/6.0/development/-/ai/ext-plu­gins

5/23/11 3:26 PM

[...] Hi, Take a look here: http://www.liferay.com/web/connor.mckay/blog/-/blogs/5262286, may help you... but it's for LR 6.x Marcar como respuesta [...] Read More

6/2/11 4:24 AM

[...] Hello Mani, If the route element is correct, try to replace implicit-parameter with ignore-parameter for p_p_lifecycle. Probably you already have seen this blog, but... :... [...] Read More

alex wom
9/7/11 12:18 AM

I tried this but it doesn't work in my example. I'm trying with a MVC plugin portlet not instatiable (LR 6.0.6) I generate a render url from my view.jsp I obtain something like this: http://localhost:8080/it/web/guest/home/-/docweb-display/12345/
but when I activate this URL I'm not able to get the number. In the rules I put:
<route>
<pattern>/{docweb:\d+}/</pattern>
<implicit-parameter name="p_p_lifecycle">0</implicit-parameter>
<implicit-parameter name="p_p_state">maximized</implicit-parameter>
</route>
But when in my doView method I perform
String docWeb = ParamUtil.getString(renderRequest, "docweb");
my docWeb variable is empty. Any suggestion?

Rohit Chaudhari
9/12/11 1:08 AM

Hi All,

For sending parameters between Portlet A on 1st page to Portlet B1,B2,B3 on second page
We have tried the way you have mentioned,
We updated the B1 portlet's entry in liferay-portlet.xml file as follows:
<portlet>
<portlet-name>B1</portlet-name>
<friendly-url-mapper-class>com.l­iferay.portal.kernel.portlet.DefaultFriendlyURLMapper</friendly-url-mapper-class­>
<friendly-url-mapping>view</friendly-url-mapping>
<friendly-url-routes>com/test/­routes.xml</friendly-url-routes>
<instanceable>false</instanceable>
</portlet>

then­ we added a routes.xml as follows:
<routes>
<route>
<pattern>/{param1}</pattern>
</route>
</routes>

Now we changes the URL part in the portlet A as follows:

<a href="/<second page URL here>/-/view/${paramValue}"> Click here </a>

On clicking on the link we are able to see the appropriate URL in the browser with proper parameter values.

To get the parameter values In Portlet B1, render method we did

renderRequest.getParameter("param1");
but we are getting the param1 value as null. We are not sure what we are doing wrong.
It seems, we are very near of the solution. Your help will be appreciated.
Thanks,
Rohit

9/13/11 3:36 AM

[...] I don't know about Spring portlet. But this blog will help you lot. For reference go through this link Mark as an Answer [...] Read More

Connor Mckay
9/14/11 10:18 PM

Alex, try leaving off the trailing slash in your routes. I'm not certain if that is always passed through to the route matching stage.

Rohit, are you able to generate the desired URL from a normal render URL tag without manually constructing it as you showed above?