51 Commits

Author SHA1 Message Date
1577bbf2d8 v1.4.1 pom 2025-05-27 09:45:52 -04:00
988326a8d5 Merge branch 'develop' into stable 2025-05-27 09:44:44 -04:00
ad4b06fc4f attempt to fix remote version range resolution 2025-05-27 09:44:00 -04:00
94825b9417 updated documentation 2025-03-26 11:26:36 -04:00
80068ef209 Merge branch 'develop' into stable 2025-03-26 11:01:04 -04:00
a7480c3d85 added file import; updated docs and deps; v1.4.x 2025-03-26 11:00:23 -04:00
034b319172 v1.3.6 pom 2024-08-21 15:39:43 -04:00
65009df10b Merge branch 'develop' into stable 2024-08-21 15:39:27 -04:00
8df2d5bd03 added Mojo annotations for config to prevent warnings 2024-08-21 15:39:13 -04:00
0edac4fc37 v1.3.5 pom 2023-08-21 15:33:12 -04:00
f837e5d1c0 Merge branch 'develop' into stable 2023-08-21 15:32:38 -04:00
78e34d85e9 added logging 2023-08-21 15:32:31 -04:00
6e2a701c0d fixed artifact/version detection 2023-08-21 15:30:51 -04:00
08498d4224 v1.3.4 pom 2023-08-20 16:56:20 -04:00
1c7997d85c Merge branch 'develop' into stable 2023-08-20 16:56:04 -04:00
9116d4bfd9 less warnings for non-empty directories during purge 2023-08-20 16:55:54 -04:00
9874ed9543 v1.3.3 pom 2023-08-20 15:03:10 -04:00
eeb7a84698 Merge branch 'develop' into stable 2023-08-20 15:02:58 -04:00
ca8913e558 don't delete pom metadata 2023-08-20 15:02:50 -04:00
4c1c4d412d v1.3.2 pom 2023-08-20 13:34:37 -04:00
fc5e0c6bd1 Merge branch 'develop' into stable 2023-08-20 13:32:59 -04:00
2a1523012b prevent sub-group matching for purge 2023-08-20 13:32:46 -04:00
d41d73fa1c v1.3.1 pom 2023-08-20 12:59:23 -04:00
a9aa47d412 Merge branch 'develop' into stable 2023-08-20 12:58:55 -04:00
47fd07247d fix version range impl 2023-08-20 12:58:46 -04:00
cfb13f3170 v1.3.0 pom 2023-08-20 12:04:15 -04:00
75f8175a1c Merge branch 'develop' into stable 2023-08-20 12:03:52 -04:00
f2c93eec50 added version range config support 2023-08-20 12:03:15 -04:00
58eb0ec013 v1.2.4 pom 2023-08-20 11:35:52 -04:00
dec181aa71 Merge branch 'develop' into stable 2023-08-20 11:35:29 -04:00
51f5d6d0a3 fixed missing group/artifact paths on purge 2023-08-20 11:35:12 -04:00
24d69c3715 forcing extension to "xml" 2023-08-20 11:26:24 -04:00
fe10e53fb4 added goal prefix config: "ban" 2023-08-20 11:26:07 -04:00
b6c7323203 v1.2.3 pom 2023-08-16 12:38:18 -04:00
9644bbe154 Merge branch 'develop' into stable 2023-08-16 12:37:38 -04:00
9a0b7207c0 fixed import/url; added default artifact ext: xml 2023-08-16 12:36:50 -04:00
118b40f3f9 v1.2.2 pom 2023-06-30 12:42:52 -04:00
db3061a3a6 Merge branch 'develop' into stable 2023-06-30 12:42:25 -04:00
fee4032208 support blank groupId/artifactId specs 2023-06-30 12:42:17 -04:00
f991975923 v1.2.1 pom 2023-05-30 09:37:29 -04:00
e4f14a81da Merge branch 'develop' into stable 2023-05-30 09:36:26 -04:00
b4426761df fix bugs 2023-05-30 09:36:13 -04:00
f749926f92 v1.2.0 pom 2023-05-29 11:13:11 -04:00
8c3ce9f069 Merge branch 'develop' into stable 2023-05-29 11:11:02 -04:00
3c73bcb83d added artifact import support 2023-05-29 11:09:22 -04:00
cf2fdf42fa v1.1.1 pom 2023-05-25 15:24:41 -04:00
3d7d8bb769 Merge branch 'develop' into stable 2023-05-25 15:24:08 -04:00
d22f657f4e added remote config import support 2023-05-25 15:23:54 -04:00
01768754f0 v1.1.0 pom 2023-05-25 13:22:00 -04:00
e241137209 Merge branch 'develop' into stable 2023-05-25 13:19:36 -04:00
e02be65780 added purge goal 2023-05-25 13:19:24 -04:00
15 changed files with 1172 additions and 190 deletions

119
README.md
View File

