001/* 002 * Copyright 2012-2016 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.cli.compiler; 018 019import java.net.MalformedURLException; 020import java.net.URI; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029 030import groovy.grape.Grape; 031import org.apache.maven.model.Model; 032import org.apache.maven.model.Repository; 033import org.apache.maven.model.building.DefaultModelBuilder; 034import org.apache.maven.model.building.DefaultModelBuilderFactory; 035import org.apache.maven.model.building.DefaultModelBuildingRequest; 036import org.apache.maven.model.building.ModelSource; 037import org.apache.maven.model.building.UrlModelSource; 038import org.apache.maven.model.resolution.InvalidRepositoryException; 039import org.apache.maven.model.resolution.ModelResolver; 040import org.apache.maven.model.resolution.UnresolvableModelException; 041import org.codehaus.groovy.ast.ASTNode; 042import org.codehaus.groovy.ast.AnnotationNode; 043import org.codehaus.groovy.ast.expr.ConstantExpression; 044import org.codehaus.groovy.ast.expr.Expression; 045import org.codehaus.groovy.ast.expr.ListExpression; 046import org.codehaus.groovy.control.messages.Message; 047import org.codehaus.groovy.control.messages.SyntaxErrorMessage; 048import org.codehaus.groovy.syntax.SyntaxException; 049import org.codehaus.groovy.transform.ASTTransformation; 050 051import org.springframework.boot.cli.compiler.dependencies.MavenModelDependencyManagement; 052import org.springframework.boot.cli.compiler.grape.DependencyResolutionContext; 053import org.springframework.boot.groovy.DependencyManagementBom; 054import org.springframework.core.Ordered; 055import org.springframework.core.annotation.Order; 056 057/** 058 * {@link ASTTransformation} for processing {@link DependencyManagementBom} annotations. 059 * 060 * @author Andy Wilkinson 061 * @since 1.3.0 062 */ 063@Order(DependencyManagementBomTransformation.ORDER) 064public class DependencyManagementBomTransformation 065 extends AnnotatedNodeASTTransformation { 066 067 /** 068 * The order of the transformation. 069 */ 070 public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 100; 071 072 private static final Set<String> DEPENDENCY_MANAGEMENT_BOM_ANNOTATION_NAMES = Collections 073 .unmodifiableSet(new HashSet<String>( 074 Arrays.asList(DependencyManagementBom.class.getName(), 075 DependencyManagementBom.class.getSimpleName()))); 076 077 private final DependencyResolutionContext resolutionContext; 078 079 public DependencyManagementBomTransformation( 080 DependencyResolutionContext resolutionContext) { 081 super(DEPENDENCY_MANAGEMENT_BOM_ANNOTATION_NAMES, true); 082 this.resolutionContext = resolutionContext; 083 } 084 085 @Override 086 protected void processAnnotationNodes(List<AnnotationNode> annotationNodes) { 087 if (!annotationNodes.isEmpty()) { 088 if (annotationNodes.size() > 1) { 089 for (AnnotationNode annotationNode : annotationNodes) { 090 handleDuplicateDependencyManagementBomAnnotation(annotationNode); 091 } 092 } 093 else { 094 processDependencyManagementBomAnnotation(annotationNodes.get(0)); 095 } 096 } 097 } 098 099 private void processDependencyManagementBomAnnotation(AnnotationNode annotationNode) { 100 Expression valueExpression = annotationNode.getMember("value"); 101 List<Map<String, String>> bomDependencies = createDependencyMaps(valueExpression); 102 updateDependencyResolutionContext(bomDependencies); 103 } 104 105 private List<Map<String, String>> createDependencyMaps(Expression valueExpression) { 106 Map<String, String> dependency = null; 107 List<ConstantExpression> constantExpressions = getConstantExpressions( 108 valueExpression); 109 List<Map<String, String>> dependencies = new ArrayList<Map<String, String>>( 110 constantExpressions.size()); 111 for (ConstantExpression expression : constantExpressions) { 112 Object value = expression.getValue(); 113 if (value instanceof String) { 114 String[] components = ((String) expression.getValue()).split(":"); 115 if (components.length == 3) { 116 dependency = new HashMap<String, String>(); 117 dependency.put("group", components[0]); 118 dependency.put("module", components[1]); 119 dependency.put("version", components[2]); 120 dependency.put("type", "pom"); 121 dependencies.add(dependency); 122 } 123 else { 124 handleMalformedDependency(expression); 125 } 126 } 127 } 128 return dependencies; 129 } 130 131 private List<ConstantExpression> getConstantExpressions(Expression valueExpression) { 132 if (valueExpression instanceof ListExpression) { 133 return getConstantExpressions((ListExpression) valueExpression); 134 } 135 if (valueExpression instanceof ConstantExpression 136 && ((ConstantExpression) valueExpression).getValue() instanceof String) { 137 return Arrays.asList((ConstantExpression) valueExpression); 138 } 139 reportError("@DependencyManagementBom requires an inline constant that is a " 140 + "string or a string array", valueExpression); 141 return Collections.emptyList(); 142 } 143 144 private List<ConstantExpression> getConstantExpressions( 145 ListExpression valueExpression) { 146 List<ConstantExpression> expressions = new ArrayList<ConstantExpression>(); 147 for (Expression expression : valueExpression.getExpressions()) { 148 if (expression instanceof ConstantExpression 149 && ((ConstantExpression) expression).getValue() instanceof String) { 150 expressions.add((ConstantExpression) expression); 151 } 152 else { 153 reportError( 154 "Each entry in the array must be an " + "inline string constant", 155 expression); 156 } 157 } 158 return expressions; 159 } 160 161 private void handleMalformedDependency(Expression expression) { 162 Message message = createSyntaxErrorMessage( 163 String.format( 164 "The string must be of the form \"group:module:version\"%n"), 165 expression); 166 getSourceUnit().getErrorCollector().addErrorAndContinue(message); 167 } 168 169 private void updateDependencyResolutionContext( 170 List<Map<String, String>> bomDependencies) { 171 URI[] uris = Grape.getInstance().resolve(null, 172 bomDependencies.toArray(new Map[bomDependencies.size()])); 173 DefaultModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance(); 174 for (URI uri : uris) { 175 try { 176 DefaultModelBuildingRequest request = new DefaultModelBuildingRequest(); 177 request.setModelResolver(new GrapeModelResolver()); 178 request.setModelSource(new UrlModelSource(uri.toURL())); 179 request.setSystemProperties(System.getProperties()); 180 Model model = modelBuilder.build(request).getEffectiveModel(); 181 this.resolutionContext.addDependencyManagement( 182 new MavenModelDependencyManagement(model)); 183 } 184 catch (Exception ex) { 185 throw new IllegalStateException("Failed to build model for '" + uri 186 + "'. Is it a valid Maven bom?", ex); 187 } 188 } 189 } 190 191 private void handleDuplicateDependencyManagementBomAnnotation( 192 AnnotationNode annotationNode) { 193 Message message = createSyntaxErrorMessage( 194 "Duplicate @DependencyManagementBom annotation. It must be declared at most once.", 195 annotationNode); 196 getSourceUnit().getErrorCollector().addErrorAndContinue(message); 197 } 198 199 private void reportError(String message, ASTNode node) { 200 getSourceUnit().getErrorCollector() 201 .addErrorAndContinue(createSyntaxErrorMessage(message, node)); 202 } 203 204 private Message createSyntaxErrorMessage(String message, ASTNode node) { 205 return new SyntaxErrorMessage( 206 new SyntaxException(message, node.getLineNumber(), node.getColumnNumber(), 207 node.getLastLineNumber(), node.getLastColumnNumber()), 208 getSourceUnit()); 209 } 210 211 private static class GrapeModelResolver implements ModelResolver { 212 213 @Override 214 public ModelSource resolveModel(String groupId, String artifactId, String version) 215 throws UnresolvableModelException { 216 Map<String, String> dependency = new HashMap<String, String>(); 217 dependency.put("group", groupId); 218 dependency.put("module", artifactId); 219 dependency.put("version", version); 220 dependency.put("type", "pom"); 221 try { 222 return new UrlModelSource( 223 Grape.getInstance().resolve(null, dependency)[0].toURL()); 224 } 225 catch (MalformedURLException e) { 226 throw new UnresolvableModelException(e.getMessage(), groupId, artifactId, 227 version); 228 } 229 } 230 231 @Override 232 public void addRepository(Repository repository) 233 throws InvalidRepositoryException { 234 } 235 236 @Override 237 public ModelResolver newCopy() { 238 return this; 239 } 240 241 } 242 243}