Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #79 - Use plexus to support classpath resources #80

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,28 @@ However, if you would like to get the benefit of the community maintained defini
</plugin>
```

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
<plugin>
<groupId>biz.lermitage.oga</groupId>
<artifactId>oga-maven-plugin</artifactId>
<configuration>
<additionalDefinitionFiles>
<additionalDefinitionFile>classpath-og-definitions.json</additionalDefinitionFile>
</additionalDefinitionFiles>
</configuration>
<dependencies>
<!-- Dependency contains /classpath-og-definitions.json -->
<dependency>
<groupId>com.example.oga</groupId>
<artifactId>build-config</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</plugin>
```

#### Ignoring definitions

You can also provide a JSON ignore-list in order to exclude some *groupIds* or *groupId + artifactIds*:
Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@
<version>${gson.version}</version>
</dependency>

<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-resources</artifactId>
<version>1.3.0</version>
</dependency>

<!-- fix maven 3.9.2+ warnings:
Plugin should declare these Maven artifacts in `provided` scope: [org.apache.maven:maven-settings-builder:3.9.2, org.apache.maven:maven-builder-support:3.9.2, org.apache.maven:maven-artifact:3.9.2, org.apache.maven:maven-resolver-provider:3.9.2, org.apache.maven:maven-plugin-api:3.9.2, org.apache.maven:maven-core:3.9.2, org.apache.maven:maven-repository-metadata:3.9.2, org.apache.maven:maven-settings:3.9.2, org.apache.maven:maven-model-builder:3.9.2, org.apache.maven:maven-model:3.9.2]
-->
Expand Down
38 changes: 31 additions & 7 deletions src/main/java/biz/lermitage/oga/CheckMojo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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<IgnoreList>()
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!!)
Expand Down Expand Up @@ -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"
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/biz/lermitage/oga/util/DefinitionsTools.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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'"
Expand Down
60 changes: 25 additions & 35 deletions src/main/java/biz/lermitage/oga/util/IOTools.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 <T> readFromLocation(location: String, clazz: Class<T>): 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 <T> readFromLocation(location: String, locator: ResourceManager, log: Log, clazz: Class<T>): 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()
}
}
22 changes: 22 additions & 0 deletions src/test/java/biz/lermitage/oga/CheckMojoITest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<!-- Intentionally does not have a parent to emulate a 3rd party -->

<groupId>biz.lermitage.oga</groupId>
<artifactId>classpath-build-config</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>Build Configuration</name>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": "2",
"date": "2021/02/03",
"migration": [
{
"old": "org.mock-server",
"new": "com.example.classpath.dependency"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<artifactId>project-to-test</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Test CheckMojo</name>

<parent>
<groupId>biz.lermitage.oga</groupId>
<artifactId>parent-pom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<dependencies>
<!-- From the classpath og-definitions file -->
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-junit-rule-no-dependencies</artifactId>
<version>5.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>biz.lermitage.oga</groupId>
<artifactId>oga-maven-plugin</artifactId>
<configuration>
<additionalDefinitionFiles>
<!-- A file in the plugin dependency -->
<additionalDefinitionFile>classpath-og-definitions.json</additionalDefinitionFile>
</additionalDefinitionFiles>
</configuration>
<dependencies>
<dependency>
<groupId>biz.lermitage.oga</groupId>
<artifactId>classpath-build-config</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
Loading