Attempt to keep Kotlin totally off the classpath (#1558)

* Attempt to keep Kotlin totally off the classpath

* Add version header for new class

* Fix a few bugs with the samples and provide better debugging output for javaexec

* Use main consistently

* Fix Gradle's published pom and use maven-publish plugin consistently throughout plugin and samples

* Re-add groovydoc jar

* Have assemble create the groovydoc jar

* Switch to publishToMavenLocal

* Sync dependencies with pom.xml and fix null provider for Gradle versions less than 6.2

* Perform quoting conditionally for Windows only

* Add jsch, aether, and shaded libraries to round out compatibility

* Ensure contract tests are generated before kotlin compile task
This commit is contained in:
Shannon Pamperl
2020-12-03 03:24:55 -06:00
committed by GitHub
parent a3268f908a
commit 7804933b21
25 changed files with 644 additions and 275 deletions

View File

@@ -16,7 +16,9 @@
package org.springframework.cloud.contract.verifier.plugin;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.net.URI;
import java.util.Collection;
import java.util.Map;
@@ -27,14 +29,17 @@ import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.util.io.NullOutputStream;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.Internal;
@@ -53,7 +58,6 @@ import org.springframework.cloud.contract.stubrunner.StubRunnerOptions;
import org.springframework.cloud.contract.stubrunner.StubRunnerOptionsBuilder;
import org.springframework.cloud.contract.stubrunner.StubRunnerPropertyUtils;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
import org.springframework.cloud.contract.verifier.converter.ToYamlConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
@@ -103,6 +107,8 @@ class ContractsCopyTask extends DefaultTask {
*/
private final Property<Boolean> deleteStubsAfterTest;
private final ConfigurableFileCollection classpath;
// outputs
private final DirectoryProperty copiedContractsFolder;
@@ -129,6 +135,7 @@ class ContractsCopyTask extends DefaultTask {
contractsPath = objects.property(String.class);
excludeBuildFolders = objects.property(Boolean.class);
deleteStubsAfterTest = objects.property(Boolean.class);
classpath = objects.fileCollection();
copiedContractsFolder = objects.directoryProperty();
backupContractsFolder = objects.directoryProperty();
@@ -173,7 +180,22 @@ class ContractsCopyTask extends DefaultTask {
private void convertContractsToYaml(File file, String antPattern, String slashSeparatedAntPattern,
File outputContractsFolder, boolean excludeBuildFolders) {
sync(file, antPattern, slashSeparatedAntPattern, excludeBuildFolders, backupContractsFolder.get().getAsFile());
ToYamlConverter.replaceContractWithYaml(outputContractsFolder);
OutputStream os;
if (getLogger().isDebugEnabled()) {
os = new ByteArrayOutputStream();
} else {
os = NullOutputStream.INSTANCE;
}
getProject().javaexec(exec -> {
exec.setMain("org.springframework.cloud.contract.verifier.converter.ToYamlConverterApplication");
exec.classpath(classpath);
exec.args(quoteAndEscape(outputContractsFolder.getAbsolutePath()));
exec.setStandardOutput(os);
exec.setErrorOutput(os);
});
if (getLogger().isDebugEnabled()) {
getLogger().debug(os.toString());
}
getLogger().info("Replaced DSL files with their YAML representation at [{}]", outputContractsFolder);
}
@@ -434,6 +456,11 @@ class ContractsCopyTask extends DefaultTask {
return deleteStubsAfterTest;
}
@Classpath
ConfigurableFileCollection getClasspath() {
return classpath;
}
@OutputDirectory
DirectoryProperty getCopiedContractsFolder() {
return copiedContractsFolder;
@@ -502,4 +529,12 @@ class ContractsCopyTask extends DefaultTask {
return null;
}
// See: https://github.com/gradle/gradle/issues/6072
private String quoteAndEscape(String str) {
if (System.getProperty("os.name").contains("Windows")) {
return "\"" + str.replace("\"", "\\\"") + "\"";
}
return str;
}
}

View File

@@ -16,18 +16,22 @@
package org.springframework.cloud.contract.verifier.plugin;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.List;
import java.io.OutputStream;
import javax.inject.Inject;
import org.eclipse.jgit.util.io.NullOutputStream;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.OutputDirectory;
@@ -35,7 +39,7 @@ import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SkipWhenEmpty;
import org.gradle.api.tasks.TaskAction;
import org.springframework.cloud.contract.verifier.converter.RecursiveFilesConverter;
import org.springframework.util.StringUtils;
//TODO: Implement as an incremental task: https://gradle.org/docs/current/userguide/custom_tasks.html#incremental_tasks ?
/**
@@ -52,19 +56,22 @@ class GenerateClientStubsFromDslTask extends DefaultTask {
static final String TASK_NAME = "generateClientStubs";
static final String DEFAULT_MAPPINGS_FOLDER = "mappings";
private Property<Directory> contractsDslDir;
private final Property<Directory> contractsDslDir;
private ListProperty<String> excludedFiles;
private final ListProperty<String> excludedFiles;
private Property<Boolean> excludeBuildFolders;
private final Property<Boolean> excludeBuildFolders;
private DirectoryProperty stubsOutputDir;
private final ConfigurableFileCollection classpath;
private final DirectoryProperty stubsOutputDir;
@Inject
public GenerateClientStubsFromDslTask(ObjectFactory objects) {
contractsDslDir = objects.directoryProperty();
excludedFiles = objects.listProperty(String.class);
excludeBuildFolders = objects.property(Boolean.class);
classpath = objects.fileCollection();
stubsOutputDir = objects.directoryProperty();
}
@@ -75,10 +82,23 @@ class GenerateClientStubsFromDslTask extends DefaultTask {
getLogger().info("Stubs output dir [{}]", output);
getLogger().info("Spring Cloud Contract Verifier Plugin: Invoking DSL to client stubs conversion");
getLogger().info("Contracts dir is [{}] output stubs dir is [{}]", contractsDslDir.get().getAsFile(), output);
List<String> excludedFiles = this.excludedFiles.get();
RecursiveFilesConverter converter = new RecursiveFilesConverter(output, contractsDslDir.get().getAsFile(),
excludedFiles, ".*", excludeBuildFolders.get());
converter.processFiles();
OutputStream os;
if (getLogger().isDebugEnabled()) {
os = new ByteArrayOutputStream();
} else {
os = NullOutputStream.INSTANCE;
}
getProject().javaexec(exec -> {
exec.setMain("org.springframework.cloud.contract.verifier.converter.RecursiveFilesConverterApplication");
exec.classpath(classpath);
exec.args(quoteAndEscape(output.getAbsolutePath()), quoteAndEscape(contractsDslDir.get().getAsFile().getAbsolutePath()),
quoteAndEscape(StringUtils.collectionToCommaDelimitedString(excludedFiles.get())), quoteAndEscape(".*"), excludeBuildFolders.get());
exec.setStandardOutput(os);
exec.setErrorOutput(os);
});
if (getLogger().isDebugEnabled()) {
getLogger().debug(os.toString());
}
}
@InputDirectory
@@ -98,9 +118,22 @@ class GenerateClientStubsFromDslTask extends DefaultTask {
return excludeBuildFolders;
}
@Classpath
public ConfigurableFileCollection getClasspath() {
return classpath;
}
@OutputDirectory
public Property<Directory> getStubsOutputDir() {
return stubsOutputDir;
}
// See: https://github.com/gradle/gradle/issues/6072
private String quoteAndEscape(String str) {
if (System.getProperty("os.name").contains("Windows")) {
return "\"" + str.replace("\"", "\\\"") + "\"";
}
return str;
}
}

View File

@@ -16,14 +16,24 @@
package org.springframework.cloud.contract.verifier.plugin;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.util.List;
import javax.inject.Inject;
import org.eclipse.jgit.util.io.NullOutputStream;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.Optional;
@@ -32,15 +42,11 @@ import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SkipWhenEmpty;
import org.gradle.api.tasks.TaskAction;
import org.springframework.cloud.contract.spec.ContractVerifierException;
import org.springframework.cloud.contract.verifier.TestGenerator;
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties;
import org.springframework.cloud.contract.verifier.config.TestFramework;
import org.springframework.cloud.contract.verifier.config.TestMode;
import javax.inject.Inject;
import java.io.File;
import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Task used to generate server side tests
@@ -85,6 +91,8 @@ class GenerateServerTestsTask extends DefaultTask {
private final Property<Boolean> failOnInProgress;
private final ConfigurableFileCollection classpath;
private final DirectoryProperty generatedTestSourcesDir;
private final DirectoryProperty generatedTestResourcesDir;
@@ -106,6 +114,7 @@ class GenerateServerTestsTask extends DefaultTask {
this.baseClassMappings = objects.mapProperty(String.class, String.class);
this.assertJsonSize = objects.property(Boolean.class);
this.failOnInProgress = objects.property(Boolean.class);
this.classpath = objects.fileCollection();
this.generatedTestSourcesDir = objects.directoryProperty();
this.generatedTestResourcesDir = objects.directoryProperty();
}
@@ -121,13 +130,27 @@ class GenerateServerTestsTask extends DefaultTask {
getLogger().info("Spring Cloud Contract Verifier Plugin: Invoking test sources generation");
getLogger().info("Contracts are unpacked to [{}]", contractsDslDir);
getLogger().info("Included contracts are [{}]", includedContracts);
ContractVerifierConfigProperties properties = toConfigProperties(contractsDslDir, includedContracts, generatedTestSources, generatedTestResources);
try {
TestGenerator generator = new TestGenerator(toConfigProperties(contractsDslDir, includedContracts,
generatedTestSources, generatedTestResources));
int generatedClasses = generator.generate();
getLogger().info("Generated {} test classes", generatedClasses);
String propertiesJson = new ObjectMapper().writeValueAsString(properties);
OutputStream os;
if (getLogger().isDebugEnabled()) {
os = new ByteArrayOutputStream();
} else {
os = NullOutputStream.INSTANCE;
}
getProject().javaexec(exec -> {
exec.setMain("org.springframework.cloud.contract.verifier.TestGeneratorApplication");
exec.classpath(classpath);
exec.args(quoteAndEscape(propertiesJson));
exec.setStandardOutput(os);
exec.setErrorOutput(os);
});
if (getLogger().isDebugEnabled()) {
getLogger().debug(os.toString());
}
}
catch (ContractVerifierException e) {
catch (Exception e) {
throw new GradleException("Spring Cloud Contract Verifier Plugin exception: " + e.getMessage(), e);
}
}
@@ -213,6 +236,11 @@ class GenerateServerTestsTask extends DefaultTask {
return failOnInProgress;
}
@Classpath
ConfigurableFileCollection getClasspath() {
return classpath;
}
@OutputDirectory
DirectoryProperty getGeneratedTestSourcesDir() {
return generatedTestSourcesDir;
@@ -253,4 +281,12 @@ class GenerateServerTestsTask extends DefaultTask {
return properties;
}
// See: https://github.com/gradle/gradle/issues/6072
private String quoteAndEscape(String str) {
if (System.getProperty("os.name").contains("Windows")) {
return "\"" + str.replace("\"", "\\\"") + "\"";
}
return str;
}
}

View File

@@ -16,12 +16,14 @@
package org.springframework.cloud.contract.verifier.plugin;
import java.io.File;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.attributes.Bundling;
import org.gradle.api.attributes.Category;
import org.gradle.api.attributes.LibraryElements;
import org.gradle.api.attributes.Usage;
import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileCollection;
@@ -44,6 +46,8 @@ import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.testing.Test;
import org.springframework.cloud.contract.verifier.config.TestFramework;
import java.io.File;
/**
* Gradle plugin for Spring Cloud Contract Verifier that from the DSL contract can
* <ul>
@@ -59,6 +63,8 @@ import org.springframework.cloud.contract.verifier.config.TestFramework;
*/
public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
private static final String SPRING_CLOUD_VERSION = VersionExtractor.forClass(SpringCloudContractVerifierGradlePlugin.class);
private static final String GROUP_NAME = "Verification";
private static final String EXTENSION_NAME = "contracts";
@@ -71,6 +77,10 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
private static final String CONTRACT_TEST_RUNTIME_ONLY_CONFIGURATION_NAME = "contractTestRuntimeOnly";
private static final String CONTRACT_TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME = "contractTestRuntimeClasspath";
private static final String CONTRACT_TEST_GENERATOR_RUNTIME_CLASSPATH_CONFIGURATION_NAME = "contractTestGeneratorRuntimeClasspath";
private static final String VERIFIER_STUBS_JAR_TASK_NAME = "verifierStubsJar";
private static final String CONTRACT_TEST_TASK_NAME = "contractTest";
@@ -97,6 +107,8 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
createGenerateTestsTask(extension, contractTestSourceSet, copyContracts);
createAndConfigurePublishStubsToScmTask(extension, generateClientStubs);
project.getDependencies().add(CONTRACT_TEST_GENERATOR_RUNTIME_CLASSPATH_CONFIGURATION_NAME, "org.springframework.cloud:spring-cloud-contract-converters:" + SPRING_CLOUD_VERSION);
project.afterEvaluate(inner -> {
DirectoryProperty generatedTestSourcesDir = extension.getGeneratedTestSourcesDir();
if (generatedTestSourcesDir.isPresent()) {
@@ -149,6 +161,19 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
contractTestCompileOnly.extendsFrom(testCompileOnly);
contractTestImplementation.extendsFrom(testImplementation);
contractTestRuntimeOnly.extendsFrom(testRuntimeOnly);
configurations.create(CONTRACT_TEST_GENERATOR_RUNTIME_CLASSPATH_CONFIGURATION_NAME, conf -> {
conf.setVisible(false);
conf.setCanBeResolved(true);
conf.setCanBeConsumed(false);
conf.attributes(attributes -> {
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME));
attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY));
attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, LibraryElements.JAR));
attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.EXTERNAL));
});
conf.extendsFrom(configurations.getByName(CONTRACT_TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME));
});
}
private void registerContractTestTask(SourceSet contractTestSourceSet) {
@@ -190,6 +215,8 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
.convention(extension.getBaseClassMappings().getBaseClassMappings());
generateServerTestsTask.getAssertJsonSize().convention(extension.getAssertJsonSize());
generateServerTestsTask.getFailOnInProgress().convention(extension.getFailOnInProgress());
generateServerTestsTask.getClasspath()
.from(project.getConfigurations().getByName(CONTRACT_TEST_GENERATOR_RUNTIME_CLASSPATH_CONFIGURATION_NAME));
generateServerTestsTask.getGeneratedTestSourcesDir()
.convention(extension.getTestFramework().flatMap(testFramework -> {
Property<Directory> correctSourceSetDir;
@@ -205,14 +232,22 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
generateServerTestsTask.dependsOn(copyContracts);
});
project.getTasks().named(contractTestSourceSet.getCompileJavaTaskName(), compileContractTestJava -> {
compileContractTestJava.dependsOn(task);
});
project.getTasks().named(contractTestSourceSet.getCompileJavaTaskName(), compileContractTestJava -> compileContractTestJava.dependsOn(task));
project.getPlugins().withType(GroovyPlugin.class, groovyPlugin -> {
project.getTasks().named(contractTestSourceSet.getCompileTaskName("groovy"), compileContractTestGroovy -> {
compileContractTestGroovy.dependsOn(task);
});
});
project.getPlugins().withId("kotlin", kotlinPlugin -> {
project.getTasks().named(contractTestSourceSet.getCompileTaskName("kotlin"), compileContractTestKotlin -> {
compileContractTestKotlin.dependsOn(task);
});
});
project.getPlugins().withId("org.jetbrains.kotlin.jvm", kotlinJvmPlugin -> {
project.getTasks().named(contractTestSourceSet.getCompileTaskName("kotlin"), compileContractTestKotlin -> {
compileContractTestKotlin.dependsOn(task);
});
});
}
private void createAndConfigurePublishStubsToScmTask(ContractVerifierExtension extension,
@@ -258,6 +293,8 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
.convention(copyContracts.flatMap(ContractsCopyTask::getCopiedContractsFolder));
generateClientStubs.getExcludedFiles().convention(extension.getExcludedFiles());
generateClientStubs.getExcludeBuildFolders().convention(extension.getExcludeBuildFolders());
generateClientStubs.getClasspath()
.from(project.getConfigurations().getByName(CONTRACT_TEST_GENERATOR_RUNTIME_CLASSPATH_CONFIGURATION_NAME));
generateClientStubs.getStubsOutputDir().convention(extension.getStubsOutputDir()
.dir(buildRootPath(GenerateClientStubsFromDslTask.DEFAULT_MAPPINGS_FOLDER)));
@@ -343,22 +380,24 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
contractsCopyTask.getConvertToYaml().convention(extension.getConvertToYaml());
contractsCopyTask.getFailOnNoContracts().convention(extension.getFailOnNoContracts());
contractsCopyTask.getContractsDirectory()
.convention(extension.getContractsDslDir().map(contractsDslDir -> {
if (contractsDslDir.getAsFile().exists()) {
return contractsDslDir;
}
else {
Directory legacyContractsDslDir = project.getLayout().getProjectDirectory()
.dir("src/test/resources/contracts");
if (legacyContractsDslDir.getAsFile().exists()) {
project.getLogger().warn(
"Spring Cloud Contract Verifier Plugin: Falling back to legacy contracts directory in 'test' source set. Please switch to 'contractTest' source set as this will be removed in a future release.");
return legacyContractsDslDir;
.convention(extension.getContractsDslDir().flatMap(contractsDslDir -> {
return project.provider(() -> {
if (contractsDslDir.getAsFile().exists()) {
return contractsDslDir;
}
else {
return null;
Directory legacyContractsDslDir = project.getLayout().getProjectDirectory()
.dir("src/test/resources/contracts");
if (legacyContractsDslDir.getAsFile().exists()) {
project.getLogger().warn(
"Spring Cloud Contract Verifier Plugin: Falling back to legacy contracts directory in 'test' source set. Please switch to 'contractTest' source set as this will be removed in a future release.");
return legacyContractsDslDir;
}
else {
return null;
}
}
}
});
}));
contractsCopyTask.getContractDependency().getGroupId()
.convention(extension.getContractDependency().getGroupId());
@@ -385,6 +424,8 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
contractsCopyTask.getContractsPath().convention(extension.getContractsPath());
contractsCopyTask.getExcludeBuildFolders().convention(extension.getExcludeBuildFolders());
contractsCopyTask.getDeleteStubsAfterTest().convention(extension.getDeleteStubsAfterTest());
contractsCopyTask.getClasspath()
.from(project.getConfigurations().getByName(CONTRACT_TEST_GENERATOR_RUNTIME_CLASSPATH_CONFIGURATION_NAME));
contractsCopyTask.getCopiedContractsFolder()
.convention(extension.getStubsOutputDir().dir(buildRootPath(ContractsCopyTask.CONTRACTS)));

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2013-2020 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
*
* https://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.contract.verifier.plugin;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
final class VersionExtractor {
private VersionExtractor() {
}
static String forClass(Class<?> cls) {
String implementationVersion = cls.getPackage().getImplementationVersion();
if (implementationVersion != null) {
return implementationVersion;
}
URL codeSourceLocation = cls.getProtectionDomain().getCodeSource().getLocation();
try {
URLConnection connection = codeSourceLocation.openConnection();
if (connection instanceof JarURLConnection) {
return getImplementationVersion(((JarURLConnection) connection).getJarFile());
}
try (JarFile jarFile = new JarFile(new File(codeSourceLocation.toURI()))) {
return getImplementationVersion(jarFile);
}
}
catch (Exception e) {
return null;
}
}
private static String getImplementationVersion(JarFile jarFile) throws IOException {
return jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);
}
}