@@ -5,6 +5,10 @@ This is a maven plugin that allows for developers and organizations to ban Maven
## Usage
### Prevent Banned Artifacts
Here is a pseudo-code example of all the options this plugin provides.
```xml
<project>
...
@@ -18,6 +22,11 @@ This is a maven plugin that allows for developers and organizations to ban Maven
<version>...</version>
<extensions>true</extensions>
<configuration>
<import>
<file>project-file.xml</file>
<url>https://host:port/path/file.xml</url>
<artifact>groupId:artifactId:version</artifact>
</import>
<includes>
<artifact>
<groupId>...<groupId>
@@ -31,7 +40,7 @@ This is a maven plugin that allows for developers and organizations to ban Maven
</artifact>
<artifact>com.inteligr8:ban-maven-plugin:[,1.0.0)</artifact>
<artifact>log4j:log4j</artifact>
<artifact>org\.springframework.*::[,4.0.0.RELEASE)</artifact>
<artifact>org\.springframe.+::[,4.0.0.RELEASE)</artifact>
</includes>
<excludes>
<artifact>
@@ -48,18 +57,114 @@ This is a maven plugin that allows for developers and organizations to ban Maven
</project>
```
The `extensions` elements is critical. Without it, the plugin does nothing. With it, the plugin is able to detected ban artifacts before they are downloaded.
The `extensions` elements is critical. Without it, the plugin does nothing as far as banning artifacts/dependencies. With it, the plugin is able to not only detect banned artifacts, but do it before they are downloaded. This works with both dependencies and plugins. This keeps libraries from even reaching your local Maven repository cache.
If no `includes` are provided, then no artifacts will be banned. An *included* artifact is a banned artifact. An *excluded* artifact is not banned. It is the opposite of what you may think.
### Purge Banned Artifacts
If `groupId` is not provided, it is ignored in the matching process. So it will match all artifact group IDs and the constraint will be for `artifactId` and `version` only. The same is true for `artifactId` and `version`. This means that `<includes><artifact></artifact></includes>` will ban every artifact.
Here is an example of the non-extension use case for the plugin. You could use the same plugin for both preventing banned artifacts and cleaning up previously downloaded ones. Just set `extensions` to `true` in those cases, as highlighted in the previous section.
If `groupId` and `groupIdRegex` are both provided, only `groupId` is used. The same is true for `artifactId` and `artifactIdRegex`. The `*Regex` values use standard Java regular expressions. If using regular expressions, remember to escape the dots (`\.`) in group IDs.
```xml
<project>
...
<build>
...
<plugins>
...
<plugin>
<groupId>com.inteligr8</groupId>
<artifactId>ban-maven-plugin</artifactId>
<version>...</version>
<configuration>
...
</configuration>
<executions>
<execution>
<id>clean</id>
<phase>clean</phase>
<goals><goal>purge-repo</goal></goals>
</execution>
</executions>
</plugin>
...
</plugins>
...
</build>
...
</project>
```
The `purge-repo` goal will remove all banned artifacts from your local Maven cache. It does not support `groupIdRegex` or blank `groupId` specifications. So any of those will not be purged/removed.
For instance, you can use the following and expect it to work for preventing and purging banned dependencies and plugins:
```xml
<include>
<artifact>
<groupId>...<groupId>
<artifactId>...<artifactId>
<version>...</version>
</artifact>
<artifact>com.inteligr8:ban-maven-plugin:[,1.0.0)</artifact>
<artifact>log4j:log4j</artifact>
</include>
```
## Configuration
If no `includes` are provided, then no artifacts will be banned. An *included* artifact is a banned artifact. An *excluded* artifact is not banned. It is the opposite of what you may think. If no `excludes` are provided, then no banned artifacts are granted an exception.
The `artifact` element supports the descriptive `groupId`/`artifactId`/`version` elements or the abbreviated colon-based notation. When using the colon-based notation, the group ID and artifact ID may be treated as `groupIdRegex` and `artifactIdRegex` (see below). If you only use acceptable `groupId` and `artifactId` characters (letters/numbers/dashes/underscores/dots), it will not. But if you include any other characters, like `\.` or `*`, then it will be treated as regex. How it is treated will impact the functionality of `purge-repo` goal, if you are using it.
If `groupId` or `artifactId` or `version` are not provided, they are ignored in the matching process. So it will match all applicable artifacts and the constraint will be only for what was specified. This means that `<includes><artifact>:</artifact></includes>` will ban every artifact and all their versions.
If `groupId` and `groupIdRegex` are both provided, only `groupId` is used. The same is true for `artifactId` and `artifactIdRegex`. The `*Regex` element values use standard Java regular expression parsing. If using regular expressions, remember to escape the dots (`\.`) in group IDs. If you do use `groupIdRegex` or use regular expressions in the colon-notation, the matching artifacts will not be purged using the `purge-repo` goal. So if you intend to use that goal, group ID regular expression matching needs be avoided.
The `version` element supports the standard Maven specification. You can match a specific version like `1.0.0`. Or you can match all versions before `1.2.17` like `[,1.2.17)`. You can match all future versions after `1.2.17` (inclusive) with `[1.2.17,)`.
There is nothing stopping you from specifying two `artifact` elements with the exact same values. So you can ban multiple version ranges of the same artifact by using multiple `artifact` elements.
If you *include* all versions by omitting the `version` element, you can still *exclude* (unban) certain versions, like `[1.2.17,)`.
If you *include* all versions by omitting the `version` element, you can still *exclude* (un-ban) certain versions, like `[1.2.17,)`.
It is recommended that you look into [Maven Tiles](https://github.com/repaint-io/maven-tiles) so you can use a tile to define your banned artifacts and side load them into all your projects.
Order does not matter. All include specifications are processed, followed by all exclude specifications.
## Import
The `import` file, URL, and artifact are to reference XML files that conform to the same `configuration` element as described here. In fact, the root elmenet of that XML should be `configuration`. It will only support the `includes` and `excludes` elements. so you cannot do recursive imports.
You can create a Maven `pom` packaging type project that deploys a configuration XML to your Maven repository. Then use an `import` to allow you to change banned dependencies without making changes to each individual project. Just like with the `version` notation in the `includes` and `excludes` elements, your `import` `artifact` element supports a version range. This way the latest banned dependencies can be side-loaded into all projects. This means previously functioning builds may eventually start failing. That is by design in this scenario.
The `import` elements supports multiple `url` or `artifact` declarations. All imported and directly specified include specifications are processed before all exclude specifications. You cannot change an include when importing, but you can add new ones, that may cover more versions; and you can exclude versions that may have been included by the import.
The `excludes` element is a way to provide project-by-project exceptions to imported banned artifacts where warranted.
## Examples
The recommended use of this plugin is for its use across whole organizations. First, you will want a simple Maven project that is referenced by all other Maven projects. That simple project will declare the banned artifacts and potentially purge existing ones. See the `examples/ban-config` project for a full example.
```xml
<configuration>
<includes>
<!-- CVE-2019-17571 -->
<artifact>org.apache.logging.log4j::[,2.17.1)</artifact>
<artifact>log4j:log4j</artifact>
</includes>
</configuration>
```
Deploying that project will result in the publication of the `ban-config.xml` to your Maven repository. That is where it can be picked up by all other projects so they can enforce the ban. If you do not have a local Maven repository, then you will have to upload the `ban-config.xml` to some other URL-accessible location by some other means.
Once you have that in place, you will want to add the following to every single Maven project that should be governed by the aforementioned `ban-config`. See the `examples/governed-artifact` project for a full example.
```xml
<plugin>
<groupId>com.inteligr8</groupId>
<artifactId>ban-maven-plugin</artifactId>
<version>...</version>
<extensions>true</extensions>
<configuration>
<import>
<artifact>com.inteligr8:ban-config:[2025.03,)</artifact>
</import>
</configuration>
</plugin>
```

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<includes>
<!-- CVE-2019-17571 -->
<artifact>org.apache.logging.log4j::[,2.17.1)</artifact>
<artifact>log4j:log4j</artifact>
</includes>
</configuration>

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.inteligr8</groupId>
<artifactId>ban-config</artifactId>
<packaging>pom</packaging>
<!-- A monthly cadence is reasonable at most organizations -->
<version>2025.03</version>
<name>Banned Artifact Configuration</name>
<build>
<resources>
<resource>
<directory>.</directory>
<includes>
<include>*-config.xml</include>
</includes>
<targetPath>${project.build.directory}</targetPath>
</resource>
</resources>
<plugins>
<plugin>
<groupId>com.inteligr8</groupId>
<artifactId>ban-maven-plugin</artifactId>
<version>1.4.0</version>
<executions>
<execution>
<id>purge-maven-repo</id>
<phase>clean</phase>
<goals><goal>purge-repo</goal></goals>
<configuration>
<import>
<file>ban-config.xml</file>
</import>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>filter</id>
<goals><goal>resources</goal></goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<configuration>
<skip>true</skip>
<file>${project.build.directory}/ban-config.xml</file>
</configuration>
<executions>
<execution>
<id>install-xml</id>
<phase>install</phase>
<goals><goal>install-file</goal></goals>
<configuration>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<packaging>xml</packaging>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<file>ban-config.xml</file>
<pomFile>pom.xml</pomFile>
<packaging>xml</packaging>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.inteligr8</groupId>
<artifactId>governed-artifact</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Any Governed Artifact</name>
<build>
<plugins>
<plugin>
<groupId>com.inteligr8</groupId>
<artifactId>ban-maven-plugin</artifactId>
<version>1.4.0</version>
<extensions>true</extensions>
<configuration>
<import>
<artifact>com.inteligr8:ban-config:[2025.03,)</artifact>
</import>
</configuration>
</plugin>
</plugins>
</build>
</project>

33
pom.xml
View File

@@ -7,7 +7,7 @@
<groupId>com.inteligr8</groupId>
<artifactId>ban-maven-plugin</artifactId>
<version>1.0.0</version>
<version>1.4.1</version>
<packaging>maven-plugin</packaging>
<name>Ban Dependencies Maven Plugin</name>
@@ -43,14 +43,14 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.version>3.9.0</maven.version>
<maven.version>3.9.9</maven.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
<version>3.17.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
@@ -66,7 +66,7 @@
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.7.1</version>
<version>3.15.1</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -90,22 +90,36 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.15.1</version>
</plugin>
<plugin>
<artifactId>maven-invoker-plugin</artifactId>
<version>3.9.0</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.7.1</version>
<executions>
<execution>
<id>default-descriptor</id>
<goals>
<goal>descriptor</goal>
</goals>
<configuration>
<goalPrefix>ban</goalPrefix>
</configuration>
</execution>
<execution>
<id>help-descriptor</id>
@@ -131,7 +145,7 @@
<plugin>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-metadata</artifactId>
<version>2.1.1</version>
<version>2.2.0</version>
<executions>
<execution>
<goals>
@@ -142,11 +156,9 @@
</plugin>
<plugin>
<artifactId>maven-invoker-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<projectsDirectory>${basedir}/src/it</projectsDirectory>
<cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
<localRepositoryPath>${project.build.directory}/it-repo</localRepositoryPath>
<mavenHome>${env.MAVEN_HOME}</mavenHome>
<debug>true</debug>
<ignoreFailures>true</ignoreFailures>
@@ -179,7 +191,6 @@
<plugins>
<plugin>
<artifactId>maven-invoker-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<id>run-its</id>
@@ -238,7 +249,7 @@
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.13</version>
<version>1.7.0</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.inteligr8</groupId>
<artifactId>ban-maven-plugin-log4j-old</artifactId>
<version>@pom.version@</version>
<packaging>jar</packaging>
<name>Log4j Ban Plugin Tests</name>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<groupId>${project.groupId}</groupId>
<artifactId>ban-maven-plugin</artifactId>
<version>@pom.version@</version>
<extensions>true</extensions>
<configuration>
<includes>
<artifact>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</artifact>
</includes>
<excludes>
<artifact>log4j:log.+:[1.2.17,)</artifact>
</excludes>
</configuration>
<executions>
<execution>
<id>purge</id>
<phase>prepare-package</phase>
<goals><goal>purge-repo</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,224 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.ban;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.impl.ArtifactResolver;
import org.eclipse.aether.impl.VersionRangeResolver;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResolutionException;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.version.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.inteligr8.maven.model.ArtifactFilter;
public abstract class AbstractBanConfiguration implements BanConfiguration {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Pattern artifactPattern = Pattern.compile("^([^:]*):([^:]*)(:([^:]+))?$");
private final Pattern notRegexPattern = Pattern.compile("^[A-Za-z0-9_\\-\\.]*$");
protected final List<ArtifactFilter> includeArtifacts = new LinkedList<>();
protected final List<ArtifactFilter> excludeArtifacts = new LinkedList<>();
private final ArtifactResolver artifactResolver;
private final VersionRangeResolver versionRangeResolver;
private final MavenSession session;
public AbstractBanConfiguration(MavenSession session, ArtifactResolver artifactResolver, VersionRangeResolver versionRangeResolver) {
this.session = session;
this.artifactResolver = artifactResolver;
this.versionRangeResolver = versionRangeResolver;
}
public void init(Xpp3Dom rootDom) throws IOException, MojoFailureException {
if (rootDom == null)
return;
Xpp3Dom importDom = rootDom.getChild("import");
if (importDom != null)
this.processImports(importDom);
this.processIncludesExcludes(rootDom);
}
private void processImports(Xpp3Dom importDom) throws IOException, MojoFailureException {
for (Xpp3Dom child : importDom.getChildren()) {
BanConfigurationDownloader downloader = null;
if (child.getName().equals("file")) {
File file = new File(StringUtils.trimToNull(child.getValue()));
downloader = new BanConfigurationDownloader(this.session, this.artifactResolver, this.versionRangeResolver, file);
} else if (child.getName().equals("url")) {
String url = StringUtils.trimToNull(child.getValue());
downloader = new BanConfigurationDownloader(this.session, this.artifactResolver, this.versionRangeResolver, url);
} else if (child.getName().equals("artifact")) {
Artifact artifact = new DefaultArtifact(child.getValue());
if (!"xml".equals(artifact.getExtension()))
artifact = new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), "xml", artifact.getVersion());
Version latestVersion = this.findLatestVersion(artifact, child.getValue());
Artifact latestArtifact = this.findLatestArtifact(artifact, child.getValue());
if (latestArtifact == null && latestVersion != null) {
this.logger.debug("A latest version was found, but could not resolve the artifact using the range; trying to resolve the artifact with the specific version: {}: {}", latestVersion, child.getValue());
artifact = artifact.setVersion(latestVersion.toString());
latestArtifact = this.findLatestArtifact(artifact, child.getValue());
}
if (latestArtifact != null && latestArtifact.getFile() != null) {
this.logger.debug("The latest artifact was found: {}", latestArtifact);
File file = latestArtifact.getFile();
downloader = new BanConfigurationDownloader(this.session, this.artifactResolver, this.versionRangeResolver, file);
} else if (artifact != null) {
File file = artifact.getFile();
downloader = new BanConfigurationDownloader(this.session, this.artifactResolver, this.versionRangeResolver, file);
}
} else {
this.logger.debug("Unrecognized configuration element ignored: {}: {}", child.getName(), child.getValue());
}
if (downloader != null) {
this.includeArtifacts.addAll(downloader.getIncludeArtifacts());
this.excludeArtifacts.addAll(downloader.getExcludeArtifacts());
}
}
}
private Version findLatestVersion(Artifact artifact, String logId) {
this.logger.trace("Inspecting the local and remote repositories to select the version to import: {}", logId);
VersionRangeRequest vrrequest = new VersionRangeRequest(artifact, this.session.getCurrentProject().getRemoteProjectRepositories(), null);
try {
VersionRangeResult vrresult = this.versionRangeResolver.resolveVersionRange(this.session.getRepositorySession(), vrrequest);
if (vrresult.getVersions().isEmpty()) {
this.logger.info("The artifact version range could not be resolved: {}", logId);
return null;
} else {
this.logger.debug("The artifact version discovered: {}: {}", vrresult.getHighestVersion(), logId);
return vrresult.getHighestVersion();
}
} catch (VersionRangeResolutionException vrre) {
this.logger.error("The artifact version range could not be resolved; skipping: {}", logId);
return null;
}
}
private Artifact findLatestArtifact(Artifact artifact, String logId) {
this.logger.trace("Inspecting the local/remote repositories to select the artifact to import: {}", logId);
ArtifactRequest arequest = new ArtifactRequest(artifact, this.session.getCurrentProject().getRemoteProjectRepositories(), null);
try {
ArtifactResult aresult = this.artifactResolver.resolveArtifact(this.session.getRepositorySession(), arequest);
this.logger.debug("This artifact version discovered: {}: {}", aresult.getArtifact().getVersion(), logId);
return aresult.getArtifact();
} catch (ArtifactResolutionException are) {
this.logger.warn("The artifact could not be resolved; skipping: {}", artifact);
return null;
}
}
private void processIncludesExcludes(Xpp3Dom rootDom) {
Xpp3Dom includesDom = rootDom.getChild("includes");
if (includesDom != null)
this.includeArtifacts.addAll(this.parseArtifacts(includesDom));
Xpp3Dom excludesDom = rootDom.getChild("excludes");
if (excludesDom != null)
this.excludeArtifacts.addAll(this.parseArtifacts(excludesDom));
this.logger.debug("Include artifacts: {}", this.includeArtifacts);
this.logger.debug("Exclude artifacts: {}", this.excludeArtifacts);
}
public List<ArtifactFilter> getIncludeArtifacts() {
return this.includeArtifacts;
}
public List<ArtifactFilter> getExcludeArtifacts() {
return this.excludeArtifacts;
}
private List<ArtifactFilter> parseArtifacts(Xpp3Dom artifactsDom) {
List<ArtifactFilter> filters = new LinkedList<>();
for (Xpp3Dom artifactDom : artifactsDom.getChildren("artifact")) {
ArtifactFilter filter = new ArtifactFilter();
String versionSpec = null;
if (artifactDom.getChildCount() == 0) {
Matcher matcher = this.artifactPattern.matcher(artifactDom.getValue());
if (!matcher.matches()) {
this.logger.warn("The artifact format '{}' does not match the expected regular expression: {}; ignoring ...", artifactDom.getValue(), this.artifactPattern.pattern());
continue;
}
if (this.notRegexPattern.matcher(matcher.group(1)).matches()) {
filter.setGroupId(StringUtils.trimToNull(matcher.group(1)));
} else {
filter.setGroupIdRegex(StringUtils.trimToNull(matcher.group(1)));
}
if (this.notRegexPattern.matcher(matcher.group(2)).matches()) {
filter.setArtifactId(StringUtils.trimToNull(matcher.group(2)));
} else {
filter.setArtifactIdRegex(StringUtils.trimToNull(matcher.group(2)));
}
versionSpec = StringUtils.trimToNull(matcher.group(4));
} else {
filter.setGroupId(this.getChildValue(artifactDom, "groupId"));
filter.setGroupIdRegex(this.getChildValue(artifactDom, "groupIdRegex"));
filter.setArtifactId(this.getChildValue(artifactDom, "artifactId"));
filter.setArtifactIdRegex(this.getChildValue(artifactDom, "artifactIdRegex"));
versionSpec = this.getChildValue(artifactDom, "version");
}
if (versionSpec != null) {
try {
VersionRange versionRange = VersionRange.createFromVersionSpec(versionSpec);
filter.setVersionRange(versionRange);
} catch (InvalidVersionSpecificationException ivse) {
this.logger.warn("The artifact '{}' has an invalid version specification; the artifact element will be ignored: {}", ivse.getMessage());
continue;
}
}
filters.add(filter);
}
return filters;
}
private String getChildValue(Xpp3Dom dom, String child) {
Xpp3Dom childDom = dom.getChild(child);
return childDom == null ? null : StringUtils.trimToNull(childDom.getValue());
}
}

