Liferay service builder, class loader issues

When you create a new service with Liferay service builder that system generates a whole lot of boilerplate code based on the few lines you put into the service.xml. Part of the code deals with the problem related to passing objects between different web applications.

Think about a simple example where you have your portlet project and a hook project. In portlet project you have defined some services and in hook project you consume those. Both portlet and hook are deployed as separate webapps under the application server of your choice (probably Tomcat).

When you are calling a service from the hook project the service actually executes in the portlet webapp context. The results are then returned to the code that executes under the hook webapp context.

Sounds easy but it’s not. Each webapp has its own class loader. Same class loaded by different class loaders is not considered to be the same from JVM perspective. This means they are not compatible. If you take instance of MyModel that has been loaded under class loader B and try to cast it to MyModel that has been loaded by class loader A you will get ClassCastException. This is a problem for us. The service running in portlet project instantiates MyModel using its own classloader. The instance is then supposed to be returned to the Hook project but it has its own view of MyModel class (since it is using different classloader).

Liferay service builder deals with the problem with class loader proxies and with some “magic”. When you call the porlet service from the hook project you don’t actually get back the same instance of MyModel that was instantiated in the service. Instead the code generated by service builder instantiates a new model using the class loader from the hook project. Information from the model instantiated in the service is then copied to this new model instance and it is returned to the caller.

This works beautifully and is pretty transparent for the developer. As long as all necessary code is generated by service builder. If your service returns a class that is not generated with service builder you may run into trouble.

Let’s say you are implementing a service that does not actually access the database. Therefore you have a service in your service.xml that does not define any columns. Instead you want to create your own model class by hand and return that from the service. This becomes an issue, because all the magical code generated by service builder to deal with class loader issues only applies to model classes generated by service builder. There is an issue in Liferay bug tracker for this:

One way to go around the problem is to implement your own serialize – deserialize procedure in the ClpSerializer that gets generated by service builder. The process is pretty simple. You first serialize the class returned from the service and the deserialize it. Since the deserialization happens under the hook project class loader you end up with an instance that is compatible with other code in the hook project.

Below is a example of the serialize/deserialize process:

	public static Object translateOutputGeneric(Object obj) {
		try {		
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);	
			oos.writeObject(obj);
			ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
			ObjectInputStream ois = new ObjectInputStream(bis);
			return ois.readObject();
		} catch(IOException e) {
			throw new RuntimeException(e);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
	}