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

org.tiogasolutions.notify.server.grizzly.JarClassLoader Maven / Gradle / Ivy

There is a newer version: 4.4.5
Show newest version
/*
 * File: JarClassLoader.java
 *
 * Copyright (C) 2008-2013 JDotSoft. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA
 *
 * Visit jdotsoft.com for commercial license.
 *
 * $Id: JarClassLoader.java,v 1.38 2013/03/29 15:27:33 mg Exp $
 */
package org.tiogasolutions.notify.server.grizzly;

import javax.swing.*;
import java.applet.AppletContext;
import java.applet.AppletStub;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

public class JarClassLoader extends ClassLoader {

  /** VM parameter key to turn on logging to file or console. */
  public static final String KEY_LOGGER = "JarClassLoader.logger";

  public static final String KEY_LOGGER_LEVEL = "JarClassLoader.logger.level";

  /**
   * VM parameter key to define log area.
   * Valid areas are defined in {@link LogArea}.
   * Default value is {@link LogArea#ALL}. Multiple areas could be specified
   * with ',' delimiter (no spaces!).
   */
  public static final String KEY_LOGGER_AREA = "JarClassLoader.logger.area";

  public enum LogLevel { ERROR, WARN, INFO, DEBUG }
  public enum LogArea {
    /** Enable all logging areas. */
    ALL,
    /** Configuration related logging. Enabled always. */
    CONFIG,
    /** Enable JAR related logging. */
    JAR,
    /** Enable class loading related logging. */
    CLASS,
    /** Enable resource loading related logging. */
    RESOURCE,
    /** Enable native libraries loading related logging. */
    NATIVE
  }

