|
What this is
Other links
The source code
/*
* Copyright 1999-2004 The Apache Sofware Foundation.
*
* 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.apache.tomcat.modules.mappers;
import java.io.File;
import java.util.Enumeration;
import java.util.Hashtable;
import org.apache.tomcat.core.BaseInterceptor;
import org.apache.tomcat.core.Container;
import org.apache.tomcat.core.Context;
import org.apache.tomcat.core.ContextManager;
import org.apache.tomcat.core.Request;
import org.apache.tomcat.core.TomcatException;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.collections.SimpleHashtable;
import org.apache.tomcat.util.io.FileUtil;
/**
* This class will set up the data structures used by a simple patern matching
* algorithm and use it to extract the path components from the request URI.
*
* This particular implementation does the following:
* - extract the information that is relevant to matching from the Request
* object. The current implementation deals with the Host header and the
* request URI.
* - Use an external mapper to find the best match.
* - Adjust the request paths
*
* SimpleMapper1 will set 2 context notes - "map.extensions" is a
* SimpleHashtable containing the extension mappings, and "tomcat.map.default"
* for the default map, if defined explicitely.
*
* It will also maintain a global mapping structure for all prefix mappings,
* including contexts.
*
* The execution time is proportional with the number of hosts, number of
* context, number of mappings and with the length of the request.
*
*/
public class SimpleMapper1 extends BaseInterceptor {
ContextManager cm;
PrefixMapper map;
// We store the extension maps as per/context notes.
int ctExtMapNote=-1;
int defaultMapNOTE=-1;
// Property for the PrefixMapper - cache the mapping results
boolean mapCacheEnabled=false;
public SimpleMapper1() {
map=new PrefixMapper();
ignoreCase= (File.separatorChar == '\\');
map.setIgnoreCase( ignoreCase );
}
/* -------------------- Support functions -------------------- */
/** Allow the mapper to cache mapping results - resulting in a
* faster match for frequent requests. ( treat this as experimental)
*/
public void setMapCache( boolean v ) {
mapCacheEnabled = v;
map.setMapCache( v );
}
// -------------------- Ingore case --------------------
boolean ignoreCase=false;
/** Use case insensitive match, for windows and
similar platforms
*/
public void setIgnoreCase( boolean b ) {
ignoreCase=b;
map.setIgnoreCase( b );
}
/* -------------------- Initialization -------------------- */
/** Set the context manager. To keep it simple we don't support
* dynamic add/remove for this interceptor.
*/
public void engineInit( ContextManager cm )
throws TomcatException
{
this.cm=cm;
// set-up a per/container note for maps
ctExtMapNote = cm.getNoteId( ContextManager.CONTAINER_NOTE,
"map.extension");
defaultMapNOTE=cm.getNoteId( ContextManager.CONTAINER_NOTE,
"tomcat.map.default");
}
/** Called when a context is added.
*/
public void addContext( ContextManager cm, Context ctx )
throws TomcatException
{
map.addMapping( ctx.getHost(), ctx.getPath(), ctx.getContainer());
map.addMappings( ctx.getHostAliases(), ctx.getPath(), ctx.getContainer());
}
/** Called when a context is removed from a CM - we must ask the mapper to
remove all the maps related with this context
*/
public void removeContext( ContextManager cm, Context ctx )
throws TomcatException
{
if(debug>0) log( "Removed from maps ");
map.removeAllMappings( ctx.getHost(), ctx);
Enumeration vhostAliases=ctx.getHostAliases();
while( vhostAliases.hasMoreElements() )
map.removeAllMappings( (String)vhostAliases.nextElement(), ctx );
// extension mappings are local to ctx, no need to do something
// about that
}
/**
* Associate URL pattern to a set of propreties.
*
* Note that the order of resolution to handle a request is:
*
* exact mapped servlet (eg /catalog)
* prefix mapped servlets (eg /foo/bar/*)
* extension mapped servlets (eg *jsp)
* default servlet
*
*/
public void addContainer( Container ct )
throws TomcatException
{
Context ctx=ct.getContext();
String vhost=ctx.getHost();
Enumeration vhostAliases=ctx.getHostAliases();
String path=ct.getPath();
String ctxP=ctx.getPath();
// Special containers ( the default is url-mapping ).
if( ct.isSpecial() ) return;
if( ct.getNote( "type" ) != null ) return;
if(ct.getRoles() != null || ct.getTransport() != null ) {
// it was only a security map, no handler defined
return;
}
switch( ct.getMapType() ) {
case Container.PREFIX_MAP:
// cut /* ( no need to do a string concat for every match )
// workaround for frequent bug in web.xml ( backw. compat )
if( ! path.startsWith( "/" ) ) {
log("WARNING: Correcting error in web.xml for context \"" + ctxP +
"\". Mapping for path \"" + path + "\" is missing a leading '/'.");
path="/" + path;
}
String prefixPath=ctxP + path.substring( 0, path.length()-2 );
map.addMapping( vhost, prefixPath, ct);
map.addMappings( vhostAliases, prefixPath, ct);
if( debug>0 )
log("SM: prefix map " + vhost + ":" + ctxP +
path + " -> " + ct + " " );
break;
case Container.DEFAULT_MAP:
// This will be used if no other map match.
// AVOID USING IT - STATIC FILES SHOULD BE HANDLED BY
// APACHE ( or tomcat )
Container defMapC=ct.getContext().getContainer();
defMapC.setNote( defaultMapNOTE, ct );
if( debug>0 )
log("SM: default map " + vhost + ":" + ctxP +
path + " -> " + ct + " " );
break;
case Container.EXTENSION_MAP:
// Add it per/defaultContainer - as spec require ( it may also be
// possible to support type maps per/Container, i.e. /foo/*.jsp -
// but that would require changes in the spec.
Context mapCtx=ct.getContext();
Container defC=mapCtx.getContainer();
SimpleHashtable eM=(SimpleHashtable) defC.getNote( ctExtMapNote );
if( eM==null ) {
eM=new SimpleHashtable();
defC.setNote( ctExtMapNote, eM );
}
// add it to the Container local maps
if( ignoreCase )
eM.put( path.substring( 1 ).toLowerCase() , ct );
else
eM.put( path.substring( 1 ), ct );
if(debug>0)
log( "SM: extension map " + ctxP + "/" +
path + " " + ct + " " );
break;
case Container.PATH_MAP:
// workaround for frequent bug in web.xml
if( ! path.startsWith( "/" ) ) {
log("WARNING: Correcting error in web.xml for context \"" + ctxP +
"\". Mapping for path \"" + path + "\" is missing a leading '/'.");
path="/" + path;
}
map.addExactMapping( vhost, ctxP + path, ct);
map.addExactMappings( vhostAliases, ctxP + path, ct);
if( debug>0 )
log("SM: exact map " + vhost + ":" + ctxP +
path + " -> " + ct + " " );
break;
}
}
// XXX not implemented - will deal with that after everything else works.
// Remove context will still work
public void removeContainer( Container ct )
throws TomcatException
{
Context ctx=ct.getContext();
String mapping=ct.getPath();
String ctxP=ctx.getPath();
mapping = mapping.trim();
if(debug>0) log( "Remove mapping " + mapping );
}
/* -------------------- Request mapping -------------------- */
/** First step of request processing is finding the Context.
*/
public int contextMap( Request req ) {
MessageBytes pathMB = req.requestURI();
try {
// String host=null;
MessageBytes hostMB=req.serverName();
// host=req.serverName().toString();
if(debug>0) cm.log("Host = " + hostMB.toString());
Container container =(Container)map.
getLongestPrefixMatch( hostMB, pathMB);
if( container == null ) {
// It is too easy to configure Tomcat w/o a default context
// Add this back in when we do better checking
//throw new RuntimeException( "Assertion failed: " +
// "container==null");
cm.log("Assertion failed: " +
"container==null (no Default Context?)");
return 404;
}
if(debug>0)
cm.log("SM: Prefix match " + pathMB.toString() + " -> " +
container.getPath() + " " + container.getHandler() +
" " + container.getRoles());
// Once - adjust for prefix and context path
// If cached - we don't need to do it again ( since it is the
// final Container,
// either prefix or extension )
fixRequestPaths( pathMB.toString() /*XXX*/, req, container );
// if it's default container - try extension match
// if ( container.getMapType() == Container.DEFAULT_MAP ) {
if ( container.getHandler() == null ) {
Container extC = matchExtension( req );
if( extC != null ) {
// change the handler
if( extC.getHandler() != null ) {
fixRequestPaths( pathMB.toString(), req, extC );
container=extC;
}
if( debug > 0 )
log("SM: Found extension mapping " +
extC.getHandler());
// change security roles
}
}
// Default map - if present
if( container.getHandler() == null ) {
Container ctxDef=req.getContext().getContainer();
Container defC=(Container)ctxDef.getNote( defaultMapNOTE );
if( defC != null && defC.getHandler() !=null ) {
fixRequestPaths( pathMB.toString(), req, defC );
if( debug > 0 )
log("SM: Found default mapping " +
defC.getHandler() + " " + defC.getPath() +
" " + defC.getMapType());
}
}
if(debug>0) log("SM: After mapping " + req + " " +
req.getHandler());
} catch(Exception ex ) {
log("Mapping " + req, ex);
return 500;
}
return 0;
}
/** No need to do that - we finished everything in the first step.
*
*/
// public int requestMap(Request req) {
// No op. All mapping is done in the first step - it's better because
// the algorithm is more efficient. The only case where those 2 are
// not called togheter is in getContext( "path" ).
//
// We can split it again later if that creates problems - but right
// now it's important to have a clear algorithm. Note that requestMap
// is _allways_ called after contextMap ( it was asserted in all
// implementations).
// return 0;
// }
// -------------------- Implementation methods --------------------
/** Will match an extension - note that Servlet API use special rules
* for mapping extension, different from what is used in existing web
* servers. That makes this code very easy ( only need to deal with
* the last component of the name ), but it's hard to integrate and you
* have no way to use pathInfo.
*/
Container matchExtension( Request req ) {
Context ctx=req.getContext();
String ctxP=ctx.getPath();
// we haven't matched any prefix,
String path = req.servletPath().toString();
if( path == null ) return null;
String extension=FileUtil.getExtension( path );
if( extension == null ) return null;
if(debug>0)
cm.log("SM: Extension match " + ctxP + " " +
path + " " + extension );
// Find extension maps for the context
SimpleHashtable extM=(SimpleHashtable)ctx.
getContainer().getNote( ctExtMapNote );
if( extM==null ) return null;
// Find the container associated with that extension
if( ignoreCase ) extension=extension.toLowerCase();
Container container= (Container)extM.get(extension);
if (container == null)
return null;
// This container doesn't change the mappings - it only
// has "other" properties ( in the current code security
// constraints
if( container.getHandler() == null) return container;
return container;
}
/** Adjust the paths in request after matching a container
*/
void fixRequestPaths( String path, Request req, Container container )
throws Exception
{
// Set servlet path and path info
// Found a match !
// Adjust paths based on the match
String s=container.getPath();
String ctxP=container.getContext().getPath();
int sLen=s.length();
int pathLen=path.length();
int ctxPLen=ctxP.length();
String pathI=null;
// Perform URL decoding only if necessary
switch( container.getMapType()) {
case Container.PREFIX_MAP:
s=s.substring( 0, sLen -2 );
pathI= path.substring( ctxPLen + sLen - 2, pathLen);
if( debug>0 ) log( "Adjust for prefix map " + s + " " + pathI );
break;
case Container.DEFAULT_MAP:
s = path.substring( ctxPLen );
pathI = null;
if( debug>0 ) log( "Default map " + s + " " + pathI );
break;
case Container.PATH_MAP:
pathI= null;
// For exact matching - can't have path info ( or it's
// a prefix map )
//path.substring( ctxPLen + sLen , pathLen);
if( debug>0 ) log( "Adjust for path map " +
s + " " + pathI + container.getPath());
break; // keep the path
case Container.EXTENSION_MAP:
/* adjust paths */
s= path.substring( ctxPLen );
pathI=null;
}
req.servletPath().setString( s );
if( ! "".equals(pathI))
req.pathInfo().setString(pathI);
Context ctx=container.getContext();
req.setContext(ctx);
req.setHandler( container.getHandler() );
req.setContainer( container );
}
}
/** Prefix and exact mapping algorithm.
*XXX finish factoring out the creation of the map ( right now direct field access is
* used, since the code was just cut out from SimpleMapper).
* XXX make sure the code is useable as a general path mapper - or at least a bridge
* can be created between SimpleMapper and a patern matcher like the one in XPath
*
* @author costin@costin.dnt.ro
*/
class PrefixMapper {
private static int debug=1;
// host -> PrefixMapper for virtual hosts
// hosts are stored in lower case ( the "common" case )
Hashtable vhostMaps=new Hashtable();
// host -> PrefixMapper for virtual hosts with leading '*'
// host key has '*' removed
// Can't use SimpleHashtable, more than a thread will use keys()
Hashtable vhostMapsWC=new Hashtable();
boolean hasWCMap=false; // to avoid getting the Enumeration
Hashtable prefixMappedServlets;
Hashtable exactMappedServlets;
// Cache the most recent mappings
// Disabled by default ( since we haven't implemented
// capacity and remove ).
SimpleHashtable mapCache;
// By using TreeMap instead of SimpleMap you go from 143 to 161 RPS
// ( at least on my machine )
// Interesting - even if SimpleHashtable is faster than Hashtable
// most of the time, the average is very close for both - it seems
// that while the synchronization in Hashtable is locking, GC have
// a chance to work, while in SimpleHashtable case GC creates big
// peeks. That will go away with more reuse, so we should use SH.
// An alternative to explore after everything works is to use specialized
// mappers ( extending this one for example ) using 1.2 collections
// TreeMap mapCache;
boolean mapCacheEnabled=false;
boolean ignoreCase=false;
public PrefixMapper() {
prefixMappedServlets=new Hashtable();
exactMappedServlets=new Hashtable();
mapCache=new SimpleHashtable();
}
public void setMapCache( boolean v ) {
mapCacheEnabled=v;
}
public void setIgnoreCase( boolean b ) {
ignoreCase=b;
}
/** Remove all mappings matching path
*/
public void removeAllMappings( String host, Context ctx ) {
PrefixMapper vmap=this;
if( host!=null ) {
host=host.toLowerCase();
if( host.startsWith( "*" ) )
vmap=(PrefixMapper)vhostMapsWC.get(host.substring( 1 ));
else
vmap=(PrefixMapper)vhostMaps.get(host);
}
// remove all paths starting with path
Enumeration en=vmap.prefixMappedServlets.keys();
while( en.hasMoreElements() ) {
String s=(String)en.nextElement();
Container ct=(Container)vmap.prefixMappedServlets.get( s );
if( ct!=null && ct.getContext() == ctx ) {
if(debug > 0 )
ctx.log( "Remove mapping " + s );
vmap.prefixMappedServlets.remove( s );
}
}
en=vmap.exactMappedServlets.keys();
while( en.hasMoreElements() ) {
String s=(String)en.nextElement();
Container ct=(Container)vmap.exactMappedServlets.get( s );
if( ct.getContext() == ctx ) {
if(debug > 0 )
ctx.log( "Remove mapping " + s );
vmap.exactMappedServlets.remove( s );
}
}
// reset the cache
mapCache=new SimpleHashtable();
}
/**
*/
void addMapping( String path, Object target ) {
prefixMappedServlets.put( path, target);
}
/**
*/
void addExactMapping( String path, Object target ) {
exactMappedServlets.put( path, target);
}
/**
*/
public void addMapping( String host, String path, Object target ) {
if( host == null ) {
if( ignoreCase )
prefixMappedServlets.put( path.toLowerCase(), target);
else
prefixMappedServlets.put( path, target);
} else {
host=host.toLowerCase();
Hashtable maps;
if( host.startsWith( "*" ) ) {
maps=vhostMapsWC;
host=host.substring( 1 );
hasWCMap=true;
} else {
maps=vhostMaps;
}
PrefixMapper vmap=(PrefixMapper)maps.get( host );
if( vmap == null ) {
vmap=new PrefixMapper();
vmap.setIgnoreCase( ignoreCase );
maps.put( host, vmap );
vmap.setMapCache( mapCacheEnabled );
}
if( ignoreCase )
vmap.addMapping( path.toLowerCase(), target );
else
vmap.addMapping( path, target );
}
}
/**
*/
public void addMappings( Enumeration hostAliases, String path, Object target ) {
while ( hostAliases.hasMoreElements() )
addMapping( (String)hostAliases.nextElement(), path, target );
}
/**
*/
public void addExactMapping( String host, String path, Object target ) {
if( host==null ) {
if ( ignoreCase )
exactMappedServlets.put( path.toLowerCase(), target);
else
exactMappedServlets.put( path, target);
} else {
host=host.toLowerCase();
Hashtable maps;
if( host.startsWith( "*" ) ) {
maps = vhostMapsWC;
host=host.substring( 1 );
hasWCMap=true;
} else {
maps = vhostMaps;
}
PrefixMapper vmap=(PrefixMapper)maps.get( host );
if( vmap == null ) {
vmap=new PrefixMapper();
maps.put( host, vmap );
}
if( ignoreCase )
vmap.addExactMapping( path.toLowerCase(), target );
else
vmap.addExactMapping( path, target );
}
}
/**
*/
public void addExactMappings( Enumeration hostAliases, String path, Object target ) {
while ( hostAliases.hasMoreElements() )
addExactMapping( (String)hostAliases.nextElement(), path, target );
}
// -------------------- Implementation --------------------
/** Match a prefix rule - /foo/bar/index.html/abc
*/
public Object getLongestPrefixMatch( MessageBytes hostMB,
MessageBytes pathMB )
{
// XXX fixme
String host=hostMB.toString();
String path=pathMB.toString();
Object container = null;
PrefixMapper myMap=null;
if( host!=null ) {
myMap=(PrefixMapper)vhostMaps.get( host );
if( myMap==null ) {
myMap=(PrefixMapper)vhostMaps.get( host.toLowerCase() );
}
}
if( myMap==null && hasWCMap ) {
// Check host against virtual hosts that began with '*'
Enumeration vhosts = vhostMapsWC.keys();
while(vhosts.hasMoreElements()) {
String vhostName = (String)vhosts.nextElement();
if(host.endsWith(vhostName)) {
myMap = (PrefixMapper)vhostMapsWC.get(vhostName);
break;
}
}
}
if( myMap==null ) myMap = this; // default server
if( ignoreCase ) path=path.toLowerCase();
container=myMap.exactMappedServlets.get( path );
if( container != null ) return container; // and we're done!
/** Cache for request results - exploit the fact that few
* request are more "popular" than other.
* Disable it if you want to benchmark the mapper !!!
*/
if( myMap.mapCacheEnabled ) {
container=myMap.mapCache.get(path);
if( container!=null ) return container;
}
String s = path;
while (s.length() >= 0) {
//if(debug>8) context.log( "Prefix: " + s );
container = myMap.prefixMappedServlets.get(s);
if (container == null) {
// if empty string didn't map, time to give up
if ( s.length() == 0 )
break;
s=FileUtil.removeLast( s );
} else {
if( myMap.mapCacheEnabled ) {
// XXX implement LRU or another replacement algorithm
myMap.mapCache.put( path, container );
}
return container;
}
}
return container;
}
}
|
| ... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.