/*
 * 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.*;
import java.security.Principal;
import java.util.jar.*;
import org.apache.log4j.Logger;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;

class ModuleLoader extends URLClassLoader implements Module
{
  /*--------------------------------------------------------------------------------
   * Internal implementation of the module site
   *--------------------------------------------------------------------------------
   */

  private class Site implements ModuleSite, ResourceProvider
  {
    /*====================================================================
     * Constructor
     *====================================================================
     */

    Site()
    { // do nothing
    } // end constructor

    /*====================================================================
     * Implementations from interface ModuleSite
     *====================================================================
     */

    public ResourceProvider getResourceProvider()
    {
      return (ResourceProvider)this;

    } // end getResourceProvider

    /*====================================================================
     * Implementations from interface ResourceProvider
     *====================================================================
     */

    public InputStream getResource(String resource_path) throws IOException
    {
      URL url = urlFromPath(resource_path);
      if (logger.isDebugEnabled())
	logger.debug("Opening URL: " + url.toString());
      return url.openStream();

    } // end getResource

    public long getResourceModTime(String resource_path)
    {
      URL url = urlFromPath(resource_path);
      if (logger.isDebugEnabled())
	logger.debug("Opening URL: " + url.toString());
      try
      { // open the connection, get the JAR entry, and from that, get the mod time
	JarURLConnection conn = (JarURLConnection)(url.openConnection());
	JarEntry ntry = conn.getJarEntry();
	if (ntry==null)
	  throw new NoSuchResourceException(resource_path);
	return ntry.getTime();

      } // end try
      catch (IOException e)
      { // just translate this exception
	logger.error("I/O error loading from " + url.toString(),e);
	throw new NoSuchResourceException(resource_path,e);

      } // end catch

    } // end getResourceModTime

  } // end class Site

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

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

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

  private URL m_jar_url;
  private String m_resource_path;
  private ModuleFunctions m_modfuncs;
  private boolean m_initialized = false;

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

  ModuleLoader(File modfile) throws ModuleException, MalformedURLException
  {
    super(new URL[] { modfile.toURL() },ModuleLoader.class.getClassLoader());
    logger.info("Loading module " + modfile.getAbsolutePath());
    String main_class_name = null;
    try
    { // create a JAR URL and use it to get the JAR attributes
      m_jar_url = new URL("jar","",modfile.toURL() + "!/");
      JarURLConnection conn = (JarURLConnection)(m_jar_url.openConnection());
      Attributes attrs = conn.getMainAttributes();

      // get the main class name
      main_class_name = attrs.getValue("X-Dynamo-Module-Class");
      if (main_class_name==null)
      { // this JAR is obviously not a Dynamo module
	logger.error("ModuleLoader: " + modfile.getAbsolutePath() + " does not have a defined module class");
	ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","jar.notModule");
	me.setParameter(0,modfile.getAbsolutePath());
	throw me;

      } // end if

      if (logger.isDebugEnabled())
	logger.debug("Module main classname: " + main_class_name);

      // load the main class
      Class main_class = null;
      try
      { // attempt to load the class
	main_class = super.loadClass(main_class_name,true);

      } // end try
      catch (RuntimeException e)
      { // if there's some weird class loading error, comes in here
	logger.error("ClassLoader error loading " + main_class_name,e);
	ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","class.noLoad",e);
	me.setParameter(0,modfile.getAbsolutePath());
	me.setParameter(1,main_class_name);
	throw me;

      } // end catch

      if (!(ModuleFunctions.class.isAssignableFrom(main_class)))
      { // the module class is not of the right type
	logger.error("ModuleLoader: " + modfile.getAbsolutePath() + " module class " + main_class_name
		     + " is not a ModuleFunctions implementor");
	ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","class.notModule");
	me.setParameter(0,modfile.getAbsolutePath());
	me.setParameter(1,main_class_name);
	throw me;

      } // end if

      // Create instance of the module functions object
      m_modfuncs = (ModuleFunctions)(main_class.newInstance());

      // Create the resource loader URL.
      m_resource_path = attrs.getValue("X-Dynamo-Resource-Prefix");
      if (logger.isDebugEnabled())
	logger.debug("Resource path " + m_resource_path);

    } // end try
    catch (IOException e)
    { // unable to read JAR file
      logger.error("Error reading JAR file " + modfile.getAbsolutePath(),e);
      ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","jar.readError",e);
      me.setParameter(0,modfile.getAbsolutePath());
      throw me;

    } // end catch
    catch (ClassNotFoundException e)
    { // the class was not present
      logger.error("Module class " + main_class_name + " not found",e);
      ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","class.noLoad",e);
      me.setParameter(0,modfile.getAbsolutePath());
      me.setParameter(1,main_class_name);
      throw me;

    } // end catch
    catch (IllegalAccessException e)
    { // unable to access constructor
      ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","class.noCreate",e);
      me.setParameter(0,modfile.getAbsolutePath());
      me.setParameter(1,main_class_name);
      throw me;

    } // end catch
    catch (InstantiationException e)
    { // unable to instantiate class
      ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","class.noCreate",e);
      me.setParameter(0,modfile.getAbsolutePath());
      me.setParameter(1,main_class_name);
      throw me;

    } // end catch
    catch (ExceptionInInitializerError e)
    { // exception initializing the class
      ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","class.noCreate",e);
      me.setParameter(0,modfile.getAbsolutePath());
      me.setParameter(1,main_class_name);
      throw me;

    } // end catch
    catch (ClassCastException e)
    { // could not cast to ModuleFunctions
      ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","class.notModule",e);
      me.setParameter(0,modfile.getAbsolutePath());
      me.setParameter(1,main_class_name);
      throw me;

    } // end catch

  } // end constructor

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

  private final URL urlFromPath(String resource_path)
  {
    try
    { // check the resource path and build the URL
      String foo;
      if (m_resource_path==null)
	foo = resource_path;
      else
	foo = m_resource_path + resource_path;
      if (logger.isDebugEnabled())
	logger.debug("Appending " + foo + " to " + m_jar_url.toString());
      return new URL(m_jar_url,foo);

    } // end try
    catch (MalformedURLException e)
    { // yak!  bail out!
      throw new NoSuchResourceException(resource_path,e);

    } // end catch

  } // end urlFromPath

  /*--------------------------------------------------------------------------------
   * Implementations from interface Module
   *--------------------------------------------------------------------------------
   */

  public QualifiedNameKey getModuleID()
  {
    return m_modfuncs.getModuleID();

  } // end getModuleID

  public void install(ServiceProvider services, Principal installer) throws ModuleException
  {
    if (!(m_modfuncs.canInstall(services,installer)))
    { // you are not permitted to install or uninstall modules
      ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","module.noAuth");
      me.setParameter(0,m_modfuncs.getModuleID().toString());
      throw me;

    } // end if

    if (m_initialized)
    { // the object cannot be initialized first!
      ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","module.alreadyInit");
      me.setParameter(0,m_modfuncs.getModuleID().toString());
      throw me;

    } // end if

    ModuleSite s = new Site();
    m_modfuncs.install(s,services,installer);
    m_modfuncs.initialize(s,services);
    m_initialized = true;

  } // end install

  public void uninstall(ServiceProvider services, Principal uninstaller) throws ModuleException
  {
    if (!(m_modfuncs.canInstall(services,uninstaller)))
    { // you are not permitted to install or uninstall modules
      ModuleException me = new ModuleException(ModuleLoader.class,"ModuleMessages","module.noAuth");
      me.setParameter(0,m_modfuncs.getModuleID().toString());
      throw me;

    } // end if

    if (m_initialized)
    { // uninitialize this module before uninstalling it
      m_modfuncs.shutdown();
      m_initialized = false;

    } // end if

    m_modfuncs.uninstall(new Site(),services,uninstaller);

  } // end uninstall

  public DynamicObject getProvidedObject(String namespace, String name) throws ModuleException
  {
    return m_modfuncs.getProvidedObject(namespace,name);

  } // end getProvidedObject

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

  void initialize(ServiceProvider services) throws ModuleException
  {
    m_modfuncs.initialize(new Site(),services);
    m_initialized = true;

  } // end initialize

  void shutdown()
  {
    m_modfuncs.shutdown();
    m_initialized = false;

  } // end shutdown

  boolean isInitialized()
  {
    return m_initialized;

  } // end isInitialized

  boolean inUse()
  {
    return (m_initialized ? m_modfuncs.inUse() : false);

  } // end inUse

} // end class ModuleLoader
