FileDocCategorySizeDatePackage
Archivist.javaAPI DocGlassfish v2 API63993Fri May 04 22:31:36 BST 2007com.sun.enterprise.deployment.archivist

Archivist.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, 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/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.enterprise.deployment.archivist;

import static com.sun.enterprise.deployment.io.DescriptorConstants.PERSISTENCE_DD_ENTRY;

import java.io.*;
import java.util.jar.*;
import java.util.jar.Attributes.Name;
import java.util.zip.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xml.sax.SAXParseException;
import org.xml.sax.InputSource;

import javax.enterprise.deploy.shared.ModuleType;

import com.sun.enterprise.deployment.annotation.AnnotatedElementHandler;
import com.sun.enterprise.deployment.annotation.AnnotationProcessor;
import com.sun.enterprise.deployment.annotation.AnnotationProcessorException;
import com.sun.enterprise.deployment.annotation.ErrorHandler;
import com.sun.enterprise.deployment.annotation.ResultType;
import com.sun.enterprise.deployment.annotation.factory.AnnotatedElementHandlerFactory;
import com.sun.enterprise.deployment.annotation.factory.ScannerFactory;
import com.sun.enterprise.deployment.annotation.factory.SJSASFactory;
import com.sun.enterprise.deployment.annotation.impl.AnnotationUtils;
import com.sun.enterprise.deployment.annotation.impl.DefaultErrorHandler;
import com.sun.enterprise.deployment.annotation.ProcessingContext;
import com.sun.enterprise.deployment.annotation.ProcessingResult;
import com.sun.enterprise.deployment.annotation.Scanner;
import com.sun.enterprise.deployment.deploy.shared.AbstractArchive;
import com.sun.enterprise.deployment.deploy.shared.AbstractArchiveFactory;
import com.sun.enterprise.deployment.deploy.shared.JarArchiveFactory;
import com.sun.enterprise.deployment.deploy.shared.Archive;
import com.sun.enterprise.deployment.io.*;
import com.sun.enterprise.deployment.*;
import com.sun.enterprise.deployment.util.DescriptorVisitor;
import com.sun.enterprise.deployment.util.DOLUtils;
import com.sun.enterprise.deployment.backend.OptionalPkgDependency;
import com.sun.enterprise.deployment.backend.IASDeploymentException;
import com.sun.enterprise.deployment.util.ModuleDescriptor;
import com.sun.enterprise.deployment.util.TracerVisitor;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.util.shared.ArchivistUtils;
import com.sun.enterprise.util.io.FileUtils;
import com.sun.logging.LogDomains;

/**
 * This abstract class contains all common behaviour for Achivisits. Archivists 
 * classes are responsible for reading and writing correct J2EE Archives
 *
 * @author  Jerome Dochez
 * @version 
 */
public abstract class Archivist {
    protected static final Logger logger =
            LogDomains.getLogger(LogDomains.DPL_LOGGER);

    static {
        AnnotationUtils.setLogger(logger);
        AnnotationUtils.setLoggerTarget("*");
    }

    public static final String MANIFEST_VERSION_VALUE = "1.0";

    // the path for the underlying archive file
    protected String path;

    // should we read or save the runtime info.
    protected boolean handleRuntimeInfo = true;

    // default should be false in production
    protected boolean annotationProcessingRequested = false;
    
    // attributes of this archive
    protected Manifest manifest;
    
    // the archive abstraction we use to access the physical storage
    protected AbstractArchiveFactory abstractArchiveFactory= null;
    
    /**
     * plugable archivist factory for creating sub modules archivists
     */
    protected PluggableArchivists pa = null;    
    
    // resources...
    private static final LocalStringManagerImpl localStrings =
	    new LocalStringManagerImpl(Archivist.class);    
    
    // class loader to use when validating the DOL
    protected ClassLoader classLoader=null;
    
    // boolean for XML validation
    private boolean isValidatingXML = true;     

    // boolean for runtime XML validation
    private boolean isValidatingRuntimeXML = false;     
    
    // xml validation error level reporting/recovering
    private String validationLevel = "parsing";

    // runtime xml validation error level reporting/recovering
    private String runtimeValidationLevel = "parsing";

    // error handler for annotation processing
    private ErrorHandler annotationErrorHandler = null;

    private static final String WSDL = ".wsdl";
    private static final String XML = ".xml";
    private static final String XSD = ".xsd";


    protected static final String APPLICATION_EXTENSION = ".ear";
    protected static final String APPCLIENT_EXTENSION = ".jar";
    protected static final String WEB_EXTENSION = ".war";
    protected static final String EJB_EXTENSION = ".jar";
    protected static final String CONNECTOR_EXTENSION = ".rar";
    //Used to detect the uploaded files which always end in ".tmp"
    protected static final String UPLOAD_EXTENSION = ".tmp";

    private static final String PROCESS_ANNOTATION_FOR_OLD_DD = 
        "process.annotation.for.old.dd";

    private static final boolean processAnnotationForOldDD =
        Boolean.getBoolean(PROCESS_ANNOTATION_FOR_OLD_DD);

    /** Creates new Archivist */
    public Archivist() {
        // by default we are dealing with jar files, can be changed with 
        // setAbstractArchiveFactory
        abstractArchiveFactory = new JarArchiveFactory();
        annotationErrorHandler = new DefaultErrorHandler();
    }
    
    /** 
     * initializes this instance from another archivist, this is used 
     * to transfer contextual information between archivists, for 
     * example whether we should handle runtime information and such
     */  
    protected void initializeContext(Archivist other) {
        handleRuntimeInfo = other.isHandlingRuntimeInfo();
        annotationProcessingRequested = other.isAnnotationProcessingRequested();
        isValidatingXML = other.isValidatingXML;
        validationLevel = other.validationLevel;
        abstractArchiveFactory = other.getAbstractArchiveFactory();
        classLoader = other.classLoader;
        annotationErrorHandler = other.annotationErrorHandler;
    }
    
    /**
     * sets the abstract archive factory associated with this archivist.
     * @param the new factory for creating abstract archives
     */
    public void setAbstractArchiveFactory(AbstractArchiveFactory aa) {
        abstractArchiveFactory = aa;
    }
    
    /**
     * @return the currently associated abstract archive factory
     */
    public AbstractArchiveFactory getAbstractArchiveFactory() {
        return abstractArchiveFactory;
    }
    
    /**
     * Open a new archive file, read the XML descriptor and set the  constructed
     * DOL descriptor instance 
     * 
     * @param path the archive file path
     * @return the deployment descriptor for this archive
     */
    public RootDeploymentDescriptor open(AbstractArchive archive) 
        throws IOException, SAXParseException {

        setManifest(archive.getManifest());
        
        RootDeploymentDescriptor descriptor = readDeploymentDescriptors(archive);
        if (descriptor!=null){
            postOpen(descriptor, archive);
        }
        return descriptor;
    }
    
