/*! * 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.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.owasp.encoder.Encode; import org.pentaho.platform.api.engine.ICacheManager; import org.pentaho.platform.api.engine.IContentGenerator; import org.pentaho.platform.api.engine.IMessageFormatter; import org.pentaho.platform.api.engine.IMimeTypeListener; import org.pentaho.platform.api.engine.IOutputHandler; import org.pentaho.platform.api.engine.IParameterProvider; import org.pentaho.platform.api.engine.IPentahoRequestContext; import org.pentaho.platform.api.engine.IPentahoSession; import org.pentaho.platform.api.engine.IPluginManager; import org.pentaho.platform.engine.core.solution.SimpleParameterProvider; import org.pentaho.platform.engine.core.system.PentahoRequestContextHolder; import org.pentaho.platform.engine.core.system.PentahoSessionHolder; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.util.messages.LocaleHelper; import org.pentaho.platform.util.web.MimeHelper; import org.pentaho.platform.util.web.SimpleUrlFactory; import org.pentaho.platform.web.http.HttpOutputHandler; import org.pentaho.platform.web.http.request.HttpRequestParameterProvider; import org.pentaho.platform.web.http.session.HttpSessionParameterProvider; import org.pentaho.platform.web.servlet.messages.Messages; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; public class GenericServlet extends ServletBase { private static final long serialVersionUID = 6713118348911206464L; private static final Log logger = LogFactory.getLog( GenericServlet.class ); private static final String CACHE_FILE = "file"; //$NON-NLS-1$ private static ICacheManager cache = PentahoSystem.getCacheManager( null ); private boolean showDeprecationMessage; static { if ( cache != null ) { cache.addCacheRegion( CACHE_FILE ); } } @Override public Log getLogger() { return GenericServlet.logger; } @Override public void init() throws ServletException { // TODO Auto-generated method stub super.init(); String value = getServletConfig().getInitParameter( "showDeprecationMessage" ); showDeprecationMessage = Boolean.parseBoolean( value ); } @Override protected void doPost( final HttpServletRequest request, final HttpServletResponse response ) throws ServletException, IOException { doGet( request, response ); } @Override protected void doGet( final HttpServletRequest request, final HttpServletResponse response ) throws ServletException, IOException { if ( showDeprecationMessage ) { String deprecationMessage = "GenericServlet is deprecated and should no longer be handling requests. More detail below..." + "\n | You have issued a {0} request to {1} from referer {2} " + "\n | Please consider using one of the following REST services instead:" + "\n | * GET /api/repos/<pluginId>/<path> to read files from a plugin public dir" + "\n | * POST|GET /api/repos/<pathId>/generatedContent to create content resulting from execution of a " + "repo file" + "\n | * POST|GET /api/repos/<pluginId>/<contentGeneratorId> to execute a content generator by name (RPC " + "compatibility service)" + "\n \\ To turn this message off, set init-param 'showDeprecationMessage' to false in the GenericServlet " + "declaration" + ""; String referer = StringUtils.defaultString( request.getHeader( "Referer" ), "" ); logger.warn( MessageFormat.format( deprecationMessage, request.getMethod(), request.getRequestURL(), referer ) ); } PentahoSystem.systemEntryPoint(); IOutputHandler outputHandler = null; // BISERVER-2767 - grabbing the current class loader so we can replace it at the end ClassLoader origContextClassloader = Thread.currentThread().getContextClassLoader(); try { InputStream in = request.getInputStream(); String servletPath = request.getServletPath(); String pathInfo = request.getPathInfo(); String contentGeneratorId = ""; //$NON-NLS-1$ String urlPath = ""; //$NON-NLS-1$ SimpleParameterProvider pathParams = new SimpleParameterProvider(); if ( StringUtils.isEmpty( pathInfo ) ) { logger.error( Messages.getInstance().getErrorString( "GenericServlet.ERROR_0005_NO_RESOURCE_SPECIFIED" ) ); //$NON-NLS-1$ response.sendError( 403 ); return; } String path = pathInfo.substring( 1 ); int slashPos = path.indexOf( '/' ); if ( slashPos != -1 ) { pathParams.setParameter( "path", pathInfo.substring( slashPos + 1 ) ); //$NON-NLS-1$ contentGeneratorId = path.substring( 0, slashPos ); } else { contentGeneratorId = path; } urlPath = "content/" + contentGeneratorId; //$NON-NLS-1$ pathParams.setParameter( "query", request.getQueryString() ); //$NON-NLS-1$ pathParams.setParameter( "contentType", request.getContentType() ); //$NON-NLS-1$ pathParams.setParameter( "inputstream", in ); //$NON-NLS-1$ pathParams.setParameter( "httpresponse", response ); //$NON-NLS-1$ pathParams.setParameter( "httprequest", request ); //$NON-NLS-1$ pathParams.setParameter( "remoteaddr", request.getRemoteAddr() ); //$NON-NLS-1$ if ( PentahoSystem.debug ) { debug( "GenericServlet contentGeneratorId=" + contentGeneratorId ); //$NON-NLS-1$ debug( "GenericServlet urlPath=" + urlPath ); //$NON-NLS-1$ } IPentahoSession session = getPentahoSession( request ); IPluginManager pluginManager = PentahoSystem.get( IPluginManager.class, session ); if ( pluginManager == null ) { OutputStream out = response.getOutputStream(); String message = Messages.getInstance().getErrorString( "GenericServlet.ERROR_0001_BAD_OBJECT", IPluginManager.class.getSimpleName() ); //$NON-NLS-1$ error( message ); out.write( message.getBytes() ); return; } // TODO make doing the HTTP headers configurable per content generator SimpleParameterProvider headerParams = new SimpleParameterProvider(); Enumeration names = request.getHeaderNames(); while ( names.hasMoreElements() ) { String name = (String) names.nextElement(); String value = request.getHeader( name ); headerParams.setParameter( name, value ); } String pluginId = pluginManager.getServicePlugin( pathInfo ); if ( pluginId != null && pluginManager.isStaticResource( pathInfo ) ) { boolean cacheOn = "true".equals( pluginManager .getPluginSetting( pluginId, "settings/cache", "false" ) ); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ String maxAge = (String) pluginManager.getPluginSetting( pluginId, "settings/max-age", null ); //$NON-NLS-1$ allowBrowserCache( maxAge, pathParams ); String mimeType = MimeHelper.getMimeTypeFromFileName( pathInfo ); if ( mimeType != null ) { response.setContentType( mimeType ); } OutputStream out = response.getOutputStream(); // do we have this resource cached? ByteArrayOutputStream byteStream = null; if ( cacheOn ) { byteStream = (ByteArrayOutputStream) cache.getFromRegionCache( CACHE_FILE, pathInfo ); } if ( byteStream != null ) { IOUtils.write( byteStream.toByteArray(), out ); return; } InputStream resourceStream = pluginManager.getStaticResource( pathInfo ); if ( resourceStream != null ) { try { byteStream = new ByteArrayOutputStream(); IOUtils.copy( resourceStream, byteStream ); // if cache is enabled, drop file in cache if ( cacheOn ) { cache.putInRegionCache( CACHE_FILE, pathInfo, byteStream ); } // write it out IOUtils.write( byteStream.toByteArray(), out ); return; } finally { IOUtils.closeQuietly( resourceStream ); } } logger.error( Messages.getInstance().getErrorString( "GenericServlet.ERROR_0004_RESOURCE_NOT_FOUND", pluginId, pathInfo ) ); //$NON-NLS-1$ response.sendError( 404 ); return; } // content generators defined in plugin.xml are registered with 2 aliases, one is the id, the other is type // so, we can still retrieve a content generator by id, even though this is not the correct way to find // it. the correct way is to look up a content generator by pluginManager.getContentGenerator(type, // perspectiveName) IContentGenerator contentGenerator = (IContentGenerator) pluginManager.getBean( contentGeneratorId ); if ( contentGenerator == null ) { OutputStream out = response.getOutputStream(); String message = Messages.getInstance().getErrorString( "GenericServlet.ERROR_0002_BAD_GENERATOR", Encode.forHtml( contentGeneratorId ) ); //$NON-NLS-1$ error( message ); out.write( message.getBytes() ); return; } // set the classloader of the current thread to the class loader of // the plugin so that it can load its libraries // Note: we cannot ask the contentGenerator class for it's classloader, since the cg may // actually be a proxy object loaded by main the WebAppClassloader Thread.currentThread().setContextClassLoader( pluginManager.getClassLoader( pluginId ) ); // String proxyClass = PentahoSystem.getSystemSetting( module+"/plugin.xml" , // "plugin/content-generators/"+contentGeneratorId, // "content generator not found"); IParameterProvider requestParameters = new HttpRequestParameterProvider( request ); // see if this is an upload // File uploading is a service provided by UploadFileServlet where appropriate protections // are in place to prevent uploads that are too large. // boolean isMultipart = ServletFileUpload.isMultipartContent(request); // if (isMultipart) { // requestParameters = new SimpleParameterProvider(); // // Create a factory for disk-based file items // FileItemFactory factory = new DiskFileItemFactory(); // // // Create a new file upload handler // ServletFileUpload upload = new ServletFileUpload(factory); // // // Parse the request // List<?> /* FileItem */items = upload.parseRequest(request); // Iterator<?> iter = items.iterator(); // while (iter.hasNext()) { // FileItem item = (FileItem) iter.next(); // // if (item.isFormField()) { // ((SimpleParameterProvider) requestParameters).setParameter(item.getFieldName(), item.getString()); // } else { // String name = item.getName(); // ((SimpleParameterProvider) requestParameters).setParameter(name, item.getInputStream()); // } // } // } response.setCharacterEncoding( LocaleHelper.getSystemEncoding() ); IMimeTypeListener listener = new HttpMimeTypeListener( request, response ); outputHandler = getOutputHandler( response, true ); outputHandler.setMimeTypeListener( listener ); IParameterProvider sessionParameters = new HttpSessionParameterProvider( session ); IPentahoRequestContext requestContext = PentahoRequestContextHolder.getRequestContext(); Map<String, IParameterProvider> parameterProviders = new HashMap<String, IParameterProvider>(); parameterProviders.put( IParameterProvider.SCOPE_REQUEST, requestParameters ); parameterProviders.put( IParameterProvider.SCOPE_SESSION, sessionParameters ); parameterProviders.put( "headers", headerParams ); //$NON-NLS-1$ parameterProviders.put( "path", pathParams ); //$NON-NLS-1$ SimpleUrlFactory urlFactory = new SimpleUrlFactory( requestContext.getContextPath() + urlPath + "?" ); //$NON-NLS-1$ //$NON-NLS-2$ List<String> messages = new ArrayList<String>(); contentGenerator.setOutputHandler( outputHandler ); contentGenerator.setMessagesList( messages ); contentGenerator.setParameterProviders( parameterProviders ); contentGenerator.setSession( session ); contentGenerator.setUrlFactory( urlFactory ); // String contentType = request.getContentType(); // contentGenerator.setInput(input); contentGenerator.createContent(); if ( PentahoSystem.debug ) { debug( "Generic Servlet content generate successfully" ); //$NON-NLS-1$ } } catch ( Exception e ) { StringBuffer buffer = new StringBuffer(); error( Messages.getInstance() .getErrorString( "GenericServlet.ERROR_0002_BAD_GENERATOR", request.getQueryString() ), e ); //$NON-NLS-1$ List errorList = new ArrayList(); String msg = e.getMessage(); errorList.add( msg ); PentahoSystem.get( IMessageFormatter.class, PentahoSessionHolder.getSession() ).formatFailureMessage( "text/html", null, buffer, errorList ); //$NON-NLS-1$ response.getOutputStream().write( buffer.toString().getBytes( LocaleHelper.getSystemEncoding() ) ); } finally { // reset the classloader of the current thread Thread.currentThread().setContextClassLoader( origContextClassloader ); PentahoSystem.systemExitPoint(); } } protected void allowBrowserCache( String maxAge, IParameterProvider pathParams ) { if ( maxAge == null || "0".equals( maxAge ) ) { //$NON-NLS-1$ return; } HttpServletResponse response = (HttpServletResponse) pathParams.getParameter( "httpresponse" ); //$NON-NLS-1$ if ( response != null ) { response.setHeader( "Cache-Control", "max-age=" + maxAge ); //$NON-NLS-1$ //$NON-NLS-2$ } } protected IOutputHandler getOutputHandler( HttpServletResponse response, boolean allowFeedback ) throws IOException { OutputStream out = response.getOutputStream(); HttpOutputHandler handler = new HttpOutputHandler( response, out, allowFeedback ); return handler; } }