/*
 * 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) 2003 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.dynamo.module;

import java.io.*;
import java.net.MalformedURLException;
import java.util.*;
import org.w3c.dom.*;
import com.silverwrist.util.xml.*;
import com.silverwrist.dynamo.Namespaces;
import com.silverwrist.dynamo.app.ApplicationContainer;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;

public class ModuleManager implements NamedObject, ComponentInitialize, ComponentShutdown, ModuleOperations
{
  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private String m_name;
  private ApplicationContainer m_appcon;
  private File m_mod_dir;
  private HashMap m_jar_to_module = new HashMap();
  private HashMap m_qname_to_module = new HashMap();
  private ComponentShutdown m_shut1;
  private ComponentShutdown m_shut2;
  private ComponentShutdown m_shut3;

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

  public ModuleManager()
  { // do nothing
  } // end constructor

  /*--------------------------------------------------------------------------------
   * Implementations from interface NamedObject
   *--------------------------------------------------------------------------------
   */

  public String getName()
  {
    return m_name;

  } // end getName

  /*--------------------------------------------------------------------------------
   * Implementations from interface ComponentInitialize
   *--------------------------------------------------------------------------------
   */

  /**
   * Initialize the component.
   *
   * @param config_root Pointer to the section of the Dynamo XML configuration file that configures this
   *                    particular component.  This is to be considered "read-only" by the component.
   * @param services An implementation of {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider}
   *                 which provides initialization services to the component.  This will include an implementation
   *                 of {@link com.silverwrist.dynamo.iface.ObjectProvider ObjectProvider} which may be used to
   *                 get information about other objects previously initialized by the application.
   * @exception com.silverwrist.dynamo.except.ConfigException If an error is encountered in the component
   *            configuration.
   */
  public void initialize(Element config_root, ServiceProvider services) throws ConfigException
  {
    XMLLoader loader = XMLLoader.get();
    String mod_dir = null;
    try
    { // verify the right node name
      loader.verifyNodeName(config_root,"object");

      // get the object's name
      m_name = loader.getAttribute(config_root,"name");

      // get the raw template directory
      mod_dir = loader.getSubElementText(config_root,"module-directory");

    } // end try
    catch (XMLLoadException e)
    { // error loading XML config data
      throw new ConfigException(e);

    } // end catch

    // Get the application container.
    ObjectProvider op = (ObjectProvider)(services.queryService(ObjectProvider.class));
    m_appcon = (ApplicationContainer)(op.getObject(Namespaces.DYNAMO_APPLICATION_NAMESPACE,"__container__"));

    // Create the module directory object.
    m_mod_dir = new File(m_appcon.translateSubstratePathName(mod_dir));
    if (!(m_mod_dir.isDirectory()))
    { // module directory does not exist - this is an error
      ConfigException ce = new ConfigException(ModuleManager.class,"ModuleMessages","moddir.notexist");
      ce.setParameter(0,m_mod_dir.getAbsolutePath());
      throw ce;

    } // end if

    // Hook this into the service providers.
    SingletonServiceProvider ssp = new SingletonServiceProvider("ModuleManager",ModuleOperations.class,this);
    HookServiceProviders hooker = (HookServiceProviders)(services.queryService(HookServiceProviders.class));
    m_shut1 = hooker.hookInitServiceProvider(ssp);
    m_shut2 = hooker.hookRuntimeServiceProvider(ssp);
    m_shut3 = hooker.hookOutputServiceProvider(ssp);

  } // end initialize

  /*--------------------------------------------------------------------------------
   * Implementations from interface ComponentShutdown
   *--------------------------------------------------------------------------------
   */

  public void shutdown()
  {
    // Get the list of all modules and clear the maps.
    LinkedList tmp = new LinkedList(m_jar_to_module.values());
    m_jar_to_module.clear();
    m_qname_to_module.clear();

    // Shut down all the modules.
    while (!(tmp.isEmpty()))
    { // remove modules one at a time and shut them down
      ModuleLoader ml = (ModuleLoader)(tmp.removeFirst());
      if (ml.isInitialized())
	ml.shutdown();

    } // end while

    // Shut down everything else.
    m_shut3.shutdown();
    m_shut3 = null;
    m_shut2.shutdown();
    m_shut2 = null;
    m_shut1.shutdown();
    m_shut1 = null;
    m_appcon = null;

  } // end shutdown

  /*--------------------------------------------------------------------------------
   * Implementations from interface ModuleOperations
   *--------------------------------------------------------------------------------
   */

  public Module findModule(String namespace, String name)
  {
    QualifiedNameKey qname = new QualifiedNameKey(namespace,name);
    synchronized (this)
    { // look up module in hash table
      ModuleLoader rc = (ModuleLoader)(m_qname_to_module.get(qname));
      if (rc!=null)
	return (Module)rc;

    } // end synchronized block

    return null;

  } // end findModule

  public Module loadModule(String name, boolean initialize) throws ModuleException
  {
    ModuleLoader rc = null;
    synchronized (this)
    { // look for the right module already in the map
      rc = (ModuleLoader)(m_jar_to_module.get(name));
      if (rc!=null)
	return (Module)rc;  // found it already

      // create the JAR file reference
      File mod_file = new File(m_mod_dir,name);
      if (!(mod_file.canRead()))
      { // unable to read the module file
	ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","jar.notFound");
	me.setParameter(0,mod_file.getAbsolutePath());
	throw me;

      } // end if

      try
      { // create a new ModuleLoader
	rc = new ModuleLoader(mod_file);

      } // end try
      catch (MalformedURLException e)
      { // the module URL is bad
	ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","jar.badURL",e);
	me.setParameter(0,mod_file.getAbsolutePath());
	throw me;

      } // end catch

      // enter this module into the maps
      QualifiedNameKey key = rc.getModuleID();
      m_qname_to_module.put(key,rc);
      m_jar_to_module.put(name,rc);

    } // end synchronized block

    // initialize the module if we need to do that
    if (initialize)
      rc.initialize(m_appcon.getInitServices());

    return (Module)rc;

  } // end loadModule

  public void freeUnusedModules()
  {
    HashMap tmp = new HashMap();
    synchronized (this)
    { // build the map of modules to free
      Iterator it = m_jar_to_module.entrySet().iterator();
      while (it.hasNext())
      { // test it and build the map to remove
	Map.Entry ntry = (Map.Entry)(it.next());
	ModuleLoader ml = (ModuleLoader)(ntry.getValue());
	if (!(ml.inUse()))
	  tmp.put(ntry.getKey(),ml.getModuleID());

      } // end while

      it = tmp.entrySet().iterator();
      while (it.hasNext())
      { // loop and remove the modules, shutting them down as we go
	Map.Entry ntry = (Map.Entry)(it.next());
	ModuleLoader ml = (ModuleLoader)(m_qname_to_module.get(ntry.getValue()));
	m_jar_to_module.remove(ntry.getKey());
	m_qname_to_module.remove(ntry.getValue());
	if (ml.isInitialized())
	  ml.shutdown();

      } // end while

    } // end synchronized block

  } // end freeUnusedModules

} // end class ModuleManager
