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

org.glassfish.osgijavaeebase.OSGiBundleArchive Maven / Gradle / Ivy

There is a newer version: 1.0.9
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2009-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.
 */

package org.glassfish.osgijavaeebase;

import org.glassfish.api.deployment.archive.Archive;
import org.glassfish.api.deployment.archive.ReadableArchive;
import static org.glassfish.osgijavaeebase.Constants.FILE_PROTOCOL;
import static org.glassfish.osgijavaeebase.Constants.REFERENCE_PROTOCOL;
import org.osgi.framework.Bundle;
import static org.osgi.framework.Constants.BUNDLE_VERSION;
import org.osgi.service.url.AbstractURLStreamHandlerService;
import com.sun.enterprise.deploy.shared.AbstractReadableArchive;

import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.jar.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * Adapts a {@link Bundle} to {@link Archive}.
 * It uses JAR File space of the bundle (via getEntry and getEntryPaths APIs),
 * so a bundle does not have to be in resolved state.
 * Since it represents JAR File space of the bundle, it does not
 * consider resources from any fragments.
 *
 * @author [email protected]
 */
public class OSGiBundleArchive extends AbstractReadableArchive implements URIable, Iterable {
    private Bundle b;

    private String name;

    private URI uri;

    private Map subArchives = new HashMap();

    public OSGiBundleArchive(Bundle b) {
        this.b = b;
        init();
    }

    /**
     * This method initializes {@link #uri} and {@link #name}
     */
    private void init() {
        // The only time we can rely on a bundle's location is when the
        // location string begins with reference: scheme, as both Felix and
        // equinox assumes that the rest of the location is a file.
        // In no other case, we can use rely on bundle.getLocation()
        // to arrive at the URI of the underlying archive.
        // e.g., user can install like this:
        // bundleContext.install ("file:/a/b.jar", new URL("file:/c/d.jar").openStream));
        // In the above case, although location returns a.jar, the actual archive
        // is read from d.jar.
        // So, we return a valid URI only for reference: scheme and in all
        // cases, we prefer to return null as opposed to throwing an exception
        // to keep the behavior same as MemoryMappedArchive.
        String location = b.getLocation();
        if (location != null && location.startsWith(REFERENCE_PROTOCOL)) {
            location = location.substring(REFERENCE_PROTOCOL.length());

            // We only know how to handle reference:file: type urls.
            if (location.startsWith(FILE_PROTOCOL)) {

                // Decode any URL escaped sequences.
                location = URLDecoder.decode(location);

                // Return iff referenced file exists.
                File file = new File(location.substring(FILE_PROTOCOL.length()));
                if (file.exists()) {
                    uri = file.toURI();
                }
            }
        }

        // See issue #10536. We can't use the same policy for obtaining
        // the name as OSGi container does.
//        if (uri != null) {
//            name = Util.getURIName(uri);
//        } else {
        // See if there is a symbolic name & version. Use them,
        // else use location. Either symbolic name or location must exist
        // in a bundle.
        String symName = b.getSymbolicName();
        String version = (String) b.getHeaders().get(BUNDLE_VERSION);
        if (symName != null) {
            name = version == null ?
                    symName : symName.concat("_").concat(version);
        } else {
            name = location;
        }
//        }
    }

    public void close() throws IOException {
    }

    public Enumeration entries() {
        ArrayList entries = new ArrayList();
        getEntryPaths(entries, "/");
        ListIterator entriesIter = entries.listIterator();
        while (entriesIter.hasNext()) {
            String next = entriesIter.next();
            if (next.endsWith("/")) {
                // return only file entries as per the conract of this method
                entriesIter.remove();
            }
        }
        return Collections.enumeration(entries);
    }

    /**
     * Returns the enumeration of first level directories in this
     * archive
     *
     * @return enumeration of directories under the root of this archive
     */
    public Collection getDirectories() throws IOException {
        return getSubDiretcories("/");
    }

    /**
     * Return subdirectories under a given path. This returns only result from one level, i.e., non-recurssive
     *
     * @param path
     * @return
     */
    private Collection getSubDiretcories(String path) {
        final Enumeration firstLevelEntries = b.getEntryPaths(path);
        if (firstLevelEntries == null) return Collections.EMPTY_LIST;
        Collection firstLevelDirs = new ArrayList();
        while (firstLevelEntries.hasMoreElements()) {
            String firstLevelEntry = (String) firstLevelEntries.nextElement();
            if (firstLevelEntry.endsWith("/")) firstLevelDirs.add(firstLevelEntry);
        }
        return firstLevelDirs;
    }

