From 820e0b1123fdcbf41ead94ad464b2b61f84c2882 Mon Sep 17 00:00:00 2001 From: Phillip Whittlesea-Clark Date: Thu, 2 May 2024 17:19:54 +0100 Subject: [PATCH 1/3] Use plexus to support classpath resources This is inspired by the PMD maven plugin https://github.com/apache/maven-pmd-plugin/blob/4afad98d229a6764d2ebdd246c037d1645c7cbb9/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java#L394 The aim is to support classpath configuration when using this plugin in parent aggregator projects where config is in a plugin dependency --- pom.xml | 6 ++ src/main/java/biz/lermitage/oga/CheckMojo.kt | 38 +++++++++--- .../lermitage/oga/util/DefinitionsTools.kt | 5 +- .../java/biz/lermitage/oga/util/IOTools.kt | 60 ++++++++----------- 4 files changed, 65 insertions(+), 44 deletions(-) diff --git a/pom.xml b/pom.xml index 4be3077..83255d5 100644 --- a/pom.xml +++ b/pom.xml @@ -104,6 +104,12 @@ ${gson.version} + + org.codehaus.plexus + plexus-resources + 1.3.0 + + diff --git a/src/main/java/biz/lermitage/oga/CheckMojo.kt b/src/main/java/biz/lermitage/oga/CheckMojo.kt index 54c45d0..f4282be 100644 --- a/src/main/java/biz/lermitage/oga/CheckMojo.kt +++ b/src/main/java/biz/lermitage/oga/CheckMojo.kt @@ -5,15 +5,17 @@ import biz.lermitage.oga.cfg.IgnoreList import biz.lermitage.oga.util.DefinitionsTools import biz.lermitage.oga.util.IOTools import biz.lermitage.oga.util.IgnoreListTools +import org.apache.maven.execution.MavenSession import org.apache.maven.plugin.AbstractMojo import org.apache.maven.plugin.MojoExecutionException +import org.apache.maven.plugins.annotations.Component import org.apache.maven.plugins.annotations.Mojo import org.apache.maven.plugins.annotations.Parameter import org.apache.maven.project.MavenProject +import org.codehaus.plexus.resource.ResourceManager +import org.codehaus.plexus.resource.loader.FileResourceLoader import org.codehaus.plexus.util.xml.pull.XmlPullParserException -import java.io.File import java.io.IOException -import java.net.URL import java.util.Optional /** @@ -60,6 +62,12 @@ class CheckMojo : AbstractMojo() { @Parameter(property = "project", readonly = true) var project: MavenProject? = null + @Parameter(defaultValue = "\${session}", required = true, readonly = true) + private var session: MavenSession? = null + + @Component + private val locator: ResourceManager? = null + /** * Execute goal. */ @@ -72,22 +80,25 @@ class CheckMojo : AbstractMojo() { return } + setUpLocator(locator!!) + try { - val allDefinitions = mutableListOf(DefinitionsTools.loadDefinitionsFromUrl(ogDefinitionsUrl ?: DEFINITIONS_URL, log)) + val allDefinitions = mutableListOf(DefinitionsTools.loadDefinitionsFromUrl(ogDefinitionsUrl ?: DEFINITIONS_URL, log, locator)) if (!ignoreUnofficialMigrations) { - allDefinitions += DefinitionsTools.loadDefinitionsFromUrl(ogUnofficialDefinitionsUrl ?: UNOFFICIAL_DEFINITIONS_URL, log) + allDefinitions += DefinitionsTools.loadDefinitionsFromUrl(ogUnofficialDefinitionsUrl ?: UNOFFICIAL_DEFINITIONS_URL, log, locator) } // Load additional definitions if defined - additionalDefinitionFiles!!.forEach { allDefinitions += DefinitionsTools.loadDefinitionsFromUrl(it, log) } + additionalDefinitionFiles!!.forEach { allDefinitions += DefinitionsTools.loadDefinitionsFromUrl(it, log, locator) } var ignoreList = Optional.empty() if (!ignoreListFile.isNullOrEmpty()) { log.info("Loading ignore list from file $ignoreListFile") - ignoreList = Optional.of(IOTools.readIgnoreList(File(ignoreListFile))) + // TODO given that we now support loading a file from classpath, file and URL we could consolidate configuration to definitions and suppressions (like PMD/Checkstyle) + ignoreList = Optional.of(IOTools.readIgnoreList(ignoreListFile, locator, log)) } else if (!ignoreListUrl.isNullOrEmpty()) { log.info("Loading ignore list from url $ignoreListUrl") - ignoreList = Optional.of(IOTools.readIgnoreList(URL(ignoreListUrl))) + ignoreList = Optional.of(IOTools.readIgnoreList(ignoreListUrl, locator, log)) } val dependencies = DefinitionsTools.mapDependenciesToOgaDependencies(project!!.dependencies!!) @@ -162,6 +173,19 @@ class CheckMojo : AbstractMojo() { } } + private fun setUpLocator(locator: ResourceManager) { + val searchPaths = listOf( + // 0. The locator will read from classpath and URL locations + // 1. in the directory of the current project's pom file - note: extensions might replace the pom file on the fly + project!!.file.parentFile.absolutePath, + // 2. in the current project's directory + project!!.basedir.absolutePath, + // 3. in the base directory - that's the directory of the initial pom requested to build, e.g. the root of a multi-module build + session!!.request!!.baseDirectory + ) + searchPaths.forEach { searchPath -> locator.addSearchPath(FileResourceLoader.ID, searchPath) } + } + companion object { private const val GITHUB_PRJ_RAW_URL = "https://raw.githubusercontent.com/jonathanlermitage/oga-maven-plugin/master/" private const val DEFINITIONS_URL = GITHUB_PRJ_RAW_URL + "uc/og-definitions.json" diff --git a/src/main/java/biz/lermitage/oga/util/DefinitionsTools.kt b/src/main/java/biz/lermitage/oga/util/DefinitionsTools.kt index 9918ef5..1399827 100644 --- a/src/main/java/biz/lermitage/oga/util/DefinitionsTools.kt +++ b/src/main/java/biz/lermitage/oga/util/DefinitionsTools.kt @@ -4,6 +4,7 @@ import biz.lermitage.oga.Dependency import biz.lermitage.oga.DependencyType import biz.lermitage.oga.cfg.Definitions import org.apache.maven.plugin.logging.Log +import org.codehaus.plexus.resource.ResourceManager /** * Definitions tools. @@ -12,9 +13,9 @@ import org.apache.maven.plugin.logging.Log */ object DefinitionsTools { - fun loadDefinitionsFromUrl(url: String, log: Log): Definitions { + fun loadDefinitionsFromUrl(url: String, log: Log, locator: ResourceManager): Definitions { log.info("Loading definitions from $url") - val definitions = IOTools.readDefinitions(url) + val definitions = IOTools.readDefinitions(url, locator, log) val nbDefinitions = definitions.migration?.size var welcomeMsg = "Loaded $nbDefinitions definitions from '$url'" diff --git a/src/main/java/biz/lermitage/oga/util/IOTools.kt b/src/main/java/biz/lermitage/oga/util/IOTools.kt index 4f838ea..aa0e909 100644 --- a/src/main/java/biz/lermitage/oga/util/IOTools.kt +++ b/src/main/java/biz/lermitage/oga/util/IOTools.kt @@ -4,13 +4,15 @@ import biz.lermitage.oga.cfg.Definitions import biz.lermitage.oga.cfg.IgnoreList import com.google.gson.GsonBuilder import org.apache.commons.io.FileUtils -import java.io.BufferedReader +import org.apache.maven.plugin.MojoExecutionException +import org.apache.maven.plugin.logging.Log +import org.codehaus.plexus.resource.ResourceManager +import org.codehaus.plexus.resource.loader.FileResourceCreationException +import org.codehaus.plexus.resource.loader.ResourceNotFoundException import java.io.File import java.io.IOException -import java.io.InputStreamReader -import java.net.HttpURLConnection -import java.net.URL -import java.util.regex.Pattern +import java.nio.file.Files +import kotlin.io.path.pathString /** * IO tools. @@ -22,45 +24,33 @@ object IOTools { private val GSON = GsonBuilder().create() @Throws(IOException::class) - fun readDefinitions(url: String): Definitions { - return readFromLocation(url, Definitions::class.java) + fun readDefinitions(location: String, locator: ResourceManager, log: Log): Definitions { + return readFromLocation(location, locator, log, Definitions::class.java) } @Throws(IOException::class) - fun readIgnoreList(url: URL): IgnoreList { - val ignoreListAsString = readContent(url) - return GSON.fromJson(ignoreListAsString, IgnoreList::class.java) + fun readIgnoreList(location: String, locator: ResourceManager, log: Log): IgnoreList { + return readFromLocation(location, locator, log, IgnoreList::class.java) } @Throws(IOException::class) - fun readIgnoreList(file: File): IgnoreList { - val ignoreListAsString = FileUtils.readFileToString(file, Charsets.UTF_8) - return GSON.fromJson(ignoreListAsString, IgnoreList::class.java) - } - - @Throws(IOException::class) - private fun readFromLocation(location: String, clazz: Class): T { - val uriRx: Pattern = Pattern.compile("^https?:.*", Pattern.CASE_INSENSITIVE) - val asString = if (uriRx.matcher(location).matches()) { - readContent(URL(location)) - } else { - FileUtils.readFileToString(File(location), Charsets.UTF_8) - } + private fun readFromLocation(location: String, locator: ResourceManager, log: Log, clazz: Class): T { + val file = locationToFile(location, locator, log) + val asString = FileUtils.readFileToString(file, Charsets.UTF_8) return GSON.fromJson(asString, clazz) } - @Throws(IOException::class) - private fun readContent(url: URL): String { - val content = StringBuilder() - val conn = url.openConnection() as HttpURLConnection - conn.requestMethod = "GET" - BufferedReader(InputStreamReader(conn.inputStream)).use { rd -> - var line: String? = rd.readLine() - while (line != null) { - content.append(line) - line = rd.readLine() - } + @Throws(MojoExecutionException::class) + private fun locationToFile(location: String, locator: ResourceManager, log: Log): File { + try { + val resolvedLocation = Files.createTempFile("oga-", ".json") + log.debug("Resolved file from '$location' to '$resolvedLocation'") + return locator.getResourceAsFile(location, resolvedLocation.pathString) + ?: throw MojoExecutionException("Could not resolve $location") + } catch (e: ResourceNotFoundException) { + throw MojoExecutionException(e.message, e) + } catch (e: FileResourceCreationException) { + throw MojoExecutionException(e.message, e) } - return content.toString() } } From 53c60b093aa7f6d232c440303b4ff6a685d96164 Mon Sep 17 00:00:00 2001 From: Phillip Whittlesea-Clark Date: Sun, 12 May 2024 21:11:43 +0100 Subject: [PATCH 2/3] Add test case for classpath resources --- README.md | 22 +++++++++ .../java/biz/lermitage/oga/CheckMojoITest.kt | 22 +++++++++ .../oga/classpath_build_config/pom.xml | 11 +++++ .../resources/classpath-og-definitions.json | 10 ++++ .../oga/ko_classpath_definitions/pom.xml | 48 +++++++++++++++++++ 5 files changed, 113 insertions(+) create mode 100644 src/test/resources/biz/lermitage/oga/classpath_build_config/pom.xml create mode 100644 src/test/resources/biz/lermitage/oga/classpath_build_config/src/main/resources/classpath-og-definitions.json create mode 100644 src/test/resources/biz/lermitage/oga/ko_classpath_definitions/pom.xml diff --git a/README.md b/README.md index 4ffb21f..22ce58a 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,28 @@ However, if you would like to get the benefit of the community maintained defini ``` +In some cases your definition file might be shared across many projects. +As an alternative to copying and pasting this file into each build, you can package your definitions into a JAR and reference it like so: +```xml + + biz.lermitage.oga + oga-maven-plugin + + + classpath-og-definitions.json + + + + + + com.example.oga + build-config + 1.0.0 + + + +``` + #### Ignoring definitions You can also provide a JSON ignore-list in order to exclude some *groupIds* or *groupId + artifactIds*: diff --git a/src/test/java/biz/lermitage/oga/CheckMojoITest.kt b/src/test/java/biz/lermitage/oga/CheckMojoITest.kt index 8b0c0f5..1adddb6 100644 --- a/src/test/java/biz/lermitage/oga/CheckMojoITest.kt +++ b/src/test/java/biz/lermitage/oga/CheckMojoITest.kt @@ -16,6 +16,28 @@ class CheckMojoITest { @JvmField var mockServerRule: MockServerRule = MockServerRule(this) + @Test + fun testProjectWithClasspathDefinitionFiles() { + // GIVEN a project that contains our definition file + val classpathResourcesDir = ResourceExtractor.simpleExtractResources(javaClass, "/biz/lermitage/oga/classpath_build_config") + val classpathVerifier = Verifier(classpathResourcesDir.absolutePath) + classpathVerifier.deleteArtifact("biz.lermitage.oga", "classpath-build-config", "1.0.0-SNAPSHOT", "jar") + classpathVerifier.executeGoal("install") + + // AND a project which uses the definition file as a classpath resource + val testDir = ResourceExtractor.simpleExtractResources(javaClass, "/biz/lermitage/oga/ko_classpath_definitions") + val verifier = Verifier(testDir.absolutePath) + + verifier.deleteArtifact("biz.lermitage.oga", "project-to-test", "1.0.0-SNAPSHOT", "pom") + + // WHEN checking the projects dependencies + // THEN the build fails + assertThrows(VerificationException::class.java) { verifier.executeGoal("biz.lermitage.oga:oga-maven-plugin:check") } + + // AND the build contains a failure message from the classpath definition file + verifier.verifyTextInLog("[ERROR] (dependency) 'org.mock-server' groupId should be replaced by 'com.example.classpath.dependency'") + } + @Throws(Exception::class) @Test fun testProjectWithAdditionalDefinitionFiles() { diff --git a/src/test/resources/biz/lermitage/oga/classpath_build_config/pom.xml b/src/test/resources/biz/lermitage/oga/classpath_build_config/pom.xml new file mode 100644 index 0000000..0f43d1c --- /dev/null +++ b/src/test/resources/biz/lermitage/oga/classpath_build_config/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + + + + biz.lermitage.oga + classpath-build-config + 1.0.0-SNAPSHOT + Build Configuration + diff --git a/src/test/resources/biz/lermitage/oga/classpath_build_config/src/main/resources/classpath-og-definitions.json b/src/test/resources/biz/lermitage/oga/classpath_build_config/src/main/resources/classpath-og-definitions.json new file mode 100644 index 0000000..2e5959a --- /dev/null +++ b/src/test/resources/biz/lermitage/oga/classpath_build_config/src/main/resources/classpath-og-definitions.json @@ -0,0 +1,10 @@ +{ + "version": "2", + "date": "2021/02/03", + "migration": [ + { + "old": "org.mock-server", + "new": "com.example.classpath.dependency" + } + ] +} diff --git a/src/test/resources/biz/lermitage/oga/ko_classpath_definitions/pom.xml b/src/test/resources/biz/lermitage/oga/ko_classpath_definitions/pom.xml new file mode 100644 index 0000000..98d5da8 --- /dev/null +++ b/src/test/resources/biz/lermitage/oga/ko_classpath_definitions/pom.xml @@ -0,0 +1,48 @@ + + 4.0.0 + + project-to-test + 1.0.0-SNAPSHOT + pom + Test CheckMojo + + + biz.lermitage.oga + parent-pom + 1.0.0-SNAPSHOT + ../pom.xml + + + + + + org.mock-server + mockserver-junit-rule-no-dependencies + 5.13.2 + test + + + + + + + biz.lermitage.oga + oga-maven-plugin + + + + classpath-og-definitions.json + + + + + com.example.oga + build-config + 1.0.0-SNAPSHOT + + + + + + From ce35df479f3447724ce821f1a253adf738f31d28 Mon Sep 17 00:00:00 2001 From: Phillip Whittlesea-Clark Date: Mon, 13 May 2024 09:05:38 +0100 Subject: [PATCH 3/3] Correct renamed GAV --- .../biz/lermitage/oga/ko_classpath_definitions/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/resources/biz/lermitage/oga/ko_classpath_definitions/pom.xml b/src/test/resources/biz/lermitage/oga/ko_classpath_definitions/pom.xml index 98d5da8..bc3bae1 100644 --- a/src/test/resources/biz/lermitage/oga/ko_classpath_definitions/pom.xml +++ b/src/test/resources/biz/lermitage/oga/ko_classpath_definitions/pom.xml @@ -37,8 +37,8 @@ - com.example.oga - build-config + biz.lermitage.oga + classpath-build-config 1.0.0-SNAPSHOT