/** * Copyright 2011 meltmedia * * Licensed 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.xchain.framework.servlet; import java.io.IOException; import java.util.HashMap; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.namespace.QName; import org.apache.commons.jxpath.JXPathContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xchain.CatalogNotFoundException; import org.xchain.CommandNotFoundException; import org.xchain.framework.factory.CatalogFactory; import org.xchain.framework.jxpath.QNameVariables; import org.xchain.framework.lifecycle.ContainerLifecycle; import org.xchain.framework.lifecycle.LifecycleException; import org.xchain.framework.lifecycle.ThreadLifecycle; import org.xchain.namespaces.servlet.Constants; /** * This servlet can be used to bind servlet requests to xchains inside of an application. To do this, this servlet tasks the following action: * <ol> * <li>The requested url is translated into the name of a catalog. This catalog is then loaded.</li> * <li>The requested method is translated into a qname. This command is loaded from the catalog.</li> * <li>The servlet request and response are used to create a JXPathContext.</li> * <li>The JXPathcContext is used to execute the command.</li> * </ol> * * @author Mike Moulton * @author Devon Tackett * @author Christian Trimble * @author John Trimble * @author Josh Kennedy */ public class CatalogServlet extends HttpServlet { public static Logger log = LoggerFactory.getLogger(CatalogServlet.class); public static final String DEFAULT_BASE_CATALOG_NAME = "resource://" + ContainerLifecycle.SERVLET_CONTEXT_ATHORITY + "/"; public static final String BASE_CATALOG_NAME_PARAM = "base-catalog-name"; protected String baseCatalogName = null; public void init( ServletConfig config ) throws ServletException { super.init(config); // read the base catalog name. baseCatalogName = config.getInitParameter(BASE_CATALOG_NAME_PARAM); if( baseCatalogName == null ) { baseCatalogName = DEFAULT_BASE_CATALOG_NAME; } // log the config. if( log.isDebugEnabled() ) { log.debug("Base Catalog Name: "+baseCatalogName); } } protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, java.io.IOException { ServletThreadContext context = new ServletThreadContext(request, response); try { ThreadLifecycle.getInstance().startThread(context); } catch( LifecycleException le ) { throw new ServletException("Could not start the servlet thread due to an exception."); } try { // All methods may be valid commands for a catalog. No need to break out the usage into different // method calls. executeCommand( request, response ); } finally { try { ThreadLifecycle.getInstance().stopThread(context); } catch( LifecycleException le ) { if( log.isWarnEnabled() ) { log.warn("An exception was thrown while cleaning up a thread.", le); } } } } /** * Execute the proper XChain based on the incoming {@link HttpServletRequest} and sending output on the {@link HttpServletResponse}. * The path on the request determines which catalog to use while the method of the request determines which command to execute. * * @param request The incoming request. * @param response The outgoing response. */ public void executeCommand( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { try { // get the catalog name for the request. String catalogName = catalogNameForRequest( request); // get the command name for the request. QName commandName = commandNameForRequest( request ); if( log.isDebugEnabled() ) { log.debug("Executing '"+commandName+"' in catalog '"+catalogName+"'."); } // create the context for the request. JXPathContext context = jXPathContext( request, response ); // execute the command. boolean result = CatalogFactory.getInstance().getCatalog(catalogName).getCommand(commandName).execute(context); // boolean result = CommandUtil.execute(catalogName, commandName, context); if( response.isCommitted() == false && result == false ) { // it looks like we didn't do anything... } } catch( CatalogNotFoundException cae ) { if( log.isDebugEnabled() ) { log.debug("Could not find xchain catalog.", cae); } response.sendError( HttpServletResponse.SC_NOT_FOUND ); } catch( CommandNotFoundException coe ) { if( log.isDebugEnabled() ) { log.debug("Could not find xchain command.", coe); } response.sendError( HttpServletResponse.SC_NOT_FOUND ); } catch( Exception e ) { if (log.isErrorEnabled()) { log.error("Unable to execute xchain.", e); } throw new ServletException(e); } } /** * Get the catalog based on the requested path. * * @param request The incoming HttpServletRequest. * * @return The full requested catalog name. */ protected String catalogNameForRequest( HttpServletRequest request) { StringBuffer sb = new StringBuffer(); // append the base catalog name. sb.append( baseCatalogName ); String servletPath = request.getServletPath(); if( servletPath.startsWith("/") && baseCatalogName.endsWith("/") ) { sb.append(servletPath.replaceAll("\\A/(.*)\\Z", "$1")); } else if( !servletPath.startsWith("/") && !baseCatalogName.endsWith("/") ) { sb.append("/").append(servletPath); } else { sb.append(servletPath); } // return the catalog name. return sb.toString(); } /** * Get the command requested based on the request method. * * @param request The incoming HttpServletRequest. * * @return The QName of the requested command. */ protected QName commandNameForRequest( HttpServletRequest request ) { return new QName( Constants.URI, request.getMethod().toLowerCase() ); } /** * Build a new JXPathContext and populate it with the given request and response. * * @param request The incoming HttpServletRequest. * @param response The outgoing HttpServletResponse. * * @return A JXPath which contains the servlet request and response. */ protected JXPathContext jXPathContext( HttpServletRequest request, HttpServletResponse response ) { JXPathContext context = JXPathContext.newContext( new HashMap() ); ((QNameVariables)context.getVariables()).declareVariable( new QName( Constants.URI, Constants.CONTEXT ), getServletConfig().getServletContext() ); ((QNameVariables)context.getVariables()).declareVariable( new QName( Constants.URI, Constants.REQUEST ), request ); ((QNameVariables)context.getVariables()).declareVariable( new QName( Constants.URI, Constants.RESPONSE ), response ); return context; } }