/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.platform.web.servlet; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.platform.api.engine.IPluginManager; import org.pentaho.platform.api.engine.IPluginManagerListener; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.ListableBeanFactory; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * Dispatches requests to Servlets provided by BIServer plugins. To define a Servlet in a plugin, simply add a bean * element for the Servlet class in the plugin.spring.xml file in your plugin root directory. * * @author Aaron Phillips */ public class PluginDispatchServlet implements Servlet { private static final Log logger = LogFactory.getLog( PluginDispatchServlet.class ); private ServletConfig servletConfig; private boolean initialized = false; private IPluginManager pluginManager = PentahoSystem.get( IPluginManager.class ); private Map<String, Servlet> pluginServletMap = new HashMap<String, Servlet>(); public PluginDispatchServlet() { super(); } public void destroy() { for ( Map.Entry<String, Servlet> pluginServlet : pluginServletMap.entrySet() ) { pluginServlet.getValue().destroy(); } } public void service( final ServletRequest req, final ServletResponse res ) throws ServletException, IOException { if ( !initialized ) { doInit(); } if ( !( req instanceof HttpServletRequest ) ) { throw new IllegalArgumentException( PluginDispatchServlet.class.getSimpleName() + " cannot handle non HTTP requests" ); //$NON-NLS-1$ } HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; Servlet pluginServlet = getTargetServlet( request, response ); if ( pluginServlet == null ) { response.setStatus( 404 ); // FIXME: log more detail here for debugging return; } pluginServlet.service( req, res ); } protected Servlet getTargetServlet( HttpServletRequest request, HttpServletResponse response ) throws ServletException { String dispatchKey = getDispatchKey( request ); if ( StringUtils.isEmpty( dispatchKey ) ) { if ( logger.isDebugEnabled() ) { logger .debug( "dispatcher servlet is invoked but there is nothing telling it where to dispatch to" ); //$NON-NLS-1$ } return null; } Servlet targetServlet = null; String checkPath = dispatchKey; do { logger.debug( "checking for servlet registered to service request for \"" + checkPath + "\"" ); //$NON-NLS-1$//$NON-NLS-2$ targetServlet = pluginServletMap.get( checkPath ); if ( targetServlet != null ) { logger.debug( "servlet " + targetServlet.getClass().getName() + " will service request for \"" + dispatchKey //$NON-NLS-1$//$NON-NLS-2$ + "\"" ); //$NON-NLS-1$ return targetServlet; } if ( checkPath.contains( "/" ) ) { //$NON-NLS-1$ checkPath = checkPath.substring( 0, checkPath.lastIndexOf( "/" ) ); //$NON-NLS-1$ } else { checkPath = null; } } while ( checkPath != null ); if ( targetServlet == null ) { logger .debug( "no servlet registered to service request for \"" + dispatchKey + "\"" ); //$NON-NLS-1$ //$NON-NLS-2$ } return targetServlet; } /** * Returns the dispatch key for this request. This name is the part of the request path beyond the servlet base path. * I.e. if the PluginDispatchServlet is mapped to the "/plugin" context in web.xml, then this method will return * "myPlugin/myServlet" given a request to "http://localhost:8080/pentaho/plugin/myPlugin/myServlet". * * @return the part of the request url used to dispatch the request */ public String getDispatchKey( HttpServletRequest request ) { // path info will give us what we want with String requestPathInfo = request.getPathInfo(); if ( requestPathInfo.startsWith( "/" ) ) { //$NON-NLS-1$ requestPathInfo = requestPathInfo.substring( 1 ); } if ( requestPathInfo.endsWith( "/" ) ) { //$NON-NLS-1$ requestPathInfo = requestPathInfo.substring( requestPathInfo.length() ); } return requestPathInfo; } public void init( final ServletConfig config ) throws ServletException { this.servletConfig = config; pluginManager.addPluginManagerListener( new IPluginManagerListener() { @Override public void onReload() { try { initialized = false; doInit(); } catch ( ServletException e ) { logger.error( e ); } } } ); doInit(); } @SuppressWarnings( "unchecked" ) /** Restore the caching once the Plugin Type Tracking system is in place, for now we'll look-up every time **/ private synchronized void doInit() throws ServletException { if ( logger.isDebugEnabled() ) { logger.debug( "PluginDispatchServlet.init" ); //$NON-NLS-1$ } if ( initialized ) { return; } pluginServletMap.clear(); Map<String, ListableBeanFactory> pluginBeanFactoryMap = getPluginBeanFactories(); for ( Map.Entry<String, ListableBeanFactory> pluginBeanFactoryEntry : pluginBeanFactoryMap.entrySet() ) { Map<String, Servlet> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors( pluginBeanFactoryEntry.getValue(), Servlet.class, true, true ); if ( logger.isDebugEnabled() ) { logger.debug( "found " + beans.size() + " servlets in " + pluginBeanFactoryEntry.getKey() ); //$NON-NLS-1$//$NON-NLS-2$ } for ( Map.Entry<String, Servlet> beanEntry : beans.entrySet() ) { Servlet pluginServlet = (Servlet) beanEntry.getValue(); String servletId = beanEntry.getKey(); String pluginId = pluginBeanFactoryEntry.getKey(); String context = pluginId + "/" + servletId; //$NON-NLS-1$ pluginServletMap.put( context, pluginServlet ); if ( logger.isDebugEnabled() ) { logger .debug( "calling init on servlet " + pluginServlet.getClass().getName() + " serving context " + context ); //$NON-NLS-1$//$NON-NLS-2$ } try { pluginServlet.init( servletConfig ); } catch ( Throwable t ) { logger.error( "Could not load servlet '" + context + "'", t ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } } // Set initialized to true at the end of the synchronized method, so // that invocations of service() before this method has completed will not // cause NullPointerException initialized = true; } public ServletConfig getServletConfig() { return servletConfig; } public String getServletInfo() { return "A servlet to dispatch requests to Servlets defined in BIServer plugins"; //$NON-NLS-1$ } protected Map<String, ListableBeanFactory> getPluginBeanFactories() { Collection<String> pluginIds = pluginManager.getRegisteredPlugins(); Map<String, ListableBeanFactory> pluginBeanFactoryMap = new HashMap<String, ListableBeanFactory>(); for ( String pluginId : pluginIds ) { ListableBeanFactory beanFactory = pluginManager.getBeanFactory( pluginId ); if ( beanFactory != null ) { pluginBeanFactoryMap.put( pluginId, beanFactory ); } } return pluginBeanFactoryMap; } }