Recent Bloggers

Olaf Kock

Staff
89 Publications
15 décembre 2014

David Kubitza

2 Publications
14 décembre 2014

Meera Prince

19 Publications
4 décembre 2014

James Falkner

Staff
100 Publications
2 décembre 2014

Juan Fernández

Staff
17 Publications
28 novembre 2014

Andrea Di Giorgi

2 Publications
26 novembre 2014

Gregory Amerson

Staff
26 Publications
25 novembre 2014

Cody Hoag

Staff
6 Publications
25 novembre 2014

Duke H

Staff
2 Publications
24 novembre 2014

Nicholas Gaskill

Staff
1 Publications
21 novembre 2014

User-specific versus shared portlet preferences - part 2

Technical Blogs 10 août 2014 Par 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.

See also User-specific versus shared portlet preferences - part 1.

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.

Problem situation

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.

  • getPortalWidePreferences(PortletRequest)
  • getSiteWidePreferences(PortletRequest)
  • getPortletSpecificPreferences(PortletRequest)
  • getUserSpecificPreferences(PortletRequest)
  • getUserSpecificPortletSpecificPreferences(PortletRequest)

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.

Example

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).

QuickLinksBean.java
public class QuickLinksPortlet extends MVCPortlet {
 
    private ScopedPreferencesService prefService = new ScopedPreferencesServiceImpl();
 
    @Override
    public void doView(RenderRequest request, RenderResponse response) {
        String feedUrl = prefService.getUserSpecificPreferences(request).getValue("feedUrl", "");
        if (feedUrl.isEmpty()) {
            feedUrl = prefService.getSiteWidePreferences(request).getValue("feedUrl", "");
        }
        renderRequest.setAttribute("feedUrl", feedUrl);
        super.doView(request, response);
    }
 
    @ProcessAction(name="storeUserPrefs")
    public void storeUserPrefs(ActionRequest request, ActionResponse response) {
        String feedUrl = request.getParameter("feedUrl");
        PortletPreferences prefs = prefService.getUserSpecificPreferences(request);
        prefs.setValue("feedUrl", feedUrl);
        prefs.store();
    }
 
    @ProcessAction(name="storeSitePrefs")
    public void storeSitePrefs(ActionRequest request, ActionResponse response) {
        String feedUrl = request.getParameter("feedUrl");
 
        PortletPreferences prefs = prefService.getSiteWidePreferences(request);
        prefs.setValue("feedUrl", feedUrl);
        prefs.store();
    }
 
}

Prerequisites

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.

Conclusion

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).

User-specific versus shared portlet preferences - part 1

General Blogs 15 mai 2014 Par Peter Mesotten

This article is the first in a series of 2 posts on how personalization can be achieved in a portal project. Moreover, the concept of sharing personalized settings between users is explained and how this behavior can be changed in Liferay.

See also User-specific versus shared portlet preferences - part 2.
 

Personalization

One of the most important features of a portal is personalization, or the ability for users to change the behavior, content or visualization of portlets. In most projects we've done, customers are very skeptical about personalization. For those customers, enabling personalization equals enabling users to mess things up and call for support afterwards. We believe however that personalization, if implemented the right way, creates a dynamic experience that will increase the usage and ultimately extend the lifetime of the portal.
 

Preferences scopes

The Portlet 2.0 specification (JSR-286) enables personalization through the definition of portlet preferences. According to the spec, preferences are simple key-value pairs that are stored for each portlet and for each user. This means that two users who look at the same portlet will see two different versions of that portlet, according to the preferences they have set on that portlet.

Since Liferay implements JSR-286 you would expect Liferay to store preferences on a per-user basis too. This is however not the case. By default, Liferay stores a portlet's preferences on a per-portlet basis. This means that all users will see the same preferences for a certain portlet instance. Luckily, the scope in which preferences are stored can be changed. For this, we need to update the portlet's liferay-portlet.xml. There are 3 properties that are related to the storage of portlet preferences:

  • preferences-company-wide
  • preferences-unique-per-layout
  • preferences-owned-by-group

The first configuration parameter is the easiest one to understand. If preferences-company-wide is set to true, the preferences for the portlet are stored portal-wide. Every instance of the portlet on every page within every site will have the same preferences. Changing the preferences in one place will change the portlet in all other places. If this parameter is true, the other configuration parameters are ignored.

The other 2 parameters, preferences-unique-per-layout and preferences-owned-by-group, can both be true or false as well. In this case, the combination of these settings will define the scope in which the preferences for a portlet are stored. 

If we do the math, we end up with 5 options for storing portlet preferences. The implications of those options can best be explained by giving an example.
 

Example: QuickLinks portlet

Consider a custom portlet that can be configured to show one or more links to internal or external pages, resources or applications. The set of links that should be visible is configured using portlet preferences. In this case, the preferences scope configuration will decide which quick links will be visible for which users. 

  • Portal-wide quick links (preferences-company-wide=true)
    In this case, all portal users will see the same set of quick links throughout the portal. So even if there are multiple QuickLinks portlets on different pages, they will all show the same links. Usually, you won't allow every user to update company-wide preferences, because users will start to override each other's preferences. It is more common to assign this task to a global administrator.
     
  • Site-wide quick links (preferences-company-wide=false, preferences-unique-per-layout=false, preferences-owned-by-group=true)
    This case behaves very much like the previous one. Except for the fact that the links will be the same in portlets on pages within one site. Each site can thus show its own quick links. Again, it is not recommended to let every site member update these site-wide preferences. The ideal person to manage these kinds of preferences is the site administrator.
     
  • Per-portlet quick links, shared across users (preferences-company-wide=false, preferences-unique-per-layout=true, preferences-owned-by-group=true)
    In this case, every portlet instance has its own preferences. So we can have different QuickLinks portlets in different pages and even in different sites and each of them will show different links. For a given portlet instance however, all users will see the same links. Again it's mostly the site administrator who will manage these links or maybe a small group of users that will manage different parts of a site. Remember that this is the default setting, so it will be used if no configuration is set in liferay-portlet.xml.
     
  • Per-user, per-portlet quick links (preferences-company-wide=false, preferences-unique-per-layout=true, preferences-owned-by-group=false)
    In this case, every user will be able to store a dedicated portlet configuration for each portlet instance separately. So each user can decide for himself which links are visible in the portlet. If there are multiple QuickLinks portlets, the configured links can be different in each portlet. The user must of course be given the appropriate permissions to allow him to update the preferences (enable the PREFERENCES action on the portlet resource).
     
  • Per-user quick links, shared across portlets (preferences-company-wide=false, preferences-unique-per-layout=false, preferences-owned-by-group=false)
    In case there are multiple QuickLinks portlet instances on different pages or even different sites, this setting will share a user's preferences between all those instances. So if a user changes the quick links in one portlet, they will be updated in all other QuickLinks portlets as well. Still, the preferences are not shared across different users.
     

Conclusion

It is clear that Liferay's addition to the Portlet 2.0 specification with regard to portlet preferences enables a great amount of flexibility. Defining the scope in which preferences should be stored is an important decision that needs to be taken during functional analysis, not during development.

One limitation of this is that it seems impossible to store preferences in multiple scopes at the same time. Indeed, the liferay-portlet.xml configuration is applied to all preferences. Sometimes however, it could be useful to store default preferences on a global level (e.g. portal-wide or site-wide), while allowing end-users to override this global configuration with custom preferences. In case of the QuickLinks portlet, an administrator could provide a default set of quick links that are used if a user has not set his own quick links. The next part of this series will explain in detail how this can be achieved in a clean, reusable way.

Inject any custom class or service into web content templates

General Blogs 15 avril 2014 Par Peter Mesotten

Problem situation

Web content templates are easy to write in Liferay, but as they become more complex they tend to contain a lot of scripting. Moreover, complex Velocity or Freemarker scripts are hard to maintain and even harder to debug. Also, unit testing your scripts is impossible.

Macros

A first possible optimization is to use macros. Generic script blocks can be captured into a macro so the same logic can be used multiple times in your script. You can even reuse macro definitions across multiple templates by defining a generic macro template and including (parsing) that template into other templates that want to use those macros.

However, macros are not that flexible either. References to macro calls are basically replaced at parse time, so they can not really be used as functions. Maintaining the scripts remains difficult and you still don't get to unit test your macro scripts. For complex functionality, writing Java code is clearly the preferred approach.

Velocity tools

Ray Augé of Liferay proposed a way to write custom Velocity utilities in his blog post Custom Velocity Tools. The approach was to create an empty hook project with a proper configuration and to create interface/implementation pairs for each tool you wanted to expose. Those tools could then be referenced with the $utilLocator variable in Velocity or Freemarker templates:

my-template.vm
#set ($sayHelloTool = $utilLocator.findUtil("velocity-hook", "be.aca.literay.tool.SayHelloTool"))
 
$sayHelloTool.sayHello("Peter")

However, there are some limitations with this approach. At deploy time, all Velocity tools are put into a custom context, in which only these tools are accessible. There is no way to use normal beans, loaded by an application context, as Velocity tools. Ideally, when using Spring, you want to scan the classpath of your hook for beans and inject those beans into the Velocity context. This is not possible with Velocity tools.

Example

Suppose we have the following service, defined as a Spring component (@Service could be @Named as well, if you prefer using CDI):

SayHelloService.java
@Service
public class SayHelloService {
 
    public String sayHello(String name) {
        return String.format("Hello, %s!", name);
    }
    
}

This service is added to an application context by classpath scanning. This is configured in the applicationContext.xml file of our hook:

applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
 
    <context:component-scan base-package="be.aca"/>
    
</beans>

The goal is to expose and use SayHelloService in our web content templates.

The solution

When deploying a hook with Velocity tools to Liferay, these tools will be stored behind a custom bean locator of Liferay. Check outcom.liferay.portal.bean.BeanLocatorImpl for its implementation. We will create a custom implementation of this bean locator that will search for beans in Spring's application context instead of in the context that Liferay creates for Velocity tools.

Create a custom bean locator

So first we need to implement the com.liferay.portal.kernel.bean.BeanLocator interface. We will only implement the methods that are important for us and let the other methods throw an UnsupportedOperationException.

VelocityBeanLocator.java
@Component
public class VelocityBeanLocator implements BeanLocator {
 
    private static final String SUFFIX = ".velocity";
    
    public Object locate(String name) {
        String realName = stripVelocitySuffix(name);
        return SpringBeanLocator.getBean(realName);
    }
 
    private String stripVelocitySuffix(String name) {
        String realName = name;
        if (realName.endsWith(SUFFIX)) {
            realName = realName.substring(0, realName.length() - SUFFIX.length());
        }
        return realName;
    }
 
    public ClassLoader getClassLoader() {
        throw new UnsupportedOperationException();
    }
 
    public String[] getNames() {
        throw new String[0];
    }
 
    public Class<?> getType(String name) throws BeanLocatorException {
        throw new UnsupportedOperationException();
    }
 
    public <T> Map<String, T> locate(Class<T> clazz) throws BeanLocatorException {
        throw new UnsupportedOperationException();
    }
}

The important method here is the locate(String) method. We redirect this method to SpringBeanLocator, which is a static class that holds the ApplicationContext object of Spring. This context will be initialized at deploy time because it implements the ApplicationContextAware interface of Spring:

SpringBeanLocator.java
@Component
public class SpringBeanLocator implements ApplicationContextAware {
 
