Month: May 2014

AUI Autocomplete with service builder json datasource

Since it’s not so trivial to correctly understand AlloyUI Autocomplete that’s shipped with Liferay 6.2 then here’s an example on how to make an autocomplete that uses a custom service builder json web service as a datasource.
We will use AUI Autocomplete widget and then user Liferay Service library to fetch the result from our own custom service builder built JSON web service.

This is the look we are trying to achive:
aui autocomplete service builder

Full portlet view.jsp code:


<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme" %>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui"%>
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>

<portlet:defineObjects/>
<liferay-theme:defineObjects />

<aui:input name="contactName" type="text" />

<aui:script use="autocomplete-list,aui-base,aui-io-request-deprecated,autocomplete-filters,autocomplete-highlighters,datasource,datasource-get,datatable-datasource">

// Please note that this contact portlet service is a service builder generated JSON web service.
// We pass the groupId as a query param because our service expects it. Liferay has a nice javascript method for finding the group id.
var contactSearchDS = new A.DataSource.IO({source: '/api/jsonws/contact-portlet.contact/get-contacts-by-name?groupId='+Liferay.ThemeDisplay.getScopeGroupId()});

var contactSearchQueryTemplate = function(query) {
        // Here's an example on how to pass additional parameters to the query for you service
        // In our case we are fetching only the first 20 items and specify the ordering by name
	var output = '&name='+query.trim()+'&sort=name&dir=asc&start=0&end=20';
	return output;
}

var contactSearchLocator = function (response) {
	var responseData = A.JSON.parse(response[0].responseText);
// For debugging you can do: console.debug(responseData);
    return responseData;
};

var contactSearchFormatter = function (query, results) {
	return A.Array.map(results, function (result) {
// For debugging: console.debug(result.raw);
		return '<strong>'+result.raw.fullName+'</strong><br/>'+result.raw.mobile+' '+result.raw.phone+' '+result.raw.email;
	});
};

var contactSearchTextLocator = function (result) {
// This is what we place in the input once the user selects an item from the autocomplete list.
// In our case we want to put contact full name in there.
	return result.row.fullName;
};

var contactSearchInput = new A.AutoCompleteList({
	allowBrowserAutocomplete: 'false',
	resultHighlighter: 'phraseMatch',
	activateFirstItem: 'false',
	inputNode: '#<portlet:namespace/>contactName',
	render: 'true',
	source: contactSearchDS,
	requestTemplate: contactSearchQueryTemplate,
	resultListLocator: contactSearchLocator,
        resultFormatter: contactSearchFormatter,
	resultTextLocator: contactSearchTextLocator		
});

</aui:script>		

We’ll post an example of “multi-autocomplete” input also soon. That one requires a lot more code but allows selecting multiple contacts and shows them as “tags” above the input box. Much like the facebook name finder works.

As always, if you have questions or suggestions then please comment below.

Struts2 Convention & Spring Junit4

The problem

You normally would use StrutsSpringJunit4TestCase as the base class to unit-test your actions. But this class does not support Struts2 Convention plugin Annotations. So if you are like me and you like to keep your actions and results defined in the Action classes by annotations you are s***-out-of-luck. Most likely on your first attempt you will see exceptions like this:

WARN org.springframework.mock.web.MockServletContext.getResourcePaths:212 – Couldn’t get resource paths for class path resource [WEB-INF/content/]¬†java.io.FileNotFoundException: class path resource [WEB-INF/content/] cannot be resolved to URL because it does not exist. This effectively means that the conventions plugin is attempting to locate the resources etc.. Now the simple solution to resolve all those warnings is to modify your struts.xml file and add this setting in there:

<constantname=“struts.convention.result.path”value=“/”/>

Ok, so the warnings are gone but non of your annotated struts actions have actually been loaded into the configuration and you can’t unit-test them. So what gives?

The Solution – Almost

Now, here’s a nice solution almost:¬†http://depressedprogrammer.wordpress.com/2007/06/18/unit-testing-struts-2-actions-spring-junit/

I do want to credit Zarar Siddiqi for that class mostly. It’s a good starting base, but needs a bit of customization for the latest Struts / Spring and Junit 4 environment.

The Solution – Finally

Here’s the source code for the class i created based on the above posting:

import java.util.HashMap;

import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.mapper.ActionMapper;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.views.JspSupportServlet;
import org.junit.Before;
import org.junit.BeforeClass;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ContextLoader;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionProxyFactory;

/**
 * Improved the sample from originally
 * http://depressedprogrammer.wordpress.com/2007/06/18/unit-testing
 * -struts-2-actions-spring-junit/
 * 
 * - Added support for Struts Convention plugin annotations, also made it run
 * with JUnit 4.
 * 
 * @author Zarar Siddiqi
 * @author codeyouneed.com
 * 
 */
public abstract class StrutsConventionSpringJUnit4TestCase {

	/**
	 * Where are the Spring configurations?
	 */
	private static final String CONFIG_LOCATIONS = "applicationContext-test.xml,"
			+ "applicationContext-test-security.xml";

	private static ApplicationContext applicationContext;
	protected Dispatcher dispatcher;
	protected ActionProxy proxy;
	protected static MockServletContext servletContext;
	protected static MockServletConfig servletConfig;
	protected MockHttpServletRequest request;
	protected MockHttpServletResponse response;

	/**
	 * Mimic the getActionProxy method the usual StrutsSpringJuni4TestCase uses.
	 * 
	 * @param clazz
	 *          Class for which to create Action
	 * @param namespace
	 *          Namespace of action
	 * @param name
	 *          Action name
	 * @return Action class
	 * @throws Exception
	 *           Catch-all exception
	 */
	@SuppressWarnings("unchecked")
	protected ActionProxy getActionProxy(String uri) {
		request.setRequestURI(uri);
		ActionMapping mapping = Dispatcher
				.getInstance()
				.getContainer()
				.getInstance(ActionMapper.class)
				.getMapping(request, Dispatcher.getInstance().getConfigurationManager());
		String namespace = mapping.getNamespace();
		String name = mapping.getName();
		String method = mapping.getMethod();

		// create a proxy class which is just a wrapper around the action call.
		// The proxy is created by checking the namespace and name against the
		// struts.xml configuration
		proxy = dispatcher
				.getContainer()
				.getInstance(ActionProxyFactory.class)
				.createActionProxy(namespace, name, method,
						new HashMap&lt;String, Object&gt;(), true, false);

		// Add all request parameters to our action
		ActionContext invocationContext = proxy.getInvocation()
				.getInvocationContext();
		invocationContext.setParameters(new HashMap&lt;String, Object&gt;(request
				.getParameterMap()));

		// set the action context to the one used by the proxy
		ActionContext.setContext(invocationContext);

		// By default, have a clean session.
		proxy.getInvocation().getInvocationContext()
				.setSession(new HashMap&lt;String, Object&gt;());

		// Important - unit testing JSP's is just not very helpful.
		// do not execute the result after executing the action.
		proxy.setExecuteResult(false);

		// set the actions context to the one which the proxy is using
		ServletActionContext.setContext(proxy.getInvocation()
				.getInvocationContext());
		ServletActionContext.setRequest(request);
		ServletActionContext.setResponse(response);
		ServletActionContext.setServletContext(servletContext);
		return proxy;
	}

	/**
	 * Create a new dispatcher before every test.
	 */
	@Before
	public void initDispatcher() {
		// Dispatcher is the guy that actually handles all requests. Pass in
		// an empty. Map as the parameters but if you want to change stuff like
		// what config files to read, you need to specify them here. Here's how to
		// scan packages for actions. Thanks to Hardy Ferentschik, for action
		// packages ideas. (see Dispatcher's source code)
		dispatcher = new Dispatcher(servletContext, new HashMap&lt;String, String&gt;());
		dispatcher.init();
		Dispatcher.setInstance(dispatcher);
		request = new MockHttpServletRequest();
		response = new MockHttpServletResponse();
	}

	/**
	 * Initialize Spring context before the unit tests.
	 * 
	 * @throws Exception
	 *           When something bad happens.
	 */
	@BeforeClass
	public static void setUpContext() throws Exception {
		// Only initialize Spring context once - speeds up the tests a lot.
		if (applicationContext == null) {
			// this is the first time so initialize Spring context
			servletContext = new MockServletContext();
			servletContext.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,
					CONFIG_LOCATIONS);
			applicationContext = (new ContextLoader())
					.initWebApplicationContext(servletContext);

			// Struts JSP support servlet (for Freemarker)
			new JspSupportServlet().init(new MockServletConfig(servletContext));
		}
	}
}

 

