/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

package com.sun.enterprise.deployment.archivist;

import javax.xml.parsers.SAXParser;
import org.xml.sax.SAXParseException;
import org.xml.sax.SAXException;
import org.xml.sax.InputSource;

import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.util.zip.ZipEntry;

import java.util.logging.Logger;
import java.util.logging.Level;

import javax.enterprise.deploy.shared.ModuleType;

import com.sun.enterprise.deployment.deploy.shared.AbstractArchive;
import com.sun.enterprise.deployment.deploy.shared.FileArchive;
import com.sun.enterprise.deployment.deploy.shared.InputJarArchive;
import com.sun.enterprise.deployment.deploy.shared.Archive;
import com.sun.enterprise.deployment.io.*;
import com.sun.enterprise.deployment.io.runtime.ApplicationRuntimeDDFile;
import com.sun.enterprise.deployment.node.ApplicationNode;
import com.sun.enterprise.deployment.util.ApplicationValidator;
import com.sun.enterprise.deployment.util.ApplicationVisitor;
import com.sun.enterprise.deployment.util.DOLUtils;
import com.sun.enterprise.deployment.util.ModuleDescriptor;
import com.sun.enterprise.deployment.*;
import com.sun.enterprise.deployment.xml.ConnectorTagNames;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.util.io.FileUtils;
import com.sun.enterprise.util.shared.ArchivistUtils;

/**
 * This class is responsible for handling application archive files
 *
 * @author  Jerome Dochez
 * @version
 */
public class ApplicationArchivist extends Archivist {
    
    /**
     * The application archivist handles J2EE Application archive file
     */
    Application application = null;
    
    /**
     * The DeploymentDescriptorFile handlers we are delegating for XML i/o
     */
    DeploymentDescriptorFile standardDD = new ApplicationDeploymentDescriptorFile();   
    
    /** resources... */
    private static LocalStringManagerImpl localStrings =
	    new LocalStringManagerImpl(ApplicationArchivist.class);        
    
    /** Creates new ApplicationArchivist */
    public ApplicationArchivist() {
        handleRuntimeInfo = true;
    }
    
    /**
     * @return the  module type handled by this archivist
     * as defined in the application DTD
     *
     */
    public ModuleType getModuleType() {
        return ModuleType.EAR;
    }
    
    /**
     * Set the DOL descriptor  for this Archivist, used by super classes
     */
    public void setDescriptor(Descriptor descriptor) {
        if (descriptor instanceof Application) {
            application = (Application) descriptor;
        } else {
            throw new RuntimeException("Error setting descriptor in " + this);
        }
    }    
            
    /**
     * writes the content of an archive to a JarFile
     *
     * @param the descriptors to use for writing
     * @param the output stream to write to
     */
    protected void writeContents(AbstractArchive in, AbstractArchive out) throws IOException {
        
        Vector filesToSkip = new Vector();
        
        if(DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {	
	    DOLUtils.getDefaultLogger().fine("Write " + out.getArchiveUri() + " with " + this);
	}
         
        // any files already written to the output should never be rewritten
        for (Enumeration alreadyWritten = out.entries(); alreadyWritten.hasMoreElements();) {
            String elementName = (String) alreadyWritten.nextElement();
            filesToSkip.add(elementName);
        }
                
        // write this application .ear file contents...
        for (Iterator modules  = application.getModules();modules.hasNext();) {
            ModuleDescriptor aModule = (ModuleDescriptor) modules.next();
            Archivist subArchivist = getPluggableArchivists().getArchivistForType(aModule.getModuleType());
            subArchivist.initializeContext(this);
            subArchivist.setModuleDescriptor(aModule);
            if(DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {
                DOLUtils.getDefaultLogger().info("Write " + aModule.getArchiveUri() + " with " + subArchivist);
            }
            
            if (aModule.getAlternateDescriptor()!=null) {
                // no need to rewrite the original bundle since
                // the deployment descriptors are saved at the application level
                // so I don't put it in the list of files to be skipped and it will
                // be copied as a library.
                
                // but I need to save the deployment descriptor for this bundle
                OutputStream os = out.putNextEntry(aModule.getAlternateDescriptor());
                subArchivist.writeStandardDeploymentDescriptors(os);
                out.closeEntry();
		
		// now write runtime descriptors 
                if (isHandlingRuntimeInfo()) {
                    os = out.putNextEntry("sun-" + aModule.getAlternateDescriptor());
                    subArchivist.writeRuntimeDeploymentDescriptors(os);
                    out.closeEntry();
                }
                
            } else {
                // Create a new jar file inside the application .ear
                AbstractArchive internalJar = out.getEmbeddedArchive(aModule.getArchiveUri());
                
                // we need to copy the old archive to a temp file so
                // the save method can copy its original contents from
                InputStream is = in.getEntry(aModule.getArchiveUri());
                File tmpFile=null;
                try {
                    if (in.supportsElementsOverwriting()) {
                        subArchivist.setArchiveUri(internalJar.getArchiveUri());
                    } else {
                        tmpFile = getTempFile(path);
                        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tmpFile));
                        ArchivistUtils.copy(is, bos);
                        
                        // configure archivist
                        subArchivist.setArchiveUri(tmpFile.getAbsolutePath());
                    }
                    subArchivist.writeContents(internalJar);
                    out.closeEntry(internalJar);
                    
                } catch(IOException ioe) {
                    throw ioe;
                } finally {
                    if (tmpFile!=null)
                        tmpFile.delete();
                }
                
                // no need to copy the bundle from the original jar file
                filesToSkip.add(aModule.getArchiveUri());
            }
        }
        
        // now write the old contents and new descriptors
        super.writeContents(in, out, filesToSkip);
    }
    
