ACA Blog

ACA Blog


May 2019
M T W T F S S
« Apr    
 12345
6789101112
13141516171819
20212223242526
2728293031  

Categories


Inject any custom service into Liferay WCM templates

Peter MesottenPeter 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:

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

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

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.

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:

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.

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:

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.

Or, if you prefer using Freemarker:

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:

Screen shot 2014-04-15 at 22.46.29

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!

Leave a Reply

avatar
  Subscribe  
Notify of