diff --git a/capsule/src/main/java/Capsule.java b/capsule/src/main/java/Capsule.java index 813de13..31e7399 100644 --- a/capsule/src/main/java/Capsule.java +++ b/capsule/src/main/java/Capsule.java @@ -87,7 +87,6 @@ import javax.management.MBeanServerConnection; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; -import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXServiceURL; import static java.util.Collections.*; @@ -1072,14 +1071,16 @@ public boolean isAgent() { // /////////// Capsule JAR /////////////////////////////////// - private static Path findOwnJarFile() { - final URL url = MY_CLASSLOADER.getResource(Capsule.class.getName().replace('.', '/') + ".class"); - assert url != null; + private static Path findJarFile(Class capsuleClass) { + assert capsuleClass != null; + final URL url = MY_CLASSLOADER.getResource(capsuleClass.getName().replace('.', '/') + ".class"); + if (url == null) // Could happen with embedded capsules + throw new IllegalStateException("The " + capsuleClass + " class must be in a JAR file, but was not found"); if (!"jar".equals(url.getProtocol())) - throw new IllegalStateException("The Capsule class must be in a JAR file, but was loaded from: " + url); + throw new IllegalStateException("The " + capsuleClass + " class must be in a JAR file, but was loaded from: " + url); final String path = url.getPath(); if (path == null) // || !path.startsWith("file:") - throw new IllegalStateException("The Capsule class must be in a local JAR file, but was loaded from: " + url); + throw new IllegalStateException("The " + capsuleClass + " class must be in a local JAR file, but was loaded from: " + url); try { final URI jarUri = new URI(path.substring(0, path.indexOf('!'))); @@ -1089,6 +1090,10 @@ private static Path findOwnJarFile() { } } + private static Path findOwnJarFile() { + return findJarFile(Capsule.class); + } + private String toJarUrl(String relPath) { return "jar:file:" + getJarFile().toAbsolutePath() + "!/" + relPath; } @@ -1656,8 +1661,12 @@ private T agentAttributes(Entry attr, T value) { if (ATTR_JAVA_AGENTS == attr) { // add the capsule as an agent final Map agents = new LinkedHashMap<>(cast(ATTR_JAVA_AGENTS, value)); - assert isWrapperCapsule() ^ findOwnJarFile().equals(getJarFile()); - agents.put(processOutgoingPath(findOwnJarFile()), isWrapperCapsule() ? processOutgoingPath(getJarFile()) : ""); + Path ownJar = null; + try { + ownJar = findOwnJarFile(); + } catch (final IllegalStateException ignored) {} + if (ownJar != null) + agents.put(processOutgoingPath(ownJar), isWrapperCapsule() ? processOutgoingPath(getJarFile()) : ""); return (T) agents; } @@ -2281,9 +2290,9 @@ private List buildClassPath0(List classPath0) { final long start = clock(); final List classPath = new ArrayList<>(); - // the capsule jar + // The caplets and capsule jars if (!isWrapperOfNonCapsule() && getAttribute(ATTR_CAPSULE_IN_CLASS_PATH)) - classPath.add(getJarFile()); + addCapsuleJars(classPath); if (hasAttribute(ATTR_APP_ARTIFACT)) { final String artifact = getAttribute(ATTR_APP_ARTIFACT); @@ -2298,12 +2307,30 @@ private List buildClassPath0(List classPath0) { classPath.addAll(nullToEmpty(getAttribute(ATTR_DEPENDENCIES))); - // the capsule jar + // The caplets and capsule jars if (!isWrapperOfNonCapsule() && isDeepEmpty(classPath)) - classPath.add(getJarFile()); + addCapsuleJars(classPath); + + // Remove duplicate JARs while preserving order + final List ret = new ArrayList<>(new LinkedHashSet<>(classPath)); time("buildClassPath", start); - return classPath; + + return ret; + } + + private void addCapsuleJars(List classPath) { + Capsule c = this.cc; + do { + Path p = null; + try { + p = findJarFile(c.getClass()); + } catch (final IllegalStateException ignored) {} // Ignore non-JARs + if (p != null && !classPath.contains(p)) + classPath.add(p); + } while ((c = sup) != null); + + classPath.add(getJarFile()); } /** diff --git a/capsule/src/test/java/CapsuleAgentTest.java b/capsule/src/test/java/CapsuleAgentTest.java new file mode 100644 index 0000000..9e0e68a --- /dev/null +++ b/capsule/src/test/java/CapsuleAgentTest.java @@ -0,0 +1,85 @@ +/* + * Capsule + * Copyright (c) 2014-2015, Parallel Universe Software Co. All rights reserved. + * + * This program and the accompanying materials are licensed under the terms + * of the Eclipse Public License v1.0, available at + * http://www.eclipse.org/legal/epl-v10.html + */ +import capsule.test.Pair; +import co.paralleluniverse.capsule.Jar; +import co.paralleluniverse.capsule.test.CapsuleTestUtils; +import co.paralleluniverse.common.FlexibleClassLoader; +import co.paralleluniverse.common.JarClassLoader; +import co.paralleluniverse.common.PathClassLoader; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author circlespainter + */ +public class CapsuleAgentTest { + @Test + public void testWrapperCapsuleAgent() throws Exception { + final Jar wrapper = new Jar() + .setAttribute("Manifest-Version", "1.0") + .setAttribute("Main-Class", "Capsule") + .setAttribute("Premain-Class", "Capsule") + .setAttribute("Capsule-Agent", "true") + .setAttribute("Caplets", "MyCapsule") + .addClass(TestCapsule.class) + .addClass(Pair.class) + .addClass(JarClassLoader.class) + .addClass(PathClassLoader.class) + .addClass(FlexibleClassLoader.class) + .addClass(MyCapsule.class) + .addClass(Capsule.class); + + final Jar app = new Jar() + .setAttribute("Manifest-Version", "1.0") + .setAttribute("Main-Class", Capsule.class.getName()) + .setAttribute("Premain-Class", Capsule.class.getName()) + .setAttribute("Capsule-Agent", "true") + .setAttribute("Application-Class", MainTest.class.getName()) + .addClass(Capsule.class) + .addClass(MainTest.class); + + final Path wrapperPath = Files.createTempFile("capsule-agent-test-wrapper", ".jar"); + CapsuleTestUtils.newCapsule(wrapper, wrapperPath); // Create + final Path appPath = Files.createTempFile("capsule-agent-test-app", ".jar"); + CapsuleTestUtils.newCapsule(app, appPath); // Create + final Path cacheDir = Files.createTempDirectory("capsule-agent-test-cache"); + + final ProcessBuilder pb = new ProcessBuilder("java", "-jar", wrapperPath.toString(), appPath.toString()); + pb.environment().put("CAPSULE_CACHE_DIR", cacheDir.toAbsolutePath().toString()); + assertEquals(0, pb.start().waitFor()); + + final Path appCache = cacheDir.resolve("apps").resolve(MainTest.class.getName()); + assertTrue(Files.exists(appCache.resolve(MyCapsule.WRAPPER_AGENT_OK_FNAME))); + + Files.delete(wrapperPath); + Files.delete(appPath); + Files.walkFileTree(cacheDir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } +} diff --git a/capsule/src/test/java/CapsuleTest.java b/capsule/src/test/java/CapsuleTest.java index 87b6581..cbe71f9 100644 --- a/capsule/src/test/java/CapsuleTest.java +++ b/capsule/src/test/java/CapsuleTest.java @@ -11,6 +11,7 @@ import co.paralleluniverse.capsule.test.CapsuleTestUtils; import co.paralleluniverse.capsule.test.CapsuleTestUtils.StringPrintStream; import static co.paralleluniverse.capsule.test.CapsuleTestUtils.*; + import co.paralleluniverse.common.ZipFS; import com.google.common.jimfs.Jimfs; import java.io.IOException; @@ -41,7 +42,6 @@ import static com.google.common.truth.Truth.*; import java.nio.file.Paths; import org.joor.Reflect; -import org.junit.Ignore; //import static org.mockito.Mockito.*; public class CapsuleTest { @@ -1206,14 +1206,14 @@ public void testMerge() throws Exception { .addEntry("META-INF/x.txt", emptyInputStream()); Class capsuleClass = loadCapsule(wrapper); - setProperties(capsuleClass, props); +// setProperties(capsuleClass, props); Path fooPath = mockDep(capsuleClass, "com.acme:foo", "jar", "com.acme:foo:1.0").get(0); Files.createDirectories(fooPath.getParent()); app.write(fooPath); props.setProperty("capsule.merge", "out.jar"); - props.setProperty("capsule.log", "verbose"); +// props.setProperty("capsule.log", "verbose"); int exit = main0(capsuleClass, "com.acme:foo"); @@ -1556,7 +1556,8 @@ private Jar newCapsuleJar() { return new Jar() .setAttribute("Manifest-Version", "1.0") .setAttribute("Main-Class", "TestCapsule") - .setAttribute("Premain-Class", "TestCapsule"); + .setAttribute("Premain-Class", "TestCapsule") + .setAttribute("Capsule-Agent", "true"); } private Jar makeRealCapsuleJar(Jar jar) throws IOException { diff --git a/capsule/src/test/java/MainTest.java b/capsule/src/test/java/MainTest.java new file mode 100644 index 0000000..e80b875 --- /dev/null +++ b/capsule/src/test/java/MainTest.java @@ -0,0 +1,10 @@ +import java.util.Arrays; + +/** + * Created by fabio on 9/29/15. + */ +public class MainTest { + public static void main(String[] args) { + System.out.print(Arrays.toString(args)); + } +} diff --git a/capsule/src/test/java/MyCapsule.java b/capsule/src/test/java/MyCapsule.java index 778ee79..5b125ab 100644 --- a/capsule/src/test/java/MyCapsule.java +++ b/capsule/src/test/java/MyCapsule.java @@ -7,6 +7,9 @@ * http://www.eclipse.org/legal/epl-v10.html */ +import java.io.IOException; +import java.lang.instrument.Instrumentation; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -17,6 +20,8 @@ * A custom capsule example */ public class MyCapsule extends TestCapsule { + public static final String WRAPPER_AGENT_OK_FNAME = "startJMXServer-specialized.empty"; + public MyCapsule(Path jarFile) { super(jarFile); } @@ -52,6 +57,16 @@ else if (arg.startsWith("-Xms")) return super.attribute(attr); //To change body of generated methods, choose Tools | Templates. } + @Override + protected void agent(Instrumentation inst) { + try { + Files.createFile(getWritableAppCache().resolve(WRAPPER_AGENT_OK_FNAME)); + } catch (final IOException e) { + throw new RuntimeException(e); + } + super.agent(inst); + } + @Override protected List buildArgs(List args) { return super.buildArgs(args); diff --git a/capsule/src/test/java/TestCapsule.java b/capsule/src/test/java/TestCapsule.java index 24621e9..7852f1e 100644 --- a/capsule/src/test/java/TestCapsule.java +++ b/capsule/src/test/java/TestCapsule.java @@ -51,7 +51,7 @@ public static void reset() { public static void mock(String coords, String type, List paths) { if (DEPS == null) DEPS = new HashMap<>(); - DEPS.put(new Pair(coords, type), paths); + DEPS.put(new Pair<>(coords, type), paths); } @Override @@ -68,7 +68,7 @@ protected Object lookup0(Object o, String type, Map.Entry attrContext if (o instanceof String) { String x = (String) o; if (x.contains(":")) - o = new Pair(x, type.isEmpty() ? "jar" : type); + o = new Pair<>(x, type.isEmpty() ? "jar" : type); } return super.lookup0(o, type, attrContext, context); //To change body of generated methods, choose Tools | Templates. }