diff --git a/ivy.xml b/ivy.xml
index a695f6f5..065ca7ca 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -26,6 +26,7 @@
+
diff --git a/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java b/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java
index 3a59c9fc..5ecb0328 100644
--- a/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java
+++ b/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java
@@ -18,9 +18,15 @@
import static de.thetaphi.forbiddenapis.Checker.Option.*;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.factory.ArtifactFactory;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
+import org.apache.maven.artifact.resolver.ArtifactResolutionException;
+import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
-import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.util.DirectoryScanner;
@@ -33,6 +39,8 @@
import java.io.File;
import java.io.IOException;
import java.lang.annotation.RetentionPolicy;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URLClassLoader;
import java.net.MalformedURLException;
import java.net.URL;
@@ -41,6 +49,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
/**
* Base class for forbiddenapis Mojos.
@@ -56,6 +65,36 @@ public abstract class AbstractCheckMojo extends AbstractMojo {
@Parameter(required = false)
private File[] signaturesFiles;
+ /**
+ * Lists all Maven artifacts, which contain signatures and comments for forbidden API calls.
+ * The artifact needs to be specified like a Maven dependency. Resolution is not transitive.
+ * You can refer to plain text Maven artifacts ({@code type="txt"}, e.g., with a separate {@code classifier}):
+ *
+ * <signaturesArtifact>
+ * <groupId>org.apache.foobar</groupId>
+ * <artifactId>example</artifactId>
+ * <version>1.0</version>
+ * <classifier>signatures</classifier>
+ * <type>txt</type>
+ * </signaturesArtifact>
+ *
+ * Alternatively, refer to signatures files inside JAR artifacts. In that case, the additional
+ * parameter {@code path} has to be given:
+ *
+ * <signaturesArtifact>
+ * <groupId>org.apache.foobar</groupId>
+ * <artifactId>example</artifactId>
+ * <version>1.0</version>
+ * <type>jar</type>
+ * <path>path/inside/jar/file/signatures.txt</path>
+ * </signaturesArtifact>
+ *
+ * The signatures are resolved against the compile classpath.
+ * @since 2.0
+ */
+ @Parameter(required = false)
+ private SignaturesArtifact[] signaturesArtifacts;
+
/**
* Gives a multiline list of signatures, inline in the pom.xml. Use an XML CDATA section to do that!
* The signatures are resolved against the compile classpath.
@@ -163,6 +202,18 @@ public abstract class AbstractCheckMojo extends AbstractMojo {
/** The project packaging (pom, jar, etc.). */
@Parameter(defaultValue = "${project.packaging}", readonly = true, required = true)
private String packaging;
+
+ @Component
+ private ArtifactFactory artifactFactory;
+
+ @Component
+ private ArtifactResolver artifactResolver;
+
+ @Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true)
+ private List remoteRepositories;
+
+ @Parameter(defaultValue = "${localRepository}", readonly = true, required = true)
+ private ArtifactRepository localRepository;
/** provided by the concrete Mojos for compile and test classes processing */
protected abstract List getClassPathElements();
@@ -174,10 +225,46 @@ public abstract class AbstractCheckMojo extends AbstractMojo {
protected String getTargetVersion() {
return targetVersion;
}
+
+ private File resolveSignaturesArtifact(SignaturesArtifact signaturesArtifact) throws ArtifactResolutionException, ArtifactNotFoundException {
+ final Artifact artifact = signaturesArtifact.createArtifact(artifactFactory);
+ artifactResolver.resolve(artifact, this.remoteRepositories, this.localRepository);
+ return artifact.getFile();
+ }
+
+ private String encodeUrlPath(String path) {
+ try {
+ // hack to encode the URL path by misusing URI class:
+ return new URI(null, path, null).toASCIIString();
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+
+ private URL createJarUrl(File f, String jarPath) throws MalformedURLException {
+ final URL fileUrl = f.toURI().toURL();
+ final URL jarBaseUrl = new URL("jar", null, fileUrl.toExternalForm() + "!/");
+ return new URL(jarBaseUrl, encodeUrlPath(jarPath));
+ }
@Override
public void execute() throws MojoExecutionException {
- final Log log = getLog();
+ final Logger log = new Logger() {
+ @Override
+ public void error(String msg) {
+ getLog().error(msg);
+ }
+
+ @Override
+ public void warn(String msg) {
+ getLog().warn(msg);
+ }
+
+ @Override
+ public void info(String msg) {
+ getLog().info(msg);
+ }
+ };
if (skip) {
log.info("Skipping forbidden-apis checks.");
@@ -202,7 +289,6 @@ public void execute() throws MojoExecutionException {
urls[i++] = new File(cpElement).toURI().toURL();
}
assert i == urls.length;
- if (log.isDebugEnabled()) log.debug("Compile Classpath: " + Arrays.toString(urls));
} catch (MalformedURLException e) {
throw new MojoExecutionException("Failed to build classpath.", e);
}
@@ -218,22 +304,7 @@ public void execute() throws MojoExecutionException {
if (failOnMissingClasses) options.add(FAIL_ON_MISSING_CLASSES);
if (failOnViolation) options.add(FAIL_ON_VIOLATION);
if (failOnUnresolvableSignatures) options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
- final Checker checker = new Checker(new Logger() {
- @Override
- public void error(String msg) {
- log.error(msg);
- }
-
- @Override
- public void warn(String msg) {
- log.warn(msg);
- }
-
- @Override
- public void info(String msg) {
- log.info(msg);
- }
- }, loader, options);
+ final Checker checker = new Checker(log, loader, options);
if (!checker.isSupportedJDK) {
final String msg = String.format(Locale.ENGLISH,
@@ -287,9 +358,27 @@ public void info(String msg) {
checker.parseBundledSignatures(bs, targetVersion);
}
}
- if (signaturesFiles != null) for (final File f : new LinkedHashSet(Arrays.asList(signaturesFiles))) {
+ final Set sigFiles = new LinkedHashSet();
+ final Set sigUrls = new LinkedHashSet();
+ if (signaturesFiles != null) {
+ sigFiles.addAll(Arrays.asList(signaturesFiles));
+ }
+ if (signaturesArtifacts != null) {
+ for (final SignaturesArtifact artifact : signaturesArtifacts) {
+ final File f = resolveSignaturesArtifact(artifact);
+ if (artifact.path != null) {
+ sigUrls.add(createJarUrl(f, artifact.path));
+ } else {
+ sigFiles.add(f);
+ }
+ }
+ }
+ for (final File f : sigFiles) {
checker.parseSignaturesFile(f);
}
+ for (final URL u : sigUrls) {
+ checker.parseSignaturesFile(u);
+ }
final String sig = (signatures != null) ? signatures.trim() : null;
if (sig != null && sig.length() != 0) {
checker.parseSignaturesString(sig);
@@ -298,11 +387,15 @@ public void info(String msg) {
throw new MojoExecutionException("IO problem while reading files with API signatures.", ioe);
} catch (ParseException pe) {
throw new MojoExecutionException("Parsing signatures failed: " + pe.getMessage(), pe);
+ } catch (ArtifactResolutionException e) {
+ throw new MojoExecutionException("Problem while resolving Maven artifact.", e);
+ } catch (ArtifactNotFoundException e) {
+ throw new MojoExecutionException("Maven artifact does not exist.", e);
}
if (checker.hasNoSignatures()) {
if (failOnUnresolvableSignatures) {
- throw new MojoExecutionException("No API signatures found; use parameters 'signatures', 'bundledSignatures', and/or 'signaturesFiles' to define those!");
+ throw new MojoExecutionException("No API signatures found; use parameters 'signatures', 'bundledSignatures', 'signaturesFiles', and/or 'signaturesArtifacts' to define those!");
} else {
log.info("Skipping execution because no API signatures are available.");
return;
diff --git a/src/main/java/de/thetaphi/forbiddenapis/maven/SignaturesArtifact.java b/src/main/java/de/thetaphi/forbiddenapis/maven/SignaturesArtifact.java
new file mode 100644
index 00000000..01da5c11
--- /dev/null
+++ b/src/main/java/de/thetaphi/forbiddenapis/maven/SignaturesArtifact.java
@@ -0,0 +1,60 @@
+package de.thetaphi.forbiddenapis.maven;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.factory.ArtifactFactory;
+
+/*
+ * (C) Copyright Uwe Schindler (Generics Policeman) and others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Defines coordinates of a Maven artifact that provides signatures files.
+ * It may be a plain text file ({@link #path} is not given) or alternatively
+ * refer to a text file inside a JAR file. For that, define the resource path
+ * using {@link #path}.
+ * @since 2.0
+ */
+public final class SignaturesArtifact {
+
+ /** Artifact's group ID (required) */
+ public String groupId;
+
+ /** Artifact's ID (required) */
+ public String artifactId;
+
+ /** Version (required) */
+ public String version;
+
+ /** Classifier (optional) */
+ public String classifier;
+
+ /**
+ * Type (required; {@code txt} or {@code jar}).
+ * If the artifact refers to a JAR file, the {@link #path} should be
+ * given, that identifies the signatures file inside the JAR.
+ * */
+ public String type;
+
+ /** Path to resource inside JAR artifacts. If given, the {@link #type} must be {@code "jar"} or {@code "zip"}. */
+ public String path;
+
+ /** Used by the mojo to fetch the artifact */
+ Artifact createArtifact(ArtifactFactory artifactFactory) {
+ if (groupId == null || artifactId == null || version == null || type == null) {
+ throw new NullPointerException("signaturesArtifact is missing some properties. Required are: groupId, artifactId, version, type");
+ }
+ return artifactFactory.createArtifactWithClassifier(groupId, artifactId, version, type, classifier);
+ }
+}
diff --git a/src/test/antunit/TestMavenMojo.xml b/src/test/antunit/TestMavenMojo.xml
index 4c0edeb0..821eaa9f 100644
--- a/src/test/antunit/TestMavenMojo.xml
+++ b/src/test/antunit/TestMavenMojo.xml
@@ -109,4 +109,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/antunit/pom-sigArtifacts.xml b/src/test/antunit/pom-sigArtifacts.xml
new file mode 100644
index 00000000..d5a60df1
--- /dev/null
+++ b/src/test/antunit/pom-sigArtifacts.xml
@@ -0,0 +1,56 @@
+
+
+
+ 4.0.0
+ de.thetaphi
+ forbiddenapi-test3
+ 0.0-SNAPSHOT
+ Dummy Project to test signatures from Maven artifacts
+
+
+
+
+ ${groupId}
+ ${artifactId}
+ ${version}
+
+ .
+ false
+ false
+
+
+ ${groupId}
+ ${artifactId}
+ ${version}
+ jar
+ de/thetaphi/forbiddenapis/signatures/jdk-deprecated-${jdk.version}.txt
+
+
+
+
+
+
+ check
+
+
+
+
+
+
+
+