View File

@@ -0,0 +1,27 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.ban;
import java.util.List;
import com.inteligr8.maven.model.ArtifactFilter;
public interface BanConfiguration {
List<ArtifactFilter> getIncludeArtifacts();
List<ArtifactFilter> getExcludeArtifacts();
}

View File

@@ -0,0 +1,82 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.ban;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.eclipse.aether.impl.ArtifactResolver;
import org.eclipse.aether.impl.VersionRangeResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BanConfigurationDownloader extends AbstractBanConfiguration {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public BanConfigurationDownloader(MavenSession session, ArtifactResolver artifactResolver, VersionRangeResolver versionRangeResolver, String url) throws IOException, MojoFailureException {
super(session, artifactResolver, versionRangeResolver);
try {
Xpp3Dom rootDom = this.load(new URL(url));
this.init(rootDom);
} catch (XmlPullParserException xppe) {
throw new MojoFailureException(xppe.getMessage(), xppe);
}
}
public BanConfigurationDownloader(MavenSession session, ArtifactResolver artifactResolver, VersionRangeResolver versionRangeResolver, File file) throws IOException, MojoFailureException {
super(session, artifactResolver, versionRangeResolver);
try {
Xpp3Dom rootDom = this.load(file);
this.init(rootDom);
} catch (XmlPullParserException xppe) {
throw new MojoFailureException(xppe.getMessage(), xppe);
}
}
private Xpp3Dom load(URL url) throws IOException, XmlPullParserException {
InputStream istream = url.openStream();
BufferedInputStream bistream = new BufferedInputStream(istream, 16384);
try {
this.logger.debug("Downloading configuration: {}", url);
return Xpp3DomBuilder.build(bistream, "utf-8");
} finally {
bistream.close();
}
}
private Xpp3Dom load(File file) throws IOException, XmlPullParserException {
FileInputStream fistream = new FileInputStream(file);
BufferedInputStream bistream = new BufferedInputStream(fistream, 16384);
try {
this.logger.debug("Downloading configuration: {}", file);
return Xpp3DomBuilder.build(bistream, "utf-8");
} finally {
bistream.close();
}
}
}