    /**
     * Open a new archive file, read the XML descriptor and set the  constructed
     * DOL descriptor instance 
     * 
     * @param path the archive file path
     * @return the deployment descriptor for this archive
     */
    public RootDeploymentDescriptor open(String path) 
        throws IOException, SAXParseException {

        this.path = path;
	File file = new File(path);
	if (!file.exists()) {
	    throw new FileNotFoundException(path);	    
	}
        AbstractArchive abstractArchive = abstractArchiveFactory.openArchive(path);
        RootDeploymentDescriptor descriptor  = open(abstractArchive);
        
        abstractArchive.close();
     
        // attempt validation
        validate(null);

        return descriptor;
    }
    
    /**
     * open a new archive file using a file descriptor
     * @param the archive to open
     */
    public RootDeploymentDescriptor open(File file) throws IOException, SAXParseException {
        return open(file.getAbsolutePath());
    }
    
    /**
     * perform any action after all standard DDs is read
     * @param the deployment descriptor for the module
     * @param the module archive
     */
    protected void postStandardDDsRead(RootDeploymentDescriptor descriptor,
            AbstractArchive archive) throws IOException {
    }
    
    /**
     * perform any action after annotation processed
     * @param the deployment descriptor for the module
     * @param the module archive
     */
    protected void postAnnotationProcess(RootDeploymentDescriptor descriptor,
            AbstractArchive archive) throws IOException {
    }

    /**
     * perform any action after all runtime DDs read
     * @param the deployment descriptor for the module
     * @param the module archive
     */
    protected void postRuntimeDDsRead(RootDeploymentDescriptor descriptor,
            AbstractArchive archive) throws IOException {
    }

    /**
     * perform any post deployment descriptor reading action
     *
     * @param the deployment descriptor for the module
     * @param the module archive
     */
    protected void postOpen(RootDeploymentDescriptor descriptor, AbstractArchive archive) 
        throws IOException
    {
    }

    /**
     * Read the standard deployment descriptors (can contained in one or 
     * many file) and return the corresponding initialized descriptor instance.
     * By default, the standard deployment descriptors are all contained in 
     * the xml file characterized with the path returned by 
     * @see getDeploymentDescriptorPath
     *
     * @return the initialized descriptor
     */
    public RootDeploymentDescriptor readDeploymentDescriptors(AbstractArchive abstractArchive)
        throws IOException, SAXParseException {
            
        // read the standard deployment descriptors 
        BundleDescriptor descriptor = (BundleDescriptor)readStandardDeploymentDescriptor(abstractArchive);

        ModuleDescriptor newModule = createModuleDescriptor(descriptor);
        newModule.setArchiveUri(abstractArchive.getArchiveUri());
        
        readWebServicesDescriptor(abstractArchive, descriptor);

        // Now that we have parsed the standard DD, let's read all the
        // PersistenceUnits defined in this archive as well.
        readPersistenceDeploymentDescriptors(abstractArchive, getDescriptor());

        postStandardDDsRead(descriptor, abstractArchive);

        readAnnotations(abstractArchive, descriptor);
        postAnnotationProcess(descriptor, abstractArchive);

        // now read the runtime deployment descriptors
        readRuntimeDeploymentDescriptor(abstractArchive, descriptor);
        postRuntimeDDsRead(descriptor, abstractArchive);
	
	return descriptor;     	
    }

