Using custom java classes in a JSP hook

Issue

In Liferay hook development, one common question is:

How to import/use/access custom java classes in a JSP hook?

The question can be described as:

1. Create a hook project (for example: sample-hook)

2. Create a custom class (for example: com.kzhang.CustomJavaClass) in hook project.

3. Override a Liferay portlet jsp (for example, html/portlet/blogs/view.jsp) and try to use the CustomJavaClass in the jsp. Liferay will throw "class CustomJavaClass can't be resolved to a type" exception or ClassNotFound exception.

 

Cause

The jsps in hook plugin is deployed to ROOT servlet context. for example, the html/portlet/blogs/view.jsp in hook plugin will be copied to ${container.deploy.folder}/ROOT/html/portlet/blogs/view.jsp, and the origional view.jsp will be renamed as view.portal.jsp.

The custom classes are still remains in the hook servlet context. for example, the com.kzhang.CustomJavaClass in hook plugin will be still in ${container.deploy.folder}/sample-hook/WEB-INF/classes/com/kzhang/CustomJavaClass.class.

In html/portlet/blogs/view.jsp it can not find CustomJavaClass because each servlet context can only see it's own classes and those of the parent classloaders. In our case, the html/portlet/blogs/view.jsp is in ROOT servlet context which can not see the CustomJavaClass in sample-hook servlet context

 

Solution

There are some discussions in Liferay forum:
http://www.liferay.com/community/forums/-/message_boards/message/7454307
http://www.liferay.com/community/forums/-/message_boards/message/13467179
http://www.liferay.com/community/forums/-/message_boards/message/21397246
http://www.liferay.com/community/forums/-/message_boards/message/11508053

The most common solution is to use the ext plugin instead of hook plugin.

But can we avoid using the ext plugin and leave everything as a hook?

Solution 1: Use Spring + PortletBeanLocatorUtil + JAVA reflection

Step 1. Create the custom-spring.xml in /sample-hook/docroot/WEB-INF/src/context/custom-spring.xml folder.

Step 2. In custom-spring.xml, define the bean for your custom java class:

Step 3. In web.xml, add the context-param to load the custom-spring.xml:

Step 4. In custom_jsps/html/portlet/blogs/view.jsp, use PortletBeanLocatorUtil to load the custom java class:

If your CustomJavaClass implements an interface which is accessable in ROOT servlet context, then you can cast the customJavaClass to the interface and use the class thru interface. Otherwise you will need to use JAVA reflection to dynamically invoke the methods in the custom class.

Example here.

Solution 2: Override Struts Action and call the custom classes in Struts Action.

Setp 1. In liferay-hook.xml, override the view action of blogs portlet:

Step 2. Create the CustomStrutsAction, invoke the custom class in CustomStrutsAction and set the result in to renderRequest attribute:

