From 4a61d877ed10e18258d4957bdd519fd5a524b85b Mon Sep 17 00:00:00 2001 From: Phillip Whittlesea-Clark Date: Thu, 2 May 2024 17:19:54 +0100 Subject: [PATCH] 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() } }