  /**
   * Sub directory name for temporary files.
   * 

* JarClassLoader extracts all JARs and native libraries into temporary files * and makes the best attempt to clean these files on exit. *

* The sub directory is created in the directory defined in a system * property "java.io.tmpdir". Verify the content of this directory * periodically and empty it if required. Temporary files could accumulate * there if application was killed. */ public static final String TMP_SUB_DIRECTORY = "JarClassLoader"; private File dirTemp; private PrintStream logger; private List lstJarFile; private Set hsDeleteOnExit; private Map> hmClass; private LogLevel logLevel; private Set hsLogArea; private boolean bLogConsole; private JApplet applet; /** * Default constructor. * Defines system class loader as a parent class loader. */ public JarClassLoader() { this(ClassLoader.getSystemClassLoader()); } /** * Constructor. * * @param parent class loader parent. */ public JarClassLoader(ClassLoader parent) { super(parent); initLogger(); hmClass = new HashMap>(); lstJarFile = new ArrayList(); hsDeleteOnExit = new HashSet(); // Prepare common for all protocols String sUrlTopJar = null; ProtectionDomain pdTop = getClass().getProtectionDomain(); CodeSource cs = pdTop.getCodeSource(); URL urlTopJar = cs.getLocation(); String protocol = urlTopJar.getProtocol(); // Work with different cases: JarFileInfo jarFileInfo = null; if ("http".equals(protocol) || "https".equals(protocol)) { // Protocol 'http' or 'https' - application launched from WebStart / JNLP or as Java applet try { // Convert: // urlTopJar = "http://.../MyApp.jar" --> connection sun.net.www.protocol.http.HttpURLConnection // to // urlTopJar = "jar:http://.../MyApp.jar!/" --> connection java.net.JarURLConnection urlTopJar = new URL("jar:" + urlTopJar + "!/"); JarURLConnection jarCon = (JarURLConnection)urlTopJar.openConnection(); JarFile jarFile = jarCon.getJarFile(); jarFileInfo = new JarFileInfo(jarFile, jarFile.getName(), null, pdTop, null); logInfo(LogArea.JAR, "Loading from top JAR: '%s' PROTOCOL: '%s'", urlTopJar, protocol); } catch (Exception e) { // ClassCastException, IOException logError(LogArea.JAR, "Failure to load HTTP JAR: %s %s", urlTopJar, e.toString()); return; } } if ("file".equals(protocol)) { // Protocol 'file' - application launched from exploded dir or JAR // Decoding required for 'space char' in URL: // URL.getFile() returns "/C:/my%20dir/MyApp.jar" for "/C:/my dir/MyApp.jar" try { sUrlTopJar = URLDecoder.decode(urlTopJar.getFile(), "UTF-8"); } catch (UnsupportedEncodingException e) { logError(LogArea.JAR, "Failure to decode URL: %s %s", urlTopJar, e.toString()); return; } File fileJar = new File(sUrlTopJar); // Application is loaded from directory: if (fileJar.isDirectory()) { logInfo(LogArea.JAR, "Loading from exploded directory: %s", sUrlTopJar); for (String fileName : fileJar.list()) { logInfo(LogArea.JAR, " %s", fileName); } return; // JarClassLoader completed its job } // Application is loaded from a JAR: try { jarFileInfo = new JarFileInfo(new JarFile(fileJar), fileJar.getName(), null, pdTop, null); logInfo(LogArea.JAR, "Loading from top JAR: '%s' PROTOCOL: '%s'", sUrlTopJar, protocol); } catch (IOException e) { logError(LogArea.JAR, "Not a JAR: %s %s", sUrlTopJar, e.toString()); return; } } // FINALLY LOAD TOP JAR: try { if (jarFileInfo == null) { throw new IOException(String.format( "Unknown protocol %s", protocol)); } loadJar(jarFileInfo); // start recursive JAR loading } catch (IOException e) { logError(LogArea.JAR, "Not valid URL: %s %s", urlTopJar, e.toString()); return; } checkShading(); Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { shutdown(); } }); } // JarClassLoader() //--------------------------------separator-------------------------------- static int ______INIT; private void initLogger() { // Logger defaults: bLogConsole = true; this.logger = System.out; // default to console logLevel = LogLevel.ERROR; hsLogArea = new HashSet(); hsLogArea.add(LogArea.CONFIG); // Logger stream console or file: String sLogger = System.getProperty(KEY_LOGGER); if (sLogger != null) { try { this.logger = new PrintStream(sLogger); bLogConsole = false; } catch (FileNotFoundException e) { logError(LogArea.CONFIG, "Cannot create log file %s.", sLogger); } } // Logger level: String sLogLevel = System.getProperty(KEY_LOGGER_LEVEL); if (sLogLevel != null) { try { logLevel = LogLevel.valueOf(sLogLevel); } catch (Exception e) { logError(LogArea.CONFIG, "Not valid parameter in %s=%s", KEY_LOGGER_LEVEL, sLogLevel); } } // Logger area: String sLogArea = System.getProperty(KEY_LOGGER_AREA); if (sLogArea != null) { String[] tokenAll = sLogArea.split(","); try { for (String t : tokenAll) { hsLogArea.add(LogArea.valueOf(t)); } } catch (Exception e) { logError(LogArea.CONFIG, "Not valid parameter in %s=%s", KEY_LOGGER_AREA, sLogArea); } } if (hsLogArea.size() == 1 && hsLogArea.contains(LogArea.CONFIG)) { for (LogArea la : LogArea.values()) { hsLogArea.add(la); } } } // initLogger() /** * Using temp files (one per inner JAR/DLL) solves many issues: * 1. There are no ways to load JAR defined in a JarEntry directly * into the JarFile object (see also #6 below). * 2. Cannot use memory-mapped files because they are using * nio channels, which are not supported by JarFile ctor. * 3. JarFile object keeps opened JAR files handlers for fast access. * 4. Deep resource in a jar-in-jar does not have well defined URL. * Making temp file with JAR solves this problem. * 5. Similar issues with native libraries: * ClassLoader.findLibrary() accepts ONLY string with * absolute path to the file with native library. * 6. Option "java.protocol.handler.pkgs" does not allow access to nested JARs(?). * * @param inf JAR entry information. * @return temporary file object presenting JAR entry. * @throws JarClassLoaderException */ private File createTempFile(JarEntryInfo inf) throws JarClassLoaderException { // Temp files directory: // WinXP: C:/Documents and Settings/username/Local Settings/Temp/JarClassLoader // Unix: /var/tmp/JarClassLoader if (dirTemp == null) { File dir = new File(System.getProperty("java.io.tmpdir"), TMP_SUB_DIRECTORY); if (!dir.exists()) { dir.mkdir(); } chmod777(dir); // Unix - allow temp directory RW access to all users. if (!dir.exists() || !dir.isDirectory()) { throw new JarClassLoaderException( "Cannot create temp directory " + dir.getAbsolutePath()); } dirTemp = dir; } File fileTmp = null; try { fileTmp = File.createTempFile(inf.getName() + ".", null, dirTemp); fileTmp.deleteOnExit(); logInfo(LogArea.JAR, "Created temp file: %s", fileTmp.getAbsolutePath()); chmod777(fileTmp); // Unix - allow temp file deletion by any user byte[] a_by = inf.getJarBytes(); BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(fileTmp)); os.write(a_by); os.close(); return fileTmp; } catch (IOException e) { throw new JarClassLoaderException(String.format( "Cannot create temp file '%s' for %s", fileTmp, inf.jarEntry), e); } } // createTempFile() /** * Loads specified JAR. * * @param jarFileInfo * @throws IOException */ private void loadJar(JarFileInfo jarFileInfo) throws IOException { lstJarFile.add(jarFileInfo); try { Enumeration en = jarFileInfo.jarFile.entries(); final String EXT_JAR = ".jar"; while (en.hasMoreElements()) { JarEntry je = en.nextElement(); if (je.isDirectory()) { continue; } String s = je.getName().toLowerCase(); // JarEntry name if (s.lastIndexOf(EXT_JAR) == s.length() - EXT_JAR.length()) { JarEntryInfo inf = new JarEntryInfo(jarFileInfo, je); File fileTemp = createTempFile(inf); logInfo(LogArea.JAR, "Loading inner JAR %s from temp file %s", inf.jarEntry, getFilename4Log(fileTemp)); // Construct ProtectionDomain for this inner JAR: URL url = fileTemp.toURI().toURL(); ProtectionDomain pdParent = jarFileInfo.pd; // 'csParent' is never null: top JAR has it, JCL creates it for child JAR: CodeSource csParent = pdParent.getCodeSource(); Certificate[] certParent = csParent.getCertificates(); CodeSource csChild = (certParent == null ? new CodeSource(url, csParent.getCodeSigners()) : new CodeSource(url, certParent)); ProtectionDomain pdChild = new ProtectionDomain(csChild, pdParent.getPermissions(), pdParent.getClassLoader(), pdParent.getPrincipals()); loadJar(new JarFileInfo( new JarFile(fileTemp), inf.getName(), jarFileInfo, pdChild, fileTemp)); } } } catch (JarClassLoaderException e) { throw new RuntimeException( "ERROR on loading inner JAR: " + e.getMessageAll()); } } // loadJar() private JarEntryInfo findJarEntry(String sName) { for (JarFileInfo jarFileInfo : lstJarFile) { JarFile jarFile = jarFileInfo.jarFile; JarEntry jarEntry = jarFile.getJarEntry(sName); if (jarEntry != null) { return new JarEntryInfo(jarFileInfo, jarEntry); } } return null; } // findJarEntry() private List findJarEntries(String sName) { List lst = new ArrayList(); for (JarFileInfo jarFileInfo : lstJarFile) { JarFile jarFile = jarFileInfo.jarFile; JarEntry jarEntry = jarFile.getJarEntry(sName); if (jarEntry != null) { lst.add(new JarEntryInfo(jarFileInfo, jarEntry)); } } return lst; } // findJarEntries() /** * Finds native library entry. * * @param sLib Library name. For example for the library name "Native" * the Windows returns entry "Native.dll", * the Linux returns entry "libNative.so", * the Mac returns entry "libNative.jnilib". * * @return Native library entry. */ private JarEntryInfo findJarNativeEntry(String sLib) { String sName = System.mapLibraryName(sLib); for (JarFileInfo jarFileInfo : lstJarFile) { JarFile jarFile = jarFileInfo.jarFile; Enumeration en = jarFile.entries(); while (en.hasMoreElements()) { JarEntry je = en.nextElement(); if (je.isDirectory()) { continue; } // Example: sName is "Native.dll" String sEntry = je.getName(); // "Native.dll" or "abc/xyz/Native.dll" // sName "Native.dll" could be found, for example // - in the path: abc/Native.dll/xyz/my.dll <-- do not load this one! // - in the partial name: abc/aNative.dll <-- do not load this one! String[] token = sEntry.split("/"); // the last token is library name if (token.length > 0 && token[token.length - 1].equals(sName)) { logInfo(LogArea.NATIVE, "Loading native library '%s' found as '%s' in JAR %s", sLib, sEntry, jarFileInfo.simpleName); return new JarEntryInfo(jarFileInfo, je); } } } return null; } // findJarNativeEntry() /** * Loads class from a JAR and searches for all jar-in-jar. * * @param sClassName class to load. * @return Loaded class. * @throws JarClassLoaderException on failure */ private Class findJarClass(String sClassName) throws JarClassLoaderException { Class c = hmClass.get(sClassName); if (c != null) { return c; } // Char '/' works for Win32 and Unix. String sName = sClassName.replace('.', '/') + ".class"; JarEntryInfo inf = findJarEntry(sName); String jarSimpleName = null; if (inf != null) { jarSimpleName = inf.jarFileInfo.simpleName; definePackage(sClassName, inf); byte[] a_by = inf.getJarBytes(); try { c = defineClass(sClassName, a_by, 0, a_by.length, inf.jarFileInfo.pd); } catch (ClassFormatError e) { throw new JarClassLoaderException(null, e); } } if (c == null) { throw new JarClassLoaderException(sClassName); } hmClass.put(sClassName, c); logInfo(LogArea.CLASS, "Loaded %s by %s from JAR %s", sClassName, getClass().getName(), jarSimpleName); return c; } // findJarClass() private void checkShading() { if (logLevel.ordinal() < LogLevel.WARN.ordinal()) { // Do not waste time if no logging. return; } Map hm = new HashMap(); for (JarFileInfo jarFileInfo : lstJarFile) { JarFile jarFile = jarFileInfo.jarFile; Enumeration en = jarFile.entries(); while (en.hasMoreElements()) { JarEntry je = en.nextElement(); if (je.isDirectory()) { continue; } String sEntry = je.getName(); // "Some.txt" or "abc/xyz/Some.txt" if ("META-INF/MANIFEST.MF".equals(sEntry)) { continue; } JarFileInfo jar = hm.get(sEntry); if (jar == null) { hm.put(sEntry, jarFileInfo); } else { logWarn(LogArea.JAR, "ENTRY %s IN %s SHADES %s", sEntry, jar.simpleName, jarFileInfo.simpleName); } } } } // checkShading() //--------------------------------separator-------------------------------- static int ______SHUTDOWN; /** * Called on shutdown to cleanup temporary files. *

* JVM does not close handles to native libraries files or JARs with * resources loaded as getResourceAsStream(). Temp files are not deleted * even if they are marked deleteOnExit(). They also fail to delete explicitly. * Workaround is to preserve list with temp files in configuration file * "[user.home]/.JarClassLoader" and delete them on next application run. *

* See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4171239 * "This occurs only on Win32, which does not allow a file to be deleted * until all streams on it have been closed." */ private void shutdown() { for (JarFileInfo jarFileInfo : lstJarFile) { try { jarFileInfo.jarFile.close(); } catch (IOException e) { // Ignore. In the worst case temp files will accumulate. } File file = jarFileInfo.fileDeleteOnExit; if (file != null && !file.delete()) { hsDeleteOnExit.add(file); } } // Private configuration file with failed to delete temporary files: // WinXP: C:/Documents and Settings/username/.JarClassLoader // Unix: /export/home/username/.JarClassLoader // -or- /home/username/.JarClassLoader File fileCfg = new File(System.getProperty("user.home") + File.separator + ".JarClassLoader"); deleteOldTemp(fileCfg); persistNewTemp(fileCfg); } // shutdown() /** * Deletes temporary files listed in the file. * The method is called on shutdown(). * * @param fileCfg file with temporary files list. */ private void deleteOldTemp(File fileCfg) { BufferedReader reader = null; try { int count = 0; reader = new BufferedReader(new FileReader(fileCfg)); String sLine; while ((sLine = reader.readLine()) != null) { File file = new File(sLine); if (!file.exists()) { continue; // already deleted; from command line? } if (file.delete()) { count++; } else { // Cannot delete, will try next time. hsDeleteOnExit.add(file); } } logDebug(LogArea.CONFIG, "Deleted %d old temp files listed in %s", count, fileCfg.getAbsolutePath()); } catch (IOException e) { // Ignore. This file may not exist. } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { } } } } // deleteOldTemp() /** * Creates file with temporary files list. This list will be used to * delete temporary files on the next application launch. * The method is called from shutdown(). * * @param fileCfg file with temporary files list. */ private void persistNewTemp(File fileCfg) { if (hsDeleteOnExit.size() == 0) { logDebug(LogArea.CONFIG, "No temp file names to persist on exit."); fileCfg.delete(); // do not pollute disk return; } logDebug(LogArea.CONFIG, "Persisting %d temp file names into %s", hsDeleteOnExit.size(), fileCfg.getAbsolutePath()); BufferedWriter writer = null; try { writer = new BufferedWriter(new FileWriter(fileCfg)); for (File file : hsDeleteOnExit) { if (!file.delete()) { String f = file.getCanonicalPath(); writer.write(f); writer.newLine(); logWarn(LogArea.JAR, "JVM failed to release %s", f); } } } catch (IOException e) { // Ignore. In the worst case temp files will accumulate. } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { } } } } // persistNewTemp() //--------------------------------separator-------------------------------- static int ______ACCESS; /** * Checks how the application was loaded: from JAR or file system. * * @return true if application was started from JAR. */ public boolean isLaunchedFromJar() { return (lstJarFile.size() > 0); } // isLaunchedFromJar() /** * Returns the name of the jar file main class, or null if * no "Main-Class" manifest attributes was defined. * * @return Main class declared in JAR's manifest. */ public String getManifestMainClass() { Attributes attr = null; if (isLaunchedFromJar()) { try { // The first element in array is the top level JAR Manifest m = lstJarFile.get(0).jarFile.getManifest(); attr = m.getMainAttributes(); } catch (IOException e) { } } return (attr == null ? null : attr.getValue(Name.MAIN_CLASS)); } // getManifestMainClass() public void invokeStart(String sClass, String[] args) throws Throwable { Class clazz = loadClass(sClass); logInfo(LogArea.CONFIG, "Launch: %s.main(); Loader: %s", sClass, clazz.getClassLoader()); Method method = clazz.getMethod("main", new Class[] { String[].class }); boolean bValidModifiers = false; boolean bValidVoid = false; if (method != null) { logInfo(LogArea.CONFIG, "Found main method"); method.setAccessible(true); // Disable IllegalAccessException int nModifiers = method.getModifiers(); // main() must be "public static" bValidModifiers = Modifier.isPublic(nModifiers) && Modifier.isStatic(nModifiers); Class clazzRet = method.getReturnType(); // main() must be "void" bValidVoid = (clazzRet == void.class); } if (method == null || !bValidModifiers || !bValidVoid) { throw new NoSuchMethodException( "The main() method in class \"" + sClass + "\" not found."); } // Invoke method. // Crazy cast "(Object)args" because param is: "Object... args" try { method.invoke(null, (Object)args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } // invokemain() /** * Call this method to initialize an applet from your launcher class * MyAppletLauncher.init() method. * * @param sClass class name in form "MyClass" for default package * or "com.abc.MyClass" for class in some package * * @param appletParent parent applet from a launcher. * * @throws Throwable wrapper for many exceptions thrown while applet * instantiation and calling init() method. */ public void initApplet(String sClass, final JApplet appletParent) throws Throwable { Class clazz = loadClass(sClass); logInfo(LogArea.CONFIG, "initApplet() --> %s.init(); Loader: %s", sClass, clazz.getClassLoader()); applet = (JApplet)clazz.newInstance(); applet.setStub(new AppletStub() { @Override public boolean isActive() { return appletParent.isActive(); } @Override public URL getDocumentBase() { return appletParent.getDocumentBase(); } @Override public URL getCodeBase() { return appletParent.getCodeBase(); } @Override public String getParameter(String name) { return appletParent.getParameter(name); } @Override public AppletContext getAppletContext() { return appletParent.getAppletContext(); } @Override public void appletResize(int width, int height) { appletParent.resize(width, height); } }); applet.init(); appletParent.setContentPane(applet.getContentPane()); } // initApplet() /** * Call this method to start the applet from your launcher class * MyAppletLauncher.start() method. */ public void startApplet() { checkApplet(); logInfo(LogArea.CONFIG, "startApplet() --> %s.start()", applet.getClass().getName()); applet.start(); } /** * Call this method to stop the applet from your launcher class * MyAppletLauncher.stop() method. */ public void stopApplet() { checkApplet(); logInfo(LogArea.CONFIG, "stopApplet() --> %s.stop()", applet.getClass().getName()); applet.stop(); } /** * Call this method to destroy the applet from your launcher class * MyAppletLauncher.destroy() method. */ public void destroyApplet() { checkApplet(); logInfo(LogArea.CONFIG, "destroyApplet() --> %s.destroy()", applet.getClass().getName()); applet.destroy(); } //--------------------------------separator-------------------------------- static int ______OVERRIDE; /** * Class loader JavaDoc encourages overriding findClass(String) in derived * class rather than overriding this method. This does not work for * loading classes from a JAR. Default implementation of loadClass() is * able to load a class from a JAR without calling findClass(). */ @Override protected synchronized Class loadClass(String sClassName, boolean bResolve) throws ClassNotFoundException { logDebug(LogArea.CLASS, "LOADING %s (resolve=%b)", sClassName, bResolve); // Each thread must have THIS class loader set as a context class loader. // This is required to prevent failure finding a class or resource from // external JAR requested by a common class loaded from rt.jar. // The best example is external LnF, explained in steps: // 1. Application requests 'javax.swing.JOptionPane'. // 2. THIS class loader passes request to system default class loader // to load the class from rt.jar. // 3. The class 'javax.swing.JOptionPane' is loaded by system default class // loader. // 4. The class 'javax.swing.JOptionPane' is requesting 'UIDefaults.getUI()' // for component, which resides in external LnF JAR. // 5. The class loader which is used to load the requested component is // current thread context class loader if it is set, otherwise the parent // thread context class loader, or the default system class loader // for the top level thread. // 6. The system class loader is used to load requested component if // thread context class loader is not set. The default system class loader is // - sun.misc.Launcher$AppClassLoader - run from file system or JAR // - com.sun.jnlp.JNLPClassLoader - run from JNLP // System class loaders cannot find requested component in external // JAR and throw exception. // // Setting thread context class loader for the top thread in invokeStart() // method is sufficient for most cases. It fails for new threads created // not from the start thread. // // Setting thread context class loader below must be reconsidered // for specific conditions. // // Essential reading: // - Thread.getContextClassLoader() JavaDoc. // - http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html Thread.currentThread().setContextClassLoader(this); Class c = null; try { // Step 0. This class is already loaded by system classloader. if (getClass().getName().equals(sClassName)) { return JarClassLoader.class; } // Step 1. Load from JAR. if (isLaunchedFromJar()) { try { c = findJarClass(sClassName); // Do not simplify! See "finally"! return c; } catch (JarClassLoaderException e) { if (e.getCause() == null) { logDebug(LogArea.CLASS, "Not found %s in JAR by %s: %s", sClassName, getClass().getName(), e.getMessage()); } else { logDebug(LogArea.CLASS, "Error loading %s in JAR by %s: %s", sClassName, getClass().getName(), e.getCause()); } // keep looking... } } // Step 2. Load by parent (usually system) class loader. // Call findSystemClass() AFTER attempt to find in a JAR. // If it called BEFORE it will load class-in-jar using // SystemClassLoader and "infect" it with SystemClassLoader. // The SystemClassLoader will be used to load all dependent // classes. SystemClassLoader will fail to load a class from // jar-in-jar and to load dll-in-jar. try { // No need to call findLoadedClass(sClassName) because it's called inside: ClassLoader cl = getParent(); c = cl.loadClass(sClassName); // System classloader does not define ProtectionDomain->CodeSource - null logInfo(LogArea.CLASS, "Loaded %s by %s", sClassName, cl.getClass().getName()); return c; } catch (ClassNotFoundException e) { } // What else? throw new ClassNotFoundException("Failure to load: " + sClassName); } finally { if (c != null && bResolve) { resolveClass(c); } } } // loadClass() /** * @see ClassLoader#findResource(String) * * @return A URL object for reading the resource, or null if the resource could not be found. * Example URL: jar:file:C:\...\some.jar!/resources/InnerText.txt */ @Override protected URL findResource(String sName) { logDebug(LogArea.RESOURCE, "findResource: %s", sName); if (isLaunchedFromJar()) { JarEntryInfo inf = findJarEntry(normalizeResourceName(sName)); if (inf != null) { URL url = inf.getURL(); logInfo(LogArea.RESOURCE, "found resource: %s", url); return url; } logInfo(LogArea.RESOURCE, "not found resource: %s", sName); return null; } return super.findResource(sName); } // findResource() /** * @see ClassLoader#findResources(String) * * @return An enumeration of {@link URL URL} objects for * the resources */ @Override public Enumeration findResources(String sName) throws IOException { logDebug(LogArea.RESOURCE, "getResources: %s", sName); if (isLaunchedFromJar()) { List lstJarEntry = findJarEntries(normalizeResourceName(sName)); List lstURL = new ArrayList(); for (JarEntryInfo inf : lstJarEntry) { URL url = inf.getURL(); if (url != null) { lstURL.add(url); } } return Collections.enumeration(lstURL); } return super.findResources(sName); } // findResources() /** * @see ClassLoader#findLibrary(String) * * @return The absolute path of the native library. */ @Override protected String findLibrary(String sLib) { logDebug(LogArea.NATIVE, "findLibrary: %s", sLib); if (isLaunchedFromJar()) { JarEntryInfo inf = findJarNativeEntry(sLib); if (inf != null) { try { File file = createTempFile(inf); logDebug(LogArea.NATIVE, "Loading native library %s from temp file %s", inf.jarEntry, getFilename4Log(file)); hsDeleteOnExit.add(file); return file.getAbsolutePath(); } catch (JarClassLoaderException e) { logError(LogArea.NATIVE, "Failure to load native library %s: %s", sLib, e.toString()); } } return null; } return super.findLibrary(sLib); } // findLibrary() //--------------------------------separator-------------------------------- static int ______HELPERS; /** * The default ClassLoader.defineClass() does not create package * for the loaded class and leaves it null. Each package referenced by this * class loader must be created only once before the * ClassLoader.defineClass() call. * The base class ClassLoader keeps cache with created packages * for reuse. * * @param sClassName class to load. * @throws IllegalArgumentException * If package name duplicates an existing package either in this * class loader or one of its ancestors. */ private void definePackage(String sClassName, JarEntryInfo inf) throws IllegalArgumentException { int pos = sClassName.lastIndexOf('.'); String sPackageName = pos > 0 ? sClassName.substring(0, pos) : ""; if (getPackage(sPackageName) == null) { JarFileInfo jfi = inf.jarFileInfo; definePackage(sPackageName, jfi.getSpecificationTitle(), jfi.getSpecificationVersion(), jfi.getSpecificationVendor(), jfi.getImplementationTitle(), jfi.getImplementationVersion(), jfi.getImplementationVendor(), jfi.getSealURL()); } } /** * The system class loader could load resources defined as * "com/abc/Foo.txt" or "com\abc\Foo.txt". * This method converts path with '\' to default '/' JAR delimiter. * * @param sName resource name including path. * @return normalized resource name. */ private String normalizeResourceName(String sName) { return sName.replace('\\', '/'); } private void chmod777(File file) { file.setReadable(true, false); file.setWritable(true, false); file.setExecutable(true, false); // Unix: allow content for dir, redundant for file } private String getFilename4Log(File file) { if (logger != null) { try { // In form "C:\Documents and Settings\..." return file.getCanonicalPath(); } catch (IOException e) { // In form "C:\DOCUME~1\..." return file.getAbsolutePath(); } } return null; } private void checkApplet() { if (applet == null) { throw new IllegalStateException("Applet is not inited. " + "Please call JarClassLoader.initApplet() first."); } } private void logDebug(LogArea area, String sMsg, Object ... obj) { log(LogLevel.DEBUG, area, sMsg, obj); } private void logInfo(LogArea area, String sMsg, Object ... obj) { log(LogLevel.INFO, area, sMsg, obj); } private void logWarn(LogArea area, String sMsg, Object ... obj) { log(LogLevel.WARN, area, sMsg, obj); } private void logError(LogArea area, String sMsg, Object ... obj) { log(LogLevel.ERROR, area, sMsg, obj); } private void log(LogLevel level, LogArea area, String sMsg, Object ... obj) { // if (level == LogLevel.WARN || level == LogLevel.ERROR) { // System.err.printf("JarClassLoader-" + level + ": " + sMsg + "\n", obj); // } else { // System.out.printf("JarClassLoader-" + level + ": " + sMsg + "\n", obj); // } // if (level.ordinal() <= logLevel.ordinal()) { // if (hsLogArea.contains(LogArea.ALL) || hsLogArea.contains(area)) { // logger.printf("JarClassLoader-" + level + ": " + sMsg + "\n", obj); // } // } // if (!bLogConsole && level == LogLevel.ERROR) { // repeat to console // System.out.printf("JarClassLoader-" + level + ": " + sMsg + "\n", obj); // } } /** * Inner class with JAR file information. */ private static class JarFileInfo { JarFile jarFile; // this is the essence of JarFileInfo wrapper String simpleName; // accumulated for logging like: "topJar!childJar!kidJar" File fileDeleteOnExit; Manifest mf; // required for package creation ProtectionDomain pd; /** * @param jarFile * Never null. * @param simpleName * Used for logging. Never null. * @param jarFileParent * Used to make simpleName for logging. Null for top level JAR. * @param fileDeleteOnExit * Used only to delete temporary file on exit. * Could be null if not required to delete on exit (top level JAR) * @throws JarClassLoaderException */ JarFileInfo(JarFile jarFile, String simpleName, JarFileInfo jarFileParent, ProtectionDomain pd, File fileDeleteOnExit) { this.simpleName = (jarFileParent == null ? "" : jarFileParent.simpleName + "!") + simpleName; this.jarFile = jarFile; this.pd = pd; this.fileDeleteOnExit = fileDeleteOnExit; try { this.mf = jarFile.getManifest(); // 'null' if META-INF directory is missing } catch (IOException e) { // Ignore and create blank manifest } if (this.mf == null) { this.mf = new Manifest(); } } String getSpecificationTitle() { return mf.getMainAttributes().getValue(Name.SPECIFICATION_TITLE); } String getSpecificationVersion() { return mf.getMainAttributes().getValue(Name.SPECIFICATION_VERSION); } String getSpecificationVendor() { return mf.getMainAttributes().getValue(Name.SPECIFICATION_VENDOR); } String getImplementationTitle() { return mf.getMainAttributes().getValue(Name.IMPLEMENTATION_TITLE); } String getImplementationVersion() { return mf.getMainAttributes().getValue(Name.IMPLEMENTATION_VERSION); } String getImplementationVendor() { return mf.getMainAttributes().getValue(Name.IMPLEMENTATION_VENDOR); } URL getSealURL() { String seal = mf.getMainAttributes().getValue(Name.SEALED); if (seal != null) { try { return new URL(seal); } catch (MalformedURLException e) { // Ignore, will return null } } return null; } } // inner class JarFileInfo /** * Inner class with JAR entry information. Keeps JAR file and entry object. */ private static class JarEntryInfo { JarFileInfo jarFileInfo; JarEntry jarEntry; JarEntryInfo(JarFileInfo jarFileInfo, JarEntry jarEntry) { this.jarFileInfo = jarFileInfo; this.jarEntry = jarEntry; } URL getURL() { // used in findResource() and findResources() try { return new URL("jar:file:" + jarFileInfo.jarFile.getName() + "!/" + jarEntry); } catch (MalformedURLException e) { return null; } } String getName() { // used in createTempFile() and loadJar() return jarEntry.getName().replace('/', '_'); } @Override public String toString() { return "JAR: " + jarFileInfo.jarFile.getName() + " ENTRY: " + jarEntry; } /** * Read JAR entry and returns byte array of this JAR entry. This is * a helper method to load JAR entry into temporary file. * * @return byte array for the specified JAR entry * @throws JarClassLoaderException */ byte[] getJarBytes() throws JarClassLoaderException { DataInputStream dis = null; byte[] a_by = null; try { long lSize = jarEntry.getSize(); if (lSize <= 0 || lSize >= Integer.MAX_VALUE) { throw new JarClassLoaderException( "Invalid size " + lSize + " for entry " + jarEntry); } a_by = new byte[(int)lSize]; InputStream is = jarFileInfo.jarFile.getInputStream(jarEntry); dis = new DataInputStream(is); dis.readFully(a_by); } catch (IOException e) { throw new JarClassLoaderException(null, e); } finally { if (dis != null) { try { dis.close(); } catch (IOException e) { } } } return a_by; } } // inner class JarEntryInfo /** * Inner class to handle JarClassLoader exceptions. */ @SuppressWarnings("serial") private static class JarClassLoaderException extends Exception { JarClassLoaderException(String sMsg) { super(sMsg); } JarClassLoaderException(String sMsg, Throwable eCause) { super(sMsg, eCause); } String getMessageAll() { StringBuilder sb = new StringBuilder(); for (Throwable e = this; e != null; e = e.getCause()) { if (sb.length() > 0) { sb.append(" / "); } String sMsg = e.getMessage(); if (sMsg == null || sMsg.length() == 0) { sMsg = e.getClass().getSimpleName(); } sb.append(sMsg); } return sb.toString(); } } // inner class JarClassLoaderException } // class JarClassLoader





© 2015 - 2024 Weber Informatics LLC | Privacy Policy