    /**
     * @return the descriptor associated with the archivist
     */
    public Descriptor getDescriptor() {
        return application;
    }

    /**
     * @return a default BundleDescriptor for this archivist
     */
    public Descriptor getDefaultBundleDescriptor() {
        Application appDesc = new Application();
        return appDesc;
    }
    
    /**
     * open a new application archive file, read all the deployment descriptors
     *
     * @param path the file path for the J2EE Application archive
     */
    public RootDeploymentDescriptor open(AbstractArchive appArchive) 
        throws IOException, SAXParseException { 
        
        setManifest(appArchive.getManifest());
        
        // read the standard deployment descriptors
        Descriptor appDesc = readStandardDeploymentDescriptor(appArchive);
        setDescriptor(appDesc);

        // Now that we have parsed the standard DD, let's read all the
        // PersistenceUnits defined in the ear level.
        readPersistenceDeploymentDescriptors(appArchive, getDescriptor());

        // read the modules deployment descriptors
        if (!readModulesDescriptors((Application) appDesc, appArchive))
            return null;
        
        // now read the runtime deployment descriptors
        super.readRuntimeDeploymentDescriptor(appArchive, appDesc);

        // validate...
        if (classLoader!=null && isHandlingRuntimeInfo()) {
            validate(null);
        }
        
        return (RootDeploymentDescriptor) appDesc;
    }
    
