Liferay Freemarker Spring MVC portlets
Liferay Freemarker Spring MVC portlets are great, and certainly are my favourite way of developing portlets in Liferay at the moment, when creating non-webapp portlets. For business applications created as webapps Vaadin is most certainly the way to go, and for non-business applications created as webapps, my personal favourite at the moment is AngularJS which plays very nicely with Liferay. I'll get back to Vaadin and AngularJS in later posts.
Now creating Spring MVC portlets in Liferay is common, and fairly well documented all over the place on the net. It does involve a manual process where you start out with a Liferay MVC portlet, and change it to a Spring MVC portlet. I'm hoping Liferay soon will realize the value of adding Spring MVC portlets to their SDK, so we avoid having to do this manually. Intellij support would be nice as well, but their new Maven support in 6.2 goes a long way towards mitigating the problem, though it still is a bit flaky in the current version of the new Liferay IDE I'm using.
Spring MVC portlets are great, but you still have to deal with JSP. For the real killer combination, you need to add Freemarker to the mix. Unfortunately, though using Freemarker in Spring MVC is very straight forward, using it in a Liferay portlet is not. The issue manifests itself as a nullpointer exception in FreeMarkerView.getTemplate(FreeMarkerView.java) This "bug" has existed in all versions of Liferay I have worked with, and has yet to be fixed in Liferay 6.2. Hopefully as Freemarker becomes more and more popular, Liferay will finally decide to fix this issue. Luckily there is a workaround which I will describe here. This works in Liferay 6.1.1+ and Liferay 6.2. For earlier versions a different workaround exists, but I will not describe it here, as you really should have moved on from Liferay 6.0 and 6.1.0 by now.
The first step of the workaround is to create the following Java class:
package is.brendan.liferay.web.freemarker;
import java.lang.reflect.Method;
import javax.portlet.PortletContext;
import javax.servlet.ServletContext;
import org.springframework.web.portlet.context.PortletContextAware;
import org.springframework.web.servlet.view.AbstractUrlBasedView;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerView;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
public class ExtendedFreemarkerViewResolver extends FreeMarkerViewResolver implements PortletContextAware {
private FreeMarkerConfigurer freemarkerConfigurer;
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
FreeMarkerView freemarkerView = (FreeMarkerView)super.buildView(viewName);
freemarkerView.setConfiguration(freemarkerConfigurer.getConfiguration());
freemarkerView.setServletContext(getServletContext());
return freemarkerView;
}
public void setFreemarkerConfigurer(FreeMarkerConfigurer freemarkerConfigurer) {
this.freemarkerConfigurer = freemarkerConfigurer;
}
@Override
public void setPortletContext(PortletContext context) {
try {
Method meth = context.getClass().getDeclaredMethod("getServletContext");
setServletContext((ServletContext)meth.invoke(context));
} catch (Exception e) {
throw new UnsupportedOperationException("Could not get servletcontext", e);
}
}
}
Next, in your applicationcontext do the following:
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
</bean>
<bean id="viewResolver" class="is.brendan.liferay.web.freemarker.ExtendedFreemarkerViewResolver">
<property name="cache" value="true"/>
<property name="prefix" value=""/>
<property name="suffix" value=".ftl"/>
<property name="exposeSpringMacroHelpers" value="true"/>
<property name="freemarkerConfigurer" ref="freemarkerConfig"/>
</bean>
Now you are good to go. Drop your Freemarker templates in /WEB-INF/freemarker/
and use them just like you would in a regular Spring MVC portlet.
And if you want to get really fancy, you can add the ability to load Freemarker templates from outside of your portlet project for added power. This allows you to allow your portlet to use Freemarker templates uploaded through the Liferay Sync application. Check out this proof of concept. Though this is a rather neat feature, if you already have moved to Liferay 6.2 you are probably going to want to use the new Liferay Application Display Template feature since it's fully supported by Liferay, and not a hack like the proof of concept mentioned above.