Author: markt
Date: Fri Nov 19 17:18:04 2010
New Revision: 1036949

URL: http://svn.apache.org/viewvc?rev=1036949&view=rev
Log:
Add the final component of parallel deployment

Modified:
    tomcat/trunk/java/org/apache/catalina/connector/CoyoteAdapter.java
    tomcat/trunk/java/org/apache/catalina/connector/MapperListener.java
    tomcat/trunk/java/org/apache/tomcat/util/http/mapper/Mapper.java
    tomcat/trunk/java/org/apache/tomcat/util/http/mapper/MappingData.java
    tomcat/trunk/test/org/apache/tomcat/util/http/mapper/TestMapper.java
    tomcat/trunk/webapps/docs/changelog.xml
    tomcat/trunk/webapps/docs/config/context.xml
    tomcat/trunk/webapps/docs/tomcat-docs.xsl

Modified: tomcat/trunk/java/org/apache/catalina/connector/CoyoteAdapter.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/connector/CoyoteAdapter.java?rev=1036949&r1=1036948&r2=1036949&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/connector/CoyoteAdapter.java 
(original)
+++ tomcat/trunk/java/org/apache/catalina/connector/CoyoteAdapter.java Fri Nov 
19 17:18:04 2010
@@ -570,62 +570,89 @@ public class CoyoteAdapter implements Ad
             //reset mapping data, should prolly be done elsewhere
             request.getMappingData().recycle();
         }
-        connector.getMapper().map(serverName, decodedURI, 
-                                  request.getMappingData());
-        request.setContext((Context) request.getMappingData().context);
-        request.setWrapper((Wrapper) request.getMappingData().wrapper);
-
-        // Filter trace method
-        if (!connector.getAllowTrace() 
-                && req.method().equalsIgnoreCase("TRACE")) {
-            Wrapper wrapper = request.getWrapper();
-            String header = null;
-            if (wrapper != null) {
-                String[] methods = wrapper.getServletMethods();
-                if (methods != null) {
-                    for (int i=0; i<methods.length; i++) {
-                        if ("TRACE".equals(methods[i])) {
-                            continue;
-                        }
-                        if (header == null) {
-                            header = methods[i];
-                        } else {
-                            header += ", " + methods[i];
-                        }
-                    }
+        
+        boolean mapRequired = true;
+        String version = null;
+        
+        while (mapRequired) {
+            if (version != null) {
+                // Once we have a version - that is it
+                mapRequired = false;
+            }
+            // This will map the the latest version by default
+            connector.getMapper().map(serverName, decodedURI, version,
+                                      request.getMappingData());
+            request.setContext((Context) request.getMappingData().context);
+            request.setWrapper((Wrapper) request.getMappingData().wrapper);
+
+            // Single contextVersion therefore no possibility of remap
+            if (request.getMappingData().contexts == null) {
+                mapRequired = false;
+            }
+
+            // If there is no context at this point, it is likely no ROOT 
context
+            // has been deployed
+            if (request.getContext() == null) {
+                res.setStatus(404);
+                res.setMessage("Not found");
+                // No context, so use host
+                request.getHost().logAccess(request, response, 0, true);
+                return false;
+            }
+        
+            // Now we have the context, we can parse the session ID from the 
URL
+            // (if any). Need to do this before we redirect in case we need to
+            // include the session id in the redirect
+            String sessionID = null;
+            if (request.getServletContext().getEffectiveSessionTrackingModes()
+                    .contains(SessionTrackingMode.URL)) {
+                
+                // Get the session ID if there was one
+                sessionID = request.getPathParameter(
+                        ApplicationSessionCookieConfig.getSessionUriParamName(
+                                request.getContext()));
+                if (sessionID != null) {
+                    request.setRequestedSessionId(sessionID);
+                    request.setRequestedSessionURL(true);
                 }
-            }                               
-            res.setStatus(405);
-            res.addHeader("Allow", header);
-            res.setMessage("TRACE method is not allowed");
-            request.getContext().logAccess(request, response, 0, true);
-            return false;
-        }
+            }
 
-        // If there is no context at this point, it is likely no ROOT context
-        // has been deployed
-        if (request.getContext() == null) {
-            res.setStatus(404);
-            res.setMessage("Not found");
-            // No context, so use host
-            request.getHost().logAccess(request, response, 0, true);
-            return false;
-        }
-        
-        // Now we have the context, we can parse the session ID from the URL
-        // (if any). Need to do this before we redirect in case we need to
-        // include the session id in the redirect
-        if (request.getServletContext().getEffectiveSessionTrackingModes()
-                .contains(SessionTrackingMode.URL)) {
+            // Look for session ID in cookies and SSL session
+            parseSessionCookiesId(req, request);
+            parseSessionSslId(request);
             
-            // Get the session ID if there was one
-            String sessionID = request.getPathParameter(
-                    ApplicationSessionCookieConfig.getSessionUriParamName(
-                            request.getContext()));
-            if (sessionID != null) {
-                request.setRequestedSessionId(sessionID);
-                request.setRequestedSessionURL(true);
+            sessionID = request.getRequestedSessionId();
+            
+            if (mapRequired) {
+                if (sessionID == null) {
+                    // No session means no possibility of needing to remap
+                    mapRequired = false;
+                } else {
+                    // Find the context associated with the session
+                    Object[] objs = request.getMappingData().contexts;
+                    for (int i = (objs.length); i > 0; i--) {
+                        Context ctxt = (Context) objs[i - 1];
+                        if (ctxt.getManager().findSession(sessionID) != null) {
+                            // Was the correct context already mapped?
+                            if (ctxt.equals(request.getMappingData().context)) 
{
+                                mapRequired = false;
+                            } else {
+                                // Set version so second time through mapping 
the
+                                // correct context is found
+                                version = ctxt.getWebappVersion();
+                                // Reset mapping 
+                                request.getMappingData().recycle();
+                                break;
+                            }
+                        }
+                    }
+                    if (version == null) {
+                        // No matching context found. No need to re-map
+                        mapRequired = false;
+                    }
+                }                
             }
+
         }
 
         // Possible redirect
@@ -651,9 +678,33 @@ public class CoyoteAdapter implements Ad
             return false;
         }
 
-        // Finally look for session ID in cookies and SSL session
-        parseSessionCookiesId(req, request);
-        parseSessionSslId(request);
+        // Filter trace method
+        if (!connector.getAllowTrace() 
+                && req.method().equalsIgnoreCase("TRACE")) {
+            Wrapper wrapper = request.getWrapper();
+            String header = null;
+            if (wrapper != null) {
+                String[] methods = wrapper.getServletMethods();
+                if (methods != null) {
+                    for (int i=0; i<methods.length; i++) {
+                        if ("TRACE".equals(methods[i])) {
+                            continue;
+                        }
+                        if (header == null) {
+                            header = methods[i];
+                        } else {
+                            header += ", " + methods[i];
+                        }
+                    }
+                }
+            }                               
+            res.setStatus(405);
+            res.addHeader("Allow", header);
+            res.setMessage("TRACE method is not allowed");
+            request.getContext().logAccess(request, response, 0, true);
+            return false;
+        }
+
         return true;
     }
 

Modified: tomcat/trunk/java/org/apache/catalina/connector/MapperListener.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/connector/MapperListener.java?rev=1036949&r1=1036948&r2=1036949&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/connector/MapperListener.java 
(original)
+++ tomcat/trunk/java/org/apache/catalina/connector/MapperListener.java Fri Nov 
19 17:18:04 2010
@@ -161,12 +161,13 @@ public class MapperListener implements C
             if ("/".equals(contextPath)) {
                 contextPath = "";
             }
+            String version = ((Context) 
wrapper.getParent()).getWebappVersion();
             String hostName = context.getParent().getName();
             String wrapperName = wrapper.getName();
             String mapping = (String) event.getData();
             boolean jspWildCard = ("jsp".equals(wrapperName)
                     && mapping.endsWith("/*"));
-            mapper.addWrapper(hostName, contextPath, mapping, wrapper,
+            mapper.addWrapper(hostName, contextPath, version, mapping, wrapper,
                     jspWildCard, context.isResourceOnlyServlet(wrapperName));
         } else if (event.getType() == Wrapper.REMOVE_MAPPING_EVENT) {
             // Handle dynamically removing wrappers
@@ -176,11 +177,12 @@ public class MapperListener implements C
             if ("/".equals(contextPath)) {
                 contextPath = "";
             }
+            String version = ((Context) 
wrapper.getParent()).getWebappVersion();
             String hostName = wrapper.getParent().getParent().getName();
 
             String mapping = (String) event.getData();
             
-            mapper.removeWrapper(hostName, contextPath, mapping);
+            mapper.removeWrapper(hostName, contextPath, version, mapping);
         } else if (event.getType() == Context.ADD_WELCOME_FILE_EVENT) {
             // Handle dynamically adding welcome files
             Context context = (Context) event.getSource();
@@ -194,7 +196,8 @@ public class MapperListener implements C
             
             String welcomeFile = (String) event.getData();
             
-            mapper.addWelcomeFile(hostName, contextPath, welcomeFile);
+            mapper.addWelcomeFile(hostName, contextPath,
+                    context.getWebappVersion(), welcomeFile);
         } else if (event.getType() == Context.REMOVE_WELCOME_FILE_EVENT) {
             // Handle dynamically removing welcome files
             Context context = (Context) event.getSource();
@@ -208,7 +211,8 @@ public class MapperListener implements C
             
             String welcomeFile = (String) event.getData();
             
-            mapper.removeWelcomeFile(hostName, contextPath, welcomeFile);
+            mapper.removeWelcomeFile(hostName, contextPath,
+                    context.getWebappVersion(), welcomeFile);
         } else if (event.getType() == Context.CLEAR_WELCOME_FILES_EVENT) {
             // Handle dynamically clearing welcome files
             Context context = (Context) event.getSource();
@@ -220,7 +224,8 @@ public class MapperListener implements C
                 contextPath = "";
             }
             
-            mapper.clearWelcomeFiles(hostName, contextPath);
+            mapper.clearWelcomeFiles(hostName, contextPath,
+                    context.getWebappVersion());
         }
     }
 
