diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java index b4263a1462c..01d560c61db 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,20 +30,28 @@ import org.springframework.boot.loader.archive.Archive.EntryFilter; /** * Base class for executable archive {@link Launcher}s. - * + * * @author Phillip Webb + * @author Andy Wilkinson */ public abstract class ExecutableArchiveLauncher extends Launcher { private final Archive archive; + private final JavaAgentDetector javaAgentDetector; + public ExecutableArchiveLauncher() { + this(new InputArgumentsJavaAgentDetector()); + } + + public ExecutableArchiveLauncher(JavaAgentDetector javaAgentDetector) { try { this.archive = createArchive(); } catch (Exception ex) { throw new IllegalStateException(ex); } + this.javaAgentDetector = javaAgentDetector; } protected final Archive getArchive() { @@ -74,7 +82,9 @@ public abstract class ExecutableArchiveLauncher extends Launcher { ClassLoader loader = getDefaultClassLoader(); if (loader instanceof URLClassLoader) { for (URL url : ((URLClassLoader) loader).getURLs()) { - copy.add(url); + if (!this.javaAgentDetector.isJavaAgentJar(url)) { + copy.add(url); + } } } for (URL url : urls) { diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetector.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetector.java new file mode 100644 index 00000000000..411bff41e5f --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetector.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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 org.springframework.boot.loader; + +import java.io.File; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A {@link JavaAgentDetector} that detects jars supplied via the {@code -javaagent} JVM + * input argument. + * + * @author Andy Wilkinson + * @since 1.1.0 + */ +public class InputArgumentsJavaAgentDetector implements JavaAgentDetector { + + private static final String JAVA_AGENT_PREFIX = "-javaagent:"; + + private final Set javaAgentJars; + + public InputArgumentsJavaAgentDetector() { + this(getInputArguments()); + } + + InputArgumentsJavaAgentDetector(List inputArguments) { + this.javaAgentJars = getJavaAgentJars(inputArguments); + } + + private static List getInputArguments() { + try { + return AccessController.doPrivileged(new PrivilegedAction>() { + @Override + public List run() { + return ManagementFactory.getRuntimeMXBean().getInputArguments(); + } + }); + } + catch (Exception ex) { + return Collections. emptyList(); + } + } + + private Set getJavaAgentJars(List inputArguments) { + Set javaAgentJars = new HashSet(); + for (String argument : inputArguments) { + String path = getJavaAgentJarPath(argument); + if (path != null) { + try { + javaAgentJars.add(new File(path).getCanonicalFile().toURI().toURL()); + } + catch (IOException ex) { + throw new IllegalStateException( + "Failed to determine canonical path of Java agent at path '" + + path + "'"); + } + } + } + return javaAgentJars; + } + + private String getJavaAgentJarPath(String arg) { + if (arg.startsWith(JAVA_AGENT_PREFIX)) { + String path = arg.substring(JAVA_AGENT_PREFIX.length()); + int equalsIndex = path.indexOf('='); + if (equalsIndex > -1) { + path = path.substring(0, equalsIndex); + } + return path; + } + + return null; + } + + @Override + public boolean isJavaAgentJar(URL url) { + return this.javaAgentJars.contains(url); + } + +} diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JavaAgentDetector.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JavaAgentDetector.java new file mode 100644 index 00000000000..6166dfbb87a --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JavaAgentDetector.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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 org.springframework.boot.loader; + +import java.net.URL; + +/** + * A strategy for detecting Java agents + * + * @author Andy Wilkinson + * + * @since 1.1 + */ +public interface JavaAgentDetector { + + /** + * Returns {@code true} if {@code url} points to a Java agent jar file, otherwise + * {@code false} is returned. + * + * @param url The url to examine + */ + public boolean isJavaAgentJar(URL url); +} diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/ExecutableArchiveLauncherTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/ExecutableArchiveLauncherTests.java new file mode 100644 index 00000000000..d987ef0914c --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/ExecutableArchiveLauncherTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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 org.springframework.boot.loader; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.concurrent.Callable; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.boot.loader.archive.Archive.Entry; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link ExecutableArchiveLauncher} + * + * @author Andy Wilkinson + */ +public class ExecutableArchiveLauncherTests { + + @Mock + private JavaAgentDetector javaAgentDetector; + + private ExecutableArchiveLauncher launcher; + + @Before + public void setupMocks() { + MockitoAnnotations.initMocks(this); + + this.launcher = new UnitTestExecutableArchiveLauncher(this.javaAgentDetector); + } + + @Test + public void createdClassLoaderContainsUrlsFromThreadContextClassLoader() + throws Exception { + final URL[] urls = new URL[] { new URL("file:one"), new URL("file:two") }; + + doWithTccl(new URLClassLoader(urls), new Callable() { + + @Override + public Void call() throws Exception { + ClassLoader classLoader = ExecutableArchiveLauncherTests.this.launcher + .createClassLoader(new URL[0]); + assertClassLoaderUrls(classLoader, urls); + return null; + } + }); + } + + @Test + public void javaAgentJarsAreExcludedFromClasspath() throws Exception { + URL javaAgent = new File("my-agent.jar").getCanonicalFile().toURI().toURL(); + final URL one = new URL("file:one"); + + when(this.javaAgentDetector.isJavaAgentJar(javaAgent)).thenReturn(true); + + doWithTccl(new URLClassLoader(new URL[] { javaAgent, one }), new Callable() { + + @Override + public Void call() throws Exception { + ClassLoader classLoader = ExecutableArchiveLauncherTests.this.launcher + .createClassLoader(new URL[0]); + assertClassLoaderUrls(classLoader, new URL[] { one }); + return null; + } + }); + } + + private void assertClassLoaderUrls(ClassLoader classLoader, URL[] urls) { + assertTrue(classLoader instanceof URLClassLoader); + assertArrayEquals(urls, ((URLClassLoader) classLoader).getURLs()); + } + + private static final class UnitTestExecutableArchiveLauncher extends + ExecutableArchiveLauncher { + + public UnitTestExecutableArchiveLauncher(JavaAgentDetector javaAgentDetector) { + super(javaAgentDetector); + } + + @Override + protected boolean isNestedArchive(Entry entry) { + return false; + } + } + + private void doWithTccl(ClassLoader classLoader, Callable action) throws Exception { + ClassLoader old = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(classLoader); + action.call(); + } + finally { + Thread.currentThread().setContextClassLoader(old); + } + } + +} diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetectorTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetectorTests.java new file mode 100644 index 00000000000..f6123d4c527 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetectorTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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 org.springframework.boot.loader; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Arrays; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link InputArgumentsJavaAgentDetector} + * + * @author Andy Wilkinson + */ +public class InputArgumentsJavaAgentDetectorTests { + + @Test + public void nonAgentJarsDoNotProduceFalsePositives() throws MalformedURLException, + IOException { + InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector( + Arrays.asList("-javaagent:my-agent.jar")); + assertFalse(detector.isJavaAgentJar(new File("something-else.jar") + .getCanonicalFile().toURI().toURL())); + } + + @Test + public void singleJavaAgent() throws MalformedURLException, IOException { + InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector( + Arrays.asList("-javaagent:my-agent.jar")); + assertTrue(detector.isJavaAgentJar(new File("my-agent.jar").getCanonicalFile() + .toURI().toURL())); + } + + @Test + public void singleJavaAgentWithOptions() throws MalformedURLException, IOException { + InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector( + Arrays.asList("-javaagent:my-agent.jar=a=alpha,b=bravo")); + assertTrue(detector.isJavaAgentJar(new File("my-agent.jar").getCanonicalFile() + .toURI().toURL())); + } + + @Test + public void multipleJavaAgents() throws MalformedURLException, IOException { + InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector( + Arrays.asList("-javaagent:my-agent.jar", "-javaagent:my-other-agent.jar")); + assertTrue(detector.isJavaAgentJar(new File("my-agent.jar").getCanonicalFile() + .toURI().toURL())); + assertTrue(detector.isJavaAgentJar(new File("my-other-agent.jar") + .getCanonicalFile().toURI().toURL())); + } + +}