« Back to Friendly URLs

FriendlyURLMapper

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.

Changes in FriendlyURL in 5.1.1 #

This information is largely irrelevant when using the new friendly URL routes system in Liferay 6, but is kept here for legacy purposes.

It is important to note that changes in Liferay 5.1.1 added a friendly URL delimiter to the URL in order to avoid clashes with Layout friendly URLs.

Previously, if someone made a page with path:

/web/guest/community/forums/message_boards

Liferay could not distinguish between it and the Portlet Friendly URL

/message_boards/category/243728

The solution was to have a delimiter, and after considering many options, /-/ was the winner.

Now there is no collision:

/web/guest/community/forums/message_boards/-/message_boards/category/243728

0 Attachments
37614 Views
Average (5 Votes)
Comments

Showing 20 Comments

François Cassin
11/28/08 2:46 AM

Would it be possible to have more information on the buildPath method please ?

I am unable to get custom parameters from the portletURL, where does it come from ?

Luca Preziati
6/9/09 5:06 AM

The problem can be that you build a friendlyUrlMapper for non instanziable portlet while yours are instanziable.
Do you set the correct portletId: for example

String portletId = parts[2];
if (Validator.equals(portletId, _PORTLET_ID))
portletId = _PORTLET_DEFAULT_INSTANCE;


addParam(params, "p_p_id", portletId);

and this part for passing correct parameter:

String namespace = StringPool.UNDERLINE + portletId + StringPool.UNDERLINE;


if(parts.length == 4 || parts.length == 5){
if(parts[3].compareTo("-")!=0)
params.put(namespace + "tagsSelected", new String[] { parts[3]});
}

if(parts.length == 5 )
params.put(namespace + "keywords",new String[]{parts[4]});



params.put(namespace + "struts_action",new String[] { VIEW_STRUTS_ACTION});

Denis O'Sullivan
10/23/09 3:44 PM

Hi does Liferay 5.2 support portlets on the same page? I have 2 portlets on the same page and both need to translate the SEO friendly url.

If the 2 portlets are on separate pages, all works ok and my FriendlyUrlMapper implemenation works fine for each
http://localhost:8080/web/guest/mypage/-/portletA/friendlyname
http://localhost:8080/web/guest/mypage/-/portletB/friendlyname

But if both on same page I can't get it to work. I'm not sure how to define the url with both on the same page but assumed the following: http://localhost:8080/web/guest/mypage/-/portletA/portletB/friendlyname

Since I assumed portletA and portletB would each be triggered in turn. ...This url may be wrong and may be part of the problem.

When both portlets are on the same page then it appears only 1 has their FriendlyUrlMapper populateParams() method called. And that is the one which appears first after the Liferay dash
i.e. in the following case only portletA populateParams() method is called
http://localhost:8080/web/guest/mypage/-/portletA/portletB/friendlyname

...and in the following case only portletB populateParams() method is called
http://localhost:8080/web/guest/mypage/-/portletB/portletA/friendlyname

any help/workarounds is greatly appreciated.

Denis O'Sullivan
10/29/09 5:50 AM

Hi all, I got my answer "If you are trying to hit two portlets on the same page at once with one URL, the FriendlyUrlMapper is not the way to do. FriendlyUrlMapper hits one portlet at a time."

Naga Surya Dhanunjaya Rao Sriapthy
1/21/10 8:25 AM

Then please guide me the way how to create friendly URL to the two different porltets in the same page.
Thanks in advance.

Andrew Koltsov
4/8/10 3:48 AM

any guilde for JSF portlets?

Alex Wallace
4/12/10 2:48 PM

Does it not work for JSF ones?

Muhammed Shakir AK Misarwala
7/28/10 1:26 AM