( Instead of result, you can also set the custom class in the renderRequest attribute and invoke it in jsp either by using it's Interface or use java reflection)

Step 3. In custom_jsps/html/portlet/blogs/view.jsp, get the result from the renderRequest attribute:

17
Blogs
[...] Check below link it might help you. http://www.liferay.com/web/kzhang/blog/-/blogs/using-custom-java-classes-in-a-jsp-hook Thanks, Tejas Sign in to vote. Flag Please sign in to flag this as... [...] Read More
I'm trying to use a custom class in a jsp hook. However, I get the following error:
org.springframework.beans.factory.NoSuchBeanDefinitionException

Do you have an example of a simple hook using this approach or do you know of any reason why the "PortletBeanLocatorUtil.locate("my-hook","MyBean")" would fail?
Hi Jimmy, Please find the example here: https://docs.google.com/file/d/0B7stjZahI-VjZWhXZFNGeVlJQms/edit?usp=sharing
Thanks for the sample-hook!
I've deployed it to 6.1.1 ce ga2 and 6.1.20 ee ga2 but I get the same result as with my own hook.
Which version are you running the example on?
Could you please try to change "<param-name>contextConfigLocation</param-name>" to "<param-name>portalContextConfigLocation</param-name>" in your web.xml?

Thanks.
First, thank you your post. It is great.
Second, in this case:
If your CustomJavaClass implements an interface which is accessable in ROOT servlet context, then you can cast the customJavaClass to the interface and use the class thru interface. Otherwise you will need to use JAVA reflection to dynamically invoke the methods in the custom class.

We can implement as below:
ClassLoader classLoader = PortletBeanLocatorUtil.getBeanLocator("sample-hook").getClassLoader();
Class clazz = classLoader.loadClass("com.kzhang.CustomJavaClass");
java.lang.reflect.Field field = clazz.getField("HELP_DOMAIN");
Object obj = field.get(null);
String domain = (String)obj;

In this case, HELP_DOMAIN is constant so I don't need to create object for CustomJavaClass.

With this implement, we no need create custom-spring.xml and also update web.xml

Again, thank you your help emoticon

Uyen
Hi Kan

I found your post very interesting, although I am having some problems with my implementation. First of all your code works perfect! In mine there is something special. I do not use any liferay existing class (I do not extend liferay class) but I got some external library. I want to extend one class in there and use it in the jsp file.
The error message is:
21:26:07,485 ERROR [localhost-startStop-5][PortletApplicationContext:138] org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Bean class [my.test.CustomComparator] not found_Offending resource: ServletContext resource [/WEB-INF/classes/context/custom-spring.xml]_Bean 'CustomComparatorClass'; nested exception is java.lang.ClassNotFoundException: my.test.CustomComparator [Sanitized]
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Bean class [my.test.CustomComparator] not found_Offending resource: ServletContext resource [/WEB-INF/classes/context/custom-spring.xml]_Bean 'CustomComparatorClass'; nested exception is java.lang.ClassNotFoundException: my.test.CustomComparator [Sanitized]
My code looks exactly like yours (even names) except two things:
1. extend from different external class
2. in jsp file I use MyParentClass customComparator = (MyParentClass )PortletBeanLocatorUtil.locate("Hook test-hook","CustomComparatorClass");

can you please, please help me with this one ?

regards,
pawel
I need to add another listeners in Audit portlet of Liferay Enterprise version.
To log the activities for some another model event. Like i have some Document type to with add and remove events. So i want to logs the information that who added that document type or remove type.

I wrote the following code in listener
================
public class DocumentAuditListener extends BaseModelListener<DocumentType> {

@Override
public void onAfterAddAssociation(Object classPK,
String associationClassName, Object associationClassPK)
throws ModelListenerException {
// TODO Auto-generated method stub
super.onAfterAddAssociation(classPK, associationClassName, associationClassPK);
}

@Override
public void onAfterCreate(DocumentType model) throws ModelListenerException {
// TODO Auto-generated method stub
try {

AuditMessage auditMessage = AuditMessageBuilder.buildAuditMessage("New Doc Created", User.class.getName(), 12122, null);

}
catch (Exception e) {
throw new ModelListenerException(e);
}
}

}

And in portal.properties

value.object.listener.com.test.documenttype.model.DocumentType=com.liferay.portal.audit.hook.listeners.DocumentAuditListener

And put the service jar in tomcat/lib/ext

And getting following errors........................

13:39:54,994 ERROR [HotDeployUtil:114] com.liferay.portal.kernel.deploy.hot.HotDeployException: Error registering hook for audit-hook
com.liferay.portal.kernel.deploy.hot.HotDeployException: Error registering hook for audit-hook
at com.liferay.portal.kernel.deploy.hot.BaseHotDeployListener.throwHotDeployException(BaseHotDeployListener.java:46)
at com.liferay.portal.deploy.hot.HookHotDeployListener.invokeDeploy(HookHotDeployListener.java:271)
at com.liferay.portal.kernel.deploy.hot.HotDeployUtil._doFireDeployEvent(HotDeployUtil.java:111)
at com.liferay.portal.kernel.deploy.hot.HotDeployUtil._fireDeployEvent(HotDeployUtil.java:188)
at com.liferay.portal.kernel.deploy.hot.HotDeployUtil.fireDeployEvent(HotDeployUtil.java:40)
at com.liferay.portal.kernel.deploy.hot.HotDeployUtil$1.doPortalInit(HotDeployUtil.java:175)
at com.liferay.portal.kernel.util.BasePortalLifecycle.portalInit(BasePortalLifecycle.java:42)
at com.liferay.portal.kernel.util.PortalLifecycleUtil.flushInits(PortalLifecycleUtil.java:45)
at com.liferay.portal.servlet.MainServlet.initPlugins(MainServlet.java:792)
at com.liferay.portal.servlet.MainServlet.init(MainServlet.java:356)
at javax.servlet.GenericServlet.init(GenericServlet.java:212)
at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1173)
at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:993)
at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4420)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4733)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:799)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:779)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:601)
at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:675)
at org.apache.catalina.startup.HostConfig.deployDescriptors(HostConfig.java:601)
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:502)
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1315)
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:324)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:142)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1061)
at org.apache.catalina.core.StandardHost.start(StandardHost.java:840)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1053)
at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:463)
at org.apache.catalina.core.StandardService.start(StandardService.java:525)
at org.apache.catalina.core.StandardServer.start(StandardServer.java:754)
at org.apache.catalina.startup.Catalina.start(Catalina.java:595)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414)
Caused by: com.liferay.portal.kernel.bean.BeanLocatorException: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'com.mcmcg.lawfirmportal.documenttype.service.persistence.DocumentTypePersistence' is defined
at com.liferay.portal.bean.BeanLocatorImpl.locate(BeanLocatorImpl.java:71)
at com.liferay.portal.kernel.bean.PortalBeanLocatorUtil.locate(PortalBeanLocatorUtil.java:48)
at com.liferay.portal.deploy.hot.HookHotDeployListener.getPersistence(HookHotDeployListener.java:1097)
at com.liferay.portal.deploy.hot.HookHotDeployListener.initModelListener(HookHotDeployListener.java:1442)
at com.liferay.portal.deploy.hot.HookHotDeployListener.initModelListeners(HookHotDeployListener.java:1473)
at com.liferay.portal.deploy.hot.HookHotDeployListener.doInvokeDeploy(HookHotDeployListener.java:538)
at com.liferay.portal.deploy.hot.HookHotDeployListener.invokeDeploy(HookHotDeployListener.java:268)
... 35 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'com.mcmcg.lawfirmportal.documenttype.service.persistence.DocumentTypePersistence' is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:527)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1083)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:274)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:190)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1075)
at com.liferay.portal.bean.BeanLocatorImpl.doLocate(BeanLocatorImpl.java:113)
at com.liferay.portal.bean.BeanLocatorImpl.locate(BeanLocatorImpl.java:68)
... 41 more
One other way is to get classloader from ClassLoaderPool of the hook & get class.
Now using reflection, can access the class fields or methods.
[...] Hi This might help you https://web.liferay.com/web/kzhang/blog/-/blogs/using-custom-java-classes-in-a-jsp-hook This link shows how to use custom java classes in a JSP hook. Thanks Gautam Sharma... [...] Read More
[...] Hi This might help you https://web.liferay.com/web/kzhang/blog/-/blogs/using-custom-java-classes-in-a-jsp-hook This link shows how to use custom java classes in a JSP hook. Thanks Gautam Sharma... [...] Read More