brush clojure

Sunday, April 27, 2008

Portlet development with Eclipse and Pluto

I had to set up a portlet development environment with Eclipse. The portlet is being built with Spring Portlet MVC 2.5.2 and will eventually run under Websphere, but Eclipse WTP and Websphere don't go together well. So I thought I'll try to run my portlet from Eclipse/WTP in a portlet container running under Tomcat. Pluto is such a container and does the job. It has its own peculiarities, it needs to generate its own web.xml using your web.xml and portlet.xml as input. Pluto provides an ant task to do this.

A good integration with eclipse and Maven should take care of the following issues:
- dependency management: download the needed Pluto ant task jar with all its dependencies.
- automatically generate web.xml for Pluto each time when needed - in Eclipse and during a Maven build.

The steps I took to get all nicely running:
1. Install Tomcat/Pluto bundle and configre it as a server in eclipse.
2. Tell Maven to save together all dependencies needed for the Pluto Ant Task.
3. Define an Ant build file with the Pluto task.
4. Add the execution of the Ant task in the Maven build.
5. Generate eclipse project, add the portlet application to the pluto server, run the server end configure a page with the portlet. It is fun to have more portlet instances on the same page.
6. Fix issues that prevent the portlet from running.
7. Define an Eclipse Custom Builder to update the web.xml automatically.

Once this is done, start Tomcat/Pluto from eclipse, log in as admin and add your portlet to a Pluto portal page. After that, just start the server and go to your portal page.

The details of how to perform the above steps:

Install Tomcat/Pluto bundle and configre it as a server in eclipse

I installed pluto 1.1.6 based on tomcat 5.5 - pluto-current-bundle.zip from http://archive.apache.org/dist/portals/pluto/

In the Eclipse Servers tab, define a new Apache Tomcat 5.5 server. IMPORTANT: In the server configuration page, select 'Use Tomcat installation (takes control of tomcat installation)' under Server locations. I don't understande why, but otherwise pluto doesn't see the portlet.


Tell Maven to save together all dependencies needed for the Pluto Ant Task.


First of all, the org.apache.pluto:pluto-ant-tasks dependency must be present in your (root) pom.xml. Then, add the following, preferably under a new profile pluto:


<profile>
<id>pluto</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>target/libs-for-pluto</outputDirectory>
<includeArtifactIds>
pluto-ant-tasks,pluto-util,pluto-descriptor-api,pluto-descriptor-impl,commons-logging,castor,xercesImpl
</includeArtifactIds>
</configuration>
</execution>
. . .


This way, all dependencies needed for the Pluto ant task will be copied in target/libs-for-pluto folder where they can be used by the ant task.

Define an Ant build file with the Pluto task

In a new file, e.g. generate-pluto-web-xml.xml, define the task which generates the web.xml file needed by pluto in the web-inf folder. A place is needed for the original web.xml that will be procerssd by the task, I've placed it in src/main/config.

The contents of update-web-xml.xml:

<project name="pluto-update-web-xml" basedir="." default="generate-web-xml">
<path id="pluto.task.classes" >
<fileset dir="target/libs-for-pluto" includes="**/*" />
</path>

<typedef name="passemble"
classname="org.apache.pluto.ant.AssembleTask" classpathref="pluto.task.classes" />

<target name="generate-web-xml">
<echo>Generating Pluto web.xml</echo>
<passemble webxml="src/main/config/web.xml"
portletxml="src/main/webapp/web-inf/portlet.xml"
destfile="src/main/webapp/web-inf/web.xml" />
</target>

</project>

At this point, you should be able to run this ant task from the command line. It is better to do this from maven, see next step.

Add the execution of the Ant task in the Maven build.
In pom.xml, add the following under the profile pluto:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>
<configuration>
<tasks>
<ant antfile="${basedir}/generate-pluto-web-xml.xml">
<target name="generate-web-xml" />
</ant>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>


At this point, running manually
mvn eclipse:eclipse -P pluto
will produce a web.xml as needed for pluto. The original web.xml stays in src/main/config. Needless to say, you must have a good portlet.xml in src/main/webapp/WEB-INF. Don't forget to make it a Web project.