    protected void readAnnotations(AbstractArchive abstractArchive, BundleDescriptor descriptor)  throws IOException {
        // if the system property is set to process annotation for pre-JavaEE5
        // DD, the semantics of isFull flag is: full attribute is set to 
        // true in DD. Otherwise the semantics is full attribute set to 
        // true or it is pre-JavaEE5 DD.
        boolean isFull = false;
        if (processAnnotationForOldDD) {
            isFull = descriptor.isFullAttribute();
        } else {
            isFull = descriptor.isFullFlag();
        }

        // only process annotation when these two requirements satisfied:
        // 1. It is not a full deployment descriptor
        // 2. It is called through dynamic deployment
        if (!isFull && annotationProcessingRequested 
            && classLoader != null) {
            try {
                ProcessingResult result = processAnnotations(descriptor, abstractArchive);
                if (result != null  &&
                        ResultType.FAILED.equals(result.getOverallResult())){
                    DOLUtils.getDefaultLogger().severe(localStrings.getLocalString(
                            "enterprise.deployment.archivist.annotationprocessingfailed",
                            "Annotations processing failed for {0}",
                            new Object[]{abstractArchive.getArchiveUri()}));
                }
            //XXX for backward compatible in case of having cci impl in EJB
            } catch(NoClassDefFoundError err) {
                 if (DOLUtils.getDefaultLogger().isLoggable(Level.WARNING)) {	
                     DOLUtils.getDefaultLogger().warning(
                         "Error in annotation processing: " + err);
                 }
            } catch(AnnotationProcessorException ex) {
                DOLUtils.getDefaultLogger().severe(ex.getMessage());
                DOLUtils.getDefaultLogger().log(Level.FINE, ex.getMessage(), ex);
                throw new IllegalStateException(ex);
            }
        } else if (DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {
            DOLUtils.getDefaultLogger().fine("Annotation is not processed for this archive.");
        }
    }

    /**
     * This method is responsible for reading of any persistence DD defined
     * in the SCOPE of this descriptor. Note, it only reads persistence DD
     * defined in the current SCOPE.
     * @param archive is the archive that will be scanned for persistence DD
     * @param descriptor which will be populated with information read from
     * persistence DD.
     * @throws IOException
     * @throws SAXParseException
     */
    public void readPersistenceDeploymentDescriptors(
            AbstractArchive archive, Descriptor descriptor)
            throws IOException, SAXParseException {
        // This is a no-op implementation as opposed to an abstract method
        // because it gets called from ConnectorArchivist.
    }

    /**
     * Process annotations in a bundle descriptor, the annoation processing
     * is dependent on the type of descriptor being passed.
     */
    public ProcessingResult processAnnotations(BundleDescriptor bundleDesc,
            AbstractArchive abstractArchive)
            throws AnnotationProcessorException, IOException {

        AnnotatedElementHandler aeHandler =
                AnnotatedElementHandlerFactory.createAnnotatedElementHandler(
                bundleDesc);

        if (aeHandler == null) {
            return null;
        }
        
        Scanner scanner = ScannerFactory.createScanner(
                bundleDesc, this, abstractArchive, classLoader);
        if (!scanner.getElements().isEmpty()) {
            if (bundleDesc.isDDWithNoAnnotationAllowed()) {
                // if we come into this block, it means an old version 
                // of deployment descriptor has annotation which is not correct
                // throw exception in this case
                String ddName = 
                    getStandardDDFile().getDeploymentDescriptorPath();
                String explodedArchiveName = 
                    new File(abstractArchive.getArchiveUri()).getName();
                String archiveName = FileUtils.revertFriendlyFilenameExtension(
                    explodedArchiveName);
                throw new AnnotationProcessorException(
                    localStrings.getLocalString(
                    "enterprise.deployment.oldDDwithAnnotation", 
                    "{0} in archive {1} is of version {2}, which cannot support annotations in an application.  Please upgrade the deployment descriptor to be a version supported by Java EE 5.0 (or later).", 
                    new Object[] {ddName, archiveName, bundleDesc.getSpecVersion()}));
            }
            AnnotationProcessor ap = SJSASFactory.getAnnotationProcessor();
            ProcessingContext ctx = ap.createContext();
            if (annotationErrorHandler != null) {
                ctx.setErrorHandler(annotationErrorHandler);
            }
            ctx.setProcessingInput(scanner);
            ctx.pushHandler(aeHandler);
            
            // Make sure there is a classloader available on the descriptor
            // during annotation processing.
            ClassLoader originalBundleClassLoader = null;
            try {
                originalBundleClassLoader = bundleDesc.getClassLoader();
            } catch(Exception e) {
                // getClassLoader can throw exception if not available
            }
            
            // Only set classloader if it's not already set.
            if( originalBundleClassLoader == null ) {
                bundleDesc.setClassLoader(classLoader);
            }
            
            try {
                return ap.process(ctx);
            } finally {
                if( originalBundleClassLoader == null ) {
                    bundleDesc.setClassLoader(null);
                }
            }
        } else {
            bundleDesc.setFullFlag(true);
        }
        return null;
    }

    /**
     * Read the standard deployment descriptors (can contained in one or 
     * many file) and return the corresponding initialized descriptor instance.
     * By default, the standard deployment descriptors are all contained in 
     * the xml file characterized with the path returned by 
     * @see getDeploymentDescriptorPath
     *
     * @return the initialized descriptor
     */
    public Descriptor readStandardDeploymentDescriptor(AbstractArchive abstractArchive)  
        throws IOException, SAXParseException {

        InputStream is = null;
        
        try {
            is = abstractArchive.getEntry(getStandardDDFile().getDeploymentDescriptorPath());
            if (is!=null) {
                DeploymentDescriptorFile ddFile = getStandardDDFile();
                ddFile.setXMLValidation(getXMLValidation());
                ddFile.setXMLValidationLevel(validationLevel);
                if (abstractArchive.getArchiveUri()!=null) {
                    ddFile.setErrorReportingString(abstractArchive.getArchiveUri());
                }
                Descriptor result = ddFile.read(is);
                return result;        
            } else {
                /*
                 *Always return at least the default, because the info is needed 
                 *when an app is loaded during a server restart and there might not
                 *be a physical descriptor file.
                 */
                return getDefaultBundleDescriptor();
            }
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }

    /**
     * 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 abstractArchive, Descriptor descriptor)  
        throws IOException, SAXParseException {
        
        // if we are not supposed to handle runtime info, just pass
        String ddFileEntryName = getRuntimeDeploymentDescriptorPath();
        if (!isHandlingRuntimeInfo() || ddFileEntryName==null) {
            return;
        }
        
        InputStream is = null;
        try {
            // apply the runtime settings if any
            is = abstractArchive.getEntry(ddFileEntryName);
            DeploymentDescriptorFile confDD = getConfigurationDDFile();
            if (abstractArchive.getArchiveUri()!=null) {
                confDD.setErrorReportingString(abstractArchive.getArchiveUri());
            }     
        
            if (is!=null && confDD!=null) {
                confDD.setXMLValidation(getRuntimeXMLValidation());
                confDD.setXMLValidationLevel(runtimeValidationLevel);
                confDD.read(descriptor, is);                      
            }
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException ioe) {
                }
            }
        }
    }

    /**
     * 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 we are not supposed to handle runtime info, just pass
        String runtimeDDPath = getRuntimeDeploymentDescriptorPath();
        if (runtimeDDPath==null || 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());
        }

        String entry = runtimeDDPath.substring(runtimeDDPath.lastIndexOf('/')+1);
        if (dpEntries.contains(entry)) {
            readRuntimeDDFromDeploymentPlan(entry, planArchive, 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.
     */
    protected void readRuntimeDDFromDeploymentPlan(
        String entry, AbstractArchive planArchive, Descriptor descriptor)  
        throws IOException, SAXParseException {

        InputStream is = null;
        try {
            is = planArchive.getEntry(entry);
            DeploymentDescriptorFile confDD = getConfigurationDDFile();
            if (is!=null && confDD!=null) {
                if (planArchive.getArchiveUri()!=null) {
                    confDD.setErrorReportingString(planArchive.getArchiveUri());
                }
                confDD.setXMLValidation(getXMLValidation());
                confDD.read(descriptor, is);
            }
        } finally {
            if (is != null) {
            try {
                is.close();
            } catch (IOException ioe) { }
            }
        }
    }

    /**
     * Read the (optional) webservices.xml descriptor in this module.  
     * Only applicable for web and ejb modules.  
     */
    protected void readWebServicesDescriptor(AbstractArchive abstractArchive,
                                          Descriptor descriptor)  
        throws IOException, SAXParseException {

        DeploymentDescriptorFile confDD = getWebServicesDDFile(descriptor);
        if (abstractArchive.getArchiveUri()!=null) {
            confDD.setErrorReportingString(abstractArchive.getArchiveUri());
        }        
        InputStream is = null;
        try {
            is = abstractArchive.getEntry(confDD.getDeploymentDescriptorPath());
            if (is != null) {
                confDD.setXMLValidation(getXMLValidation());
                confDD.setXMLValidationLevel(validationLevel);
                confDD.read(descriptor, is);
            }
        } finally {
            if (is != null) {
                is.close();
            }
        }

    }

    /*
     * write the J2EE module represented by this instance to a new 
     * J2EE archive file
     * 
     */
    public void write() throws IOException {
        write(path);
    }   
    
    /**
     * saves the archive
     * 
     * @param outPath the file to use
     */
    public void write(String outPath) throws IOException {
        AbstractArchive in = abstractArchiveFactory.openArchive(path);        
        write(in, outPath);
        in.close();
    }    
    
    /**
     * save the archive
     * 
     * @param input abstract archive to copy old elements from
     * @param outPath the file to use
     */
    public void write(AbstractArchive in, String outPath) throws IOException {
        
        AbstractArchive oldArchive=null;
        try {
            oldArchive = abstractArchiveFactory.openArchive(outPath);
        } catch (IOException ioe) {
            // there could be many reasons why we cannot open this archive, 
            // we should continue
        }
        AbstractArchive out = null;
        BufferedOutputStream bos=null;
        try {
            String tmpName = null;
            if (oldArchive!=null && oldArchive.exists() && 
                !oldArchive.supportsElementsOverwriting()) {
                // this is a rewrite, get a temp file name...
                // I am creating a tmp file just to get a name
                File outputFile = getTempFile(outPath);    
                tmpName = outputFile.getAbsolutePath();
                outputFile.delete();
                out = abstractArchiveFactory.createArchive(tmpName);
                oldArchive.close();
            } else {
                out = abstractArchiveFactory.createArchive(outPath);
            }
        
            // write archivist content
            writeContents(in, out);
            out.close();    
            in.close();
            
            // if we were using a temp file, time to rewrite the original
            if (tmpName!=null) {
                AbstractArchive finalArchive = abstractArchiveFactory.openArchive(outPath);
                finalArchive.delete();
                AbstractArchive tmpArchive = abstractArchiveFactory.openArchive(tmpName);
                tmpArchive.renameTo(outPath);
            }       
        } catch (IOException ioe) {
            // cleanup
            if (out!=null) {
		try {
                    out.close();
                    //out.delete(); <-- OutputJarArchive.delete isn't supported.
	 	} catch (IOException outIoe) {
		    // ignore exceptions here, otherwise this will end up masking the real
		    // IOException in 'ioe'.
		}
            }
            // propagate exception
            throw ioe;
        }
    }   
    
    public void write(AbstractArchive in, AbstractArchive out) throws IOException {
        writeContents(in,out);
    }
    
    /**
     * writes the content of an archive to a JarFile
     * 
     * @param the jar output stream to write to
     */
    protected void writeContents(AbstractArchive out) throws IOException {
        AbstractArchive in = abstractArchiveFactory.openArchive(path);        
        writeContents(in, out);
        in.close();
    }

    
    /**
     * writes the content of an archive to a JarFile
     * 
     * @param the jar output stream to write to
     */
    protected void writeContents(AbstractArchive in, AbstractArchive out) throws IOException {

        writeContents(in, out, null);
    }
    
        
    /**
     * writes the content of an archive to a JarFile
     * 
     * @param the input  archive
     * @param the archive output stream to write to
     * @param the files to not write from the original archive
     */
    protected void writeContents(AbstractArchive in, AbstractArchive out, Vector entriesToSkip) 
        throws IOException {        
        
        // Copy original jarFile elements
        if (in!=null && in.exists()) {
            if (entriesToSkip==null) {
                entriesToSkip = getListOfFilesToSkip();
            } else {
                entriesToSkip.addAll(getListOfFilesToSkip());
            }
            copyJarElements(in, out, entriesToSkip);            
        }
        
        // now the deployment descriptors
        writeDeploymentDescriptors(out);  
        
        // manifest file
        if (manifest!=null) {
            OutputStream os = out.putNextEntry(JarFile.MANIFEST_NAME);
            manifest.write(new DataOutputStream(os));
            out.closeEntry();
        }
    }        
    
    /**
     * writes the deployment descriptors (standard and runtime) 
     * to a JarFile using the right deployment descriptor path
     * 
     * @out the abstract archive file to write to
     */    
    public void writeDeploymentDescriptors(AbstractArchive out) throws IOException {
        
        // Standard DDs
        writeStandardDeploymentDescriptors(out);
	
        // the rest...
	writeExtraDeploymentDescriptors(out);
    }
    
    /** 
     * writes the standard deployment descriptors to an abstract archive
     *
     * @param the abstract archive to write to
     */ 
    public void writeStandardDeploymentDescriptors(AbstractArchive out) throws IOException {
        
        OutputStream os = out.putNextEntry(getDeploymentDescriptorPath());
        writeStandardDeploymentDescriptors(os);
        out.closeEntry();   
        
        Descriptor desc = getDescriptor();
        
        // only bundle descriptor can have web services
        if (desc instanceof BundleDescriptor) {            
            writeWebServicesDescriptors((BundleDescriptor) desc, out);
        }        
    }
    
    /**
     * writes the runtime deployment descriptors to an abstract archive
     * 
     * @param the abstract archive
     */
    public void writeRuntimeDeploymentDescriptors(AbstractArchive out) throws IOException {
        
        Descriptor desc = getDescriptor();

        // Runtime DDs
        if (isHandlingRuntimeInfo()) {
            DeploymentDescriptorFile confDD = getConfigurationDDFile();
            if (confDD!=null) {
                OutputStream os = out.putNextEntry(getRuntimeDeploymentDescriptorPath());
                confDD.write(desc, os);
                out.closeEntry();
            }        
        }
        
    }
    
    /**
     * write all extra deployment descriptors (like cmp related and runtime dds)
     *
     * @out the abstract archive file to write to
     */
    protected void writeExtraDeploymentDescriptors(AbstractArchive out) throws IOException {
	writeRuntimeDeploymentDescriptors(out);
    }    
    
    /**
     * writes the standard deployment descriptor to an output stream
     * 
     * @param output stream to write out the descriptors
     */
    public void writeStandardDeploymentDescriptors(OutputStream os) throws IOException {
        getStandardDDFile().write(getDescriptor(), os);
    }
    
    /** 
     * writes de configuration deployment descriptor to a new XML file
     *
     * @param output stream to write the configuration deployment descriptors
     */
    public void writeRuntimeDeploymentDescriptors(OutputStream os) throws IOException {
        DeploymentDescriptorFile confDD = getConfigurationDDFile();
        if (confDD!=null) {
            confDD.write(getDescriptor(), os);
        }      
    }
    
    /**
     * @return whether this archivist should write a web services
     * deployment descriptor
     */
    protected void writeWebServicesDescriptors(BundleDescriptor desc, AbstractArchive out) 
        throws IOException {
        if (desc.hasWebServices()) {            
            DeploymentDescriptorFile webServicesDD = getWebServicesDDFile(desc);
            OutputStream os = out.putNextEntry(webServicesDD.getDeploymentDescriptorPath());
            webServicesDD.write(desc, os);
            out.closeEntry();
        }
    }    
    
    /** 
     * @return the location of the DeploymentDescriptor file for a 
     * particular type of J2EE Archive
     */
    public String getDeploymentDescriptorPath() {
        return getStandardDDFile().getDeploymentDescriptorPath();
    }
    
    /** 
     * @return the location of the web services related deployment 
     * descriptor file inside this archive or null if this archive
     * does not support webservices implementation.
     */
    public String getWebServicesDeploymentDescriptorPath() {
        return null;
    }

    /**
     * @return the location of the runtime deployment descriptor file
     * for a particular type of J2EE Archive
     */
    public String getRuntimeDeploymentDescriptorPath() {
        DeploymentDescriptorFile ddFile = getConfigurationDDFile();
        if (ddFile!=null) {
            return ddFile.getDeploymentDescriptorPath();
        } else {
            return null;
        }
    }
    
    /** 
     * @return true if the passed AbstractArchive contains runtime 
     * deployment descriptors information
     */
    public boolean containsRuntimeDeploymentDescriptors(AbstractArchive in) {
        
        String ddFileName = getRuntimeDeploymentDescriptorPath();
        if (ddFileName==null) {
            return false;
        }
        for (Enumeration e = in.entries();e.hasMoreElements();) {
            String entryName = (String) e.nextElement();
            if (entryName.equals(ddFileName)) {
                return true;
            }
        }
        // we iterated all archive elements, could not find our 
        // runtime DD file, it's a pure j2ee archive
        return false;
    }

    /**
     * Archivists can be associated with a module descriptor once the 
     * XML deployment descriptors have been read and the DOL tree
     * is initialized. 
     */
    public void setModuleDescriptor(ModuleDescriptor module) {
        setDescriptor(module.getDescriptor());
        setManifest(module.getManifest());
    }
    
    /**
     * Perform Optional packages dependencies checking on an archive 
     */
    public boolean performOptionalPkgDependenciesCheck(AbstractArchive archive) throws IOException {
        
        boolean dependenciesSatisfied = true;
        Manifest m = archive.getManifest();
        if (m!=null) {
            dependenciesSatisfied=OptionalPkgDependency.optionalPkgDependencyLogic(m, archive.getArchiveUri());
        }
        // now check my libraries.
        Vector<String> libs = getLibraries(archive);
        if (libs!=null) {
            for (String libUri : libs) {
                JarInputStream jis=null;
                try {
                    jis = new JarInputStream(archive.getEntry(libUri));
                    m = jis.getManifest();
                    if (m!=null) {
                        if (!OptionalPkgDependency.optionalPkgDependencyLogic(m, libUri)) {
                            dependenciesSatisfied=false;
                        }                         
                    }
                } finally {
                    if (jis!=null)
                        jis.close();
                }
            }
        }
        return dependenciesSatisfied;
    }
    
    /** 
     * Archivist read XML deployment descriptors and keep the 
     * parsed result in the DOL descriptor instances. Sets the descriptor 
     * for a particular Archivist type
     */
    public abstract void setDescriptor(Descriptor descriptor);
    
    /**
     * @return the Descriptor for this archvist
     */ 
    public abstract Descriptor getDescriptor();

    /**
     * @return the  module type handled by this archivist 
     * as defined in the application DTD
     *
     */
    public abstract ModuleType getModuleType();        
    
    /**
     * @return the DeploymentDescriptorFile responsible for handling
     * standard deployment descriptor
     */
    public abstract DeploymentDescriptorFile getStandardDDFile();
    
    /**
     * @return if exists the DeploymentDescriptorFile responsible for
     * handling the configuration deployment descriptors
     */
    public abstract DeploymentDescriptorFile getConfigurationDDFile();

    /**
     * @return a default BundleDescriptor for this archivist
     */
    public abstract Descriptor getDefaultBundleDescriptor();

    /**
     * @param arch archive for this module.  can be null. only used for reading
     * @return the DeploymentDescriptorFile responsible for
     * handling the web services deployment descriptors
     */
    public DeploymentDescriptorFile getWebServicesDDFile(Descriptor desc) {
        return new WebServicesDeploymentDescriptorFile(desc);
    }

    /**
     *@return The archive extension handled by a specific archivist
     */
    protected abstract String getArchiveExtension();

    /**
     * @return true if the archivist is handling the provided archive 
     */
    protected abstract boolean postHandles(AbstractArchive abstractArchive) throws IOException;

    public boolean hasStandardDeploymentDescriptor(AbstractArchive archive) 
        throws IOException {
        InputStream stIs = archive.getEntry(getDeploymentDescriptorPath());
        if (stIs != null) {
             stIs.close();
             return true;
        }
        return false;
    }

    public boolean hasRuntimeDeploymentDescriptor(AbstractArchive archive) 
        throws IOException {

        //check null: since .par archive does not have runtime dds
        if (getConfigurationDDFile() != null) {
            InputStream runIs = archive.getEntry(
                getConfigurationDDFile().getDeploymentDescriptorPath());
            if (runIs != null) {
                runIs.close();
                return true;
            }
        }
        return false;
    }
    
    /**
     * creates a new module descriptor for this archivist
     * @return the new module descriptor
     */
    public ModuleDescriptor createModuleDescriptor(BundleDescriptor descriptor) {
        ModuleDescriptor newModule = new ModuleDescriptor();
        newModule.setModuleType(getModuleType());
        newModule.setDescriptor(descriptor);
        setDescriptor(descriptor);        
        return newModule;
    } 
        
    
    /** 
     * print the current descriptor associated with this archivist
     */
    public void printDescriptor() {
        getDescriptor().visit((DescriptorVisitor) new TracerVisitor());
    }
    
    /**
     * sets if this archivist saves the runtime info 
     * 
     * @param true to save the runtime info
     */
    public void setHandleRuntimeInfo(boolean handleRuntimeInfo) {
        this.handleRuntimeInfo = handleRuntimeInfo;
    }
    
    /**
     * @return true if this archivist will save the runtime info
     */
    public boolean isHandlingRuntimeInfo() {
        return handleRuntimeInfo;
    }
    
    /**
     * sets if this archivist process annotation
     * 
     * @param true to process annotation
     */
    public void setAnnotationProcessingRequested(
                boolean annotationProcessingRequested) {
        this.annotationProcessingRequested = annotationProcessingRequested;
    }
    
    /**
     * @return true if this archivist will process annotation
     */
    public boolean isAnnotationProcessingRequested() {
        return annotationProcessingRequested;
    }
    
    /**
     * sets annotation ErrorHandler for this archivist
     * 
     * @param annotationErrorHandler
     */
    public void setAnnotationErrorHandler(ErrorHandler annotationErrorHandler) {
        this.annotationErrorHandler = annotationErrorHandler;
    }
    
    /**
     * @return annotation ErrorHandler of this archivist
     */
    public ErrorHandler getAnnotationErrorHandler() {
        return annotationErrorHandler;
    }
    
    /**
     * sets the manifest file for this archive
     * 
     * @param manifest to use at saving time
     */
    public void setManifest(Manifest m) {
        manifest = m;
    }
    
    /**
     * @return the manifest file for this archive
     */ 
    public Manifest getManifest() {
        return manifest;
    }
    
    /**
     * Sets the class-path for this archive 
     * 
     * @param the new class-path
     */
    public void setClassPath(String newClassPath) {
        
        if (manifest==null) {
            manifest = new Manifest();
        }
        
        Attributes atts = manifest.getMainAttributes();
        atts.putValue(Attributes.Name.CLASS_PATH.toString(), newClassPath);                
    }
    
    /**
     * @return the class-path as set in the manifest associated 
     * with the archive
     */
    public String getClassPath() {
        
        if (manifest==null) {
            return null;
        }
        
        Attributes atts = manifest.getMainAttributes();
        return atts.getValue(Attributes.Name.CLASS_PATH);        
    }    
    
    /**
     * @return a list of libraries included in the archivist
     */
    public Vector getLibraries(AbstractArchive archive) {
        
        Enumeration<String> entries = archive.entries();
        if (entries==null)
            return null;
        
        Vector libs = new Vector();        
        while (entries.hasMoreElements()) {
            
            String entryName = entries.nextElement();
            if (entryName.indexOf('/')!=-1) {
                continue; // not on the top level
            }
            if (entryName.endsWith(".jar")) {
                libs.add(entryName);
            }            
        }
        return libs;
    }

    /** 
    * @returns an entry name unique amongst names in this archive based on the triel name.
    */
    protected String getUniqueEntryFilenameFor(AbstractArchive archive, String trialName) throws IOException {
        Vector entriesNames = new Vector();
        Enumeration e = archive.entries();
        while (e!=null && e.hasMoreElements()) {
            entriesNames.add(e.nextElement());
        }
	return Descriptor.createUniqueFilenameAmongst(trialName, entriesNames);
    }
    
    /**
     * Add this archive to an application archivist 
     * 
     * @param application archive to add itself to
     * @param library jars for this archive
     * @param external deployment descriptor path
     */
    protected ModuleDescriptor addToArchive(ApplicationArchivist appArch, String externalDD) 
        throws IOException, SAXParseException {
                 

        AbstractArchive archiveToBeAdded = abstractArchiveFactory.openArchive(path);
        File archiveFile = new File(path);
        AbstractArchive appArchive = abstractArchiveFactory.openArchive(appArch.getArchiveUri());
	String archiveName = getUniqueEntryFilenameFor(appArchive, archiveFile.getName());
        appArchive.close();
        Descriptor descriptor = null;
	if (externalDD != null && !"".equals(externalDD)) {
            File externalDescriptorFile = new File(externalDD);            
            if (externalDescriptorFile.exists()) {
                FileInputStream fis = new FileInputStream(externalDescriptorFile);
                try {
                    DeploymentDescriptorFile ddf = getStandardDDFile();
                    descriptor = ddf.read(fis);                                       
                } catch(SAXParseException pe) {
                    archiveToBeAdded.close();
                    pe.printStackTrace();     
                    // propagate
                    throw pe;
                } 
            }
        }
        if (descriptor==null) {
            // We get the deployment descriptors, including maybe the 
            // runtime deployment descriptors
	    descriptor = open(archiveToBeAdded);
        }
        archiveToBeAdded.close();
        
        // Create a new module descriptor for this new module
        if (descriptor instanceof BundleDescriptor) {
            ModuleDescriptor newModule = new ModuleDescriptor();
            newModule.setArchiveUri(archiveName);
            newModule.setModuleType(getModuleType());
            newModule.setDescriptor((BundleDescriptor) descriptor);
            newModule.setManifest(getManifest());
            if (externalDD != null && !"".equals(externalDD)) {
                newModule.setAlternateDescriptor(externalDD);
            }        
            return newModule;
        } else {
            return null;
        }
    }
        
    /**
     * prepare an archivist for inclusion in a application archive.
     * 
     * @param archive file where this archivist will be saved
     */
    protected void prepareForInclusion(AbstractArchive out) throws IOException {
    }
      
    public void saveRuntimeInfo(File output) throws IOException {
        // if output file is null, we overwrite the current archive...
        File outputFile = output;        
        if (outputFile==null) {
            outputFile = getTempFile(path);
        }
        
        // copy all entries from source to target except the 
        // runtime descriptor file
        AbstractArchive out = abstractArchiveFactory.createArchive(outputFile.getAbsolutePath());
        AbstractArchive in = abstractArchiveFactory.openArchive(path);
        Vector skipFiles = new Vector();
        skipFiles.add(getRuntimeDeploymentDescriptorPath());
        copyInto(in, out, skipFiles);
        in.close();
        
        // now save the runtime deployment descriptor...
        OutputStream os = out.putNextEntry(getRuntimeDeploymentDescriptorPath());
        writeRuntimeDeploymentDescriptors(os);
        out.closeEntry();
        out.close();
        
        // if we overwrote the old archive, need to rename the tmp now
        if (output==null) {
            AbstractArchive finalArchive = abstractArchiveFactory.openArchive(path);
            finalArchive.delete();
            AbstractArchive tmpArchive = abstractArchiveFactory.openArchive(outputFile.getAbsolutePath());
            tmpArchive.renameTo(path);
        }
        
    }
    
    /**
     * apply runtimne info to this archive descriptors and saves it
     */
    public void applyRuntimeInfo(File runtimeDD, File output) throws IOException, SAXParseException {

        // update the runtime info
        getConfigurationDDFile().read(getDescriptor(), new FileInputStream(runtimeDD));

        // save the runtime info...
        saveRuntimeInfo(output);
    }
    
    /**
     * utility method to get a tmp file in the current user directory of the provided
     * directory
     * 
     * @param File file or directory to use as temp location (use parent directory 
     * if a file is provided)
     */
    protected static File getTempFile(String fileOrDirPath) throws IOException {
        if (fileOrDirPath!=null) {
            return getTempFile(new File(fileOrDirPath));
        } else {
            return getTempFile((File) null);
        }
    }
    
    /**
     * @return the list of files that should not be copied from the old archive 
     * when a save is performed.
     */
    public Vector getListOfFilesToSkip() {
        
        Vector filesToSkip = new Vector();
        filesToSkip.add(getDeploymentDescriptorPath());
        if (manifest!=null) {
            filesToSkip.add(JarFile.MANIFEST_NAME);
        }
        if (getRuntimeDeploymentDescriptorPath()!=null) {
            filesToSkip.add(getRuntimeDeploymentDescriptorPath());
        }

        // Can't depend on having a descriptor, so skip all possible
        // web service deployment descriptor paths.
        filesToSkip.addAll(WebServicesDeploymentDescriptorFile.
                        getAllDescriptorPaths());

        return filesToSkip;
    }
    
    /**
     * utility method to get a tmp file in the current user directory of the provided
     * directory
     * 
     * @param File file or directory to use as temp location (use parent directory 
     * if a file is provided)
     */
    protected static File getTempFile(File fileOrDir) throws IOException {
        
        File dir = null;
        if (fileOrDir==null) {
            dir=new File(System.getProperty("user.dir"));
        } else {            
            if (!fileOrDir.isDirectory()) {
                dir = fileOrDir.getParentFile();
                if (dir==null) {
                    dir=new File(System.getProperty("user.dir"));
                }     
            } else {
                dir = fileOrDir;
            }
        }
        return File.createTempFile("tmp", ".jar", dir);
    }
    
    /**
     * add a file to an output abstract archive
     * 
     * @param archive abstraction to use when adding the file
     * @param path to the file to add
     * @param entryName the entry name in the archive
     */
    protected static void addFileToArchive(AbstractArchive archive, String filePath, String entryName) 
        throws IOException {
            
        FileInputStream is = new FileInputStream(new File(filePath));
        OutputStream os = archive.putNextEntry(entryName);
        ArchivistUtils.copyWithoutClose(is, os);
        is.close();
        archive.closeEntry();
    }
    
    /**
     * copy all contents of a jar file to a new jar file except 
     * for all the deployment descriptors files
     * 
     * @param input jar file
     * @param output jar file
     * @param vector of entry name to not copy from to source jar file
     */
    protected void copyJarElements(AbstractArchive in, AbstractArchive out, Vector ignoreList) 
        throws IOException {
                    
        Enumeration entries = in.entries();
        // we need to add the list of existing entries in the output
        // archive to the list of files to ignore to avoid any collision
        for (Enumeration outEntriesItr = out.entries();outEntriesItr.hasMoreElements();) {
            if (ignoreList==null) {
                ignoreList = new Vector();
            }
            ignoreList.add(outEntriesItr.nextElement());
        }
        if (entries!=null) {
            for (;entries.hasMoreElements();) {
                String anEntry = (String) entries.nextElement();
                if (ignoreList==null || !ignoreList.contains(anEntry)) {
                    InputStream is = in.getEntry(anEntry);
                    OutputStream os = out.putNextEntry(anEntry);
                    ArchivistUtils.copyWithoutClose(is, os);
                    is.close();
                    out.closeEntry();
                }
            }  
        }
    }
    
    /**
     * rename a tmp file 
     * 
     * @param old name
     * @param new name
     */
    protected boolean renameTmp(String from, String to) throws IOException {
        
        AbstractArchive finalArchive = abstractArchiveFactory.openArchive(to);
        finalArchive.delete();
        AbstractArchive tmpArchive = abstractArchiveFactory.openArchive(from);
        boolean success = tmpArchive.renameTo(to);
        if (!success) {            
            throw new IOException("Error renaming JAR");
        }
        return success;
    }    
    
    /**
     * Sets the path for this archivist's archive file
     */
    public void setArchiveUri(String path) {
        this.path = path;
    }
    
    /**
     * @return the path for this archivist's archive file
     */
    public String getArchiveUri() {
        return path;
    }
    
    /**
     * Sets the classloader for this archivist
     * @param class loader
     */
    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }
    
    /**
     * Turn on or off the XML Validation for all standard deployment 
     * descriptors loading
     * @param true to turn on XML validation
     */
     public void setXMLValidation(boolean validate) {
	 isValidatingXML=validate;
     }
     
     /**
      * @return true if the Deployment Descriptors XML will be validated
      * while reading.
      */
    public boolean getXMLValidation() {
	return isValidatingXML;
    }

    /**
     * Turn on or off the XML Validation for runtime deployment
     * descriptors loading
     * @param true to turn on XML validation
     */
     public void setRuntimeXMLValidation(boolean validate) {
         isValidatingRuntimeXML = validate;
     }

     /**
      * @return true if the runtime XML will be validated
      * while reading.
      */
    public boolean getRuntimeXMLValidation() {
        return isValidatingRuntimeXML;
    }

    /**
     * Sets the xml validation error reporting/recovering level.
     * The reporting level is active only when xml validation is
     * turned on @see setXMLValidation.
     * so far, two values can be passed, medium which reports the 
     * xml validation and continue and full which reports the 
     * xml validation and stop the xml parsing.
     */
     public void setXMLValidationLevel(String level) {
	 validationLevel = level;
     }
     
     /**
      * @return the xml validation reporting level
      */
      public String getXMLValidationLevel() {
	  return validationLevel;
      }

    /**
     * Sets the runtime xml validation error reporting/recovering level.
     * The reporting level is active only when xml validation is
     * turned on @see setXMLValidation.
     * so far, two values can be passed, medium which reports the
     * xml validation and continue and full which reports the
     * xml validation and stop the xml parsing.
     */
     public void setRuntimeXMLValidationLevel(String level) { 
         runtimeValidationLevel = level;
     }
     
     /**
      * @return the runtime xml validation reporting level
      */
      public String getRuntimeXMLValidationLevel() {
          return runtimeValidationLevel;
      }
    
    /**
     * 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) {   
    }   
    
    /**
     * Copy this archivist to a new abstract archive
     * @param out the new archive to use to copy our contents into
     */
    public void copyInto(AbstractArchive target) throws IOException {
        AbstractArchive source = abstractArchiveFactory.openArchive(path);
        copyInto(source, target);
    }     
    
    /**
     * Copy source archivist to a target abstract archive.  By default, 
     * every entry in source archive will be copied to the target archive,
     * including the manifest of the source archive.
     * @param source the source archive to copy from
     * @param target the target archive to copy to
     * @param entriesToSkip the entries that will be skipped by target archive
     */
    public void copyInto(AbstractArchive source, AbstractArchive target) throws IOException {
        copyInto(source, target, null, true);
    }    

    /**
     * Copy source archivist to a target abstract archive.  By default, 
     * every entry in source archive will be copied to the target archive.
     * @param source the source archive to copy from
     * @param target the target archive to copy to
     * @param overwriteManifest if true, the manifest in source archive 
     *                          overwrites the one in target archive
     */
    public void copyInto(AbstractArchive source, AbstractArchive target, boolean overwriteManifest) throws IOException {
        copyInto(source, target, null, overwriteManifest);
    }    

    /**
     * Copy source archivist to a target abstract archive.  By default, the manifest
     * in source archive overwrites the one in target archive.
     * @param source the source archive to copy from
     * @param target the target archive to copy to
     * @param entriesToSkip the entries that will be skipped by target archive
     */
    public void copyInto(AbstractArchive source, AbstractArchive target, Vector entriesToSkip) 
        throws IOException {   
        copyInto(source, target, entriesToSkip, true);
    }
    
    /**
     * Copy this archivist to a new abstract archive
     * @param source the source archive to copy from
     * @param target the target archive to copy to
     * @param entriesToSkip the entries that will be skipped by target archive
     * @param overwriteManifest if true, the manifest in source archive 
     *                          overwrites the one in target archive
     */
    public void copyInto(AbstractArchive source, AbstractArchive target, 
                         Vector entriesToSkip, boolean overwriteManifest) 
        throws IOException {   
            
        copyJarElements(source, target, entriesToSkip);
        if (overwriteManifest) {
            Manifest m = source.getManifest();
            if (m != null) {
                OutputStream os = target.putNextEntry(JarFile.MANIFEST_NAME);
                m.write(os);
                target.closeEntry();
            }
        }
    }
    
    /**
     * extract a entry of this archive to a file
     * @param the entry name
     * @param the file to copy the entry into
     */
    public void extractEntry(String entryName, File out) throws IOException {
        AbstractArchive archive = abstractArchiveFactory.openArchive(path);
        InputStream is = archive.getEntry(entryName);
        OutputStream os = new BufferedOutputStream(new FileOutputStream(out));
        ArchivistUtils.copy(new BufferedInputStream(is), os);
        archive.close();
    }
    
    /**
     * Sets the pluggable archivist factory for this instance
     */
    public void setPluggableArchivists(PluggableArchivists pa) {
        this.pa = pa;
    }
    
    /**
     * @return the pluggable archivist factory
     */
    public PluggableArchivists getPluggableArchivists() {
        if (pa==null) {
            return ArchivistFactory.getPluggableArchivists();
        } else {
            return pa;
        }
    }    

    public static void copyAnEntry(AbstractArchive in,
        AbstractArchive out, String entryName) throws IOException {
        InputStream is = null;
        InputStream is2 = null;
        try {
            is = in.getEntry(entryName);
            is2 = out.getEntry(entryName);
            if (is != null && is2 == null) {
                OutputStream os = out.putNextEntry(entryName);
                ArchivistUtils.copyWithoutClose(is, os);
            }
        } finally {
            /*
             *Close any streams that were opened.
             */
            if (is != null) {
                is.close();
            }
            if (is2 != null) {
                is2.close();
            }
            out.closeEntry();
        }
    }

    public void copyStandardDeploymentDescriptors(AbstractArchive in,
        AbstractArchive out) throws IOException {
        String entryName = getDeploymentDescriptorPath();
        copyAnEntry(in, out, entryName);
        
        Descriptor desc = getDescriptor();

        // only bundle descriptor can have web services
        if (desc instanceof BundleDescriptor) { 
            BundleDescriptor desc2 = (BundleDescriptor)desc;
            if (desc2.hasWebServices()) {
                DeploymentDescriptorFile webServicesDD = 
                    getWebServicesDDFile((BundleDescriptor)desc2);
                String anEntry = webServicesDD.getDeploymentDescriptorPath();
                copyAnEntry(in, out, anEntry);
            }
        }
    }   

    // copy wsdl and mapping files etc 
    public static void copyExtraElements(AbstractArchive in, 
        AbstractArchive out) throws IOException {
        Enumeration entries = in.entries();
        if (entries!=null) {
            for (;entries.hasMoreElements();) {
                String anEntry = (String) entries.nextElement();
                if(anEntry.endsWith(PERSISTENCE_DD_ENTRY)) {
                    // Don't copy persistence.xml file because they are some times
                    // bundled inside war/WEB-INF/lib/*.jar and hence we always
                    // read them from exploded directory.
                    // see Integration Notice #80587
                    continue;
                }
                if (anEntry.indexOf(WSDL) != -1  ||
                    anEntry.indexOf(XML) != -1 ||
                    anEntry.indexOf(XSD) != -1) {
                    copyAnEntry(in, out, anEntry);
                }
            }
        }
     }

    protected void readPersistenceDeploymentDescriptor(
            Archive subArchive, String puRoot, Descriptor descriptor)
            throws IOException, SAXParseException {
        final String subArchiveURI = AbstractArchive.class.cast(subArchive).getArchiveUri();
        if (logger.isLoggable(Level.FINE)) {
            logger.logp(Level.FINE, "Archivist",
                    "readPersistenceDeploymentDescriptor",
                    "PURoot = [{0}] subArchive = {1}",
                    new Object[]{puRoot, subArchiveURI});
        }
        final RootDeploymentDescriptor rootDD =
                RootDeploymentDescriptor.class.cast(descriptor);
        if (rootDD.getPersistenceUnitsDescriptor(puRoot) != null) {
            if (logger.isLoggable(Level.FINE)) {
                logger.logp(Level.FINE, "Archivist",
                        "readPersistenceDeploymentDescriptor",
                        "PU has been already read for = {0}",
                        subArchiveURI);
            }
            return;
        }
        PersistenceDeploymentDescriptorFile persistenceDeploymentDescriptorFile
                = new PersistenceDeploymentDescriptorFile();
        persistenceDeploymentDescriptorFile.setErrorReportingString(
                subArchiveURI.toString());
        persistenceDeploymentDescriptorFile.setXMLValidation(getXMLValidation());
        persistenceDeploymentDescriptorFile.setXMLValidationLevel(
                getXMLValidationLevel());
        InputStream is = subArchive.getEntry(
                persistenceDeploymentDescriptorFile.getDeploymentDescriptorPath());
        if (is == null) {
            if (logger.isLoggable(Level.FINE)) {
                logger.logp(Level.FINE, "Archivist",
                    "readPersistenceDeploymentDescriptor",
                    "{0} does not contain {1}, so it is not a PU Root.",
                    new Object[]{subArchiveURI,
                                 persistenceDeploymentDescriptorFile.getDeploymentDescriptorPath()});
            }
            return;
        }
        try {
            PersistenceUnitsDescriptor persistenceUnitsDescriptor =
                    PersistenceUnitsDescriptor.class.cast(
                            persistenceDeploymentDescriptorFile.read(rootDD, is));
            rootDD.addPersistenceUnitsDescriptor(puRoot,
                    persistenceUnitsDescriptor);
        } finally {
            is.close();
        }
    }    
}