    /**
     * read the modules deployment descriptor from this application object using
     * the passed archive
     * @param application containing the list of modules.
     * @param appArchive containing the sub modules files.
     * @return true if everything went fine
     */
    public boolean readModulesDescriptors(Application app, AbstractArchive appArchive)
        throws IOException, SAXParseException { 
        
        for (Iterator modules  = app.getModules();modules.hasNext();) {
            ModuleDescriptor aModule = (ModuleDescriptor) modules.next();
            if(DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {
                DOLUtils.getDefaultLogger().fine("Opening sub-module " + aModule);
            }
            Descriptor descriptor = null;
            Archivist newArchivist = getPluggableArchivists().getArchivistForType(aModule.getModuleType());
            newArchivist.initializeContext(this);
            newArchivist.setRuntimeXMLValidation(this.getRuntimeXMLValidation());
            newArchivist.setRuntimeXMLValidationLevel(
                this.getRuntimeXMLValidationLevel());

            AbstractArchive embeddedArchive = appArchive.getEmbeddedArchive(aModule.getArchiveUri());                
            if (aModule.getAlternateDescriptor()!=null) {
                // the module use alternate deployement descriptor, ignore the
                // DDs in the archive.
                InputStream is = appArchive.getEntry(aModule.getAlternateDescriptor());
                DeploymentDescriptorFile ddFile = newArchivist.getStandardDDFile();
                ddFile.setXMLValidation(newArchivist.getXMLValidation());
                ddFile.setXMLValidationLevel(newArchivist.getXMLValidationLevel());
                if (appArchive.getArchiveUri()!=null) {
                    ddFile.setErrorReportingString(appArchive.getArchiveUri());
                }

                descriptor = ddFile.read(is);
                is.close();
                newArchivist.readWebServicesDescriptor(embeddedArchive, descriptor);
                newArchivist.readPersistenceDeploymentDescriptors(embeddedArchive, descriptor);
                newArchivist.postStandardDDsRead((RootDeploymentDescriptor) descriptor, embeddedArchive);
                newArchivist.readAnnotations(embeddedArchive, (BundleDescriptor)descriptor);
                newArchivist.postAnnotationProcess((BundleDescriptor)descriptor, embeddedArchive); 
                newArchivist.postOpen((RootDeploymentDescriptor) descriptor, embeddedArchive);
                // now reads the runtime deployment descriptor...
                if (isHandlingRuntimeInfo()) {
                    is = appArchive.getEntry("sun-" + aModule.getAlternateDescriptor());
                    if (is!=null) {
                        DeploymentDescriptorFile confDD = 
                            newArchivist.getConfigurationDDFile();
                        confDD.setXMLValidation(
                            newArchivist.getRuntimeXMLValidation());
                        confDD.setXMLValidationLevel(
                            newArchivist.getRuntimeXMLValidationLevel());
                        if (appArchive.getArchiveUri()!=null) {
                            confDD.setErrorReportingString(
                                appArchive.getArchiveUri());
                        }

                        confDD.read(descriptor, is);
                        is.close();
                        newArchivist.postRuntimeDDsRead((RootDeploymentDescriptor)descriptor, embeddedArchive);
                    } else {
                        if (embeddedArchive!=null) {
                            newArchivist.readRuntimeDeploymentDescriptor(embeddedArchive,descriptor);
                        }
                    }
                }
            } else {
                // open the subarchive to get the deployment descriptor...
                if (embeddedArchive!=null) {
                    descriptor = newArchivist.open(embeddedArchive);
                } else {
                    DOLUtils.getDefaultLogger().info(localStrings.getLocalString(
                        "enterprise.deployment.cannotfindmodule",
                        "Cannot find module {0} in application bundle", 
                        new Object[] {aModule.getArchiveUri()}));
                    return false;
                }
            }
            if (embeddedArchive!=null) {
                embeddedArchive.close();
            }
            if (descriptor != null && descriptor instanceof BundleDescriptor) {
                aModule.setDescriptor((BundleDescriptor) descriptor);
                ((BundleDescriptor) descriptor).setApplication(app);
                aModule.setManifest(newArchivist.getManifest());
            } else {
                // display a message only if we had a handle on the sub archive
                if (embeddedArchive!=null) {
                    DOLUtils.getDefaultLogger().info(localStrings.getLocalString(
                        "enterprise.deployment.cannotreadDDs",
                        "Cannot read the Deployment Descriptors for module {0}", 
                        new Object[] {aModule.getArchiveUri()}));
                }
                return false;
            }
        }        
        return true;
    }
    
    /**
     * Read the runtime deployment descriptors (can contained in one or 
     * many file) set the corresponding information in the passed descriptor.
     * By default, the runtime deployment descriptors are all contained in 
     * the xml file characterized with the path returned by 
     * @see getRuntimeDeploymentDescriptorPath
     *
     * @param the initialized deployment descriptor
     */
    public void readRuntimeDeploymentDescriptor(AbstractArchive archive, Descriptor descriptor)  
        throws IOException, SAXParseException {    
        
        if (descriptor instanceof Application) {
            Application application = (Application) descriptor;
            
            // each modules first...
            for (Iterator itr=application.getModules();itr.hasNext();) {
                ModuleDescriptor md = (ModuleDescriptor) itr.next(); 
                Archivist archivist = getPluggableArchivists().getArchivistForType(md.getModuleType());
                archivist.initializeContext(this);
                archivist.setRuntimeXMLValidation(
                    this.getRuntimeXMLValidation());
                archivist.setRuntimeXMLValidationLevel(
                    this.getRuntimeXMLValidationLevel());
                InputStream is = null;
                
                if (md.getAlternateDescriptor()!=null) {
                    // we are using alternate deployment descriptors
                    is = archive.getEntry("sun-" + md.getAlternateDescriptor());
                    if (is!=null) {
                        DeploymentDescriptorFile confDD =
                            archivist.getConfigurationDDFile();
                        confDD.setXMLValidation(
                            archivist.getRuntimeXMLValidation());
                        confDD.setXMLValidationLevel(
                            archivist.getRuntimeXMLValidationLevel());
                        if (archive.getArchiveUri()!=null) {
                            confDD.setErrorReportingString(
                                archive.getArchiveUri());
                        }
                        confDD.read(md.getDescriptor(), is);
                        is.close();
                    }                    
                }
                // if is variable is null, it means that we are either 
                // not using alternate deployment descriptors or we could 
                // not find the appropriate sun-???.xml alternate DD.
                if (is==null) {
                    AbstractArchive subArchive = archive.getEmbeddedArchive(md.getArchiveUri());
                    archivist.readRuntimeDeploymentDescriptor(subArchive,  md.getDescriptor());
                }
            }
        }
        // for the application
        super.readRuntimeDeploymentDescriptor(archive,  descriptor);
    }

    /**
     * Read the runtime deployment descriptors (can contained in one or 
     * many file) from a deployment plan archive,  set the corresponding 
     * information in the passed descriptor.
     */
    public void readRuntimeDDFromDeploymentPlan(
            AbstractArchive planArchive, Descriptor descriptor)  
        throws IOException, SAXParseException {

        if (planArchive == null) {
            return;
        }

        // list of entries in the deployment plan
        Vector dpEntries = new Vector();
        for (Enumeration e = planArchive.entries(); e.hasMoreElements();) {
            dpEntries.add(e.nextElement());
        }

        if (descriptor instanceof Application) {
            Application application = (Application) descriptor;
            
            //runtime deployment descriptor for the sub modules
            for (Iterator itr = application.getModules(); itr.hasNext();) {
                ModuleDescriptor moduleDesc = (ModuleDescriptor) itr.next();
                Archivist subArchivist = ArchivistFactory.getArchivistForType(moduleDesc.getModuleType());
                String archiveUri = moduleDesc.getArchiveUri();
                String runtimeDDPath = subArchivist.getRuntimeDeploymentDescriptorPath();

                if (runtimeDDPath!=null) {
                    String mangledName;
                    // the runtime deployment descriptor from the deployment file
                    mangledName = archiveUri + "." 
                        + runtimeDDPath.substring(runtimeDDPath.lastIndexOf('/')+1);
                    DOLUtils.getDefaultLogger().fine("mangledName is " + mangledName);

                    if (dpEntries.contains(mangledName)) {
                        subArchivist.readRuntimeDDFromDeploymentPlan(
                            mangledName, planArchive, moduleDesc.getDescriptor());
                    }
                }
            }
        }

        //for sun-application.xml
        super.readRuntimeDDFromDeploymentPlan(planArchive, descriptor);
    }
    
    /**
     * validates the DOL Objects associated with this archivist, usually
     * it requires that a class loader being set on this archivist or passed
     * as a parameter
     */
    public void validate(ClassLoader aClassLoader) {
        ClassLoader cl = aClassLoader;
        if (cl==null) {
            cl = classLoader;
        }
        if (cl==null) {
            return;
        }
	application.setClassLoader(cl);
        application.visit((ApplicationVisitor) new ApplicationValidator());
        
    }    
    
    /**
     * add a new module to this archivist
     *
     * @param path for the new module to be added
     * @param set of library jars for this module
     * @param external deployment descriptor to use instead of the
     * the ones in the jar file.
     */
    public ModuleDescriptor addArchive(String archivePath, Set libraryJars, String externalDD) 
        throws SAXParseException, IOException {
        
        AbstractArchive newArchive = abstractArchiveFactory.openArchive(archivePath);
        return addArchive(newArchive, libraryJars, externalDD);
    }
    
    /**
     * add a new module to this archivist
     *
     * @param AbstractArchive for the new module to be added
     * @param set of library jars for this module
     * @param external deployment descriptor to use instead of the
     * the ones in the jar file.
     */
    public ModuleDescriptor addArchive(AbstractArchive newArchive, Set libraryJars, String externalDD) 
        throws SAXParseException, IOException {        
            
        // let's create a tmp file to store the new application .ear
        File outputFile=null;
        ModuleDescriptor newModule = null;
        try {
            outputFile = getTempFile(path);
            AbstractArchive out = abstractArchiveFactory.createArchive(outputFile.getAbsolutePath());
        
            // let's create a new module descriptor for this module
            newModule = getModuleFor(out, newArchive, libraryJars, externalDD);
            application.addModule(newModule);
                    
            // this should take care of writing the sub modules with the right
            // manifests and deployment descriptors settings...
            AbstractArchive in = abstractArchiveFactory.openArchive(path);        
            Vector filesToSkip = new Vector();
            filesToSkip.add(newModule.getArchiveUri());
            writeContents(in, out, filesToSkip);
            in.close();

            out.close();
        
            // rename the tmp file
            renameTmp(outputFile.getAbsolutePath(), path);
            
        } finally {
            if (outputFile!=null)
                outputFile.delete();
        }   
        return newModule;
    }
    
    
    /**
     * creates a new module descriptor for a J2EE Archive to be added to the current 
     * application archive. 
     * 
     * @param abstract archive for the application archive file
     * @param the archive file path to be added
     * @param set of library jars to add to the classpath of this module
     * @param optional external deployment descriptors
     *
     * @return an initialized ModuleDescriptor for the new module
     */
    private ModuleDescriptor getModuleFor(AbstractArchive out, AbstractArchive newArchive, Set libraryJars, String externalDD) 
        throws SAXParseException, IOException {
    
        // Let's get an archivist for the new archive to be added and delegate
        Archivist newArchivist = getPluggableArchivists().getArchivistForArchive(newArchive);
        newArchivist.initializeContext(this);
        newArchivist.setArchiveUri(newArchive.getArchiveUri());
        ModuleDescriptor newModule = newArchivist.addToArchive(this, externalDD);
        newArchivist.prepareForInclusion(out);
                        
        // now update the manifest for this archivist... and writes the libray jars
        // to our application archive...
        if (libraryJars!=null && !libraryJars.isEmpty()) {
            StringBuffer classPath = new StringBuffer();
            for (Iterator e = libraryJars.iterator(); e.hasNext();) {
                String libUri = (String) e.next();
                File libFile = new File(libUri);
                if (libFile.exists()) {
                    classPath.append(libUri);
                    classPath.append(' ');
                    addFileToArchive(out, libFile.getAbsolutePath(), libUri);
                } else {
                    throw new FileNotFoundException("Library file " + libFile.getAbsolutePath() + " not found");
                }
            }
            Manifest manifest = newArchivist.getManifest();
            if (manifest==null) {
                manifest = new Manifest();
                newArchivist.setManifest(manifest);
            }
            Attributes atts = manifest.getMainAttributes();
            atts.putValue(Attributes.Name.CLASS_PATH.toString(), classPath.toString());
        }    
        
        // now include the new module in the output archive
        AbstractArchive newModuleJar = out.getEmbeddedArchive(newModule.getArchiveUri());
        
        // write and close
        newArchivist.writeContents(newModuleJar);
        out.closeEntry(newModuleJar);        
                
        return  newModule;
    }
    
    /**
     * @return the DeploymentDescriptorFile responsible for handling
     * standard deployment descriptor
     */
    public DeploymentDescriptorFile getStandardDDFile() {
        return standardDD;
    }   
    
    /**
     * @return if exists the DeploymentDescriptorFile responsible for
     * handling the configuration deployment descriptors
     */
    public DeploymentDescriptorFile getConfigurationDDFile() {
        return new ApplicationRuntimeDDFile();
    }   
    
    /**
     * Perform Optional packages dependencies checking on an archive 
     */
    public boolean performOptionalPkgDependenciesCheck(AbstractArchive archive) throws IOException {
        
        if (!super.performOptionalPkgDependenciesCheck(archive))
            return false;
        
        // now check sub modules
        if (application==null) {
            throw new IOException("Application object not set on archivist");
        }
        Iterator<ModuleDescriptor> modulesItr = application.getModules();
        boolean returnValue = true;
        while(modulesItr.hasNext()) {
            ModuleDescriptor md = modulesItr.next();
            AbstractArchive sub = archive.getEmbeddedArchive(md.getArchiveUri());
            if (sub!=null) {
                Archivist subArchivist = getPluggableArchivists().getArchivistForType(md.getModuleType());
                if (!subArchivist.performOptionalPkgDependenciesCheck(sub))
                    returnValue = false;
            }
        }
        return returnValue;
    }
        
    /**
     * Copy this archivist to a new abstract archive
     * @param out the new archive to use to copy our contents into
     */
    public void copyInto(AbstractArchive source, AbstractArchive target) throws IOException {
        try {
            Application a = (Application) readStandardDeploymentDescriptor(source);     
        } catch(SAXParseException spe) {
            spe.printStackTrace();
            DOLUtils.getDefaultLogger().log(Level.SEVERE, "enterprise.deployment.backend.fileCopyFailure");
        }
    }
    
    /**
     * Copy this archivist to a new abstract archive
     * @param a the deployment descriptor for an application
     * @param source the source archive
     * @param target the target archive
     */
    public void copyInto(Application a, AbstractArchive source, AbstractArchive target) throws IOException {
        copyInto(a, source, target, true);
    }
    
    /**
     * Copy this archivist to a new abstract archive
     * @param a the deployment descriptor for an application
     * @param source the source archive
     * @param target the target archive
     * @param overwriteManifest if true, the manifest in source archive overwrites the one in target
     */
    public void copyInto(Application a, AbstractArchive source, 
                         AbstractArchive target, boolean overwriteManifest) 
        throws IOException {
        Vector entriesAdded = new Vector();
        for (Iterator modules  = a.getModules();modules.hasNext();) {
            ModuleDescriptor aModule = (ModuleDescriptor) modules.next();
            entriesAdded.add(aModule.getArchiveUri());
            AbstractArchive subSource = source.getEmbeddedArchive(aModule.getArchiveUri());
            AbstractArchive subTarget = target.getEmbeddedArchive(aModule.getArchiveUri());
            Archivist newArchivist = getPluggableArchivists().getArchivistForType(aModule.getModuleType());
            newArchivist.copyInto(subSource, subTarget, overwriteManifest);
            target.closeEntry(subTarget);
            String subModulePath = subSource.getArchiveUri();
            if (subModulePath.startsWith(source.getArchiveUri())) {
                subModulePath = subModulePath.substring(source.getArchiveUri().length()+File.separator.length());
                for (Enumeration subEntries = subSource.entries();subEntries.hasMoreElements();) {
                    String anEntry = (String) subEntries.nextElement();
                    entriesAdded.add(subModulePath + "/" + anEntry);
                }
            }
            subSource.close();
        }
        super.copyInto(source, target, entriesAdded, overwriteManifest);
    }
    
    /**
     * Open a jar file and return an application object for the modules contained
     * in the archive. If the archive is a standalone module, this API will 
     * create an empty application and add the standalone module to it
     * 
     * @param the archive file
     * @param true to read configuration deployment descriptors
     * @return the application object
     */
    public static Application openArchive(File jarFile) 
        throws IOException, SAXParseException {    
           
        return openArchive(jarFile, false);
    }
    
    /**
     * Open a jar file and return an application object for the modules contained
     * in the archive/directory. If the archive/directory is a standalone module, this API will 
     * create an empty application and add the standalone module to it
     * 
     * @param archivist to use to open the archive file
     * @param the archive file
     * @param true to read configuration deployment descriptors
     * @return the application object
     */
    public static Application openArchive(Archivist archivist, File jarFile, boolean handleRuntimeInfo)  
        throws IOException, SAXParseException{
            
        // never read the runtime deployment descriptor before the 
        // module type is found and the application object created
        AbstractArchive originalArchive  = null;
        if(jarFile.isDirectory())
        {
            originalArchive = new FileArchive();
            ((FileArchive)originalArchive).open(jarFile.getAbsolutePath());
        }
        else
        {
            originalArchive = new InputJarArchive();
            ((InputJarArchive)originalArchive).open(jarFile.getAbsolutePath());
        }
	Application application = openArchive(archivist, originalArchive, handleRuntimeInfo);
	originalArchive.close();
	return application;
    }
    
    /**
     * Open a jar file and return an application object for the modules contained
     * in the archive. If the archive is a standalone module, this API will 
     * create an empty application and add the standalone module to it
     * 
     * @param archivist to use to open the archive file
     * @param the archive abstraction
     * @param true to read configuration deployment descriptors
     * @return the application object
     */

    public static Application openArchive(Archivist archivist, AbstractArchive in, boolean handleRuntimeInfo)
            throws IOException, SAXParseException {
                
        return openArchive(in.getArchiveUri(), archivist, in, handleRuntimeInfo);
    }
    /**
     * Open a jar file and return an application object for the modules contained
     * in the archive. If the archive is a standalone module, this API will 
     * create an empty application and add the standalone module to it
     * 
     * @param the application moduleID 
     * @param archivist to use to open the archive file
     * @param the archive abstraction
     * @param true to read configuration deployment descriptors
     * @return the application object
     */

    public static Application openArchive(String appName, Archivist archivist, AbstractArchive in, boolean handleRuntimeInfo)
            throws IOException, SAXParseException {                
        // we are not reading the runtime deployment descriptor now...
        archivist.setHandleRuntimeInfo(false);
        
        Descriptor descriptor = archivist.open(in);
        Application application;
        if (descriptor instanceof Application) {
            application = (Application) descriptor;
            application.setRegistrationName(appName);
        } else {
            BundleDescriptor aBundle = (BundleDescriptor) descriptor;
            if (aBundle==null) {
                logger.log(Level.SEVERE, localStrings.getLocalString(
                    "enterprise.deployment.cannotreadDDs",
                    "Cannot read the Deployment Descriptors for module {0}",
                    new Object[] { in.getArchiveUri() }));
                return null;        
            }
            ModuleDescriptor newModule = archivist.createModuleDescriptor(aBundle);
            newModule.setArchiveUri(in.getArchiveUri());
            application = Application.createApplication(appName, newModule);
        }
        
        // now read the runtime deployment descriptor
        if (handleRuntimeInfo) {
            // now read the runtime deployment descriptors from the original jar file
            archivist.setHandleRuntimeInfo(true);
            archivist.readRuntimeDeploymentDescriptor(in, descriptor);
        }
        
        // validate
        archivist.validate(null);
        return application;
        
    }

    /**
     * Open a jar file with the Archivists define by the 
     * pluggable archivists instance and return an application 
     * object for the modules contained in the archive. 
     * If the archive is a standalone module, this API will 
     * create an empty application and add the standalone module to it
     * 
     * @param the archive file
     * @param the instance of PluggableArchivists responsible for 
     * instanciating the archivists.
     * @param true to read configuration deployment descriptors
     * @return the application object
     */
    public static Application openArchive(File jarFile, 
        PluggableArchivists pa, boolean handleRuntimeInfo) 
        throws IOException, SAXParseException {
            
        Archivist archivist = pa.getArchivistForArchive(jarFile);
        return openArchive(archivist, jarFile, handleRuntimeInfo);
    }
    
    /**
     * Open a jar file with the default Archivists and return an application 
     * object for the modules contained in the archive. 
     * If the archive is a standalone module, this API will 
     * create an empty application and add the standalone module to it
     * 
     * @param the archive file
     * @param true to read configuration deployment descriptors
     * @return the application object
     */
    public static Application openArchive(File jarFile, boolean handleRuntimeInfo) 
        throws IOException, SAXParseException {
            
        Archivist archivist = ArchivistFactory.getArchivistForArchive(jarFile);
        return openArchive(archivist, jarFile, handleRuntimeInfo);
    }
    
    /**
     * @return the application name from an application .ear file
     * @param the .ear file
     */
    static public String getApplicationName(File jarFile) throws IOException {

	if (!jarFile.exists()) {
	    throw new IOException(localStrings.getLocalString(
		"enterprise.deployment.exceptionjarfiledoesn'texist",
		"{0} does not exist", new Object[] {jarFile}));
	}
        
        /*
         *Add finally clause containing explicit close of jar file.
         */
	JarFile jar = null;
	try {
            jar = new JarFile(jarFile);
            ApplicationDeploymentDescriptorFile node = new ApplicationDeploymentDescriptorFile();
            node.setXMLValidation(false);
            ZipEntry deploymentEntry = jar.getEntry(node.getDeploymentDescriptorPath());
            if (deploymentEntry != null) {
                try {
                    Application application = (Application) node.read(jar.getInputStream(deploymentEntry));	     
                    return application.getDisplayName();          
                } catch (Exception pe) {
                    pe.printStackTrace();
                } 
            }
        } finally {
            if (jar != null) {
                jar.close();
            }
        }
        return null;
    }        

    /**
     * This method will be invoked if and only if the following is true:
     * 1. directory deployment with neither standard nor runtime DD
     * 2. JSR88 DeploymentManager.distribute using InputStream with neither
     *    standard nor runtime DD
     * <p>
     * Note that we will only venture a guess for case 1.  JSR88 deployment
     * of an application (ear) using InputStream without any deployment 
     * descriptor will NOT be supported at this time.
     */
    protected boolean postHandles(AbstractArchive abstractArchive) 
            throws IOException {

        // Only try to make a guess if the archive is a directory
           
        // We will try to conclude if a directory represents an application
        // by looking at if it contains any Java EE modules.            
        File file = new File(abstractArchive.getArchiveUri());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File content : files) {
                if (content.isDirectory()) {
                    String dirPath = content.getPath();
                    if (dirPath.endsWith("_war")
                        || dirPath.endsWith("_jar")
                        || dirPath.endsWith("_rar")) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    protected String getArchiveExtension() {
        return APPLICATION_EXTENSION;
    }

    @Override public void readPersistenceDeploymentDescriptors(
            AbstractArchive appArchive, Descriptor descriptor) throws IOException, SAXParseException {
        if(logger.isLoggable(Level.FINE)) {
            logger.logp(Level.FINE, "ApplicationArchivist",
                    "readPersistenceDeploymentDescriptors", "archive = {0}",
                    appArchive.getURI());
        }
        Application application = Application.class.cast(descriptor);
        Map<String, Archive> subArchives = new HashMap<String, Archive>();
        try{
            Enumeration entries = appArchive.entries();
            while(entries.hasMoreElements()){
                String entry = String.class.cast(entries.nextElement());
                // at ear level only jar files can be root of PU.
                if (entry.endsWith(".jar")) {
                    Iterator modules = application.getModules();
                    boolean belongsToSomeModule = false;
                    while(modules.hasNext()){
                        // We need to do the following check because we don't want
                        // to process files like foo_war/WEB-INF/lib/a.jar.
                        // So the following check ensures that
                        // we have found a .jar embedded in ear file which is
                        // neither an appclient jar nor an ejb jar and nor is it
                        // part of a rar or war.
                        String explodedModuleURI = FileUtils.makeFriendlyFileName(
                                ModuleDescriptor.class.cast(modules.next()).
                                getArchiveUri()) + "/"; // add '/' to indicate its a directory
                        if(entry.startsWith(explodedModuleURI)){
                            belongsToSomeModule = true;
                            break; // no need to see if it belongs to some other module
                        }
                    }
                    if(!belongsToSomeModule) {
                        subArchives.put(entry, appArchive.getSubArchive(entry));
                    }
                }
            }
            for(Map.Entry<String, Archive> pathToArchiveEntry : subArchives.entrySet()) {
                readPersistenceDeploymentDescriptor(pathToArchiveEntry.getValue(), pathToArchiveEntry.getKey(), descriptor);
            }
        }finally{
            for(Archive subArchive : subArchives.values()) {
                subArchive.close();
            }
        }

    }

    /**
     * This is a helper method.
     * This method is needed because we sometimes have to read PU descriptors
     * from the original app exploded dir as opposed to generated dir
     * because generated XML do not contain any information about PUs.
     * This method also handles the case where application object is virtual,
     * (a virtual application represents a standalone module deployment object).
     * @param archive from where persistence descriptors will be read from.
     * @param application the application object whose persistence units
     * will be read. This method will read all the persistence units
     * recurssively and register at appropriate level.
     * @throws IOException
     * @throws SAXParseException
     */
    public static void readPersistenceDeploymentDescriptorsRecursively(
            AbstractArchive archive, Application application) throws IOException,
            SAXParseException {
        if(!application.isVirtual()) {
            ApplicationArchivist appArchivist = new ApplicationArchivist();
            appArchivist.readPersistenceDeploymentDescriptors(archive, application);
            for(Iterator modules = application.getModules(); modules.hasNext();) {
                final ModuleDescriptor moduleDescriptor =
                        ModuleDescriptor.class.cast(modules.next());
                Archivist moduleArchivist = ArchivistFactory.
                        getArchivistForType(moduleDescriptor.getModuleType());
                AbstractArchive moduleArchive = archive.getEmbeddedArchive(
                        moduleDescriptor.getArchiveUri());
                try{
                    moduleArchivist.readPersistenceDeploymentDescriptors(
                            moduleArchive, moduleDescriptor.getDescriptor());
                } finally {
                    moduleArchive.close();
                }
            }
        } else {
            // it's a standalone war/jar/rar etc.
            final ModuleDescriptor module =
                    ModuleDescriptor.class.cast(application.getModules().next());
            Archivist archivist =
                    ArchivistFactory.getArchivistForType(module.getModuleType());
            archivist.readPersistenceDeploymentDescriptors(
                    archive, module.getDescriptor());
        }
    }

    // for test purposes
    public static void main(String args[]) {
        
        if (args.length==0) {
            return;
        } else {
            String fileName = args[0];
            try {
                Archivist archivist = ArchivistFactory.getArchivistForArchive(fileName);
                archivist.setHandleRuntimeInfo(true);
                archivist.setArchiveUri(fileName);
		archivist.setXMLValidation(true);
		archivist.setXMLValidationLevel("full");
                Descriptor descriptor = ApplicationArchivist.openArchive(archivist, new File(fileName), true);     
                if (args.length >1 && args[1]!=null) {
                    archivist.write(args[1]);
                }
                
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    
}
