Liferay Jpa Portlet – Part 2

In the previous article of this serie we’ve taken a look at the service layer of Warehouse portlet using EclipseLink Jpa implementation.

Now it’s time to go on and see the Spring Jpa controller and the anatomy of portlet.

In the Spring configuration file I defined:

  • Spring component scan package;
  • Jndi resource of the database;
  • The entity manager;
  • Transaction manager.

Here the code:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:util="http://www.springframework.org/schema/util" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/jee
        http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        ">
	<context:component-scan base-package="it.techannotation.warehouse" />

	<jee:jndi-lookup jndi-name="java:comp/env/jdbc/OrderDB"
		id="dataSource" />

	<bean id="sessionFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="persistenceUnitName" value="OrderPU" />
		<property name="dataSource" ref="dataSource" />
		<property name="jpaVendorAdapter">
			<bean
				class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
				<property name="showSql" value="true" />
				<property name="generateDdl" value="false" />
				<property name="databasePlatform"
					value="org.eclipse.persistence.platform.database.MySQLPlatform" />
			</bean>
		</property>
		<property name="loadTimeWeaver">
			<bean
				class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
		</property>
		<property name="jpaDialect">
			<bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect" />
		</property>

	</bean>

	<!-- Add Transaction support -->
	<bean id="myTxManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="sessionFactory" />
	</bean>

	<!-- Use @Transaction annotations for managing transactions -->
	<tx:annotation-driven transaction-manager="myTxManager" />

</beans>

We’re talking about jpa, so, this is the persistence unit configured:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
	xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
 http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

	<persistence-unit name="OrderPU">
		<!-- Only for local test -->
		<class>it.techannotation.warehouse.jpa.Order</class>
		<class>it.techannotation.warehouse.jpa.Product</class>

		<properties>
			<property name="eclipselink.allow-zero-id" value="true" />
		</properties>

	</persistence-unit>

</persistence>

A very strange thing, at least in my opinion, is the property configured. For some strange reason, without it, eclipselink thinks that a key set to zero is equal null..

Remember to put that file into the folder classes\META-INF\persistence.xml. Don’t make confusion with the root META-INF folder.

Let’s go on with the portlet code and, before do it, remember to add the follow entry at the VM arguments of Tomcat instance of Liferay:

-javaagent:\liferay-portal-6.1.1-ce-ga2\tomcat-7.0.27\lib\spring-instrument-3.1.0.RELEASE.jar

However, the core of the project is the portlet code:

package it.techannotation.warehouse.portlet;

import it.techannotation.warehouse.jpa.Order;
import it.techannotation.warehouse.jpa.Product;
import it.techannotation.warehouse.service.OrderService;
import it.techannotation.warehouse.service.ProductService;
import it.techannotation.warehouse.utils.Constants;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.MimeResponse;
import javax.portlet.PortalContext;
import javax.portlet.PortletException;
import javax.portlet.ProcessAction;
import javax.portlet.RenderMode;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.web.portlet.context.PortletApplicationContextUtils;
import org.w3c.dom.Element;

public class OrderPortlet extends GenericPortlet {

	private Logger logger = Logger.getLogger(OrderPortlet.class);
	private OrderService orderService;
	private ProductService productService;

	public void initDaoService() {

		if (orderService == null) {
			synchronized (OrderPortlet.class) {
				if (orderService == null) {
					ApplicationContext springCtx = PortletApplicationContextUtils
							.getWebApplicationContext(getPortletContext());
					orderService = (OrderService) springCtx
							.getBean("orderService");

					productService = (ProductService) springCtx
							.getBean("productService");
				}
			}
		}
	}

	protected void doHeaders(RenderRequest request, RenderResponse response) {
		Element cssElement = response.createElement("link");
		// --encoding URLs is important
		cssElement.setAttribute("href", response.encodeURL((request
				.getContextPath() + "/css/warehouse.css")));
		cssElement.setAttribute("rel", "stylesheet");
		cssElement.setAttribute("type", "text/css");
		response.addProperty(MimeResponse.MARKUP_HEAD_ELEMENT, cssElement);
	}

