jdk/src/java.base/share/classes/sun/misc/ExtensionDependency.java
changeset 25859 3317bb8137f4
parent 23010 6dadb192ad81
child 27565 729f9700483a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/misc/ExtensionDependency.java	Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,570 @@
+/*
+ * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.misc;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.jar.Attributes;
+import java.util.jar.Attributes.Name;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
+import java.security.PrivilegedActionException;
+import java.net.URL;
+import java.net.MalformedURLException;
+import sun.net.www.ParseUtil;
+
+/**
+ * <p>
+ * This class checks dependent extensions a particular jar file may have
+ * declared through its manifest attributes.
+ * </p>
+ * Jar file declared dependent extensions through the extension-list
+ * attribute. The extension-list contains a list of keys used to
+ * fetch the other attributes describing the required extension.
+ * If key is the extension key declared in the extension-list
+ * attribute, the following describing attribute can be found in
+ * the manifest :
+ * key-Extension-Name:  (Specification package name)
+ * key-Specification-Version: (Specification-Version)
+ * key-Implementation-Version: (Implementation-Version)
+ * key-Implementation-Vendor-Id: (Imlementation-Vendor-Id)
+ * key-Implementation-Version: (Implementation version)
+ * key-Implementation-URL: (URL to download the requested extension)
+ * <p>
+ * This class also maintain versioning consistency of installed
+ * extensions dependencies declared in jar file manifest.
+ * </p>
+ * @author  Jerome Dochez
+ */
+public class ExtensionDependency {
+
+    /* Callbak interfaces to delegate installation of missing extensions */
+    private static Vector<ExtensionInstallationProvider> providers;
+
+    /**
+     * <p>
+     * Register an ExtensionInstallationProvider. The provider is responsible
+     * for handling the installation (upgrade) of any missing extensions.
+     * </p>
+     * @param eip ExtensionInstallationProvider implementation
+     */
+    public synchronized static void addExtensionInstallationProvider
+        (ExtensionInstallationProvider eip)
+    {
+        if (providers == null) {
+            providers = new Vector<>();
+        }
+        providers.add(eip);
+    }
+
+    /**
+     * <p>
+     * Unregister a previously installed installation provider
+     * </p>
+     */
+    public synchronized static void removeExtensionInstallationProvider
+        (ExtensionInstallationProvider eip)
+    {
+        providers.remove(eip);
+    }
+
+    /**
+     * <p>
+     * Checks the dependencies of the jar file on installed extension.
+     * </p>
+     * @param jarFile containing the attriutes declaring the dependencies
+     */
+    public static boolean checkExtensionsDependencies(JarFile jar)
+    {
+        if (providers == null) {
+            // no need to bother, nobody is registered to install missing
+            // extensions
+            return true;
+        }
+
+        try {
+            ExtensionDependency extDep = new ExtensionDependency();
+            return extDep.checkExtensions(jar);
+        } catch (ExtensionInstallationException e) {
+            debug(e.getMessage());
+        }
+        return false;
+    }
+
+    /*
+     * Check for all declared required extensions in the jar file
+     * manifest.
+     */
+    protected boolean checkExtensions(JarFile jar)
+        throws ExtensionInstallationException
+    {
+        Manifest man;
+        try {
+            man = jar.getManifest();
+        } catch (IOException e) {
+            return false;
+        }
+
+        if (man == null) {
+            // The applet does not define a manifest file, so
+            // we just assume all dependencies are satisfied.
+            return true;
+        }
+
+        boolean result = true;
+        Attributes attr = man.getMainAttributes();
+        if (attr != null) {
+            // Let's get the list of declared dependencies
+            String value = attr.getValue(Name.EXTENSION_LIST);
+            if (value != null) {
+                StringTokenizer st = new StringTokenizer(value);
+                // Iterate over all declared dependencies
+                while (st.hasMoreTokens()) {
+                    String extensionName = st.nextToken();
+                    debug("The file " + jar.getName() +
+                          " appears to depend on " + extensionName);
+                    // Sanity Check
+                    String extName = extensionName + "-" +
+                        Name.EXTENSION_NAME.toString();
+                    if (attr.getValue(extName) == null) {
+                        debug("The jar file " + jar.getName() +
+                              " appers to depend on "
+                              + extensionName + " but does not define the " +
+                              extName + " attribute in its manifest ");
+
+                    } else {
+                        if (!checkExtension(extensionName, attr)) {
+                            debug("Failed installing " + extensionName);
+                            result = false;
+                        }
+                    }
+                }
+            } else {
+                debug("No dependencies for " + jar.getName());
+            }
+        }
+        return result;
+    }
+
+
+    /*
+     * <p>
+     * Check that a particular dependency on an extension is satisfied.
+     * </p>
+     * @param extensionName is the key used for the attributes in the manifest
+     * @param attr is the attributes of the manifest file
+     *
+     * @return true if the dependency is satisfied by the installed extensions
+     */
+    protected synchronized boolean checkExtension(final String extensionName,
+                                     final Attributes attr)
+        throws ExtensionInstallationException
+    {
+        debug("Checking extension " + extensionName);
+        if (checkExtensionAgainstInstalled(extensionName, attr))
+            return true;
+
+        debug("Extension not currently installed ");
+        ExtensionInfo reqInfo = new ExtensionInfo(extensionName, attr);
+        return installExtension(reqInfo, null);
+    }
+
+    /*
+     * <p>
+     * Check if a particular extension is part of the currently installed
+     * extensions.
+     * </p>
+     * @param extensionName is the key for the attributes in the manifest
+     * @param attr is the attributes of the manifest
+     *
+     * @return true if the requested extension is already installed
+     */
+    boolean checkExtensionAgainstInstalled(String extensionName,
+                                           Attributes attr)
+        throws ExtensionInstallationException
+    {
+        File fExtension = checkExtensionExists(extensionName);
+
+        if (fExtension != null) {
+        // Extension already installed, just check against this one
+            try {
+                if (checkExtensionAgainst(extensionName, attr, fExtension))
+                    return true;
+            } catch (FileNotFoundException e) {
+                debugException(e);
+            } catch (IOException e) {
+                debugException(e);
+            }
+            return false;
+
+        } else {
+        // Not sure if extension is already installed, so check all the
+        // installed extension jar files to see if we get a match
+
+            File[] installedExts;
+
+            try {
+            // Get the list of installed extension jar files so we can
+            // compare the installed versus the requested extension
+                installedExts = getInstalledExtensions();
+            } catch(IOException e) {
+                debugException(e);
+                return false;
+            }
+
+            for (int i=0;i<installedExts.length;i++) {
+                try {
+                    if (checkExtensionAgainst(extensionName, attr, installedExts[i]))
+                        return true;
+                } catch (FileNotFoundException e) {
+                    debugException(e);
+                } catch (IOException e) {
+                    debugException(e);
+                    // let's continue with the next installed extension
+                }
+            }
+        }
+        return false;
+    }
+
+    /*
+     * <p>
+     * Check if the requested extension described by the attributes
+     * in the manifest under the key extensionName is compatible with
+     * the jar file.
+     * </p>
+     *
+     * @param extensionName key in the attribute list
+     * @param attr manifest file attributes
+     * @param file installed extension jar file to compare the requested
+     * extension against.
+     */
+    protected boolean checkExtensionAgainst(String extensionName,
+                                            Attributes attr,
+                                            final File file)
+        throws IOException,
+               FileNotFoundException,
+               ExtensionInstallationException
+    {
+
+        debug("Checking extension " + extensionName +
+              " against " + file.getName());
+
+        // Load the jar file ...
+        Manifest man;
+        try {
+            man = AccessController.doPrivileged(
+                new PrivilegedExceptionAction<Manifest>() {
+                    public Manifest run()
+                            throws IOException, FileNotFoundException {
+                         if (!file.exists())
+                             throw new FileNotFoundException(file.getName());
+                         JarFile jarFile =  new JarFile(file);
+                         return jarFile.getManifest();
+                     }
+                 });
+        } catch(PrivilegedActionException e) {
+            if (e.getException() instanceof FileNotFoundException)
+                throw (FileNotFoundException) e.getException();
+            throw (IOException) e.getException();
+        }
+
+        // Construct the extension information object
+        ExtensionInfo reqInfo = new ExtensionInfo(extensionName, attr);
+        debug("Requested Extension : " + reqInfo);
+
+        int isCompatible = ExtensionInfo.INCOMPATIBLE;
+        ExtensionInfo instInfo = null;
+
+        if (man != null) {
+            Attributes instAttr = man.getMainAttributes();
+            if (instAttr != null) {
+                instInfo = new ExtensionInfo(null, instAttr);
+                debug("Extension Installed " + instInfo);
+                isCompatible = instInfo.isCompatibleWith(reqInfo);
+                switch(isCompatible) {
+                case ExtensionInfo.COMPATIBLE:
+                    debug("Extensions are compatible");
+                    return true;
+
+                case ExtensionInfo.INCOMPATIBLE:
+                    debug("Extensions are incompatible");
+                    return false;
+
+                default:
+                    // everything else
+                    debug("Extensions require an upgrade or vendor switch");
+                    return installExtension(reqInfo, instInfo);
+
+                }
+            }
+        }
+        return false;
+    }
+
+    /*
+     * <p>
+     * An required extension is missing, if an ExtensionInstallationProvider is
+     * registered, delegate the installation of that particular extension to it.
+     * </p>
+     *
+     * @param reqInfo Missing extension information
+     * @param instInfo Older installed version information
+     *
+     * @return true if the installation is successful
+     */
+    protected boolean installExtension(ExtensionInfo reqInfo,
+                                       ExtensionInfo instInfo)
+        throws ExtensionInstallationException
+    {
+        Vector<ExtensionInstallationProvider> currentProviders;
+        synchronized(providers) {
+            @SuppressWarnings("unchecked")
+            Vector<ExtensionInstallationProvider> tmp =
+                (Vector<ExtensionInstallationProvider>) providers.clone();
+            currentProviders = tmp;
+        }
+        for (Enumeration<ExtensionInstallationProvider> e = currentProviders.elements();
+                e.hasMoreElements();) {
+            ExtensionInstallationProvider eip = e.nextElement();
+
+            if (eip!=null) {
+                // delegate the installation to the provider
+                if (eip.installExtension(reqInfo, instInfo)) {
+                    debug(reqInfo.name + " installation successful");
+                    Launcher.ExtClassLoader cl = (Launcher.ExtClassLoader)
+                        Launcher.getLauncher().getClassLoader().getParent();
+                    addNewExtensionsToClassLoader(cl);
+                    return true;
+                }
+            }
+        }
+        // We have tried all of our providers, noone could install this
+        // extension, we just return failure at this point
+        debug(reqInfo.name + " installation failed");
+        return false;
+    }
+
+    /**
+     * <p>
+     * Checks if the extension, that is specified in the extension-list in
+     * the applet jar manifest, is already installed (i.e. exists in the
+     * extension directory).
+     * </p>
+     *
+     * @param extensionName extension name in the extension-list
+     *
+     * @return the extension if it exists in the extension directory
+     */
+    private File checkExtensionExists(String extensionName) {
+        // Function added to fix bug 4504166
+        final String extName = extensionName;
+        final String[] fileExt = {".jar", ".zip"};
+
+        return AccessController.doPrivileged(
+            new PrivilegedAction<File>() {
+                public File run() {
+                    try {
+                        File fExtension;
+                        File[] dirs = getExtDirs();
+
+                        // Search the extension directories for the extension that is specified
+                        // in the attribute extension-list in the applet jar manifest
+                        for (int i=0;i<dirs.length;i++) {
+                            for (int j=0;j<fileExt.length;j++) {
+                                if (extName.toLowerCase().endsWith(fileExt[j])) {
+                                    fExtension = new File(dirs[i], extName);
+                                } else {
+                                    fExtension = new File(dirs[i], extName+fileExt[j]);
+                                }
+                                debug("checkExtensionExists:fileName " + fExtension.getName());
+                                if (fExtension.exists()) {
+                                    return fExtension;
+                                }
+                            }
+                        }
+                        return null;
+
+                    } catch(Exception e) {
+                         debugException(e);
+                         return null;
+                    }
+                }
+            });
+    }
+
+    /**
+     * <p>
+     * @return the java.ext.dirs property as a list of directory
+     * </p>
+     */
+    private static File[] getExtDirs() {
+        String s = java.security.AccessController.doPrivileged(
+                new sun.security.action.GetPropertyAction("java.ext.dirs"));
+
+        File[] dirs;
+        if (s != null) {
+            StringTokenizer st =
+                new StringTokenizer(s, File.pathSeparator);
+            int count = st.countTokens();
+            debug("getExtDirs count " + count);
+            dirs = new File[count];
+            for (int i = 0; i < count; i++) {
+                dirs[i] = new File(st.nextToken());
+                debug("getExtDirs dirs["+i+"] "+ dirs[i]);
+            }
+        } else {
+            dirs = new File[0];
+            debug("getExtDirs dirs " + dirs);
+        }
+        debug("getExtDirs dirs.length " + dirs.length);
+        return dirs;
+    }
+
+    /*
+     * <p>
+     * Scan the directories and return all files installed in those
+     * </p>
+     * @param dirs list of directories to scan
+     *
+     * @return the list of files installed in all the directories
+     */
+    private static File[] getExtFiles(File[] dirs) throws IOException {
+        Vector<File> urls = new Vector<File>();
+        for (int i = 0; i < dirs.length; i++) {
+            String[] files = dirs[i].list(new JarFilter());
+            if (files != null) {
+                debug("getExtFiles files.length " + files.length);
+                for (int j = 0; j < files.length; j++) {
+                    File f = new File(dirs[i], files[j]);
+                    urls.add(f);
+                    debug("getExtFiles f["+j+"] "+ f);
+                }
+            }
+        }
+        File[] ua = new File[urls.size()];
+        urls.copyInto(ua);
+        debug("getExtFiles ua.length " + ua.length);
+        return ua;
+    }
+
+    /*
+     * <p>
+     * @return the list of installed extensions jar files
+     * </p>
+     */
+    private File[] getInstalledExtensions() throws IOException {
+        return AccessController.doPrivileged(
+            new PrivilegedAction<File[]>() {
+                public File[] run() {
+                     try {
+                         return getExtFiles(getExtDirs());
+                     } catch(IOException e) {
+                         debug("Cannot get list of installed extensions");
+                         debugException(e);
+                        return new File[0];
+                     }
+                 }
+            });
+    }
+
+    /*
+     * <p>
+     * Add the newly installed jar file to the extension class loader.
+     * </p>
+     *
+     * @param cl the current installed extension class loader
+     *
+     * @return true if successful
+     */
+    private Boolean addNewExtensionsToClassLoader(Launcher.ExtClassLoader cl) {
+        try {
+            File[] installedExts = getInstalledExtensions();
+            for (int i=0;i<installedExts.length;i++) {
+                final File instFile = installedExts[i];
+                URL instURL = AccessController.doPrivileged(
+                    new PrivilegedAction<URL>() {
+                        public URL run() {
+                            try {
+                                return ParseUtil.fileToEncodedURL(instFile);
+                            } catch (MalformedURLException e) {
+                                debugException(e);
+                                return null;
+                            }
+                        }
+                    });
+                if (instURL != null) {
+                    URL[] urls = cl.getURLs();
+                    boolean found=false;
+                    for (int j = 0; j<urls.length; j++) {
+                        debug("URL["+j+"] is " + urls[j] + " looking for "+
+                                           instURL);
+                        if (urls[j].toString().compareToIgnoreCase(
+                                    instURL.toString())==0) {
+                            found=true;
+                            debug("Found !");
+                        }
+                    }
+                    if (!found) {
+                        debug("Not Found ! adding to the classloader " +
+                              instURL);
+                        cl.addExtURL(instURL);
+                    }
+                }
+            }
+        } catch (MalformedURLException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+            // let's continue with the next installed extension
+        }
+        return Boolean.TRUE;
+    }
+
+    // True to display all debug and trace messages
+    static final boolean DEBUG = false;
+
+    private static void debug(String s) {
+        if (DEBUG) {
+            System.err.println(s);
+        }
+    }
+
+    private void debugException(Throwable e) {
+        if (DEBUG) {
+            e.printStackTrace();
+        }
+    }
+
+}