@@ -307,12 +312,13 @@ public class MapperListener implements C
         if ("/".equals(contextPath)) {
             contextPath = "";
         }
+        String version = ((Context) wrapper.getParent()).getWebappVersion();
         String hostName = wrapper.getParent().getParent().getName();
 
         String[] mappings = wrapper.findMappings();
         
         for (String mapping : mappings) {
-            mapper.removeWrapper(hostName, contextPath, mapping);
+            mapper.removeWrapper(hostName, contextPath, version,  mapping);
         }
         
         if(log.isDebugEnabled()) {
@@ -336,8 +342,8 @@ public class MapperListener implements C
         javax.naming.Context resources = context.getResources();
         String[] welcomeFiles = context.findWelcomeFiles();
 
-        mapper.addContext(host.getName(), host, contextPath, context,
-                welcomeFiles, resources);
+        mapper.addContextVersion(host.getName(), host, contextPath,
+                context.getWebappVersion(), context, welcomeFiles, resources);
 
         for (Container container : context.findChildren()) {
             registerWrapper((Wrapper) container);
@@ -370,7 +376,8 @@ public class MapperListener implements C
             log.debug(sm.getString("mapperListener.unregisterContext",
                     contextPath, connector));
 
-        mapper.removeContext(hostName, contextPath);
+        mapper.removeContextVersion(hostName, contextPath,
+                context.getWebappVersion());
     }
 
 
@@ -385,7 +392,7 @@ public class MapperListener implements C
         if ("/".equals(contextPath)) {
             contextPath = "";
         }
-
+        String version = ((Context) wrapper.getParent()).getWebappVersion();
         String hostName = context.getParent().getName();
         
         String[] mappings = wrapper.findMappings();
@@ -393,7 +400,7 @@ public class MapperListener implements C
         for (String mapping : mappings) {
             boolean jspWildCard = (wrapperName.equals("jsp")
                                    && mapping.endsWith("/*"));
-            mapper.addWrapper(hostName, contextPath, mapping, wrapper,
+            mapper.addWrapper(hostName, contextPath, version, mapping, wrapper,
                               jspWildCard,
                               context.isResourceOnlyServlet(wrapperName));
         }

Modified: tomcat/trunk/java/org/apache/tomcat/util/http/mapper/Mapper.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/http/mapper/Mapper.java?rev=1036949&r1=1036948&r2=1036949&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/http/mapper/Mapper.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/util/http/mapper/Mapper.java Fri Nov 19 
17:18:04 2010
@@ -57,7 +57,7 @@ public final class Mapper {
     /**
      * Context associated with this wrapper, used for wrapper mapping.
      */
-    protected Context context = new Context();
+    protected ContextVersion context = new ContextVersion();
 
 
     // --------------------------------------------------------- Public Methods
@@ -189,7 +189,7 @@ public final class Mapper {
      */
     public void setContext(String path, String[] welcomeResources,
                            javax.naming.Context resources) {
-        context.name = path;
+        context.path = path;
         context.welcomeResources = welcomeResources;
         context.resources = resources;
     }
@@ -201,13 +201,14 @@ public final class Mapper {
      * @param hostName Virtual host name this context belongs to
      * @param host Host object
      * @param path Context path
+     * @param version Context version
      * @param context Context object
      * @param welcomeResources Welcome files defined for this context
      * @param resources Static resources of the context
      */
-    public void addContext
-        (String hostName, Object host, String path, Object context,
-         String[] welcomeResources, javax.naming.Context resources) {
+    public void addContextVersion(String hostName, Object host, String path,
+            String version, Object context, String[] welcomeResources,
+            javax.naming.Context resources) {
 
         Host[] hosts = this.hosts;
         int pos = find(hosts, hostName);
@@ -228,14 +229,30 @@ public final class Mapper {
                 if (slashCount > mappedHost.contextList.nesting) {
                     mappedHost.contextList.nesting = slashCount;
                 }
-                Context[] newContexts = new Context[contexts.length + 1];
-                Context newContext = new Context();
-                newContext.name = path;
-                newContext.object = context;
-                newContext.welcomeResources = welcomeResources;
-                newContext.resources = resources;
-                if (insertMap(contexts, newContexts, newContext)) {
-                    mappedHost.contextList.contexts = newContexts;
+                int pos2 = find(contexts, path);
+                if (pos2 < 0 || !path.equals(contexts[pos2].name)) {
+                    Context newContext = new Context();
+                    newContext.name = path;
+                    Context[] newContexts = new Context[contexts.length + 1];
+                    if (insertMap(contexts, newContexts, newContext)) {
+                        mappedHost.contextList.contexts = newContexts;
+                    }
+                    pos2 = find(newContexts, path);
+                }
+                
+                Context mappedContext = mappedHost.contextList.contexts[pos2];
+                
+                ContextVersion[] contextVersions = mappedContext.versions;
+                ContextVersion[] newContextVersions =
+                    new ContextVersion[contextVersions.length + 1];
+                ContextVersion newContextVersion = new ContextVersion();
+                newContextVersion.path = path;
+                newContextVersion.name = version;
+                newContextVersion.object = context;
+                newContextVersion.welcomeResources = welcomeResources;
+                newContextVersion.resources = resources;
+                if (insertMap(contextVersions, newContextVersions, 
newContextVersion)) {
+                    mappedContext.versions = newContextVersions;
                 }
             }
         }
@@ -248,8 +265,10 @@ public final class Mapper {
      *
      * @param hostName Virtual host name this context belongs to
      * @param path Context path
+     * @param version Context version
      */
-    public void removeContext(String hostName, String path) {
+    public void removeContextVersion(String hostName, String path,
+            String version) {
         Host[] hosts = this.hosts;
         int pos = find(hosts, hostName);
         if (pos < 0) {
@@ -259,18 +278,35 @@ public final class Mapper {
         if (host.name.equals(hostName)) {
             synchronized (host) {
                 Context[] contexts = host.contextList.contexts;
-                if( contexts.length == 0 ){
+                if (contexts.length == 0 ){
                     return;
                 }
-                Context[] newContexts = new Context[contexts.length - 1];
-                if (removeMap(contexts, newContexts, path)) {
-                    host.contextList.contexts = newContexts;
-                    // Recalculate nesting
-                    host.contextList.nesting = 0;
-                    for (int i = 0; i < newContexts.length; i++) {
-                        int slashCount = slashCount(newContexts[i].name);
-                        if (slashCount > host.contextList.nesting) {
-                            host.contextList.nesting = slashCount;
+                
+                int pos2 = find(contexts, path);
+                if (pos2 < 0 || !path.equals(contexts[pos2].name)) {
+                    return;
+                }
+                Context context = contexts[pos2];
+                
+                ContextVersion[] contextVersions = context.versions;
+                ContextVersion[] newContextVersions =
+                    new ContextVersion[contextVersions.length - 1];
+                if (removeMap(contextVersions, newContextVersions, version)) {
+                    context.versions = newContextVersions;
+                    
+                    if (context.versions.length == 0) {
+                        // Remove the context
+                        Context[] newContexts = new Context[contexts.length 
-1];
+                        if (removeMap(contexts, newContexts, path)) {
+                            host.contextList.contexts = newContexts;
+                            // Recalculate nesting
+                            host.contextList.nesting = 0;
+                            for (int i = 0; i < newContexts.length; i++) {
+                                int slashCount = 
slashCount(newContexts[i].name);
+                                if (slashCount > host.contextList.nesting) {
+                                    host.contextList.nesting = slashCount;
+                                }
+                            }
                         }
                     }
                 }
@@ -279,8 +315,8 @@ public final class Mapper {
     }
 
 
-    public void addWrapper(String hostName, String contextPath, String path,
-                           Object wrapper, boolean jspWildCard,
+    public void addWrapper(String hostName, String contextPath, String version,
+                           String path, Object wrapper, boolean jspWildCard,
                            boolean resourceOnly) {
         Host[] hosts = this.hosts;
         int pos = find(hosts, hostName);
@@ -291,13 +327,24 @@ public final class Mapper {
         if (host.name.equals(hostName)) {
             Context[] contexts = host.contextList.contexts;
             int pos2 = find(contexts, contextPath);
-            if( pos2<0 ) {
+            if (pos2 < 0) {
                 log.error("No context found: " + contextPath );
                 return;
             }
             Context context = contexts[pos2];
             if (context.name.equals(contextPath)) {
-                addWrapper(context, path, wrapper, jspWildCard, resourceOnly);
+                ContextVersion[] contextVersions = context.versions;
+                int pos3 = find(contextVersions, version);
+                if( pos3<0 ) {
+                    log.error("No context version found: " + contextPath + " " 
+
+                            version);
+                    return;
+                }
+                ContextVersion contextVersion = contextVersions[pos3];
+                if (contextVersion.name.equals(version)) {
+                    addWrapper(contextVersion, path, wrapper, jspWildCard,
+                            resourceOnly);
+                }
             }
         }
     }
@@ -320,8 +367,8 @@ public final class Mapper {
      *                     resource to be present (such as a JSP)
      * and the mapping path contains a wildcard; false otherwise
      */
-    protected void addWrapper(Context context, String path, Object wrapper,
-                              boolean jspWildCard, boolean resourceOnly) {
+    protected void addWrapper(ContextVersion context, String path,
+            Object wrapper, boolean jspWildCard, boolean resourceOnly) {
 
         synchronized (context) {
             Wrapper newWrapper = new Wrapper();
@@ -386,7 +433,7 @@ public final class Mapper {
      * @param path Wrapper mapping
      */
     public void removeWrapper
-        (String hostName, String contextPath, String path) {
+        (String hostName, String contextPath, String version, String path) {
         Host[] hosts = this.hosts;
         int pos = find(hosts, hostName);
         if (pos < 0) {
@@ -401,12 +448,20 @@ public final class Mapper {
             }
             Context context = contexts[pos2];
             if (context.name.equals(contextPath)) {
-                removeWrapper(context, path);
+                ContextVersion[] contextVersions = context.versions;
+                int pos3 = find(contextVersions, version);
+                if( pos3<0 ) {
+                    return;
+                }
+                ContextVersion contextVersion = contextVersions[pos3];
+                if (contextVersion.name.equals(version)) {
+                    removeWrapper(contextVersion, path);
+                }
             }
         }
     }
 
-    protected void removeWrapper(Context context, String path) {
+    protected void removeWrapper(ContextVersion context, String path) {
         
         if (log.isDebugEnabled()) {
             log.debug(sm.getString("mapper.removeWrapper", context.name, 
path));
@@ -473,7 +528,7 @@ public final class Mapper {
      * @param welcomeFile
      */
     public void addWelcomeFile(String hostName, String contextPath,
-            String welcomeFile) {
+            String version, String welcomeFile) {
         Host[] hosts = this.hosts;
         int pos = find(hosts, hostName);
         if (pos < 0) {
@@ -483,18 +538,28 @@ public final class Mapper {
         if (host.name.equals(hostName)) {
             Context[] contexts = host.contextList.contexts;
             int pos2 = find(contexts, contextPath);
-            if( pos2<0 ) {
+            if (pos2 < 0) {
                 log.error("No context found: " + contextPath );
                 return;
             }
             Context context = contexts[pos2];
             if (context.name.equals(contextPath)) {
-                int len = context.welcomeResources.length + 1;
-                String[] newWelcomeResources = new String[len];
-                System.arraycopy(context.welcomeResources, 0,
-                        newWelcomeResources, 0, len - 1);
-                newWelcomeResources[len - 1] = welcomeFile;
-                context.welcomeResources = newWelcomeResources;
+                ContextVersion[] contextVersions = context.versions;
+                int pos3 = find(contextVersions, version);
+                if( pos3<0 ) {
+                    log.error("No context version found: " + contextPath + " " 
+
+                            version);
+                    return;
+                }
+                ContextVersion contextVersion = contextVersions[pos3];
+                if (contextVersion.name.equals(version)) {
+                    int len = contextVersion.welcomeResources.length + 1;
+                    String[] newWelcomeResources = new String[len];
+                    System.arraycopy(contextVersion.welcomeResources, 0,
+                            newWelcomeResources, 0, len - 1);
+                    newWelcomeResources[len - 1] = welcomeFile;
+                    contextVersion.welcomeResources = newWelcomeResources;
+                }
             }
         }
     }
@@ -508,7 +573,7 @@ public final class Mapper {
      * @param welcomeFile
      */
     public void removeWelcomeFile(String hostName, String contextPath,
-            String welcomeFile) {
+            String version, String welcomeFile) {
         Host[] hosts = this.hosts;
         int pos = find(hosts, hostName);
         if (pos < 0) {
@@ -518,29 +583,39 @@ public final class Mapper {
         if (host.name.equals(hostName)) {
             Context[] contexts = host.contextList.contexts;
             int pos2 = find(contexts, contextPath);
-            if( pos2<0 ) {
+            if (pos2 < 0) {
                 log.error("No context found: " + contextPath );
                 return;
             }
             Context context = contexts[pos2];
             if (context.name.equals(contextPath)) {
-                int match = -1;
-                for (int i = 0; i < context.welcomeResources.length; i++) {
-                    if (welcomeFile.equals(context.welcomeResources[i])) {
-                        match = i;
-                        break;
-                    }
+                ContextVersion[] contextVersions = context.versions;
+                int pos3 = find(contextVersions, version);
+                if( pos3<0 ) {
+                    log.error("No context version found: " + contextPath + " " 
+
+                            version);
+                    return;
                 }
-                if (match > -1) {
-                    int len = context.welcomeResources.length - 1;
-                    String[] newWelcomeResources = new String[len];
-                    System.arraycopy(context.welcomeResources, 0,
-                            newWelcomeResources, 0, match);
-                    if (match < len) {
-                        System.arraycopy(context.welcomeResources, match + 1,
-                                newWelcomeResources, match, len - match);
+                ContextVersion contextVersion = contextVersions[pos3];
+                if (contextVersion.name.equals(version)) {
+                    int match = -1;
+                    for (int i = 0; i < 
contextVersion.welcomeResources.length; i++) {
+                        if 
(welcomeFile.equals(contextVersion.welcomeResources[i])) {
+                            match = i;
+                            break;
+                        }
+                    }
+                    if (match > -1) {
+                        int len = contextVersion.welcomeResources.length - 1;
+                        String[] newWelcomeResources = new String[len];
+                        System.arraycopy(contextVersion.welcomeResources, 0,
+                                newWelcomeResources, 0, match);
+                        if (match < len) {
+                            System.arraycopy(contextVersion.welcomeResources, 
match + 1,
+                                    newWelcomeResources, match, len - match);
+                        }
+                        contextVersion.welcomeResources = newWelcomeResources;
                     }
-                    context.welcomeResources = newWelcomeResources;
                 }
             }
         }
@@ -553,7 +628,8 @@ public final class Mapper {
      * @param hostName
      * @param contextPath
      */
-    public void clearWelcomeFiles(String hostName, String contextPath) {
+    public void clearWelcomeFiles(String hostName, String contextPath,
+            String version) {
         Host[] hosts = this.hosts;
         int pos = find(hosts, hostName);
         if (pos < 0) {
@@ -563,13 +639,23 @@ public final class Mapper {
         if (host.name.equals(hostName)) {
             Context[] contexts = host.contextList.contexts;
             int pos2 = find(contexts, contextPath);
-            if( pos2<0 ) {
+            if (pos2 < 0) {
                 log.error("No context found: " + contextPath );
                 return;
             }
             Context context = contexts[pos2];
             if (context.name.equals(contextPath)) {
-                context.welcomeResources = new String[0];
+                ContextVersion[] contextVersions = context.versions;
+                int pos3 = find(contextVersions, version);
+                if( pos3<0 ) {
+                    log.error("No context version found: " + contextPath + " " 
+
+                            version);
+                    return;
+                }
+                ContextVersion contextVersion = contextVersions[pos3];
+                if (contextVersion.name.equals(version)) {
+                    contextVersion.welcomeResources = new String[0];
+                }
             }
         }
     }
@@ -583,7 +669,7 @@ public final class Mapper {
      * @param mappingData This structure will contain the result of the mapping
      *                    operation
      */
-    public void map(MessageBytes host, MessageBytes uri,
+    public void map(MessageBytes host, MessageBytes uri, String version,
                     MappingData mappingData)
         throws Exception {
 
@@ -592,7 +678,8 @@ public final class Mapper {
         }
         host.toChars();
         uri.toChars();
-        internalMap(host.getCharChunk(), uri.getCharChunk(), mappingData);
+        internalMap(host.getCharChunk(), uri.getCharChunk(), version,
+                mappingData);
 
     }
 
@@ -623,13 +710,14 @@ public final class Mapper {
      * Map the specified URI.
      */
     private final void internalMap(CharChunk host, CharChunk uri,
-                                   MappingData mappingData)
-        throws Exception {
+            String version, MappingData mappingData) throws Exception {
 
         uri.setLimit(-1);
 
         Context[] contexts = null;
         Context context = null;
+        ContextVersion contextVersion = null;
+        
         int nesting = 0;
 
         // Virtual host mapping
@@ -695,14 +783,39 @@ public final class Mapper {
                 context = contexts[pos];
             }
             if (context != null) {
-                mappingData.context = context.object;
                 mappingData.contextPath.setString(context.name);
             }
         }
 
+        if (context != null) {
+            ContextVersion[] contextVersions = context.versions;
+            int versionCount = contextVersions.length;
+            if (versionCount > 1) {
+                Object[] contextObjects = new Object[contextVersions.length];
+                for (int i = 0; i < contextObjects.length; i++) {
+                    contextObjects[i] = contextVersions[i].object;
+                }
+                mappingData.contexts = contextObjects;
+            }
+            
+            if (version == null) {
+                // Return the latest version
+                contextVersion = contextVersions[versionCount - 1];
+            } else {
+                int pos = find(contextVersions, version);
+                if (pos < 0 || !contextVersions[pos].name.equals(version)) {
+                    // Return the latest version
+                    contextVersion = contextVersions[versionCount - 1];
+                } else {
+                    contextVersion = contextVersions[pos];
+                }
+            }
+            mappingData.context = contextVersion.object;
+        }
+
         // Wrapper mapping
-        if ((context != null) && (mappingData.wrapper == null)) {
-            internalMapWrapper(context, uri, mappingData);
+        if ((contextVersion != null) && (mappingData.wrapper == null)) {
+            internalMapWrapper(contextVersion, uri, mappingData);
         }
 
     }
@@ -711,7 +824,8 @@ public final class Mapper {
     /**
      * Wrapper mapping.
      */
-    private final void internalMapWrapper(Context context, CharChunk path,
+    private final void internalMapWrapper(ContextVersion contextVersion,
+                                          CharChunk path,
                                           MappingData mappingData)
         throws Exception {
 
@@ -720,7 +834,7 @@ public final class Mapper {
         int servletPath = pathOffset;
         boolean noServletPath = false;
 
-        int length = context.name.length();
+        int length = contextVersion.path.length();
         if (length != (pathEnd - pathOffset)) {
             servletPath = pathOffset + length;
         } else {
@@ -734,14 +848,14 @@ public final class Mapper {
         path.setOffset(servletPath);
 
         // Rule 1 -- Exact Match
-        Wrapper[] exactWrappers = context.exactWrappers;
+        Wrapper[] exactWrappers = contextVersion.exactWrappers;
         internalMapExactWrapper(exactWrappers, path, mappingData);
 
         // Rule 2 -- Prefix Match
         boolean checkJspWelcomeFiles = false;
-        Wrapper[] wildcardWrappers = context.wildcardWrappers;
+        Wrapper[] wildcardWrappers = contextVersion.wildcardWrappers;
         if (mappingData.wrapper == null) {
-            internalMapWildcardWrapper(wildcardWrappers, context.nesting, 
+            internalMapWildcardWrapper(wildcardWrappers, 
contextVersion.nesting, 
                                        path, mappingData);
             if (mappingData.wrapper != null && mappingData.jspWildCard) {
                 char[] buf = path.getBuffer();
@@ -774,7 +888,7 @@ public final class Mapper {
         }
 
         // Rule 3 -- Extension Match
-        Wrapper[] extensionWrappers = context.extensionWrappers;
+        Wrapper[] extensionWrappers = contextVersion.extensionWrappers;
         if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
             internalMapExtensionWrapper(extensionWrappers, path, mappingData,
                     true);
@@ -788,12 +902,12 @@ public final class Mapper {
                 checkWelcomeFiles = (buf[pathEnd - 1] == '/');
             }
             if (checkWelcomeFiles) {
-                for (int i = 0; (i < context.welcomeResources.length)
+                for (int i = 0; (i < contextVersion.welcomeResources.length)
                          && (mappingData.wrapper == null); i++) {
                     path.setOffset(pathOffset);
                     path.setEnd(pathEnd);
-                    path.append(context.welcomeResources[i], 0,
-                                context.welcomeResources[i].length());
+                    path.append(contextVersion.welcomeResources[i], 0,
+                            contextVersion.welcomeResources[i].length());
                     path.setOffset(servletPath);
 
                     // Rule 4a -- Welcome resources processing for exact macth
@@ -802,18 +916,18 @@ public final class Mapper {
                     // Rule 4b -- Welcome resources processing for prefix match
                     if (mappingData.wrapper == null) {
                         internalMapWildcardWrapper
-                            (wildcardWrappers, context.nesting, 
+                            (wildcardWrappers, contextVersion.nesting, 
                              path, mappingData);
                     }
 
                     // Rule 4c -- Welcome resources processing
                     //            for physical folder
                     if (mappingData.wrapper == null
-                        && context.resources != null) {
+                        && contextVersion.resources != null) {
                         Object file = null;
                         String pathStr = path.toString();
                         try {
-                            file = context.resources.lookup(pathStr);
+                            file = contextVersion.resources.lookup(pathStr);
                         } catch(NamingException nex) {
                             // Swallow not found, since this is normal
                         }
@@ -821,9 +935,9 @@ public final class Mapper {
                             internalMapExtensionWrapper(extensionWrappers, 
path,
                                                         mappingData, true);
                             if (mappingData.wrapper == null
-                                && context.defaultWrapper != null) {
+                                && contextVersion.defaultWrapper != null) {
                                 mappingData.wrapper =
-                                    context.defaultWrapper.object;
+                                    contextVersion.defaultWrapper.object;
                                 mappingData.requestPath.setChars
                                     (path.getBuffer(), path.getStart(), 
                                      path.getLength());
@@ -857,12 +971,12 @@ public final class Mapper {
                 checkWelcomeFiles = (buf[pathEnd - 1] == '/');
             }
             if (checkWelcomeFiles) {
-                for (int i = 0; (i < context.welcomeResources.length)
+                for (int i = 0; (i < contextVersion.welcomeResources.length)
                          && (mappingData.wrapper == null); i++) {
                     path.setOffset(pathOffset);
                     path.setEnd(pathEnd);
-                    path.append(context.welcomeResources[i], 0,
-                                context.welcomeResources[i].length());
+                    path.append(contextVersion.welcomeResources[i], 0,
+                                contextVersion.welcomeResources[i].length());
                     path.setOffset(servletPath);
                     internalMapExtensionWrapper(extensionWrappers, path,
                                                 mappingData, false);
@@ -876,8 +990,8 @@ public final class Mapper {
 
         // Rule 7 -- Default servlet
         if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
-            if (context.defaultWrapper != null) {
-                mappingData.wrapper = context.defaultWrapper.object;
+            if (contextVersion.defaultWrapper != null) {
+                mappingData.wrapper = contextVersion.defaultWrapper.object;
                 mappingData.requestPath.setChars
                     (path.getBuffer(), path.getStart(), path.getLength());
                 mappingData.wrapperPath.setChars
@@ -885,11 +999,11 @@ public final class Mapper {
             }
             // Redirection to a folder
             char[] buf = path.getBuffer();
-            if (context.resources != null && buf[pathEnd -1 ] != '/') {
+            if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
                 Object file = null;
                 String pathStr = path.toString();
                 try {
-                    file = context.resources.lookup(pathStr);
+                    file = contextVersion.resources.lookup(pathStr);
                 } catch(NamingException nex) {
                     // Swallow, since someone else handles the 404
                 }
@@ -1371,9 +1485,13 @@ public final class Mapper {
     // ---------------------------------------------------- Context Inner Class
 
 
-    protected static final class Context
-        extends MapElement {
+    protected static final class Context extends MapElement {
+        public ContextVersion[] versions = new ContextVersion[0];
+    }
 
+
+    protected static final class ContextVersion extends MapElement {
+        public String path = null;
         public String[] welcomeResources = new String[0];
         public javax.naming.Context resources = null;
         public Wrapper defaultWrapper = null;

Modified: tomcat/trunk/java/org/apache/tomcat/util/http/mapper/MappingData.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/http/mapper/MappingData.java?rev=1036949&r1=1036948&r2=1036949&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/http/mapper/MappingData.java 
(original)
+++ tomcat/trunk/java/org/apache/tomcat/util/http/mapper/MappingData.java Fri 
Nov 19 17:18:04 2010
@@ -28,6 +28,7 @@ public class MappingData {
 
     public Object host = null;
     public Object context = null;
+    public Object[] contexts = null;
     public Object wrapper = null;
     public boolean jspWildCard = false;
 
@@ -41,6 +42,7 @@ public class MappingData {
     public void recycle() {
         host = null;
         context = null;
+        contexts = null;
         wrapper = null;
         jspWildCard = false;
         contextPath.recycle();

Modified: tomcat/trunk/test/org/apache/tomcat/util/http/mapper/TestMapper.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/util/http/mapper/TestMapper.java?rev=1036949&r1=1036948&r2=1036949&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/tomcat/util/http/mapper/TestMapper.java 
(original)
+++ tomcat/trunk/test/org/apache/tomcat/util/http/mapper/TestMapper.java Fri 
Nov 19 17:18:04 2010
@@ -51,30 +51,30 @@ public class TestMapper extends TestCase
         welcomes[0] = "boo/baba";
         welcomes[1] = "bobou";
         
-        mapper.addContext("iowejoiejfoiew", "blah7", "",
-                "context0", new String[0], null);
-        mapper.addContext("iowejoiejfoiew", "blah7", "/foo",
-                "context1", new String[0], null);
-        mapper.addContext("iowejoiejfoiew", "blah7", "/foo/bar",
-                "context2", welcomes, null);
-        mapper.addContext("iowejoiejfoiew", "blah7", "/foo/bar/bla",
-                "context3", new String[0], null);
+        mapper.addContextVersion("iowejoiejfoiew", "blah7", "",
+                "0", "context0", new String[0], null);
+        mapper.addContextVersion("iowejoiejfoiew", "blah7", "/foo",
+                "0", "context1", new String[0], null);
+        mapper.addContextVersion("iowejoiejfoiew", "blah7", "/foo/bar",
+                "0", "context2", welcomes, null);
+        mapper.addContextVersion("iowejoiejfoiew", "blah7", "/foo/bar/bla",
+                "0", "context3", new String[0], null);
 
-        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/fo/*",
+        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "0", "/fo/*",
                 "wrapper0", false, false);
-        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/",
+        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "0", "/",
                 "wrapper1", false, false);
-        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/blh",
+        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "0", "/blh",
                 "wrapper2", false, false);
-        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "*.jsp",
+        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "0", "*.jsp",
                 "wrapper3", false, false);
-        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/blah/bou/*",
+        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "0", "/blah/bou/*",
                 "wrapper4", false, false);
-        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/blah/bobou/*",
+        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "0", "/blah/bobou/*",
                 "wrapper5", false, false);
-        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "*.htm",
+        mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "0", "*.htm",
                 "wrapper6", false, false);
-        mapper.addWrapper("iowejoiejfoiew", "/foo/bar/bla", "/bobou/*",
+        mapper.addWrapper("iowejoiejfoiew", "/foo/bar/bla", "0", "/bobou/*",
                 "wrapper7", false, false);
     }
     
@@ -106,7 +106,7 @@ public class TestMapper extends TestCase
         uri.toChars();
         uri.getCharChunk().setLimit(-1);
 
-        mapper.map(host, uri, mappingData);
+        mapper.map(host, uri, null, mappingData);
         assertEquals("blah7", mappingData.host);
         assertEquals("context2", mappingData.context);
         assertEquals("wrapper5", mappingData.wrapper);
@@ -120,7 +120,7 @@ public class TestMapper extends TestCase
         uri.setString("/foo/bar/bla/bobou/foo");
         uri.toChars();
         uri.getCharChunk().setLimit(-1);
-        mapper.map(host, uri, mappingData);
+        mapper.map(host, uri, null, mappingData);
         assertEquals("blah7", mappingData.host);
         assertEquals("context3", mappingData.context);
         assertEquals("wrapper7", mappingData.wrapper);
@@ -143,7 +143,7 @@ public class TestMapper extends TestCase
         long start = System.currentTimeMillis();
         for (int i = 0; i < 1000000; i++) {
             mappingData.recycle();
-            mapper.map(host, uri, mappingData);
+            mapper.map(host, uri, null, mappingData);
         }
         long time = System.currentTimeMillis() - start;
         

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1036949&r1=1036948&r2=1036949&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Fri Nov 19 17:18:04 2010
@@ -146,6 +146,14 @@
         of children whilst the new child is being started since this can block
         other threads and cause issues such as lost cluster messages. (markt)
       </fix>
+      <add>
+        Implement support for parallel deployment. This allows multiple 
versions
+        of the same web application to be deployed to the same context path at
+        the same time. Users without a current session will be mapped to the
+        latest version of the web application. Users with a current session 
will
+        continue to use the version of the web application with which the
+        session is associated until the session expires. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Coyote">

Modified: tomcat/trunk/webapps/docs/config/context.xml
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/context.xml?rev=1036949&r1=1036948&r2=1036949&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/config/context.xml (original)
+++ tomcat/trunk/webapps/docs/config/context.xml Fri Nov 19 17:18:04 2010
@@ -59,25 +59,85 @@
   Request URI against the <em>context path</em> of each defined Context.
   Once selected, that Context will select an appropriate servlet to
   process the incoming request, according to the servlet mappings defined
-  in the <em>web application deployment descriptor</em> file (which MUST
-  be located at <code>/WEB-INF/web.xml</code> within the web app's
-  directory hierarchy).</p>
+  by the web application deployment.</p>
 
   <p>You may define as many <strong>Context</strong> elements as you
-  wish.  Each such Context MUST have a unique context path. In
+  wish.  Each such Context MUST have a unique context name. In
   addition, a Context must be present with a context path equal to
   a zero-length string.  This Context becomes the <em>default</em>
   web application for this virtual host, and is used to process all
   requests that do not match any other Context's context path.</p>
 
-  <p><b>For the current versions of Tomcat, unlike Tomcat 4.x,
-  it is NOT recommended to place
-  &lt;Context&gt; elements directly in the server.xml file.</b> This
-  is because it makes modifying the <strong>Context</strong> configuration
-  more invasive since the main <code>conf/server.xml</code> file cannot be
-  reloaded without restarting Tomcat.</p>
+  <p>You may deploy multiple versions of a web application with the same 
context
+  path at the same time. The rules used to match requests to a context version
+  are as follows:
+  <ul>
+  <li>If no session information is present in the request, use the latest
+  version.</li>
+  <li>If session information is present in the request, check the session
+  manager of each version for a matching session and if one is found, use that
+  version.</li>
+  <li>If session information is present in the request but no matching session
+  can be found, use the latest version.</li>
+  </ul>
+  </p>
+  
+  <p>There is a close relationship between the <em>context name</em>,
+  <em>context path</em>, <em>context version</em> and the <em>base file
+  name</em> used for the WAR and/or directory that contains the web 
application.
+  When no version is specified, the rules are:
+  <ul>
+    <li>contextName = contextPath</li>
+    <li>If the contextPath is a zero length string, the base name is ROOT</li>
+    <li>If the contextPath is not a zero length string, the base name is the
+        contextPath with the leading '/' removed and any remaining '/'
+        characters in the path replaced with '#'.</li>
+  </ul>
+  When a version is specified, ##version is added to the contextName and base
+  name. To help clarify these rules, some examples are given in the following
+  table.</p>
+  
+  <table class="detail-table">
+    <tr><th>Context Path</th><th>Context Version</th><th>Context 
Name</th><th>Base filename</th></tr>
+    <tr><td>/foo</td><td><i>None</i></td><td>/foo</td><td>foo</td></tr>
+    
<tr><td>/foo/bar</td><td><i>None</i></td><td>/foo/bar</td><td>foo#bar</td></tr>
+    <tr><td><i>Empty String</i></td><td><i>None</i></td><td><i>Empty 
String</i></td><td>ROOT</td></tr>
+    <tr><td>/foo</td><td>42</td><td>/foo##42</td><td>foo##42</td></tr>
+    
<tr><td>/foo/bar</td><td>42</td><td>/foo/bar##42</td><td>foo#bar##42</td></tr>
+    <tr><td><i>Empty 
String</i></td><td>42</td><td>##42</td><td>ROOT##42</td></tr>
+  </table>
+  
+  <p><b>It is NOT recommended to place &lt;Context&gt; elements directly in the
+  server.xml file.</b> This is because it makes modifying the
+  <strong>Context</strong> configuration more invasive since the main
+  <code>conf/server.xml</code> file cannot be reloaded without restarting
+  Tomcat.</p>
 
-  <p><strong>Context</strong> elements may be explicitly defined:
+  <p>Individual <strong>Context</strong> elements may be explicitly defined:
+  <ul>
+  <li>In an individual file at <code>/META-INF/context.xml</code> inside the
+  application files. Optionally (based on the Host&apos;s copyXML attribute)
+  this may be copied to
+  <code>$CATALINA_BASE/conf/[enginename]/[hostname]/</code> and renamed to
+  application&apos;s base file name plus a ".xml" extension.</li>
+  <li>In individual files (with a ".xml" extension) in the
+  <code>$CATALINA_BASE/conf/[enginename]/[hostname]/</code> directory.
+  The context path and version will be derived from the base name of the file
+  (the file name less the .xml extension). This file will always take 
precedence
+  over any context.xml file packaged in the web application&apos;s META-INF
+  directory.</li>
+  <li>Inside a <a href="host.html">Host</a> element in the main
+  <code>conf/server.xml</code>.</li>
+  </ul>
+  </p>
+
+  <p>Default <strong>Context</strong> elements may be defined that apply to
+  multiple web applications. Configuration for an individual web application
+  will override anything configured in one of these defaults. Any nested
+  elements, e.g. &lt;Resource&gt; elements, that are defined in a default
+  <strong>Context</strong> will be created once for each
+  <strong>Context</strong> to which the default applies. They will <b>not</b> 
be
+  shared between <strong>Context</strong> elements.
   <ul>
   <li>In the <code>$CATALINA_BASE/conf/context.xml</code> file: 
   the Context element information will be loaded by all webapps.</li>
@@ -85,24 +145,6 @@
   <code>$CATALINA_BASE/conf/[enginename]/[hostname]/context.xml.default</code>
   file: the Context element information will be loaded by all webapps of that
   host.</li>
-  <li>In individual files (with a ".xml" extension) in the
-  <code>$CATALINA_BASE/conf/[enginename]/[hostname]/</code> directory.
-  The name of the file (less the .xml extension) will be used as the
-  context path. Multi-level context paths may be defined using #, e.g.
-  <code>foo#bar.xml</code> for a context path of <code>/foo/bar</code>. The
-  default web application may be defined by using a file called
-  <code>ROOT.xml</code>.</li>
-  <li>Only if a context file does not exist for the application in the 
-  <code>$CATALINA_BASE/conf/[enginename]/[hostname]/</code>, in an individual
-  file at <code>/META-INF/context.xml</code> inside the application files. If
-  the web application is packaged as a WAR then
-  <code>/META-INF/context.xml</code> will be copied to
-  <code>$CATALINA_BASE/conf/[enginename]/[hostname]/</code> and renamed to
-  match the application's context path. Once this file exists, it will not be
-  replaced if a new WAR with a newer <code>/META-INF/context.xml</code> is
-  placed in the host's appBase.</li>
-  <li>Inside a <a href="host.html">Host</a> element in the main
-  <code>conf/server.xml</code>.</li>
   </ul>
   </p>
 

Modified: tomcat/trunk/webapps/docs/tomcat-docs.xsl
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/tomcat-docs.xsl?rev=1036949&r1=1036948&r2=1036949&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/tomcat-docs.xsl (original)
+++ tomcat/trunk/webapps/docs/tomcat-docs.xsl Fri Nov 19 17:18:04 2010
@@ -521,6 +521,28 @@
       <a href="{$link}">r<xsl:apply-templates/></a>
   </xsl:template>
 
+  <!-- specially process td tags ala site.vsl -->
+  <xsl:template match="tab...@class='detail-table']/tr/td">
+    <td bgcolor="{$table-td-bg}" valign="top" align="left">
+        <xsl:if test="@colspan"><xsl:attribute name="colspan"><xsl:value-of 
select="@colspan"/></xsl:attribute></xsl:if>
+        <xsl:if test="@rowspan"><xsl:attribute name="rowspan"><xsl:value-of 
select="@rowspan"/></xsl:attribute></xsl:if>
+        <font color="#000000" size="-1" face="arial,helvetica,sanserif">
+            <xsl:apply-templates/>
+        </font>
+    </td>
+  </xsl:template>
+
+  <!-- handle th ala site.vsl -->
+  <xsl:template match="tab...@class='detail-table']/tr/th">
+    <td bgcolor="{$table-th-bg}" valign="top">
+        <xsl:if test="@colspan"><xsl:attribute name="colspan"><xsl:value-of 
select="@colspan"/></xsl:attribute></xsl:if>
+        <xsl:if test="@rowspan"><xsl:attribute name="rowspan"><xsl:value-of 
select="@rowspan"/></xsl:attribute></xsl:if>
+        <font color="#000000" size="-1" face="arial,helvetica,sanserif">
+            <xsl:apply-templates />
+        </font>
+    </td>
+  </xsl:template>
+  
   <!-- Process everything else by just passing it through -->
   <xsl:template match="*|@*">
     <xsl:copy>



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to