Hi,
I have created a custom portlet using portlet plug-in, also mapped the friendly-url-routes file in liferay-portlet.xml file. There is no error or exception thrown while deploying or running however it does not give me the value of the parameter in my jsp. The routes.xml looks like this:
<route>
<pattern>/{instanceId}/{programId:\d+}</pattern>
<implicit-para­meter name="p_p_lifecycle">0</implicit-parameter>
<implicit-parameter name="struts_action">/material/material_view
</implicit-parameter>
</route>
Now­ when I type the following url in my browser: http://localhost:8080/web/guest/course-material/-/coursematerial/3223
I am expecting
long prId = ParamUtil.getLong(request, "programId"); to give me the programId as 3223. But it does not.

Any help or pointer to where I am going wrong will be of great help. Stuck on this since many days.

Muhammed Shakir AK Misarwala
7/28/10 3:06 AM

Please note that I am doing all this in custom portet in plugin environment

Alberto Gallardo
10/19/10 2:05 AM

Typo:

The PROLOG for the file
document-library-display-friendly-url-routes.xml
contains a typo:
<!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">]

The square braquets should be removed.

Alberto Gallardo
10/19/10 2:35 AM

I tried to edit the wiki, but the failure is not in the source code!

Could it be a bug in the wiki component?

Regards

Raja Nagendra Kumar
11/9/10 4:23 AM

Is ant once successful in making this work in 6.x of Liferay..

We tried to this but nothing seem to work..
Any ways to debug..

Babu Dobwal
11/10/10 3:35 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:49 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 ?....

Alberto Gallardo
11/19/10 8:15 AM

Well, this has nothing to do with FriendlyUrlMapper: you need to use "public render parameters," and they are independent of the "FriendlyUrlMapper."

I have tested public render parameters and FriendlyUrlMapper together in LR 6.0.5 with non-instanceable portlets, and it works.

Raja Nagendra Kumar
11/23/10 9:55 PM

Alberto,how does we make a URL like /home/-/sales/2010 land in home page with all the portlets having access to /sales/2010.

Raja Nagendra Kumar
12/4/10 8:44 PM

Basically how to apply SEO friendly URL's to Pages instead of portlets..

Also, once page is able to support friendly URL, can all the portlets on this page know what is the page URL including Query String.

For SEO, we think page URL being friendly would be more important than portlet URL's being more friendly.

Flávio Stutz
12/17/10 1:46 PM

Is it possible for a friendly url mapper to send the parameters to portlets as Public Render Parameters?

Multiple portlets on the same page that supports the same public param could read the same parameter set in FriendlyURL mechanism. Both would have a route.xml like <pattern>/${myPublicParam}</pattern>. When I hit a page with /-/valueOfPublicParam, both could get it by renderRequest.getParameter("myPublicParam").

Is it possible?

See my request at http://issues.liferay.com/browse/LPS-4023

Rajendra Mhetre
3/7/11 7:47 AM

Hi Shakir,
I am facing the same problem and by any way its not working.
Can you please share the progress on this.

Thanks

lpu123 xyz
8/28/11 8:07 PM

anyone how to write a route for:

http://localhost:8080/web/guest/home?
p_p_id=slogancontest_WAR_slogancontes­tportlet&
p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&
p_p_col_id=column-2&p_­p_col_count=1&
_slogancontest_WAR_slogancontestportlet_jspPage=%2Fhtml%2Fsearch.­jsp&
_slogancontest_WAR_slogancontestportlet_redirect=%2Fweb%2Fguest%2Fhome&
_sl­ogancontest_WAR_slogancontestportlet_groupId=10181&
_slogancontest_WAR_slogancon­testportlet_keywords=myslogan123

I have tried with the following, but it does not work (due to "redirect and "groupId" ?!):
<route>
<pattern>/BLA_search/{groupId:.\d+}/{keywords:.*}</pattern>
<implicit-param­eter name="redirect">/web/guest/home</implicit-parameter>
<implicit-parameter name="jspPage">/html/search.jsp</implicit-parameter>
</route>