// ========================================================================
// $Id: WebApplicationHandler.java,v 1.62 2006/01/04 13:55:31 gregwilkins Exp $
// Copyright 1996-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// 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 net.lightbody.bmp.proxy.jetty.jetty.servlet;
import net.lightbody.bmp.proxy.jetty.http.HttpContext;
import net.lightbody.bmp.proxy.jetty.http.HttpResponse;
import net.lightbody.bmp.proxy.jetty.http.PathMap;
import net.lightbody.bmp.proxy.jetty.log.LogFactory;
import net.lightbody.bmp.proxy.jetty.util.*;
import org.apache.commons.logging.Log;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.*;
/* --------------------------------------------------------------------- */
/** WebApp HttpHandler.
* This handler extends the ServletHandler with security, filter and resource
* capabilities to provide full J2EE web container support.
* <p>
* @since Jetty 4.1
* @see net.lightbody.bmp.proxy.jetty.jetty.servlet.WebApplicationContext
* @version $Id: WebApplicationHandler.java,v 1.62 2006/01/04 13:55:31 gregwilkins Exp $
* @author Greg Wilkins
*/
public class WebApplicationHandler extends ServletHandler
{
private static Log log= LogFactory.getLog(WebApplicationHandler.class);
private Map _filterMap= new HashMap();
private List _pathFilters= new ArrayList();
private List _filters= new ArrayList();
private MultiMap _servletFilterMap= new MultiMap();
private boolean _acceptRanges= true;
private boolean _filterChainsCached=true;
private transient WebApplicationContext _webApplicationContext;
protected transient Object _requestListeners;
protected transient Object _requestAttributeListeners;
protected transient Object _sessionListeners;
protected transient Object _contextAttributeListeners;
protected transient FilterHolder jsr154FilterHolder;
protected transient JSR154Filter jsr154Filter;
protected transient HashMap _chainCache[];
protected transient HashMap _namedChainCache[];
/* ------------------------------------------------------------ */
public boolean isAcceptRanges()
{
return _acceptRanges;
}
/* ------------------------------------------------------------ */
/** Set if the handler accepts range requests.
* Default is false;
* @param ar True if the handler should accept ranges
*/
public void setAcceptRanges(boolean ar)
{
_acceptRanges= ar;
}
/* ------------------------------------------------------------ */
/**
* @return Returns the jsr154Filter.
*/
public JSR154Filter getJsr154Filter()
{
return jsr154Filter;
}
/* ------------------------------------------------------------ */
public FilterHolder defineFilter(String name, String className)
{
FilterHolder holder= newFilterHolder(name,className);
addFilterHolder(holder);
return holder;
}
/* ------------------------------------------------------------ */
protected FilterHolder newFilterHolder(String name, String className)
{
return new FilterHolder(this, name, className);
}
/* ------------------------------------------------------------ */
public void addFilterHolder(FilterHolder holder)
{
_filterMap.put(holder.getName(), holder);
_filters.add(holder);
addComponent(holder);
}
/* ------------------------------------------------------------ */
public FilterHolder getFilter(String name)
{
return (FilterHolder)_filterMap.get(name);
}
/* ------------------------------------------------------------ */
/** Add a mapping from a pathSpec to a Filter.
* @param pathSpec The path specification
* @param filterName The name of the filter (must already be added or defined)
* @param dispatches An integer formed by the logical OR of FilterHolder.__REQUEST,
* FilterHolder.__FORWARD,FilterHolder.__INCLUDE and/or FilterHolder.__ERROR.
* @return The holder of the filter instance.
*/
public FilterHolder addFilterPathMapping(String pathSpec, String filterName, int dispatches)
{
FilterHolder holder = (FilterHolder)_filterMap.get(filterName);
if (holder==null)
throw new IllegalArgumentException("unknown filter: "+filterName);
FilterMapping mapping = new FilterMapping(pathSpec,holder,dispatches);
_pathFilters.add(mapping);
return holder;
}
/* ------------------------------------------------------------ */
/**
* Add a servlet filter mapping
* @param servletName The name of the servlet to be filtered.
* @param filterName The name of the filter.
* @param dispatches An integer formed by the logical OR of FilterHolder.__REQUEST,
* FilterHolder.__FORWARD,FilterHolder.__INCLUDE and/or FilterHolder.__ERROR.
* @return The holder of the filter instance.
*/
public FilterHolder addFilterServletMapping(String servletName, String filterName, int dispatches)
{
FilterHolder holder= (FilterHolder)_filterMap.get(filterName);
if (holder == null)
throw new IllegalArgumentException("Unknown filter :" + filterName);
_servletFilterMap.add(servletName, new FilterMapping(null,holder,dispatches));
return holder;
}
/* ------------------------------------------------------------ */
public List getFilters()
{
return _filters;
}
/* ------------------------------------------------------------ */
public synchronized void addEventListener(EventListener listener)
throws IllegalArgumentException
{
if ((listener instanceof HttpSessionActivationListener)
|| (listener instanceof HttpSessionAttributeListener)
|| (listener instanceof HttpSessionBindingListener)
|| (listener instanceof HttpSessionListener))
{
if (_sessionManager != null)
_sessionManager.addEventListener(listener);
_sessionListeners= LazyList.add(_sessionListeners, listener);
}
if (listener instanceof ServletRequestListener)
{
_requestListeners= LazyList.add(_requestListeners, listener);
}
if (listener instanceof ServletRequestAttributeListener)
{
_requestAttributeListeners= LazyList.add(_requestAttributeListeners, listener);
}
if (listener instanceof ServletContextAttributeListener)
{
_contextAttributeListeners= LazyList.add(_contextAttributeListeners, listener);
}
super.addEventListener(listener);
}
/* ------------------------------------------------------------ */
public synchronized void removeEventListener(EventListener listener)
{
if (_sessionManager != null)
_sessionManager.removeEventListener(listener);
_sessionListeners= LazyList.remove(_sessionListeners, listener);
_requestListeners= LazyList.remove(_requestListeners, listener);
_requestAttributeListeners= LazyList.remove(_requestAttributeListeners, listener);
_contextAttributeListeners= LazyList.remove(_contextAttributeListeners, listener);
super.removeEventListener(listener);
}
/* ------------------------------------------------------------ */
public void setSessionManager(SessionManager sm)
{
if (isStarted())
throw new IllegalStateException("Started");
SessionManager old= getSessionManager();
if (getHttpContext() != null)
{
// recover config and remove listeners from old session manager
if (old != null && old != sm)
{
if (_sessionListeners != null)
{
for (Iterator i= LazyList.iterator(_sessionListeners); i.hasNext();)
{
EventListener listener= (EventListener)i.next();
_sessionManager.removeEventListener(listener);
}
}
}
// Set listeners and config on new listener.
if (sm != null && old != sm)
{
if (_sessionListeners != null)
{
for (Iterator i= LazyList.iterator(_sessionListeners); i.hasNext();)
{
EventListener listener= (EventListener)i.next();
sm.addEventListener(listener);
}
}
}
}
super.setSessionManager(sm);
}
/* ----------------------------------------------------------------- */
protected synchronized void doStart() throws Exception
{
// Start Servlet Handler
super.doStart();
if (log.isDebugEnabled())
log.debug("Path Filters: " + _pathFilters);
if (log.isDebugEnabled())
log.debug("Servlet Filters: " + _servletFilterMap);
if (getHttpContext() instanceof WebApplicationContext)
_webApplicationContext= (WebApplicationContext)getHttpContext();
if (_filterChainsCached)
{
_chainCache = getChainCache();
_namedChainCache = getChainCache();
}
}
/* ----------------------------------------------------------------- */
private HashMap[] getChainCache() {
HashMap[] _chainCache=new HashMap[Dispatcher.__ERROR+1];
_chainCache[Dispatcher.__REQUEST]=new HashMap();
_chainCache[Dispatcher.__FORWARD]=new HashMap();
_chainCache[Dispatcher.__INCLUDE]=new HashMap();
_chainCache[Dispatcher.__ERROR]=new HashMap();
return _chainCache;
}
/* ------------------------------------------------------------ */
public void initializeServlets() throws Exception
{
// initialize Filters
MultiException mex= new MultiException();
Iterator iter= _filters.iterator();
while (iter.hasNext())
{
FilterHolder holder= (FilterHolder)iter.next();
try
{
holder.start();
}
catch (Exception e)
{
mex.add(e);
}
}
// initialize Servlets
try
{
super.initializeServlets();
}
catch (Exception e)
{
mex.add(e);
}
jsr154FilterHolder=getFilter("jsr154");
if (jsr154FilterHolder!=null)
jsr154Filter= (JSR154Filter)jsr154FilterHolder.getFilter();
log.debug("jsr154filter="+jsr154Filter);
if (LazyList.size(_requestAttributeListeners) > 0 || LazyList.size(_requestListeners) > 0)
{
if (jsr154Filter==null)
log.warn("Filter jsr154 not defined for RequestAttributeListeners");
else
{
jsr154Filter.setRequestAttributeListeners(_requestAttributeListeners);
jsr154Filter.setRequestListeners(_requestListeners);
}
}
mex.ifExceptionThrow();
}
/* ------------------------------------------------------------ */
protected synchronized void doStop() throws Exception
{
try
{
// Stop servlets
super.doStop();
// Stop filters
for (int i= _filters.size(); i-- > 0;)
{
FilterHolder holder= (FilterHolder)_filters.get(i);
holder.stop();
}
}
finally
{
_webApplicationContext= null;
_sessionListeners= null;
_requestListeners= null;
_requestAttributeListeners= null;
_contextAttributeListeners= null;
}
}
/* ------------------------------------------------------------ */
public String getErrorPage(int status, ServletHttpRequest request)
{
String error_page= null;
Class exClass= (Class)request.getAttribute(ServletHandler.__J_S_ERROR_EXCEPTION_TYPE);
if (ServletException.class.equals(exClass))
{
error_page= _webApplicationContext.getErrorPage(exClass.getName());
if (error_page == null)
{
Throwable th= (Throwable)request.getAttribute(ServletHandler.__J_S_ERROR_EXCEPTION);
while (th instanceof ServletException)
th= ((ServletException)th).getRootCause();
if (th != null)
exClass= th.getClass();
}
}
if (error_page == null && exClass != null)
{
while (error_page == null && exClass != null && _webApplicationContext != null)
{
error_page= _webApplicationContext.getErrorPage(exClass.getName());
exClass= exClass.getSuperclass();
}
if (error_page == null)
{}
}
if (error_page == null && _webApplicationContext != null)
error_page= _webApplicationContext.getErrorPage(TypeUtil.toString(status));
return error_page;
}
/* ------------------------------------------------------------ */
protected void dispatch(String pathInContext,
HttpServletRequest request,
HttpServletResponse response,
ServletHolder servletHolder,
int type)
throws ServletException, UnavailableException, IOException
{
if (type == Dispatcher.__REQUEST)
{
// This is NOT a dispatched request (it it is an initial request)
ServletHttpRequest servletHttpRequest= (ServletHttpRequest)request;
ServletHttpResponse servletHttpResponse= (ServletHttpResponse)response;
// protect web-inf and meta-inf
if (StringUtil.startsWithIgnoreCase(pathInContext, "/web-inf") || StringUtil.startsWithIgnoreCase(pathInContext, "/meta-inf"))
{
response.sendError(HttpResponse.__404_Not_Found);
return;
}
// Security Check
if (!getHttpContext().checkSecurityConstraints(
pathInContext,
servletHttpRequest.getHttpRequest(),
servletHttpResponse.getHttpResponse()))
return;
}
else
{
// This is a dispatched request.
// Handle dispatch to j_security_check
HttpContext context= getHttpContext();
if (context != null
&& context instanceof ServletHttpContext
&& pathInContext != null
&& pathInContext.endsWith(FormAuthenticator.__J_SECURITY_CHECK))
{
ServletHttpRequest servletHttpRequest=
(ServletHttpRequest)context.getHttpConnection().getRequest().getWrapper();
ServletHttpResponse servletHttpResponse= servletHttpRequest.getServletHttpResponse();
ServletHttpContext servletContext= (ServletHttpContext)context;
if (!servletContext.jSecurityCheck(pathInContext,servletHttpRequest.getHttpRequest(),servletHttpResponse.getHttpResponse()))
return;
}
}
// Build and/or cache filter chain
FilterChain chain=null;
if (pathInContext != null) {
chain = getChainForPath(type, pathInContext, servletHolder);
} else {
chain = getChainForName(type, servletHolder);
}
if (log.isDebugEnabled()) log.debug("chain="+chain);
// Do the handling thang
if (chain!=null)
chain.doFilter(request, response);
else if (servletHolder != null)
servletHolder.handle(request, response);
else // Not found
notFound(request, response);
}
/* ------------------------------------------------------------ */
private FilterChain getChainForName(int requestType, ServletHolder servletHolder) {
if (servletHolder == null) {
throw new IllegalStateException("Named dispatch must be to an explicitly named servlet");
}
if (_filterChainsCached)
{
synchronized(this)
{
if (_namedChainCache[requestType].containsKey(servletHolder.getName()))
return (FilterChain)_namedChainCache[requestType].get(servletHolder.getName());
}
}
// Build list of filters
Object filters= null;
if (jsr154Filter!=null)
{
// Slight hack for Named servlets
// TODO query JSR how to apply filter to all dispatches
filters=LazyList.add(filters,jsr154FilterHolder);
}
// Servlet filters
if (_servletFilterMap.size() > 0)
{
Object o= _servletFilterMap.get(servletHolder.getName());
for (int i=0; i<LazyList.size(o);i++)
{
FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
if (mapping.appliesTo(null,requestType))
filters=LazyList.add(filters,mapping.getHolder());
}
}
FilterChain chain = null;
if (_filterChainsCached)
{
synchronized(this)
{
if (LazyList.size(filters) > 0)
chain= new CachedChain(filters, servletHolder);
_namedChainCache[requestType].put(servletHolder.getName(),chain);
}
}
else if (LazyList.size(filters) > 0)
chain = new Chain(filters, servletHolder);
return chain;
}
/* ------------------------------------------------------------ */
private FilterChain getChainForPath(int requestType, String pathInContext, ServletHolder servletHolder)
{
if (_filterChainsCached)
{
synchronized(this)
{
if(_chainCache[requestType].containsKey(pathInContext))
return (FilterChain)_chainCache[requestType].get(pathInContext);
}
}
// Build list of filters
Object filters= null;
// Path filters
for (int i= 0; i < _pathFilters.size(); i++)
{
FilterMapping mapping = (FilterMapping)_pathFilters.get(i);
if (mapping.appliesTo(pathInContext, requestType))
filters= LazyList.add(filters, mapping.getHolder());
}
// Servlet filters
if (servletHolder != null && _servletFilterMap.size() > 0)
{
Object o= _servletFilterMap.get(servletHolder.getName());
for (int i=0; i<LazyList.size(o);i++)
{
FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
if (mapping.appliesTo(null,requestType))
filters=LazyList.add(filters,mapping.getHolder());
}
}
FilterChain chain = null;
if (_filterChainsCached)
{
synchronized(this)
{
if (LazyList.size(filters) > 0)
chain= new CachedChain(filters, servletHolder);
_chainCache[requestType].put(pathInContext,chain);
}
}
else if (LazyList.size(filters) > 0)
chain = new Chain(filters, servletHolder);
return chain;
}
/* ------------------------------------------------------------ */
public synchronized void setContextAttribute(String name, Object value)
{
Object old= super.getContextAttribute(name);
super.setContextAttribute(name, value);
if (_contextAttributeListeners != null)
{
ServletContextAttributeEvent event=
new ServletContextAttributeEvent(getServletContext(), name, old != null ? old : value);
for (int i= 0; i < LazyList.size(_contextAttributeListeners); i++)
{
ServletContextAttributeListener l=
(ServletContextAttributeListener)LazyList.get(_contextAttributeListeners, i);
if (old == null)
l.attributeAdded(event);
else
if (value == null)
l.attributeRemoved(event);
else
l.attributeReplaced(event);
}
}
}
/* ------------------------------------------------------------ */
public synchronized void removeContextAttribute(String name)
{
Object old= super.getContextAttribute(name);
super.removeContextAttribute(name);
if (old != null && _contextAttributeListeners != null)
{
ServletContextAttributeEvent event= new ServletContextAttributeEvent(getServletContext(), name, old);
for (int i= 0; i < LazyList.size(_contextAttributeListeners); i++)
{
ServletContextAttributeListener l=
(ServletContextAttributeListener)LazyList.get(_contextAttributeListeners, i);
l.attributeRemoved(event);
}
}
}
/* ------------------------------------------------------------ */
/**
* @return Returns the filterChainsCached.
*/
public boolean isFilterChainsCached()
{
return _filterChainsCached;
}
/* ------------------------------------------------------------ */
/** Cache filter chains.
* If true, filter chains are cached by the URI path within the
* context. Caching should not be used if the webapp encodes
* information in URLs.
* @param filterChainsCached The filterChainsCached to set.
*/
public void setFilterChainsCached(boolean filterChainsCached)
{
_filterChainsCached = filterChainsCached;
}
/* ------------------------------------------------------------ */
/**
* @see net.lightbody.bmp.proxy.jetty.util.Container#addComponent(java.lang.Object)
*/
protected void addComponent(Object o)
{
if (_filterChainsCached && isStarted())
{
synchronized(this)
{
for (int i=0;i<_chainCache.length;i++)
if (_chainCache[i]!=null)
_chainCache[i].clear();
}
}
super.addComponent(o);
}
/* ------------------------------------------------------------ */
/**
* @see net.lightbody.bmp.proxy.jetty.util.Container#removeComponent(java.lang.Object)
*/
protected void removeComponent(Object o)
{
if (_filterChainsCached && isStarted())
{
synchronized(this)
{
for (int i=0;i<_chainCache.length;i++)
if (_chainCache[i]!=null)
_chainCache[i].clear();
}
}
super.removeComponent(o);
}
/* ----------------------------------------------------------------- */
public void destroy()
{
Iterator iter = _filterMap.values().iterator();
while (iter.hasNext())
{
Object sh=iter.next();
iter.remove();
removeComponent(sh);
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private static class FilterMapping
{
private String _pathSpec;
private FilterHolder _holder;
private int _dispatches;
/* ------------------------------------------------------------ */
FilterMapping(String pathSpec,FilterHolder holder,int dispatches)
{
_pathSpec=pathSpec;
_holder=holder;
_dispatches=dispatches;
}
/* ------------------------------------------------------------ */
FilterHolder getHolder()
{
return _holder;
}
/* ------------------------------------------------------------ */
/** Check if this filter applies to a path.
* @param path The path to check.
* @param type The type of request: __REQUEST,__FORWARD,__INCLUDE or __ERROR.
* @return True if this filter applies
*/
boolean appliesTo(String path, int type)
{
boolean b=((_dispatches&type)!=0 || (_dispatches==0 && type==Dispatcher.__REQUEST)) && (_pathSpec==null || PathMap.match(_pathSpec, path,true));
return b;
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class Chain implements FilterChain
{
int _filter= 0;
Object _filters;
ServletHolder _servletHolder;
/* ------------------------------------------------------------ */
Chain(Object filters, ServletHolder servletHolder)
{
_filters= filters;
_servletHolder= servletHolder;
}
/* ------------------------------------------------------------ */
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException
{
if (log.isTraceEnabled())
log.trace("doFilter " + _filter);
// pass to next filter
if (_filter < LazyList.size(_filters))
{
FilterHolder holder= (FilterHolder)LazyList.get(_filters, _filter++);
if (log.isTraceEnabled())
log.trace("call filter " + holder);
Filter filter= holder.getFilter();
filter.doFilter(request, response, this);
return;
}
// Call servlet
if (_servletHolder != null)
{
if (log.isTraceEnabled())
log.trace("call servlet " + _servletHolder);
_servletHolder.handle(request, response);
}
else // Not found
notFound((HttpServletRequest)request, (HttpServletResponse)response);
}
public String toString()
{
StringBuffer b = new StringBuffer();
for (int i=0; i<LazyList.size(_filters);i++)
{
b.append(LazyList.get(_filters, i).toString());
b.append("->");
}
b.append(_servletHolder);
return b.toString();
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class CachedChain implements FilterChain
{
FilterHolder _filterHolder;
ServletHolder _servletHolder;
CachedChain _next;
/* ------------------------------------------------------------ */
CachedChain(Object filters, ServletHolder servletHolder)
{
if (LazyList.size(filters)>0)
{
_filterHolder=(FilterHolder)LazyList.get(filters, 0);
filters=LazyList.remove(filters,0);
_next=new CachedChain(filters,servletHolder);
}
else
_servletHolder=servletHolder;
}
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException
{
// pass to next filter
if (_filterHolder!=null)
{
if (log.isTraceEnabled())
log.trace("call filter " + _filterHolder);
Filter filter= _filterHolder.getFilter();
filter.doFilter(request, response, _next);
return;
}
// Call servlet
if (_servletHolder != null)
{
if (log.isTraceEnabled())
log.trace("call servlet " + _servletHolder);
_servletHolder.handle(request, response);
}
else // Not found
notFound((HttpServletRequest)request, (HttpServletResponse)response);
}
public String toString()
{
if (_filterHolder!=null)
return _filterHolder+"->"+_next.toString();
if (_servletHolder!=null)
return _servletHolder.toString();
return "null";
}
}
}