User-specific versus shared portlet preferences - part 2
Technical Blogs August 10, 2014 By Peter Mesotten
This article is the second and last in a series of 2 posts on how personalization can be achieved in a portal project. In this post I'll explain how to combine user-specific and shared portlet preferences in one portlet. This will allow portlets to have a common, shared set of preferences and additionally a set of user-defined preferences on top of those shared preferences.
Recap: preferences scopes
In the first part, the standard portlet personalization concepts were explained with regard to portlet preferences. We learned that preferences can be configured to be stored in a certain scope. Such scope enables preferences to be shared across users and/or portlet instances.
Choosing the right scope depends on the use case that the portlet tries to fulfill. If portlet preferences should only be modified by an administrative role, it makes sense to share this configuration throughout the site that the portlet lives in (or maybe even throughout the portal). If individual users should be able to modify the behavior, content or visualization of the portlet, it makes more sense to store preferences on a per-user basis. In this case, each user sees his or her own version of the portlet.
By configuring a combination of properties in liferay-portlet.xml we can easily change the preferences storage scope. However, this scope applies to allpreferences. By default, there is no way to store some preferences in one scope and some preferences in another. This is nevertheless a valid use case.
Consider the QuickLinks portlet of the first part of this post series again. If we configure the per-user quick links, shared across portlets setting we can allow each user to define his or her own quick links. But maybe the portlet always needs to have a set of general quick links, defined by a site administrator, that should not be modified nor deleted by normal users. Normal users are only allowed to define additional quick links on top of these common links.
In this example, we clearly need a solution that requires preferences to be stored in multiple scopes at the same time, i.e. a user-specific scope and a more general, site-level scope. We cannot achieve this by configuration alone. Instead, we'll have to reside to custom implementation.
Our multi-scope preferences API
We wrote a custom, reusable implementation layer on top of the preferences service layer of Liferay and called this the ScopedPreferencesService.This interface has different methods, corresponding to the different scope configurations we identified in the previous post.
Every method returns an actual javax.portlet.PortletPreferences object. So we can interact with this object as if it was coming directly from the PortletRequest. The difference is that, when you call the store method on the object, Liferay will not look at liferay-portlet.xml to know in which scope to save the preferences. This is now the responsibility of our custom service. Explaining in detail how this is done, will lead us to far. Check out the Github code if you're interested in the internal logic.
In this example we'll write a portlet that is able to display the content of an RSS feed. Every user can configure a personal RSS feed. If a user does not have configured an RSS feed by himself, a general RSS feed will be used that is configurable by the site administrator.
In our portlet class, we have a doView method that first tries to retrieve the user specific RSS feed. If this one is not defined, the site scoped RSS feed will be used. Apart from that, we have two processAction methods: one for storing the per-user RSS feed (configurable by every site member) and one for storing the site-wide RSS feed (configurable only by the site administrator).
Your portlet must be non-instanceable if you want to use the multi-scope preferences API. This means that you need to set the instanceable parameter to false in liferay-portlet.xml. As this is the default value, you could also just omit the parameter altogether.
If you use the ScopedPreferencesService, it's no longer important what preference scope related parameters are set in liferay-portlet.xml. Instead, you'll need to decide for yourself what preferences are stored in what scope by calling the right method from the ScopedPreferencesService.
By creating a layer on top of the preferences API of Liferay we are now able to build portlets that store their preferences in multiple scopes at the same time. This further enhances the personalization aspect of Liferay, one of the key features of the platform.
Check out or clone the code in Github: https://github.com/limburgie/scoped-preferences-service.
If you like this solution or, even better, have some suggestions for improvements, please drop a comment or spread the word via Twitter (@pmesotten).