    private static ApplicationContext ctx;
 
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String className) {
        try {
            return (T) ctx.getBean(Class.forName(className));
        } catch (BeansException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
 
    public void setApplicationContext(ApplicationContext applicationContext) {
        ctx = applicationContext;
    }
    
}

Replace Liferay's bean locator with your own

We have to tell Liferay that it has to use your bean locator instead of the default Bean Locator implementation. We will replace the original bean locator in a servlet context listener, which we will configure in the web.xml of our project.

VelocityBeanLocatorContextListener.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class VelocityBeanLocatorContextListener implements ServletContextListener {
 
    private static final String CONTEXT_CONFIG = "contextConfigLocation";
 
    private XmlWebApplicationContext ctx;
 
    public VelocityBeanLocatorContextListener() {
        ctx = new XmlWebApplicationContext();
    }
 
    public void contextInitialized(ServletContextEvent sce) {
        initializeContext(sce);
        String servletContext = sce.getServletContext().getContextPath().substring(1);
        PortletBeanLocatorUtil.setBeanLocator(servletContext, new VelocityBeanLocator());
    }
 
    private void initializeContext(ServletContextEvent sce) {
        ctx.setServletContext(sce.getServletContext());
        ctx.setConfigLocation(sce.getServletContext().getInitParameter(CONTEXT_CONFIG));
        ctx.refresh();
    }
 
    public void contextDestroyed(ServletContextEvent sce) {
        ctx.close();
    }
 
}

Line 14 is important here. Here, the bean locator is set to our custom bean locator using the PortletBeanLocatorUtil. The bean locator is only used within the context of this hook, so you don't risk breaking anything in other portlets or hooks. The rest of the code just initializes the application context of Spring.

This context listener has to be configured inside the web.xml so it is called when your hook gets deployed. This is what your web.xml will look like:

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
    
    <listener>
        <listener-class>be.aca.literay.spring.VelocityBeanLocatorContextListener</listener-class>
    </listener>
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:applicationContext.xml</param-value>
    </context-param>
</web-app>

Call your service the normal way in Velocity or Freemarker

When your hook is deployed (don't forget to add an empty liferay-hook.xml file!), you'll be able to call your Spring services by using the utilLocator variable inside Velocity templates.

custom_service.vm
#set ($helloTool = $utilLocator.findUtil("velocity-hook", "be.aca.liferay.SayHelloService"))
$helloTool.sayHello("Peter")

Or, if you prefer using Freemarker:

custom_service.ftl
<#assign sayHelloTool=utilLocator.findUtil("velocity-hook", "be.aca.liferay.SayHelloService")>
${sayHelloTool.sayHello("Peter")}

In the background, the util locator will now pass through your bean locator and your Spring service will be retrieved from the application context, defined in your hook. And we'll have this awesome result:

Mission successful!

Conclusion

The ability to use advanced logic inside web content templates allows you to better separate business logic from presentation logic. Your templates will be much cleaner and only contain presentation logic. Your Java classes will do the heavy work and can be managed, optimized and unit tested properly. Guess this is a win-win situation!

Clone the full example of the code on Github: https://github.com/limburgie/velocity-bean-locator. Your feedback is very welcome!

Classloading in Liferay

General Blogs 20 mars 2014 Par Peter Mesotten

Libraries (or JAR-files if you will) exist in many different locations in your Liferay installation. Some reside in Tomcat's lib folder, others are included in the lib folder of Liferay's ROOT. And of course, there are the libraries that are included in your own portlets. It's no secret that Liferay performs some magic tricks to enable access to different classes in different contexts. This complexity is mostly abstracted away from developers, so you don't have to worry if class X is indeed available for you from within a portlet. However, there are some very specific particularities that you need to be aware of as a Liferay developer. Welcome to the wonderful world of classloading in Liferay!

By the way, understanding the principles in this article might help you in certifying for Liferay, as you're guaranteed to get some questions on this topic in your certification exam!

Classpaths in Liferay

Roughly speaking, 3 different classpaths are active in a Liferay installation.

The global classpath includes all JAR files that are defined in Tomcat's lib and lib/ext folders. All deployed webapps (including ROOT and your own portlets and hooks) have access to these libraries. Examples of included libraries are servlet-api.jar (Servlet API), portlet.jar (Portlet 2.0 API) and portal-service (Liferay Service API). Because they are already available, you don't want to include these libraries in your own portlet or hook. Otherwise, the library will be loaded twice, which in most cases will result in deploy failure.

The portal classpath includes all JAR files from the lib folder of ROOT. These 200+ libraries are only accessible from within the ROOT application itself. Examples of included libraries are portal-impl.jar (Liferay Implementation), util-java.jar, util-bridges.jar, util-taglib.jar, Spring libraries, ...  

The portlet/hook classpath includes all JAR files from the lib folder of the deployed portlet or hook. These libraries are only accessible from within the portlet or hook itself.

Who can access what?

With all those different classpaths, it's important to know which libraries are available in which context.

Inside Liferay's ROOT, classes and JSPs can only access the global and the portal classpath.

Inside a portlet, you have access to both the global and the portlet classpath. This means that both the global libraries and the portlet libraries are loaded together. Or why it is extremely important never to include libraries in your portlet that are already available in the global classpath! If you're using Maven and you still want to compile against those libraries in the global classpath, put the scope of your dependency to provided.

Inside a hook the same rules apply as for portlets. Most hooks have access to both the global and the hook classpath. There is however one exception: JSP hooks. At deploy time, JSPs that are overriden in a JSP hook replace the original JSP in the ROOT. At this point, the JSP can only access the global and portal classpaths, NOT the hook classpath. For instance, it is impossible to let an overridden JSP access a class that's contained in a third-party JAR that's included in the lib folder of the hook.

Sharing libraries between portlets/hooks

If you've been paying attention, you'll agree with me that the only way to share libraries between different portlets is to add those libraries tot the global classpath, i.e. in Tomcat's lib or lib/ext folder. However, generally, this is not a great idea. At least, you should make sure that those libraries don't end up to be duplicated in different classpaths. An example.

Suppose all of your portlets make heavy use of the Spring framework. To not include libraries like spring-context.jar, spring-beans.jar, spring-core.jar in every portlet, you decide to add them to the global classpath in tomcat/lib/ext. But, oh my, suddenly your portal stops working. Why? Because Liferay's ROOT already includes these Spring libraries, and now Liferay tries to load those libraries twice for the ROOT application. Result: conflicts!

If you still wish to share libraries through the global classpath, take the following thoughts into account:

  • Make sure the libraries don't already exist on the portal classpath. If so, forget about sharing these libraries and include them in every portlet or hook that needs them.
  • Make sure the libraries are not included in any of the deployed portlets or hooks. If so, set the scope of these dependencies to provided instead of compile, so they are not packaged in the WAR file.
  • Prepare to lose some amount of flexibility. Indeed, all your portlets and hooks will now have to commit to the same version of the library you're adding. This can be good for consistency, but do you really want to recompile all existing portlets and hooks if you suddenly need to use the latest version of library X in one of them?

Dependency JARs

Liferay does provide a mechanism to gain access to classes in the portal classpath while inside your portlet by the means of portal dependency JARs. This is a property you can set in the liferay-plugin-package.properties configuration file. It accepts a list of JAR files that are to be found in the portal classpath.

WEB-INF/portlet-plugin-package.properties
name=sample-portlet
module-group-id=liferay
module-incremental-version=1
...
portal.dependency.jars=commons-logging.jar,commons-io.jar

At deploy time, Liferay will copy over all matching JARs from the portal's lib folder to the lib folder of the portlet or hook. So you don't actually get access to the portal classpath. Instead, the necessary JAR files are copied over to the portlet/hook classpath.

In some rare cases, it can be useful to define these dependency JARs. Some framework JARs are enhanced by Liferay to enable some specific behavior. And by including the JAR this way, you're sure you have the exact same implementation version of the library. But in general, try to avoid this type of configuration as it tends to create more problems than it solves. If you're using it anyway and your compilation units depend on those JAR's, don't forget to specify these dependencies as provided in Maven. Otherwise, they will be added twice to the portlet's lib folder.

About portal-impl.jar

Being able to include dependency JARs from the portal context into your own portlet or hook effectively allows you to depend on portal-impl.jar in your plugin. Don't. Do. This. The portal-impl.jar library is not meant to be exposed to custom plugins. That's the whole point of having an API (portal-service.jar). And remember what we said about portal-service.jar: you don't explicitely add this library to your portlet as it's already available in the global classloader.

Evaluation time!

If you can solve these questions, you've totally mastered the classloading concepts in Liferay. Feel free to post a comment if you have more questions on the topic.

  1. I created a JSP hook that uses Apache POI to export the list of user groups to Excel through a new button in the UI. Which of the following approaches will work if you know that poi.jar is included in ROOT/WEB-INF/lib?
    1. Add poi.jar to the lib folder of my JSP hook
    2. Add poi.jar to the list of portal.dependency.jars in the portlet-plugin-package.properties file of my JSP hook
    3. Add poi.jar to the lib/ext folder of Tomcat
    4. None of the above
  2. I created a service hook that uses POI to export the list of user groups to Excel each time a new user group is created. Which of the following approaches will work? Multiple answers are possible
    1. Add poi.jar to the lib folder of my hook
    2. Add poi.jar to the list of portal.dependency.jars in the portlet-plugin-package.properties file of my hook
    3. Add poi.jar to the lib/ext folder of Tomcat
    4. None of the above
  3. My portlets always use the Vaadin framework. Which of the following approaches will work if you know that vaadin.jar is included in ROOT/WEB-INF/lib? Multiple answers are possible.
    1. Add vaadin.jar to every portlet's lib folder separately
    2. Add vaadin.jar to the lib/ext folder of Tomcat
    3. Add vaadin.jar to the lib/ext folder of Tomcat and remove vaadin.jar in ROOT/WEB-INF/lib
    4. Add vaadin.jar to the list of portal.depency.jars in the portlet-plugin-package.properties file of my portlets

Visit http://blogs.aca-it.be for more blogs on Liferay and Java.

Database connection pool sharing between portal and portlets

General Blogs 15 février 2014 Par Peter Mesotten

Liferay ROOT uses a database connection pool that manages database connections for all data-related requests inside the portal. Most portlets depend on Liferay's service API, so implicitly they depend on the same pool. However, it is possible that you have to persist your own entities inside a portlet, independent of the existing Liferay entities. E.g. you want to store a Dossier entity with Hibernate. In this case, you have to take care of the persistence layer yourself. Concerning connection management, you have 2 choices:

  • Let your portlet define and use its own connection pool. A disadvantage is that there will be a separate pool for each portlet. Each pool has to reserve a certain amount of memory.
  • Let your portlet use the connection pool of the portal. In this case there's a central place of configuration and the sizing and tweaking of the pool can be done once and for all.

The second approach is clearly the better one. Now, there are 2 ways of sharing the connection pool between portal and portlet. The first one uses merely Spring configuration to assign Liferay's data source to a SessionFactory or EntityManagerFactory. The second approach exposes the portal data source through JNDI, which can then be referenced easily as well.


Approach 1: Spring configuration

In this configuration, database connection properties live inside portal-ext.properties as usual:

portal-ext.properties
jdbc.default.driverClassName=com.mysql.jdbc.Driver
jdbc.default.url=jdbc:mysql://db_prod/liferay_prod?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false&autoReconnectForPools=true&dumpQueriesOnException=true&logSlowQueries=true&explainSlowQueries=true
jdbc.default.username=sa_aca
jdbc.default.password=tesla
jdbc.default.testConnectionOnCheckout=true
jdbc.default.preferredTestQuery=SELECT 1;
jdbc.default.acquireIncrement=1
jdbc.default.maxIdleTime=10800
jdbc.default.maxConnectionAge=14400
jdbc.default.numHelperThreads=20

Beware!

By default, Liferay uses C3PO for connection pooling. This means that you can only use C3PO configuration parameters, such as preferredTestquery. Check here for a parameter overview. If you'd like to use another mechanism, set the jdbc.default.liferay.pool.provider parameter to dbcp or tomcat.

Internally, Liferay will encapsulate these properties into a datasource object. The datasource is exposed by a static factory called InfrastructureUtil. Given that you use a Spring setup, you can configure something like this in the context configuration of your portlet:

applicationContext.xml
<beans ...>
    <context:component-scan base-package="be.vlaanderen"/>
 
    <bean id="dataSource" class="com.liferay.portal.kernel.util.InfrastructureUtil" factory-method="getDataSource" />
 
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="be.vlaanderen.contact.hibernate.model"/>
    </bean>
 
    <tx:annotation-driven />
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>

Let's go through this configuration line by line:

  • First, we do a component scan for @Named or @Component classes. These beans are automatically added to the context.
  • Next, we define a datasource. Notice that we can use InfrastructureUtil of Liferay to get a DataSource object based on the connection parameters specified in the portal-ext.properties.
  • This datasource is then assigned to a SessionFactoryBean. This could as well be a LocalContainerEntityManagerBean, if you prefer using JPA's EntityManager instead of Hibernate's Session syntax.
  • Last but not least, you'd want to enable transaction handling. For this, enable annotation-driven transaction management and configure Spring's transaction manager with the session factory.

Now you can write a DAO or Repository like this:

DossierDAO
@Named
@Transactional(readOnly=true)
public class DossierController implements Serializable, IDossierController {
 
    @Inject private SessionFactory sessionFactory;
 
    public List<Dossier> getDossiers(long companyId, boolean dirty) {
        Criteria criteria = sessionFactory.getCurrentSession().createCriteria(Dossier.class);
        criteria.add(Restrictions.eq("companyId", companyId));
        criteria.add(Restrictions.eq("dirty", dirty));
        return criteria.list();
    }
 
    @Transactional
    public void deleteContact(Dossier dossier) {
        sessionFactory.getCurrentSession().delete(dossier);
    }
}

Beware for the following:

  • Always define an interface for your DAO if you're working with transactions and inject the interface on the beans that want to use the DAO.
  • Set readOnly=true by default on class-level and redefine @Transactional on methods that are not read-only (CUD).


Approach 2: JNDI

This second approach is especially useful if there are multiple data sources to connect to. E.g. some of your portlets might connect to a different database to retrieve their data. First, you'll need to define all datasources inside the context.xml of Tomcat:

tomcat/conf/context.xml
<Context>
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
 
    <Resource name="jdbc/liferay"
        auth="Container"
        type="javax.sql.DataSource"
        maxActive="40"
        maxIdle="20"
        maxWait="1000"
        username="%db_user%"
        password="%db_password%"
        driverClassName="org.postgresql.Driver"
        url="jdbc:postgresql://%db_host%:%db_port%/%db_name%"
        validationQuery="select version();"
    />
 
    <Resource name="jdbc/other" auth="Container" type="javax.sql.DataSource"
          ....
    />
</Context>

Beware!

Tomcat has its own JDBC connection pooling mechanism with its own set of parameters, such as validationQuery. Check this page for more information and an overview of configuration parameters. If you want to use another DB connection pooling mechanism (e.g. C3PO), set the type of the Resource tag to com.mchange.v2.c3p0.ComboPooledDataSource.

Now your portal-ext.properties can reference the JNDI name of the datasource, instead of configuring all properties itself. All other properties will be silently ignored.

portal-ext.properties
jdbc.default.jndi.name=jdbc/liferay

In the Spring configuration of your portlet, reference the necessary datasource(s) and assign them to a Spring bean.

applicationContext.xml
<beans ...>
    <bean id="liferayDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName" value="java:comp/env/jdbc/liferay"/>
    </bean>
    <bean id="otherDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName" value="java:comp/env/jdbc/other"/>
    </bean>
</beans>


Conclusion

  • Know for sure which connection pooling mechanism is active. Tune the configuration parameters appropriately.
  • Use JNDI if there are multiple databases to connect to or if you've deployed other web applications inside Liferay's Tomcat.
  • Use InfrastructureUtil to quickly get a reference to the portal-level datasource object.

Liferay 6.2 - What's new under the hood?

General Blogs 3 février 2014 Par Peter Mesotten

The release of Liferay 6.2 will be remembered as "The one with the complete UI overhaul". As much as the marketing machine of Liferay.com likes to focus on the skin of Liferay Portal, it is equally important what has changed under the hood. And how these changes on the inside affect the product on the outside. This article presents the coolest new features in Liferay Portal 6.2 EE regarding responsive design and web content management that will help both customers and implementors to create better portal solutions.
 

Responsive design

Responsive design allows the developer to create one design that scales automatically across different (mobile) devices and resolutions. Before Liferay 6.2 it was already possible to use responsive design for page layouts and theme templates. Page layouts in Liferay are column-based portlet containers, so they perfectly lend themselves to the column-based grids that most responsive frameworks are based on. Liferay themes give the developer full control over HTML, CSS and Javascript, so the inclusion of responsive frameworks is seamlessly.

At ACA, we've been implementing responsive design with Twitter Bootstrap for some time now. Bootstrap has become more or less the de facto standard if it comes to responsive web design. Our projects requiring mobile support implemented Bootstrap by implementing page layouts following the Bootstrap grid and themes that included Bootstrap CSS and Javascript. 

So what's changed? In Liferay 6.2 responsive layouts are first-class citizens. All page layouts were rewritten as Twitter Bootstrap grids and the default Liferay theme was overhauled to conform to Bootstrap CSS guidelines and components. Specifically, this has the following consequences:
 

  • Pages in Liferay have a fluid width, filling the entire screen.
  • At certain break points, the columns are split so the grid is converted to a stack.
     

  
 

But the fun doesn't stop there. Even the UI of the Control Panel, Liferay's administrative module, was rewritten in Bootstrap allowing administrators to manage their sites from any device. That being said, most administrative tasks will still be easier to accomplish on a desktop/laptop/tablet than on a mobile device. But updating, say, a user's roles on a smartphone is no longer impossible.
 

 
 

This all means that your custom mobile-ready themes and page layouts will blend wonderfully with the look-and-feel of Liferay's default theme and Control Panel.
 

Mobile previews

While properly implemented page layouts and themes form the basis of a responsive interface, the content itself is equally important. Until now it was difficult to know on beforehand if the content an editor created was looking good on handhelds too. Liferay 6.2 aids in this process by giving you the ability to show a preview of a page for different device types and resolutions.
 


 

This gives the content editor a good first look on how the final pages will look like on smartphones and tablets as well as on desktops with different resolutions.
 

Improved WCM

Liferay is not just a portal. It packages with a full-fledged Web Content Management module as well. And this WCM has become much more powerful again in Liferay 6.2.
 

Dynamic data structures

In Liferay 6.1 the concept of dynamic data lists was introduced. These allowed administrators to define custom data definitions, such as a Task or an Event. Through a couple of portlets, the portal allowed users to manipulate data based on these definitions using CRUD operations. The ultimate goal of this was to create and present user-managed data lists, hence the name of the concept.

Conceptually there was an overlap between dynamic data definitions and web content structures. Both were structured data containers that could be attached to a workflow. The advantage of structures was that its data could be internationalized and categorized. Moreover there were more publication and search indexing options. The disadvantage was that structures supported few data types and a limited form of data validation. E.g. there was no "Date" field and the only available validation option was "Required" validation.

Liferay 6.2. web content structures don't only feel like dynamic data definitions. Both concepts now share, in fact, the same underlying framework. This means that all fancy features of data definitions are available for creating web content types. In addition, web content structures didn't give away their own powerful features such as categorization and internationalization.
 


 

Structure forms can be created by drag-and-dropping and configuring ready-to-use components such as input fields, radio buttons, file uploads, ... Structure field labels can now also be translated in multiple languages.
 

Web content folders

In the Control Panel, web content items used to be organized in one flat list. True, the search was powerful enough to find the right items. But there was no way to conceptually structure your content. This has changed too in Liferay 6.2. Similar to documents, web content can now be subdivided into folders.
 


 

Recycle bin

Before Liferay 6.2, web content was either published or expired. Expired web content items were not visible on the front-end, but in the back-end they still lived alongside published items. Most of these expired items would never be published again. Deleting them, on the other hand, would mean that they could never be restored again. In other words, no room for mistakes.

Meet the Recycle Bin. This trash can is very similar to what can be seen in most operating systems. Recycle Bin can not only contain trashed web content, but also documents, blogs, wiki pages, bookmarks, ... Trashed content can be easily restored and a maximum age can be configured for assets to live in the Recycle Bin.
 


 

The Recycle Bin is per-site, so each site administrator can enforce his or her own recycling policy. Recycling can even be disabled altogether.
 


 

Application display templates

The Asset Publisher has always been one of the most useful portlets that comes for free with Liferay's default bundle. This portlet allows you to create overviews of all kinds of assets, created in the portal. Assets can be web content, documents or images, blogs, wiki pages, ...

Before Liferay 6.2, such asset lists could be visualized in 4 predefined display styles:
 

  • table: displayed the list as a table with every data field (title, description, categories, size, ...) in a separate column.
  • title list: displayed the data as a bullet list with titles that linked to the full content of the items.
  • abstracts: displayed both the title and a short description, either manually entered or automatically retrieved from the content.
  • full content: displayed the full content of every item in the list.
     

Custom display styles could be developed by implementing a JSP hook, but this proved to be cumbersome, not portable and time intensive.

In Liferay 6.2, display styles can be created at runtime in the portal by scripting an application display template. Select a scripting language of your choice (Freemarker or Velocity) and use the predefined script variables to write your own display in minutes.

E.g. this is how you write a tiny Velocity script that displays the assets' titles in a simple unordered list without linking to the full content:
 


 

As you can see, the script editor gives a nice overview of all variables you can use. Also, a small tooltip explains the meaning of each variable.

When selecting this display style, your portlet's output will look something like this:
 


 

The great thing about application display templates is that it doesn't only apply to Asset Publisher portlets. Similar templates can be created for Blogs, Categories Navigation, Documents and Media and many other default portlets of Liferay. Even your custom portlets can use the application display template API to give the users of your portlet the same flexibility.
 

Conclusion

This article presented only a handful of aspects of the new Liferay 6.2 release. The support for responsive design by implementing Bootstrap was a great decision, proving that Liferay is not lagging behind in the mobile revolution. Several improvements in the web content management system have made Liferay a better competitor with full-blown CMS products.

Of course, much more has changed in Liferay 6.2, both visible on the outside and under the hood. For more information, check out the following references:
 

More blogs on Liferay and Java via http://blogs.aca-it.be.

Affichage de 6 résultat(s).
Items 20
de 1