/* vim: set ts=2 et sw=2 cindent fo=qroca: */ package com.globant.katari.trails; import java.io.IOException; import java.util.Locale; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.commons.lang.Validate; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hivemind.ErrorHandler; import org.apache.hivemind.Registry; import org.apache.hivemind.impl.RegistryBuilder; import org.apache.hivemind.impl.XmlModuleDescriptorProvider; import org.apache.hivemind.service.ThreadLocale; import org.apache.tapestry.ApplicationServlet; import org.hibernate.FlushMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.orm.hibernate3.SessionFactoryUtils; import org.springframework.orm.hibernate3.SessionHolder; import org.springframework.transaction.support .TransactionSynchronizationManager; import org.springframework.web.context.WebApplicationContext; import com.globant.katari.core.web.ServletConfigWrapper; /** Servlet used by trails modules. * * It extends ApplicationServlet and redefines the constructRegistry method in * order to look for extra locations of the hivemodule.xml file.<br> * * It also wraps the ServletContext to create a scoped application context, * adding beans that are private to this module without affecting the * application context.<br> * * Finally, it implements the open session in view for trails. * * To configure where to find the hivemodule file, you have to define an init * parameter with name "hivemindModulePath" with the location of your hivemind * module configuration.<br> * * You also have to define an init parameter with the name * "trailsApplicationContextLocation" that points to the location of the * configuration of the bean factory for this module.<br> */ public class TrailsModuleApplicationServlet extends ApplicationServlet { /** * Serial uid. */ private static final long serialVersionUID = 1L; /** * Name of the init parameter used to indicate where the hivemodule is. */ private static final String HIVEMIND_MODULE_PATH = "hivemindModulePath"; /** * Name for the init parameter that specifies the location of the xml file * which configures the context for this module, typically named * module-beans.xml. */ private static final String CONTEXT_LOCATION = "trailsApplicationContextLocation"; /** The class logger. */ private static Log log = LogFactory.getLog( TrailsModuleApplicationServlet.class); /** The spring bean factory used in trails. * * This is initialized when this servlet is initialized, after that it is not * null. */ private BeanFactory beanFactory = null; /** This is used to share the Registry through all the application. * * TODO this makes it impossible to use several instances of trails. */ private static Registry tapestryRegistry = null; /** Processes the request. * * This method implements the open session in view, opening a new hibernate * session when the request starts and closing the session on end. * * {@inheritDoc} */ public void service(final ServletRequest request, final ServletResponse response) throws ServletException, IOException { SessionFactory sessionFactory; sessionFactory = (SessionFactory) beanFactory.getBean("sessionFactory"); Session session = null; // True if there was a previous hibernate session active. In that case, we // just leave it alone, efectively 'participating' in the existing session. boolean participate = false; // True if this is the first time we call this servlet in the current // request. boolean firstCall = false; // The name of the attribute to check if the request has already been // handled by this servlet. We open the hibernate session the first time // only. String alreadyHandled = getServletName() + ".SESSION_OPENED"; // Check if we have already entered this servlet in the current request. if (null == request.getAttribute(alreadyHandled)) { request.setAttribute(alreadyHandled, "filtered"); if (TransactionSynchronizationManager.hasResource(sessionFactory)) { // Do not modify the Session: just set the participate flag. participate = true; } else { log.debug("Opening single Hibernate Session in open session in view"); session = getSession(sessionFactory); TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); } firstCall = true; } /* This is due to a bug in tapestry 4.1.5: when rendenig the exception * page, tapestry iterates recursively over all request attributes. Katari * puts for convenience, an atribute named 'request' that contains the * servlet request. This makes tapestry iterate infinitely through the * attributes, causing a stack trace exception. * * The bug is in tapestry HttpServletRequestStrategy.describeObject. */ Object savedRequest = request.getAttribute("request"); request.removeAttribute("request"); try { super.service(request, response); } finally { request.setAttribute("request", savedRequest); if (firstCall) { if (!participate) { TransactionSynchronizationManager.unbindResource(sessionFactory); log.debug( "Closing single Hibernate session in open session in view"); closeSession(session, sessionFactory); } request.removeAttribute(alreadyHandled); } } } /** Get a Session for the SessionFactory that this servlet uses. * * Note that this just applies in single session mode! <p>The default * implementation delegates to SessionFactoryUtils' getSession method and * sets the Session's flushMode to NEVER. <p>Can be overridden in subclasses * for creating a Session with a custom entity interceptor or JDBC exception * translator. * * @param sessionFactory the SessionFactory that this filter uses. * * @return the Session to use. */ protected Session getSession(final SessionFactory sessionFactory) { Session session = SessionFactoryUtils.getSession(sessionFactory, true); session.setFlushMode(FlushMode.MANUAL); return session; } /** Close the given Session. * * Note that this just applies in single session mode! * * <p>The default implementation delegates to SessionFactoryUtils' * closeSessionIfNecessary method. * * <p>Can be overridden in subclasses, e.g. for flushing the Session before * closing it. See class-level javadoc for a discussion of flush handling. * Note that you should also override getSession accordingly, to set the * flush mode to something else than NEVER. * * @param session the Session used for filtering * * @param sessionFactory the SessionFactory that this filter uses */ protected void closeSession(final Session session, final SessionFactory sessionFactory) { SessionFactoryUtils.closeSession(session); } /** * {@inheritDoc} */ @Override protected Registry constructRegistry(final ServletConfig config) { String hivemodulePath = getInitParameter(HIVEMIND_MODULE_PATH); synchronized (TrailsModuleApplicationServlet.class) { if (hivemodulePath == null) { setRegistry(super.constructRegistry(config)); return super.constructRegistry(config); } else { ErrorHandler errorHandler = constructErrorHandler(config); RegistryBuilder builder = new RegistryBuilder(errorHandler); builder.addModuleDescriptorProvider(new XmlModuleDescriptorProvider( createClassResolver())); ServletContext context = config.getServletContext(); addModuleIfExists(builder, context, hivemodulePath); setRegistry(builder.constructRegistry(Locale.getDefault())); } } return tapestryRegistry; } /** * {@inheritDoc} */ @Override public void init(final ServletConfig config) throws ServletException { beanFactory = createBeanFactory(config); TrailsModuleServletContext newContext = new TrailsModuleServletContext( beanFactory, config.getServletContext()); ServletConfig newConfig = new ServletConfigWrapper(config, newContext); super.init(newConfig); } /** * Creates a BeanFactory child of the application context factory. * * The BeanFactory is created from the file specified in the servlet init * parameter named "trailsApplicationContextLocation". * * @param config The servlet config. * * @return the bean factory loaded from the configured location. */ private BeanFactory createBeanFactory(final ServletConfig config) { String springConfigurationFile; springConfigurationFile = config.getInitParameter(CONTEXT_LOCATION); Validate.notNull(springConfigurationFile, "You should define '" + CONTEXT_LOCATION + "' init parameter for this servlet to work."); ApplicationContext parentContext = (ApplicationContext) config .getServletContext().getAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); Validate.notNull(parentContext, "There should be one Web Application Context for this servlet to work."); return new ClassPathXmlApplicationContext( new String[] {springConfigurationFile}, parentContext); } public static Locale getCurrentLocale() { return ((ThreadLocale) tapestryRegistry.getService("hivemind.ThreadLocale", ThreadLocale.class)).getLocale(); } /** Sets the class level registry, defined only to make it clear that we are * setting the registry from a non static method. * * @param registry The new registry, it cannot be null. */ private static void setRegistry(final Registry registry) { Validate.notNull(registry, "The registry cannot be null."); tapestryRegistry = registry; } }