« 返回

Custom Velocity Tools and Liferay 6.0

Company Blogs 2010年10月4日 按 Ray Augé Staff

A while back I wrote a post about adding custom tools to the Liferay Velocity context.

In 6.0 a change was made such that the behaviour has changed slightly. Now all such tools are plain old beans which must implement an interface.

The changes also means that I have a lot less code to write and less wiring to do. Let's see how we'd do it now using exactly the same tool as that old post.

The interface again:

package com.mytool;

public interface MyTool {

	public String operationOne();

	public String operationTwo(String name);

}

We need an implementation of the interface:

package com.mytool;

public class MyToolImpl implements MyTool {

	public String operationOne() {
		return "Hello out there!";
	}

	public String operationTwo(String name) {
		return "Hello " + name + "!";
	}

}

Our spring configuration only requires a single bean definition:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
	<bean id="com.mytool.MyTool" class="com.mytool.MyToolImpl" />
</beans>

Of course in order for our bean definitions to be read we need to make sure it gets loaded, so we'll name it so that it's found by the context loader WEB-INF/applicationContext.xml (We could have used the portal-esk spring config mechanism, but I wanted to demonstrate that Liferay is flexible and sensitive to existing coding behaviors.

I mentioned that we need a context loader, so there is one more change required. While before you could only add tools within a ServiceBuilder enabled plugin, that is no longer required. All you have to do is add the following context loader listener the your web.xml (but only if your plugin is not a portlet type plugin):

    <listener>
        <listener-class>com.liferay.portal.kernel.spring.context.PortletContextLoaderListener</listener-class>
    </listener>

This effectively tells the portal to create a BeanLocator object associated with your plugin and to read the beans definitions that were defined in WEB-INF/applicationContext.xml.

Now we're ready to use our tool in a Velocity template:

Since we've done this in a plugin, you will have to specify the 'contextPathName' of the plugin so that the appropriate BeanLocator can be used to lookup your tool. For example, the context path name of your plugin being "test-velotool-hook", then you'd use the following in your template:

#set ($myTool = $utilLocator.findUtil('test-velotool-hook', 'com.mytool.MyTool'))

$myTool.operationOne()

$myTool.operationTwo('Ray')

I've linked a source hook plugin that you can drop into your plugins SDK and deploy just by doing ant deploy.

[test-velotool-hook.zip]

讨论主题回复 作者 日期
Thanks, this is useful information. Question -... Sampsa Sohlman 2010年10月4日 上午8:23
Sorry about that! I fixed the file permissions,... Ray Augé 2010年10月4日 上午8:50
Great, now I can go forward... Thanks Ray Thiago Leão Moreira 2010年10月4日 上午10:40
Nice update! Thank you, Ray. Jonas Yuan 2010年10月4日 下午1:31
Hello Ray, In this context i would like to ask... Kamesh Sampath 2011年1月11日 上午8:40
Hmm.. If you're referring to programmatic... Ray Augé 2011年1月14日 上午7:05
Actually I was referring to the ways how... Kamesh Sampath 2011年1月14日 下午9:47
Ah, I see! Typically when ear deployment of the... Ray Augé 2011年1月15日 下午12:13
Thanks very much for the information Ray, can... Kamesh Sampath 2011年1月18日 下午11:08
Thanks for a very useful article. I am able to... Ashok Chilakapati 2011年6月13日 下午1:59
They should be... as there is no difference in... Ray Augé 2011年7月1日 上午6:04
Very nice information. Thank you. Daniel Wilmes 2011年7月25日 上午11:31
[...] I had to create a custom hook from the... 匿名 2011年7月26日 上午4:44
[...] Hi there, I am trying to work with Java... 匿名 2011年10月10日 上午8:31
[...] Hi, I am trying to build a custom... 匿名 2011年10月13日 上午2:05
[...] One approach to overcome these challenges... 匿名 2011年11月1日 下午4:40
Really awesome feature. Thanks a lot Ray for... Jignesh Shukla 2011年11月9日 下午7:20
[...] Hi, i have downloaded and deployed the... 匿名 2012年1月20日 上午9:09
Is there a way to call this MyTool... Kyle Chaplin 2012年5月18日 上午8:57
Object bean =... Ray Augé 2012年5月18日 上午9:09
Thank you Ray this is very helpful. I was... Craig Laidlaw 2012年9月12日 上午7:27
Hey Craig, The main difference is that we're... Ray Augé 2012年9月12日 上午7:36
Thank you very much for this useful post, Ray!... Toni Pérez Rodil 2012年9月21日 上午12:25
Oh! I forget to say that I changed the... Toni Pérez Rodil 2012年9月21日 上午12:33
Hi all, for version 6.1 or later you need to... Jose Jimenez 2012年9月25日 上午5:04
Other option for LF 6.1 or later: Add a file... Jose Jimenez 2012年9月25日 上午8:25
Thanks for great article! Alex Galkin 2012年11月29日 下午3:27
Please let me know if there is a way to pass... Alex Galkin 2012年11月29日 下午3:53
I hope I don't generally come off so pesimistic... Ray Augé 2012年11月30日 上午9:17
.. wait! There might be a possibility if you... Ray Augé 2012年11月30日 上午9:19
Thanks for prompt response. I am using... Alex Galkin 2012年11月30日 上午9:41
Ok, try this to get the real request: #set... Ray Augé 2012年11月30日 上午9:54
Or even... Ray Augé 2012年11月30日 上午9:58
Provided solution works just fine! Thanks. I... Alex Galkin 2012年11月30日 下午3:31
Hi Ray.Great Thread. I have a question. ... Rui Horta 2013年4月9日 上午6:26
You cannot include a jsp in a web content... Ray Augé 2013年4月23日 上午11:22
Hello Ray, i'm trying this with an Spring-MVC... René Hengstermann 2013年12月17日 上午2:35
Hello Ray, Thank you for the useful post!... Cédric De Backer 2014年1月29日 上午7:29
Hi In Liferay 6.2 the... Simon Göransson 2014年2月25日 上午2:30
[...] Ray Augé of Liferay proposed a way to... 匿名 2014年4月17日 上午7:34

Thanks, this is useful information. Question - is this 6.0 only or did you backport this to EE ( 5.2sp4 )?

And one more thing zip file is not available. I'm getting "Forbidden".
在 10-10-4 上午8:23 发帖。
Sorry about that! I fixed the file permissions, it should work now.

This behavior is for 6.0 (and I believe later versions of 5.2). See the old post for how it worked previously.
在 10-10-4 上午8:50 发帖以回复 Sampsa Sohlman
Great, now I can go forward...

Thanks Ray
在 10-10-4 上午10:40 发帖以回复 Ray Augé
Nice update! Thank you, Ray.
在 10-10-4 下午1:31 发帖。
Hello Ray,
In this context i would like to ask a Question what are the other ways that we can use for Liferay to pickup the liferay plugins from an archive a WAR or EAR etc.,
I see that adding PortletContextListener will do the job just curious to know any other alternatives any Liferay service we can invoke to do this job.
Thanks.
Kamesh
在 11-1-11 上午8:40 发帖。
Hmm.. If you're referring to programmatic injection I don't think we do. At least not yet. The bootstrapping process for plugins is pretty intense. I'm hoping that once we get going with OSGi that sort of scenario begins to open as an option.

Then again, maybe I didn't understand your question!
在 11-1-14 上午7:05 发帖以回复 Kamesh Sampath
Actually I was referring to the ways how Liferay picks up the plugins from the Archieves e.g war, since WAR undergoes normal deployment model in any J2EE server, Liferay adds one more additional step of registering the plugins from the WAR, not sure which service is called to do the same this will help when we deploy EAR in bigger servers where Liferay is deployed to enable registering of Liferay specific plugins.
Hope i made it clear
在 11-1-14 下午9:47 发帖以回复 Ray Augé
Ah, I see! Typically when ear deployment of the portal is used, it's expected that the plugins that are to be used are pre-bundled in the ear (using plugin pre-deployment technique).
在 11-1-15 下午12:13 发帖以回复 Kamesh Sampath
Thanks very much for the information Ray, can you please point to WIKI where it talks on the "plugin pre-deployment technique", i search the WIKI in vain emoticon
在 11-1-18 下午11:08 发帖以回复 Ray Augé
Thanks for a very useful article. I am able to build extra functionality and use it in velocity theming. Works great!

However, these extra tools I am building do not seem to be available for Journal Velocity Templates. I have set

journal.template.velocity.restricted.variables=

in my portal-ext.properties so as to remove all restrictions.

What gives?

Thanks

- ashok
在 11-6-13 下午1:59 发帖。
They should be... as there is no difference in the access to them between themes and WCT! You still have to make the load call first though to retrieve them (Even if already loaded in the theme, since it's a completely different context... not sure if that's the issue though).
在 11-7-1 上午6:04 发帖以回复 Ashok Chilakapati
Very nice information. Thank you.
在 11-7-25 上午11:31 发帖。
[...] I had to create a custom hook from the SDK. This has the method to create a hook and call in from your velocity page. How to create a custom hook for velocity liferay Mark as an Answer [...] Read More
在 11-7-26 上午4:44 发帖。
[...] Hi there, I am trying to work with Java objects within velocity in a web content portlet template. I got some interesting tips on how to do that here. However, I realized that when I make... [...] Read More
在 11-10-10 上午8:31 发帖。
[...] Hi, I am trying to build a custom velocity tool as described here but using an ext plugin instead of a hook as in the example. I tried to covert the hook into an ext plugin velotool-ext modifying the... [...] Read More
在 11-10-13 上午2:05 发帖。
[...] One approach to overcome these challenges is to use a utility class written in Java and called within your script. We'll follow the approach that Ray outlined in a blog post about using Java utils... [...] Read More
在 11-11-1 下午4:40 发帖。
Really awesome feature. Thanks a lot Ray for sharing such a nice tutorial.

Thanks
Jignesh
在 11-11-9 下午7:20 发帖。
[...] Hi, i have downloaded and deployed the hook within the project from Ray Augés example from Custom Velocity Tools and Liferay 6.0 on tomcat-liferay-bundle 6.1. Unforunately i' getting an exception:... [...] Read More
在 12-1-20 上午9:09 发帖。
Is there a way to call this MyTool implementation from a portlet? Thanks
在 12-5-18 上午8:57 发帖。
Object bean = PortletBeanLocatorUtil.locate(String servletContextName /* plugin context name */, String name /* name of bean */);

then do whatever you need to do with it.
在 12-5-18 上午9:09 发帖以回复 Kyle Chaplin
Thank you Ray this is very helpful.
I was able to create a hook with methods I can call from a VM. This works great in 6.0eespX. When I moved to 6.1eega2 the VM call couldn't find the bean until I added the ext-spring.xml definition back in to the hook in addition to the applicationContext.xml. Something to look at.

Now I am trying to access the hook methods from a portlet with the PortletBeanLocatorUtil call and I'm getting ClassNotFoundExceptions in my portlet. This is in 6.1eega2, haven't tried it in 6.0.

I'm getting an object from the bean locator call but when I try to use a method in the class I get the exception. Would you please expand on your "...do whatever you need to do with it." statement and provide a more detailed example so I can see where I may be getting off track?

Thank you,
Craig
在 12-9-12 上午7:27 发帖以回复 Ray Augé
Hey Craig,

The main difference is that we're creating a Proxy around the spring bean. What that means is that the bean you're exposing must implement an interface that the Proxy can implement. This breaks when the bean is a static Util wrapper (which doesn't actually implement the interface).
在 12-9-12 上午7:36 发帖以回复 Craig Laidlaw
Thank you very much for this useful post, Ray!
I've deployed the test-velotool-hook on a Liferay 6.1 EE SP2 but I weren't able to get it working.
I tested on a clean bundle following these steps:
1. Deploy the "test-velotool-hook". The log is OK.
2. Create a "Dummy" structure with a "dummy" text field.
3. Create a "Dummy" journal template associated to the "Dummy" structure. Its script is
#set ($myTool = $utilLocator.findUtil('test-velotool-hook', 'com.mytool.MyTool'))
<p>myTool=[$myTool]</p>
4. Create a "Dummy" web content that uses the "Dummy" structure and the "Dummy" template.
5. Add a "Web Content Display" showing the "Dummy" content in a page. In the log appears an exception that, in short, says:

[UtilLocator:53] com.liferay.portal.kernel.bean.BeanLocatorException: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'com.mytool.MyTool' is defined
com.liferay.portal.kernel.bean.BeanLocatorException: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'com.mytool.MyTool' is defined
at com.liferay.portal.bean.BeanLocatorImpl.locate(BeanLocatorImpl.java:89)
at com.liferay.portal.kernel.bean.PortletBeanLocatorUtil.locate(PortletBeanLocatorU­til.java:47)
at com.liferay.portal.velocity.UtilLocator.findUtil(UtilLocator.java:49)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'com.mytool.MyTool' is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefi­nition(DefaultListableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBean­Definition(AbstractBeanFactory.java:1094)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstract­BeanFactory.java:276)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBe­anFactory.java:192)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractA­pplicationContext.java:1075)
at com.liferay.portal.bean.BeanLocatorImpl.doLocate(BeanLocatorImpl.java:125)
at com.liferay.portal.bean.BeanLocatorImpl.locate(BeanLocatorImpl.java:83)
....

