Spring-Hibernate-DWR

Integrating Spring MVC Portlet 3.0 M2, Hibernate 3.3.1 GA, DWR 3 RC1 and Liferay 5.2.2

by Ashish Sarin <<TableOfContents>>

INTRODUCTION#

This article describes developing plugin portlets for Liferay using Spring, Hibernate and Direct Web Remoting (DWR). The sample application described in this article makes use of Liferay APIs to achieve integration with DWR. The article proposes a project structure ( still at a very early stage ) which can allow portlets (dependent on API exposed by the current portal server) to be deployed on any other portal server.

The preferred approach for development on Liferay has been to use the EXT environment for developing portlets. The plugin-portlets are not used extensively for development because of not much information is available on how some of the features can be achieved in plugin-portlets, for example: security, integration with DWR, integration with Hibernate, etc. The plugin development approach has certain advantages over EXT environment, for example, you are not tied to a specific project structure, build environment, a particular version of Spring/Hibernate frameworks, etc. In Plugin-development environment one gets maximum flexibility, at the expense of increased complexity, in the form of glue code thats required to bind the various frameworks together, and still able to access Liferay services when required. Liferay makes development of plugin portlets easy by exposing its functionality via Util classes, which helps portlets to access Liferay services without being developed as part of Liferay EXT development environment.

SAMPLE APPLICATION#

The sample application has been developed to show the integration of DWR, Spring Portlet MVC and Hibernate, and it doesn't attempt to show all the features of these frameworks but only shows how the integration works. It sets the stage for developing more complex portlets which can extensively make use of these frameworks.

Sample application consists of 2 portlets: Manage Announcement and Recent Announcement. Manage Announcement portlet is used by a user to create, edit, view and delete the announcements. Recent Announcement portlet is used to view the recently published announcement.

The Manage Announcement portlet makes use of DWR 3.0 rc1 to do a file upload, as announcements The upload happens to a Spring command object stored in the session ( remember sessionForm = true in application context XML ? ); the Spring Portlet MVC is used to create the complete application; Hibernate is used as the ORM for saving and retrieving data from the database. The database used by the application is MySQL, but it can be changed to any other database supported by Hibernate.

The application also makes use of CSS file to give a uniform look and feel to the application portlets. The CSS was used so that if the portlets are deployed in any other portal server, even then they have the same look and feel.

SPRING PORTLET MVC#

The choice of using Spring Portlet MVC was obvious as its similar to a normal Spring MVC and is widely used in web application development. The use of Spring Portlet MVC is best shown in the following article : http://www.liferay.com/web/guest/community/wiki/-/wiki/Main/Sample+Spring+Portlet

SPRING-HIBERNATE INTEGRATION#

The Spring-Hibernate integration is like in any other Spring-Hibernate application. There are multiple Spring application context XML files in the portal project, one for each of the portlets and one for overall portal application.

The following entry in web.xml specifies the application context for the sample web application (the portlets will form part of this sample web application) :

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/context/applicationContext.xml</param-value>
 </context-param>

The following elements in the applicationContext.xml are used to integrate with Hibernate:

	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="mappingResources">
			<list>
				<value>com/sample/portal/portlet/announcement/Announcement.hbm.xml</value>
				<value>com/sample/portal/portlet/announcement/AnnouncementFile.hbm.xml</value>
			</list>
		</property>

		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">
					org.hibernate.dialect.MySQLDialect
      				</prop>
      				<prop key="hibernate.show_sql">true</prop>
			</props>
		</property>

		<property name="dataSource">
			<ref bean="dataSource" />
		</property>
	</bean>

	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName">
			<value>${db.driverClassName}</value>
		</property>
		<property name="url">
			<value>${db.url}</value>
		</property>
		<property name="username">
			<value>${db.username}</value>
		</property>
		<property name="password">
			<value>${db.password}</value>
		</property>
	</bean> 

The above configuration reflects that we are using MySQL as the data source. In the MySQL database we are storing the announcement related information. This also shows a way in which you can configure a different database from the database used by Liferay portal server. The Announcement.hbm.xml and AnnouncementFile.hbm.xml are the hibernate mapping files that are used by the sample application.

The information about the database is stored in the jdbc.properties properties file and is configured using org.springframework.beans.factory.config.PropertyPlaceholderConfigurer in the applicationContext.xml file.

SPRING-DWR INTEGRATION#

The sample application uses DWR3 RC1 for uploading announcement related files. The feature to upload files was introduced in DWR3 RC1 and the sample application makes use of this feature to demonstrate how the integration Spring-DWR in Liferay works. The following code shows the content of the dwr.xml, used for configuring a DWR object for handling AJAX requests:

 <dwr>
	<allow>
		<create creator="spring" javascript="AnnouncementAjaxBean">
			<param name="beanName" value="announcementAjaxBean" />
			<include method="validateFields" />
			<include method="uploadFile" />
			<include method="removeFile" />
			<include method="getUploadedFileTable"/>
		</create>
		<convert match="com.sample.portal.portlet.announcement.UploadedFile" converter="bean"></convert>
		<convert match="com.sample.portal.base.ActionMessage" converter="bean"></convert>
	</allow>
</dwr>

The listing above shows that a DWR spring creator has been used to lookup the Spring application context XML and use Spring to create the announcementAjaxBean. The methods exposed for receiving AJAX requests are defined using the <include> tag. There are two bean converters also configured, one is used while uploading file and the other is used for returning a response/message after processing of the AJAX request.

The applicationContext.xml declares the announcementAjaxBean bean as follows:

	<bean id="announcementAjaxBean"
		class="com.sample.portal.portlet.announcement.AnnouncementAjaxBean">
		<property name="htmlTemplates" ref="htmlTemplates"/>
		<property name="maxUploadSize"><value>5242880</value></property>
	</bean>

The htmlTemplates is a bean which has a collection of HTML templates used for creating an HTML fragment and is used by announcementAjaxBean to return an HTML fragment as a response to the AJAX request. The maxUploadSize property specifies the maximum size of the file that can be uploaded.

 Fig. 1: Add Anouncement page of Manage Announcement Portlet

Fig. 1 shows the add announcement page of the Manage Announcement portlet. The Uploaded Files table is updated whenever a user selects a new file to upload. The table is returned as a response to the AJAX request that is sent to the AnnouncementAjaxBean. The table layout and tags are part of the htmlTemplates bean, which it loads from a properties file.

So far, the configuration shown for DWR only allows creating a Spring bean which will handle the AJAX request. This spring bean needs to maintain its state in the Spring's command object ( which has sessionForm = true ) so that when the user finally selects to save the announcement then all the uploaded files are available to the command object.

The integration between the Spring bean (that handles the AJAX request) and the command object ( used by the Spring Portlet MVC controller) requires writing some Liferay specific code. (Note: The Spring bean that handles AJAX request is part of the applicationContext.xml specified in the web.xml file. The Spring controller and the command object are specified in the Spring context XML file which is specified in the portlet.xml file). The following listing shows the announcement.xml file, which is Spring context XML specific to Manage Announcement portlet, and is specified in the portlet.xml file.

	<bean id="announcementController"
		class="com.sample.portal.portlet.announcement.AnnouncementController"
		parent="announcementControllerTemplate">
		<property name="sessionForm" value="true" />
		<property name="commandName" value="announcement" />
		<property name="commandClass"
			value="com.sample.portal.portlet.announcement.Announcement" />
		<property name="formView" value="announcement" />
		<property name="successView" value="announcementSuccess" />
		<property name="validator">
			<ref bean="announcementValidator" />
		</property>
	</bean>

The command object announcement is stored in the HttpSession (which is accessible to AnnouncementAjaxBean via method parameter). The PortletSession is not accessible in case of AJAX requests because AJAX requests are sent as HttpServletRequest and not as PortletRequest. The announcement command object is stored in the session (as specified by <property name="sessionForm" value="true" />) and can be accessed by AnnouncementAjaxBean. In portlet environment, the command object is stored in the session with a name that follows portal server specific format. In case of Liferay, the format is:

	"javax.portlet.p." + Portlet_Id + "_LAYOUT_" + Layout_id + "?" + fully_qualified_controller_name + ".FORM." + command_name

The Portlet_Id and Layout_Id information can be obtained from the PortletRequest object, which is accessible to the JSP pages. The layout and portlet id information can be passed to the AnnouncementAjaxBean to retrieve the command object from the HttpSession. The announcement command object has the following name in one of the deployed portlet instance, and it will be different for every instance:

javax.portlet.p.AnnouncementPortlet_WAR_csdportalplugin_INSTANCE_V4uM_LAYOUT_10206?com.sample.portal.portlet.announcement.AnnouncementController.FORM.announcement

INTEGRATING PLUGIN PORTLETS WITH LIFERAY#

In the following screenshot, a logged in user can view all the announcements published by him. As mentioned earlier, the announcement related information is stored in a different database(lets call it businessDb) then the database used internally (lets call it portalDb) by Liferay portal server. One of the issues that needed to be addressed here, was how to get the information (like username/login id) about the logged in user, so that the logged in username/login id is stored alongwith the announcements in the businessDb.

The username of the logged in user can be obtained using the PortletRequest's getRemoteUser method, as Liferay uses container managed authentication. The getRemoteUser method returns a numeric value which uniquely identifies a user in the user_ table of liferay portal. The PortalUtil class provides many getUserName(..) methods to retrieve full-name, screename(ie. login id) of the user based on value received from the getRemoteUser method. The sample application makes use of the getUserName(long userId, String defaultUserName, String userAttribute, HttpServletRequest request) to obtain the screen name of the user and uses it as user id to uniquely identify announcements associated with a user in the businessDb database.

Liferay exposes its functionality through Util classes which can be used to access Liferay services from plugin portlets. The only difficult part is to find the service methods which meet the application requirements. As far as possible, direct access to the portal database should be avoided and all access should be using the APIs exposed by the portal server.

PROJECT STRUCTURE#

The following figure shows the sample project structure, which is inspired from the project structure defined in Sample Spring Portlets: http://www.liferay.com/web/guest/community/wiki/-/wiki/Main/Sample+Spring+Portlet

 Sample application project structure

The project structure shown here is more intuitive and can be easily used for all the portal projects. The important configuration files in this structure are described below:

  • announcement_messages.properties - properties file that is specific to the announcement portlets(i.e., for Manage Announcement and Recent Announcement portlets)
  • htmlTemplates.properties - contains HTML templates used by AJAX request handlers to create HTML response
  • jdbc.properties - contains businessDb configuration details
  • log4j.properties - contains logging configuration for the application
  • mimeTypes.properties - contains the mime types information. This is required as the users will be downloading announcement files from the portal.
  • startup.properties - contains the property that identifies the Adapter class for the portal server on which the deployment of the application is to be done (more on this in the next section)
  • applicationContext.xml - Spring application context XML
  • dwr.xml - DWR configuration XML file
  • Language-ext.properties, liferay-display.xml, liferay-portlet.xml - Liferay specific configuration files
  • build.properties - contains build properties. The sample project uses build.properties file to provide the home directory of Liferay installation.

DESIGN FOR CHANGE#

The sample project described so far, tries to address the issue of migrating from one portal server to another, with minimal/no change to the application code. The ease of migration can be achieved at two levels: application build (when you are creating the project) and application code level.

application build : When the application is built using ANT, there are different targets defined in build.xml so that the generated WAR file contains the configuration files specific to the portal server for which the build was executed. For example, the build.xml in the sample project contains targets as build-websphere and build-liferay. The build-liferay target compiles the source code, copies Liferay specific config files and then copies the generated WAR file to hot deploy folder of Liferay. The build-websphere target does nothing for now but it gives the opportunity to the developer to specify WebSphere Portal server specific build. If the application needs to be deployed on a different server then a new ANT target can be added to the build.xml file and the portal server specific build information can be added to it.

