FileDocCategorySizeDatePackage
J2EEModuleExploder.javaAPI DocGlassfish v2 API17289Fri May 04 22:34:32 BST 2007com.sun.enterprise.deployment.backend

J2EEModuleExploder.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.backend;

import java.io.IOException;
import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.nio.*;
import java.nio.channels.*;

import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.serverbeans.DasConfig;
import com.sun.enterprise.config.serverbeans.ServerBeansFactory;
import com.sun.enterprise.deployment.Application;
import com.sun.enterprise.deployment.archivist.ApplicationArchivist;
import com.sun.enterprise.deployment.archivist.Archivist;
import com.sun.enterprise.deployment.archivist.ArchivistFactory;
import com.sun.enterprise.deployment.deploy.shared.AbstractArchive;
import com.sun.enterprise.deployment.deploy.shared.AbstractArchiveFactory;
import com.sun.enterprise.deployment.deploy.shared.FileArchive;
import com.sun.enterprise.deployment.deploy.shared.FileArchiveFactory;
import com.sun.enterprise.deployment.deploy.shared.InputJarArchive;
import com.sun.enterprise.deployment.deploy.shared.JarArchiveFactory;
import com.sun.enterprise.deployment.io.DeploymentDescriptorFile;
import com.sun.enterprise.deployment.util.ModuleDescriptor;
import com.sun.enterprise.server.ApplicationServer;
import com.sun.enterprise.util.i18n.StringManager;
import com.sun.enterprise.util.io.FileUtils;
import com.sun.enterprise.util.OS;
import com.sun.enterprise.util.zip.ZipFile;
import com.sun.enterprise.util.zip.ZipFileException;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipException;
import javax.enterprise.deploy.shared.ModuleType;


/**
 * this class is responsible for exploding a J2EE archive file into
 * a directory/file based structures that is used by the deployment
 * and app server runtime.
 *
 * @author Jerome Dochez
 */

public class J2EEModuleExploder {
    
    private static final StringManager localStrings =
            StringManager.getManager( J2EEModuleExploder.class );

    private static Logger logger = null;
    
    private static final String PRESERVED_MANIFEST_NAME = java.util.jar.JarFile.MANIFEST_NAME + ".preserved";
    
    private static final String WEB_INF_PREFIX = "WEB-INF/";

	private static String validationLevel = null;
    
    /**
     * explode the passed archive file inside the directory
     * <p>
     * This is the original signature for this method.  It now delegates to the other variant
     * but specifies the directory argument as also the directory into which to expand nested jar
     * file contents.  This preserves the original behavior of the method.
     *
     * @param the archive
     * @param the destination directory
     * @param the module name for the archive
     */
    public static void explode(File archive, File directory, String moduleName)
    throws IOException, IASDeploymentException {
        explode(archive, directory, moduleName, false);
    }
    
    /**
     * explode the passed archive file inside the directory
     * @param the archive
     * @param the destination directory
     * @param the module name for the archive
     * @param whether to prevent manifests from exploded nested jar files from overwriting the archive's manifest
     */
    public static void explode(File archive, File directory, String moduleName, boolean preserveManifest)
    throws IOException, IASDeploymentException {
        assert archive != null;
        
        
        AbstractArchiveFactory factory = null;
        if (archive.isDirectory()) {
            factory = new FileArchiveFactory();
        } else {
            factory = new JarArchiveFactory();
        }
        AbstractArchive source = factory.openArchive(archive.getAbsolutePath());
        
        // now copy the archive, let the archivist do the job...
        Archivist archivist = null;
        try {
            archivist = ArchivistFactory.getArchivistForArchive(source);
            if (archivist == null) {
                String msg = localStrings.getString
                        ("enterprise.deployment.backend.no_archivist_recognized_arch",
                        archive.getAbsolutePath()
                        );
                throw new IASDeploymentException(msg);
            }
        } catch (IOException ioe) {
            String msg = localStrings.getString
                    ("enterprise.deployment.backend.error_getting_archivist",
                    archive.getAbsolutePath()
                    );
            throw new IASDeploymentException(msg, ioe);
        }
        if (!archivist.getModuleType().equals(ModuleType.EAR)) {
            explodeModule(archivist, source, directory, moduleName, preserveManifest);
        }
    }
    
    private static void explodeModule(Archivist archivist, AbstractArchive source, File directory, String moduleName, boolean preserveManifest)
    throws IOException, IASDeploymentException {
        
        File explodedManifest = null;
        File preservedManifestFromArchive = null;
        
        FileArchive target = new FileArchive();
        target.create(directory.getAbsolutePath());
        
        explodeJar(new File(source.getArchiveUri()), directory);
        //archivist.copyInto(source, target);
        
        if (preserveManifest) {
            explodedManifest = new File(directory, java.util.jar.JarFile.MANIFEST_NAME);
            if (explodedManifest.exists()) {
                /* Rename the manifest so it can be restored later. */
                preservedManifestFromArchive = new File(directory, PRESERVED_MANIFEST_NAME);
                try {
                    if (OS.isWindows()) {
                        FileUtils.validateWindowsFilePathLength(preservedManifestFromArchive);
                    }
                } catch (IOException ioe) {
                    IOException newIOE = new IOException(localStrings.getString(
                            "enterprise.deployment.backend.error_saving_manifest",
                            new Object[] 
                                { explodedManifest.getAbsolutePath(),
                                          preservedManifestFromArchive.getAbsolutePath()
                                }
                            ));
                    newIOE.initCause(ioe);
                    throw newIOE;
                }
                if ( ! explodedManifest.renameTo(preservedManifestFromArchive)) {
                    throw new RuntimeException(localStrings.getString(
                            "enterprise.deployment.backend.error_saving_manifest",
                            new Object[]
                    { explodedManifest.getAbsolutePath(),
                              preservedManifestFromArchive.getAbsolutePath()
                    } ) ) ;
                }
            }
        }
        // now explode all top level jar files and delete them.
        // this cannot be done before since the optionalPkgDependency
        // require access to the manifest file of each .jar file.
        for (Enumeration itr = source.entries();itr.hasMoreElements();) {
            String fileName = (String) itr.nextElement();
            
            
            // check for optional packages depencies
            // XXX : JEROME look if this is still done
            // optionalPkgDependencyLogic(new File(directory, fileName));
            
             /*
              *Expand the file only if it is a jar and only if it does not lie in WEB-INF/lib.
              */
            if (fileName.toLowerCase().endsWith(".jar") && ( ! fileName.replace('\\', '/').toUpperCase().startsWith(WEB_INF_PREFIX)) ) {
                
                try {
                    File f = new File(directory, fileName);
                    
                    File targetDirectory = directory;
                    
                    ZipFile zip = new ZipFile(f, targetDirectory);
                    zip.explode();
                } catch(ZipFileException e) {
                    IOException ioe = new IOException(e.getMessage());
                    ioe.initCause(e);
                    throw ioe;
                }
            }
        }
         /*
          *If the archive's manifest was renamed to protect it from being overwritten by manifests from
          *jar files, then rename it back.  Delete an existing manifest file first if needed.
          */
        if (preservedManifestFromArchive != null) {
            if (explodedManifest.exists()) {
                if ( ! explodedManifest.delete()) {
                    throw new RuntimeException(localStrings.getString(
                            "enterprise.deployment.backend.error_deleting_manifest",
                            new Object []
                    { explodedManifest.getAbsolutePath(),
                              preservedManifestFromArchive.getAbsolutePath()
                    }
                    ) );
                }
            }
            
            if ( ! preservedManifestFromArchive.renameTo(explodedManifest)) {
                throw new RuntimeException(localStrings.getString(
                        "enterprise.deployment.backend.error_restoring_manifest",
                        new Object []
                { preservedManifestFromArchive.getAbsolutePath(),
                          explodedManifest.getAbsolutePath()
                }
                ) );
            }
        }
        
        source.close();
        target.close();
    }