View File

@@ -14,172 +14,22 @@
*/
package com.inteligr8.maven.ban;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.graph.DependencyNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.aether.impl.ArtifactResolver;
import org.eclipse.aether.impl.VersionRangeResolver;
import com.inteligr8.maven.model.ArtifactFilter;
public class BanConfigurationParser implements DependencyFilter {
public class BanConfigurationParser extends AbstractBanConfiguration {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Pattern artifactPattern = Pattern.compile("^([^:]+):([^:]+)(:([^:]+))?$");
private final Pattern notRegexPattern = Pattern.compile("^[A-Za-z0-9_\\.]*$");
private final List<ArtifactFilter> includeArtifacts;
private final List<ArtifactFilter> excludeArtifacts;
private boolean failFast = false;
public BanConfigurationParser(Plugin plugin) {
public BanConfigurationParser(MavenSession session, ArtifactResolver artifactResolver, VersionRangeResolver versionRangeResolver, Plugin plugin) throws IOException, MojoFailureException {
super(session, artifactResolver, versionRangeResolver);
Xpp3Dom rootDom = (Xpp3Dom) plugin.getConfiguration();
if (rootDom == null) {
this.includeArtifacts = Collections.emptyList();
this.excludeArtifacts = Collections.emptyList();
return;
}
Xpp3Dom includesDom = rootDom.getChild("includes");
if (includesDom != null) {
this.includeArtifacts = this.parseArtifacts(includesDom);
this.logger.debug("Include artifacts: {}", this.includeArtifacts);
} else {
this.includeArtifacts = Collections.emptyList();
}
Xpp3Dom excludesDom = rootDom.getChild("excludes");
if (excludesDom != null) {
this.excludeArtifacts = this.parseArtifacts(excludesDom);
this.logger.debug("Exclude artifacts: {}", this.excludeArtifacts);
} else {
this.excludeArtifacts = Collections.emptyList();
}
}
public void setFailFast(boolean failFast) {
this.failFast = failFast;
}
private List<ArtifactFilter> parseArtifacts(Xpp3Dom artifactsDom) {
List<ArtifactFilter> filters = new LinkedList<>();
for (Xpp3Dom artifactDom : artifactsDom.getChildren("artifact")) {
ArtifactFilter filter = new ArtifactFilter();
String versionSpec = null;
if (artifactDom.getChildCount() == 0) {
Matcher matcher = this.artifactPattern.matcher(artifactDom.getValue());
if (!matcher.matches()) {
this.logger.warn("The artifact format '{}' does not match the expected regular expression: {}; ignoring ...", artifactDom.getValue(), this.artifactPattern.pattern());
continue;
}
if (this.notRegexPattern.matcher(matcher.group(1)).matches()) {
filter.setGroupId(StringUtils.trimToNull(matcher.group(1)));
} else {
filter.setGroupIdRegex(StringUtils.trimToNull(matcher.group(1)));
}
if (this.notRegexPattern.matcher(matcher.group(1)).matches()) {
filter.setArtifactId(StringUtils.trimToNull(matcher.group(2)));
} else {
filter.setArtifactIdRegex(StringUtils.trimToNull(matcher.group(2)));
}
versionSpec = StringUtils.trimToNull(matcher.group(4));
} else {
filter.setGroupId(this.getChildValue(artifactDom, "groupId"));
filter.setGroupIdRegex(this.getChildValue(artifactDom, "groupIdRegex"));
filter.setArtifactId(this.getChildValue(artifactDom, "artifactId"));
filter.setArtifactIdRegex(this.getChildValue(artifactDom, "artifactIdRegex"));
versionSpec = this.getChildValue(artifactDom, "version");
}
if (versionSpec != null) {
try {
VersionRange versionRange = VersionRange.createFromVersionSpec(versionSpec);
filter.setVersionRange(versionRange);
} catch (InvalidVersionSpecificationException ivse) {
this.logger.warn("The artifact '{}' has an invalid version specification; the artifact element will be ignored: {}", ivse.getMessage());
continue;
}
}
filters.add(filter);
}
return filters;
}
private String getChildValue(Xpp3Dom dom, String child) {
Xpp3Dom childDom = dom.getChild(child);
return childDom == null ? null : StringUtils.trimToNull(childDom.getValue());
}
@Override
public boolean accept(DependencyNode node, List<DependencyNode> parents) {
this.logger.debug("Evaluating dependency '{}'", node);
boolean ban = false;
for (ArtifactFilter afilter : this.includeArtifacts) {
Artifact depArtifact = node.getArtifact();
if (this.matches(afilter.getGroupId(), afilter.getGroupIdRegex(), depArtifact.getGroupId()) &&
this.matches(afilter.getArtifactId(), afilter.getArtifactIdRegex(), depArtifact.getArtifactId()) &&
this.withinRange(afilter.getVersionRange(), depArtifact.getVersion())) {
this.logger.debug("The dependency '{}' matches the ban inclusion filter", depArtifact);
ban = true;
break;
}
}
if (!ban)
return false;
for (ArtifactFilter afilter : this.excludeArtifacts) {
Artifact depArtifact = node.getArtifact();
if (this.matches(afilter.getGroupId(), afilter.getGroupIdRegex(), depArtifact.getGroupId()) &&
this.matches(afilter.getArtifactId(), afilter.getArtifactIdRegex(), depArtifact.getArtifactId()) &&
this.withinRange(afilter.getVersionRange(), depArtifact.getVersion())) {
this.logger.debug("The dependency '{}' matches the ban exlusion filter", depArtifact);
return false;
}
}
if (this.failFast) {
// plugin resolution downloads banned dependencies unless we fail now; not later
throw new RuntimeException("Banned dependency detected: " + node + " => " + parents);
} else {
return true;
}
}
private boolean matches(String exactFilter, String regexFilter, String value) {
if (exactFilter == null && regexFilter == null) {
return true;
} else if (exactFilter != null) {
return exactFilter.equals(value);
} else {
Pattern filterPattern = Pattern.compile(regexFilter);
Matcher matcher = filterPattern.matcher(value);
return matcher.matches();
}
}
private boolean withinRange(VersionRange versionRange, String version) {
return versionRange == null || versionRange.containsVersion(new DefaultArtifactVersion(version));
this.init(rootDom);
}
}

View File

@@ -0,0 +1,102 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.ban;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.graph.DependencyNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.inteligr8.maven.model.ArtifactFilter;
public class BanDependencyFilter implements DependencyFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final List<ArtifactFilter> includeArtifacts;
private final List<ArtifactFilter> excludeArtifacts;
private boolean failFast = false;
public BanDependencyFilter(List<ArtifactFilter> includeArtifacts, List<ArtifactFilter> excludeArtifacts) {
this.includeArtifacts = includeArtifacts;
this.excludeArtifacts = excludeArtifacts;
}
public void setFailFast(boolean failFast) {
this.failFast = failFast;
}
@Override
public boolean accept(DependencyNode node, List<DependencyNode> parents) {
this.logger.debug("Evaluating dependency '{}'", node);
Artifact depArtifact = node.getArtifact();
boolean ban = false;
for (ArtifactFilter afilter : this.includeArtifacts) {
if (this.matches(afilter.getGroupId(), afilter.getGroupIdRegex(), depArtifact.getGroupId()) &&
this.matches(afilter.getArtifactId(), afilter.getArtifactIdRegex(), depArtifact.getArtifactId()) &&
this.withinRange(afilter.getVersionRange(), depArtifact.getVersion())) {
this.logger.debug("The dependency '{}' matches the ban inclusion filter", depArtifact);
ban = true;
break;
}
}
if (!ban)
return false;
for (ArtifactFilter afilter : this.excludeArtifacts) {
if (this.matches(afilter.getGroupId(), afilter.getGroupIdRegex(), depArtifact.getGroupId()) &&
this.matches(afilter.getArtifactId(), afilter.getArtifactIdRegex(), depArtifact.getArtifactId()) &&
this.withinRange(afilter.getVersionRange(), depArtifact.getVersion())) {
this.logger.debug("The dependency '{}' matches the ban exlusion filter", depArtifact);
return false;
}
}
if (this.failFast) {
// plugin resolution downloads banned dependencies unless we fail now; not later
throw new RuntimeException("Banned dependency detected: " + node + " => " + parents);
} else {
return true;
}
}
private boolean matches(String exactFilter, String regexFilter, String value) {
if (exactFilter == null && regexFilter == null) {
return true;
} else if (exactFilter != null) {
return exactFilter.equals(value);
} else {
Pattern filterPattern = Pattern.compile(regexFilter);
Matcher matcher = filterPattern.matcher(value);
return matcher.matches();
}
}
private boolean withinRange(VersionRange versionRange, String version) {
boolean within = versionRange == null || versionRange.containsVersion(new DefaultArtifactVersion(version));
this.logger.debug("Tested version range: {} <=> {}: {}", versionRange, version, within);
return within;
}
}

View File

@@ -14,6 +14,7 @@
*/
package com.inteligr8.maven.ban;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
@@ -25,8 +26,10 @@ import org.apache.maven.AbstractMavenLifecycleParticipant;
import org.apache.maven.MavenExecutionException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.plugin.internal.PluginDependenciesResolver;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.DefaultDependencyResolutionRequest;
import org.apache.maven.project.DependencyResolutionException;
import org.apache.maven.project.DependencyResolutionResult;
@@ -37,40 +40,65 @@ import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.impl.ArtifactResolver;
import org.eclipse.aether.impl.VersionRangeResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.inteligr8.maven.ban.model.ImportConfig;
@Named("ban")
@Singleton
public class BanExtension extends AbstractMavenLifecycleParticipant {
private static final String THIS_PLUGIN_GROUP_ID = "com.inteligr8";
private static final String THIS_PLUGIN_ARTIFACT_ID = "ban-maven-plugin";
private static final String THIS_PLUGIN_KEY = THIS_PLUGIN_GROUP_ID + ":" + THIS_PLUGIN_ARTIFACT_ID;
public static final String THIS_PLUGIN_GROUP_ID = "com.inteligr8";
public static final String THIS_PLUGIN_ARTIFACT_ID = "ban-maven-plugin";
public static final String THIS_PLUGIN_KEY = THIS_PLUGIN_GROUP_ID + ":" + THIS_PLUGIN_ARTIFACT_ID;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Inject
private ArtifactResolver artifactResolver;
@Inject
private VersionRangeResolver versionRangeResolver;
@Inject
private ProjectDependenciesResolver projDepResolver;
@Inject
private PluginDependenciesResolver pluginDepResolver;
/**
* The configuration is parsed manually. This is here to prevent warning messages with IDEs and builders.
*/
@Parameter(name = "import")
private ImportConfig importConfig;
@Parameter(name = "includes")
private List<String> includes;
@Parameter(name = "excludes")
private List<String> excludes;
@Override
public void afterProjectsRead(MavenSession session) throws MavenExecutionException {
MavenProject project = session.getCurrentProject();
BanConfigurationParser config = this.getConfiguration(project);
BanConfiguration config = this.getConfiguration(session);
if (config == null)
return;
config.setFailFast(true);
BanDependencyFilter depFilter = new BanDependencyFilter(config.getIncludeArtifacts(), config.getExcludeArtifacts());
depFilter.setFailFast(true);
MavenProject project = session.getCurrentProject();
try {
for (Plugin plugin : project.getBuildPlugins()) {
this.logger.debug("Evaluating plugin dependencies: {}", plugin);
Artifact artifact = new DefaultArtifact(plugin.getId());
DependencyNode depNodeRoot = this.pluginDepResolver.resolve(plugin, artifact, config, project.getRemotePluginRepositories(), session.getRepositorySession());
List<Dependency> bannedDependencies = this.crawlDependencyTree(depNodeRoot, config);
DependencyNode depNodeRoot = this.pluginDepResolver.resolve(plugin, artifact, depFilter, project.getRemotePluginRepositories(), session.getRepositorySession());
List<Dependency> bannedDependencies = this.crawlDependencyTree(depNodeRoot, depFilter);
if (!bannedDependencies.isEmpty())
throw new MavenExecutionException("Banned dependencies were detected in plugin '" + plugin + "': " + bannedDependencies, project.getFile());
}
@@ -78,10 +106,10 @@ public class BanExtension extends AbstractMavenLifecycleParticipant {
throw new MavenExecutionException(pre.getMessage(), pre);
}
config.setFailFast(false);
depFilter.setFailFast(false);
DefaultDependencyResolutionRequest request = new DefaultDependencyResolutionRequest(project, session.getRepositorySession());
request.setResolutionFilter(config);
request.setResolutionFilter(depFilter);
try {
DependencyResolutionResult result = this.projDepResolver.resolve(request);
@@ -93,7 +121,8 @@ public class BanExtension extends AbstractMavenLifecycleParticipant {
}
}
private BanConfigurationParser getConfiguration(MavenProject project) throws MavenExecutionException {
private BanConfiguration getConfiguration(MavenSession session) throws MavenExecutionException {
MavenProject project = session.getCurrentProject();
Plugin plugin = project.getPlugin(THIS_PLUGIN_KEY);
if (plugin == null)
throw new MavenExecutionException("The plugin is executing but it cannot be found", project.getFile());
@@ -102,7 +131,11 @@ public class BanExtension extends AbstractMavenLifecycleParticipant {
this.logger.warn("The '{}' plugin must be defined with '<extensions>true</extensions>'; ignoring plugin", plugin.getId());
return null;
} else {
return new BanConfigurationParser(plugin);
try {
return new BanConfigurationParser(session, this.artifactResolver, this.versionRangeResolver, plugin);
} catch (IOException | MojoFailureException e) {
throw new MavenExecutionException(e.getMessage(), project.getFile());
}
}
}

View File

@@ -0,0 +1,347 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.ban;
import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.annotations.Component;
import org.eclipse.aether.impl.ArtifactResolver;
import org.eclipse.aether.impl.VersionRangeResolver;
import com.inteligr8.maven.model.ArtifactFilter;
@Mojo( name = "purge-repo", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class PurgeRepoMojo extends AbstractMojo {
@Inject
private MavenSession session;
@Inject
private ArtifactResolver artifactResolver;
@Inject
private VersionRangeResolver versionRangeResolver;
@Parameter(name = "skip", defaultValue = "false")
private boolean skip = false;
@Parameter(name = "dryRun", defaultValue = "false")
private boolean dryRun = false;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (this.skip) {
this.getLog().debug("Skipping purge of banned artifacts");
} else {
this.getLog().info("Purging banned artifacts from local repository: " + this.session.getLocalRepository().getBasedir());
try {
this.purge();
} catch (IOException ie) {
throw new MojoFailureException(ie.getMessage(), ie);
}
}
}
private void purge() throws MojoFailureException, IOException {
List<Path> includePaths = new LinkedList<>();
BanConfigurationParser config = this.getConfiguration();
for (ArtifactFilter afilter : config.getIncludeArtifacts()) {
if (afilter.getGroupId() == null) {
this.getLog().warn("The purge does not support wildcard group ID specifications, so skipping it: " + afilter);
continue;
}
Path groupPath = this.resolveGroupPath(afilter);
if (groupPath == null)
continue;
List<Path> artifactPaths = this.resolveArtifactPaths(groupPath, afilter);
for (Path artifactPath : artifactPaths)
includePaths.addAll(this.resolveVersionPaths(artifactPath, afilter.getVersionRange()));
}
this.getLog().debug("May be purging all files in " + includePaths.size() + " paths");
String regexDirectorySeparator = String.valueOf(File.separatorChar);
if (File.separatorChar == '\\')
regexDirectorySeparator += "\\";
for (ArtifactFilter afilter : config.getExcludeArtifacts()) {
StringBuilder regex = new StringBuilder();
if (afilter.getGroupId() != null) {
regex.append('^').append(this.resolveGroupPath(afilter));
} else if (afilter.getGroupIdRegex() != null) {
regex.append(afilter.getGroupIdRegex().replace("\\.", regexDirectorySeparator));
if (regex.charAt(0) != '^')
regex.insert(0, '^');
} else {
regex.append("^.+");
}
regex.append(regexDirectorySeparator);
if (afilter.getArtifactId() != null) {
regex.append(afilter.getArtifactId());
} else if (afilter.getArtifactIdRegex() != null) {
regex.append(afilter.getArtifactIdRegex());
} else {
regex.append("[^").append(regexDirectorySeparator).append("]+");
}
Pattern pattern = Pattern.compile(regex.toString());
Iterator<Path> i = includePaths.iterator();
while (i.hasNext()) {
Path path = i.next();
Matcher matcher = pattern.matcher(path.toString());
if (!matcher.find())
continue;
// group/artifact match; now for version
if (afilter.getVersionRange() == null) {
i.remove();
} else {
String version = path.getFileName().toString();
if (afilter.getVersionRange().containsVersion(new DefaultArtifactVersion(version)))
i.remove();
}
}
}
Path repoPath = this.getRepositoryPath();
if (this.dryRun) {
this.getLog().info("DRYRUN: Would have deleted certain paths from local Maven cache: " + repoPath);
this.getLog().info("DRYRUN: Would have deleted these paths: " + includePaths);
} else {
for (Path versionPath : includePaths) {
Path fullVersionPath = repoPath.resolve(versionPath);
if (Files.exists(fullVersionPath)) {
this.getLog().info("Deleting version from Maven cache: " + versionPath);
Files.walkFileTree(fullVersionPath, new DeleteNonMetadataVisitor());
} else {
// this will probably never happen
this.getLog().debug("Maven cache does not exist: " + versionPath);
}
}
}
}
private BanConfigurationParser getConfiguration() throws MojoFailureException, IOException {
MavenProject project = this.session.getCurrentProject();
Plugin plugin = project.getPlugin(BanExtension.THIS_PLUGIN_KEY);
if (plugin == null)
throw new MojoFailureException("The plugin is executing but it cannot be found");
return new BanConfigurationParser(this.session, this.artifactResolver, this.versionRangeResolver, plugin);
}
private Path resolveGroupPath(ArtifactFilter afilter) {
if (afilter.getGroupId() == null)
return null;
String[] pathElements = afilter.getGroupId().split("\\.");
Path groupPath = Paths.get("");
for (String pathElement : pathElements)
groupPath = groupPath.resolve(pathElement);
Path fullGroupPath = this.getRepositoryPath().resolve(groupPath);
if (!Files.exists(fullGroupPath)) {
this.getLog().debug("The group path does not exist, so nothing to purge: " + fullGroupPath);
return null;
}
return groupPath;
}
private List<Path> resolveArtifactPaths(Path groupPath, ArtifactFilter afilter) throws IOException {
Path repoPath = this.getRepositoryPath();
if (afilter.getArtifactId() != null) {
Path artifactPath = groupPath.resolve(afilter.getArtifactId());
Path fullArtifactPath = repoPath.resolve(artifactPath);
if (Files.exists(fullArtifactPath)) {
return Arrays.asList(artifactPath);
} else {
this.getLog().debug("The artifact path does not exist, so nothing to purge: " + fullArtifactPath);
return Collections.emptyList();
}
}
Pattern artifactPattern = afilter.getArtifactIdRegex() == null ? null : Pattern.compile(afilter.getArtifactIdRegex());
List<Path> paths = new LinkedList<>();
if (artifactPattern == null)
this.getLog().debug("All artifact directories in '" + groupPath + "' qualify as included");
Files.list(repoPath.resolve(groupPath)).forEach(new Consumer<Path>() {
@Override
public void accept(Path fullArtifactPath) {
if (artifactPattern == null) {
// these may include sub-groups and not just artifacts
// which will lead to paths with artifacts as versions
// so we are looping through versions to see if it is indeed an artifact
try {
Files.list(fullArtifactPath).findFirst().ifPresent(new Consumer<Path>() {
@Override
public void accept(Path fullVersionPath) {
if (Files.exists(fullVersionPath.resolve("_remote.repositories")))
paths.add(repoPath.relativize(fullArtifactPath));
}
});
} catch (IOException ie) {
getLog().error(ie.getMessage(), ie);
}
} else {
Matcher matcher = artifactPattern.matcher(fullArtifactPath.getFileName().toString());
if (matcher.matches()) {
getLog().debug("The artifact directory '" + fullArtifactPath.getFileName() + "' qualifies as included");
paths.add(repoPath.relativize(fullArtifactPath));
}
}
}
});
return paths;
}
private List<Path> resolveVersionPaths(Path artifactPath, VersionRange versionRange) throws IOException {
Path repoPath = this.getRepositoryPath();
List<Path> paths = new LinkedList<>();
if (versionRange == null)
this.getLog().debug("All artifact version directories in '" + artifactPath + "' qualify as included");
Files.list(repoPath.resolve(artifactPath)).forEach(new Consumer<Path>() {
@Override
public void accept(Path t) {
if (versionRange == null) {
paths.add(repoPath.relativize(t));
} else {
ArtifactVersion artifactVersion = new DefaultArtifactVersion(t.getFileName().toString());
if (versionRange.containsVersion(artifactVersion)) {
getLog().debug("The artifact version directory '" + t.getFileName() + "' qualifies as included");
paths.add(repoPath.relativize(t));
}
}
}
});
return paths;
}
private Path getRepositoryPath() {
return new File(this.session.getLocalRepository().getBasedir()).toPath();
}
private class DeleteNonMetadataVisitor implements FileVisitor<Path> {
private final Pattern versionPathPattern = Pattern.compile("/([^/]+)/([^/]+)/[^/]+$");
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (attrs.isDirectory()) {
getLog().warn("An unexpected directory was found: " + file);
return FileVisitResult.SKIP_SUBTREE;
}
Matcher matcher = this.versionPathPattern.matcher(file.toString());
if (!matcher.find()) {
getLog().debug("Ignoring file from purge: " + file);
return FileVisitResult.CONTINUE;
}
String artifactId = matcher.group(1);
String version = matcher.group(2);
String includeName = artifactId + "-" + version;
String excludeName = artifactId + "-" + version + ".pom";
getLog().debug("artifact-version: " + includeName);
if (file.getFileName().toString().startsWith(includeName) &&
!file.getFileName().toString().startsWith(excludeName)) {
try {
getLog().info("Deleting artifact: " + file);
Files.delete(file);
} catch (IOException ie) {
getLog().debug(ie);
getLog().warn("The file failed to delete: " + file);
return FileVisitResult.SKIP_SIBLINGS;
}
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
try {
Files.delete(dir);
} catch (DirectoryNotEmptyException dnee) {
getLog().debug("The folder will not be deleted as it is not empty: " + dir);
} catch (IOException ie) {
getLog().debug(ie);
getLog().warn("The folder failed to delete: " + dir);
}
return FileVisitResult.CONTINUE;
}
}
}

View File

@@ -0,0 +1,11 @@
package com.inteligr8.maven.ban.model;
import java.util.List;
public class Config {
public ImportConfig importConfigs;
public List<String> includes;
public List<String> excludes;
}

View File

@@ -0,0 +1,10 @@
package com.inteligr8.maven.ban.model;
import java.util.List;
public class ImportConfig {
public List<String> url;
public List<String> artifact;
}