/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * 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 the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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.apereo.portal.portlet.container.services; import java.io.InputStream; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.pluto.container.PortletAppDescriptorService; import org.apache.pluto.container.PortletContainerException; import org.apache.pluto.container.PortletWindow; import org.apache.pluto.container.RequestDispatcherService; import org.apache.pluto.container.driver.DriverPortletConfig; import org.apache.pluto.container.driver.DriverPortletContext; import org.apache.pluto.container.driver.PortletContextService; import org.apache.pluto.container.driver.PortletRegistryEvent; import org.apache.pluto.container.driver.PortletRegistryListener; import org.apache.pluto.container.driver.PortletRegistryService; import org.apache.pluto.container.impl.PortletAppDescriptorServiceImpl; import org.apache.pluto.container.om.portlet.PortletApplicationDefinition; import org.apache.pluto.container.om.portlet.PortletDefinition; import org.apache.pluto.driver.container.DriverPortletConfigImpl; import org.apache.pluto.driver.container.DriverPortletContextImpl; import org.apereo.portal.api.PlatformApiBroker; import org.apereo.portal.api.PlatformApiBrokerImpl; import org.apereo.portal.portlet.dao.jpa.ThreadContextClassLoaderAspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** */ @Service public class LocalPortletContextManager implements PortletRegistryService, PortletContextService { /** Web deployment descriptor location. */ private static final String WEB_XML = "/WEB-INF/web.xml"; /** Portlet deployment descriptor location. */ private static final String PORTLET_XML = "/WEB-INF/portlet.xml"; protected final Log logger = LogFactory.getLog(this.getClass()); /** * The PortletContext cache map: key is servlet context, and value is the associated portlet * context. */ private Map<String, DriverPortletContext> portletContexts = new ConcurrentHashMap<String, DriverPortletContext>(); /** * The PortletContext cache map: key is servlet context, and value is the associated portlet * context. */ private final Map<String, DriverPortletConfig> portletConfigs = new ConcurrentHashMap<String, DriverPortletConfig>(); /** The registered listeners that should be notified upon registry events. */ private final List<PortletRegistryListener> registryListeners = new CopyOnWriteArrayList<PortletRegistryListener>(); /** * The classloader for the portal, key is portletWindow and value is the classloader. TODO this * looks like a horrible memory leak */ private final Map<String, ClassLoader> classLoaders = new ConcurrentHashMap<String, ClassLoader>(); /** * Cache of descriptors. WeakHashMap is used so that once the context is destroyed (kinda), the * cache is eliminated. Ideally we'd use a ServletContextListener, but at this point I'm * wondering if we really want to add another config requirement in the servlet xml? Hmm. . . */ private final Map<ServletContext, PortletApplicationDefinition> portletAppDefinitionCache = new WeakHashMap<ServletContext, PortletApplicationDefinition>(); private PortletAppDescriptorService portletAppDescriptorService = new PortletAppDescriptorServiceImpl(); private RequestDispatcherService requestDispatcherService; @Autowired public void setRequestDispatcherService(RequestDispatcherService requestDispatcherService) { this.requestDispatcherService = requestDispatcherService; } public void setPortletAppDescriptorService( PortletAppDescriptorService portletAppDescriptorService) { this.portletAppDescriptorService = portletAppDescriptorService; } private PlatformApiBrokerImpl platformApiBroker; @Autowired public void setPlatformApiBroker(PlatformApiBrokerImpl platformApiBroker) { this.platformApiBroker = platformApiBroker; } // Public Methods ---------------------------------------------------------- /** * Retrieves the PortletContext associated with the given ServletContext. If one does not exist, * it is created. * * @param config the servlet config. * @return the InternalPortletContext associated with the ServletContext. * @throws PortletContainerException */ @Override public synchronized String register(ServletConfig config) throws PortletContainerException { ServletContext servletContext = config.getServletContext(); String contextPath = servletContext.getContextPath(); if (!portletContexts.containsKey(contextPath)) { PortletApplicationDefinition portletApp = this.getPortletAppDD(servletContext, contextPath, contextPath); DriverPortletContext portletContext = new DriverPortletContextImpl( servletContext, portletApp, requestDispatcherService); portletContext.setAttribute( PlatformApiBroker.PORTLET_CONTEXT_ATTRIBUTE_NAME, platformApiBroker); portletContexts.put(contextPath, portletContext); fireRegistered(portletContext); if (logger.isInfoEnabled()) { logger.info("Registered portlet application for context '" + contextPath + "'"); logger.info( "Registering " + portletApp.getPortlets().size() + " portlets for context " + portletContext.getApplicationName()); } //TODO have the portlet servlet provide the portlet's classloader as parameter to this method //This approach is needed as all pluto callbacks in uPortal have an aspect that switches the thread classloader back //to uPortal's classloader. ClassLoader classLoader = ThreadContextClassLoaderAspect.getPreviousClassLoader(); if (classLoader == null) { classLoader = Thread.currentThread().getContextClassLoader(); } classLoaders.put(portletApp.getName(), classLoader); for (PortletDefinition portlet : portletApp.getPortlets()) { String appName = portletContext.getApplicationName(); if (appName == null) { throw new PortletContainerException( "Portlet application name should not be null."); } portletConfigs.put( portletContext.getApplicationName() + "/" + portlet.getPortletName(), new DriverPortletConfigImpl(portletContext, portlet)); } } else { if (logger.isInfoEnabled()) { logger.info( "Portlet application for context '" + contextPath + "' already registered."); } } return contextPath; } @Override public synchronized void unregister(DriverPortletContext context) { portletContexts.remove(context.getApplicationName()); classLoaders.remove(context.getApplicationName()); Iterator<String> configs = portletConfigs.keySet().iterator(); while (configs.hasNext()) { String key = configs.next(); if (key.startsWith(context.getApplicationName() + "/")) { configs.remove(); } } fireRemoved(context); } @Override public Iterator<String> getRegisteredPortletApplicationNames() { return new HashSet<String>(portletContexts.keySet()).iterator(); } @Override public Iterator<DriverPortletContext> getPortletContexts() { return new HashSet<DriverPortletContext>(portletContexts.values()).iterator(); } @Override public DriverPortletContext getPortletContext(String applicationName) { return portletContexts.get(applicationName); } @Override public DriverPortletContext getPortletContext(PortletWindow portletWindow) throws PortletContainerException { return portletContexts.get(portletWindow.getPortletDefinition().getApplication().getName()); } @Override public DriverPortletConfig getPortletConfig(String applicationName, String portletName) throws PortletContainerException { DriverPortletConfig ipc = portletConfigs.get(applicationName + "/" + portletName); if (ipc != null) { return ipc; } String msg = "Unable to locate portlet config [applicationName=" + applicationName + "]/[" + portletName + "]."; logger.warn(msg); throw new PortletContainerException(msg); } @Override public PortletDefinition getPortlet(String applicationName, String portletName) throws PortletContainerException { DriverPortletConfig ipc = portletConfigs.get(applicationName + "/" + portletName); if (ipc != null) { return ipc.getPortletDefinition(); } String msg = "Unable to retrieve portlet: '" + applicationName + "/" + portletName + "'"; logger.warn(msg); throw new PortletContainerException(msg); } @Override public PortletApplicationDefinition getPortletApplication(String applicationName) throws PortletContainerException { DriverPortletContext ipc = portletContexts.get(applicationName); if (ipc != null) { return ipc.getPortletApplicationDefinition(); } String msg = "Unable to retrieve portlet application: '" + applicationName + "'"; logger.warn(msg); throw new PortletContainerException(msg); } @Override public ClassLoader getClassLoader(String applicationName) { return classLoaders.get(applicationName); } @Override public void addPortletRegistryListener(PortletRegistryListener listener) { registryListeners.add(listener); } @Override public void removePortletRegistryListener(PortletRegistryListener listener) { registryListeners.remove(listener); } private void fireRegistered(DriverPortletContext context) { PortletRegistryEvent event = new PortletRegistryEvent(); event.setPortletApplication(context.getPortletApplicationDefinition()); for (PortletRegistryListener l : registryListeners) { l.portletApplicationRegistered(event); } logger.info("Portlet Context '" + context.getApplicationName() + "' registered."); } private void fireRemoved(DriverPortletContext context) { PortletRegistryEvent event = new PortletRegistryEvent(); event.setPortletApplication(context.getPortletApplicationDefinition()); for (PortletRegistryListener l : registryListeners) { l.portletApplicationRemoved(event); } logger.info("Portlet Context '" + context.getApplicationName() + "' removed."); } /** * Retrieve the Portlet Application Deployment Descriptor for the given servlet context. Create * it if it does not allready exist. * * @param servletContext the servlet context. * @return The portlet application deployment descriptor. * @throws PortletContainerException if the descriptor can not be found or parsed */ public synchronized PortletApplicationDefinition getPortletAppDD( ServletContext servletContext, String name, String contextPath) throws PortletContainerException { PortletApplicationDefinition portletApp = this.portletAppDefinitionCache.get(servletContext); if (portletApp == null) { portletApp = createDefinition(servletContext, name, contextPath); this.portletAppDefinitionCache.put(servletContext, portletApp); } return portletApp; } // Private Methods --------------------------------------------------------- /** * Creates the portlet.xml deployment descriptor representation. * * @param servletContext the servlet context for which the DD is requested. * @return the Portlet Application Deployment Descriptor. * @throws PortletContainerException */ private PortletApplicationDefinition createDefinition( ServletContext servletContext, String name, String contextPath) throws PortletContainerException { PortletApplicationDefinition portletApp = null; try { InputStream paIn = servletContext.getResourceAsStream(PORTLET_XML); InputStream webIn = servletContext.getResourceAsStream(WEB_XML); if (paIn == null) { throw new PortletContainerException( "Cannot find '" + PORTLET_XML + "'. Are you sure it is in the deployed package?"); } if (webIn == null) { throw new PortletContainerException( "Cannot find '" + WEB_XML + "'. Are you sure it is in the deployed package?"); } portletApp = this.portletAppDescriptorService.read(name, contextPath, paIn); this.portletAppDescriptorService.mergeWebDescriptor(portletApp, webIn); } catch (Exception ex) { throw new PortletContainerException( "Exception loading portlet descriptor for: " + servletContext.getServletContextName(), ex); } return portletApp; } }