application code : In the sample application, there are many portal server specific method calls, like getUsername, getInstanceId, etc. If these calls are embedded directly in the portlet code then migrating these portlets from one portal server to another will require changes in all the portlets. The sample application uses different adapters for different portal servers, so that the code specific to the portal server is outside the portlet code. This is achieved by having a an interface PortalServices, which is implemented by LiferayAdapter class and by WebSphereAdapter class. The PortalServices interface defines all the possible methods that require portal server specific code. The classes that implement these interfaces are responsible for providing the implementation that is specific to that portal server. For example, in Liferay, the username of a user is obtained by calling getUserName method of PortalUtils class, so the Manage Announcement portlet code simply makes a call to the PortletUtility class (which makes all calls to PortalServices as static method calls). The implementation object of PortalService interface is determined by the entry in the startup.properties file. The injection of appropriate implementation object is done in the applicationContext.xml file using MethodInvokingFacotoryBean of Spring framework.

	<bean id="initializeService" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
		<property name="targetClass" value="com.sample.portal.base.PortletUtility"/>
		<property name="targetMethod" value="setPortalServices"/>
		<property name="arguments">
			<list>
				<bean class="${portal.server.delegateClass}"/>
			</list>
		</property>
	</bean>

The portal.server.delegateClass property in startup.properties file specifies the fully-qualified class name of the adapter class that will be used for the current deployment of the portal application.

PortalServices interface is implemented by portal server specific Adapters

Since, all the methods defined in PortletUtility class are static, it becomes easy to call these methods from JSPs or from any layer (View/Controller/Data Access)of the application. If new portal-specific methods are discovered that the application needs then its declared in the PortalServices interfaces and its implementation specific to the portal server is defined in LiferayAdapater and WebSphereAdapter (or any other adapter class which identifies any other portal server).

MISCELLANEOUS#

  • File Download in Portlet 2.0

The sample application also shows a portlet 2.0 feature where downloading binary files is possible. Earlier, this was achieved by using a servlet to download binary files. The Recent Announcement portlet and View Announcement page of Manage Announcement portlet shows this feature.

Recent Announcement Portlet

To implement this feature, you need to extend the org.springframework.web.portlet.DispatcherPortlet class and override the serveResource(ResourceRequest request, ResourceResponse response) method. The DispatcherPortlet class is configured as the portlet class in portlet.xml file. The following listing from portlet.xml shows this:

    <portlet>
        <portlet-name>AnnouncementPortlet</portlet-name>
        <portlet-class>com.sample.portal.base.MyDispatcherPortlet</portlet-class>
		<init-param>
			<name>contextConfigLocation</name>
			<value>/WEB-INF/context/portlet/announcement.xml</value>
		</init-param>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>view</portlet-mode>
        </supports>
        <resource-bundle>content.Language-ext</resource-bundle>      
        <portlet-info>
            <title>MANAGE ANNOUNCEMENT</title>
        </portlet-info>
    </portlet>
  • Use of <portlet:namespace> tag in JSP

The <portlet:namespace> tag is added to every HTML form element name and id so that its unique on the page. This can allow having more than one instance of the same portlet on the portal page, without creating any conflicts of form element names and ids.

Issue with Spring <form> tags : If you put <portlet:namespace> tag inside the attributes of Spring's <form> tags then its not parsed. For example, the following JSP code when parsed by the server will not process the <portlet:namespace> tag:

<form:form name="<portlet:namespace/>_announcementForm" commandName="announcement" method="post" action="${formAction}">
.....
.....
</form:form>

A simple solution to this is to use JSTL <c:set> tag to set a variable that has the value of portlet namespace ( obtained using getNamespace() method of RenderResponse object, that is implicitly available to JSP pages in portlet environment ) and the obtain it using Expression Language in the <form> tag attributes. Example:

<c:set var="portletnamespace" value="<%=renderResponse.getNamespace()%>"/>
<form:form name="${portletnamespace}_announcementForm" commandName="announcement" method="post" action="${formAction}">
.....
.....
</form:form>
  • Returning complex HTML fragments in response to AJAX request

In some cases, its required to return complex HTML pages in response to an AJAX request. This can be achieved with ease by using HTML templates. The approach is described in the following article at www.infoq.com :Creating dynamic web applications with JSF/DWR/DOJO

  • Splitting portlet's Spring context XML file

