/* * Copyright (c) 1999, 2006, 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 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 attibute 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 = (Manifest) AccessController.doPrivileged ( new PrivilegedExceptionAction() { public Object 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 currentProviders; synchronized(providers) { currentProviders = (Vector) providers.clone(); } for (Enumeration e=currentProviders.elements();e.hasMoreElements();) { ExtensionInstallationProvider eip = (ExtensionInstallationProvider) 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 (File) AccessController.doPrivileged (new PrivilegedAction() { public Object 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 urls = new Vector(); 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 (File[]) AccessController.doPrivileged ( new PrivilegedAction() { public Object run() { try { return getExtFiles(getExtDirs()); } catch(IOException e) { debug("Cannot get list of installed extensions"); debugException(e); return new URL[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(); } } }