Fixes java contract runtime compilation; fixes gh-1849

This commit is contained in:
Marcin Grzejszczak
2022-11-21 18:32:32 +01:00
parent ff7cb6364a
commit 3f0fd7a004
3 changed files with 88 additions and 8 deletions

View File

@@ -21,7 +21,7 @@
<configprops.inclusionPattern>stubrunner.*|wiremock.*|</configprops.inclusionPattern>
<upload-docs-zip.phase>deploy</upload-docs-zip.phase>
<!-- Aligned with Groovy in SC-Build -->
<groovy.version>4.0.0</groovy.version>
<groovy.version>4.0.6</groovy.version>
</properties>
<build>
<plugins>

View File

@@ -138,6 +138,49 @@
<failIfNoTests>true</failIfNoTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>process-test-resources</phase>
<configuration>
<target>
<taskdef resource="net/sf/antcontrib/antcontrib.properties" />
<fail message="Generated test files are missing.">
<condition>
<not>
<resourcecount count="6">
<fileset id="fs" dir="${project.build.directory}/generated-test-sources/contracts/com/example/fraud/" includes="BinaryTest.java,FraudnameTest.java,FraudTest.java,MessagingTest.java,MultipartTest.java,TestTest.java"/>
</resourcecount>
</not>
</condition>
</fail>
<fail message="Generated test files are missing.">
<condition>
<not>
<resourcecount count="2">
<fileset id="fs" dir="${project.build.directory}/generated-test-sources/contracts/com/example/fraud/yml" includes="FraudnameTest.java,FraudTest.java"/>
</resourcecount>
</not>
</condition>
</fail>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>ant-contrib</groupId>
<artifactId>ant-contrib</artifactId>
<version>20020829</version>
</dependency>
</dependencies>
</plugin>
</plugins>
<pluginManagement>
<plugins>

View File

@@ -26,10 +26,13 @@ import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.regex.Matcher;
@@ -52,6 +55,7 @@ import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.ContractConverter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Converts a String or a Groovy or Java file into a {@link Contract}.
@@ -140,7 +144,7 @@ public class ContractVerifierDslConverter implements ContractConverter<Collectio
private static Object toObject(ClassLoader cl, File rootFolder, File dsl) throws IOException {
if (isJava(dsl)) {
try {
return parseJavaFile(dsl);
return parseJavaFile(rootFolder, dsl);
}
catch (Exception ex) {
if (LOG.isWarnEnabled()) {
@@ -153,9 +157,9 @@ public class ContractVerifierDslConverter implements ContractConverter<Collectio
return groovyShell(cl, rootFolder).evaluate(dsl);
}
private static Object parseJavaFile(File dsl) throws IllegalAccessException, InvocationTargetException,
InstantiationException, IOException, NoSuchMethodException {
Constructor<?> constructor = classConstructor(dsl);
private static Object parseJavaFile(File rootFolder, File dsl) throws IllegalAccessException,
InvocationTargetException, InstantiationException, IOException, NoSuchMethodException {
Constructor<?> constructor = classConstructor(rootFolder, dsl);
Object newInstance = constructor.newInstance();
if (!(newInstance instanceof Supplier)) {
if (LOG.isDebugEnabled()) {
@@ -167,7 +171,7 @@ public class ContractVerifierDslConverter implements ContractConverter<Collectio
return supplier.get();
}
private static Constructor<?> classConstructor(File dsl)
private static Constructor<?> classConstructor(File rootFolder, File dsl)
throws IllegalAccessException, IOException, NoSuchMethodException {
try (StandardJavaFileManager fileManager = COMPILER.getStandardFileManager(null, null, null)) {
try (Stream<String> lines = Files.lines(Paths.get(dsl.getAbsolutePath()))) {
@@ -175,6 +179,15 @@ public class ContractVerifierDslConverter implements ContractConverter<Collectio
String fqn = fqn(classText);
Path directory = Files.createTempDirectory(fqn);
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(directory.toFile()));
// set compiler's classpath to be same as the runtime's
Set<File> classpathLocations = new HashSet<>();
classpathLocations.add(rootFolder);
appendUrlsFromAllClassLoaders(classpathLocations);
String classPath = System.getProperty("java.class.path", "");
if (StringUtils.hasText(classPath)) {
classpathLocations.addAll(Arrays.stream(classPath.split(":")).map(File::new).toList());
}
fileManager.setLocation(StandardLocation.CLASS_PATH, classpathLocations);
// Compile the file
DiagnosticCollector<Object> diagnostics = new DiagnosticCollector<>();
JavaCompiler.CompilationTask task = COMPILER.getTask(null, fileManager, diagnostics, null, null,
@@ -183,12 +196,16 @@ public class ContractVerifierDslConverter implements ContractConverter<Collectio
if (!success) {
throw new IllegalStateException("Exceptions occurred while trying to compile the file \n"
+ diagnostics.getDiagnostics().stream()
.map(d -> "Error " + d.getMessage(Locale.getDefault()) + "on line "
.map(d -> "Error " + d.getMessage(Locale.getDefault()) + " on line "
+ d.getLineNumber() + " in " + d.getSource())
.collect(Collectors.joining("\n")));
}
try {
Class<?> clazz = ClassUtils.forName(fqn, null);
// Add the folder with compiled classes to the class loader
URLClassLoader urlClassLoader = new URLClassLoader("contract-classloader",
new URL[] { new URL("file://" + directory.toAbsolutePath() + "/") },
Thread.currentThread().getContextClassLoader());
Class<?> clazz = ClassUtils.forName(fqn, urlClassLoader);
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor;
@@ -200,6 +217,26 @@ public class ContractVerifierDslConverter implements ContractConverter<Collectio
}
}
private static void appendUrlsFromAllClassLoaders(Set<File> files) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader != null) {
appendUrlsFromClasspath(files, classLoader);
while (classLoader.getParent() != null) {
classLoader = classLoader.getParent();
appendUrlsFromClasspath(files, classLoader);
}
}
}
private static void appendUrlsFromClasspath(Set<File> files, ClassLoader classLoader) {
if (classLoader instanceof URLClassLoader urlClassLoader) {
URL[] urLs = urlClassLoader.getURLs();
if (urLs.length > 0) {
Arrays.stream(urLs).forEach(url -> files.add(new File(url.getFile())));
}
}
}
private static boolean isJava(File dsl) {
return dsl.getName().endsWith(".java");
}