A good idea would be to split the context XML for a portlet so that it can be reused in other portlets also. For example, there is Manage Announcement and Recent Announcement portlet, and both of them need to access similar Service methods. So, it would be a good idea to keep the Service and DAO configuration in a common context XML (like announcement-common.xml) and create different context XMLs for configuring controllers/validators/handlers for the two portlets and these context XMLs will import the announcement-common.xml context XML. This way, if new portlets are developed which need to access the Service/DAOs of the announcement functionality then they can simply import the announcement-common.xml context XML file.

  • Internationalizing messages in AJAX

A MessageSource is defined in the applicationContext.xml, which can be injected into the AJAX request handler (i.e., AnnouncementAjaxBean) specified in the same XML file. This is not the only way to access Spring beans in DWR request handler object. The messageSource bean can also be obtained programmatically using org.springframework.web.context.support.WebApplicationContextUtils class.

	public static MessageSource getMessageSource(HttpServletRequest request) {
		WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getSession(false).getServletContext());
		return (ResourceBundleMessageSource) context.getBean(ApplicationConstants.MESSAGE_SOURCE_BEAN_NAME);
	}
  • Security

The sample application doesn't show use of Liferay security features, but the following Wiki page can give you some pointers on how it can be achieved programmatically: Using Liferay Permission System

SAMPLE APPLICATION SETUP#

The sample application is part of a portal-plugin project, which is uploaded with this Wiki page. The name of the uploaded attachment is portal-plugin.zip and it contains an Eclipse project which can be directly imported in Eclipse by using the import option.

Change build.properties : set the property liferay.portal.home to point to the Liferay home directory

Change jdbc.properties : set the properties to point to the database that you are going to use for running the sample application.

Create database tables : the attached sql.zip file contains the two tables that you need to create for making the sample application work. The sample application was created to work with MySQL database, therefore, the scipts may require modification if some other database is used for running the portlets.

Run ANT build : Right click the build.xml file in Eclipse and run its build or build-liferay target. This will compile the source code and copy the generated WAR file (portal-plugin.war)to the deploy folder of liferay for hot deployment. You may have to do hot deployment again to ensure that the application works, the reason for this is mentioned in the TROUBLESHOOTING section below.

Add portlets : Add the portlets to the portal page by using the 'Add Application' option of Liferay. You will see the two portlets under 'My Announcement' category, as shown in the screenshot below.

TROUBLESHOOTING#

  • Browser compatibility issues
    • The application has been tested to work properly in Mozilla browser. The File upload will not work in Chrome, as mentioned below.
  • Spring-Hibernate integration
    • Spring 3.0 M2 requires antlr-3.0.1 JAR and Hibernate 3.3.1 GA requires antlr-2.7.6 JAR file. If you keep both the JAR files then it will not be a problem as they following different packaging structure.
    • Hibernate gives the follwing exception at the beginning: org.springframework.transaction.CannotCreateTransactionException: Could not open Hibernate Session for transaction; nested exception is java.lang.IllegalArgumentException: interface org.hibernate.jdbc.ConnectionWrapper is not visible from class loader. The bug is entered at the hibernate JIRA site: http://opensource.atlassian.com/projects/hibernate/browse/HHH-3529 The only solution to this problem for now is to redploy your WAR file and the exception will disappear.
  • DWR integration
    • If you define the Spring bean (referred from dwr.xml) in the application context XML that is specified in the portlet.xml file then it is not available to the application. The Spring bean should be defined only in the applicationContext.xml that is configured in web.xml file.
    • DRW 3 RC1 File Upload feature doesn't work with Google Chrome. A bug was logged at DWR bug tracking site and has been fixed in the DWR 3 RC2: http://bugs.directwebremoting.org/bugs/browse/DWR-331
