Chapter 12. StandardContext

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.

Note

There is no application accompanying this chapter. StandardContext is used in Chapter 11 and previous chapters.

StandardContext Configuration

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.

Note

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.

StandardContext Class’s 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.

Starting StandardContext

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.

Note

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.

The Invoke Method

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.

StandardContextMapper

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.

Support for Reloading

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.

The backgroundProcess Method

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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.198.43