diff --git a/spring-cloud-commons/pom.xml b/spring-cloud-commons/pom.xml
index 8f4ab523..d2e27cf4 100644
--- a/spring-cloud-commons/pom.xml
+++ b/spring-cloud-commons/pom.xml
@@ -13,6 +13,10 @@
jar
Spring Cloud Commons
Spring Cloud Commons
+
+ 1.0.2.v20150114
+ 3.3.9
+
@@ -35,7 +39,7 @@
-
+
org.eclipse.m2e
lifecycle-mapping
@@ -135,5 +139,53 @@
spring-boot-starter-test
test
+
+ org.eclipse.aether
+ aether-api
+ ${aether.version}
+ test
+
+
+ org.eclipse.aether
+ aether-connector-basic
+ ${aether.version}
+ test
+
+
+ org.eclipse.aether
+ aether-impl
+ ${aether.version}
+ test
+
+
+ org.eclipse.aether
+ aether-spi
+ ${aether.version}
+ test
+
+
+ org.eclipse.aether
+ aether-transport-file
+ ${aether.version}
+ test
+
+
+ org.eclipse.aether
+ aether-transport-http
+ ${aether.version}
+ test
+
+
+ org.eclipse.aether
+ aether-util
+ ${aether.version}
+ test
+
+
+ org.apache.maven
+ maven-core
+ ${maven.version}
+ test
+
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/ClassPathOverrides.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/ClassPathOverrides.java
new file mode 100644
index 00000000..b74e1bf9
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/ClassPathOverrides.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012-2017 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.cloud;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used in combination with {@link ModifiedClassPathRunner} to override entries
+ * on the classpath.
+ *
+ * @author Andy Wilkinson
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ClassPathOverrides {
+
+ /**
+ * One or more sets of Maven coordinates ({@code groupId:artifactId:version}) to be
+ * added to the classpath. The additions will take precedence over any existing
+ * classes on the classpath.
+ * @return the coordinates
+ */
+ String[] value();
+
+}
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/FilteredClassPathRunner.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/FilteredClassPathRunner.java
deleted file mode 100644
index 2f432a54..00000000
--- a/spring-cloud-commons/src/test/java/org/springframework/cloud/FilteredClassPathRunner.java
+++ /dev/null
@@ -1,220 +0,0 @@
-package org.springframework.cloud;
-
-import java.io.File;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.jar.Attributes;
-import java.util.jar.JarFile;
-
-import org.junit.runners.BlockJUnit4ClassRunner;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.InitializationError;
-import org.junit.runners.model.TestClass;
-
-import org.springframework.core.annotation.AnnotationUtils;
-import org.springframework.util.AntPathMatcher;
-import org.springframework.util.StringUtils;
-
-/**
- * Taken from Spring Boot test utils.
- * https://github.com/spring-projects/spring-boot/blob/1.4.x/spring-boot/src/test/java/org/springframework/boot/testutil/FilteredClassPathRunner.java
- * @author Ryan Baxter
- */
-public class FilteredClassPathRunner extends BlockJUnit4ClassRunner {
- public FilteredClassPathRunner(Class> testClass) throws InitializationError {
- super(testClass);
- }
-
- @Override
- protected TestClass createTestClass(Class> testClass) {
- try {
- ClassLoader classLoader = createTestClassLoader(testClass);
- return new FilteredTestClass(classLoader, testClass.getName());
- }
- catch (Exception ex) {
- throw new IllegalStateException(ex);
- }
- }
-
- private URLClassLoader createTestClassLoader(Class> testClass) throws Exception {
- URLClassLoader classLoader = (URLClassLoader) this.getClass().getClassLoader();
- return new FilteredClassLoader(filterUrls(extractUrls(classLoader), testClass),
- classLoader.getParent(), classLoader);
- }
-
- private URL[] extractUrls(URLClassLoader classLoader) throws Exception {
- List extractedUrls = new ArrayList();
- for (URL url : classLoader.getURLs()) {
- if (isSurefireBooterJar(url)) {
- extractedUrls.addAll(extractUrlsFromManifestClassPath(url));
- }
- else {
- extractedUrls.add(url);
- }
- }
- return extractedUrls.toArray(new URL[extractedUrls.size()]);
- }
-
- private boolean isSurefireBooterJar(URL url) {
- return url.getPath().contains("surefirebooter");
- }
-
- private List extractUrlsFromManifestClassPath(URL booterJar) throws Exception {
- List urls = new ArrayList();
- for (String entry : getClassPath(booterJar)) {
- urls.add(new URL(entry));
- }
- return urls;
- }
-
- private String[] getClassPath(URL booterJar) throws Exception {
- JarFile jarFile = new JarFile(new File(booterJar.toURI()));
- try {
- return StringUtils.delimitedListToStringArray(jarFile.getManifest()
- .getMainAttributes().getValue(Attributes.Name.CLASS_PATH), " ");
- }
- finally {
- jarFile.close();
- }
- }
-
- private URL[] filterUrls(URL[] urls, Class> testClass) throws Exception {
- ClassPathEntryFilter filter = new ClassPathEntryFilter(testClass);
- List filteredUrls = new ArrayList();
- for (URL url : urls) {
- if (!filter.isExcluded(url)) {
- filteredUrls.add(url);
- }
- }
- return filteredUrls.toArray(new URL[filteredUrls.size()]);
- }
-
- /**
- * Filter for class path entries.
- */
- private static final class ClassPathEntryFilter {
-
- private final List exclusions;
-
- private final AntPathMatcher matcher = new AntPathMatcher();
-
- private ClassPathEntryFilter(Class> testClass) throws Exception {
- ClassPathExclusions exclusions = AnnotationUtils.findAnnotation(testClass,
- ClassPathExclusions.class);
- this.exclusions = exclusions == null ? Collections.emptyList()
- : Arrays.asList(exclusions.value());
- }
-
- private boolean isExcluded(URL url) throws Exception {
- if (!"file".equals(url.getProtocol())) {
- return false;
- }
- String name = new File(url.toURI()).getName();
- for (String exclusion : this.exclusions) {
- if (this.matcher.match(exclusion, name)) {
- return true;
- }
- }
- return false;
- }
- }
-
- /**
- * Filtered version of JUnit's {@link TestClass}.
- */
- private static final class FilteredTestClass extends TestClass {
-
- private final ClassLoader classLoader;
-
- FilteredTestClass(ClassLoader classLoader, String testClassName)
- throws ClassNotFoundException {
- super(classLoader.loadClass(testClassName));
- this.classLoader = classLoader;
- }
-
- @Override
- public List getAnnotatedMethods(
- Class extends Annotation> annotationClass) {
- try {
- return getAnnotatedMethods(annotationClass.getName());
- }
- catch (ClassNotFoundException ex) {
- throw new RuntimeException(ex);
- }
- }
-
- @SuppressWarnings("unchecked")
- private List getAnnotatedMethods(String annotationClassName)
- throws ClassNotFoundException {
- Class extends Annotation> annotationClass = (Class extends Annotation>) this.classLoader
- .loadClass(annotationClassName);
- List methods = super.getAnnotatedMethods(annotationClass);
- return wrapFrameworkMethods(methods);
- }
-
- private List wrapFrameworkMethods(
- List methods) {
- List wrapped = new ArrayList(
- methods.size());
- for (FrameworkMethod frameworkMethod : methods) {
- wrapped.add(new FilteredFrameworkMethod(this.classLoader,
- frameworkMethod.getMethod()));
- }
- return wrapped;
- }
-
- }
-
- /**
- * Filtered version of JUnit's {@link FrameworkMethod}.
- */
- private static final class FilteredFrameworkMethod extends FrameworkMethod {
-
- private final ClassLoader classLoader;
-
- private FilteredFrameworkMethod(ClassLoader classLoader, Method method) {
- super(method);
- this.classLoader = classLoader;
- }
-
- @Override
- public Object invokeExplosively(Object target, Object... params)
- throws Throwable {
- ClassLoader originalClassLoader = Thread.currentThread()
- .getContextClassLoader();
- Thread.currentThread().setContextClassLoader(this.classLoader);
- try {
- return super.invokeExplosively(target, params);
- }
- finally {
- Thread.currentThread().setContextClassLoader(originalClassLoader);
- }
- }
-
- }
-
- private static final class FilteredClassLoader extends URLClassLoader {
-
- private final ClassLoader junitLoader;
-
- FilteredClassLoader(URL[] urls, ClassLoader parent, ClassLoader junitLoader) {
- super(urls, parent);
- this.junitLoader = junitLoader;
- }
-
- @Override
- public Class> loadClass(String name) throws ClassNotFoundException {
- if (name.startsWith("org.junit")) {
- return this.junitLoader.loadClass(name);
- }
- return super.loadClass(name);
- }
-
- }
-}
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/ModifiedClassPathRunner.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/ModifiedClassPathRunner.java
new file mode 100644
index 00000000..669fc904
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/ModifiedClassPathRunner.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2012-2017 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.cloud;
+
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.impl.DefaultServiceLocator;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.resolution.DependencyResult;
+import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
+import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.transport.http.HttpTransporterFactory;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.TestClass;
+
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.StringUtils;
+
+/**
+ * A custom {@link BlockJUnit4ClassRunner} that runs tests using a modified class path.
+ * Entries are excluded from the class path using {@link ClassPathExclusions} and
+ * overridden using {@link ClassPathOverrides} on the test class. A class loader is
+ * created with the customized class path and is used both to load the test class and as
+ * the thread context class loader while the test is being run.
+ *
+ * @author Andy Wilkinson
+ */
+public class ModifiedClassPathRunner extends BlockJUnit4ClassRunner {
+
+ public ModifiedClassPathRunner(Class> testClass) throws InitializationError {
+ super(testClass);
+ }
+
+ @Override
+ protected TestClass createTestClass(Class> testClass) {
+ try {
+ ClassLoader classLoader = createTestClassLoader(testClass);
+ return new ModifiedClassPathTestClass(classLoader, testClass.getName());
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ @Override
+ protected Object createTest() throws Exception {
+ ModifiedClassPathTestClass testClass = (ModifiedClassPathTestClass) getTestClass();
+ return testClass.doWithModifiedClassPathThreadContextClassLoader(
+ new ModifiedClassPathTestClass.ModifiedClassPathTcclAction