    public static Application explodeEar(File source, File destination) throws Exception {
        
        // first explode the ear file
        explodeJar(source, destination);
        
        // now we need to load the application standard deployment descriptor.
        ApplicationArchivist archivist = new ApplicationArchivist();
        archivist.setXMLValidationLevel(getValidationLevel());
        FileArchive appArchive = new FileArchive();
        appArchive.open(destination.getAbsolutePath());
        
        archivist.setManifest(appArchive.getManifest());
        
        // read the standard deployment descriptors
        Application appDesc = null;
        if (archivist.hasStandardDeploymentDescriptor(appArchive)) {
            appDesc = (Application) 
            archivist.readStandardDeploymentDescriptor(appArchive);
        } else {
            appDesc = Application.createApplication(appArchive,true);
        }

        archivist.setDescriptor(appDesc);
        
        // ok we should now have the list of modules, so we can happily explode them...
        Iterator<ModuleDescriptor> bundles = appDesc.getModules();
        while (bundles.hasNext()) {
            
            ModuleDescriptor bundle = bundles.next();
            
            String moduleName = bundle.getArchiveUri();
            String massagedModuleName =  FileUtils.makeFriendlyFilename(moduleName);
            File archiveFile = new File(destination, moduleName);
            File moduleDir = new File(destination, massagedModuleName);
            explodeJar(archiveFile, moduleDir);
            
            // delete the original module file
            archiveFile.delete();
        }
        
        return appDesc;
    }
    
    
    
    public static void explodeJar(File source, File destination) throws IOException {
        JarFile jarFile = null;
        String fileSystemName = null; // declared outside the try block so it's available in the catch block
        try {
            jarFile = new JarFile(source);
            Enumeration<JarEntry> e = jarFile.entries();
            while (e.hasMoreElements()) {
                JarEntry entry = e.nextElement();
                fileSystemName = entry.getName().replace('/', File.separatorChar);
                File out = new File(destination, fileSystemName);
                if (OS.isWindows() ) {
                    FileUtils.validateWindowsFilePathLength(out);
                }
                
                if (entry.isDirectory()) {
                    out.mkdirs();
                } else {
                    InputStream is = null;
                    FileOutputStream fos = null;
                    try {
                        if (!out.getParentFile().exists()) {
                            out.getParentFile().mkdirs();
                        }
                        is = new BufferedInputStream(jarFile.getInputStream(entry));
                        fos = FileUtils.openFileOutputStream(out);
                        ReadableByteChannel inChannel = Channels.newChannel(is);
                        FileChannel outChannel = fos.getChannel();
                        outChannel.transferFrom(inChannel, 0, entry.getSize());
                    } finally {
                        if (is!=null)
                            is.close();
                        if (fos!=null)
                            fos.close();
                    }
                }
            }
        } catch(Throwable e) {
            /*
             *Use the logger here, even though we rethrow the exception.  In
             *at least some cases the caller does not propagate this exception 
             *further, instead replacing it with a serializable 
             *IASDeployException.  The added information is then lost.
             *By logging the exception here, we make sure the log file at least
             *displays as much as we know about the problem even though the 
             *exception sent to the client may not.
             */
            String msg0 = localStrings.getString(
                    "enterprise.deployment.backend.error_expanding", 
                    new Object[] {source.getAbsolutePath()});
            String msg = localStrings.getString(
                    "enterprise.deployment.backend.could_not_expand",
                    new Object[] {fileSystemName, destination.getAbsolutePath() });
            IOException ioe = new IOException(msg0);
            ioe.initCause(e);
            getLogger().log(Level.SEVERE, msg, ioe);
            throw ioe;
        } finally {
            if (jarFile != null) {
                jarFile.close();
            }
        }
    }
    
    /**
     *Retrieves the deployment-related logger for the exploder's private use.
     *@return Logger for deployment-related messages
     */
    private static Logger getLogger() {
        if (logger == null) {
            logger = DeploymentLogger.get();
        }
        return logger;
    }

    /**
     * @return the validation level
     */
    private static String getValidationLevel() {
        if (validationLevel == null) {
	        try {
                if (ApplicationServer.getServerContext() == null) {//verifier
                    validationLevel = DeploymentDescriptorFile.FULL_VALIDATION;
                } else {
		            ConfigContext ctx = 
                    ApplicationServer.getServerContext().getConfigContext();
		            DasConfig dc = ServerBeansFactory.getDasConfigBean(ctx);
		            validationLevel = dc.getDeployXmlValidation();
                }
	        } catch(ConfigException ce) {
                //ignore error, provide default
                validationLevel = DeploymentDescriptorFile.FULL_VALIDATION;
	        }		
        }
        return validationLevel;
    }
}