Thursday, November 20, 2008

RESTlet with Portlet in Liferay

Liferay allows authentication plugins in order to flexibly accommodate implementations that need more than what comes out-of-the-box. Incidentally, a portlet we have been working on has some rules governing its render state. There are situations where it shouldn't be rendered due to a blacklisting strategy in our requirements. Unfortunately, Liferay's permissions are strictly based on whitelists.

So, how to not render the portlet for a group of people and do render it for others using a whitelist? Logic for who should see it is purely internal to the portlet and it made sense (in simplicity and respecting separation of concerns) to expose a RESTlet for consumption of our authentication module. The authentication module in turn puts people in the proper groups for various community and portlet access. Setup was incredibly simple and it didn't take much more effort to enable JPA transaction support with this class:



/**
* Enables JPA transactional support for subclassed Restlets.
*/
public abstract class AbstractJpaRestlet extends Restlet {
private static final Logger logger = Logger.getLogger(AbstractJpaRestlet.class);

@Autowired
private EntityManagerFactory emf;

/**
* Handler for Restlet requests and responses. Implementing this method
* will ensure db connectivity and transactions support with the DAOs
*
* @param req incoming Request
* @param resp outgoing Response
*/
public abstract void doHandle(Request req, Response resp);


@Override
public void handle(Request request, Response response) {
EntityManager em = emf.createEntityManager();
TransactionSynchronizationManager.bindResource(emf, new EntityManagerHolder(em));

try {

doHandle(request, response);
} catch (Throwable t) {

logger.error(this, t);
TransactionSynchronizationManager.unbindResource(emf);

throw new RestletException(t.getMessage());
}
finally{
TransactionSynchronizationManager.unbindResource(emf);
}
}
}

This is my first RESTlet and I'd be interested in any feedback or pointers in this approach. I'm quite happy with how fast it was to code up. There's only one implementation of this class at this point, but the pattern is very simple and allows for quick future expansion as we need to expose more data.

Wednesday, November 19, 2008

Revisited: Spring InvervalJobs and scheduling in Liferay 5

Update to a better way to go about Spring scheduling: I tried simply defining a destroy method on the scheduler bean instead of relying on the extended ContextLoaderListener. This never appeared to be invoked and subsequently didn't unschedule the jobs. This is quite problematic in the long-run. Not good to have duplicate jobs running at the same time, it's sloppy and bad things could happen. So simply having the destroy method was insufficient.

Instead of extending Liferay's JobScheduler (as I suggested in the previous post), it made more sense to create a singleton POJO that then invokes com.liferay.portal.kernel.job.JobSchedulerUtil.getJobScheduler().schedule(). That way the dependency is relying on Liferay's Quartz integration altogether (not thinking any of this would ever live outside of Liferay, but in the event that it does should be able to propagate that dependency with minor adjustments). Everything said and done (and tested), this is the scheduler:


public class JobScheduler {
private static Log _log = LogFactory.getLog(JobScheduler.class);
public Set jobs = new HashSet();

/**
* Set all of the scheduled jobs.
*
* @param jobs Set of jobs to schedule.
*/
public void setJobs(Set jobs) {
this.jobs = jobs;
}

public void init(){
com.liferay.portal.kernel.job.JobScheduler scheduler = JobSchedulerUtil.getJobScheduler();

for(IntervalJob job : jobs){
try {
_log.info("Initializating " + job);
scheduler.schedule(job);
}
catch (Exception e) {
_log.error("Initialization error instantiating " +job);
_log.error(e);
}
}
}

public void destroy() {
com.liferay.portal.kernel.job.JobScheduler scheduler = JobSchedulerUtil.getJobScheduler();

for(IntervalJob job : jobs){
try {
_log.info("Unscheduling " + job);
scheduler.unschedule(job);
}
catch (Exception e) {
_log.error("Unscheduling error with" +job);
_log.error(e);
}
}
}
}

Spring InvervalJobs and scheduling in Liferay 5

If you have a Liferay portlet that requires some scheduling you can easily use Liferay's built-in Scheduler to add an IntervalJob to the job list, like this. However, what if your IntervalJob is a Spring bean and has dependencies on other Spring beans in the portlet? Unfortunately, at the time of this writing (Liferay 5.1.2), the hot deploy code invokes the Scheduler configuration and execution before the context is initialized--which means you're up a creek when Spring is setup to be initialized with the context (happens to be my case).

An alternative approach is to extend com.liferay.portal.job.JobSchedulerImpl with a Spring singleton and configure the jobs via Spring. While this is very flexible, the singleton is now operating outside the Liferay Quartz realm and therefore will not be subject to the lifecycle of the portlet. That is to say, when you redeploy the portlet the jobs stay scheduled. A more annoying aspect to this is that if you try to shutdown Liferay it appears to hang. Sure the log says that Coyote is stopped, but that's not the case and the process appears to be waiting on a thread. This in turn requires manually killing every time. During development this is such a pain. My guess, without significant research into the bowels of the Liferay Quartz integration, is that the Spring singleton hasn't been properly disposed of.

One solution to this situation is to extend org.springframework.web.context.ContextLoaderListener with something like this:


public class SpringSchedulerContextLoaderListener extends org.springframework.web.context.ContextLoaderListener{
private static final Logger logger = Logger.getLogger(SpringSchedulerContextLoaderListener.class);
public void contextInitialized(ServletContextEvent event) {
super.contextInitialized(event);
}

public void contextDestroyed(ServletContextEvent event) {

JobScheduler j = (JobScheduler) StaticApplicationContextHolder.getApplicationContext().getBean("jobScheduler");
j.shutdown();

super.contextDestroyed(event);
}
}



This will ensure that the Spring singleton JobScheduler will unschedule the registered IntervalJobs when the context is destroyed. You're good to go once this entry replaces org.springframework.web.context.ContextLoaderListener in web.xml.

There may be a more efficient way to do this, but for now this works.