All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.enterprise.deployment.deploy.shared.InputJarArchive Maven / Gradle / Ivy

There is a newer version: 7.2024.1.Alpha1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2006-2013 Oracle and/or its affiliates. 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_1_1.html
 * or packager/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 packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [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.
 */
// Portions Copyright [2022] Payara Foundation and/or affiliates

package com.sun.enterprise.deployment.deploy.shared;

import com.sun.enterprise.util.i18n.StringManager;
import com.sun.enterprise.util.io.FileUtils;
import org.glassfish.api.deployment.archive.ReadableArchive;
import org.jvnet.hk2.annotations.Service;

import org.glassfish.hk2.api.PerLookup;

import java.io.*;
import org.glassfish.hk2.utilities.CleanerFactory;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.net.URI;
import java.net.URISyntaxException;

import org.glassfish.logging.annotation.LogMessageInfo;

/**
 * This implementation of the Archive deal with reading
 * jar files either from a JarFile or from a JarInputStream
 *
 * @author Jerome Dochez
 */
@Service(name="jar")
@PerLookup
public class InputJarArchive extends JarArchive implements ReadableArchive {
    
    public static final Logger deplLogger = org.glassfish.deployment.common.DeploymentContextImpl.deplLogger;

    @LogMessageInfo(message = " file open failure; file = {0}", level="WARNING")
    private static final String FILE_OPEN_FAILURE = "NCLS-DEPLOYMENT-00019";

    @LogMessageInfo(message = "exception message:  {0} -- invalid zip file: {1}", level="WARNING")
    private static final String INVALID_ZIP_FILE = "NCLS-DEPLOYMENT-00020";

    // the file we are currently mapped to 
    volatile protected JarFile jarFile;
    
    // in case this abstraction is dealing with a jar file
    // within a jar file, the jarFile will be null and this
    // JarInputStream will contain the 
    volatile protected JarInputStream jarIS;
    
    // the archive Uri
    volatile private URI uri;

    // parent jar file for embedded jar
    private InputJarArchive parentArchive;

    private static StringManager localStrings = StringManager.getManager(InputJarArchive.class);

    // track entry enumerations to close them if needed when the archive is closed
    private final WeakHashMap entryEnumerations =
            new WeakHashMap();

    /**
     * Get the size of the archive
     * @return tje the size of this archive or -1 on error
     */
    public long getArchiveSize() throws NullPointerException, SecurityException {
        if(uri == null) {
            return -1;
        }

        return new File(uri).length();
    }
    
    /** @return an @see java.io.OutputStream for a new entry in this
     * current abstract archive.
     * @param name the entry name
     */
    public OutputStream addEntry(String name) throws IOException {
        throw new UnsupportedOperationException("Cannot write to an JAR archive open for reading");        
    }

    /**
     * close the abstract archive
     */
    public synchronized void close() throws IOException {
        for (EntryEnumeration e : entryEnumerations.keySet()) {
            e.closeNoRemove();
        }
        entryEnumerations.clear();
        if (jarFile != null) {
            jarFile.close();
            jarFile = null;
        }
        if (jarIS != null) {
            jarIS.close();
            jarIS = null;
        }
    }

    private synchronized EntryEnumeration recordEntryEnumeration(final EntryEnumeration e) {
        entryEnumerations.put(e, null);
        return e;
    }

    /**
     * Returns the collection of first level directories in this
     * archive.
     * 

* Avoid having to fetch all the entries if we can avoid it. The only time * we must do that is if size() is invoked on the collection. Use * the CollectionWrappedEnumeration for this optimization which will * create a full in-memory list of the entries only if and when needed * to satisfy the size() method. * * @return collection of directories under the root of this archive */ @Override public Collection getDirectories() throws IOException { return new CollectionWrappedEnumeration( new CollectionWrappedEnumeration.EnumerationFactory() { @Override public Enumeration enumeration() { return entries(true); } }); } /** * creates a new abstract archive with the given path * * @param uri the path to create the archive */ public void create(URI uri) throws IOException { throw new UnsupportedOperationException("Cannot write to an JAR archive open for reading"); } @Override public Enumeration entries() { return entries(false); } /** * Returns an enumeration of the entry names in the archive. * * @param topLevelDirectoriesOnly whether to report directories only or non-directories only * @return enumeration of the matching entry names, excluding the manifest */ private Enumeration entries(final boolean topLevelDirectoriesOnly) { try { /* * We have two decisions to make: * * 1. whether the caller wants top-level directory entries or all * non-directory entries enumerated, and * * 2. how to obtain the sequence of JarEntry objects which we filter * before returning their names. * */ return recordEntryEnumeration(createEntryEnumeration(topLevelDirectoriesOnly)); } catch (IOException ex) { throw new RuntimeException(ex); } } /** * @return an @see java.util.Enumeration of entries in this abstract * archive, providing the list of embedded archive to not count their * entries as part of this archive */ public Enumeration entries(Enumeration embeddedArchives) { // jar file are not recursive return entries(); } public JarEntry getJarEntry(String name) { if (jarFile == null) { return null; } return jarFile.getJarEntry(name); } /** * Returns the existence of the given entry name * The file name must be relative to the root of the module. * * @param name the file name relative to the root of the module. * @return the existence the given entry name. */ public boolean exists(String name) throws IOException { if (jarFile == null) { return false; } return jarFile.getEntry(name) != null; } /** * @return a @see java.io.InputStream for an existing entry in * the current abstract archive * @param entryName entry name */ public InputStream getEntry(String entryName) throws IOException { //for the momment this is a fix to prevent inputstream issues if(jarFile == null) { open(this.getURI()); } if (jarFile != null) { ZipEntry ze = jarFile.getEntry(entryName); if(ze == null) { return null; } return new BufferedInputStream(jarFile.getInputStream(ze)); } if (parentArchive == null || parentArchive.jarFile == null) { return null; } // close the current input stream if (jarIS != null) { jarIS.close(); } // reopen the embedded archive and position the input stream // at the beginning of the desired element JarEntry archiveJarEntry = (uri != null) ? parentArchive.jarFile.getJarEntry(uri.getSchemeSpecificPart()) : null; if (archiveJarEntry == null) { return null; } JarEntry je; jarIS = new JarInputStream(parentArchive.jarFile.getInputStream(archiveJarEntry)); do { je = jarIS.getNextJarEntry(); } while (je != null && !je.getName().equals(entryName)); if(je == null) { return null; } return new BufferedInputStream(jarIS); } /** * Returns the entry size for a given entry name or 0 if not known * * @param name the entry name * @return the entry size */ public long getEntrySize(String name) { if (jarFile == null) { return 0; } ZipEntry ze = jarFile.getEntry(name); if (ze == null) { return 0; } return ze.getSize(); } /** Open an abstract archive * @param uri the path to the archive */ public void open(URI uri) throws IOException { this.uri = uri; jarFile = getJarFile(uri); } /** * @return a JarFile instance for a file path */ protected static JarFile getJarFile(URI uri) throws IOException { JarFile jarFile = null; try { File file = new File(uri); if (file.exists()) { jarFile = new JarFile(file); } } catch (IOException e) { deplLogger.log(Level.WARNING, FILE_OPEN_FAILURE, new Object[]{uri}); // add the additional information about the path // since the IOException from jdk doesn't include that info String additionalInfo = localStrings.getString("enterprise.deployment.invalid_zip_file", uri); deplLogger.log(Level.WARNING, INVALID_ZIP_FILE, new Object[]{e.getLocalizedMessage(), additionalInfo}); } return jarFile; } /** * @return the manifest information for this abstract archive */ public Manifest getManifest() throws IOException { if (jarFile != null) { return jarFile.getManifest(); } if (parentArchive == null) { return null; } // close the current input stream if (jarIS != null) { jarIS.close(); } // reopen the embedded archive and position the input stream // at the beginning of the desired element if (jarIS == null) { jarIS = new JarInputStream(parentArchive.jarFile.getInputStream(parentArchive.jarFile. getJarEntry(uri.getSchemeSpecificPart()))); } Manifest manifest = jarIS.getManifest(); if (manifest == null) { java.io.InputStream is = getEntry(java.util.jar.JarFile.MANIFEST_NAME); if (is != null) { manifest = new Manifest(); manifest.read(is); is.close(); } } return manifest; } /** * Returns the path used to create or open the underlying archive * * @return the path for this archive. */ public URI getURI() { return uri; } /** * @return true if this abstract archive maps to an existing * jar file */ public boolean exists() { return jarFile != null; } /** * deletes the underlying jar file */ public boolean delete() { if (jarFile == null) { return false; } try { jarFile.close(); jarFile = null; } catch (IOException ioe) { return false; } return FileUtils.deleteFile(new File(uri)); } /** * rename the underlying jar file */ public boolean renameTo(String name) { if (jarFile == null) { return false; } try { jarFile.close(); jarFile = null; } catch (IOException ioe) { return false; } return FileUtils.renameFile(new File(uri), new File(name)); } /** * @return an Archive for an embedded archive indentified with * the name parameter */ public ReadableArchive getSubArchive(String name) throws IOException { if (jarFile != null) { // for now, I only support one level down embedded archives InputJarArchive ija = new InputJarArchive(); JarEntry je = jarFile.getJarEntry(name); if (je != null) { JarInputStream jis = new JarInputStream(new BufferedInputStream(jarFile.getInputStream(je))); try { ija.uri = new URI("jar", name, null); } catch (URISyntaxException e) { // do nothing } ija.jarIS = jis; ija.parentArchive = this; return ija; } } return null; } /** * Creates the correct type of entry enumeration, depending on whether the * current archive is nested or not and depending on whether the caller * requested top-level directory entries or all non-directory entries be returned * in the enumeration. * * @param uriToReadForEntries * @param topLevelDirectoriesOnly * @return * @throws FileNotFoundException * @throws IOException */ private EntryEnumeration createEntryEnumeration(final boolean topLevelDirectoriesOnly) throws FileNotFoundException, IOException { final JarEntrySource source = (parentArchive == null ? new ArchiveJarEntrySource(uri) : new SubarchiveJarEntrySource(parentArchive.jarFile, uri)); if (topLevelDirectoriesOnly) { return new TopLevelDirectoryEntryEnumeration(source); } else { return new NonDirectoryEntryEnumeration(source); } } /** * Logic for enumerations of the entry names that is common between the * top-level directory entry enumeration and the full non-directory * enumeration. *

* The goal is to wrap an Enumeration around the underlying entries * available in the archive. This avoids collecting all * the entry names first and then returning an enumeration of the collection; * that can be very costly for large JARs. *

* But, the trade-off is that we need to be careful because we leave a stream * opened to the JAR. So, even though the finalizer is not guaranteed to be * invoked, we still provide one to close up the JarFile. This should help * reduce the chance for locked JARs on Windows due to open streams. */ private abstract class EntryEnumeration implements Enumeration { /* look-ahead of one entry */ private JarEntry nextMatchingEntry; /* source of JarEntry objects for building the enumeration values */ private final JarEntrySource jarEntrySource; private EntryEnumeration(final JarEntrySource jarEntrySource) { this.jarEntrySource = jarEntrySource; registerCloseEvent(); } /** * Finishes the initialization for the enumeration; MUST be invoked * from the subclass constructor after super(...). */ protected void completeInit() { nextMatchingEntry = skipToNextMatchingEntry(); } @Override public boolean hasMoreElements() { return nextMatchingEntry != null; } @Override public String nextElement() { if (nextMatchingEntry == null) { throw new NoSuchElementException(); } final String answer = nextMatchingEntry.getName(); nextMatchingEntry = skipToNextMatchingEntry(); return answer; } protected JarEntry getNextJarEntry() throws IOException { return jarEntrySource.getNextJarEntry(); } /** * Returns the next JarEntry available from the archive. *

* Different concrete subclasses implement this differently so as to * enumerate the correct sequence of entry names. * * @return the next available JarEntry; null if no more are available */ protected abstract JarEntry skipToNextMatchingEntry(); private void closeNoRemove() { try { jarEntrySource.close(); } catch (IOException ex) { throw new RuntimeException(ex); } } void close() { closeNoRemove(); entryEnumerations.remove(this); } public final void registerCloseEvent() { CleanerFactory.create().register(this, () -> { close(); }); } } /** * Defines behavior for sources of JarEntry objects for EntryEnumeration * implementations. *

* The implementation must be different for top-level archives vs. * subarchives. */ private interface JarEntrySource { /** * Returns the next JarEntry from the raw entries() enumeration * of the archive or subarchive. * @return JarEntry for the next entry in the JarArchive * @throws IOException */ JarEntry getNextJarEntry() throws IOException; /** * Closes the source of the JarEntry objects. * @throws IOException */ void close() throws IOException; } /** * Source of JarEntry objects for a top-level archive (as opposed to a * subarchive). */ private static class ArchiveJarEntrySource implements JarEntrySource { private JarFile sourceJarFile; private Enumeration jarEntries; private ArchiveJarEntrySource(final URI archiveURI) throws IOException { sourceJarFile = getJarFile(archiveURI); if (sourceJarFile == null){ throw new IOException(localStrings.getString("enterprise.deployment.invalid_zip_file", archiveURI)); } jarEntries = sourceJarFile.entries(); } @Override public JarEntry getNextJarEntry() { return (jarEntries.hasMoreElements()) ? jarEntries.nextElement() : null; } @Override public void close() throws IOException { sourceJarFile.close(); } } /** * Source of JarEntry objects for a subarchive. */ private static class SubarchiveJarEntrySource implements JarEntrySource { private JarInputStream jis; private SubarchiveJarEntrySource(final JarFile jf, final URI uri) throws IOException { final JarEntry subarchiveJarEntry = jf.getJarEntry(uri.getSchemeSpecificPart()); jis = new JarInputStream(jf.getInputStream(subarchiveJarEntry)); } @Override public JarEntry getNextJarEntry() throws IOException { return jis.getNextJarEntry(); } @Override public void close() throws IOException { jis.close(); } } /** * Enumerates the top-level directory entries. *

* This implementation uses the enumeration of JarEntry objects from the * JarFile itself. */ private class TopLevelDirectoryEntryEnumeration extends EntryEnumeration { private TopLevelDirectoryEntryEnumeration(final JarEntrySource jarEntrySource) throws FileNotFoundException, IOException { super(jarEntrySource); completeInit(); } @Override protected JarEntry skipToNextMatchingEntry() { JarEntry candidateNextEntry; try { /* * The next entry should be returned (and not skipped) if the entry is a * directory entry and it contains only a single slash at the * end of the entry name. */ while ((candidateNextEntry = getNextJarEntry()) != null) { final String candidateNextEntryName = candidateNextEntry.getName(); if (candidateNextEntry.isDirectory() && (candidateNextEntryName.indexOf('/') == candidateNextEntryName.lastIndexOf('/')) && (candidateNextEntryName.indexOf('/') == candidateNextEntryName.length() - 1)) { break; } } return candidateNextEntry; } catch (IOException ex) { throw new RuntimeException(ex); } } } /** * Enumerates entries in the archive that are not directories. */ private class NonDirectoryEntryEnumeration extends EntryEnumeration { private NonDirectoryEntryEnumeration(final JarEntrySource jarEntrySource) throws IOException { super(jarEntrySource); completeInit(); } @Override protected JarEntry skipToNextMatchingEntry() { JarEntry candidateNextEntry; try { /* * The next entry should be returned (and not skipped) if the entry is * not a directory entry and if it also not the manifest. */ while ((candidateNextEntry = getNextJarEntry()) != null) { final String candidateNextEntryName = candidateNextEntry.getName(); if (!candidateNextEntry.isDirectory() && !candidateNextEntryName.equals(JarFile.MANIFEST_NAME)) { break; } } return candidateNextEntry; } catch (IOException ex) { throw new RuntimeException(ex); } } } /** * A Collection which wraps an Enumeration. *

* Note that the nextSlot field is always updated, even if we are using * the original enumeration to return the next value from the iterator. This * is so that, if the caller invokes size() which causes us to build the * ArrayList containing all the elements -- even if that invocation comes while * the iterator is being used to return values -- the subsequent invocations * of hasNext and next will use the correct place in the newly-constructed * ArrayList of values. * * @param */ static class CollectionWrappedEnumeration extends AbstractCollection { /** Used only if size is invoked */ private ArrayList entries = null; /** always updated, even if we use the enumeration */ private int nextSlot = 0; private final EnumerationFactory factory; private Enumeration e; static interface EnumerationFactory { public Enumeration enumeration(); } CollectionWrappedEnumeration(final EnumerationFactory factory) { this.factory = factory; e = factory.enumeration(); } @Override public Iterator iterator() { return new Iterator() { @Override public boolean hasNext() { return (entries != null) ? nextSlot < entries.size() : e.hasMoreElements(); } @Override public T next() { T result = null; if (entries != null) { if (nextSlot >= entries.size()) { throw new NoSuchElementException(); } result = entries.get(nextSlot++); } else { result = e.nextElement(); nextSlot++; } return result; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public int size() { if (entries == null) { populateEntries(); }; return entries.size(); } private void populateEntries() { entries = new ArrayList(); /* * Fill up the with data from * a new enumeration. */ for (Enumeration newE = factory.enumeration(); newE.hasMoreElements(); ) { entries.add(newE.nextElement()); } e = null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy