Implementing a reusable Liferay Service Without Ext or Service Builder
April 19th, 2010 by Bert WillemsTags:Java, Liferay, Spring
In Liferay you can split your application logic vertically exposing each component as a separate independent service. This service can be consumed by your portlet applications and any other applications. Liferay itself exposes several services to you: GroupService (for managing communities) and UserService (for managing users) for example. In this article I will show you how you can create a reusable service yourself and host it in Liferay.
In this article we will build a simple hello world service, nothing to fancy (I want to leave something for you to do yourself
). We will code the service from scratch without using tools like service builder.
The project will contain 3 modules:
- A library containing the service contract
- A web application implementing the service contract
- A web application consuming the service
I hope you like this article, please let me know if you have any questions or comments. Lets get started. I assume you have read my previous article about using Maven to build Liferay applications. If not please read it first.
0. Project Setup
Please download and unzip the empty project and load it into an IDE of choice or use the command shell if you want. The project contains empty files. Please familiarizes yourself with the project structure and notice the dependencies in the different pom.xml files. Nothing special going on though.
1. Service Contract
This library contains the code contract of our HelloWorld service and a utility class which will provide easy access for non Spring portlets. Lets first set up our service contract, copy he following code into HelloWorldService.java:
package nl.devatwork.hello.world.service;
public interface HelloWorldService {
String sayHello(String name);
}
That wasn’t so bad now was it? I think you have written more complex service contracts
.
Lets implement the access utility class. Copy the following code into HelloWorldServiceUtil.java:
package nl.devatwork.hello.world.service;
public class HelloWorldServiceUtil {
private static HelloWorldService _service;
public static HelloWorldService getService() {
if (_service == null) {
throw new RuntimeException("HelloWorldService is not set");
}
return _service;
}
public void setService(HelloWorldService service) {
_service = service;
}
public static String sayHello(String name) {
return getService().sayHello(name);
}
}
Again a really simple class. It has a getter and a setter which will be used to inject our service instance into this utility class. The static sayHello method is a proxies the service method.
You can build the service contract library by executing the following command in the /hello-world-service/ folder:
mvn clean package
Copy the resulting JAR file (/hello-world-service/target/hello-world-service-1.0.0-SNAPSHOT.jar) to the /lib/ext folder of your application server. You have to do this in order to avoid class loader issues. We will use this library later.
2. Service Implementation
Now we will implement the service in a self contained web application. This application can be deployed using the auto deploy mechanism of your application server.
Lets implement the base class for our service implementation which implements the required plumbing. Copy the following code into HelloWorldServiceBaseImpl.java:
package nl.devatwork.hello.world.service.base;
import com.liferay.portal.SystemException;
import com.liferay.portal.kernel.annotation.BeanReference;
import com.liferay.portal.service.base.PrincipalBean;
import com.liferay.portal.util.PortalUtil;
import nl.devatwork.hello.world.service.HelloWorldService;
public abstract class HelloWorldServiceBaseImpl extends PrincipalBean implements HelloWorldService {
@BeanReference(name = "nl.devatwork.hello.world.service.HelloWorldService.impl")
protected HelloWorldService helloWorldService;
public HelloWorldService getHelloWorldService() {
return helloWorldService;
}
public void setHelloWorldService(HelloWorldService helloWorldService) {
this.helloWorldService = helloWorldService;
}
protected void runSQL(String sql) throws SystemException {
try {
PortalUtil.runSQL(sql);
} catch (Exception e) {
throw new SystemException(e);
}
}
}
Now that we have the plumbing in place lets concentrate on our state of the art business logic. Copy the following code into HelloWorldServiceImpl.java:
package nl.devatwork.hello.world.service.impl;
import nl.devatwork.hello.world.service.base.HelloWorldServiceBaseImpl;
public class HelloWorldServiceImpl extends HelloWorldServiceBaseImpl {
public String sayHello(String name) {
return "Hello, " + name;
}
}
Very exiting business logic isn’t it?
.
Ok, now that we have our code in place lets apply some glue to make it all work. Copy the following content to service.properties:
##
## Properties Override
##
#
# Specify where to get the overridden properties. Updates should not be made
# on this file but on the overridden version of this file.
#
include-and-override=service-ext.properties
##
## Build
##
build.namespace=DevAtWork
build.number=1
build.date=1269952033689
build.auto.upgrade=true
##
## Spring
##
#
# Input a list of comma delimited Spring configurations. These will be
# loaded after the bean definitions specified in the
# portalContextConfigLocation parameter in web.xml.
#
spring.configs=\
WEB-INF/classes/META-INF/portlet-spring.xml
Notice the last two lines. This tells Liferay to load our Spring configuration.
Lets create our Spring configuration now. Copy the following code to portlet-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" default-init-method="afterPropertiesSet" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="nl.devatwork.hello.world.service.HelloWorldService.impl" class="nl.devatwork.hello.world.service.impl.HelloWorldServiceImpl"/>
<bean id="nl.devatwork.hello.world.service.HelloWorldServiceUtil" class="nl.devatwork.hello.world.service.HelloWorldServiceUtil">
<property name="service" ref="nl.devatwork.hello.world.service.HelloWorldService.impl"/>
</bean>
</beans>
We register two beans into the Spring container. One for our service implementation instance and one for our service util.
Lets add the following code to web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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 http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
<listener>
<listener-class>com.liferay.portal.kernel.spring.context.PortletContextLoaderListener</listener-class>
</listener>
</web-app>
This will load the Spring configuration for us.
Add the following code to liferay-plugin-package.properties:
name=Hello World Service Implementation module-group-id=nl.devatwork.service.impl module-incremental-version=1 short-description= change-log= page-url=http://www.devatwork.nl author=Dev @ Work licenses=Free portal-dependency-jars=hello-world-service-1.0.0-SNAPSHOT.jar
Notice that we declare a dependency on the library we deployed earlier. Liferay will load the library on our class path.
We are ready with the service implementation now. You can build the service implementation by executing the following command in the /hello-world-service-impl/ folder:
mvn clean package
Copy the resulting WAR file (/hello-world-service-impl/target/hello-world-service-impl-1.0.0-SNAPSHOT.war to the auto deploy folder of your application server. The service implementation will be installed now.
3. Consumer Implementation
Now that we have the service contract and implementation in place lets create a portlet which consumes the service.
Copy the following code into SayHelloPortlet.java:
package nl.devatwork.hello.world.portlets;
import nl.devatwork.hello.world.service.HelloWorldServiceUtil;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
public class SayHelloPortlet extends GenericPortlet {
public void doView(RenderRequest request, RenderResponse response) throws PortletException, java.io.IOException {
String msg = HelloWorldServiceUtil.sayHello("Liferay Dev");
response.setContentType("text/html");
response.getWriter().print(msg);
}
}
Copy the following code to portlet.xml:
<?xml version="1.0" encoding="UTF-8"?>
<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd">
<portlet>
<portlet-name>SayHelloPortlet</portlet-name>
<display-name>Say Hello Portlet</display-name>
<portlet-class>nl.devatwork.hello.world.portlets.SayHelloPortlet</portlet-class>
<expiration-cache>0</expiration-cache>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>view</portlet-mode>
</supports>
<portlet-info>
<title>Say Hello Portlet</title>
<short-title>Say Hello Portlet</short-title>
<keywords>DevAtWork</keywords>
</portlet-info>
</portlet>
</portlet-app>
Copy the following code to liferay-plugin-package.properties:
name=Hello World Portlets module-group-id=nl.devatwork.portlets module-incremental-version=1 short-description= change-log= page-url=http://www.devatwork.nl author=Dev @ Work licenses=Free portal-dependency-jars=hello-world-service-1.0.0-SNAPSHOT.jar
Copy the following code to web.xml:
<?xml version="1.0" encoding="UTF-8"?> <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 http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> </web-app>
We are ready with the consumer implementation now. You can build the portlet by executing the following command in the /hello-world-portlets/ folder:
mvn clean package
Copy the resulting WAR file (/hello-world-portlets/target/hello-world-portlets-1.0.0-SNAPSHOT.war to the auto deploy folder of your application server. The portlet will be installed now. Add the new portlet to a page and if everything works well then it should display the message: ‘Hello, Liferay Dev’.
You can download the complete project here.
That is it for this article, I hope you enjoyed reading it and that you learned something. If you have any questions or comments please post a comment and I will get back to you as soon as I can. In the next article I will show you how you can expose the HelloWorld service as a web service so it can be consumed by external applications.
11 Replies to “Implementing a reusable Liferay Service Without Ext or Service Builder”
May 3rd, 2010 at 07:42
[...] you how you can develop custom Liferay services. In the previous article I showed you how you can implement a custom Liferay service and expose it as a web service. In this article I will show you how you can secure your service [...]
May 3rd, 2010 at 09:10
Dear
Each time I want to try to run my helloWorld method, I get a RuntimeException that the HelloWorldService is not set.
I’ve carfully followed your instructions again and again and I can’t find why. Do you have any idea what I could be doing wrong?
grtz
Stece
May 3rd, 2010 at 18:42
Helo Stece,
That message is telling you that the service implementation isn’t deployed properly. I know Liferay is very sensitive about little details. However I need to have more details in order to figure out why not.
Can you please try to compile and deploy the finished solution to see if that is working? You can download it from the post above.
If that doesn’t solve your problem, please give me the following info:
- Which Liferay version you are using
- Which application server you are using (including version number)
- Stacktrace when deploying service implementation
If anyone else has this problem, please report info request above.
Best regards,
Bert
May 4th, 2010 at 13:33
Hello Bert
When I imported your project, it all worked fine. Then I started to rename all the HelloWorld to a new name for my usage and then I started getting the service is not set exception. I probably forgot to rename a file or change a file but I can’t seem to find where.
version: 5,2,3
glassfish v3
stack:
14:31:42,116 ERROR [JSONServiceAction:160] java.lang.reflect.InvocationTargetException
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.liferay.portal.action.JSONServiceAction.getJSON(JSONServiceAction.java:148)
at com.liferay.portal.struts.JSONAction.execute(JSONAction.java:60)
at com.liferay.portal.servlet.JSONServlet.service(JSONServlet.java:77)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:847)
at com.liferay.portal.kernel.servlet.PortalClassLoaderServlet.service(PortalClassLoaderServlet.java:108)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:847)
at org.apache.catalina.core.ApplicationFilterChain.servletService(ApplicationFilterChain.java:431)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:337)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:218)
at com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter.java:154)
at com.liferay.portal.servlet.filters.secure.SecureFilter.processFilter(SecureFilter.java:282)
at com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:91)
at com.liferay.portal.kernel.servlet.PortalClassLoaderFilter.doFilter(PortalClassLoaderFilter.java:78)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:250)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:218)
at org.apache.catalina.core.StandardWrapperValve.preInvoke(StandardWrapperValve.java:460)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:139)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:186)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:719)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:657)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:96)
at com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:98)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:187)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:719)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:657)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:651)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1030)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:142)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:719)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:657)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:651)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1030)
at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:325)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:242)
at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:180)
at com.sun.grizzly.http.DefaultProcessorTask.invokeAdapter(DefaultProcessorTask.java:633)
at com.sun.grizzly.http.DefaultProcessorTask.doProcess(DefaultProcessorTask.java:570)
at com.sun.grizzly.http.DefaultProcessorTask.process(DefaultProcessorTask.java:827)
at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:152)
at com.sun.enterprise.v3.services.impl.GlassfishProtocolChain.executeProtocolFilter(GlassfishProtocolChain.java:71)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:103)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:89)
at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:76)
at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:67)
at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:56)
at com.sun.grizzly.util.WorkerThreadImpl.processTask(WorkerThreadImpl.java:325)
at com.sun.grizzly.util.WorkerThreadImpl.run(WorkerThreadImpl.java:184)
Caused by: java.lang.RuntimeException: BuurtService is not set
at be.diget.buurt.publiek.service.BuurtServiceUtil.getService(BuurtServiceUtil.java:8)
at be.diget.buurt.publiek.service.BuurtServiceUtil.sayHello(BuurtServiceUtil.java:19)
… 51 more
June 11th, 2010 at 06:30
Hi,
Thanks for your help! I’m using Liferay 5.2.3. In order to get this thing to work, I had to add the following empty sql-files inside WEB-INF/sql:
- indexes.sql
- sequences.sql
- tables.sql
I got one problem left, though. My service keeps reloading itself whenever Catalina’s standard context reload happens? I’m using Tomcat 5.5.27.
11-Jun-2010 05:14:41 org.apache.catalina.core.StandardContext reload
INFO: Reloading this Context has started
05:14:46,865 INFO [PortletHotDeployListener:381] Unregistering portlets for extextensionsweb
05:14:46,894 INFO [PortletHotDeployListener:412] 1 portlet for extextensionsweb was unregistered
Loading file:/C:/Programs/liferay523tc55/tomcat-5.5.27/temp/3-extextensionsweb/WEB-INF/classes/service.properties
05:14:47,486 INFO [PortletHotDeployListener:227] Registering portlets for extextensionsweb
05:14:47,706 INFO [PortletHotDeployListener:346] 1 portlet for extextensionsweb is available for use
August 31st, 2010 at 04:57
Hi,
[WARNING] Unable to get resource ‘org.apache.maven.plugins:maven-resources-plugi
n:pom:2.3′ from repository central (http://repo1.maven.org/maven2): Error transf
erring file: repo1.maven.org
Downloading: http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-resour
ces-plugin/2.3/maven-resources-plugin-2.3.pom
[WARNING] Unable to get resource ‘org.apache.maven.plugins:maven-resources-plugi
n:pom:2.3′ from repository central (http://repo1.maven.org/maven2): Error transf
erring file: repo1.maven.org
[INFO] ————————————————————————
[ERROR] BUILD ERROR
[INFO] ————————————————————————
[INFO] Error building POM (may not be this project’s POM).
Project ID: org.apache.maven.plugins:maven-resources-plugin
Reason: POM ‘org.apache.maven.plugins:maven-resources-plugin’ not found in repos
itory: Unable to download the artifact from any repository
org.apache.maven.plugins:maven-resources-plugin:pom:2.3
August 31st, 2010 at 04:58
Hi,
I am getting below error; i do not have admin rights, is that is problem? Or is there any other issue, please help on this.
[WARNING] Unable to get resource ‘org.apache.maven.plugins:maven-resources-plugi
n:pom:2.3′ from repository central (http://repo1.maven.org/maven2): Error transf
erring file: repo1.maven.org
Downloading: http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-resour
ces-plugin/2.3/maven-resources-plugin-2.3.pom
[WARNING] Unable to get resource ‘org.apache.maven.plugins:maven-resources-plugi
n:pom:2.3′ from repository central (http://repo1.maven.org/maven2): Error transf
erring file: repo1.maven.org
[INFO] ————————————————————————
[ERROR] BUILD ERROR
[INFO] ————————————————————————
[INFO] Error building POM (may not be this project’s POM).
Project ID: org.apache.maven.plugins:maven-resources-plugin
Reason: POM ‘org.apache.maven.plugins:maven-resources-plugin’ not found in repos
itory: Unable to download the artifact from any repository
org.apache.maven.plugins:maven-resources-plugin:pom:2.3
August 31st, 2010 at 20:32
Hello Madan,
I think this error relates to Maven. Please check the Maven website for support.
Regards,
Bert
September 1st, 2010 at 02:54
Hi Bert,
I am able to resolve this by adding proxy details, thanks.
ALso i have another questions can I build all these service, impl and portlet like this using Eclipse also, i do not want to use Maven. Will this given code can be used for that?
September 2nd, 2010 at 12:24
Hi Bret.
I updated the impl to call web service to get data. But at run time it is giving class not found even if all the jars are in the lib directory. Is there any other way to fix this.
September 2nd, 2010 at 17:01
Could not initialize class org.apache.axis.client.AxisClient
Actually it is giving this error but all necessary Jars are in web-inf\lib folder. After that I moved all jars to lib/ext then this error not coming but while calling web service it is giving error that null data is sent to service. When i use this server from any other portlet it works fine. Is there some thing special while using this in the implementation as we use “PortletContextLoaderListener”