    private void getEntryPaths(Collection entries, String path) {
        Enumeration subPaths = b.getEntryPaths(path);
        if (subPaths != null) {
            while (subPaths.hasMoreElements()) {
                String next = subPaths.nextElement();
                if ("META-INF/".equals(next) && entries.contains(next)) {
                    // Work around for FELIX-2935 (GLASSFISH-16477)
                    continue;
                }
                entries.add(next);
                getEntryPaths(entries, next);
            }
        }
//        BECAUSE OF A BUG IN FELIX (FELIX-1210), THE CODE ABOVE DOES NOT WORK
//        WHEN THERE ARE NO DIRECTORY ENTRIES IN THE JAR FILE.
//        IF WE CONSISTENTLY FACE THE ISSUE, THEN WE CAN USE AN ALTERNATIVE IMPL BASED ON findEntries.
//        OF COURSE, IT WILL HAVE THE UNDESIRED SIDE EFFECT OF FINDINDG ENTRIES FROM FRAGMENTS AS WELL.
//        WE HAVE NASTY SIDE EFFECTS WHEN THAT HAPPENS. e.g. NPE. SO, WE DON'T USE THE ALTERNATIVE
//        IMPLEMENTATION ANY MORE. WE EXPECT JAR TO HAVE PROPER DIRECTORY ENTRIES.        
//        getEntryPaths2(entries, path); // call the new implementation
    }

    private void getEntryPaths2(Collection entries, String path) {
        // findEntries expect the path to begin with "/"
        Enumeration e = b.findEntries(
                path.startsWith("/") ? path : "/".concat(path), "*", true);
        if (e != null) {
            while (e.hasMoreElements()) {
                URL next = (URL) e.nextElement();
                String nextPath = next.getPath();
                // As per the OSGi R4 spec,
                // "The getPath method for a bundle entry URL must return
                // an absolute path (a path that starts with '/') to a resource
                // or entry in a bundle. For example, the URL returned from
                // getEntry("myimages/test .gif ") must have a path of
                // /myimages/test.gif.
                entries.add(nextPath.substring(1)); // remove the leading "/"
            }
        }
    }

    public Enumeration entries(String prefix) {
        Collection entries = new ArrayList();
        getEntryPaths(entries, prefix);
        return Collections.enumeration(entries);
    }

    public boolean isDirectory(String name) {
        return b.getEntry(name.endsWith("/") ? name : name + "/") != null;
    }

    public Manifest getManifest() throws IOException {
        URL url = b.getEntry(JarFile.MANIFEST_NAME);
        if (url != null) {
            InputStream is = url.openStream();
            try {
                return new Manifest(is);
            }
            finally {
                is.close();
            }

        }
        return null;
    }

    /**
     * It returns URI for the underlying file if it can locate such a file.
     * Else, it returns null.
     *
     * @return
     */
    public URI getURI() {
        return uri;
    }

    public long getArchiveSize() throws SecurityException {
        return -1; // Don't know how to calculate the size.
    }

    public String getName() {
        return name;
    }

    public InputStream getEntry(String name) throws IOException {
        URL entry = b.getEntry(name);
        return entry != null ? entry.openStream() : null;
    }

    public boolean exists(String name) {
        return b.getEntry(name) != null;
    }

    public long getEntrySize(String name) {
        return 0;
    }

    public void open(URI uri) throws IOException {
        throw new UnsupportedOperationException("Not applicable method");
    }

    public ReadableArchive getSubArchive(String name) throws IOException {
        if (!exists(name)) {
            return null;
        }
        synchronized (this) {
            if (!subArchives.containsKey(name)) {
                ReadableArchive subArchive =
                        isDirectory(name) ? new EmbeddedDirectoryArchive(name) : new EmbeddedJarArchive(name);
                subArchives.put(name, subArchive);
            }
            return subArchives.get(name);
        }
    }

    public boolean exists() {
        return true;
    }

    public boolean delete() {
        return false;
    }

    public boolean renameTo(String name) {
        return false;
    }