Configure your portlet in Pluto
That is pretty straightforward - go to the Admin tab in pulto, and add your portlet to a new or existing page. The portlets in the application you deployed on pluto should be visible in the Portlet Application list box on the admin page.
Fix issues

When I first started the page with the portlet, I got the following stack trace:

java.lang.ClassCastException: org.springframework.web.servlet.support.JstlUtils$SpringLocalizationContext cannot be cast to java.lang.String
org.apache.taglibs.standard.tag.common.fmt.BundleSupport.getLocalizationContext(BundleSupport.java:134)
org.apache.taglibs.standard.tag.common.fmt.SetLocaleSupport.getFormattingLocale(SetLocaleSupport.java:251)
org.apache.taglibs.standard.tag.common.fmt.FormatDateSupport.doEndTag(FormatDateSupport.java:116)
org.apache.jsp.WEB_002dINF.themes.pluto_002ddefault_002dtheme_jsp._jspx_meth_fmt_formatDate_0(pluto_002ddefault_002dtheme_jsp.java:602)


Whatever the reason, I just removed the tag causing the error: a tag in pluto-default-theme.jsp and voila, it worked.

Define an Eclipse Custom Builder to update the web.xml automatically

You can define a custom bulder to update your web.xml in src/main/webapp/WEB-INF automatically each time you build the project or change web.xml in src/main/config. That's not really necessary, unless you are going to change web.xml often.

Alternatively, run mvn generate-resources manually each time you change web.xml.

To define a custom builder:

- In Eclipse, go to Project properties, Builders. New, Ant Builder.
In the Main tab, select the ant file and the working folder.


In the Refresh tab, select Specific Resources an click on Specify Resources. Select src/main/webapp/WEB-INF/web.xml. After each build, eclipse will refresh this resource.

In the Build Options tab, check 'Specify working set of relevant resources'. Click on Specify resources and select main/webapp/config/web.xml.

Now each time you change this file you see the ant task output in the console and the file is updated.

Tuesday, April 15, 2008

Open Session In View with JSF and Spring

I expected that a quick google session would immediately deliver the standard solution for this situation, but it didn't go this way.

The problem: I want Open Session in View for an JSF/Spring application. Ok, I know there is something fishy about open session in view, but believe me, for this application this is just fine. There is a chance that the application will become a portlet eventually, so I didn't want to have to handle portlet and servlet filters issues too. Instead, I want to use the convenient hooks provided by JSF to open and close the hibernate session - phase listeners. Happily, the Spring-provided OpenSessionInViewFilter reveals the technicalities of how Spring deals with the hibernate session factory.

Here is the result:

public class HibernateRestoreViewPhaseListener implements PhaseListener {

public void afterPhase(PhaseEvent event) {
}

protected SessionFactory lookupSessionFactory() {

FacesContext context = FacesContext.getCurrentInstance();
ServletContext servletContext = (ServletContext) context
.getExternalContext().getContext();
WebApplicationContext wac = WebApplicationContextUtils
.getWebApplicationContext(servletContext);
return (SessionFactory) wac.getBean("hibernate-session-factory",
SessionFactory.class);
}

public void beforePhase(PhaseEvent event) {
SessionFactory sessionFactory = lookupSessionFactory();
if (!TransactionSynchronizationManager.hasResource(sessionFactory)) {
Session session = getSession(sessionFactory);
TransactionSynchronizationManager.bindResource(sessionFactory,
new SessionHolder(session));
}
}

public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}

protected Session getSession(SessionFactory sessionFactory)
throws DataAccessResourceFailureException {
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
session.setFlushMode(FlushMode.MANUAL);
return session;
}
}

The session gets closed when Render Response phase is finished:


public class HibernateRenderResponsePhaseListener implements PhaseListener {

public void afterPhase(PhaseEvent event) {
SessionFactory sessionFactory = lookupSessionFactory();

SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
.unbindResource(sessionFactory);
closeSession(sessionHolder.getSession(), sessionFactory);
}
. . .



Don't forget to register the listeners in faces-config.xml

<lifecycle>
<phase-listener>
...HibernateRestoreViewPhaseListener
</phase-listener>
<phase-listener>
...HibernateRenderResponsePhaseListener
</phase-listener>
</lifecycle>



I have the feeling this is not the final solution and this post will be edited soon.