As you have witnessed in the previous chapters, a context represents a web application and contains one or more wrappers, each of which represents a servlet definition. However, a context requires other components as well, notably a loader and a manager. This chapter explains the org.apache.catalina.core.StandardContext
class, which represents Catalina’s standard implementation of the Context
interface.
We first have a look at the StandardContext
object instantiation and configuration. We then discuss the related classes StandardContextMapper
(in Tomcat 4) and ContextConfig
. Next, we look at the sequence of method invocation for each incoming HTTP request. Then, we revisit the StandardContext
class by discussing its important properties. Finally, the last section of this chapter discusses the backgroundProcess
method in Tomcat 5.
There is no application accompanying this chapter. StandardContext is used in Chapter 11 and previous chapters.
After a StandardContext
instance is constructed, its start
method must be called to make the instance available to service incoming HTTP requests. For one reason or another, a StandardContext
object might fail to start. If this happens, the available
property of the StandardContext
object will be set to false
. The available
property of course indicates the availability of the StandardContext
.
For the start
method to succeed, the StandardContext
object must be configured properly. In a Tomcat deployment, the configuration of StandardContext
does a number of things. It prepares the StandardContext
so that the context can read and parse the default web.xml file, which is located in the %CATALINA_HOME%/conf directory and applies to all applications deployed. It also makes sure that the StandardContext
instance can process application-level web.xml files. In addition, the configuration installs an authenticator valve and a certificate valve.
More details on StandardContext configuration are discussed in Chapter 15.
One of the properties in the StandardContext
class is the configured
property, which is a boolean
that indicates whether or not a StandardContext
instance is configured properly. StandardContext
uses an event listener as its configurator. When the start
method on a StandardContext
instance is called, one of the things it does is fire a lifecycle event. This event invokes the listener that in turn will configure the StandardContext
instance. If configuration is successful, the listener will set the configured
property to true
. Otherwise, the StandardContext
instance will refuse to start and thus will be unavailable to service HTTP requests.
In Chapter 11 you have seen an implementation of a lifecycle listener added to the StandardContext
instance. Its type is ch11.pyrmont.core.SimpleContextConfig
and it simply sets the configured
property of the StandardContext
to true
without doing anything else, just to fool the StandardContext
into thinking that it has been configured correctly. In a Tomcat deployment, the lifecycle listener that configures the StandardContext
is of type org.apache.catalina.startup.ContextConfig
, which will be explained in detail in Chapter 15.
Now that you understand the importance of configuring a StandardContext
, let’s look at the StandardContext
class in more detail, starting from its constructor.
Here is the StandardContext
class’s constructor:
public StandardContext() { super(); pipeline.setBasic(new StandardContextValve()); namingResources.setContainer(this); }
The most important thing to note in the constructor is that the StandardContext's
pipeline is given a basic valve of type org.apache.catalina.core.StandardContextValve
. This valve will handle every HTTP request that comes through the connector.
The start
method initializes the StandardContext
instance and gives the lifecycle listener a chance to configure the StandardContext
instance. The listener will set the configured
property to true
upon a successful configuration. At the end of the day, the start
method sets the available
property to either true
or false
. A value of true
means the StandardContext
instance has been configured properly and all related child containers and components have been started successfully and therefore the StandardContext
instance is ready to service incoming HTTP requests. A value of false
indicates otherwise.
The StandardContext
class employs a boolean
called configured
that is initially set to false
. If the lifecycle listener is successful in performing its tasks of configuring a StandardContext
instance, it will set the StandardContext's configured
property to true
. Towards the end of the start
method, the StandardContext
checks the value of the configured
variable. If it is true
, the StandardContext
has started successfully. Otherwise, the stop
method is called to stop all the components that have been started by the start
method.
The start
method of the StandardContext class in Tomcat 4 is given in Listing 12.1.
In Tomcat 5, the start
method is similar to the one in Tomcat 4. However, it includes some JMX-related coding, which will not make sense unless you understand JMX. JMX is discussed in Chapter 20 and if you are new to JMX you are welcomed to visit the start
method in Tomcat 5 once you’ve read that chapter.
Example 12.1. The start
method of the StandardContext
class in Tomcat 4
public synchronized void start() throws LifecycleException { if (started) throw new LifecycleException (sm.getString("containerBase.alreadyStarted", logName())); if (debug >= 1) log("Starting"); // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); if (debug >= 1) log("Processing start(), current available=" + getAvailable()); setAvailable(false); setConfigured(false); boolean ok = true; // Add missing components as necessary if (getResources() == null) { // (1) Required by Loader if (debug >= 1) log("Configuring default Resources"); try { if ((docBase != null) && (docBase.endsWith(".war"))) setResources(new WARDirContext()); else setResources(new FileDirContext()); } catch (IllegalArgumentException e) { log("Error initializing resources: " + e.getMessage()); ok = false; } } if (ok && (resources instanceof ProxyDirContext)) { DirContext dirContext = ((ProxyDirContext) resources).getDirContext(); if ((dirContext != null) && (dirContext instanceof BaseDirContext)) { ((BaseDirContext) dirContext).setDocBase(getBasePath()); ((BaseDirContext) dirContext).allocate(); } } if (getLoader() == null) { // (2) Required by Manager if (getPrivileged()) { if (debug >= 1) log("Configuring privileged default Loader"); setLoader(new WebappLoader(this.getClass().getClassLoader())); } else { if (debug >= 1) log("Configuring non-privileged default Loader"); setLoader(new WebappLoader(getParentClassLoader())); } } if (getManager() == null) { // (3) After prerequisites if (debug >= 1) log("Configuring default Manager"); setManager(new StandardManager()); } // Initialize character set mapper getCharsetMapper(); // Post work directory postWorkDirectory(); // Reading the "catalina.useNaming" environment variable String useNamingProperty = System.getProperty("catalina.useNaming"); if ((useNamingProperty != null) && (useNamingProperty.equals("false"))) { useNaming = false; } if (ok && isUseNaming()) { if (namingContextListener == null) { namingContextListener = new NamingContextListener(); namingContextListener.setDebug(getDebug()); namingContextListener.setName(getNamingContextName()); addLifecycleListener(namingContextListener); } } // Binding thread ClassLoader oldCCL = bindThread(); // Standard container startup if (debug >= 1) log("Processing standard container startup"); if (ok) { try { addDefaultMapper(this.mapperClass); started = true; // Start our subordinate components, if any if ((loader != null) && (loader instanceof Lifecycle)) ((Lifecycle) loader).start(); if ((logger != null) && (logger instanceof Lifecycle)) ((Lifecycle) logger).start(); // Unbinding thread unbindThread(oldCCL); // Binding thread oldCCL = bindThread(); if ((cluster != null) && (cluster instanceof Lifecycle)) ((Lifecycle) cluster).start(); if ((realm != null) && (realm instanceof Lifecycle)) ((Lifecycle) realm).start(); if ((resources != null) && (resources instanceof Lifecycle)) ((Lifecycle) resources).start(); // Start our Mappers, if any Mapper mappers[] = findMappers(); for (int i = 0; i < mappers.length; i++) { if (mappers[i] instanceof Lifecycle) ((Lifecycle) mappers[i]).start(); } // Start our child containers, if any Container children[] = findChildren(); for (int i = 0; i < children.length; i++) { if (children[i] instanceof Lifecycle) ((Lifecycle) children[i]).start(); } // Start the Valves in our pipeline (including the basic), // if any if (pipeline instanceof Lifecycle) ((Lifecycle) pipeline).start(); // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(START_EVENT, null); if ((manager != null) && (manager instanceof Lifecycle)) ((Lifecycle) manager).start(); } finally { // Unbinding thread unbindThread(oldCCL); } } if (!getConfigured()) ok = false; // We put the resources into the servlet context if (ok) getServletContext().setAttribute (Globals.RESOURCES_ATTR, getResources()); // Binding thread oldCCL = bindThread(); // Create context attributes that will be required if (ok) { if (debug >= 1) log("Posting standard context attributes"); postWelcomeFiles(); } // Configure and call application event listeners and filters if (ok) { if (!listenerStart()) ok = false; } if (ok) { if (!filterStart()) ok = false; } // Load and initialize all "load on startup" servlets if (ok) loadOnStartup(findChildren()); // Unbinding thread unbindThread(oldCCL); // Set available status depending upon startup success if (ok) { if (debug >= 1) log("Starting completed"); setAvailable(true); } else { log(sm.getString("standardContext.startFailed")); try { stop(); } catch (Throwable t) { log(sm.getString("standardContext.startCleanup"), t); } setAvailable(false); } // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); }
As you can see in Listing 12.1, here is the list of things that the start
method does:
Fires the BEFORE_START
event.
Sets the availability
property to false
.
Sets the configured
property to false
.
Sets the resources.
Sets a loader
Sets a manager
Initializes the character set mapper.
Starts other components associated with this context
Starts child containers (wrappers)
Starts the pipeline
Starts the manager
Fires the START
event. Here the listener (ContextConfig
) will perform some configuration operations (discussed in Chapter 15). Upon a successful configuration, the ContextConfig
will set the StandardContext
instance’s configured
property to true
.
Checks the value of the configured
property. If it is true
, do the following: call the postWelcomePages
method, load child wrappers that need to be loaded at start-up, and set the availability
property to true
. If the configured
variable is false, call the stop
method.
Fire the AFTER_START
event.
In Tomcat 4 the StandardContext's invoke
method is called by the associated connector or, if the StandardContext
is a child container of a host, by the host’s invoke
method. The StandardContext's invoke
method first checks if reloading of the application is taking place, and, if so, waits until the application reloading has finished. It then calls the invoke
method of its parent class, ContainerBase
. Listing 12.2 presents the invoke
method of the StandardContext
class.
Example 12.2. The invoke
method of the StandardContext
Class
public void invoke(Request request, Response response) throws IOException, ServletException { // Wait if we are reloading while (getPaused()) { try { Thread.sleep(1000); } catch (InterruptedException e) { ; } } // Normal request processing if (swallowOutput) { try { SystemLogHandler.startCapture(); super.invoke(request, response); } finally { String log = SystemLogHandler.stopCapture(); if (log != null && log.length() > 0) { log(log); } } } else { super.invoke(request, response); } }
The getPaused
method returns the value of the paused
property, which is true
if the application reloading is taking place. Application reloading is discussed in the next sub-section.
In Tomcat 5, the StandardContext
class does not provide an implementation of the invoke
method, therefore the ContainerBase
class’s invoke
method is executed. The checking of application reloading has been moved to the invoke
method of the StandardContextValve
class.
For each incoming request, the invoke
method of the StandardContext's
pipeline’s basic valve will be called. The basic valve for a StandardContext
is represented by the org.apache.catalina.core.StandardContextValve
class. The first thing that StandardContextValve.invoke
needs to do is obtain a wrapper that will handle the request.
In Tomcat 4, the StandardContextValve
instance looks in its containing StandardContext
. The StandardContextValve
uses the context's mapper to find a suitable wrapper. Once it obtains the wrapper, it calls the invoke
method of the wrapper. Before delving into what the StandardContextValve
does, this section presents an introduction to the mapper component.
The ContainerBase
class, the parent class of StandardContext
, defines the addDefaultMapper
method to add a default mapper as follows:
protected void addDefaultMapper(String mapperClass) { // Do we need a default Mapper? if (mapperClass == null) return; if (mappers.size() >= 1) return; // Instantiate and add a default Mapper try { Class clazz = Class.forName(mapperClass); Mapper mapper = (Mapper) clazz.newInstance(); mapper.setProtocol("http"); addMapper(mapper); } catch (Exception e) { log(sm.getString("containerBase.addDefaultMapper", mapperClass), e); } }
The StandardContext
class calls the addDefaultMapper
method from its start
method, passing the mapperClass
variable:
public synchronized void start() throws LifecycleException { ... if (ok) { try { addDefaultMapper(this.mapperClass); ... }
The StandardContext
class defines the mapperClass
variable as follows:
private String mapperClass = "org.apache.catalina.core.StandardContextMapper";
You must then associate a mapper with a container by calling the mapper’s setContainer
method, passing the instance of the container. The implementation class of the org.apache.catalina.Mapper
interface for StandardContextMapper
is org.apache.catalina.core.StandardContextMapper
. StandardContextMapper
can only be associated with a context, as indicated by its setContainer
method.
public void setContainer(Container container) { if (!(container instanceof StandardContext)) throw new IllegalArgumentException (sm.getString("httpContextMapper.container")); context = (StandardContext) container; }
The most important method in a mapper is map
, which returns a child container to handle an HTTP request. The signature of this method is as follows:
public Container map(Request request, boolean update)
In StandardContextMapper
, the map
method returns a wrapper that will handle a request. If the appropriate wrapper cannot be found, the map method returns null
.
Now back to the discussion at the beginning of this section, the StandardContextValve
instance calls the context's map
method for each incoming HTTP request, passing a org.apache.catalina.Request
object. The map
method (in the parent class ContainerBase
) first obtains a mapper for a particular protocol by calling the findMapper
method, and then calling the mapper’s map
method.
// Select the Mapper we will use Mapper mapper = findMapper(request.getRequest().getProtocol()); if (mapper == null) return (null); // Use this Mapper to perform this mapping return (mapper.map(request, update));
The map
method in StandardContextMapper
first identifies the context-relative URI to be mapped:
// Identify the context-relative URI to be mapped String contextPath = ((HttpServletRequest) request.getRequest()).getContextPath(); String requestURI = ((HttpRequest) request).getDecodedRequestURI(); String relativeURI = requestURI.substring(contextPath.length());
It then attempts to obtain a wrapper by applying four matching rules:
// Apply the standard request URI mapping rules from the specification Wrapper wrapper = null; String servletPath = relativeURI; String pathInfo = null; String name = null; // Rule 1 -- Exact Match if (wrapper == null) { if (debug >= 2) context.log(" Trying exact match"); if (!(relativeURI.equals("/"))) name = context.findServletMapping(relativeURI); if (name != null) wrapper = (Wrapper) context.findChild(name); if (wrapper != null) { servletPath = relativeURI; pathInfo = null; } } // Rule 2 -- Prefix Match if (wrapper == null) { if (debug >= 2) context.log(" Trying prefix match"); servletPath = relativeURI; while (true) { name = context.findServletMapping(servletPath + "/*"); if (name != null) wrapper = (Wrapper) context.findChild(name); if (wrapper != null) { pathInfo = relativeURI.substring(servletPath.length()); if (pathInfo.length() == 0) pathInfo = null; break; } int slash = servletPath.lastIndexOf('/'), if (slash < 0) break; servletPath = servletPath.substring(0, slash); } } // Rule 3 -- Extension Match if (wrapper == null) { if (debug >= 2) context.log(" Trying extension match"); int slash = relativeURI.lastIndexOf('/'), if (slash >= 0) { String last = relativeURI.substring(slash); int period = last.lastIndexOf('.'), if (period >= 0) { String pattern = "*" + last.substring(period); name = context.findServletMapping(pattern); if (name != null) wrapper = (Wrapper) context.findChild(name); if (wrapper != null) { servletPath = relativeURI; pathInfo = null; } } } } // Rule 4 -- Default Match if (wrapper == null) { if (debug >= 2) context.log(" Trying default match"); name = context.findServletMapping("/"); if (name != null) wrapper = (Wrapper) context.findChild(name); if (wrapper != null) { servletPath = relativeURI; pathInfo = null; } }
You might ask, how does the context have such information as the servlet mappings? Recall that the Bootstrap
class in Chapter 11 adds two servlet mappings to the StandardContext
.
context.addServletMapping("/Primitive", "Primitive"); context.addServletMapping("/Modern", "Modern");
It also adds the wrappers as children of the context:
context.addChild(wrapper1); context.addChild(wrapper2);
Tomcat 5 removed the Mapper
interface and its related classes. In fact, the StandardContextValve
class’s invoke method obtains the suitable wrapper from the request object:
Wrapper wrapper = request.getWrapper();
which indicates that the mapping information is encapsulated in the request object.
The StandardContext
class defines the reloadable
property to indicate whether reloading of the application is enabled. When reloading is enabled, the application will be reloaded if the web.xml file changes or if one of the files in the WEB-INF/classes directory is recompiled.
The StandardContext
class depends on its loader to reload the application. In Tomcat 4 the WebappLoader
class, the implementation class of Loader
in StandardContext
, has a thread that checks the timestamps of all class and JAR files in the WEB-INF directory. All you need to do to start this thread is associate the WebappLoader
with the StandardContext
by calling its setContainer
method. Here is the implementation of the setContainer
method of the WebappLoader
class in Tomcat 4:
public void setContainer(Container container) { // Deregister from the old Container (if any) if ((this.container != null) && (this.container instanceof Context)) ((Context) this.container).removePropertyChangeListener(this); // Process this property change Container oldContainer = this.container; this.container = container; support.firePropertyChange("container", oldContainer, this.container); // Register with the new Container (if any) if ((this.container!=null) && (this.container instanceof Context)) { setReloadable( ((Context) this.container).getReloadable() ); ((Context) this.container).addPropertyChangeListener(this); } }
Take a look at the last if
block. If the container is a context, the setReloadable
method will be called. This means, the value of the reloadable
property of the WebappLoader
instance will be the same as the value of the reloadable
property of the StandardContext
instance.
Here is the setReloadable
method implementation of the WebappLoader
class:
public void setReloadable(boolean reloadable) { // Process this property change boolean oldReloadable = this.reloadable; this.reloadable = reloadable; support.firePropertyChange("reloadable", new Boolean(oldReloadable), new Boolean(this.reloadable)); // Start or stop our background thread if required if (!started) return; if (!oldReloadable && this.reloadable) threadStart(); else if (oldReloadable && !this.reloadable) threadStop(); }
If the reloadable
property changes from false
to true
, the threadStart
method is called. If it changes from true
to false
, the threadStop
method is called. The threadStart
method starts a dedicated thread that continuously checks the timestamps of the class and JAR files in WEB-INF. The threadStop
method stops this thread.
In Tomcat 5, the checking of classes’ timestamps for reloading is performed by the backgroundProcess
method, which is the topic of the next section.
A context needs the help of other components, such as a loader and a manager. Often these components require a separate thread that handles background processing. For instance, a loader that support auto reload needs a thread to periodically check the timestamps of all class and JAR files in WEB-INF. A manager needs to a thread to check the expiration time of the session objects it manages. In Tomcat 4 those components end up having their own threads.
To save resources, Tomcat 5 uses a different approach. All background processes share the same thread. If a component or a container needs to have an operation done periodically, all it needs to do is write the code in its backgroundProcess
method.
The shared thread is created in a ContainerBase
object. The ContainerBase
class calls its threadStart
method in its start
method (i.e. when the container is started):
protected void threadStart() { if (thread != null) return; if (backgroundProcessorDelay <= 0) return; threadDone = false; String threadName = "ContainerBackgroundProcessor[" + toString() + "]"; thread = new Thread(new ContainerBackgroundProcessor(), threadName); thread.setDaemon(true); thread.start(); }
The threadStart
method constructs a new thread by passing an instance of ContainerBackgroundProcessor
class that implements java.lang.Runnable
. The ContainerBackgroundProcessor
class is given in Listing 12.3.
Example 12.3. The ContainerBackgroundProcessor class
protected class ContainerBackgroundProcessor implements Runnable { public void run() { while (!threadDone) { try { Thread.sleep(backgroundProcessorDelay * 1000L); } catch (InterruptedException e) { ; } if (!threadDone) { Container parent = (Container) getMappingObject(); ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (parent.getLoader() != null) { cl = parent.getLoader().getClassLoader(); } processChildren(parent, cl); } } } protected void processChildren(Container container, ClassLoader cl) { try { if (container.getLoader() != null) { Thread.currentThread().setContextClassLoader (container.getLoader().getClassLoader()); } container.backgroundProcess(); } catch (Throwable t) { log.error("Exception invoking periodic operation: ", t); } finally { Thread.currentThread().setContextClassLoader(cl); } Container[] children = container.findChildren(); for (int i = 0; i < children.length; i++) { if (children[i].getBackgroundProcessorDelay() <= 0) { processChildren(children[i], cl); } } } }
The ContainerBackgroundProcessor
class is an inner class of ContainerBase
. Inside its run
method is a while
loop that periodically calls its processChildren
method. The processChildren
method in turn calls the backgroundProcess
method and the processChildren
method of each of its children. By implementing the backgroundProcess
method, a child class of ContainerBase
can have a dedicated thread for running periodic tasks, such as checking classes’ timestamps or the expiry times of session objects.
Listing 12.4 presents the backgroundProcess
method in the StandardContext
class in Tomcat 5.
Example 12.4. The backgroundProcess method of the StandardContext class
public void backgroundProcess() { if (!started) return; count = (count + 1) % managerChecksFrequency; if ((getManager() != null) && (count == 0)) { try { getManager().backgroundProcess(); } catch ( Exception x ) { log.warn("Unable to perform background process on manager",x); } } if (getLoader() != null) { if (reloadable && (getLoader().modified())) { try { Thread.currentThread().setContextClassLoader (StandardContext.class.getClassLoader()); reload(); } finally { if (getLoader() != null) { Thread.currentThread().setContextClassLoader (getLoader().getClassLoader()); } } } if (getLoader() instanceof WebappLoader) { ((WebappLoader) getLoader()).closeJARs(false); } } }
It should be clear how StandardContext
helps its associated manager and loader with their periodic tasks.
In this chapter you have learned about the StandardContext
class and its related classes. You have also seen how a StandardContext
instance is configured and what happens inside it for each incoming HTTP request. The last section of this chapter discusses the backgroundProcess
implementation in Tomcat 5.
3.140.198.43