/*
 * 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 java.util.jar.*;
import org.apache.log4j.Logger;
import org.w3c.dom.*;
import com.silverwrist.util.xml.*;
import com.silverwrist.dynamo.Namespaces;
import com.silverwrist.dynamo.app.ApplicationContainer;
import com.silverwrist.dynamo.db.NamespaceCache;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;

public class ModuleManager implements NamedObject, ComponentInitialize, ComponentShutdown, ModuleOperations
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

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

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

  private String m_name;
  private ApplicationContainer m_appcon;
  private NamespaceCache m_nscache;
  private ModuleDBOps m_ops;
  private File m_mod_dir;
  private HashMap m_jar_to_module = new HashMap();
  private HashMap m_qname_to_module = new HashMap();
  private InstallServiceManager m_instservice;
  private ServiceProvider m_install_services;
  private ComponentShutdown m_shut1;
  private ComponentShutdown m_shut2;
  private ComponentShutdown m_shut3;

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

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

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

  private static final void pushFileList(LinkedList list, File file)
  {
    File[] subfiles = file.listFiles();
    if (subfiles==null)
      return;
    Arrays.sort(subfiles);
    for (int i=subfiles.length-1; i>=0; i--)
      list.addFirst(subfiles[i]);

  } // end pushFileList

  private static final boolean isDynamoModule(File f)
  {
    JarFile jf = null;
    try
    { // open this file as a JAR file
      jf = new JarFile(f);
      Manifest mft = jf.getManifest();
      if (mft==null)
	return false;
      Attributes attrs = mft.getMainAttributes();
      if (attrs==null)
	return false;
      if (attrs.getValue("X-Dynamo-Module-Class")!=null)
	return true;

    } // end try
    catch (IOException e)
    { // if there's an exception, we just bail
      return false;

    } // end catch
    finally
    { // clean up before we go
      try
      { // close the file
	if (jf!=null)
	  jf.close();

      } // end try
      catch (IOException e)
      { // ignore exceptions
      } // end catch

    } // end finally

    return false;

  } // end isDynamoModule

  /*--------------------------------------------------------------------------------
   * 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, conn_name = null, nscache_name = 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");

      // get the database configuration connection
      DOMElementHelper config_root_h = new DOMElementHelper(config_root);
      Element elt = loader.getSubElement(config_root_h,"database");
      conn_name = loader.getAttribute(elt,"connection");
      nscache_name = loader.getAttribute(elt,"namespaces");

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

    } // end catch

    // Get the database connection pool and namespace cache.
    DBConnectionPool pool = GetObjectUtils.getDatabaseConnection(services,conn_name);
    m_nscache =
        (NamespaceCache)(GetObjectUtils.getDynamoComponent(services,NamespaceCache.class,nscache_name));

    // Get the database operations object.
    m_ops = ModuleDBOps.get(pool);

    // 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

    // Get the install service manager and set up the install service provider.
    m_instservice = new InstallServiceManager();
    m_install_services = m_instservice.getServiceProvider(m_appcon.getInitServices());

    // 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();
      ml.dispose();

    } // 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_install_services = null;
    m_instservice.dispose();
    m_instservice = null;
    m_ops.dispose();
    m_ops = null;
    m_nscache = 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
      return (Module)(m_qname_to_module.get(qname));

    } // end synchronized block

  } // end findModule

  public synchronized Module findModuleByFilename(String filename)
  {
    // look for the right module already in the map
    return (Module)(m_jar_to_module.get(filename));

  } // end findModuleByFilename

  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,name,m_ops,m_nscache,m_install_services);

      } // 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();
	ml.dispose();

      } // end while

    } // end synchronized block

  } // end freeUnusedModules

  public List listAllModuleNames()
  {
    ArrayList rc = new ArrayList();
    int len = m_mod_dir.getAbsolutePath().length();
    LinkedList process = new LinkedList();
    pushFileList(process,m_mod_dir);
    while (process.size()>0)
    { // examine all files
      File f = (File)(process.removeFirst());
      if (f.isDirectory())
	pushFileList(process,f);  // list files in the subdirectory
      else if (isDynamoModule(f))
      { // strip off the base path and return the name
	String name = f.getAbsolutePath().substring(len);
	if (name.startsWith(File.pathSeparator))
	  name = name.substring(File.pathSeparator.length());
	rc.add(name);

      } // end else if

    } // end while

    if (rc.isEmpty())
      return Collections.EMPTY_LIST;
    rc.trimToSize();
    return Collections.unmodifiableList(rc);

  } // end listAllModuleNames

  public Set listInstalledModuleNames() throws DatabaseException
  {
    return m_ops.getInstalledModules();

  } // end listInstalledModuleNames

  public void loadInstalledModules() throws DatabaseException, ModuleException
  {
    Set set = m_ops.getInstalledModules();
    logger.info("loadInstalledModules(): " + set.size() + " module(s) to load");
    Iterator it = set.iterator();
    while (it.hasNext())
    { // load each of the installed modules in turn
      String mod_filename = (String)(it.next());
      logger.info("loadInstalledModules(): loading " + mod_filename);
      this.loadModule(mod_filename,true);
								       
    } // end while

  } // end loadInstalledModules

  public ComponentShutdown hookInstallServices(ServiceProvider sp)
  {
    return m_instservice.hook(sp);

  } // end hookInstallServices

} // end class ModuleManager
