From e5bd7b08faae89c127f8fabf5e09657f2b81de98 Mon Sep 17 00:00:00 2001 From: Arne Georg Gleditsch Date: Tue, 17 Jun 2014 16:05:41 +0200 Subject: [PATCH 1/2] Ensure that logging subsystem isn't initialized before EmJar class loader is active. --- .../src/main/java/com/comoyo/emjar/Boot.java | 24 ++-- .../com/comoyo/emjar/EmJarClassLoader.java | 67 ++++++++--- .../test/java/com/comoyo/emjar/BootTest.java | 110 ++++++++++++++++++ .../comoyo/emjar/EmJarClassLoaderTest.java | 5 +- 4 files changed, 178 insertions(+), 28 deletions(-) create mode 100644 emjar/src/test/java/com/comoyo/emjar/BootTest.java diff --git a/emjar/src/main/java/com/comoyo/emjar/Boot.java b/emjar/src/main/java/com/comoyo/emjar/Boot.java index b68de8a..a6bbf80 100644 --- a/emjar/src/main/java/com/comoyo/emjar/Boot.java +++ b/emjar/src/main/java/com/comoyo/emjar/Boot.java @@ -58,6 +58,13 @@ public class Boot public static void main(String[] args) throws Exception + { + final Method mainMethod = findMain(System.getProperties()); + mainMethod.invoke(null, new Object[]{args}); + } + + public static Method findMain(Properties props) + throws Exception { final EmJarClassLoader loader = new EmJarClassLoader(ClassLoader.getSystemClassLoader()); Thread.currentThread().setContextClassLoader(loader); @@ -73,13 +80,16 @@ public static void main(String[] args) } catch (NoSuchFieldException e) { // Unable to set system class loader; continuing anyway + if (!EmJarClassLoader.QUIET) { + System.err.println("EmJar: unable to replace system class loader: " + + e.getMessage()); + } } final String systemPropsName - = System.getProperty(EMJAR_SYSTEM_PROPS_PROP, - getManifestAttribute(EMJAR_SYSTEM_PROPS_ATTR)); + = props.getProperty(EMJAR_SYSTEM_PROPS_PROP, + getManifestAttribute(EMJAR_SYSTEM_PROPS_ATTR)); if (systemPropsName != null) { - final Properties props = System.getProperties(); final InputStream is = loader.getResourceAsStream(systemPropsName); if (is != null) { props.load(new InputStreamReader(is)); @@ -87,8 +97,8 @@ public static void main(String[] args) } } final String mainClassName - = System.getProperty(EMJAR_MAIN_CLASS_PROP, - getManifestAttribute(EMJAR_MAIN_CLASS_ATTR)); + = props.getProperty(EMJAR_MAIN_CLASS_PROP, + getManifestAttribute(EMJAR_MAIN_CLASS_ATTR)); if (mainClassName == null) { throw new RuntimeException( "No main class specified using " @@ -97,9 +107,7 @@ public static void main(String[] args) final Class mainClass = Class.forName(mainClassName, false, loader); - final Method mainMethod - = mainClass.getDeclaredMethod("main", new Class[]{String[].class}); - mainMethod.invoke(null, new Object[]{args}); + return mainClass.getDeclaredMethod("main", String[].class); } private static String getManifestAttribute(String key) diff --git a/emjar/src/main/java/com/comoyo/emjar/EmJarClassLoader.java b/emjar/src/main/java/com/comoyo/emjar/EmJarClassLoader.java index 1a04f32..b39f5fc 100644 --- a/emjar/src/main/java/com/comoyo/emjar/EmJarClassLoader.java +++ b/emjar/src/main/java/com/comoyo/emjar/EmJarClassLoader.java @@ -17,6 +17,7 @@ package com.comoyo.emjar; import java.net.JarURLConnection; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -31,8 +32,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import java.util.logging.Level; -import java.util.logging.Logger; import java.io.File; import java.io.IOException; @@ -71,9 +70,14 @@ public class EmJarClassLoader extends URLClassLoader { + public final static String EMJAR_LOG_QUIET_PROP = "emjar.log.quiet"; + public final static String EMJAR_LOG_DEBUG_PROP = "emjar.log.debug"; + + protected static boolean DEBUG = false; + protected static boolean QUIET = false; + public final static String SEPARATOR = "!/"; - private final static Logger logger = Logger.getLogger(EmJarClassLoader.class.getName()); private final static HandlerFactory factory = new HandlerFactory(); private final static Handler handler = new Handler(); @@ -103,37 +107,59 @@ protected EmJarClassLoader(Properties props) private static URL[] getClassPath(final Properties props) { final String classPath = props.getProperty("java.class.path"); - final String pathSep = props.getProperty("path.separator"); - final String fileSep = props.getProperty("file.separator"); - final String userDir = props.getProperty("user.dir"); + QUIET = "true".equalsIgnoreCase(props.getProperty(EMJAR_LOG_QUIET_PROP, "")); + DEBUG = "true".equalsIgnoreCase(props.getProperty(EMJAR_LOG_DEBUG_PROP, "")); final ArrayList urls = new ArrayList<>(); - for (String elem : classPath.split(pathSep)) { - if (!elem.endsWith(".jar")) { - continue; - } - final String full = elem.startsWith(fileSep) ? elem : userDir + fileSep + elem; + for (String elem : classPath.split(File.pathSeparator)) { + final File file = new File(elem); try { - urls.add(new URI("file", full, null).toURL()); - final JarFile jar = new JarFile(elem); + urls.add(file.toURI().toURL()); + if (!file.isFile() || !file.getName().endsWith(".jar")) { + continue; + } + final JarFile jar = new JarFile(file); final Enumeration embedded = jar.entries(); while (embedded.hasMoreElements()) { final JarEntry entry = embedded.nextElement(); if (entry.getName().endsWith(".jar")) { - final URL url = new URI("jar:file", full + SEPARATOR + entry.getName(), null).toURL(); - urls.add(new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile(), handler)); + final URI nested = new URI( + "jar:file", + file.getAbsolutePath() + SEPARATOR + entry.getName(), + null); + urls.add(uriToUrl(nested, handler)); } } jar.close(); } catch (IOException|URISyntaxException e) { - logger.log(Level.SEVERE, "Unable to process classpath entry " + elem, e); + if (!QUIET) { + System.err.println("EmJar: unable to process classpath entry " + elem); + } + if (DEBUG) { + e.printStackTrace(System.err); + } // Trying to get by on the classpath entries we can process. } } + if (DEBUG) { + System.err.println("EmJar: using classpath " + urls); + } return urls.toArray(new URL[0]); } + private static URL uriToUrl(URI uri, Handler handler) + throws MalformedURLException + { + final URL url = uri.toURL(); + return new URL( + url.getProtocol(), + url.getHost(), + url.getPort(), + url.getFile(), + handler); + } + @Override public Class loadClass(String name) throws ClassNotFoundException @@ -196,6 +222,15 @@ protected URLConnection openConnection(URL url) catch (URISyntaxException e) { throw new IOException(e); } + catch (IOException e) { + if (DEBUG) { + System.err.println("EmJar: " + e.getMessage()); + } + throw e; + } + if (DEBUG) { + System.err.println("EmJar: loading " + path); + } JarURLConnection conn = connections.get(path); if (conn == null) { synchronized (connections) { diff --git a/emjar/src/test/java/com/comoyo/emjar/BootTest.java b/emjar/src/test/java/com/comoyo/emjar/BootTest.java new file mode 100644 index 0000000..20016c4 --- /dev/null +++ b/emjar/src/test/java/com/comoyo/emjar/BootTest.java @@ -0,0 +1,110 @@ +/** + * Copyright (C) 2014 Telenor Digital AS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.comoyo.emjar; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class BootTest +{ + private boolean mainCalled = false; + + @Test + public void testBoot() + throws Exception + { + final LoggingClassLoader loader = new LoggingClassLoader(); + Thread.currentThread().setContextClassLoader(loader); + final Properties props = new Properties(); + props.setProperty(Boot.EMJAR_MAIN_CLASS_PROP, BootTest.class.getName()); + final Class bootClass = Class.forName(Boot.class.getName(), false, loader); + final Method bootMain = bootClass.getDeclaredMethod("findMain", Properties.class); + final Object result = bootMain.invoke(null, new Object[]{props}); + assertTrue("findMain did not return a Method object", + result instanceof Method); + final Method realMain = (Method) result; + realMain.invoke(this, new Object[]{new String[0]}); + assertTrue("invocation of findMain returned method did not succeed", + mainCalled); + final List loaded = loader.getLoaded(); + for (String name : loaded) { + if (name.startsWith("java.util.logging")) { + fail("Boot accessed logging packages during initialization: " + name); + } + if (!name.startsWith("java.") + && !name.startsWith(BootTest.class.getPackage().getName())) + { + fail("Boot accessed non-standard packages during initialization: " + name); + } + } + } + + public void main(String[] args) + throws Exception + { + mainCalled = true; + } + + static class LoggingClassLoader extends URLClassLoader + { + private final LinkedList loaded = new LinkedList<>(); + + public LoggingClassLoader() + throws Exception + { + super(defaultClassPath(), null); + } + + private static URL[] defaultClassPath() + throws Exception + { + final String classPath = System.getProperties().getProperty("java.class.path"); + final ArrayList urls = new ArrayList<>(); + for (String elem : classPath.split(File.pathSeparator)) { + final File file = new File(elem); + urls.add(file.toURI().toURL()); + } + return urls.toArray(new URL[0]); + } + + @Override + public Class loadClass(String name) + throws ClassNotFoundException + { + loaded.add(name); + return super.loadClass(name); + } + + public List getLoaded() + { + return loaded; + } + } +} diff --git a/emjar/src/test/java/com/comoyo/emjar/EmJarClassLoaderTest.java b/emjar/src/test/java/com/comoyo/emjar/EmJarClassLoaderTest.java index 357a6fe..06058da 100644 --- a/emjar/src/test/java/com/comoyo/emjar/EmJarClassLoaderTest.java +++ b/emjar/src/test/java/com/comoyo/emjar/EmJarClassLoaderTest.java @@ -53,10 +53,7 @@ public boolean accept(File dir, String name) { final File allJars[] = bundle.getParentFile().listFiles(jarFilter); final Properties props = new Properties(); - props.setProperty("java.class.path", Joiner.on(":").join(allJars)); - props.setProperty("path.separator", ":"); - props.setProperty("file.separator", "/"); - props.setProperty("user.dir", "/tmp"); + props.setProperty("java.class.path", Joiner.on(File.pathSeparator).join(allJars)); return new EmJarClassLoader(props); } From 990278772e15b37d1f48c3c2fa5e52034d6a5512 Mon Sep 17 00:00:00 2001 From: Arne Georg Gleditsch Date: Tue, 17 Jun 2014 22:02:14 +0200 Subject: [PATCH 2/2] Bump version number in examples. --- emjar-maven-plugin/README.md | 4 ++-- protoc-bundled-plugin/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/emjar-maven-plugin/README.md b/emjar-maven-plugin/README.md index a784a89..d8e5334 100644 --- a/emjar-maven-plugin/README.md +++ b/emjar-maven-plugin/README.md @@ -46,7 +46,7 @@ Maven Plugin Mojo for building bundling jars that contain dependency artifact ja com.comoyo.commons emjar-maven-plugin - 1.4.44 + 1.4.46 @@ -67,7 +67,7 @@ Maven Plugin Mojo for building bundling jars that contain dependency artifact ja com.comoyo.commons emjar-maven-plugin - 1.4.44 + 1.4.46 diff --git a/protoc-bundled-plugin/README.md b/protoc-bundled-plugin/README.md index fe9cf22..4cd139a 100644 --- a/protoc-bundled-plugin/README.md +++ b/protoc-bundled-plugin/README.md @@ -37,7 +37,7 @@ Maven Plugin Mojo for compiling Protobuf schema files. Protobuf compiler binarie com.comoyo.maven.plugins protoc-bundled-plugin - 1.4.44 + 1.4.46