Allow custom dependency metadata to be used with the CLI
Add support for a new annotation, @GrabMetadata, that can be used to provide the coordinates of one or more properties files, such as the one published by Spring IO Platform, as a source of dependency metadata. For example: @GrabMetadata("com.example:metadata:1.0.0") The referenced properties files must be in the format group:module=version. Limitations: - Only a single @GrabMetadata annotation is supported - The referenced properties file must be accessible in one of the default repositories, i.e. it cannot be accessed in a repository that's added using @GrabResolver Closes #814pull/890/merge
parent
3f498a4803
commit
d2fc80b818
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.AnnotatedNode;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ImportNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
|
||||
/**
|
||||
* A base class for {@link ASTTransformation AST transformations} that are solely
|
||||
* interested in {@link AnnotatedNode AnnotatedNodes}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public abstract class AnnotatedNodeASTTransformation implements ASTTransformation {
|
||||
|
||||
private final Set<String> interestingAnnotationNames;
|
||||
|
||||
private List<AnnotationNode> annotationNodes = new ArrayList<AnnotationNode>();
|
||||
|
||||
private SourceUnit sourceUnit;
|
||||
|
||||
protected AnnotatedNodeASTTransformation(Set<String> interestingAnnotationNames) {
|
||||
this.interestingAnnotationNames = interestingAnnotationNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ASTNode[] nodes, SourceUnit source) {
|
||||
this.sourceUnit = source;
|
||||
|
||||
ClassVisitor classVisitor = new ClassVisitor(source);
|
||||
for (ASTNode node : nodes) {
|
||||
if (node instanceof ModuleNode) {
|
||||
ModuleNode module = (ModuleNode) node;
|
||||
|
||||
visitAnnotatedNode(module.getPackage());
|
||||
|
||||
for (ImportNode importNode : module.getImports()) {
|
||||
visitAnnotatedNode(importNode);
|
||||
}
|
||||
for (ImportNode importNode : module.getStarImports()) {
|
||||
visitAnnotatedNode(importNode);
|
||||
}
|
||||
for (Map.Entry<String, ImportNode> entry : module.getStaticImports()
|
||||
.entrySet()) {
|
||||
visitAnnotatedNode(entry.getValue());
|
||||
}
|
||||
for (Map.Entry<String, ImportNode> entry : module.getStaticStarImports()
|
||||
.entrySet()) {
|
||||
visitAnnotatedNode(entry.getValue());
|
||||
}
|
||||
|
||||
for (ClassNode classNode : module.getClasses()) {
|
||||
visitAnnotatedNode(classNode);
|
||||
classNode.visitContents(classVisitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processAnnotationNodes(this.annotationNodes);
|
||||
}
|
||||
|
||||
protected SourceUnit getSourceUnit() {
|
||||
return this.sourceUnit;
|
||||
}
|
||||
|
||||
protected abstract void processAnnotationNodes(List<AnnotationNode> annotationNodes);
|
||||
|
||||
private void visitAnnotatedNode(AnnotatedNode annotatedNode) {
|
||||
if (annotatedNode != null) {
|
||||
for (AnnotationNode annotationNode : annotatedNode.getAnnotations()) {
|
||||
if (this.interestingAnnotationNames.contains(annotationNode
|
||||
.getClassNode().getName())) {
|
||||
this.annotationNodes.add(annotationNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ClassVisitor extends ClassCodeVisitorSupport {
|
||||
|
||||
private final SourceUnit source;
|
||||
|
||||
public ClassVisitor(SourceUnit source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SourceUnit getSourceUnit() {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAnnotations(AnnotatedNode node) {
|
||||
visitAnnotatedNode(node);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.aether.graph.Dependency;
|
||||
import org.springframework.boot.cli.compiler.dependencies.ArtifactCoordinatesResolver;
|
||||
import org.springframework.boot.cli.compiler.dependencies.ManagedDependenciesArtifactCoordinatesResolver;
|
||||
import org.springframework.boot.cli.compiler.grape.ManagedDependenciesFactory;
|
||||
import org.springframework.boot.dependency.tools.ManagedDependencies;
|
||||
|
||||
/**
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class DependencyResolutionContext {
|
||||
|
||||
private ArtifactCoordinatesResolver artifactCoordinatesResolver;
|
||||
|
||||
private List<Dependency> managedDependencies = new ArrayList<Dependency>();
|
||||
|
||||
public DependencyResolutionContext() {
|
||||
this(new ManagedDependenciesArtifactCoordinatesResolver());
|
||||
}
|
||||
|
||||
DependencyResolutionContext(ArtifactCoordinatesResolver artifactCoordinatesResolver) {
|
||||
this.artifactCoordinatesResolver = artifactCoordinatesResolver;
|
||||
}
|
||||
|
||||
void setManagedDependencies(ManagedDependencies managedDependencies) {
|
||||
this.artifactCoordinatesResolver = new ManagedDependenciesArtifactCoordinatesResolver(
|
||||
managedDependencies);
|
||||
this.managedDependencies = new ArrayList<Dependency>(
|
||||
new ManagedDependenciesFactory(managedDependencies)
|
||||
.getManagedDependencies());
|
||||
|
||||
}
|
||||
|
||||
ArtifactCoordinatesResolver getArtifactCoordinatesResolver() {
|
||||
return this.artifactCoordinatesResolver;
|
||||
}
|
||||
|
||||
public List<Dependency> getManagedDependencies() {
|
||||
return this.managedDependencies;
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
|
||||
import groovy.grape.Grape;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
import org.codehaus.groovy.ast.expr.Expression;
|
||||
import org.codehaus.groovy.ast.expr.ListExpression;
|
||||
import org.codehaus.groovy.control.messages.Message;
|
||||
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
|
||||
import org.codehaus.groovy.syntax.SyntaxException;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
import org.springframework.boot.dependency.tools.ManagedDependencies;
|
||||
import org.springframework.boot.dependency.tools.PropertiesFileManagedDependencies;
|
||||
import org.springframework.boot.dependency.tools.VersionManagedDependencies;
|
||||
import org.springframework.boot.groovy.GrabMetadata;
|
||||
|
||||
/**
|
||||
* {@link ASTTransformation} for processing {@link GrabMetadata @GrabMetadata}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class GrabMetadataTransformation extends AnnotatedNodeASTTransformation {
|
||||
|
||||
private static final Set<String> GRAB_METADATA_ANNOTATION_NAMES = Collections
|
||||
.unmodifiableSet(new HashSet<String>(Arrays.asList(
|
||||
GrabMetadata.class.getName(), GrabMetadata.class.getSimpleName())));
|
||||
|
||||
private final DependencyResolutionContext resolutionContext;
|
||||
|
||||
public GrabMetadataTransformation(DependencyResolutionContext resolutionContext) {
|
||||
super(GRAB_METADATA_ANNOTATION_NAMES);
|
||||
this.resolutionContext = resolutionContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processAnnotationNodes(List<AnnotationNode> annotationNodes) {
|
||||
if (!annotationNodes.isEmpty()) {
|
||||
if (annotationNodes.size() > 1) {
|
||||
for (AnnotationNode annotationNode : annotationNodes) {
|
||||
handleDuplicateGrabMetadataAnnotation(annotationNode);
|
||||
}
|
||||
}
|
||||
else {
|
||||
processGrabMetadataAnnotation(annotationNodes.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processGrabMetadataAnnotation(AnnotationNode annotationNode) {
|
||||
Expression valueExpression = annotationNode.getMember("value");
|
||||
|
||||
List<Map<String, String>> metadataDependencies = createDependencyMaps(valueExpression);
|
||||
updateArtifactCoordinatesResolver(metadataDependencies);
|
||||
}
|
||||
|
||||
private List<Map<String, String>> createDependencyMaps(Expression valueExpression) {
|
||||
Map<String, String> dependency = null;
|
||||
|
||||
List<ConstantExpression> constantExpressions = new ArrayList<ConstantExpression>();
|
||||
|
||||
if (valueExpression instanceof ListExpression) {
|
||||
ListExpression listExpression = (ListExpression) valueExpression;
|
||||
for (Expression expression : listExpression.getExpressions()) {
|
||||
if (expression instanceof ConstantExpression
|
||||
&& ((ConstantExpression) expression).getValue() instanceof String) {
|
||||
constantExpressions.add((ConstantExpression) expression);
|
||||
}
|
||||
else {
|
||||
reportError(
|
||||
"Each entry in the array must be an inline string constant",
|
||||
expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (valueExpression instanceof ConstantExpression
|
||||
&& ((ConstantExpression) valueExpression).getValue() instanceof String) {
|
||||
constantExpressions = Arrays.asList((ConstantExpression) valueExpression);
|
||||
}
|
||||
else {
|
||||
reportError(
|
||||
"@GrabMetadata requires an inline constant that is a string or a string array",
|
||||
valueExpression);
|
||||
}
|
||||
|
||||
List<Map<String, String>> dependencies = new ArrayList<Map<String, String>>(
|
||||
constantExpressions.size());
|
||||
|
||||
for (ConstantExpression expression : constantExpressions) {
|
||||
Object value = expression.getValue();
|
||||
if (value instanceof String) {
|
||||
String[] components = ((String) expression.getValue()).split(":");
|
||||
if (components.length == 3) {
|
||||
dependency = new HashMap<String, String>();
|
||||
dependency.put("group", components[0]);
|
||||
dependency.put("module", components[1]);
|
||||
dependency.put("version", components[2]);
|
||||
dependency.put("type", "properties");
|
||||
|
||||
dependencies.add(dependency);
|
||||
}
|
||||
else {
|
||||
handleMalformedDependency(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private void handleMalformedDependency(Expression expression) {
|
||||
Message message = createSyntaxErrorMessage(
|
||||
"The string must be of the form \"group:module:version\"\n", expression);
|
||||
getSourceUnit().getErrorCollector().addErrorAndContinue(message);
|
||||
}
|
||||
|
||||
private void updateArtifactCoordinatesResolver(
|
||||
List<Map<String, String>> metadataDependencies) {
|
||||
URI[] uris = Grape.getInstance().resolve(null,
|
||||
metadataDependencies.toArray(new Map[metadataDependencies.size()]));
|
||||
List<ManagedDependencies> managedDependencies = new ArrayList<ManagedDependencies>(
|
||||
uris.length);
|
||||
for (URI uri : uris) {
|
||||
try {
|
||||
managedDependencies.add(new PropertiesFileManagedDependencies(uri.toURL()
|
||||
.openStream()));
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to parse '" + uris[0]
|
||||
+ "'. Is it a valid properties file?");
|
||||
}
|
||||
}
|
||||
|
||||
this.resolutionContext.setManagedDependencies(new VersionManagedDependencies(
|
||||
managedDependencies));
|
||||
}
|
||||
|
||||
private void handleDuplicateGrabMetadataAnnotation(AnnotationNode annotationNode) {
|
||||
Message message = createSyntaxErrorMessage(
|
||||
"Duplicate @GrabMetadata annotation. It must be declared at most once.",
|
||||
annotationNode);
|
||||
getSourceUnit().getErrorCollector().addErrorAndContinue(message);
|
||||
}
|
||||
|
||||
private void reportError(String message, ASTNode node) {
|
||||
getSourceUnit().getErrorCollector().addErrorAndContinue(
|
||||
createSyntaxErrorMessage(message, node));
|
||||
}
|
||||
|
||||
private Message createSyntaxErrorMessage(String message, ASTNode node) {
|
||||
return new SyntaxErrorMessage(new SyntaxException(message, node.getLineNumber(),
|
||||
node.getColumnNumber(), node.getLastLineNumber(),
|
||||
node.getLastColumnNumber()), getSourceUnit());
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.groovy;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to provide an alternative source of dependency metadata that is used to deduce
|
||||
* groups and versions when processing {@code @Grab} dependencies.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE,
|
||||
ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface GrabMetadata {
|
||||
|
||||
/**
|
||||
* One or more sets of colon-separated coordinates ({@code group:module:version}) of a
|
||||
* properties file that contains dependency metadata that will add to and override the
|
||||
* default metadata.
|
||||
*/
|
||||
String[] value();
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
@org.springframework.boot.groovy.GrabMetadata('test:test:1.0.0')
|
||||
@Grab('ejb-api')
|
||||
class CustomGrabMetadata {
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
@GrabMetadata("foo:bar:1.0")
|
||||
@GrabMetadata("alpha:bravo:2.0")
|
||||
class DuplicateGrabMetadata {
|
||||
|
||||
}
|
Loading…
Reference in New Issue