/**
* 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.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.jasig.portal.api.PlatformApiBroker;
import org.jasig.portal.api.PlatformApiBrokerImpl;
import org.jasig.portal.portlet.dao.jpa.ThreadContextClassLoaderAspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author Nicholas Blair, npblair@wisc.edu
* @version $Revision$
*/
@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;
}
}