
FriendlyURLMapper
Standard portlet URLs are anything but succinct.
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