    public void setParentArchive(ReadableArchive parentArchive) {
        // Not needed until we support ear file containing bundles.
        throw new UnsupportedOperationException("Not supported");
    }

    public ReadableArchive getParentArchive() {
        return null;
    }

    public URI getEntryURI(String name) {
        try {
            return b.getEntry(name).toURI();
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    public String getDistanceFromTop() {
        return ""; // this is the top level archive
    }

    public Iterator iterator() {
        return new BundleResourceIterator();
    }

    /**
     *
     * @return a Jar format InputStream for this bundle's content
     */
    public InputStream getInputStream() throws IOException {
    	 //[TangYong]fixing GLASSFISH-19662  
        if (uri != null && !new File(uri).isDirectory()) {
            return uri.toURL().openStream();
        } else {
            // create a JarOutputStream on the fly from the bundle's content
            // Can we optimize by reading off Felix's cache? Investigate in future.
            PipedInputStream is = new PipedInputStream();
            final PipedOutputStream os = new PipedOutputStream(is);
            new Thread() {
                @Override
                public void run() {
                    try {
                        JarOutputStream jos = new JarOutputStream(os, getManifest());
                        ByteBuffer buf = ByteBuffer.allocate(1024);
                        for (String s : Collections.list(entries())) {
                            if (s.equals(JarFile.MANIFEST_NAME)) continue; // we have already inserted manifest
                            jos.putNextEntry(new JarEntry(s));
                            if (!isDirectory(s)) {
                                InputStream in = getEntry(s);
                                try {
                                    JarHelper.copy(in, jos, buf);
                                } finally {
                                    try {
                                        in.close();
                                    } catch (IOException e) {
                                        // ignore
                                    }
                                }
                            }
                            jos.closeEntry();
                        }
                        jos.close();
                        os.close();
                    } catch (IOException e) {
                        throw new RuntimeException(e); // TODO(Sahoo): Proper Exception Handling
                    }

                }
            }.start();
            return is;
        }
    }

    /**
     * A directory (typically a bundle classpath) in the bundle represented as an archive.
     */
    private class EmbeddedDirectoryArchive extends AbstractReadableArchive implements ReadableArchive, URIable {

        /**
         * This is the entry name by which this is identified in the bundle space.
         */
        private String distanceFromTop;

        public EmbeddedDirectoryArchive(String distanceFromTop) {
            this.distanceFromTop = distanceFromTop;
        }

        public InputStream getEntry(String name) throws IOException {
            if (!exists(name)) return null;
            String bundleEntry = distanceFromTop + name;
            return b.getEntry(bundleEntry).openStream();
        }

        public boolean exists(String name) {
            return OSGiBundleArchive.this.exists(distanceFromTop + name);
        }

        public long getEntrySize(String name) {
            return OSGiBundleArchive.this.getEntrySize(distanceFromTop + name);
        }

        public void open(URI uri) throws IOException {
            throw new UnsupportedOperationException();
        }

        public ReadableArchive getSubArchive(String name) throws IOException {
            return null;
        }

        public boolean exists() {
            return true;
        }

        public boolean delete() {
            return false;
        }

        public boolean renameTo(String name) {
            return false;
        }

        public void setParentArchive(ReadableArchive parentArchive) {
            throw new UnsupportedOperationException();
        }

        public ReadableArchive getParentArchive() {
            return OSGiBundleArchive.this;
        }

        public void close() throws IOException {
        }

        public Enumeration entries() {
            return entries("");
        }

        public Enumeration entries(String prefix) {
            Collection entries = new ArrayList();
            getEntryPaths(entries, distanceFromTop + prefix);

            // entries contains path names which is with respect to bundle root.
            // We need names with respect to this directory root.
            // So, we need to strip entryName from the entries.
            Collection subEntries = stripEntryName(entries);
            return Collections.enumeration(subEntries);
        }

        /**
         * This method strips off entryName from collection of entries.
         * @param entries
         * @return
         */
        private Collection stripEntryName(Collection entries) {
            Collection subEntries = new ArrayList(entries.size());
            final int idx = distanceFromTop.length();
            for (String entry : entries) {
                subEntries.add(entry.substring(idx));
            }
            return subEntries;
        }

        public Collection getDirectories() throws IOException {
            return stripEntryName(getSubDiretcories(distanceFromTop));
        }

        public boolean isDirectory(String name) {
            return exists(name.endsWith("/") ? name : name + "/");
        }

        public Manifest getManifest() throws IOException {
            return null;  //TODO(Sahoo): Not Yet Implemented
        }

        public URI getURI() {
            try {
                return b.getEntry(distanceFromTop).toURI();
            } catch (URISyntaxException e) {
                throw new RuntimeException(e);
            }
        }

        public long getArchiveSize() throws SecurityException {
            return 0;
        }

        public String getName() {
            return distanceFromTop;
        }

        public URI getEntryURI(String name) {
            return OSGiBundleArchive.this.getEntryURI(distanceFromTop + name);
        }

        public String getDistanceFromTop() {
            return distanceFromTop;
        }
    }

    /**
     * A jar (typically a bundle classpath) in the bundle represented as an archive.
     */
    private class EmbeddedJarArchive extends AbstractReadableArchive implements URIable {

        /**
         * This is the entry name by which this is identified in the bundle space.
         */
        private String distanceFromTop;

        /**
         * All the entries that this archive has
         */
        private List entries = new ArrayList();

        private EmbeddedJarArchive(String distanceFromTop) throws IOException {
            this.distanceFromTop = distanceFromTop;
            ZipInputStream zis = getZIS();
            try {
                while (true) {
                    ZipEntry ze = zis.getNextEntry();
                    if (ze == null) break;
                    entries.add(ze.getName());
                }
            } finally {
                closeZIS(zis);
            }
        }

        private ZipInputStream getZIS() throws IOException {
            // Since user can supply random entry and ask for an embedded archive, propagate the exception to user.
            return new ZipInputStream(b.getEntry(distanceFromTop).openStream());
        }

        private Collection getEntries() {
            return entries;
        }

        public InputStream getEntry(String name) throws IOException {
            if (!exists(name)) return null;
            final ZipInputStream zis = getZIS();
            while (true) {
                ZipEntry ze = zis.getNextEntry();
                if (ze == null) break; // end of stream, which is unlikely because the entry exists.
                if (ze.getName().equals(name)) return zis;
            }
            // don't close the stream, as we are returning it to caller
            assert (false);
            return null;
        }

        public boolean exists(String name) {
            return getEntries().contains(name);
        }

        public long getEntrySize(String name) {
            if (exists(name)) {
                ZipInputStream zis = null;
                try {
                    zis = getZIS();
                    while (true) {
                        ZipEntry ze = zis.getNextEntry();
                        if (name.equals(ze.getName())) {
                            return ze.getSize();
                        }
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }  finally {
                    if (zis != null) {
                        closeZIS(zis);
                    }
                }
            }
            return 0;
        }

        public void open(URI uri) throws IOException {
            throw new UnsupportedOperationException("Not Applicable");
        }

        public ReadableArchive getSubArchive(String name) throws IOException {
            return null;  // Only one level embedding allowed in a bundle
        }

        public boolean exists() {
            return true;
        }

        public boolean delete() {
            return false;  //TODO(Sahoo): Not Yet Implemented
        }

        public boolean renameTo(String name) {
            return false;  //TODO(Sahoo): Not Yet Implemented
        }

        public void setParentArchive(ReadableArchive parentArchive) {
            throw new UnsupportedOperationException();
        }

        public ReadableArchive getParentArchive() {
            return OSGiBundleArchive.this;
        }

        public void close() throws IOException {
            // noop
        }

        public Enumeration entries() {
            return Collections.enumeration(getEntries());
        }

        public Enumeration entries(String prefix) {
            List result = new ArrayList();
            for (String entry : getEntries()) {
                if (entry.startsWith(prefix)) {
                    result.add(entry);
                }
            }
            return Collections.enumeration(result);
        }

        public Collection getDirectories() throws IOException {
            List result = new ArrayList();
            for (String entry : getEntries()) {
                final int idx = entry.indexOf('/');
                if (idx != -1 && idx == entry.length() - 1) result.add(entry);
            }
            return result;
        }

        public boolean isDirectory(String name) {
            // directory entries always end with "/", so unless we append a "/" when not there, we are not going
            // to find it in our entry list.
            return exists(name.endsWith("/") ? name : (name + "/"));
        }

        public Manifest getManifest() throws IOException {
            String name = JarFile.MANIFEST_NAME;
            return exists(name) ? new Manifest(getEntry(name)) : null;
        }

        public URI getURI() {
            try {
                return b.getEntry(distanceFromTop).toURI();
            } catch (URISyntaxException e) {
                throw new RuntimeException(e);
            }
        }

        public long getArchiveSize() throws SecurityException {
            return 0; // unknown
        }

        public String getName() {
            return distanceFromTop;
        }

        public URI getEntryURI(String name) {
            return URI.create(EmbeddedJarURLStreamHandlerService.EMBEDDED_JAR_SCHEME + ":" + getURI() +
                    EmbeddedJarURLStreamHandlerService.SEPARATOR + name);
        }

        public String getDistanceFromTop() {
            return distanceFromTop;
        }

        private void closeZIS(ZipInputStream zis) {
            try {
                zis.close();
            } catch (Exception e) {
            }
        }
    }

    private class BundleResourceIterator implements Iterator {

        private static final String DOT = ".";
        private final Iterator delegate;
        private final Collection bundleResources = new ArrayList();

        private BundleResourceIterator() {
            // for each bundle classpath entry, get the subarchive
            String bcp = (String) b.getHeaders().get(org.osgi.framework.Constants.BUNDLE_CLASSPATH);
            if (bcp == null || bcp.isEmpty()) bcp = DOT;
            String seps = ";,";
            StringTokenizer bcpes = new StringTokenizer(bcp, seps);
            List archives = new ArrayList();
            while (bcpes.hasMoreTokens()) {
                String bcpe = bcpes.nextToken();
                bcpe = bcpe.trim();
                if (bcpe.startsWith("/")) bcpe = bcpe.substring(1); // it is always relative to bundle root
                if (bcpe.equals(DOT)) {
                    archives.add(OSGiBundleArchive.this);
                } else {
                    if (isDirectory(bcpe) && !bcpe.endsWith("/")) {
                        bcpe = bcpe.concat("/");
                    }
                    try {
                        ReadableArchive archive = getSubArchive(bcpe);
                        if (archive != null) archives.add(archive);
                    } catch (IOException e1) {
                        e1.printStackTrace(); // ignore and continue
                    }
                }
            }

            for (ReadableArchive archive : archives) {
                Enumeration entries = archive.entries();
                final URIable urIable = URIable.class.cast(archive);
                while (entries.hasMoreElements()) {
                    String entry = entries.nextElement();
                    URI uri = urIable.getEntryURI(entry);
                    final String archivePath = urIable.getDistanceFromTop();
                    BundleResource bundleResource = new BundleResource(uri, entry, archivePath);
                    bundleResources.add(bundleResource);
                }
            }
            delegate = bundleResources.iterator();
        }

        public boolean hasNext() {
            return delegate.hasNext();
        }

        public BundleResource next() {
            return delegate.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    static class EmbeddedJarURLStreamHandlerService extends
            AbstractURLStreamHandlerService {
        /**
         * URI scheme used for resource embedded in a jar in a bundle
         */
        static final String EMBEDDED_JAR_SCHEME = "embeddedjar";

        /**
         * Separator used by embeddedjar scheme.
         */
        static final String SEPARATOR = "!/";

        public URLConnection openConnection(URL u) throws IOException {
            assert (u.getProtocol().equals(EMBEDDED_JAR_SCHEME));
            try {
                String schemeSpecificPart = u.toURI().getSchemeSpecificPart();
                int idx = schemeSpecificPart.indexOf(SEPARATOR);
                assert (idx > 0);
                URL embeddedURL = URI.create(schemeSpecificPart.substring(0, idx)).toURL();
                final URLConnection con = embeddedURL.openConnection();
                final String entryPath = schemeSpecificPart.substring(idx + 2);
                assert (entryPath.length() > 0);
                return new URLConnection(u) {
                    public void connect() throws IOException {
                        con.connect();
                    }

                    @Override
                    public InputStream getInputStream() throws IOException {
                        JarInputStream jis = new JarInputStream(con.getInputStream());
                        for (JarEntry je = jis.getNextJarEntry(); je != null; je = jis.getNextJarEntry()) {
                            if (je.getName().equals(entryPath)) {
                                return jis;
                            }
                        }
                        throw new IOException("No entry by name " + entryPath);
                    }
                };
            } catch (URISyntaxException e) {
                throw new RuntimeException(e);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy