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);
		}
	}
Posted in Liferay |

Debugging ClassCastExceptions in Eclipse

Working with application servers it is not uncommon to run into strange ClassCastExceptions which seem to have now reason. You are trying to cast an instance of MyModel to MyModel and yet them JVM is having issues with it.

Experience has shown that there are two main categories for these “strange problems”:

  1. Multiple versions of the same class are present in the classpath. This can easily happen if you for example accidently include some libraries in your WAR file that are also provided by the application server.
  2. There is only one version of the class, but it is being loaded by different classloaders. If different classloaders load the same class it is not actually the same class from JVM’s perspective. Instead you have two different classes and casting from one to another causes ClassCastException.

The easist way to dig into the second class of problems is to use Eclipse debugger. Usually you get the issues from code like this:

   MyModel model = myRemoteService.getObj();

To make debugging easier you can split the code into two parts. This way you get the returned value in local variable that you can inspect:

   Object obj = myRemoteService.getObj();
   MyModel model = (MyModel) obj;

The ClassCastException happens on the second line. This means that the class of obj is not compatible with MyModel. To dig into the issue, put a breakpoint on the line. When the debugger stops on the line, start looking into the obj and the MyModel class.

I found that the best way is to create new expression in the debugger to see the ClassLoaders associated with each class. To do this, bring up the “Expressions” view in Eclipse (you might need to add that from Window – Show view). CLick “Add new expression” and add two expressions like this

  • obj.getClassLoader();
  • com.mycompany.MyModel.class.getClassLoader();

Now you see constantly in the expressions view which class loaders were used to load the classes. In the value field you can see if they are the same (if they are, they have the same id number). Most likely they are not, since otherwise you wouldn’t be getting the exception. To understand the problem, start looking into the properties of the class loaders. If you are working inside servlet container such as Tomcat the class loaders have a property name “contextName” that tells which webapplication loaded the class. This can give hints about the underlying problem. Then you can see the jarPath, jarNames, canonicalLoaderDir etc.

All this does not magically solve the problem but once you understand the actual cause of ClassCastException, it is easier to fix it.

Posted in Eclipse |

Using git to deploy new versions

Simple instructions for setting up git so that you can push new versions to server from the comfort of your workstation. This is based on article by Abhijit Menon-Sen. If you need more details, take a look at that.

This will create the home directory for the project underneath /var. Git repository shall be put on the same place, in .git subfolder. If you are using this to push a website, you need to either move the .git folder to some other place or use .htaccess to make sure the .git folder is not exposed to world.

On server (replace your project name on the first line)

export PROJECT=<NAME OF THE PROJECT>
mkdir /var/$PROJECT
mkdir /var/$PROJECT/.git
git init --bare /var/$PROJECT/.git
cat <<EOF > /var/$PROJECT/.git/hooks/post-receive
#!/bin/sh
GIT_WORK_TREE=/var/$PROJECT git checkout -f
EOF

chmod +x /var/$PROJECT/.git/hooks/post-receive

On client, where you have the git repository run the following to configure:

git remote add live ssh://<username>@<myserver>:/var/<project>

Then use this to push the master to the server

git push live master
Posted in Web development |

NullPointerException while creating new Portlet project in Liferay IDE

Creating new portlet project in Liferay IDE may result in NullPointerException and complaints about no IModelProvider being found for Dynamic Web Module 3.0. An example of the stack trace is below (you can see this in your $WORKSPACE/.metadata/.log -file).

This seems to result from some Eclipse components not being installed. I was able to fix the problem by installing “Eclipse Java Web Developer Tools” and “Eclipse Web Developer Tools” -packages. To do this, go to Help => Install new software. Select “all sites” from the work with drop down, enter “web” in the search box and wait a while. The user interface may freeze while Eclipse is searching for the components. Then select the components and click next from bottom of the screen.

!MESSAGE No IModelProvider exists for project P/Orbeon-4.0m5-portlet of version: Dynamic Web Module 3.0
!STACK 0
java.lang.NullPointerException: No IModelProvider exists for project P/Orbeon-4.0m5-portlet of version: Dynamic Web Module 3.0
        at org.eclipse.jst.j2ee.model.ModelProviderManager.getModelProvider(ModelProviderManager.java:101)
        at org.eclipse.jst.j2ee.web.project.facet.WebFacetInstallDelegate.populateDefaultContent(WebFacetInstallDelegate.java:261)
        at org.eclipse.jst.j2ee.web.project.facet.WebFacetInstallDelegate.createWeb30DeploymentDescriptor(WebFacetInstallDelegate.java:234)
        at org.eclipse.jst.j2ee.web.project.facet.WebFacetInstallDelegate.execute(WebFacetInstallDelegate.java:116)
        at org.eclipse.wst.common.project.facet.core.internal.FacetedProject.callDelegate(FacetedProject.java:1477)
        at org.eclipse.wst.common.project.facet.core.internal.FacetedProject.modifyInternal(FacetedProject.java:441)
        at org.eclipse.wst.common.project.facet.core.internal.FacetedProject.mergeChangesInternal(FacetedProject.java:1181)
        at org.eclipse.wst.common.project.facet.core.internal.FacetedProject.access$2(FacetedProject.java:1117)
        at org.eclipse.wst.common.project.facet.core.internal.FacetedProject$5.run(FacetedProject.java:1099)
        at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:2344)
        at org.eclipse.wst.common.project.facet.core.internal.FacetedProject.mergeChanges(FacetedProject.java:1109)
        at org.eclipse.wst.common.project.facet.core.internal.FacetedProjectWorkingCopy.commitChanges(FacetedProjectWorkingCopy.java:2020)
        at org.eclipse.wst.common.project.facet.ui.ModifyFacetedProjectWizard.performFinish(ModifyFacetedProjectWizard.java:400)
        at org.eclipse.wst.web.ui.internal.wizards.NewProjectDataModelFacetWizard.performFinish(NewProjectDataModelFacetWizard.java:282)
        at com.liferay.ide.eclipse.project.ui.wizard.NewPluginProjectWizard.performFinish(NewPluginProjectWizard.java:329)
        at org.eclipse.wst.common.project.facet.ui.ModifyFacetedProjectWizard$3.run(ModifyFacetedProjectWizard.java:331)
        at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:2344)
        at org.eclipse.wst.common.project.facet.ui.ModifyFacetedProjectWizard$4.run(ModifyFacetedProjectWizard.java:345)
        at org.eclipse.jface.operation.ModalContext$ModalContextThread.run(ModalContext.java:121)
Posted in Misc |

Starting up node.js apps on Ubuntu

Tim Smart describes in his article a great way to use upstart on Ubuntu to startup node.js applications.

Below is my version of his script. I stripped the PID file handling and moved configuration to use environment variables (to be able to use the same basescript for multiple apps). The start on -condition is modified according to comments posted on the article.

To use this you need to have upstart installed on your system. If you are running Ubuntu, you probably have. You put the following script to /etc/init/myapp.conf (obviously change the name to reflect your application). The name of the script also determines the log file that will be used under /var/log (UPSTART_JOB environment variable).

Once the script is in place, you case use the following commands to start/stop your application:

  • start myapp
  • stop myapp
  • status myapp

/etc/init/myapp.conf:

#!upstart
description "node.js MyApplication"
author      "Juha Palomaki"

env HOMEDIR=/var/myapp
env RUNAS=myappuser
env APP=/var/myapp/app.js
env NODEJS=/usr/bin/node

start on (local-filesystems and net-device-up IFACE=eth0)
stop on shutdown

script
    chdir $HOMEDIR
    exec sudo -u $RUNAS $NODEJS $APP >> /var/log/${UPSTART_JOB}.log 2>&1
end script

pre-start script
    # Date format same as (new Date()).toISOString() for consistency
    echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Starting" >> /var/log/${UPSTART_JOB}.log
end script

pre-stop script
    echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Stopping" >> /var/log/${UPSTART_JOB}.log
end script

For more information on Upstart, check out the Upstart Cookbook.

Posted in Ubuntu |

Replacing multiple occurences of character in bash

I wanted to replace multiple occurrences of underscore with single in one bash shell script. With regular expressions this is pretty simple, but by default they are not available directly in bash (using awk, sed and other tools would have obviously solved the problem). The “pure bash” solution involved turning on the regexps with shopt -s extglob:

$
$ shopt -s extglob
$ filename=Test____document__v10.pdf
$ echo ${filename//+(\_)/_}
Test_document_v10.pdf
$
Posted in Misc |

Transfer MongoDB collection to Windows

A quick’n’dirty1 way for transfering MongoDB collection from Linux to Windows, using plink as the ssh client.

plink -load null username@remotehost ^
mongoexport --db mydb --collection mycollection | ^
mongoimport --db mydb --collection mycollection

If you have setup the key based authentication for SSH and are using pageant to manage the key locally, this does not prompt for password. -load null forces plink to ignore the hostname defined in in your default PuTTY session profile.

[1] dirty because mongoexport does not transfer all datatypes, in order to really transfer everything you should create the export with mongodump.

Posted in MongoDB |

D-Link routers and ZoneEdit dynamic dns

Some D-Link models support updating selected dynamic dns services. The support is however limited and it seems to be that for example you can’t configure it to push updates to ZoneEdit.

A simple workaround is to use the D-Link provided free dynamic dns service, https://www.dlinkddns.com for the dynamic part and the ZoneEdit for static stuff. Simply register a dynamic name at D-Link, configure it to your router and then create a cname record on ZoneEdit on that points to your <something>.dlinkddns.com address.

Posted in Misc |

Install latest MongoDB in Ubuntu

The MongoDB version available from the default Ubuntu/Debian repositories can be fairly old. Luckily it is easy to get a newer version directly from 10gen repository

# If necessary, remove previous installation
# sudo apt-get remove mongodb
# Add appropriate key
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
# Add Repository to sources.list
echo deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen >> /etc/apt/sources.list
# Since we modifed repositofy list we need to run update
sudo apt-get update
# Fetch and install mongodb
sudo apt-get install mongodb-10gen

Source: http://www.mongodb.org/display/DOCS/Ubuntu+and+Debian+packages

Posted in Misc |

Apache reverse proxy on Ubuntu

Install Apache and Enable relevant Apache proxy modules

sudo apt-get install apache2
sudo a2enmod proxy proxy_http

Create new configuration file for the virtual host in /etc/apache2/sites-available.

<VirtualHost *:80>
        ServerAdmin admin@example.org
        ServerAlias www.example.org
        ServerName example.org

        ErrorLog /var/log/apache2/example-error.log

        LogLevel info
        CustomLog /var/log/apache2/example-access.log combined
        
        ProxyPreserveHost On
        ProxyRequests off
        # Allow from everywhere
        <Proxy *>
                Order deny,allow
                Allow from all
        </Proxy>
       # Send all requests to port 1122 
        ProxyPass / http://localhost:1122/
        ProxyPassReverse / http://localhost:1122/
</VirtualHost>

Link the created configuration file to sites-enabled. The numbers in target name are there to guarantee the files get loaded in certain order (Apache scans the sites-enabled folder and loads all configuration files).

ln -s /etc/apache2/sites-available/example \
    /etc/apache2/sites-enabled/001-example

Restart Apache (since we changed modules, otherwise reload would be enough)

sudo service apache2 restart
Posted in Web development |