	@RenderMode(name = "VIEW")
	public void showOrders(RenderRequest request, RenderResponse response)
			throws IOException, PortletException {

		logger.info("Show Orders and Products");

		// Load Dao resources
		initDaoService();

		// get action
		String myaction = request.getParameter(Constants.MYACTION_PARAM);
		// Set title
		response.setTitle(getResourceBundle(request.getLocale()).getString(
				"portlet.title.showOrder"));

		// --set the myaction parameter in session for debugging purposes
		request.getPortletSession().setAttribute("myaction", myaction);

		// Default home page
		String jspPage = "home.jsp";

		if ("addOrder".equalsIgnoreCase(myaction)) {
			jspPage = "addOrder.jsp";
		} else {
			List<Order> orders = orderService.getOrders();

			request.setAttribute("orders", orders);

			if ("addProduct".equalsIgnoreCase(myaction)) {
				jspPage = "addProduct.jsp";
			}
		}
		// Render the page
		getPortletContext().getRequestDispatcher(
				response.encodeURL(Constants.PATH_TO_JSP_PAGE + jspPage))
				.include(request, response);
	}

	@ProcessAction(name = "addOrderAction")
	public void addOrder(ActionRequest request, ActionResponse response)
			throws PortletException, IOException {
		logger.info("Add Order action invoked");
		int order_id = 0;
		String description = request.getParameter("description");

		// --contains map of field names to error message
		String errorMessage = "";

		Pattern integerPattern = Pattern.compile("^\\d*$");
		Matcher matchesInteger = integerPattern.matcher(request
				.getParameter("orderId"));
		boolean isInteger = matchesInteger.matches();

		if (!isInteger) {
			errorMessage = getResourceBundle(request.getLocale()).getString(
					"portlet.addorder.error");
		} else
			order_id = Integer.parseInt(request.getParameter("orderId"));

		if (description == null || description.trim().equalsIgnoreCase("")) {
			errorMessage = getResourceBundle(request.getLocale()).getString(
					"portlet.addorder.error");
		}

		// --if no error found, go ahead and save
		if (errorMessage.equals("")) {
			logger.info("adding order to the data store");
			try {
				orderService.insertOrder(new Order(order_id, description));
			} catch (Exception ex) {
				response.setRenderParameter(Constants.MYACTION_PARAM, "");
				ex.printStackTrace();

				return;
			}
			response.setRenderParameter(Constants.MYACTION_PARAM, "");
		} else {
			logger.info("validation error occurred. re-showing the add order form");
			request.setAttribute("error", errorMessage);

			// Set the value already send
			Map<String, String> valuesMap = new HashMap<String, String>();
			valuesMap.put("orderId", request.getParameter("orderId"));
			valuesMap.put("description", description);

			request.setAttribute("order", valuesMap);
			response.setRenderParameter(Constants.MYACTION_PARAM, "addOrder");
		}
	}

	@ProcessAction(name = "addProductAction")
	public void addProduct(ActionRequest request, ActionResponse response)
			throws PortletException, IOException {
		logger.info("Add Product action invoked");

		// --contains map of field names to error message
		String errorMessage = "";

		String productId = request.getParameter("productId");

		if (productId.trim().equalsIgnoreCase("")) {
			errorMessage = getResourceBundle(request.getLocale()).getString(
					"portlet.addorder.error");
		}

		String description = request.getParameter("description");
		if (description.trim().equalsIgnoreCase("")) {
			errorMessage = getResourceBundle(request.getLocale()).getString(
					"portlet.addorder.error");
		}

		double price = 0;

		Pattern doublePattern = Pattern.compile("\\d+\\.\\d+");
		Matcher matchesDouble = doublePattern.matcher(request.getParameter("price"));
		boolean isDoublePrice = matchesDouble.matches();

		if (isDoublePrice)
			price = Double.parseDouble(request.getParameter("price"));
		else
			errorMessage = getResourceBundle(request.getLocale()).getString("portlet.addorder.error");

		double qtaAvailable = 0;

		matchesDouble = doublePattern.matcher(request
				.getParameter("qtaAvailable"));
		boolean isDoubleQtaAvailable = matchesDouble.matches();

		if (isDoubleQtaAvailable)
			qtaAvailable = Double.parseDouble(request
					.getParameter("qtaAvailable"));
		else
			errorMessage = getResourceBundle(request.getLocale()).getString("portlet.addorder.error");

		int orderId = Integer.parseInt(request.getParameter("orderId"));

		// --if no error found, go ahead and save

		// --if no error found, go ahead and save
		if (errorMessage.equals("")) {
			logger.info("adding Product to the WareHouse");
			try {
				productService.insertProduct(orderId, new Product(productId,
						description, price, qtaAvailable));
			} catch (Exception ex) {
				response.setRenderParameter(Constants.MYACTION_PARAM, "");
				ex.printStackTrace();

				return;
			}
		} else {
			logger.info("validation error occurred. re-showing the add product form");
			request.setAttribute("error", errorMessage);

			// Set the value already send
			Map<String, String> valuesMap = new HashMap<String, String>();
			valuesMap.put("productId", productId);
			valuesMap.put("description", description);
			valuesMap.put("qtaAvailable", String.valueOf(qtaAvailable));
			valuesMap.put("price", String.valueOf(price));

			request.setAttribute("product", valuesMap);
			response.setRenderParameter(Constants.MYACTION_PARAM, "addProduct");
		}
		response.setRenderParameter(Constants.MYACTION_PARAM, "");

	}

	@ProcessAction(name = "removeProductAction")
	public void removeProduct(ActionRequest request, ActionResponse response)
			throws PortletException, IOException {
		logger.info("Remove Product action invoked");

		String productId = request.getParameter("productId");

		try {
			productService.deleteProduct(productId);
		} catch (Exception ex) {
			response.setRenderParameter(Constants.MYACTION_PARAM, "");
			ex.printStackTrace();

			return;
		}
		response.setRenderParameter(Constants.MYACTION_PARAM, "");
	}

	@ProcessAction(name = "removeOrderAction")
	public void removeOrder(ActionRequest request, ActionResponse response)
			throws PortletException, IOException {
		logger.info("Remove Product action invoked");

		int orderId = Integer.parseInt(request.getParameter("orderId"));

		try {
			orderService.deleteOrder(orderId);
		} catch (Exception ex) {
			response.setRenderParameter(Constants.MYACTION_PARAM, "");
			ex.printStackTrace();

			return;
		}
		response.setRenderParameter(Constants.MYACTION_PARAM, "");
	}
}

The code shows one render method (showOrders) and two action methods for adding the data  (addOrder and addProduct) and two action methods for removing them (removeOrder and removeProduct) .

The annotations of those methods let Liferay to know which method call when different actions/event are invoked.

Briefly the render method calls the getOrders service layer method while the two actions method named add* call the insert* and the remove* call the delete* service layer methods (I know, it’s easier seeing the code for getting the meaning..).

Take attention at the initDaoService() method. I don’t invoke it from the Portlet init method but I invoke it implementing the singleton pattern.

That’s because, when you deployed the portlet, you’ll see something like this at the web.xml

<listener>
  <listener-class>com.liferay.portal.kernel.servlet.PluginContextListener</listener-class>
</listener>
<listener>
  <listener-class>com.liferay.portal.kernel.servlet.SerializableSessionAttributeListener</listener-class>
</listener>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

You can see that the PluginContextListener has loaded before the ContextLoadListener, so you get an error when you try to load the Spring resource.

Now the view jsp page (home.jsp)

<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page contentType="text/html" isELIgnored="false"
	import="javax.portlet.*,it.techannotation.warehouse.utils.Constants"%>

<fmt:setLocale value="<%=request.getLocale()%>" />
<fmt:setBundle basename="content.Language-ext" />
<portlet:defineObjects />

<table>
	<c:forEach var="order" items="${orders}">
		<tr class="header">
			<td colspan="4">Order</td>
			<td><a href='<portlet:renderURL><portlet:param name="<%=Constants.MYACTION_PARAM%>" value="addOrder"/></portlet:renderURL>'><img src="<%=request.getContextPath()%>/images/addorder.jpg"></a></td>
		</tr>
		<tr class="header">
			<td>OrderId</td>
			<td>Description</td>
			<td colspan="3">&nbsp;</td>
		</tr>
		<tr>
			<td valign="top"><c:out value="${order.order_id}" /></td>
			<td valign="top"><c:out value="${order.description}" /></td>
			<td><a href="top" colspan="3"><a href='<portlet:actionURL name="removeOrderAction"><portlet:param name="productId" value="${product.product_id}" /></portlet:actionURL>'><img src="<%=request.getContextPath()%>/images/remove.jpg"></a></a></td>
		</tr>
		<tr class="header">
			<td colspan="4">Products</td>
			<td><a href='<portlet:renderURL><portlet:param name="<%=Constants.MYACTION_PARAM%>" value="addProduct"/></portlet:renderURL>'><img src="<%=request.getContextPath()%>/images/addproduct.jpg"></a></td>
		</tr>
		<tr class="headersublevel">
			<td>ProductId</td>
			<td>Description</td>
			<td>Price</td>
			<td>Available</td>
			<td>&nbsp;</td>
		</tr>
		<c:forEach var="product" items="${order.products}">
			<tr>
				<td valign="top"><c:out value="${product.product_id}" /></td>
				<td valign="top"><c:out value="${product.description}" /></td>
				<td valign="top"><c:out value="${product.price}" /></td>
				<td valign="top"><c:out value="${product.qtaAvailable}" /></td>
				<td><a href="top" colspan="3"><a href='<portlet:actionURL name="removeProductAction"><portlet:param name="productId" value="${product.product_id}" /></portlet:actionURL>'><img src="<%=request.getContextPath()%>/images/remove.jpg"></a></a></td>
			</tr>
		</c:forEach>
	</c:forEach>
</table>

And the AddOrder and AddProduct

<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page contentType="text/html" isELIgnored="false"
import="javax.portlet.*,it.techannotation.warehouse.utils.Constants"%>

<fmt:setLocale value="<%=request.getLocale()%>" />
<fmt:setBundle basename="content.Language-ext" />
<portlet:defineObjects />

<form name="<portlet:namespace/>addOrder" method="post"
action='<portlet:actionURL name="addOrderAction"/>'>
<table>
<tr>
<td><a
href='<portlet:renderURL portletMode="view"/>'><b>HOME</b></a></td>
</tr>
<tr>
<td><font style="color: #C11B17;"><c:out
value="${requestScope.error}" /></font></td>
</tr>
</table>
<table>
<tr>
<td colspan="2">Order</td>
</tr>
<tr>
<td><b>OrderId:</b><font
style="color: #C11B17;">*</font></td>
<td><input type="text" name="<portlet:namespace/>orderId"
value='<c:out value="${requestScope.order.orderId}"/>' /></td>
</tr>
<tr>
<td><b>Description:</b></td>
<td><input type="text" name="<portlet:namespace/>description"
value='<c:out value="${requestScope.order.description}"/>' /></td>
</tr>
<tr align="center">
<td colspan="3"><input type="image" src="<%=request.getContextPath()%>/images/addorder.jpg" /></td>
</tr>
</table>
</form>

AddProduct


<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page contentType="text/html" isELIgnored="false"
import="javax.portlet.*,it.techannotation.warehouse.utils.Constants"%>

<fmt:setLocale value="<%=request.getLocale()%>" />
<fmt:setBundle basename="content.Language-ext" />
<portlet:defineObjects />

<form name="<portlet:namespace/>addProduct" method="post"
action='<portlet:actionURL name="addProductAction"/>'>
<table>
<tr>
<td><a
href='<portlet:renderURL portletMode="view"/>'><b>HOME</b></a></td>
</tr>
<tr>
<td><font style="color: #C11B17;"><c:out
value="${requestScope.error}" /></font></td>
</tr>
</table>
<table>
<tr>
<td colspan="3">Product</td>
</tr>
<tr>
<td><b>Order:</b></td>
<td>
<select name="<portlet:namespace/>orderId">
<c:forEach var="order" items="${orders}">
<option value='<c:out value="${order.order_id}" />'><c:out value="${order.description}" /></option>
</c:forEach>
</select>
</td>
</tr>
<tr>
<td><b>ProductId:</b><font
style="color: #C11B17;">*</font></td>
<td><input type="text" name="<portlet:namespace/>productId" /></td>
<td><font style="color: #C11B17;"><c:out
value="${requestScope.errors.productId}" /></font></td>
</tr>
<tr>
<td><b>Description:</b></td>
<td><input type="text" name="<portlet:namespace/>description" /></td>
<td><font style="color: #C11B17;"><c:out
value="${requestScope.errors.description}" /></font></td>
</tr>
<tr>
<td><b>Price:</b></td>
<td><input type="text" name="<portlet:namespace/>price" /></td>
<td><font style="color: #C11B17;"><c:out
value="${requestScope.errors.price}" /></font></td>
</tr>
<tr>
<td><b>Quantity Available:</b></td>
<td><input type="text" name="<portlet:namespace/>qtaAvailable" /></td>
<td><font style="color: #C11B17;"><c:out
value="${requestScope.errors.qtaAvailable}" /></font></td>
</tr>

<tr align="center">
<td colspan="3"><input type="image" src="<%=request.getContextPath()%>/images/addproduct.jpg" /></td>
</tr>
</table>
</form>

That’s all folk. I think I’ve said everything about the code illustrated.

The argument is very big and, obviously, one article is not enough to cover all the arguments. I’ll try to do other articles on this argument.

The project code WareHousePortlet (rename doc as a zip).

Advertisements

One thought on “Liferay Jpa Portlet – Part 2

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s