/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2002-03 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.dynamo.app;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.collections.*;
import org.apache.log4j.Logger;
import org.w3c.dom.*;
import com.silverwrist.util.*;
import com.silverwrist.util.xml.*;
import com.silverwrist.dynamo.DynamoVersion;
import com.silverwrist.dynamo.Namespaces;
import com.silverwrist.dynamo.event.*;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.script.ScriptController;
import com.silverwrist.dynamo.util.*;

/**
 * The main "engine" class of the Dynamo framework, which holds most of the service providers and other
 * classes registered (usually by the defined {@link com.silverwrist.dynamo.iface.Application Application} class).
 * Exactly one of these is created per Dynamo instance; for Dynamo Web applications, a reference to it is saved
 * in the servlet context attributes.  It is also responsible for reading and parsing the Dynamo XML configuration
 * file and instantiating components defined therein.
 *
 * @author Eric J. Bowersox &lt;erbo@silcom.com&gt;
 * @version X
 */
public class ApplicationContainer
    implements ResourceProvider, ResourceProviderManager, RendererRegistration, ObjectProvider,
               EventListenerRegistration, OutputObjectFilterRegistration, QueryRenderer, PostDynamicUpdate,
               RenderImmediate, RequestPreprocessorRegistration, ExceptionTranslatorRegistration
{
  /*--------------------------------------------------------------------------------
   * Internal class recording renderer registrations and providing a
   * "shutdown hook"
   *--------------------------------------------------------------------------------
   */

  private class RegisteredRenderer implements ComponentShutdown
  {
    /*====================================================================
     * Attributes
     *====================================================================
     */

    private Renderer m_renderer;
    private HashSet m_known_classes;

    /*====================================================================
     * Constructors
     *====================================================================
     */

    RegisteredRenderer(Class klass, Renderer renderer)
    {
      m_renderer = renderer;
      m_known_classes = new HashSet();
      m_known_classes.add(klass);

    } // end constructor

    RegisteredRenderer(DynamicClass dclass, Renderer renderer)
    {
      m_renderer = renderer;
      m_known_classes = new HashSet();
      m_known_classes.add(dclass);

    } // end constructor

    /*====================================================================
     * Implementations from interface ComponentShutdown
     *====================================================================
     */

    public synchronized void shutdown()
    {
      Iterator it = m_known_classes.iterator();
      while (it.hasNext())
      { // remove all class rendering entries registered with this "event"
	Object obj = it.next();
	if (obj instanceof Class)
	  m_class_renderers.remove(obj);
	else if (obj instanceof DynamicClass)
	  m_class_renderers.remove(obj);

      } // end while

      m_known_classes.clear();

    } // end shutdown

    /*====================================================================
     * External operations
     *====================================================================
     */

    public Renderer getRenderer()
    {
      return m_renderer;

    } // end getRenderer

    public synchronized void add(Class klass)
    {
      m_known_classes.add(klass);

    } // end add

    public synchronized void add(DynamicClass dclass)
    {
      m_known_classes.add(dclass);

    } // end add

  } // end class RegisteredRenderer

  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static Logger logger = Logger.getLogger(ApplicationContainer.class);

  private static final String[] SUBSTRATE_MAP_KEYS =
  { ApplicationSubstrate.OBJ_BASE_PATH,    ApplicationSubstrate.OBJ_CODE_PATH,
    ApplicationSubstrate.OBJ_CLASSES_PATH, ApplicationSubstrate.OBJ_LIBS_PATH
  };

  private static final ApplicationListener[] APP_LISTENER_TEMPLATE = new ApplicationListener[0];
  private static final SessionInfoListener[] SESSION_LISTENER_TEMPLATE = new SessionInfoListener[0];

  private static final String TEMPLATE_CLASSNAME = "$$$BLORT$$$";

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private int m_refs;                                          // reference count
  private ApplicationSubstrate m_substrate;                    // application substrate object
  private File m_resource_base;                                // base directory for resources
  private Vector m_resource_providers = new Vector();          // resource providers
  private LinkedList m_shutdown_list = new LinkedList();       // list of objects to shut down
  private HashMap m_connections = new HashMap();               // list of connection pools
  private HashMap m_objects = new HashMap();                   // list of base objects
  private Application m_application = null;                    // the application object
  private Hashtable m_class_renderers = new Hashtable();       // renderers for static classes
  private Hashtable m_dclass_renderers = new Hashtable();      // renderers for dynamic classes
  private Vector m_application_listeners = new Vector();       // application event listeners
  private Vector m_session_listeners = new Vector();           // session event listeners
  private BackgroundProcessor m_background;                    // background processor
  private ScriptController m_script_ctrl;                      // script controller
  private ApplicationServiceManager m_app_sm;                  // application service manager
  private PropertySerializationSupport m_pss;                  // property serialization support
  private Set m_known_sessions;                                // known sessions
  private String m_identity;                                   // server identity
  private Map m_rewrite_rules;                                 // URL rewriting rules
  private Vector m_output_filters = new Vector();              // output filters
  private HashMap m_update_listeners = new HashMap();          // update listeners
  private Vector m_request_preprocessors = new Vector();       // request preprocessors
  private Vector m_exception_xlators = new Vector();           // exception translators

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  /**
   * Creates the application container.
   *
   * @param config_file A reference to the Dynamo XML configuration file.
   * @param substrate A reference to the {@link com.silverwrist.dynamo.iface.ApplicationSubstrate ApplicationSubstrate}
   *                  object, which provides certain services and object references to the
   *                  <CODE>ApplicationContainer</CODE>.  (Usually, this will be specific to the Dynamo application
   *                  type, for example, Web application.)
   * @exception com.silverwrist.dynamo.except.ConfigException If there is an error in the configuration which will
   *            not allow Dynamo to be initialized.
   */
  public ApplicationContainer(File config_file, ApplicationSubstrate substrate) throws ConfigException
  {
    if (logger.isDebugEnabled())
      logger.debug("new ApplicationContainer - config file is " + config_file.getAbsolutePath());
    substrate.initialize();
    m_substrate = substrate;
    m_refs = 1;
    m_app_sm = new ApplicationServiceManager();
    m_pss = new PropertySerializationSupport();
    m_known_sessions = Collections.synchronizedSet(new HashSet());
    XMLLoader loader = XMLLoader.get();

    // Initialize the init services and runtime services with defaults.
    m_app_sm.addInitService(ResourceProvider.class,(ResourceProvider)this);
    m_app_sm.addInitService(ResourceProviderManager.class,(ResourceProviderManager)this);
    m_app_sm.addInitService(RendererRegistration.class,(RendererRegistration)this);
    m_app_sm.addInitService(ObjectProvider.class,(ObjectProvider)this);
    m_app_sm.addInitService(EventListenerRegistration.class,(EventListenerRegistration)this);
    m_app_sm.addInitService(OutputObjectFilterRegistration.class,(OutputObjectFilterRegistration)this);
    m_app_sm.addInitService(PropertySerializer.class,(PropertySerializer)m_pss);
    m_app_sm.addInitService(PropertySerializerRegistration.class,(PropertySerializerRegistration)m_pss);
    m_app_sm.addInitService(PostDynamicUpdate.class,(PostDynamicUpdate)this);
    m_app_sm.addInitService(RequestPreprocessorRegistration.class,(RequestPreprocessorRegistration)this);
    m_app_sm.addInitService(ExceptionTranslatorRegistration.class,(ExceptionTranslatorRegistration)this);

    m_app_sm.addRuntimeService(ResourceProvider.class,(ResourceProvider)this);
    m_app_sm.addRuntimeService(ObjectProvider.class,(ObjectProvider)this);
    m_app_sm.addRuntimeService(EventListenerRegistration.class,(EventListenerRegistration)this);
    m_app_sm.addRuntimeService(PropertySerializer.class,(PropertySerializer)m_pss);
    m_app_sm.addRuntimeService(PostDynamicUpdate.class,(PostDynamicUpdate)this);
    m_app_sm.addRuntimeService(RenderImmediate.class,(RenderImmediate)this);

    m_app_sm.addOutputService(ResourceProvider.class,(ResourceProvider)this);
    m_app_sm.addOutputService(ObjectProvider.class,(ObjectProvider)this);
    m_app_sm.addOutputService(QueryRenderer.class,(QueryRenderer)this);

    // Create initialization services interface object.
    ServiceProvider init_svcs = m_app_sm.createInitServices();

    try
    { // load the configuration file
      Document config_doc = loader.load(config_file,false);
      Element root = loader.getRootElement(config_doc,"configuration");

      // get the <control/> element and process it
      Element control = loader.getSubElement(root,"control");
      processControlSection(control);
      m_shutdown_list.addFirst(m_background);
      m_app_sm.addInitService(BackgroundScheduler.class,m_background);
      m_app_sm.addRuntimeService(BackgroundScheduler.class,m_background);

      // initialize some default renderers
      m_shutdown_list.addFirst(registerRenderer(DataItem.class,new DataItemRenderer()));
      m_shutdown_list.addFirst(registerRenderer(java.util.List.class,new ListRenderer()));

      // initialize the scripting engine
      m_script_ctrl = new ScriptController();
      m_script_ctrl.initialize(control,init_svcs);
      m_shutdown_list.addFirst(m_script_ctrl);

      // add the scripting engine's services so they can be used by external components
      m_app_sm.addInitService(ScriptEngineConfig.class,m_script_ctrl);
      m_app_sm.addRuntimeService(ScriptExecute.class,m_script_ctrl);

      // get all database connection configurations
      List l = loader.getMatchingSubElements(root,"dbconnection");
      Iterator it = l.iterator();
      Element elt;
      while (it.hasNext())
      { // get each element in turn
	elt = (Element)(it.next());
	DBConnectionPool pool = (DBConnectionPool)createNamedObject(elt,init_svcs,DBConnectionPool.class,
                                                                    "no.notDBPool");
	m_connections.put(pool.getName(),pool);

      } // end while

      m_app_sm.addInitService(HookServiceProviders.class,m_app_sm);

      // Sort the "object" definitions by priority order to determine in what order to instantiate them.
      l = loader.getMatchingSubElements(root,"object");
      if (!(l.isEmpty()))
      { // copy elements into binary heap 
	it = l.iterator();
	BinaryHeap prioheap = new BinaryHeap(l.size());
	while (it.hasNext())
	{ // sort elements by priority 
	  elt = (Element)(it.next());
	  prioheap.insert(new HeapContents(elt));
	
	} // end while

	while (prioheap.size()>0)
	{ // now remove and instantiate the elements
	  HeapContents hc = (HeapContents)(prioheap.remove());
	  NamedObject nobj = createNamedObject(hc.getElement(),init_svcs,null,null);
	  m_objects.put(nobj.getName(),nobj);

	} // end while

      } // end if

      // Find the application definition and initialize the application.
      elt = loader.getSubElement(root,"application");
      m_application = (Application)createNamedObject(elt,init_svcs,Application.class,"no.notApp");

    } // end try
    catch (IOException e)
    { // unable to read config file - send back a ConfigException
      logger.fatal("ApplicationContainer config read failed",e);
      ConfigException ce = new ConfigException(ApplicationContainer.class,"ApplicationContainerMessages",
					       "creation.ioError",e);
      ce.setParameter(0,config_file.getAbsolutePath());
      throw ce;

    } // end catch
    catch (XMLLoadException e)
    { // XML loader failed - send back a ConfigException
      logger.fatal("ApplicationContainer config load failed",e);
      throw new ConfigException(e);

    } // end catch

    // Create the "server identity" string.
    StringBuffer buf = new StringBuffer();
    String app_id = m_application.getIdentity();
    if (app_id!=null)
      buf.append(app_id).append(' ');
    buf.append("Dynamo/").append(DynamoVersion.VERSION);
    m_identity = buf.toString();
    logger.info("Server: " + m_identity);

    // Fire the "application initialized" events.
    ApplicationListener[] listeners =
        (ApplicationListener[])(m_application_listeners.toArray(APP_LISTENER_TEMPLATE));
    if (listeners.length>0)
    { // call the event handlers...
      if (logger.isDebugEnabled())
	logger.debug("ApplicationContainer: " + listeners.length + " init handler(s) to call");
      ApplicationEventRequest req = new ApplicationEventRequest(m_app_sm.createRuntimeServices(),true);
      ApplicationEvent evt = new ApplicationEvent(req,m_application);
      for (int i=0; i<listeners.length; i++)
	listeners[i].applicationInitialized(evt);

    } // end if

    if (logger.isDebugEnabled())
      logger.debug("ApplicationContainer initialization done");

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal operations
   *--------------------------------------------------------------------------------
   */

  private static final Class getUltimateComponent(Class klass)
  {
    while (klass.isArray())
      klass = klass.getComponentType();
    return klass;

  } // end getUltimateComponent

  /**
   * Finds an instance of the {@link com.silverwrist.dynamo.iface.ComponentInitialize ComponentInitialize} interface
   * associated with the given object, either through implementation of the interface, or by querying its
   * {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider} implementation.
   *
   * @param obj The object to be queried.
   * @return The instance of <CODE>ComponentInitialize</CODE> associated with the object, or <CODE>null</CODE>
   *         if there is no such instance.
   */
  private static final ComponentInitialize getComponentInitialize(Object obj)
  {
    if (obj instanceof ComponentInitialize)
      return (ComponentInitialize)obj;
    if (obj instanceof ServiceProvider)
    { // it may be a service provider interface
      try
      { // look for ComponentInitialize as a service
	ServiceProvider sp = (ServiceProvider)obj;
	ComponentInitialize rc = (ComponentInitialize)(sp.queryService(ComponentInitialize.class));
	return rc;

      } // end try
      catch (NoSuchServiceException e)
      { // do nothing here
      } // end catch

    } // end if

    return null;  // no dice!

  } // end getComponentInitialize

  /**
   * Finds an instance of the {@link com.silverwrist.dynamo.iface.ComponentShutdown ComponentShutdown} interface
   * associated with the given object, either through implementation of the interface, or by querying its
   * {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider} implementation.
   *
   * @param obj The object to be queried.
   * @return The instance of <CODE>ComponentShutdown</CODE> associated with the object, or <CODE>null</CODE>
   *         if there is no such instance.
   */
  private static final ComponentShutdown getComponentShutdown(Object obj)
  {
    if (obj instanceof ComponentShutdown)
      return (ComponentShutdown)obj;
    if (obj instanceof ServiceProvider)
    { // it may be a service provider interface
      try
      { // look for ComponentShutdown as a service
	ServiceProvider sp = (ServiceProvider)obj;
	ComponentShutdown rc = (ComponentShutdown)(sp.queryService(ComponentShutdown.class));
	return rc;

      } // end try
      catch (NoSuchServiceException e)
      { // do nothing here
      } // end catch

    } // end if

    return null;  // no dice!

  } // end getComponentShutdown

  /**
   * Destroys all data associated with this <CODE>ApplicationContainer</CODE>.
   */
  private final synchronized void destroy()
  {
    if (logger.isDebugEnabled())
      logger.debug("ApplicationContainer.destroy(): " + m_known_sessions.size() + " sessions to shutdown");
    Iterator it = m_known_sessions.iterator();
    while (it.hasNext())
    { // shut down each session
      ComponentShutdown cs = (ComponentShutdown)(it.next());
      cs.shutdown();

    } // end while

    m_known_sessions.clear();

    // Fire the "application exiting" events.
    ApplicationListener[] listeners =
        (ApplicationListener[])(m_application_listeners.toArray(APP_LISTENER_TEMPLATE));
    if (logger.isDebugEnabled())
      logger.debug("ApplicationContainer.destroy(): " + listeners.length + " exit handlers to call");
    if (listeners.length>0)
    { // call the event handlers...
      ApplicationEventRequest req = new ApplicationEventRequest(m_app_sm.createRuntimeServices(),false);
      ApplicationEvent evt = new ApplicationEvent(req,m_application);
      for (int i=0; i<listeners.length; i++)
	listeners[i].applicationExiting(evt);

    } // end if

    if (logger.isDebugEnabled())
      logger.debug("ApplicationContainer.destroy(): " + m_shutdown_list.size() + " objects to blow away");

    while (m_shutdown_list.size()>0)
    { // shut down all components in reverse order of creation
      ComponentShutdown sd = (ComponentShutdown)(m_shutdown_list.removeFirst());
      sd.shutdown();

    } // end while

    if (logger.isDebugEnabled())
      logger.debug("ApplicationContainer.destroy(): clearing internal data structures");
    m_connections.clear();
    m_objects.clear();
    m_class_renderers.clear();
    m_dclass_renderers.clear();
    m_application = null;
    m_resource_providers.clear();
    m_application_listeners.clear();
    m_session_listeners.clear();

    if (logger.isDebugEnabled())
      logger.debug("ApplicationContainer.destroy(): terminating substrate");
    m_substrate.terminate();
    m_substrate = null;

  } // end destroy

  private final Map getSubstrateReplaceMap()
  {
    HashMap rc = new HashMap();
    for (int i=0; i<SUBSTRATE_MAP_KEYS.length; i++)
    { // load the map with return values
      try
      { // get each object in turn from the substrate and add it to the map
	Object tmp = m_substrate.getObject(ApplicationSubstrate.NAMESPACE,SUBSTRATE_MAP_KEYS[i]);
	rc.put(SUBSTRATE_MAP_KEYS[i],tmp.toString());
	
      } // end try
      catch (NoSuchObjectException e)
      { // skip this object and try the next one
      } // end catch

    } // end for

    return rc;
    
  } // end getSubstrateReplaceMap

  private final void processControlSection(Element control) throws ConfigException, XMLLoadException
  {
    Map replace_map = getSubstrateReplaceMap();
    XMLLoader loader = XMLLoader.get();
    DOMElementHelper control_h = new DOMElementHelper(control);

    // Get the thread counts and create the background processor.
    int num_norm = 2;
    int num_low = 2;
    Element elt = control_h.getSubElement("background-threads");
    if (elt!=null)
    { // read in the values
      num_norm = loader.getAttributeInt(elt,"normal",2);
      num_low = loader.getAttributeInt(elt,"low",2);

    } // end if

    // Create the background services and the background processor.
    SimpleServiceProvider bg_svcs = new SimpleServiceProvider("Background Services");
    bg_svcs.addService(ResourceProvider.class,this);
    bg_svcs.addService(ObjectProvider.class,this);
    m_background = new BackgroundProcessor(num_norm,num_low,bg_svcs);

    // Get the resource root directory.
    String res_root = loader.getSubElementText(control_h,"resource-root");
    m_resource_base = new File(StringUtils.replaceAllVariables(res_root,replace_map));
    if (!(m_resource_base.isDirectory()))
    { // the resource base MUST be a directory!
      ConfigException ce = new ConfigException(ApplicationContainer.class,"ApplicationContainerMessages",
                                               "resource.rootErr");
      ce.setParameter(0,m_resource_base.getAbsolutePath());
      throw ce;

    } // end if

    // Get the list of rewrite rules.
    Element rewrite_sect = loader.getSubElement(control_h,"url-rewrite-rules");
    List rewrite_list = loader.getMatchingSubElements(rewrite_sect,"rule");
    HashMap tmp_map = new HashMap();
    Iterator it = rewrite_list.iterator();
    while (it.hasNext())
    { // get each rule and create corresponding object
      elt = (Element)(it.next());
      RewriteRule rule = new RewriteRule(elt);
      tmp_map.put(rule.getName(),rule);

    } // end while

    m_rewrite_rules = Collections.unmodifiableMap(tmp_map);

  } // end processControlSection

  private final NamedObject createNamedObject(Element config_root, ServiceProvider services,
		                              Class extratest, String extratest_msg)
      throws ConfigException
  {
    XMLLoader loader = XMLLoader.get();
    String klassname = null;
    try
    { // get the classname and load it, and create an object
      klassname = loader.getAttribute(config_root,"classname");
      if (logger.isDebugEnabled())
      { // output a proper debug message
	Class desired = (extratest==null) ? NamedObject.class : extratest;
	logger.debug("createNamedObject: Creating new NamedObject of class " + klassname
		     + ", which should implement " + desired.getName());

      } // end if

      Class klass = Class.forName(klassname);
      if (!(NamedObject.class.isAssignableFrom(klass)))
      { // this object is not a valid NamedObject
	logger.error("object " + klassname + " does not implement NamedObject");
	ConfigException ce = new ConfigException(ApplicationContainer.class,"ApplicationContainerMessages",
						 "no.notDynamo");
	ce.setParameter(0,klassname);
	throw ce;

      } // end if

      if ((extratest!=null) && !(extratest.isAssignableFrom(klass)))
      { // this object is not a valid (whatever)
	logger.error("object " + klassname + " does not implement " + extratest.getName());
	ConfigException ce = new ConfigException(ApplicationContainer.class,"ApplicationContainerMessages",
						 extratest_msg);
	ce.setParameter(0,klassname);
	throw ce;

      } // end if

      // instantiate the object, and initialize it as needed
      Object rc = klass.newInstance();
      ComponentInitialize init = getComponentInitialize(rc);
      if (init!=null)
	init.initialize(config_root,services);

      synchronized (this)
      { // save its shutdown hook for later
	ComponentShutdown sd = getComponentShutdown(rc);
	if (sd!=null)
	  m_shutdown_list.addFirst(sd);

      } // end synchronized block

      if (logger.isDebugEnabled())
	logger.debug("Object \"" + ((NamedObject)rc).getName() + "\" (of class " + klassname + ") created");

      return (NamedObject)rc;

    } // end try
    catch (XMLLoadException e)
    { // error getting class name - throw the ConfigException
      logger.error("XML loader failure",e);
      throw new ConfigException(e);

    } // end catch
    catch (ClassNotFoundException e)
    { // the class was not found
      ConfigException ce = new ConfigException(ApplicationContainer.class,"ApplicationContainerMessages",
                                               "no.classNotFound");
      ce.setParameter(0,klassname);
      throw ce;

    } // end catch
    catch (IllegalAccessException e)
    { // unable to create this object
      ConfigException ce = new ConfigException(ApplicationContainer.class,"ApplicationContainerMessages",
                                               "no.createError");
      ce.setParameter(0,klassname);
      throw ce;

    } // end catch
    catch (InstantiationException e)
    { // unable to create this object
      ConfigException ce = new ConfigException(ApplicationContainer.class,"ApplicationContainerMessages",
                                               "no.createError");
      ce.setParameter(0,klassname);
      throw ce;

    } // end catch

  } // end createNamedObject

  private final WrappedResourceProvider locateResourceProvider(ComponentResName crn)
  {
    WrappedResourceProvider wrp = null;
    synchronized (m_resource_providers)
    { // perform the "maximum munch" algorithm to try and find a provider
      int max_munch = 0;
      Iterator it = m_resource_providers.iterator();
      while (it.hasNext())
      { // look at each mounted provider and try to find a better possibility
	WrappedResourceProvider tmp = (WrappedResourceProvider)(it.next());
	int test = tmp.getMatchCount(crn);
	if (test>max_munch)
	{ // found one that munches more...save it
	  wrp = tmp;
	  max_munch = test;

	} // end if

      } // end while

    } // end synchronized block

    return wrp;

  } // end locateResourceProvider

  private final InputStream getResourceInternal(ComponentResName crn) throws IOException
  {
    File res_file = new File(m_resource_base,crn.getStringValue(0,false));
    if (!(res_file.exists()) || res_file.isDirectory() || !(res_file.canRead()))
      throw new NoSuchResourceException(crn.getStringValue(0,true));
    return new FileInputStream(res_file);

  } // end getResourceInternal

  private final long getResourceModTimeInternal(ComponentResName crn)
  {
    File res_file = new File(m_resource_base,crn.getStringValue(0,false));
    if (!(res_file.exists()) || res_file.isDirectory() || !(res_file.canRead()))
      return 0;
    return res_file.lastModified();

  } // end getResourceModTimeInternal

  private final RegisteredRenderer searchDClassRenderers(DynamicClass dclass)
  {
    return (RegisteredRenderer)(m_dclass_renderers.get(dclass));

  } // end searchDClassRenderer

  private final RegisteredRenderer searchArrayRenderers(Class klass, String template)
  {
    if (klass.isPrimitive() || (klass==Object.class))
      return null;  // should have been picked up already

    // look at this level for the class member
    RegisteredRenderer rc = null;
    try
    { // load the array class corresponding to the right depth, then check the renderer map
      Class tmp = Class.forName(StringUtils.replace(template,TEMPLATE_CLASSNAME,klass.getName()));
      rc = (RegisteredRenderer)(m_class_renderers.get(tmp));
      if (rc!=null)
	return rc;

    } // end try
    catch (ClassNotFoundException e)
    { // this class was not found, so it can't be present
      rc = null;

    } // end catch

    // Try all interfaces implemented by the object.
    Class[] ifaces = klass.getInterfaces();
    for (int i=0; i<ifaces.length; i++)
    { // look for interfaces implemented by the object
      rc = searchArrayRenderers(ifaces[i],template);
      if (rc!=null)
	return rc;

    } // end for

    Class superclass = klass.getSuperclass();
    if (superclass!=null)
    { // try the superclass now
      rc = searchArrayRenderers(superclass,template);
      if (rc!=null)
	return rc;

    } // end if

    return null;  // give up

  } // end searchArrayRenderers

  private final RegisteredRenderer searchClassRenderers(Class klass)
  {
    if (klass.isPrimitive() || (klass==Object.class))
      return null;  // should have been picked up already

    // look at this level for the class member
    RegisteredRenderer rc = (RegisteredRenderer)(m_class_renderers.get(klass));
    if (rc!=null)
      return rc;

    if (klass.isArray())
    { // for arrays, use the parallel function to search back over the component
      // class's hierarchy
      Class component = getUltimateComponent(klass);
      if (component.isPrimitive() || (component==Object.class))
	return null;  // no chance this should have been picked up
      String template = StringUtils.replace(klass.getName(),component.getName(),TEMPLATE_CLASSNAME);
      return searchArrayRenderers(component,template);

    } // end if

    // Try all interfaces implemented by the object.
    Class[] ifaces = klass.getInterfaces();
    for (int i=0; i<ifaces.length; i++)
    { // look for interfaces implemented by the object
      rc = searchClassRenderers(ifaces[i]);
      if (rc!=null)
	return rc;

    } // end for

    Class superclass = klass.getSuperclass();
    if (superclass!=null)
    { // try the superclass now
      rc = searchClassRenderers(superclass);
      if (rc!=null)
	return rc;

    } // end if

    return null;  // give up

  } // end searchClassRenderers

  /*--------------------------------------------------------------------------------
   * Implementations from interface ResourceProvider
   *--------------------------------------------------------------------------------
   */

  public InputStream getResource(String resource_path) throws IOException
  {
    ComponentResName crn = new ComponentResName(resource_path);
    WrappedResourceProvider wrp = locateResourceProvider(crn);
    if (wrp==null)
      return getResourceInternal(crn);
    else
      return wrp.getResource(crn);

  } // end getResource

  public long getResourceModTime(String resource_path)
  {
    try
    { // build the resource name and return it
      ComponentResName crn = new ComponentResName(resource_path);
      WrappedResourceProvider wrp = locateResourceProvider(crn);
      if (wrp==null)
	return getResourceModTimeInternal(crn);
      else
	return wrp.getResourceModTime(crn);

    } // end try
    catch (NoSuchResourceException e)
    { // just deal with exceptions in here
      return 0;

    } // end catch

  } // end getResourceModTime

  /*--------------------------------------------------------------------------------
   * Implementations from interface ResourceProviderManager
   *--------------------------------------------------------------------------------
   */

  public ComponentShutdown mountResourceProvider(String mount_path, ResourceProvider provider)
      throws ConfigException
  {
    ComponentResName crn = null;
    try
    { // create component name for mount path
      crn = new ComponentResName(mount_path);

    } // end try
    catch (NoSuchResourceException e)
    { // this is not a valid name - throw ConfigException
      ConfigException ce = new ConfigException(ApplicationContainer.class,"ApplicationContainerMessages",
                                               "mountRP.badName");
      ce.setParameter(0,mount_path);
      throw ce;

    } // end catch

    WrappedResourceProvider wrp = new WrappedResourceProvider(crn,provider);
    if (m_resource_providers.contains(wrp))
    { // resource provider already mounted on this path - bogus!
      ConfigException ce = new ConfigException(ApplicationContainer.class,"ApplicationContainerMessages",
                                               "mountRP.already");
      ce.setParameter(0,mount_path);
      throw ce;

    } // end if

    m_resource_providers.add(wrp);
    return new ShutdownVectorRemove(m_resource_providers,wrp);

  } // end mountResourceProvider

  /*--------------------------------------------------------------------------------
   * Implementations from interface RendererRegistration
   *--------------------------------------------------------------------------------
   */

  public ComponentShutdown registerRenderer(Class klass, Renderer renderer) throws ConfigException
  {
    if (m_class_renderers.containsKey(klass))
    { // renderer already registered - bogus!
      ConfigException ce = new ConfigException(ApplicationContainer.class,"ApplicationContainerMessages",
                                               "registerRenderer.already");
      ce.setParameter(0,klass.getName());
      throw ce;

    } // end if

    if (logger.isDebugEnabled())
      logger.debug("Registering a new renderer for class " + klass.getName());
    RegisteredRenderer rr = new RegisteredRenderer(klass,renderer);
    m_class_renderers.put(klass,rr);
    return rr;

  } // end registerRenderer

  public ComponentShutdown registerRenderer(DynamicClass dclass, Renderer renderer) throws ConfigException
  {
    if (m_dclass_renderers.containsKey(dclass))
    { // renderer already registered - bogus!
      ConfigException ce = new ConfigException(ApplicationContainer.class,"ApplicationContainerMessages",
                                               "registerRenderer.already");
      ce.setParameter(0,dclass.getName());
      throw ce;

    } // end if

    if (logger.isDebugEnabled())
      logger.debug("Registering a new renderer for dynamic class " + dclass.getName());
    RegisteredRenderer rr = new RegisteredRenderer(dclass,renderer);
    m_class_renderers.put(dclass,rr);
    return rr;

  } // end if

  /*--------------------------------------------------------------------------------
   * Implementations from interface ObjectProvider
   *--------------------------------------------------------------------------------
   */

  /**
   * Retrieves an object from this <CODE>ObjectProvider</CODE>.
   *
   * @param namespace The namespace to interpret the name relative to.
   * @param name The name of the object to be retrieved.
   * @return The object reference specified.
   */
  public Object getObject(String namespace, String name)
  {
    if (namespace.equals(Namespaces.SUBSTRATE_NAMESPACE))
      return m_substrate.getObject(namespace,name);

    if (namespace.equals(Namespaces.DATABASE_CONNECTIONS_NAMESPACE))
    { // get a database connection
      Object rc = m_connections.get(name);
      if (rc!=null)
	return rc;

    } // end if

    if (namespace.equals(Namespaces.DYNAMO_OBJECT_NAMESPACE))
    { // get an object
      Object rc = m_objects.get(name);
      if (rc!=null)
	return rc;

    } // end if

    if (namespace.equals(Namespaces.DYNAMO_APPLICATION_NAMESPACE))
    { // return one of the application data elements
      if (name.equals("application"))
	return m_application;
      else if (name.equals("identity"))
	return m_identity;
      else if (name.equals("__container__"))
	return this;

    } // end if

    throw new NoSuchObjectException("ApplicationContainer",namespace,name);

  } // end getObject

  /*--------------------------------------------------------------------------------
   * Implementations from interface EventListenerRegistration
   *--------------------------------------------------------------------------------
   */

  public ComponentShutdown registerApplicationListener(ApplicationListener listener)
  {
    m_application_listeners.add(listener);
    return new ShutdownVectorRemove(m_application_listeners,listener);

  } // end registerApplicationListener

  public ComponentShutdown registerSessionInfoListener(SessionInfoListener listener)
  {
    m_session_listeners.add(listener);
    return new ShutdownVectorRemove(m_session_listeners,listener);

  } // end registerSessionInfoListener

  public synchronized ComponentShutdown registerDynamicUpdateListener(Class event_type,
								      DynamicUpdateListener listener)
  {
    if (!(DynamicUpdateEvent.class.isAssignableFrom(event_type)))
      throw new IllegalArgumentException("event type is not valid");
    Vector vec = (Vector)(m_update_listeners.get(event_type));
    if (vec==null)
    { // creatr vector for this event type
      vec = new Vector();
      m_update_listeners.put(event_type,vec);

    } // end if

    vec.add(listener);
    return new ShutdownVectorRemove(vec,listener);

  } // end registerDynamicUpdateListener

  /*--------------------------------------------------------------------------------
   * Implementations from interface OutputObjectFilterRegistration
   *--------------------------------------------------------------------------------
   */

  public ComponentShutdown registerOutputObjectFilter(OutputObjectFilter filter)
  {
    if (logger.isDebugEnabled())
      logger.debug("Registering new OutputObjectFilter: " + filter);
    m_output_filters.add(filter);
    return new ShutdownVectorRemove(m_output_filters,filter);

  } // end registerOutputObjectFilter

  /*--------------------------------------------------------------------------------
   * Implementations from interface QueryRenderer
   *--------------------------------------------------------------------------------
   */

  public Renderer getRendererForObject(Object obj)
  {
    RegisteredRenderer rc = null;
    if (obj instanceof DynamicObject)
    { // this is a dynamic object - search by its dynamic class first
      DynamicClass dclass = ((DynamicObject)obj).getDClass();
      rc = (RegisteredRenderer)(m_dclass_renderers.get(dclass));
      if (rc==null)
      { // search for ancestor class, "snap" reference if possible
	rc = searchDClassRenderers(dclass);
	if (rc!=null)
	{ // add this class to the mapping
	  m_dclass_renderers.put(dclass,rc);
	  rc.add(dclass);

	} // end if

      } // end if

      if (rc!=null)
	return rc.getRenderer();

    } // end if

    Class klass = obj.getClass();
    rc = (RegisteredRenderer)(m_class_renderers.get(klass));
    if (rc==null)
    { // search for ancestor class, "snap" reference if possible
      rc = searchClassRenderers(klass);
      if (rc!=null)
      { // found it - add this class to the mapping
	m_class_renderers.put(klass,rc);
	rc.add(klass);

      } // end if

    } // end if

    return ((rc==null) ? null : rc.getRenderer());

  } // end getRendererForObject

  /*--------------------------------------------------------------------------------
   * Implementations from interface PostDynamicUpdate
   *--------------------------------------------------------------------------------
   */

  public void postUpdate(DynamicUpdateEvent event)
  {
    if (logger.isDebugEnabled())
      logger.debug("PostDynamicUpdate: posting an event of type " + event.getClass().getName());
    Class klass = event.getClass();
    for (;;)
    { // get the event listeners
      Vector vec = (Vector)(m_update_listeners.get(klass));
      if (vec!=null)
      { // call the event handlers
	Iterator it = vec.iterator();
	while (it.hasNext())
	  ((DynamicUpdateListener)(it.next())).updateReceived(event);

      } // end if

      if (klass==DynamicUpdateEvent.class)
	break;

      klass = klass.getSuperclass();

    } // end for (ever)

  } // end postUpdate

  /*--------------------------------------------------------------------------------
   * Implementations from interface RenderImmediate
   *--------------------------------------------------------------------------------
   */

  public String renderTextObject(Object obj) throws IOException, RenderingException
  {
    if (logger.isDebugEnabled())
      logger.debug("RenderImmediate rendering an object of type " + obj.getClass().getName());
    BufferTextRenderControl control = new BufferTextRenderControl(wrapOutputServices(null));
    control.renderSubObject(obj);
    return control.getData();

  } // end renderTextObject

  /*--------------------------------------------------------------------------------
   * Implementations from interface RequestPreprocessorRegistration
   *--------------------------------------------------------------------------------
   */

  public ComponentShutdown registerRequestPreprocessor(RequestPreprocessor rp)
  {
    if (logger.isDebugEnabled())
      logger.debug("Registering new RequestPreprocessor: " + rp);
    m_request_preprocessors.add(rp);
    return new ShutdownVectorRemove(m_request_preprocessors,rp);

  } // end registerRequestPreprocessor

  /*--------------------------------------------------------------------------------
   * Implementations from interface ExceptionTranslatorRegistration
   *--------------------------------------------------------------------------------
   */

  public ComponentShutdown registerExceptionTranslator(ExceptionTranslator xlat)
  {
    if (logger.isDebugEnabled())
      logger.debug("Registering new ExceptionTranslator: " + xlat);
    m_exception_xlators.add(xlat);
    return new ShutdownVectorRemove(m_exception_xlators,xlat);

  } // end registerExceptionTranslator

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  /**
   * Adds a reference to the reference count of the <CODE>ApplicationContainer</CODE>.  Each servlet
   * or other initializing component that attempts to initialize the <CODE>ApplicationContainer</CODE>
   * increases its reference count by 1.
   */
  public synchronized void addRef()
  {
    ++m_refs;
    if (logger.isDebugEnabled())
      logger.debug("ApplicationContainer.addRef(): refcount now " + m_refs);

  } // end addRef

  /**
   * Removes a reference from the reference count of the <CODE>ApplicationContainer</CODE>.  Each servlet that
   * references the <CODE>ApplicationContainer</CODE> and is destroyed decreases the reference count by 1.
   * When the reference count reaches 0, the <CODE>ApplicationContainer</CODE> is destroyed.
   *
   * @return <CODE>true</CODE> if the <CODE>ApplicationContainer</CODE> was destroyed, <CODE>false</CODE> if not.
   */
  public synchronized boolean release()
  {
    if (--m_refs==0)
    { // clean up this stuff
      if (logger.isDebugEnabled())
	logger.debug("ApplicationContainer.release(): refcount now 0 - destroying!");
      destroy();
      return true;

    } // end if

    if (logger.isDebugEnabled())
      logger.debug("ApplicationContainer.release(): refcount now " + m_refs);

    return false;

  } // end release

  /**
   * Returns the instance of the {@link com.silverwrist.dynamo.iface.Application Application} object defined in
   * the Dynamo XML configuration file and instantiated when the <CODE>ApplicationContainer</CODE> was created.
   *
   * @return The <CODE>Application</CODE> object.
   */
  public Application getApplication()
  {
    return m_application;

  } // end getApplication

  public ServiceProvider getInitServices()
  {
    return m_app_sm.createInitServices();

  } // end getInitServices

  public ServiceProvider wrapServices(ServiceProvider sp)
  {
    return m_app_sm.createRuntimeServices(sp);

  } // end wrapServices

  public ServiceProvider wrapOutputServices(ServiceProvider sp)
  {
    return m_app_sm.createOutputServices(sp);

  } // end wrapOutputServices

  /**
   * Returns a list of all registered {@link com.silverwrist.dynamo.event.SessionInfoListener SessionInfoListener}
   * objects.
   *
   * @return A list of all registered <CODE>SessionInfoListener</CODE> objects.
   */
  public SessionInfoListener[] getSessionListeners()
  {
    return (SessionInfoListener[])(m_session_listeners.toArray(SESSION_LISTENER_TEMPLATE));

  } // end getSessionListeners

  /**
   * Adds a session to our list of known sessions, which will be shut down when the application container
   * is itself destroyed.
   *
   * @param link The link to the session.
   */
  public void addSessionLink(ComponentShutdown link)
  {
    m_known_sessions.add(link);

  } // end addSessionLink

  /**
   * Removes a session from our list of known sessions, so it will no longer be automatically shut down
   * when the application container is itself destroyed.
   *
   * @param link The link to the session.
   */
  public void removeSessionLink(ComponentShutdown link)
  {
    m_known_sessions.remove(link);

  } // end removeSessionLink

  public void setServerHeader(HttpServletResponse resp)
  {
    resp.setHeader("Server",m_identity);

  } // end setServerHeader

  public Map getRewriteRuleMap()
  {
    return m_rewrite_rules;

  } // end getRewriteRuleMap

  public Object filterOutput(Object out, Request r) throws RenderingException
  {
    Object rc = out;
    for (int i=(m_output_filters.size()-1); i>=0; i--)
    { // look for new objects that filter this one
      OutputObjectFilter filt = (OutputObjectFilter)(m_output_filters.get(i));
      Object tmp_rc = filt.filterObject(rc,r);
      if (tmp_rc!=null)
	rc = tmp_rc;

    } // end for

    return rc;

  } // end filterOutput

  public void preprocessRequest(Request r)
  {
    for (int i=(m_request_preprocessors.size()-1); i>=0; i--)
    { // preprocess the request, if we have any preprocessors installed
      RequestPreprocessor rp = (RequestPreprocessor)(m_request_preprocessors.get(i));
      rp.preprocessRequest(r);

    } // end for

  } // end preprocessRequest

  public Object translateException(Request r, Exception e)
  {
    for (int i=(m_exception_xlators.size()-1); i>=0; i--)
    { // try to translate exceptions
      ExceptionTranslator xlat = (ExceptionTranslator)(m_exception_xlators.get(i));
      Object o = xlat.translateException(r,e);
      if (o!=null)
	return o;  // found a translation

    } // end for

    return e;  // return the exception itself as a last resort

  } // end translateException

  public String translateSubstratePathName(String input)
  {
    return StringUtils.replaceAllVariables(input,getSubstrateReplaceMap());

  } // end translateSubstratePathName

} // end class ApplicationContainer