Should this hook work out-of-the-box on Liferay 6.1 EE SP2? Or does it need some tweaks?

Thank you.
在 12-9-21 上午12:25 发帖。
Oh! I forget to say that I changed the "web.xml" of the hook. I needed to use the Servlet 2.4 XSD declaration instead of the Servlet 2.3 DTD to deploy it.

The declaration used is:
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
在 12-9-21 上午12:33 发帖以回复 Toni Pérez Rodil
Hi all,

for version 6.1 or later you need to add the next param to the web.xml.

<context-param>
<param-name>portalContextConfigLocation</param-name>
<pa­ram-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

Kind regards!!!
在 12-9-25 上午5:04 发帖。
Other option for LF 6.1 or later:

Add a file service.properties to the src folder of the plugin and configure the property spring.config. eg:

spring.configs=\
WEB-INF/applicationContext.xml,\
\
WEB-INF/other-beans-def.xml

Enjoy!! emoticon
在 12-9-25 上午8:25 发帖以回复 Jose A. Jiménez
在 12-11-29 下午3:27 发帖。
Please let me know if there is a way to pass PortletSession object or RenderRequest to Custom method.

E.g.

I have tried to create the following interface:

package com.myTool;


import javax.portlet.PortletSession;
import javax.portlet.RenderRequest;

public interface MyTest{
public String getUserCountry(PortletSession portletSession);
public String getUserCountry(RenderRequest request);
}

In class that implements interface I have the following methods:

public String getUserCountry(PortletSession portletSession) {
try {
myUtil.setPortletSession(portletSession);
return "Success";
}
catch (Exception e) {
e.printStackTrace();
return "exception-portletSession";
}

};

public String getUserCountry(RenderRequest request) {
try {
myUtil.setPortletSession(request.getPortletSession());
return "Success request";
}
catch (Exception e) {
e.printStackTrace();
return "exception-renderRequest";
}

};


I have tried the following ways with no luck:


1)
#set($currentProfileUtil = $utilLocator.findUtil("myportlets-1.0-SNAPSHOT", "com.myTool.MyTest"))
#set($result = $currentProfileUtil.getUserCountry($request.getAttribute("javax.portlet.response­")))
$result

2)
#set($currentProfileUtil = $utilLocator.findUtil("myportlets-1.0-SNAPSHOT", "com.myTool.MyTest"))
#set($result = $currentProfileUtil.getUserCountry($request.get("portlet-session")))
$result

3)
#set($currentProfileUtil = $utilLocator.findUtil("myportlets-1.0-SNAPSHOT", "com.myTool.MyTest"))
#set($result = $currentProfileUtil.getUserCountry($request.getSession()))
$result

4)
#set($currentProfileUtil = $utilLocator.findUtil("myportlets-1.0-SNAPSHOT", "com.myTool.MyTest"))
#set($result = $currentProfileUtil.getUserCountry($request.getSession()))
$result


P.S. Please do not say "why do you need this?" - just provide any ideas on how to send PortletSession or any object from which PortletSession can be retrieved. Thanks
在 12-11-29 下午3:53 发帖。
I hope I don't generally come off so pesimistic or condesending. My appologies if I do!

I'd just like to ask if you may be attempting this in web content templates? If so, there isn't a real "ServletRequest" or "HttpServletRequest" object. It's just a Map<String,<[String|Map]>> tree of string leaf nodes.

But what you are showing should be completely doable from theme template.

The reason the request is not a real "ServletRequest" is because the rendering happens within the service tier which can be invoked without any web context.

I've been wanting to add support for web context execution, but never had chance to do it.
在 12-11-30 上午9:17 发帖以回复 Alex Galkin
.. wait! There might be a possibility if you are using a recent version of Liferay. Can you sate which version you are using?
在 12-11-30 上午9:19 发帖以回复 Ray Augé
Thanks for prompt response.

I am using Liferay-portal-6.1.1-ce-ga

even HttpSession should work for me though I am not sure how that should be passed correctly.

I am trying to send this from WebContent Template.
在 12-11-30 上午9:41 发帖。
Ok, try this to get the real request:

#set ($serviceContext = $portal.getClass().forName("com.liferay.portal.service.ServiceContextThreadLocal­").getServiceContext())
#set ($httpServletRequest = $serviceContext.getRequest())

For all the methods on ServiceContext see: https://github.com/liferay/liferay-portal/blob/6.1.1-ga2/portal-service/src/com/­liferay/portal/service/ServiceContext.java
在 12-11-30 上午9:54 发帖以回复 Alex Galkin
Or even http://cdn.docs.liferay.com/portal/6.1/javadocs/com/liferay/portal/service/Servi­ceContext.html
在 12-11-30 上午9:58 发帖以回复 Ray Augé
Provided solution works just fine! Thanks. I have another issues now but they are not related to request sending.
在 12-11-30 下午3:31 发帖以回复 Ray Augé
Hi Ray.Great Thread.

I have a question.

Velocity have a new tool that is ImportTool and i wanted to used in Templates(WebContent).

My problem is that in a hook i can not access to velocity-tools.jar, so my question is i need to make it in ext-plugin or exists any other way to do it.

The final goal is to include a jsp in a webcontent template.

Regards,

Rui Horta
在 13-4-9 上午6:26 发帖。
You cannot include a jsp in a web content template even using that tool.

Why? The web content template context does not contain a real request/response object which is required for JSPs execution.
在 13-4-23 上午11:22 发帖以回复 Rui Horta
Hello Ray,

i'm trying this with an Spring-MVC Portlet istead of a Hook.
So in my web.xml is 'org.springframework.web.context.ContextLoaderListener' defined, which prevents me from using the mentioned 'PortletContextLoaderListener'.

When i try to lookup my bean, the BeanLocator for my context is 'null' (PortletBeanLocatorUtil->_beanLocators).

Do i need more configuration than the 'applicationContext.xml'-File in WEB-INF ?
在 13-12-17 上午2:35 发帖。
Hello Ray,

Thank you for the useful post! However I'm having some troubles with classloaders while creating the spring context. I would like to import spring resource files from external libraries (<import resource="classpath:my-lib-resource.xml"/>) but I am always getting a FileNotFoundException. When I directly declare the beans in my "WEB-INF/applicationContext.xml" file, there is no problem.

From what I understood, the services created here will be registered in the PortalContext and made accessible to all the WARs deployed on the same server. However the classloader being the classloader of the server it cannot find the files defined with "classpath" that are located in the WAR file. Is this correct? And is there a way to import spring configuration files located in the classpath of the WAR?

I am using Liferay 6.2.10 on weblogic.

Thank you in advance !

Cédric
在 14-1-29 上午7:29 发帖。
Hi

In Liferay 6.2 the com.liferay.portal.kernel.spring.context.PortletContextLoaderListener is deprecated and the methods i empty, do not ask me why Liferay has left a method without any code. To get this to work in Liferay 6.2 you can add this in you web.xml and remove the listener.

<context-param>
<param-name>portalContextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
在 14-2-25 上午2:30 发帖。
[...] 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... [...] Read More
在 14-4-17 上午7:34 发帖。