| /* |
| * Copyright (c) 2008, 2009, 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.jkernel; |
| |
| import java.io.*; |
| import java.security.*; |
| import java.util.*; |
| import java.util.concurrent.*; |
| import java.util.jar.*; |
| import java.util.zip.*; |
| import sun.misc.Launcher; |
| import sun.misc.BootClassLoaderHook; |
| |
| /** |
| * Handles the downloading of additional JRE components. The bootstrap class |
| * loader automatically invokes DownloadManager when it comes across a resource |
| * that can't be located. |
| * |
| *@author Ethan Nicholas |
| */ |
| public class DownloadManager extends BootClassLoaderHook { |
| public static final String KERNEL_DOWNLOAD_URL_PROPERTY = |
| "kernel.download.url"; |
| public static final String KERNEL_DOWNLOAD_ENABLED_PROPERTY = |
| "kernel.download.enabled"; |
| |
| public static final String KERNEL_DOWNLOAD_DIALOG_PROPERTY = |
| "kernel.download.dialog"; |
| |
| public static final String KERNEL_DEBUG_PROPERTY = "kernel.debug"; |
| // disables JRE completion when set to true, used as part of the build |
| // process |
| public static final String KERNEL_NOMERGE_PROPERTY = "kernel.nomerge"; |
| |
| public static final String KERNEL_SIMULTANEOUS_DOWNLOADS_PROPERTY = |
| "kernel.simultaneous.downloads"; |
| |
| // used to bypass some problems with JAR entry modtimes not matching. |
| // originally was set to zero, but apparently the epochs are different |
| // for zip and pack so the pack/unpack cycle was causing the modtimes |
| // to change. With some recent changes to the reconstruction, I'm |
| // not sure if this is actually necessary anymore. |
| public static final int KERNEL_STATIC_MODTIME = 10000000; |
| |
| // indicates that bundles should be grabbed using getResource(), rather |
| // than downloaded from a network path -- this is used during the build |
| // process |
| public static final String RESOURCE_URL = "internal-resource/"; |
| public static final String REQUESTED_BUNDLES_PATH = "lib" + File.separator + |
| "bundles" + File.separator + "requested.list"; |
| |
| private static final boolean disableDownloadDialog = "false".equals( |
| System.getProperty(KERNEL_DOWNLOAD_DIALOG_PROPERTY)); |
| |
| static boolean debug = "true".equals( |
| System.getProperty(KERNEL_DEBUG_PROPERTY)); |
| // points to stderr in case we need to println before System.err is |
| // initialized |
| private static OutputStream errorStream; |
| private static OutputStream logStream; |
| |
| static String MUTEX_PREFIX; |
| |
| static boolean complete; |
| |
| // 1 if jbroker started; 0 otherwise |
| private static int _isJBrokerStarted = -1; |
| |
| // maps bundle names to URL strings |
| private static Properties bundleURLs; |
| |
| public static final String JAVA_HOME = System.getProperty("java.home"); |
| public static final String USER_HOME = System.getProperty("user.home"); |
| public static final String JAVA_VERSION = |
| System.getProperty("java.version"); |
| static final int BUFFER_SIZE = 2048; |
| |
| static volatile boolean jkernelLibLoaded = false; |
| |
| public static String DEFAULT_DOWNLOAD_URL = |
| "http://javadl.sun.com/webapps/download/GetList/" |
| + System.getProperty("java.runtime.version") + "-kernel/windows-i586/"; |
| |
| private static final String CUSTOM_PREFIX = "custom"; |
| private static final String KERNEL_PATH_SUFFIX = "-kernel"; |
| |
| public static final String JAR_PATH_PROPERTY = "jarpath"; |
| public static final String SIZE_PROPERTY = "size"; |
| public static final String DEPENDENCIES_PROPERTY = "dependencies"; |
| public static final String INSTALL_PROPERTY = "install"; |
| |
| private static boolean reportErrors = true; |
| |
| static final int ERROR_UNSPECIFIED = 0; |
| static final int ERROR_DISK_FULL = 1; |
| static final int ERROR_MALFORMED_BUNDLE_PROPERTIES = 2; |
| static final int ERROR_DOWNLOADING_BUNDLE_PROPERTIES = 3; |
| static final int ERROR_MALFORMED_URL = 4; |
| static final int ERROR_RETRY_CANCELLED = 5; |
| static final int ERROR_NO_SUCH_BUNDLE = 6; |
| |
| |
| // tracks whether the current thread is downloading. A count of zero means |
| // not currently downloading, >0 means the current thread is downloading or |
| // installing a bundle. |
| static ThreadLocal<Integer> downloading = new ThreadLocal<Integer>() { |
| protected Integer initialValue() { |
| return 0; |
| } |
| }; |
| |
| private static File[] additionalBootStrapPaths = { }; |
| |
| private static String[] bundleNames; |
| private static String[] criticalBundleNames; |
| |
| private static String downloadURL; |
| |
| private static boolean visitorIdDetermined; |
| private static String visitorId; |
| |
| /** |
| * File and path where the Check value properties are gotten from |
| */ |
| public static String CHECK_VALUES_FILE = "check_value.properties"; |
| static String CHECK_VALUES_DIR = "sun/jkernel/"; |
| static String CHECK_VALUES_PATH = CHECK_VALUES_DIR + CHECK_VALUES_FILE; |
| |
| /** |
| * The contents of the bundle.properties file, which contains various |
| * information about individual bundles. |
| */ |
| private static Map<String, Map<String, String>> bundleProperties; |
| |
| |
| /** |
| * The contents of the resource_map file, which maps resources |
| * to their respective bundles. |
| */ |
| private static Map<String, String> resourceMap; |
| |
| |
| /** |
| * The contents of the file_map file, which maps files |
| * to their respective bundles. |
| */ |
| private static Map<String, String> fileMap; |
| |
| private static boolean extDirDetermined; |
| private static boolean extDirIncluded; |
| |
| static { |
| AccessController.doPrivileged(new PrivilegedAction() { |
| public Object run() { |
| if (debug) |
| println("DownloadManager startup"); |
| |
| // this mutex is global and will apply to all different |
| // version of java kernel installed on the local machine |
| MUTEX_PREFIX = "jkernel"; |
| boolean downloadEnabled = !"false".equals( |
| System.getProperty(KERNEL_DOWNLOAD_ENABLED_PROPERTY)); |
| complete = !getBundlePath().exists() || |
| !downloadEnabled; |
| |
| // only load jkernel.dll if we are not "complete". |
| // DownloadManager will be loaded during build time, before |
| // jkernel.dll is built. We only need to load jkernel.dll |
| // when DownloadManager needs to download something, which is |
| // not necessary during build time |
| if (!complete) { |
| loadJKernelLibrary(); |
| log("Log opened"); |
| |
| if (isWindowsVista()) { |
| getLocalLowTempBundlePath().mkdirs(); |
| } |
| |
| new Thread() { |
| public void run() { |
| startBackgroundDownloads(); |
| } |
| }.start(); |
| |
| try { |
| String dummyPath; |
| if (isWindowsVista()) { |
| dummyPath = USER_HOME + |
| "\\appdata\\locallow\\dummy.kernel"; |
| } else { |
| dummyPath = USER_HOME + "\\dummy.kernel"; |
| } |
| |
| File f = new File(dummyPath); |
| FileOutputStream out = new FileOutputStream(f, true); |
| out.close(); |
| f.deleteOnExit(); |
| |
| } catch (IOException e) { |
| log(e); |
| } |
| // end of warm up code |
| |
| new Thread("BundleDownloader") { |
| public void run() { |
| downloadRequestedBundles(); |
| } |
| }.start(); |
| } |
| return null; |
| } |
| }); |
| } |
| |
| |
| static synchronized void loadJKernelLibrary() { |
| if (!jkernelLibLoaded) { |
| try { |
| System.loadLibrary("jkernel"); |
| jkernelLibLoaded = true; |
| debug = getDebugProperty(); |
| } catch (Exception e) { |
| throw new Error(e); |
| } |
| } |
| } |
| |
| static String appendTransactionId(String url) { |
| StringBuilder result = new StringBuilder(url); |
| String visitorId = DownloadManager.getVisitorId(); |
| if (visitorId != null) { |
| if (url.indexOf("?") == -1) |
| result.append('?'); |
| else |
| result.append('&'); |
| result.append("transactionId="); |
| result.append(DownloadManager.getVisitorId()); |
| } |
| return result.toString(); |
| } |
| |
| |
| /** |
| * Returns the URL for the directory from which bundles should be |
| * downloaded. |
| */ |
| static synchronized String getBaseDownloadURL() { |
| if (downloadURL == null) { |
| log("Determining download URL..."); |
| loadJKernelLibrary(); |
| |
| /* |
| * First check if system property has been set - system |
| * property should take over registry key setting. |
| */ |
| downloadURL = System.getProperty( |
| DownloadManager.KERNEL_DOWNLOAD_URL_PROPERTY); |
| log("System property kernel.download.url = " + downloadURL); |
| |
| /* |
| * Now check if registry key has been set |
| */ |
| if (downloadURL == null){ |
| downloadURL = getUrlFromRegistry(); |
| log("getUrlFromRegistry = " + downloadURL); |
| } |
| |
| /* |
| * Use default download url |
| */ |
| if (downloadURL == null) |
| downloadURL = DEFAULT_DOWNLOAD_URL; |
| log("Final download URL: " + downloadURL); |
| } |
| return downloadURL; |
| } |
| |
| |
| /** |
| * Loads a file representing a node tree. The format is described in |
| * SplitJRE.writeTreeMap(). The node paths (such as |
| * core/java/lang/Object.class) are interpreted with the root node as the |
| * value and the remaining nodes as |
| * the key, so the mapping for this entry would be java/lang/Object.class = |
| * core. |
| */ |
| static Map<String, String> readTreeMap(InputStream rawIn) |
| throws IOException { |
| // "token level" refers to the 0-31 byte that occurs prior to every |
| // token in the stream, and would be e.g. <0> core <1> java <2> lang |
| // <3> Object.class <3> String.class, which gives us two mappings: |
| // java/lang/Object.class = core, and java/lang/String.class = core. |
| // See the format description in SplitJRE.writeTreeMap for more details. |
| Map<String, String> result = new HashMap<String, String>(); |
| InputStream in = new BufferedInputStream(rawIn); |
| // holds the current token sequence, |
| // e.g. {"core", "java", "lang", "Object.class"} |
| List<String> tokens = new ArrayList<String>(); |
| StringBuilder currentToken = new StringBuilder(); |
| for (;;) { |
| int c = in.read(); |
| if (c == -1) // eof |
| break; |
| if (c < 32) { // new token level |
| if (tokens.size() > 0) { |
| // replace the null at the end of the list with the token |
| // we just finished reading |
| tokens.set(tokens.size() - 1, currentToken.toString()); |
| } |
| |
| currentToken.setLength(0); |
| |
| if (c > tokens.size()) { |
| // can't increase by more than one token level at a step |
| throw new InternalError("current token level is " + |
| (tokens.size() - 1) + " but encountered token " + |
| "level " + c); |
| } |
| else if (c == tokens.size()) { |
| // token level increased by 1; this means we are still |
| // adding tokens for the current mapping -- e.g. we have |
| // read "core", "java", "lang" and are just about to read |
| // "Object.class" |
| // add a placeholder for the new token |
| tokens.add(null); |
| } |
| else { |
| // we just stayed at the same level or backed up one or more |
| // token levels; this means that the current sequence is |
| // complete and needs to be added to the result map |
| StringBuilder key = new StringBuilder(); |
| // combine all tokens except the first into a single string |
| for (int i = 1; i < tokens.size(); i++) { |
| if (i > 1) |
| key.append('/'); |
| key.append(tokens.get(i)); |
| } |
| // map the combined string to the first token, e.g. |
| // java/lang/Object.class = core |
| result.put(key.toString(), tokens.get(0)); |
| // strip off tokens until we get back to the current token |
| // level |
| while (c < tokens.size()) |
| tokens.remove(c); |
| // placeholder for upcoming token |
| tokens.add(null); |
| } |
| } |
| else if (c < 254) // character |
| currentToken.append((char) c); |
| else if (c == 255) |
| currentToken.append(".class"); |
| else { // out-of-band value |
| throw new InternalError("internal error processing " + |
| "resource_map (can't-happen error)"); |
| } |
| } |
| if (tokens.size() > 0) // add token we just finished reading |
| tokens.set(tokens.size() - 1, currentToken.toString()); |
| StringBuilder key = new StringBuilder(); |
| // add the last entry to the map |
| for (int i = 1; i < tokens.size(); i++) { |
| if (i > 1) |
| key.append('/'); |
| key.append(tokens.get(i)); |
| } |
| if (!tokens.isEmpty()) |
| result.put(key.toString(), tokens.get(0)); |
| in.close(); |
| return Collections.unmodifiableMap(result); |
| } |
| |
| |
| /** |
| * Returns the contents of the resource_map file, which maps |
| * resources names to their respective bundles. |
| */ |
| public static Map<String, String> getResourceMap() throws IOException { |
| if (resourceMap == null) { |
| InputStream in = DownloadManager.class.getResourceAsStream("resource_map"); |
| if (in != null) { |
| in = new BufferedInputStream(in); |
| try { |
| resourceMap = readTreeMap(in); |
| in.close(); |
| } |
| catch (IOException e) { |
| // turns out we can be returned a broken stream instead of |
| // just null |
| resourceMap = new HashMap<String, String>(); |
| complete = true; |
| log("Can't find resource_map, forcing complete to true"); |
| } |
| in.close(); |
| } |
| else { |
| resourceMap = new HashMap<String, String>(); |
| complete = true; |
| log("Can't find resource_map, forcing complete to true"); |
| } |
| |
| for (int i = 1; ; i++) { // run through the numbered custom bundles |
| String name = CUSTOM_PREFIX + i; |
| File customPath = new File(getBundlePath(), name + ".jar"); |
| if (customPath.exists()) { |
| JarFile custom = new JarFile(customPath); |
| Enumeration entries = custom.entries(); |
| while (entries.hasMoreElements()) { |
| JarEntry entry = (JarEntry) entries.nextElement(); |
| if (!entry.isDirectory()) |
| resourceMap.put(entry.getName(), name); |
| } |
| } |
| else |
| break; |
| } |
| } |
| return resourceMap; |
| } |
| |
| |
| /** |
| * Returns the contents of the file_map file, which maps |
| * file names to their respective bundles. |
| */ |
| public static Map<String, String> getFileMap() throws IOException { |
| if (fileMap == null) { |
| InputStream in = DownloadManager.class.getResourceAsStream("file_map"); |
| if (in != null) { |
| in = new BufferedInputStream(in); |
| try { |
| fileMap = readTreeMap(in); |
| in.close(); |
| } |
| catch (IOException e) { |
| // turns out we can be returned a broken stream instead of |
| // just null |
| fileMap = new HashMap<String, String>(); |
| complete = true; |
| log("Can't find file_map, forcing complete to true"); |
| } |
| in.close(); |
| } |
| else { |
| fileMap = new HashMap<String, String>(); |
| complete = true; |
| log("Can't find file_map, forcing complete to true"); |
| } |
| } |
| return fileMap; |
| } |
| |
| |
| /** |
| * Returns the contents of the bundle.properties file, which maps |
| * bundle names to a pipe-separated list of their properties. Properties |
| * include: |
| * jarpath - By default, the JAR files (unpacked from classes.pack in the |
| * bundle) are stored under lib/bundles. The jarpath property |
| * overrides this default setting, causing the JAR to be unpacked |
| * at the specified location. This is used to preserve the |
| * identity of JRE JAR files such as lib/deploy.jar. |
| * size - The size of the download in bytes. |
| */ |
| private static synchronized Map<String, Map<String, String>> getBundleProperties() |
| throws IOException { |
| if (bundleProperties == null) { |
| InputStream in = DownloadManager.class.getResourceAsStream("bundle.properties"); |
| if (in == null) { |
| complete = true; |
| log("Can't find bundle.properties, forcing complete to true"); |
| return null; |
| } |
| in = new BufferedInputStream(in); |
| Properties tmp = new Properties(); |
| tmp.load(in); |
| bundleProperties = new HashMap<String, Map<String, String>>(); |
| for (Map.Entry e : tmp.entrySet()) { |
| String key = (String) e.getKey(); |
| String[] properties = ((String) e.getValue()).split("\\|"); |
| Map<String, String> map = new HashMap<String, String>(); |
| for (String entry : properties) { |
| int equals = entry.indexOf("="); |
| if (equals == -1) |
| throw new InternalError("error parsing bundle.properties: " + |
| entry); |
| map.put(entry.substring(0, equals).trim(), |
| entry.substring(equals + 1).trim()); |
| } |
| bundleProperties.put(key, map); |
| } |
| in.close(); |
| } |
| return bundleProperties; |
| } |
| |
| |
| /** |
| * Returns a single bundle property value loaded from the bundle.properties |
| * file. |
| */ |
| static String getBundleProperty(String bundleName, String property) { |
| try { |
| Map<String, Map<String, String>> props = getBundleProperties(); |
| Map/*<String, String>*/ map = props != null ? props.get(bundleName) : null; |
| return map != null ? (String) map.get(property) : null; |
| } |
| catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| |
| /** Returns an array of all supported bundle names. */ |
| static String[] getBundleNames() throws IOException { |
| if (bundleNames == null) { |
| Set<String> result = new HashSet<String>(); |
| Map<String, String> resourceMap = getResourceMap(); |
| if (resourceMap != null) |
| result.addAll(resourceMap.values()); |
| Map<String, String> fileMap = getFileMap(); |
| if (fileMap != null) |
| result.addAll(fileMap.values()); |
| bundleNames = result.toArray(new String[result.size()]); |
| } |
| return bundleNames; |
| } |
| |
| |
| /** |
| * Returns an array of all "critical" (must be downloaded prior to |
| * completion) bundle names. |
| */ |
| private static String[] getCriticalBundleNames() throws IOException { |
| if (criticalBundleNames == null) { |
| Set<String> result = new HashSet<String>(); |
| Map<String, String> fileMap = getFileMap(); |
| if (fileMap != null) |
| result.addAll(fileMap.values()); |
| criticalBundleNames = result.toArray(new String[result.size()]); |
| } |
| return criticalBundleNames; |
| } |
| |
| |
| public static void send(InputStream in, OutputStream out) |
| throws IOException { |
| byte[] buffer = new byte[BUFFER_SIZE]; |
| int c; |
| while ((c = in.read(buffer)) > 0) |
| out.write(buffer, 0, c); |
| } |
| |
| |
| /** |
| * Determine whether all bundles have been downloaded, and if so create |
| * the merged jars that will eventually replace rt.jar and resoures.jar. |
| * IMPORTANT: this method should only be called from the background |
| * download process. |
| */ |
| static void performCompletionIfNeeded() { |
| if (debug) |
| log("DownloadManager.performCompletionIfNeeded: checking (" + |
| complete + ", " + System.getProperty(KERNEL_NOMERGE_PROPERTY) |
| + ")"); |
| if (complete || |
| "true".equals(System.getProperty(KERNEL_NOMERGE_PROPERTY))) |
| return; |
| Bundle.loadReceipts(); |
| try { |
| if (debug) { |
| List critical = new ArrayList(Arrays.asList(getCriticalBundleNames())); |
| critical.removeAll(Bundle.receipts); |
| log("DownloadManager.performCompletionIfNeeded: still need " + |
| critical.size() + " bundles (" + critical + ")"); |
| } |
| if (Bundle.receipts.containsAll(Arrays.asList(getCriticalBundleNames()))) { |
| log("DownloadManager.performCompletionIfNeeded: running"); |
| // all done! |
| new Thread("JarMerger") { |
| public void run() { |
| createMergedJars(); |
| } |
| }.start(); |
| } |
| } |
| catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| |
| /** |
| * Returns the bundle corresponding to a given resource path (e.g. |
| * "java/lang/Object.class"). If the resource does not appear in a bundle, |
| * null is returned. |
| */ |
| public static Bundle getBundleForResource(String resource) |
| throws IOException { |
| String bundleName = getResourceMap().get(resource); |
| return bundleName != null ? Bundle.getBundle(bundleName) : null; |
| } |
| |
| |
| /** |
| * Returns the bundle corresponding to a given JRE file path (e.g. |
| * "bin/awt.dll"). If the file does not appear in a bundle, null is |
| * returned. |
| */ |
| private static Bundle getBundleForFile(String file) throws IOException { |
| String bundleName = getFileMap().get(file); |
| return bundleName != null ? Bundle.getBundle(bundleName) : null; |
| } |
| |
| |
| /** |
| * Returns the path to the lib/bundles directory. |
| */ |
| static File getBundlePath() { |
| return new File(JAVA_HOME, "lib" + File.separatorChar + "bundles"); |
| } |
| |
| private static String getAppDataLocalLow() { |
| return USER_HOME + "\\appdata\\locallow\\"; |
| } |
| |
| public static String getKernelJREDir() { |
| return "kerneljre" + JAVA_VERSION; |
| } |
| |
| static File getLocalLowTempBundlePath() { |
| return new File(getLocalLowKernelJava() + "-bundles"); |
| } |
| |
| static String getLocalLowKernelJava() { |
| return getAppDataLocalLow() + getKernelJREDir(); |
| } |
| |
| /** |
| * Returns an array of JAR files which have been added to the boot strap |
| * class path since the JVM was first booted. |
| */ |
| public static synchronized File[] getAdditionalBootStrapPaths() { |
| return additionalBootStrapPaths != null ? additionalBootStrapPaths : |
| new File[0]; |
| } |
| |
| |
| private static void addEntryToBootClassPath(File path) { |
| // Must acquire these locks in this order |
| synchronized(Launcher.class) { |
| synchronized(DownloadManager.class) { |
| File[] newBootStrapPaths = new File[ |
| additionalBootStrapPaths.length + 1]; |
| System.arraycopy(additionalBootStrapPaths, 0, newBootStrapPaths, |
| 0, additionalBootStrapPaths.length); |
| newBootStrapPaths[newBootStrapPaths.length - 1] = path; |
| additionalBootStrapPaths = newBootStrapPaths; |
| Launcher.flushBootstrapClassPath(); |
| } |
| } |
| } |
| |
| |
| /** |
| * Scan through java.ext.dirs to see if the lib/ext directory is included. |
| * If not, we shouldn't be "finding" lib/ext jars for download. |
| */ |
| private static synchronized boolean extDirIsIncluded() { |
| if (!extDirDetermined) { |
| extDirDetermined = true; |
| String raw = System.getProperty("java.ext.dirs"); |
| String ext = JAVA_HOME + File.separator + "lib" + File.separator + "ext"; |
| int index = 0; |
| while (index < raw.length()) { |
| int newIndex = raw.indexOf(File.pathSeparator, index); |
| if (newIndex == -1) |
| newIndex = raw.length(); |
| String path = raw.substring(index, newIndex); |
| if (path.equals(ext)) { |
| extDirIncluded = true; |
| break; |
| } |
| index = newIndex + 1; |
| } |
| } |
| return extDirIncluded; |
| } |
| |
| |
| private static String doGetBootClassPathEntryForResource( |
| String resourceName) { |
| boolean retry = false; |
| do { |
| Bundle bundle = null; |
| try { |
| bundle = getBundleForResource(resourceName); |
| if (bundle != null) { |
| File path = bundle.getJarPath(); |
| boolean isExt = path.getParentFile().getName().equals("ext"); |
| if (isExt && !extDirIsIncluded()) // this is a lib/ext jar, but |
| return null; // lib/ext isn't in the path |
| if (getBundleProperty(bundle.getName(), JAR_PATH_PROPERTY) == null) { |
| // if the bundle doesn't have its own JAR path, that means it's |
| // going to be merged into rt.jar. If we already have the |
| // merged rt.jar, we can simply point to that. |
| Bundle merged = Bundle.getBundle("merged"); |
| if (merged != null && merged.isInstalled()) { |
| File jar; |
| if (resourceName.endsWith(".class")) |
| jar = merged.getJarPath(); |
| else |
| jar = new File(merged.getJarPath().getPath().replaceAll("merged-rt.jar", |
| "merged-resources.jar")); |
| addEntryToBootClassPath(jar); |
| return jar.getPath(); |
| } |
| } |
| if (!bundle.isInstalled()) { |
| bundle.queueDependencies(true); |
| log("On-demand downloading " + |
| bundle.getName() + " for resource " + |
| resourceName + "..."); |
| bundle.install(); |
| log(bundle + " install finished."); |
| } |
| log("Double-checking " + bundle + " state..."); |
| if (!bundle.isInstalled()) { |
| throw new IllegalStateException("Expected state of " + |
| bundle + " to be INSTALLED"); |
| } |
| if (isExt) { |
| // don't add lib/ext entries to the boot class path, add |
| // them to the extension classloader instead |
| Launcher.addURLToExtClassLoader(path.toURL()); |
| return null; |
| } |
| |
| if ("javaws".equals(bundle.getName())) { |
| Launcher.addURLToAppClassLoader(path.toURL()); |
| log("Returning null for javaws"); |
| return null; |
| } |
| |
| if ("core".equals(bundle.getName())) |
| return null; |
| |
| // else add to boot class path |
| addEntryToBootClassPath(path); |
| |
| return path.getPath(); |
| } |
| return null; // not one of the JRE's classes |
| } |
| catch (Throwable e) { |
| retry = handleException(e); |
| log("Error downloading bundle for " + |
| resourceName + ":"); |
| log(e); |
| if (e instanceof IOException) { |
| // bundle did not get installed correctly, remove incomplete |
| // bundle files |
| if (bundle != null) { |
| if (bundle.getJarPath() != null) { |
| File packTmp = new File(bundle.getJarPath() + ".pack"); |
| packTmp.delete(); |
| bundle.getJarPath().delete(); |
| } |
| if (bundle.getLocalPath() != null) { |
| bundle.getLocalPath().delete(); |
| } |
| bundle.setState(Bundle.NOT_DOWNLOADED); |
| } |
| } |
| } |
| } while (retry); |
| sendErrorPing(ERROR_RETRY_CANCELLED); // bundle failed to install, user cancelled |
| |
| return null; // failed, user chose not to retry |
| } |
| |
| static synchronized void sendErrorPing(int code) { |
| try { |
| File bundlePath; |
| if (isWindowsVista()) { |
| bundlePath = getLocalLowTempBundlePath(); |
| } else { |
| bundlePath = getBundlePath(); |
| } |
| File tmp = new File(bundlePath, "tmp"); |
| File errors = new File(tmp, "errors"); |
| String errorString = String.valueOf(code); |
| if (errors.exists()) { |
| BufferedReader in = new BufferedReader(new FileReader(errors)); |
| String line = in.readLine(); |
| while (line != null) { |
| if (line.equals(errorString)) |
| return; // we have already pinged this error |
| line = in.readLine(); |
| } |
| } |
| tmp.mkdirs(); |
| Writer out = new FileWriter(errors, true); |
| out.write(errorString + System.getProperty("line.separator")); |
| out.close(); |
| postDownloadError(code); |
| } |
| catch (IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| |
| |
| /** |
| * Displays an error dialog and prompts the user to retry or cancel. |
| * Returns true if the user chose to retry, false if he chose to cancel. |
| */ |
| static boolean handleException(Throwable e) { |
| if (e instanceof IOException) { |
| // I don't know of a better method to determine the root cause of |
| // the exception, unfortunately... |
| int code = ERROR_UNSPECIFIED; |
| if (e.getMessage().indexOf("not enough space") != -1) |
| code = ERROR_DISK_FULL; |
| return askUserToRetryDownloadOrQuit(code); |
| } |
| else |
| return false; |
| } |
| |
| |
| static synchronized void flushBundleURLs() { |
| bundleURLs = null; |
| } |
| |
| |
| static synchronized Properties getBundleURLs(boolean showUI) |
| throws IOException { |
| if (bundleURLs == null) { |
| log("Entering DownloadManager.getBundleURLs"); |
| String base = getBaseDownloadURL(); |
| String url = appendTransactionId(base); |
| // use PID instead of createTempFile or other random filename so as |
| // to avoid dependencies on the random number generator libraries |
| File bundlePath = null; |
| // write temp file to locallow directory on vista |
| if (isWindowsVista()) { |
| bundlePath = getLocalLowTempBundlePath(); |
| } else { |
| bundlePath = getBundlePath(); |
| } |
| File tmp = new File(bundlePath, "urls." + getCurrentProcessId() + |
| ".properties"); |
| try { |
| log("Downloading from " + url + " to " + tmp); |
| downloadFromURL(url, tmp, "", showUI); |
| bundleURLs = new Properties(); |
| if (tmp.exists()) { |
| addToTotalDownloadSize((int) tmp.length()); // better late than never |
| InputStream in = new FileInputStream(tmp); |
| in = new BufferedInputStream(in); |
| bundleURLs.load(in); |
| in.close(); |
| if (bundleURLs.isEmpty()) { |
| fatalError(ERROR_MALFORMED_BUNDLE_PROPERTIES); |
| } |
| } else { |
| fatalError(ERROR_DOWNLOADING_BUNDLE_PROPERTIES); |
| } |
| } finally { |
| // delete the temp file |
| if (!debug) |
| tmp.delete(); |
| } |
| log("Leaving DownloadManager.getBundleURLs"); |
| // else an error occurred and user chose not to retry; leave |
| // bundleURLs empty so we don't continually try to re-download it |
| } |
| return bundleURLs; |
| } |
| |
| /** |
| * Checks to see if the specified resource is part of a bundle, and if so |
| * downloads it. Returns either a string which should be added to the boot |
| * class path (the newly-downloaded JAR's location), or null to indicate |
| * that it isn't one of the JRE's resources or could not be downloaded. |
| */ |
| public static String getBootClassPathEntryForResource( |
| final String resourceName) { |
| if (debug) |
| log("Entering getBootClassPathEntryForResource(" + resourceName + ")"); |
| if (isJREComplete() || downloading == null || |
| resourceName.startsWith("sun/jkernel")) { |
| if (debug) |
| log("Bailing: " + isJREComplete() + ", " + (downloading == null)); |
| return null; |
| } |
| incrementDownloadCount(); |
| try { |
| String result = (String) AccessController.doPrivileged( |
| new PrivilegedAction() { |
| public Object run() { |
| return (String) doGetBootClassPathEntryForResource( |
| resourceName); |
| } |
| } |
| ); |
| log("getBootClassPathEntryForResource(" + resourceName + ") == " + result); |
| return result; |
| } |
| finally { |
| decrementDownloadCount(); |
| } |
| } |
| |
| |
| /** |
| * Called by the boot class loader when it encounters a class it can't find. |
| * This method will check to see if the class is part of a bundle, and if so |
| * download it. Returns either a string which should be added to the boot |
| * class path (the newly-downloaded JAR's location), or null to indicate |
| * that it isn't one of the JRE's classes or could not be downloaded. |
| */ |
| public static String getBootClassPathEntryForClass(final String className) { |
| return getBootClassPathEntryForResource(className.replace('.', '/') + |
| ".class"); |
| } |
| |
| |
| private static boolean doDownloadFile(String relativePath) |
| throws IOException { |
| Bundle bundle = getBundleForFile(relativePath); |
| if (bundle != null) { |
| bundle.queueDependencies(true); |
| log("On-demand downloading " + bundle.getName() + |
| " for file " + relativePath + "..."); |
| bundle.install(); |
| return true; |
| } |
| return false; |
| } |
| |
| |
| /** |
| * Locates the bundle for the specified JRE file (e.g. "bin/awt.dll") and |
| * installs it. Returns true if the file is indeed part of the JRE and has |
| * now been installed, false if the file is not part of the JRE, and throws |
| * an IOException if the file is part of the JRE but could not be |
| * downloaded. |
| */ |
| public static boolean downloadFile(final String relativePath) |
| throws IOException { |
| if (isJREComplete() || downloading == null) |
| return false; |
| |
| incrementDownloadCount(); |
| try { |
| Object result = |
| AccessController.doPrivileged(new PrivilegedAction() { |
| public Object run() { |
| File path = new File(JAVA_HOME, |
| relativePath.replace('/', File.separatorChar)); |
| if (path.exists()) |
| return true; |
| try { |
| return new Boolean(doDownloadFile(relativePath)); |
| } |
| catch (IOException e) { |
| return e; |
| } |
| } |
| }); |
| if (result instanceof Boolean) |
| return ((Boolean) result).booleanValue(); |
| else |
| throw (IOException) result; |
| } |
| finally { |
| decrementDownloadCount(); |
| } |
| } |
| |
| |
| // increments the counter that tracks whether the current thread is involved |
| // in any download-related activities. A non-zero count indicates that the |
| // thread is currently downloading or installing a bundle. |
| static void incrementDownloadCount() { |
| downloading.set(downloading.get() + 1); |
| } |
| |
| |
| // increments the counter that tracks whether the current thread is involved |
| // in any download-related activities. A non-zero count indicates that the |
| // thread is currently downloading or installing a bundle. |
| static void decrementDownloadCount() { |
| // will generate an exception if incrementDownloadCount() hasn't been |
| // called first, this is intentional |
| downloading.set(downloading.get() - 1); |
| } |
| |
| |
| /** |
| * Returns <code>true</code> if the current thread is in the process of |
| * downloading a bundle. This is called by DownloadManager.loadLibrary() |
| * that is called by System.loadLibrary(), so |
| * that when we run into a library required by the download process itself, |
| * we don't call back into DownloadManager in an attempt to download it |
| * (which would lead to infinite recursion). |
| * |
| * All classes and libraries required to download classes must by |
| * definition already be present. So if this method returns true, we are |
| * currently in the middle of performing a download, and the class or |
| * library load must be happening due to the download itself. We can |
| * immediately abort such requests -- the class or library should already |
| * be present. If it isn't, we're not going to be able to download it, |
| * since we have just established that it is required to perform a |
| * download, and we might as well just let the NoClassDefFoundError / |
| * UnsatisfiedLinkError occur. |
| */ |
| public static boolean isCurrentThreadDownloading() { |
| return downloading != null ? downloading.get() > 0 : false; |
| } |
| |
| |
| /** |
| * Returns true if everything is downloaded and the JRE has been |
| * reconstructed. Also returns true if kernel functionality is disabled |
| * for any other reason. |
| */ |
| public static boolean isJREComplete() { |
| return complete; |
| } |
| |
| |
| // called by BackgroundDownloader |
| static void doBackgroundDownloads(boolean showProgress) { |
| if (!complete) { |
| if (!showProgress && !debug) |
| reportErrors = false; |
| try { |
| // install swing first for ergonomic reasons |
| Bundle swing = Bundle.getBundle("javax_swing_core"); |
| if (!swing.isInstalled()) |
| swing.install(showProgress, false, false); |
| // install remaining bundles |
| for (String name : getCriticalBundleNames()) { |
| Bundle bundle = Bundle.getBundle(name); |
| if (!bundle.isInstalled()) { |
| bundle.install(showProgress, false, true); |
| } |
| } |
| shutdown(); |
| } |
| catch (IOException e) { |
| log(e); |
| } |
| } |
| } |
| |
| // copy receipt file to destination path specified |
| static void copyReceiptFile(File from, File to) throws IOException { |
| DataInputStream in = new DataInputStream( |
| new BufferedInputStream(new FileInputStream(from))); |
| OutputStream out = new FileOutputStream(to); |
| String line = in.readLine(); |
| while (line != null) { |
| out.write((line + '\n').getBytes("utf-8")); |
| line = in.readLine(); |
| } |
| in.close(); |
| out.close(); |
| } |
| |
| |
| private static void downloadRequestedBundles() { |
| log("Checking for requested bundles..."); |
| try { |
| File list = new File(JAVA_HOME, REQUESTED_BUNDLES_PATH); |
| if (list.exists()) { |
| FileInputStream in = new FileInputStream(list); |
| ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
| send(in, buffer); |
| in.close(); |
| |
| // split string manually to avoid relying on regexes or |
| // StringTokenizer |
| String raw = new String(buffer.toByteArray(), "utf-8"); |
| List/*<String>*/ bundles = new ArrayList/*<String>*/(); |
| StringBuilder token = new StringBuilder(); |
| for (int i = 0; i < raw.length(); i++) { |
| char c = raw.charAt(i); |
| if (c == ',' || Character.isWhitespace(c)) { |
| if (token.length() > 0) { |
| bundles.add(token.toString()); |
| token.setLength(0); |
| } |
| } |
| else |
| token.append(c); |
| } |
| if (token.length() > 0) |
| bundles.add(token.toString()); |
| log("Requested bundles: " + bundles); |
| for (int i = 0; i < bundles.size(); i++) { |
| Bundle bundle = Bundle.getBundle((String) bundles.get(i)); |
| if (bundle != null && !bundle.isInstalled()) { |
| log("Downloading " + bundle + " due to requested.list"); |
| bundle.install(true, false, false); |
| } |
| } |
| } |
| } |
| catch (IOException e) { |
| log(e); |
| } |
| } |
| |
| |
| static void fatalError(int code) { |
| fatalError(code, null); |
| } |
| |
| |
| /** |
| * Called to cleanly shut down the VM when a fatal download error has |
| * occurred. Calls System.exit() if outside of the Java Plug-In, otherwise |
| * throws an error. |
| */ |
| static void fatalError(int code, String arg) { |
| sendErrorPing(code); |
| |
| for (int i = 0; i < Bundle.THREADS; i++) |
| bundleInstallComplete(); |
| if (reportErrors) |
| displayError(code, arg); |
| // inPlugIn check isn't 100% reliable but should be close enough. |
| // headless is for the browser side of things in the out-of-process |
| // plug-in |
| boolean inPlugIn = (Boolean.getBoolean("java.awt.headless") || |
| System.getProperty("javaplugin.version") != null); |
| KernelError error = new KernelError("Java Kernel bundle download failed"); |
| if (inPlugIn) |
| throw error; |
| else { |
| log(error); |
| System.exit(1); |
| } |
| } |
| |
| |
| // start the background download process using the jbroker broker process |
| // the method will first launch the broker process, if it is not already |
| // running |
| // it will then send the command necessary to start the background download |
| // process to the broker process |
| private static void startBackgroundDownloadWithBroker() { |
| |
| if (!BackgroundDownloader.getBackgroundDownloadProperty()) { |
| // If getBackgroundDownloadProperty() returns false |
| // we're doing the downloads from this VM; we don't want to |
| // spawn another one |
| return; |
| } |
| |
| // launch broker process if necessary |
| if (!launchBrokerProcess()) { |
| return; |
| } |
| |
| |
| String kernelDownloadURLProperty = getBaseDownloadURL(); |
| |
| String kernelDownloadURL; |
| |
| // only set KERNEL_DOWNLOAD_URL_PROPERTY if we override |
| // the default download url |
| if (kernelDownloadURLProperty == null || |
| kernelDownloadURLProperty.equals(DEFAULT_DOWNLOAD_URL)) { |
| kernelDownloadURL = " "; |
| } else { |
| kernelDownloadURL = kernelDownloadURLProperty; |
| } |
| |
| startBackgroundDownloadWithBrokerImpl(kernelDownloadURLProperty); |
| } |
| |
| private static void startBackgroundDownloads() { |
| if (!complete) { |
| if (BackgroundDownloader.getBackgroundMutex().acquire(0)) { |
| // we don't actually need to hold the mutex -- it was just a |
| // quick check to see if there is any point in even attempting |
| // to start the background downloader |
| BackgroundDownloader.getBackgroundMutex().release(); |
| if (isWindowsVista()) { |
| // use broker process to start background download |
| // at high integrity |
| startBackgroundDownloadWithBroker(); |
| } else { |
| BackgroundDownloader.startBackgroundDownloads(); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Increases the total download size displayed in the download progress |
| * dialog. |
| */ |
| static native void addToTotalDownloadSize(int size); |
| |
| |
| /** |
| * Displays a progress dialog while downloading from the specified URL. |
| * |
| *@param url the URL string from which to download |
| *@param file the destination path |
| *@param name the user-visible name of the component we are downloading |
| */ |
| static void downloadFromURL(String url, File file, String name, |
| boolean showProgress) { |
| // do not show download dialog if kernel.download.dialog is false |
| downloadFromURLImpl(url, file, name, |
| disableDownloadDialog ? false : showProgress); |
| } |
| |
| private static native void downloadFromURLImpl(String url, File file, |
| String name, boolean showProgress); |
| |
| // This is for testing purposes only - allows to specify URL |
| // to download kernel bundles from through the registry key. |
| static native String getUrlFromRegistry(); |
| |
| static native String getVisitorId0(); |
| |
| static native void postDownloadComplete(); |
| |
| static native void postDownloadError(int code); |
| |
| // Returns the visitor ID set by the installer, will be sent to the server |
| // during bundle downloads for logging purposes. |
| static synchronized String getVisitorId() { |
| if (!visitorIdDetermined) { |
| visitorIdDetermined = true; |
| visitorId = getVisitorId0(); |
| } |
| return visitorId; |
| } |
| |
| // display an error message using a native dialog |
| public static native void displayError(int code, String arg); |
| |
| // prompt user whether to retry download, or quit |
| // returns true if the user chose to retry |
| public static native boolean askUserToRetryDownloadOrQuit(int code); |
| |
| // returns true if we are running Windows Vista; false otherwise |
| static native boolean isWindowsVista(); |
| |
| private static native void startBackgroundDownloadWithBrokerImpl( |
| String command); |
| |
| private static int isJBrokerStarted() { |
| if (_isJBrokerStarted == -1) { |
| // initialize state of jbroker |
| _isJBrokerStarted = isJBrokerRunning() ? 1 : 0; |
| } |
| return _isJBrokerStarted; |
| } |
| |
| // returns true if broker process (jbroker) is running; false otherwise |
| private static native boolean isJBrokerRunning(); |
| |
| // returns true if we are running in IE protected mode; false otherwise |
| private static native boolean isIEProtectedMode(); |
| |
| private static native boolean launchJBroker(String jbrokerPath); |
| |
| static native void bundleInstallStart(); |
| |
| static native void bundleInstallComplete(); |
| |
| private static native boolean moveFileWithBrokerImpl(String fromPath, |
| String userHome); |
| |
| private static native boolean moveDirWithBrokerImpl(String fromPath, |
| String userHome); |
| |
| static boolean moveFileWithBroker(String fromPath) { |
| // launch jbroker if necessary |
| if (!launchBrokerProcess()) { |
| return false; |
| } |
| |
| return moveFileWithBrokerImpl(fromPath, USER_HOME); |
| } |
| |
| static boolean moveDirWithBroker(String fromPath) { |
| // launch jbroker if necessary |
| if (!launchBrokerProcess()) { |
| return false; |
| } |
| |
| return moveDirWithBrokerImpl(fromPath, USER_HOME); |
| } |
| |
| private static synchronized boolean launchBrokerProcess() { |
| // launch jbroker if necessary |
| if (isJBrokerStarted() == 0) { |
| // launch jbroker if needed |
| boolean ret = launchJBroker(JAVA_HOME); |
| // set state of jbroker |
| _isJBrokerStarted = ret ? 1 : 0; |
| return ret; |
| } |
| return true; |
| } |
| |
| private static class StreamMonitor implements Runnable { |
| private InputStream istream; |
| public StreamMonitor(InputStream stream) { |
| istream = new BufferedInputStream(stream); |
| new Thread(this).start(); |
| } |
| public void run() { |
| byte[] buffer = new byte[4096]; |
| try { |
| int ret = istream.read(buffer); |
| while (ret != -1) { |
| ret = istream.read(buffer); |
| } |
| } catch (IOException e) { |
| try { |
| istream.close(); |
| } catch (IOException e2) { |
| } // Should allow clean exit when process shuts down |
| } |
| } |
| } |
| |
| |
| /** Copy a file tree, excluding certain named files. */ |
| private static void copyAll(File src, File dest, Set/*<String>*/ excludes) |
| throws IOException { |
| if (!excludes.contains(src.getName())) { |
| if (src.isDirectory()) { |
| File[] children = src.listFiles(); |
| if (children != null) { |
| for (int i = 0; i < children.length; i++) |
| copyAll(children[i], |
| new File(dest, children[i].getName()), |
| excludes); |
| } |
| } |
| else { |
| dest.getParentFile().mkdirs(); |
| FileInputStream in = new FileInputStream(src); |
| FileOutputStream out = new FileOutputStream(dest); |
| send(in, out); |
| in.close(); |
| out.close(); |
| } |
| } |
| } |
| |
| |
| public static void dumpOutput(final Process p) { |
| Thread outputReader = new Thread("outputReader") { |
| public void run() { |
| try { |
| InputStream in = p.getInputStream(); |
| DownloadManager.send(in, System.out); |
| } catch (IOException e) { |
| log(e); |
| } |
| } |
| }; |
| outputReader.start(); |
| Thread errorReader = new Thread("errorReader") { |
| public void run() { |
| try { |
| InputStream in = p.getErrorStream(); |
| DownloadManager.send(in, System.err); |
| } catch (IOException e) { |
| log(e); |
| } |
| } |
| }; |
| errorReader.start(); |
| } |
| |
| |
| /** |
| * Creates the merged rt.jar and resources.jar files. |
| */ |
| private static void createMergedJars() { |
| log("DownloadManager.createMergedJars"); |
| File bundlePath; |
| if (isWindowsVista()) { |
| bundlePath = getLocalLowTempBundlePath(); |
| } else { |
| bundlePath = getBundlePath(); |
| } |
| File tmp = new File(bundlePath, "tmp"); |
| // explicitly check the final location, not the (potentially) local-low |
| // location -- a local-low finished isn't good enough to call it done |
| if (new File(getBundlePath(), "tmp" + File.separator + "finished").exists()) |
| return; // already done |
| log("DownloadManager.createMergedJars: running"); |
| tmp.mkdirs(); |
| boolean retry = false; |
| do { |
| try { |
| Bundle.getBundle("merged").install(false, false, true); |
| postDownloadComplete(); |
| // done, write an empty "finished" file to flag completion |
| File finished = new File(tmp, "finished"); |
| new FileOutputStream(finished).close(); |
| if (isWindowsVista()) { |
| if (!moveFileWithBroker(getKernelJREDir() + |
| "-bundles\\tmp\\finished")) { |
| throw new IOException("unable to create 'finished' file"); |
| } |
| } |
| log("DownloadManager.createMergedJars: created " + finished); |
| // next JRE startup will move these files into their final |
| // locations, as long as no other JREs are running |
| |
| // clean up the local low bundle directory on vista |
| if (isWindowsVista()) { |
| File tmpDir = getLocalLowTempBundlePath(); |
| File[] list = tmpDir.listFiles(); |
| if (list != null) { |
| for (int i = 0; i < list.length; i++) { |
| list[i].delete(); |
| } |
| } |
| tmpDir.delete(); |
| log("Finished cleanup, " + tmpDir + ".exists(): " + tmpDir.exists()); |
| } |
| } |
| catch (IOException e) { |
| log(e); |
| } |
| } |
| while (retry); |
| log("DownloadManager.createMergedJars: finished"); |
| } |
| |
| |
| private static void shutdown() { |
| try { |
| ExecutorService e = Bundle.getThreadPool(); |
| e.shutdown(); |
| e.awaitTermination(60 * 60 * 24, TimeUnit.SECONDS); |
| } |
| catch (InterruptedException e) { |
| } |
| } |
| |
| |
| // returns the registry key for kernel.debug |
| static native boolean getDebugKey(); |
| |
| |
| // returns the final value for the kernel debug property |
| public static boolean getDebugProperty(){ |
| /* |
| * Check registry key value |
| */ |
| boolean debugEnabled = getDebugKey(); |
| |
| /* |
| * Check system property - it should override the registry |
| * key value. |
| */ |
| if (System.getProperty(KERNEL_DEBUG_PROPERTY) != null) { |
| debugEnabled = Boolean.valueOf( |
| System.getProperty(KERNEL_DEBUG_PROPERTY)); |
| } |
| return debugEnabled; |
| |
| } |
| |
| |
| /** |
| * Outputs to the error stream even when System.err has not yet been |
| * initialized. |
| */ |
| static void println(String msg) { |
| if (System.err != null) |
| System.err.println(msg); |
| else { |
| try { |
| if (errorStream == null) |
| errorStream = new FileOutputStream(FileDescriptor.err); |
| errorStream.write((msg + |
| System.getProperty("line.separator")).getBytes("utf-8")); |
| } |
| catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| |
| |
| static void log(String msg) { |
| if (debug) { |
| println(msg); |
| try { |
| if (logStream == null) { |
| loadJKernelLibrary(); |
| File path = isWindowsVista() ? getLocalLowTempBundlePath() : |
| getBundlePath(); |
| path = new File(path, "kernel." + getCurrentProcessId() + ".log"); |
| logStream = new FileOutputStream(path); |
| } |
| logStream.write((msg + |
| System.getProperty("line.separator")).getBytes("utf-8")); |
| logStream.flush(); |
| } |
| catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| |
| |
| static void log(Throwable e) { |
| ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
| PrintStream p = new PrintStream(buffer); |
| e.printStackTrace(p); |
| p.close(); |
| log(buffer.toString(0)); |
| } |
| |
| |
| /** Dump the contents of a map to System.out. */ |
| private static void printMap(Map/*<String, String>*/ map) { |
| int size = 0; |
| Set<Integer> identityHashes = new HashSet<Integer>(); |
| Iterator/*<Map.Entry<String, String>>*/ i = map.entrySet().iterator(); |
| while (i.hasNext()) { |
| Map.Entry/*<String, String>*/ e = (Map.Entry) i.next(); |
| String key = (String) e.getKey(); |
| String value = (String) e.getValue(); |
| System.out.println(key + ": " + value); |
| Integer keyHash = Integer.valueOf(System.identityHashCode(key)); |
| if (!identityHashes.contains(keyHash)) { |
| identityHashes.add(keyHash); |
| size += key.length(); |
| } |
| Integer valueHash = Integer.valueOf(System.identityHashCode(value)); |
| if (!identityHashes.contains(valueHash)) { |
| identityHashes.add(valueHash); |
| size += value.length(); |
| } |
| } |
| System.out.println(size + " bytes"); |
| } |
| |
| |
| /** Process the "-dumpmaps" command-line argument. */ |
| private static void dumpMaps() throws IOException { |
| System.out.println("Resources:"); |
| System.out.println("----------"); |
| printMap(getResourceMap()); |
| System.out.println(); |
| System.out.println("Files:"); |
| System.out.println("----------"); |
| printMap(getFileMap()); |
| } |
| |
| |
| /** Process the "-download" command-line argument. */ |
| private static void processDownload(String bundleName) throws IOException { |
| if (bundleName.equals("all")) { |
| debug = true; |
| doBackgroundDownloads(true); |
| performCompletionIfNeeded(); |
| } |
| else { |
| Bundle bundle = Bundle.getBundle(bundleName); |
| if (bundle == null) { |
| println("Unknown bundle: " + bundleName); |
| System.exit(1); |
| } |
| else |
| bundle.install(); |
| } |
| } |
| |
| |
| static native int getCurrentProcessId(); |
| |
| private DownloadManager() { |
| } |
| |
| // Invoked by jkernel VM after the VM is initialized |
| static void setBootClassLoaderHook() { |
| if (!isJREComplete()) { |
| sun.misc.BootClassLoaderHook.setHook(new DownloadManager()); |
| } |
| } |
| |
| // Implementation of the BootClassLoaderHook interface |
| public String loadBootstrapClass(String name) { |
| // Check for download before we look for it. If |
| // DownloadManager ends up downloading it, it will add it to |
| // our search path before we proceed to the findClass(). |
| return DownloadManager.getBootClassPathEntryForClass(name); |
| } |
| |
| public boolean loadLibrary(String name) { |
| try { |
| if (!DownloadManager.isJREComplete() && |
| !DownloadManager.isCurrentThreadDownloading()) { |
| return DownloadManager.downloadFile("bin/" + |
| System.mapLibraryName(name)); |
| // it doesn't matter if the downloadFile call returns false -- |
| // it probably just means that this is a user library, as |
| // opposed to a JRE library |
| } |
| } catch (IOException e) { |
| throw new UnsatisfiedLinkError("Error downloading library " + |
| name + ": " + e); |
| } catch (NoClassDefFoundError e) { |
| // This happens while Java itself is being compiled; DownloadManager |
| // isn't accessible when this code is first invoked. It isn't an |
| // issue, as if we can't find DownloadManager, we can safely assume |
| // that additional code is not available for download. |
| } |
| return false; |
| } |
| |
| public boolean prefetchFile(String name) { |
| try { |
| return sun.jkernel.DownloadManager.downloadFile(name); |
| } catch (IOException ioe) { |
| return false; |
| } |
| } |
| |
| public String getBootstrapResource(String name) { |
| try { |
| // If this is a known JRE resource, ensure that its bundle is |
| // downloaded. If it isn't known, we just ignore the download |
| // failure and check to see if we can find the resource anyway |
| // (which is possible if the boot class path has been modified). |
| return DownloadManager.getBootClassPathEntryForResource(name); |
| } catch (NoClassDefFoundError e) { |
| // This happens while Java itself is being compiled; DownloadManager |
| // isn't accessible when this code is first invoked. It isn't an |
| // issue, as if we can't find DownloadManager, we can safely assume |
| // that additional code is not available for download. |
| return null; |
| } |
| } |
| |
| public File[] getAdditionalBootstrapPaths() { |
| return DownloadManager.getAdditionalBootStrapPaths(); |
| } |
| |
| public boolean isCurrentThreadPrefetching() { |
| return DownloadManager.isCurrentThreadDownloading(); |
| } |
| |
| public static void main(String[] arg) throws Exception { |
| AccessController.checkPermission(new AllPermission()); |
| |
| boolean valid = false; |
| if (arg.length == 2 && arg[0].equals("-install")) { |
| valid = true; |
| Bundle bundle = new Bundle() { |
| protected void updateState() { |
| // the bundle path was provided on the command line, so we |
| // just claim it has already been "downloaded" to the local |
| // filesystem |
| state = DOWNLOADED; |
| } |
| }; |
| |
| File jarPath; |
| int index = 0; |
| do { |
| index++; |
| jarPath = new File(getBundlePath(), |
| CUSTOM_PREFIX + index + ".jar"); |
| } |
| while (jarPath.exists()); |
| bundle.setName(CUSTOM_PREFIX + index); |
| bundle.setLocalPath(new File(arg[1])); |
| bundle.setJarPath(jarPath); |
| bundle.setDeleteOnInstall(false); |
| bundle.install(); |
| } |
| else if (arg.length == 2 && arg[0].equals("-download")) { |
| valid = true; |
| processDownload(arg[1]); |
| } |
| else if (arg.length == 1 && arg[0].equals("-dumpmaps")) { |
| valid = true; |
| dumpMaps(); |
| } |
| else if (arg.length == 2 && arg[0].equals("-sha1")) { |
| valid = true; |
| System.out.println(BundleCheck.getInstance(new File(arg[1]))); |
| } |
| else if (arg.length == 1 && arg[0].equals("-downloadtest")) { |
| valid = true; |
| File file = File.createTempFile("download", ".test"); |
| for (;;) { |
| file.delete(); |
| downloadFromURL(getBaseDownloadURL(), file, "URLS", true); |
| System.out.println("Downloaded " + file.length() + " bytes"); |
| } |
| } |
| if (!valid) { |
| System.out.println("usage: DownloadManager -install <path>.zip |"); |
| System.out.println(" DownloadManager -download " + |
| "<bundle_name> |"); |
| System.out.println(" DownloadManager -dumpmaps"); |
| System.exit(1); |
| } |
| } |
| } |