So what’s going on – what do I need to know?

Rather simply but, I needed the unit-testing class to support the newer versions of the used libraries, so the above class is made to work with the following environment:

  • JUnit 4.10
  • Spring 3.1.0
  • Struts 2.3.1.2

Warning about Tiles and other results

There’s no tiles support in the above class, and executing the results have been disabled. If you want to enable executing the results you have to modify this line:

proxy.setExecuteResult(false);

You can hack it in there if you like but I couldn’t get it to work because of lack of time and because I really didn’t wanted to unit-test my JSP’s and tiles. Here’s the code you need to add to the setUpContext() method to make it initialize the tiles.

// Goes way in the top of the class as a static configuration.
private static final String TILES_CONFIG = "WEB-INF/tiles.xml";

// Goes into setUpContext() method in the end of the class file...
servletContext.addInitParameter(BasicTilesContainer.DEFINITIONS_CONFIG, TILES_CONFIG);
final StrutsTilesListener tilesListener = new StrutsTilesListener();
final ServletContextEvent event = new ServletContextEvent(servletContext);
tilesListener.contextInitialized(event);

OrientDB database creation class

Problem: OrientDB does not support BigInteger’s out of the box. The simple solution is to tune OrientDB serializer to output BigIntegers as Integers and this allows us to store them in database nicely.

Although it’s a pretty straight-forward and simple class I decided to create a post about it because it took me a while to fight with the BigInteger issue.

Example:

import java.io.File;
import java.math.BigInteger;

import com.orientechnologies.orient.core.serialization.serializer.object.OObjectSerializer;
import com.orientechnologies.orient.object.db.OObjectDatabaseTx;
import com.orientechnologies.orient.object.serialization.OObjectSerializerContext;
import com.orientechnologies.orient.object.serialization.OObjectSerializerHelper;
import com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl;

public class CreateNewDatabase {

	public static void main(String[] args) {

		// Don't run without database location argument.
		if (args == null || args.length != 1) {
			System.out
					.println("Expected single argument of folder path where to store the database.");
			return;
		}

		String path = args[0];
		if (path.length() < 2) {
			System.out.println("Path is too short: " + path);
			return;
		}

		if (!path.endsWith(File.separator)) {
			path = path + File.separator;
		}

		OObjectDatabaseTx db = null;

		try {

			File folder = new File(path);
			if (folder.exists()) {
				throw new Exception("Database already exists on that path!");
			}

			// Create the folder as required.
			if (!folder.mkdirs()) {
				throw new Exception("Failed to create required folders:"
						+ folder.getAbsolutePath());
			}
			System.out.println("Creating database to: " + path);
			db = new OObjectDatabaseTx("plocal:" + path);
			// Avoid overwriting existing databases.
			if (db.exists()) {
				throw new Exception("Database already exists on that path!");
			}

			// Create database
			db.create();

			// Important code to serialize BigIntegers to Integers.
			// It's vital that this code is run before you register your classes.
			OObjectSerializerContext serializerContext = new OObjectSerializerContext();
			serializerContext
					.bind(new OObjectSerializer<BigInteger, Integer>() {

						public Integer serializeFieldValue(Class<?> itype,
								BigInteger iFieldValue) {
							return iFieldValue.intValue();
						}

						public BigInteger unserializeFieldValue(Class<?> itype,
								Integer iFieldValue) {
							return new BigInteger(iFieldValue.toString());
						}

					});
			OObjectSerializerHelper.bindSerializerContext(null,
					serializerContext);

			// Tell OrientDB to automatically generate schema during class
			// registration, this saves us some code.
			db.setAutomaticSchemaGeneration(true);
			// Now register our classes...
			db.getEntityManager().registerEntityClasses(
					"your.entity.package.goes.here");

			// Save the schema of our new database...
			db.getMetadata().getSchema().save();

			System.out.println("DONE");

		} catch (Exception e) {
			System.out.println(e.getMessage());
			e.printStackTrace();
		} finally {
			if (db != null) {
				db.close();
			}
		}

	}

}


