/*
 * JBoss, the OpenSource EJB server
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.deployment;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import org.jboss.logging.Logger;
import org.jboss.metadata.XmlFileLoader;

/** An implementation of Installer that only works with unpackaged J2EE
 modules as local directories. There is no copying of package contents.

 @author Scott.Stark@jboss.org
*/
public class LocalDirInstaller implements Installer
{
   // the URL to install
   URL src;
   // the resulting Deployment
   Deployment d;
   
   // the log4j category for output
   Logger log;

   // flag to not run execute twice
   boolean done;
   
   /** Creates a new instance of ModuleInstaller */
    public LocalDirInstaller()
    {
    }

   public Deployment execute(InstallerFactory factory, URL src)
      throws J2eeDeploymentException, IOException
   {
      this.log = factory.log;
      d = new Deployment();
      d.name = Util.getName(src.toString());
      d.sourceUrl=src;

      File localPkg = null;
      boolean trace = log.isTraceEnabled();
      if( trace )
         log.trace("execute, src="+src);
      if( src.getProtocol().startsWith("file") == false )
         throw new J2eeDeploymentException("Only local file: URL are supported by LocalDirInstaller");

      try
      {
         localPkg = new File(src.getFile());
         // Determine the type of the deployment
         d.type = Util.hasDeploymentDescriptor(localPkg);
         if( d.type < 0 )
            throw new J2eeDeploymentException("LocalPkg: "+localPkg+" has no valid deployment descriptors");
         d.localUrl = localPkg.toURL();

         if( trace )
            log.trace("Deployment type is: "+d.type);

         switch(d.type)
         {
            // A stand-alone EJB module
            case Deployment.EJB_MODULE:
            {
               log.info("install EJB module "+d.name);               
               // Check for libs declared in the EJB jar manifest
               
               URL[] libs = resolveLibraries(localPkg);
               URL localURL = localPkg.toURL();
               d.addEjbModule(d.name, localURL, libs);
            }
            break;
            // A stand-alone WAR module
            case Deployment.WAR_MODULE:
            {
               /* Build a default webContext based on the deployment war name.
                We have to do this here because the warURL passed to the WAR
                deployer is the local copy war name. We could change the deployment
                interface to include the full deployment info as is done in 3.x,
                but this will work for now. We simply pass the original name of
                the war file as the context root with a leading null '\0'
                character. The WAR deployer has to understand this wacked
                */
               String webContext = "" + '\0' + d.name;
               log.info("inflate and install WEB module "+d.name);
               // Check for libs declared int the WAR jar manifest
               URL[] libs = libs = resolveLibraries(localPkg);
               URL localURL = localPkg.toURL();
               d.addWebModule(d.name, webContext, localURL, libs);
            }
            break;
            // A J2EE app with any number of ejb and war modules
            case Deployment.EAR_MODULE:
            {
               J2eeApplicationMetaData app = null;
               try
               {
                  URL appXml = new URL(localPkg.toURL(), Installer.files[d.type]);
                  Document appDoc = XmlFileLoader.getDocument(appXml, false);
                  Element root = appDoc.getDocumentElement();
                  app = new J2eeApplicationMetaData(root);
               }
               catch (DeploymentException e)
               {
                  throw new J2eeDeploymentException("Error in parsing application.xml", e);
               }
               
               // iterating the ejb and web modules and install them
               ArrayList ejbJars = new ArrayList();
               Iterator it = app.getModules();
               while( it.hasNext() )
               {
                  // iterate the ear modules
                  J2eeModuleMetaData mod = (J2eeModuleMetaData) it.next();
                  
                  if( mod.isEjb() )
                  {
                     String name = mod.getFileName();
                     log.debug("Process EJB module "+name);
                     try
                     {
                        File ejbPkg = new File(localPkg, name);
                        if( ejbPkg.isDirectory() == false )
                           throw new J2eeDeploymentException("EJB module: "+ejbPkg+" is not a directory");
                        URL[] libs = resolveLibraries(ejbPkg);
                        URL localURL = ejbPkg.toURL();
                        d.addEjbModule(name, localURL, libs);
                        ejbJars.add(localURL);
                     }
                     catch (IOException _ioe)
                     {
                        throw _ioe;
                     }
                     catch (NullPointerException _npe)
                     {
                        log.info("module "+name+" not found in "+d.name);
                        throw new J2eeDeploymentException("module "+name+" not found in "+d.name);
                     }
                  }
                  else if( mod.isWeb() )
                  {
                     String name = mod.getFileName();                     
                     String webContext = mod.getWebContext();
                     log.debug("Process WEB module "+name);
                     try
                     {                        
                        File warPkg = new File(localPkg, name);
                        if( warPkg.isDirectory() == false )
                           throw new J2eeDeploymentException("WAR module: "+warPkg+" is not a directory");
                        URL[] libs = resolveLibraries(warPkg);
                        URL localURL = warPkg.toURL();
                        d.addWebModule(name, webContext, localURL, libs);
                     }
                     catch (IOException _ioe)
                     {
                        throw _ioe;
                     }
                     catch (NullPointerException _npe)
                     {
                        log.info("module "+name+" not found in "+d.name);
                        throw new J2eeDeploymentException("module "+name+" not found in "+d.name);
                     }
                  }
                  // other packages we dont care about (currently)
               }

               // put all ejb jars to the common classpath too
               if( ejbJars.size() > 0 )
                  log.debug("Adding all ejb jar files to the common classpath");
               for(int e = 0; e < ejbJars.size(); e ++)
               {
                  URL jar = (URL) ejbJars.get(e);
                  d.commonUrls.add(jar);
               }
            }
            break;
         }
         //saveConfig();

      }
      catch(Throwable t)
      {
         log.error("Deployment failed", t);
      }
      return d;
   }