8 archivos adjuntos
120028 Accesos
Promedio (4 Votos)
La valoración media es de 5.0 estrellas de 5.
Comentarios
Respuestas anidadas Autor Fecha
Please remove spring-hibernate3.jar file from... Ashish Sarin 16 de junio de 2009 6:14
You talked about portal-plugin.zip file, but I... tremen _73 19 de septiembre de 2009 1:51
I'm sorry! I've just seen the link right now.... tremen _73 19 de septiembre de 2009 2:04
I believe that it should be noted that any bean... Sebastian Konkol 5 de marzo de 2010 12:38
FYI. Another way to fix this.... <form:form... David Engfer 18 de mayo de 2010 7:09
can work on liferay6.0+? arron wu 8 de julio de 2010 1:59
This works great. The demo does show "error..."... Vishal Bhanderi 4 de agosto de 2010 15:32
Is it possible to exclude manual hibernate and... SZ khan 4 de octubre de 2010 2:01
I have an "error..." on both page "RECENT... Fairouz F 18 de marzo de 2011 14:27
For me its worked fine for the first entry in... Navnath Gajare 10 de noviembre de 2011 4:43
doesn't the web.xml need something to kickstart... Fox Mulder 14 de noviembre de 2011 3:30
Please share a sample project. when i include... sravan kumar 18 de marzo de 2013 6:38

Please remove spring-hibernate3.jar file from WEB-INF/lib directory. The JAR file is from an earlier version of Spring (2.0.4). I forgot to remove it from the attached portal-plugin.zip file. Removing this JAR file will not have any adverse affect on the application.

-Ashish
Publicado el día 16/06/09 6:14.
You talked about portal-plugin.zip file, but I can't see any link to the file for downloading.
Could you tell me how can I get sample application sources?

Thanks
Publicado el día 19/09/09 1:51.
I'm sorry!
I've just seen the link right now. I'm new to Liferay community and I don't know about common features such as locating attached files.

Excuse me.
Publicado el día 19/09/09 2:04 en respuesta a Jose Carlos García.
I believe that it should be noted that any bean created through spring in portlet context won't be visible in DWR unless given session-global scope (sharing beans across portlet session and http session mode). When bean has session-global scope it can be restored from HttpSession.
Publicado el día 5/03/10 12:38.
FYI. Another way to fix this....

<form:form name="<portlet:namespace/>_announcementForm" commandName="announcement" method="post" action="${formAction}">
.....
.....
</form:form>

is do do this...

<form:form commandName="announcement" method="post" action="${formAction}">
<jsp:attribute name="name"><portlet:namespace/>_announcementForm</jsp:attribute>
.....
.....
</for­m:form>
Publicado el día 18/05/10 7:09.
can work on liferay6.0+?
Publicado el día 8/07/10 1:59.
This works great. The demo does show "error..." on page if steps are not followed correctly. This method of development looks promising but i cannot seem to debug my code anymore using eclipse!
Publicado el día 4/08/10 15:32.
Is it possible to exclude manual hibernate and integrate this portlet with Liferay service builder directly?

-Sultee
Publicado el día 4/10/10 2:01.
I have an "error..." on both page "RECENT ANNOUNCEMENT" and ""MANAGE ANNOUNCEMENT".I don't understand the problem.
Can someone help me please?
Publicado el día 18/03/11 14:27 en respuesta a Sultee khan.
For me its worked fine for the first entry in table...then onwords it started giving error...on both portlets....
chek hibernate dialect for different Databases....
also Log class was visible at more than two jars...u need to hanndle tht as well...
one more problem was serialization of two classes.
Publicado el día 10/11/11 4:43 en respuesta a Fairouz F.
doesn't the web.xml need something to kickstart spring, e.g. a filter config:
 1
 2    <filter>
 3        <filter-name>requestContextFilter</filter-name>
 4        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>­
 5    </filter>
 6    <filter-mapping>
 7        <filter-name>requestContextFilter</filter-name>
 8        <url-pattern>/*</url-pattern>       
 9    </filter-mapping>


and don;t you have to add spring jars to the portlet?
Publicado el día 14/11/11 3:30 en respuesta a Navnath Gajare.
Please share a sample project.
when i include DWR i am getting error as "org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dwrController': Invocation of init method failed; nested exception is java.lang.IllegalStateException: WebApplicationObjectSupport instance [org.directwebremoting.spring.DwrController@aeac28] does not run within a ServletContext. Make sure the object is fully configured!"

thanks in advance.
Publicado el día 18/03/13 6:38.