Java tutorial
/** * Licensed to Jasig under one or more contributor license * agreements. See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Jasig licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a * copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jasig.springframework.web.portlet.context; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import javax.portlet.PortletContext; import javax.servlet.ServletContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.access.BeanFactoryReference; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.GenericTypeResolver; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.portlet.context.ConfigurablePortletApplicationContext; import org.springframework.web.portlet.context.PortletApplicationContextUtils; import org.springframework.web.portlet.context.XmlPortletApplicationContext; /** * Performs the actual initialization work for the root application context. * Called by {@link PortletContextLoaderListener}. * * <p>Looks for a {@link #CONTEXT_CLASS_PARAM "portletContextClass"} context-param * at the <code>web.xml</code> level to specify the context * class type, falling back to the default of * {@link ContribXmlPortletApplicationContext} * if not found. With the default PortletContextLoader implementation, any context class * specified needs to implement the ConfigurablePortletApplicationContext interface. * * <p>Processes a {@link #CONFIG_LOCATION_PARAM "portletContextConfigLocation"} * context-param at the <code>web.xml</code> level and passes its value to the context instance, parsing it into * potentially multiple file paths which can be separated by any number of * commas and spaces, e.g. "WEB-INF/portletApplicationContext1.xml, * WEB-INF/portletApplicationContext2.xml". Ant-style path patterns are supported as well, * e.g. "WEB-INF/portlet*Context.xml,WEB-INF/springPortlet*.xml" or "WEB-INF/**/portlet*Context.xml". * If not explicitly specified, the context implementation is supposed to use a * default location (with {@link ContribXmlPortletApplicationContext}: "/WEB-INF/portletApplicationContext.xml"). * * <p>Note: In case of multiple config locations, later bean definitions will * override ones defined in previously loaded files, at least when using one of * Spring's default ApplicationContext implementations. This can be leveraged * to deliberately override certain bean definitions via an extra XML file. * * <p>Above and beyond loading the root portlet application context, this class * can optionally load or obtain and hook up a shared parent context to * the root application context. See the * {@link #loadParentContext(PortletContext)} method for more information. * * @author Juergen Hoeller * @author Colin Sampaleanu * @author Sam Brannen * @author Eric Dalquist * @see PortletContextLoaderListener * @see PortletApplicationContext * @see XmlPortletApplicationContext */ public class PortletContextLoader { /** * Config param for the root {@link PortletApplicationContext} implementation class to use: {@value} * @see #determineContextClass(PortletContext) * @see #createWebApplicationContext(PortletContext, ApplicationContext) */ public static final String CONTEXT_CLASS_PARAM = "portletContextClass"; /** * Config param for the root PortletApplicationContext id, * to be used as serialization id for the underlying BeanFactory: {@value} */ public static final String CONTEXT_ID_PARAM = "portletContextId"; /** * Config param for which {@link ApplicationContextInitializer} classes to use * for initializing the web application context: {@value} * @see #customizeContext(PortletContext, ConfigurablePortletApplicationContext) */ public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "portletContextInitializerClasses"; /** * Name of portlet context parameter (i.e., {@value}) that can specify the * config location for the root context, falling back to the implementation's * default otherwise. * @see org.springframework.web.portlet.context.XmlPortletApplicationContext#DEFAULT_CONFIG_LOCATION */ public static final String CONFIG_LOCATION_PARAM = "portletContextConfigLocation"; /** * Name of the class path resource (relative to the PortletContextLoader class) * that defines PortletContextLoader's default strategy names. */ private static final String DEFAULT_STRATEGIES_PATH = "PortletContextLoader.properties"; private static final Properties defaultStrategies; static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, PortletContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load 'PortletContextLoader.properties': " + ex.getMessage()); } } /** * Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext. */ private static final Map<ClassLoader, PortletApplicationContext> currentContextPerThread = new ConcurrentHashMap<ClassLoader, PortletApplicationContext>( 1); /** * The 'current' WebApplicationContext, if the PortletContextLoader class is * deployed in the web app ClassLoader itself. */ private static volatile PortletApplicationContext currentContext; /** * The {@link ServletContext} the portlet application exists in */ private final ServletContext servletContext; /** * The root PortletApplicationContext instance that this loader manages. */ private PortletApplicationContext context; /** * Holds BeanFactoryReference when loading parent factory via * ContextSingletonBeanFactoryLocator. */ private BeanFactoryReference parentContextRef; public PortletContextLoader(ServletContext servletContext) { this.servletContext = servletContext; } /** * Initialize Spring's portlet application context for the given portlet context, * using the application context provided at construction time, or creating a new one * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params. * @param portletContext current portlet context * @return the new PortletApplicationContext * @see #CONTEXT_CLASS_PARAM * @see #CONFIG_LOCATION_PARAM */ public PortletApplicationContext initWebApplicationContext(PortletContext portletContext) { if (portletContext .getAttribute(PortletApplicationContext.ROOT_PORTLET_APPLICATION_CONTEXT_ATTRIBUTE) != null) { throw new IllegalStateException( "Cannot initialize context because there is already a root portlet application context present - " + "check whether you have multiple PortletContextLoader* definitions in your portlet.xml!"); } Log logger = LogFactory.getLog(PortletContextLoader.class); portletContext.log("Initializing Spring root PortletApplicationContext"); if (logger.isInfoEnabled()) { logger.info("Root portlet PortletApplicationContext: initialization started"); } long startTime = System.nanoTime(); try { // Store context in local instance variable, to guarantee that // it is available on PortletContext shutdown. if (this.context == null) { this.context = createPortletApplicationContext(portletContext); } if (this.context instanceof ConfigurablePortletApplicationContext) { configureAndRefreshPortletApplicationContext((ConfigurablePortletApplicationContext) this.context, portletContext); } portletContext.setAttribute(PortletApplicationContext.ROOT_PORTLET_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == PortletContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } if (logger.isDebugEnabled()) { logger.debug("Published root PortletApplicationContext as PortletContext attribute with name [" + PortletApplicationContext.ROOT_PORTLET_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } if (logger.isInfoEnabled()) { long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); logger.info("Root PortletApplicationContext: initialization completed in " + elapsedTime + " ms"); } return this.context; } catch (RuntimeException ex) { logger.error("Portlet context initialization failed", ex); portletContext.setAttribute(PortletApplicationContext.ROOT_PORTLET_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } catch (Error err) { logger.error("Portlet context initialization failed", err); portletContext.setAttribute(PortletApplicationContext.ROOT_PORTLET_APPLICATION_CONTEXT_ATTRIBUTE, err); throw err; } } /** * Instantiate the root PortletApplicationContext for this loader, either the * default context class or a custom context class if specified. * <p>This implementation expects custom contexts to implement the * {@link ConfigurablePortletApplicationContext} interface. * Can be overridden in subclasses. * <p>In addition, {@link #customizeContext} gets called prior to refreshing the * context, allowing subclasses to perform custom modifications to the context. * @param sc current portlet context * @return the root WebApplicationContext * @see ConfigurablePortletApplicationContext */ protected PortletApplicationContext createPortletApplicationContext(PortletContext sc) { Class<?> contextClass = determineContextClass(sc); if (!PortletApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + PortletApplicationContext.class.getName() + "]"); } PortletApplicationContext wac = (PortletApplicationContext) BeanUtils.instantiateClass(contextClass); return wac; } protected void configureAndRefreshPortletApplicationContext(ConfigurablePortletApplicationContext pac, PortletContext pc) { if (ObjectUtils.identityToString(pac).equals(pac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available information String idParam = pc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { pac.setId(idParam); } else { // Generate default id... pac.setId(ConfigurablePortletApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(pc.getPortletContextName())); } } // Determine parent for root web application context, if any. ApplicationContext parent = loadParentContext(pc); pac.setParent(parent); pac.setPortletContext(pc); String initParameter = pc.getInitParameter(CONFIG_LOCATION_PARAM); if (initParameter != null) { pac.setConfigLocation(initParameter); } else { try { pac.setConfigLocation("/WEB-INF/portletApplicationContext.xml"); } catch (UnsupportedOperationException e) { //Ignore, may get triggered if the context doesn't support config locations } } customizeContext(pc, pac); pac.refresh(); } /** * Return the PortletApplicationContext implementation class to use, either the * default XmlPortletApplicationContext or a custom context class if specified. * @param portletContext current portlet context * @return the WebApplicationContext implementation class to use * @see #CONTEXT_CLASS_PARAM * @see org.springframework.web.portlet.context.XmlPortletApplicationContext */ protected Class<?> determineContextClass(PortletContext portletContext) { String contextClassName = portletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load custom context class [" + contextClassName + "]", ex); } } else { contextClassName = defaultStrategies.getProperty(PortletApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName, PortletContextLoader.class.getClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load default context class [" + contextClassName + "]", ex); } } } /** * Return the {@link ApplicationContextInitializer} implementation classes to use * if any have been specified by {@link #CONTEXT_INITIALIZER_CLASSES_PARAM}. * @param portletContext current portlet context * @see #CONTEXT_INITIALIZER_CLASSES_PARAM */ @SuppressWarnings("unchecked") protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> determineContextInitializerClasses( PortletContext portletContext) { String classNames = portletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM); List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes = new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>(); if (classNames != null) { for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) { try { Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader()); Assert.isAssignable(ApplicationContextInitializer.class, clazz, "class [" + className + "] must implement ApplicationContextInitializer"); classes.add((Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load context initializer class [" + className + "]", ex); } } } return classes; } /** * Customize the {@link ConfigurablePortletApplicationContext} created by this * PortletContextLoader after config locations have been supplied to the context * but before the context is <em>refreshed</em>. * <p>The default implementation {@linkplain #determineContextInitializerClasses(PortletContext) * determines} what (if any) context initializer classes have been specified through * {@linkplain #CONTEXT_INITIALIZER_CLASSES_PARAM context init parameters} and * {@linkplain ApplicationContextInitializer#initialize invokes each} with the * given web application context. * <p>Any {@code ApplicationContextInitializers} implementing * {@link org.springframework.core.Ordered Ordered} or marked with @{@link * org.springframework.core.annotation.Order Order} will be sorted appropriately. * @param portletContext the current portlet context * @param applicationContext the newly created application context * @see #createPortletApplicationContext(PortletContext) * @see #CONTEXT_INITIALIZER_CLASSES_PARAM * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext) */ protected void customizeContext(PortletContext portletContext, ConfigurablePortletApplicationContext applicationContext) { List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses( portletContext); if (initializerClasses.size() == 0) { // no ApplicationContextInitializers have been declared -> nothing to do return; } ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances = new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>(); for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) { Class<?> contextClass = applicationContext.getClass(); Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); Assert.isAssignable(initializerContextClass, contextClass, String.format( "Could not add context initializer [%s] as its generic parameter [%s] " + "is not assignable from the type of application context used by this " + "context loader [%s]", initializerClass.getName(), initializerContextClass, contextClass)); initializerInstances.add(BeanUtils.instantiateClass(initializerClass)); } //TODO remove cast when ContribXmlPortletApplicationContext is merged into super classes ((ConfigurablePortletEnvironment) applicationContext.getEnvironment()) .initPropertySources(this.servletContext, portletContext, null); Collections.sort(initializerInstances, new AnnotationAwareOrderComparator()); for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : initializerInstances) { initializer.initialize(applicationContext); } } /** * Template method with default implementation (which may be overridden by a * subclass), to load or obtain an ApplicationContext instance which will be * used as the parent context of the root PortletApplicationContext. If the * return value from the method is null, no parent context is set. * <p>The default implementation uses * {@link PortletApplicationContextUtils#getWebApplicationContext(PortletContext)} * to load a parent context. * @param portletContext current portlet context * @return the parent application context, or <code>null</code> if none */ protected ApplicationContext loadParentContext(PortletContext portletContext) { return PortletApplicationContextUtils.getWebApplicationContext(portletContext); } /** * Close Spring's web application context for the given portlet context. If * the default {@link #loadParentContext(PortletContext)} implementation, * which uses ContextSingletonBeanFactoryLocator, has loaded any shared * parent context, release one reference to that shared parent context. * <p>If overriding {@link #loadParentContext(PortletContext)}, you may have * to override this method as well. * @param servletContext the PortletContext that the WebApplicationContext runs in */ public void closeWebApplicationContext(ServletContext servletContext) { servletContext.log("Closing Spring root PortletApplicationContext"); try { if (this.context instanceof ConfigurablePortletApplicationContext) { ((ConfigurablePortletApplicationContext) this.context).close(); } } finally { ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == PortletContextLoader.class.getClassLoader()) { currentContext = null; } else if (ccl != null) { currentContextPerThread.remove(ccl); } servletContext.removeAttribute(PortletApplicationContext.ROOT_PORTLET_APPLICATION_CONTEXT_ATTRIBUTE); if (this.parentContextRef != null) { this.parentContextRef.release(); } } } /** * Obtain the Spring root portlet application context for the current thread * (i.e. for the current thread's context ClassLoader, which needs to be * the web application's ClassLoader). * @return the current root web application context, or <code>null</code> * if none found * @see org.springframework.web.context.support.SpringBeanAutowiringSupport */ public static PortletApplicationContext getCurrentPortletApplicationContext() { ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl != null) { PortletApplicationContext ccpt = currentContextPerThread.get(ccl); if (ccpt != null) { return ccpt; } } return currentContext; } }