364 lines
12 KiB
Java
Executable File
364 lines
12 KiB
Java
Executable File
/*
|
|
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
* as published by the Free Software Foundation; either version 2.1 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This library 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.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
package com.arsdigita.cms.dispatcher;
|
|
|
|
import com.arsdigita.developersupport.DeveloperSupport;
|
|
import com.arsdigita.dispatcher.Dispatcher;
|
|
import com.arsdigita.dispatcher.DispatcherHelper;
|
|
import com.arsdigita.dispatcher.JSPApplicationDispatcher;
|
|
import com.arsdigita.dispatcher.RequestContext;
|
|
import com.arsdigita.kernel.security.UserContext;
|
|
import com.arsdigita.kernel.security.Util;
|
|
import com.arsdigita.ui.login.LoginHelper;
|
|
import com.arsdigita.util.LockableImpl;
|
|
import com.arsdigita.xml.XML;
|
|
import org.apache.log4j.Logger;
|
|
import org.xml.sax.helpers.DefaultHandler;
|
|
|
|
import javax.servlet.ServletException;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.servlet.http.HttpServletResponse;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
|
|
/**
|
|
* <p> The Service Dispatcher handles common services shared across all
|
|
* CMS instances. This dispatcher is called by the Subsite dispatcher.</p>
|
|
*
|
|
* <p>Here are the steps for a request to
|
|
* <tt>http://yourserver/cms-service/cheese</tt>:</p>
|
|
*
|
|
* <ol>
|
|
* <li><p>A client sends a request to the web server, which passes it on to
|
|
* the global ACS dispatcher.</p></li>
|
|
*
|
|
* <li><p>The global ACS dispatcher examines the first part of the URL,
|
|
* notices that the Content Center application is mounted at
|
|
* <tt>/cms-service</tt> and hands the request to the CMS
|
|
* Service dispatcher.</p></li>
|
|
*
|
|
* <li><p>The CMS Service dispatcher determines whether a <tt>Page</tt>
|
|
* has been registered to the URL <tt>/cheese</tt>.</p></li>
|
|
*
|
|
* <li><p>If no <tt>Page</tt> is registered to the URL <tt>/cheese</tt>,
|
|
* then the CMS Service dispatcher hands the request to the
|
|
* {@link com.arsdigita.dispatcher.JSPApplicationDispatcher},
|
|
* which serves JSPs and static content from the
|
|
* <tt>/packages/cms/www</tt>.</p></li>
|
|
* </ol>
|
|
*
|
|
* @author Michael Pih (pihman@arsdigita.com)
|
|
* @version $Revision: #13 $ $DateTime: 2004/08/17 23:15:09 $
|
|
* @version $Id: ServiceDispatcher.java 2116 2011-01-13 17:13:58Z pboy $
|
|
*/
|
|
public class ServiceDispatcher extends LockableImpl implements Dispatcher {
|
|
|
|
/** Error logging. */
|
|
private static Logger s_log =
|
|
Logger.getLogger(ServiceDispatcher.class.getName());
|
|
|
|
|
|
/** The path of the file that maps resources. */
|
|
private final static String MAP_FILE = "WEB-INF/resources/cms-service-map.xml";
|
|
|
|
/** Mapping between a relative URL and the class name of a ResourceHandler. */
|
|
private static HashMap s_pageClasses = new HashMap();
|
|
|
|
/**
|
|
* Instantiated ResourceHandler cache. This allows for lazy loading.
|
|
*/
|
|
private static SimpleCache s_pages = new SimpleCache();
|
|
|
|
|
|
private Dispatcher m_notFoundHandler;
|
|
private ArrayList m_trailingSlashList = new ArrayList();
|
|
|
|
|
|
public ServiceDispatcher() {
|
|
super();
|
|
|
|
m_trailingSlashList = new ArrayList();
|
|
requireTrailingSlash("");
|
|
|
|
setNotFoundDispatcher(JSPApplicationDispatcher.getInstance());
|
|
|
|
readFromFile(MAP_FILE);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles requests made to the Content Center package.
|
|
*
|
|
* @param request The HTTP request
|
|
* @param response The HTTP response
|
|
* @param actx The request context
|
|
*/
|
|
public void dispatch(HttpServletRequest request,
|
|
HttpServletResponse response,
|
|
RequestContext actx)
|
|
throws IOException, ServletException {
|
|
|
|
DeveloperSupport.startStage("ServiceDispatcher.dispatch");
|
|
|
|
String url = actx.getRemainingURLPart();
|
|
String originalUrl = actx.getOriginalURL();
|
|
|
|
// Empty remaining URL and doesn't end in trailing slash:
|
|
// probably want to redirect.
|
|
if ( m_trailingSlashList.contains(url) && !originalUrl.endsWith("/") ) {
|
|
DispatcherHelper.sendRedirect(response, originalUrl + "/");
|
|
return;
|
|
}
|
|
|
|
// Check user access.
|
|
checkUserAccess(request, response, actx);
|
|
|
|
ResourceHandler page = getResource(url);
|
|
if ( page != null ) {
|
|
// Serve the page.
|
|
page.init();
|
|
page.dispatch(request, response, actx);
|
|
} else {
|
|
// Fall back on the JSP application dispatcher.
|
|
m_notFoundHandler.dispatch(request, response, actx);
|
|
}
|
|
|
|
DeveloperSupport.endStage("ServiceDispatcher.dispatch");
|
|
}
|
|
|
|
/**
|
|
* sets the default page to display if no page can be found for the
|
|
* URL in the page map on dispatch.
|
|
*/
|
|
public final void setNotFoundDispatcher(Dispatcher d) {
|
|
m_notFoundHandler = d;
|
|
}
|
|
|
|
/**
|
|
* Adds a URL to the list of URLs that are required to have trailing
|
|
* slashes. A request for url will be redirected to url + "/"
|
|
* if the original URL request (what you see in your browser)
|
|
* doesn't include a trailing slash. This is required for
|
|
* creating virtual directories, so that relative URLs and redirects
|
|
* work.
|
|
*/
|
|
public void requireTrailingSlash(String url) {
|
|
m_trailingSlashList.add(url);
|
|
}
|
|
|
|
|
|
/**
|
|
* Fetch a page based on the URL stub.
|
|
*
|
|
* @param url The URL stub following the site-node URL
|
|
* @return A ResourceHandler or null if none exists.
|
|
* @pre (url != null)
|
|
*/
|
|
protected ResourceHandler getResource(String url) throws ServletException {
|
|
|
|
// First check the pages cache for existing pages.
|
|
ResourceHandler page = (ResourceHandler) s_pages.get(url);
|
|
if ( page == null ) {
|
|
|
|
// Next check if the URL maps to a page class.
|
|
String pageClassName = (String) s_pageClasses.get(url);
|
|
if ( pageClassName != null ) {
|
|
|
|
Class pageClass;
|
|
try {
|
|
pageClass = Class.forName(pageClassName);
|
|
} catch (ClassNotFoundException e) {
|
|
s_log.error("error fetching class for ResourceHandler", e);
|
|
throw new ServletException(e);
|
|
}
|
|
|
|
// Try and instantiate the page.
|
|
try {
|
|
page = (ResourceHandler) pageClass.newInstance();
|
|
} catch (InstantiationException e) {
|
|
s_log.error("error instantiating a ResourceHandler", e);
|
|
throw new ServletException(e);
|
|
} catch (IllegalAccessException e) {
|
|
s_log.error("error instantiating a ResourceHandler", e);
|
|
throw new ServletException(e);
|
|
}
|
|
|
|
page.init();
|
|
s_pages.put(url, page);
|
|
}
|
|
}
|
|
return page;
|
|
}
|
|
|
|
/**
|
|
* Map a page to a URL.
|
|
*
|
|
* @param url The URL
|
|
* @param className The name of the ResourceHandler class
|
|
* @pre (url != null && className != null)
|
|
*/
|
|
protected void addResource(String url, String className) {
|
|
s_pageClasses.put(url, className);
|
|
}
|
|
|
|
/**
|
|
* Release the page at the specified URL.
|
|
*
|
|
* @param url The URL
|
|
* @pre (url != null)
|
|
*/
|
|
public static void releaseResource(String url) {
|
|
s_pages.remove(url);
|
|
}
|
|
|
|
/**
|
|
* Verify that the user is logged in and is able to view the
|
|
* page. Subclasses can override this method if they need to, but
|
|
* should always be sure to call super.checkUserAccess(...)
|
|
*
|
|
* @deprecated subclasses of ResourceHandler should check the
|
|
* permissions directly on the ContentItem being served. This
|
|
* method currently acts as a no-op
|
|
*
|
|
* @param request The HTTP request
|
|
* @param response The HTTP response
|
|
* @param actx The request context
|
|
**/
|
|
protected void checkUserAccess(HttpServletRequest request,
|
|
HttpServletResponse response,
|
|
RequestContext actx)
|
|
throws ServletException {
|
|
|
|
// User user = KernelHelper.getCurrentUser(request);
|
|
|
|
// If the user is not logged in, redirect to the login page.
|
|
// Disabled totally bogus security check
|
|
// This should be done by the resource handler impl itself
|
|
// which can check real permissions
|
|
//if ( user == null ) {
|
|
// redirectToLoginPage(request, response);
|
|
// Break.
|
|
//}
|
|
}
|
|
|
|
|
|
/**
|
|
* Redirects the client to the login page, setting the return url to
|
|
* the current request URI.
|
|
*
|
|
* @exception ServletException If there is an exception thrown while
|
|
* trying to redirect, wrap that exception in a ServletException
|
|
**/
|
|
protected void redirectToLoginPage(HttpServletRequest req,
|
|
HttpServletResponse resp)
|
|
throws ServletException {
|
|
String url = Util.getSecurityHelper()
|
|
.getLoginURL(req)
|
|
+"?"+UserContext.RETURN_URL_PARAM_NAME
|
|
+"="+UserContext.encodeReturnURL(req);
|
|
try {
|
|
LoginHelper.sendRedirect(req, resp, url);
|
|
} catch (IOException e) {
|
|
s_log.error("IO Exception", e);
|
|
throw new ServletException(e.getMessage(), e);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
* Initializes URL-to-Page/Dispatcher/Servlet mappings from a file.
|
|
*
|
|
* Format of the file is XML:
|
|
* <pre>
|
|
* <dispatcher-configuration>
|
|
* <url-mapping
|
|
* <url>my-page</url>
|
|
* OR <page-class>com.arsdigita.Page.class</page-class>
|
|
* <url-mapping
|
|
* </dispatcher-configuration>
|
|
* </pre>
|
|
*/
|
|
private void readFromFile(final String file) {
|
|
|
|
if ( isLocked() ) {
|
|
s_log.error(
|
|
"error in readFromFile: Cannot configure resources more than once.");
|
|
throw new RuntimeException(
|
|
"error in readFromFile: Cannot configure resources once the dispatcher is locked.");
|
|
}
|
|
|
|
XML.parseResource(file, newParseConfigHandler(s_pageClasses));
|
|
|
|
lock();
|
|
}
|
|
|
|
|
|
/**
|
|
* <b><font color="red">Expirimental</font></b>
|
|
* Returns a SAX event handler object for setting up a MapDispatcher
|
|
* using an XML config file.
|
|
* @param map A map to configure
|
|
* @return a SAX DefaultHandler object for handling SAX events
|
|
* @pre md.m_map != null
|
|
*/
|
|
protected DefaultHandler newParseConfigHandler(Map map) {
|
|
return new PageClassConfigHandler(map);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
* SAX event handler class for parsing configuration file.
|
|
*/
|
|
protected static class PageClassConfigHandler extends DefaultHandler {
|
|
|
|
private Map m_map;
|
|
private StringBuffer m_buffer;
|
|
private String m_url;
|
|
private String m_className;
|
|
|
|
public PageClassConfigHandler(Map map) {
|
|
m_map = map;
|
|
m_buffer = new StringBuffer();
|
|
}
|
|
|
|
public void characters(char[] ch, int start, int len) {
|
|
for (int i = 0; i < len; i++) {
|
|
m_buffer.append(ch[start + i]);
|
|
}
|
|
}
|
|
|
|
public void endElement(String uri, String localName, String qn) {
|
|
if ( qn.equals("url") ) {
|
|
m_url = m_buffer.toString().trim();
|
|
} else if ( qn.equals("page-class") ) {
|
|
m_className = m_buffer.toString().trim();
|
|
} else if ( qn.equals("url-mapping") ) {
|
|
m_map.put(m_url, m_className);
|
|
}
|
|
m_buffer = new StringBuffer();
|
|
}
|
|
}
|
|
|
|
}
|