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.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.List; 023 024import org.codehaus.groovy.ast.ASTNode; 025import org.codehaus.groovy.ast.AnnotatedNode; 026import org.codehaus.groovy.ast.AnnotationNode; 027import org.codehaus.groovy.ast.ClassHelper; 028import org.codehaus.groovy.ast.ClassNode; 029import org.codehaus.groovy.ast.ModuleNode; 030import org.codehaus.groovy.ast.PackageNode; 031import org.codehaus.groovy.ast.expr.ConstantExpression; 032import org.codehaus.groovy.ast.expr.Expression; 033import org.codehaus.groovy.ast.expr.ListExpression; 034import org.codehaus.groovy.control.CompilePhase; 035import org.codehaus.groovy.control.SourceUnit; 036import org.codehaus.groovy.transform.GroovyASTTransformation; 037 038import org.springframework.boot.groovy.DependencyManagementBom; 039import org.springframework.core.Ordered; 040 041/** 042 * A base class that lets plugin authors easily add additional BOMs to all apps. All the 043 * dependencies in the BOM (and its transitives) will be added to the dependency 044 * management lookup, so an app can use just the artifact id (e.g. "spring-jdbc") in a 045 * {@code @Grab}. To install, implement the missing methods and list the class in 046 * {@code META-INF/services/org.springframework.boot.cli.compiler.SpringBootAstTransformation} 047 * . The {@link #getOrder()} value needs to be before 048 * {@link DependencyManagementBomTransformation#ORDER}. 049 * 050 * @author Dave Syer 051 * @since 1.3.0 052 */ 053@GroovyASTTransformation(phase = CompilePhase.CONVERSION) 054public abstract class GenericBomAstTransformation 055 implements SpringBootAstTransformation, Ordered { 056 057 private static ClassNode BOM = ClassHelper.make(DependencyManagementBom.class); 058 059 @Override 060 public void visit(ASTNode[] nodes, SourceUnit source) { 061 for (ASTNode astNode : nodes) { 062 if (astNode instanceof ModuleNode) { 063 visitModule((ModuleNode) astNode, getBomModule()); 064 } 065 } 066 } 067 068 /** 069 * The bom to be added to dependency management in compact form: 070 * <code>"<groupId>:<artifactId>:<version>"</code> (like in a 071 * {@code @Grab}). 072 * @return the maven co-ordinates of the BOM to add 073 */ 074 protected abstract String getBomModule(); 075 076 private void visitModule(ModuleNode node, String module) { 077 addDependencyManagementBom(node, module); 078 } 079 080 private void addDependencyManagementBom(ModuleNode node, String module) { 081 AnnotatedNode annotated = getAnnotatedNode(node); 082 if (annotated != null) { 083 AnnotationNode bom = getAnnotation(annotated); 084 List<Expression> expressions = new ArrayList<Expression>( 085 getConstantExpressions(bom.getMember("value"))); 086 expressions.add(new ConstantExpression(module)); 087 bom.setMember("value", new ListExpression(expressions)); 088 } 089 } 090 091 private AnnotationNode getAnnotation(AnnotatedNode annotated) { 092 List<AnnotationNode> annotations = annotated.getAnnotations(BOM); 093 if (!annotations.isEmpty()) { 094 return annotations.get(0); 095 } 096 AnnotationNode annotation = new AnnotationNode(BOM); 097 annotated.addAnnotation(annotation); 098 return annotation; 099 } 100 101 private AnnotatedNode getAnnotatedNode(ModuleNode node) { 102 PackageNode packageNode = node.getPackage(); 103 if (packageNode != null && !packageNode.getAnnotations(BOM).isEmpty()) { 104 return packageNode; 105 } 106 if (!node.getClasses().isEmpty()) { 107 return node.getClasses().get(0); 108 } 109 return packageNode; 110 } 111 112 private List<ConstantExpression> getConstantExpressions(Expression valueExpression) { 113 if (valueExpression instanceof ListExpression) { 114 return getConstantExpressions((ListExpression) valueExpression); 115 } 116 if (valueExpression instanceof ConstantExpression 117 && ((ConstantExpression) valueExpression).getValue() instanceof String) { 118 return Arrays.asList((ConstantExpression) valueExpression); 119 } 120 return Collections.emptyList(); 121 } 122 123 private List<ConstantExpression> getConstantExpressions( 124 ListExpression valueExpression) { 125 List<ConstantExpression> expressions = new ArrayList<ConstantExpression>(); 126 for (Expression expression : valueExpression.getExpressions()) { 127 if (expression instanceof ConstantExpression 128 && ((ConstantExpression) expression).getValue() instanceof String) { 129 expressions.add((ConstantExpression) expression); 130 } 131 } 132 return expressions; 133 } 134 135}