As always, freel free to comment and ask questions in the comments.

How to create a taglib with JSP’s for Liferay

This is a bit tricky since we want to be able to nicely use our tag library in all our multiple Portlet projects, so the taglib should be a maven dependency. This usually means a Jar file. This is all nice and we can easily fit our Tag classes in the jar. We can also easily fit our TLD file in META-INF folder and it’s nicely available. Now comes the tricky part – What if we want our tag output to be a separate JSP page. Like almost all the Liferay tags are. JSP allows us to access all Liferay theme variables, other taglibs and all other useful things while generating our tag output.

The problem is that you can’t load the JSP from a jar file easily so the JSP’s have to be actually outside of your reusable jar project. So Liferay has an IncludeTag class that we want to extend that provides us with JSP output.

So we end up having 2 projects:

  • One for the Taglib TLD file and actual Tag classes that’s turned into JAR that we can use as a dependency in our other portlet projects.
  • One for the JSP files that is a Hook so the JSP’s are always available.

Here’s the example Tag class, this goes into our “example-userlist-taglib” project.
Notice that we have a JSP path that follows the Liferay convention on naming the files.

import javax.servlet.http.HttpServletRequest;
import com.liferay.taglib.util.IncludeTag;

public class UserList extends IncludeTag {

	private static final boolean _CLEAN_UP_SET_ATTRIBUTES = true;

	private static final String _PAGE = "/html/taglib/example/userlist/page.jsp";

	private String label;

	@Override
	protected String getPage() {
		return _PAGE;
	}

	@Override
	public int doStartTag() {
		return EVAL_BODY_INCLUDE;
	}

	@Override
	protected void cleanUp() {
		setLabel(null);
	}

	@Override
	protected boolean isCleanUpSetAttributes() {
		return _CLEAN_UP_SET_ATTRIBUTES;
	}

	@Override
	protected void setAttributes(HttpServletRequest request) {
		request.setAttribute("example:user-list:label", getLabel());
	}

	public String getLabel() {
		return label;
	}

	public void setLabel(String label) {
		this.label = label;
	}

}

Now an example TLD file on how to define a tag in taglib that should be in /src/main/resources/META-INF/ if you are running a maven based project:


<?xml version="1.0"?>
<taglib version="2.0" 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-jsptaglibrary_2_0.xsd">
	<tlib-version>1.0</tlib-version>
	<short-name>example</short-name>
	<uri>http://example.com/tld/liferay</uri>
	<tag>
		<name>user-list</name>
		<tag-class>com.example.package.UserList</tag-class>
		<body-content>JSP</body-content>
		<attribute>
			<name>label</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>		
</taglib>

Solution to our JSP loading problem is actually rather simple: Create a Hook project that contains all your taglib JSP’s and deploy that on any Liferay server you need to use your taglib. The files for my userlist tag have to go into META-INF/custom_jsps/html/taglib/example/userlist/ folder for example.

I actually have an init.jsp and a page.jsp file there, examples follow:
init.jsp:


<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui"%>
<%@ taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet"%>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme"%>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui"%>
<%@ taglib uri="http://liferay.com/tld/util" prefix="liferay-util"%>
<%@ page import="javax.portlet.PortletRequest"%>
<%@ page import="javax.portlet.PortletResponse"%>
<%@ page import="javax.portlet.PortletPreferences"%>
<portlet:defineObjects />
<liferay-theme:defineObjects />
<%
PortletRequest portletRequest = (PortletRequest)request.getAttribute(JavaConstants.JAVAX_PORTLET_REQUEST);
PortletResponse portletResponse = (PortletResponse)request.getAttribute(JavaConstants.JAVAX_PORTLET_RESPONSE);

String label = (String)request.getAttribute("example:user-list:label");

%>

and now the userlist tag jsp:


<%@ include file="init.jsp"%>
<div class="userlist">
	<strong><%=label %></strong>:
	<!-- Add code here for listing your users. -->
</div>


So there you have it! Now we can easily use our tag in any portlet jsp, provided we first include our taglib like this:

<%@ taglib uri="http://example.com/tld/liferay" prefix="example" %>

and then we can do:

<example:user-list label="Admin users" />