   /** Resolves all <strong>Class-Path:</strong> entries from the Manifest
    *  @param mf the Manifest to process
    *  @param baseURL the URL to which the Class-Path entries will be relative
    @return URL[] the array of URLs for the valid Class-Path entries
    */
   private URL[] resolveLibraries(File localFile)
   {
      URL[] libs = {};
      // Locate a manifest
      Manifest mf = null;
      URL baseURL = null;
      try
      {
         baseURL = localFile.toURL();
         File mfFile = new File(localFile, "META-INF" + File.separator + "MANIFEST.MF");
         if( mfFile.exists() == false )
         {
            mfFile = new File(localFile, "meta-inf" + File.separator + "manifest.mf");
         }
         if( mfFile.exists() == true )
         {
            FileInputStream fis = new FileInputStream(mfFile);
            mf = new Manifest(fis);
         }
      }
      catch(IOException e)
      {
         if( log.isTraceEnabled() )
            log.trace("resolveLibraries, error reading manifest", e);
         return libs;
      }

      String classPath = null;
      if( mf != null )
      {
         Attributes mainAttributes = mf.getMainAttributes();
         classPath = mainAttributes.getValue(Attributes.Name.CLASS_PATH);
      }

      if (classPath != null)
      {
         ArrayList tmp = new ArrayList();
         StringTokenizer st = new StringTokenizer(classPath);
         log.debug("resolveLibraries, Manifest Class-Path: "+classPath);
         while (st.hasMoreTokens())
         {
            String tk = st.nextToken();
            try
            {
               URL lib = new URL(baseURL, tk);
               tmp.add(lib);
               log.debug("added "+lib+" to common classpath");
            }
            catch (IOException _ioe)
            {
               log.warn("Failed to add "+tk+" to common classpath: "+_ioe.getMessage());
            }
         }
         libs = new URL[tmp.size()];
         tmp.toArray(libs);
      }
      return libs;
   }

}
