/* * Copyright 2012-2017 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.autoconfigureprocessor; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.tools.FileObject; import javax.tools.StandardLocation; /** * Annotation processor to store certain annotations from auto-configuration classes in a * property file. * * @author Madhura Bhave * @author Phillip Webb */ @SupportedAnnotationTypes({ "org.springframework.context.annotation.Configuration", "org.springframework.boot.autoconfigure.condition.ConditionalOnClass", "org.springframework.boot.autoconfigure.AutoConfigureBefore", "org.springframework.boot.autoconfigure.AutoConfigureAfter", "org.springframework.boot.autoconfigure.AutoConfigureOrder" }) public class AutoConfigureAnnotationProcessor extends AbstractProcessor { protected static final String PROPERTIES_PATH = "META-INF/" + "spring-autoconfigure-metadata.properties"; private Map<String, String> annotations; private final Properties properties = new Properties(); public AutoConfigureAnnotationProcessor() { Map<String, String> annotations = new LinkedHashMap<>(); addAnnotations(annotations); this.annotations = Collections.unmodifiableMap(annotations); } protected void addAnnotations(Map<String, String> annotations) { annotations.put("Configuration", "org.springframework.context.annotation.Configuration"); annotations.put("ConditionalOnClass", "org.springframework.boot.autoconfigure.condition.ConditionalOnClass"); annotations.put("AutoConfigureBefore", "org.springframework.boot.autoconfigure.AutoConfigureBefore"); annotations.put("AutoConfigureAfter", "org.springframework.boot.autoconfigure.AutoConfigureAfter"); annotations.put("AutoConfigureOrder", "org.springframework.boot.autoconfigure.AutoConfigureOrder"); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Map.Entry<String, String> entry : this.annotations.entrySet()) { process(roundEnv, entry.getKey(), entry.getValue()); } if (roundEnv.processingOver()) { try { writeProperties(); } catch (Exception ex) { throw new IllegalStateException("Failed to write metadata", ex); } } return false; } private void process(RoundEnvironment roundEnv, String propertyKey, String annotationName) { TypeElement annotationType = this.processingEnv.getElementUtils() .getTypeElement(annotationName); if (annotationType != null) { for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) { Element enclosingElement = element.getEnclosingElement(); if (enclosingElement != null && enclosingElement.getKind() == ElementKind.PACKAGE) { processElement(element, propertyKey, annotationName); } } } } private void processElement(Element element, String propertyKey, String annotationName) { try { String qualifiedName = getQualifiedName(element); AnnotationMirror annotation = getAnnotation(element, annotationName); if (qualifiedName != null && annotation != null) { List<Object> values = getValues(annotation); this.properties.put(qualifiedName + "." + propertyKey, toCommaDelimitedString(values)); this.properties.put(qualifiedName, ""); } } catch (Exception ex) { throw new IllegalStateException( "Error processing configuration meta-data on " + element, ex); } } private AnnotationMirror getAnnotation(Element element, String type) { if (element != null) { for (AnnotationMirror annotation : element.getAnnotationMirrors()) { if (type.equals(annotation.getAnnotationType().toString())) { return annotation; } } } return null; } private String toCommaDelimitedString(List<Object> list) { StringBuilder result = new StringBuilder(); for (Object item : list) { result.append(result.length() != 0 ? "," : ""); result.append(item); } return result.toString(); } @SuppressWarnings("unchecked") private List<Object> getValues(AnnotationMirror annotation) { List<Object> result = new ArrayList<>(); for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotation .getElementValues().entrySet()) { String attributeName = entry.getKey().getSimpleName().toString(); if ("name".equals(attributeName) || "value".equals(attributeName)) { Object value = entry.getValue().getValue(); if (value instanceof List) { for (AnnotationValue annotationValue : (List<AnnotationValue>) value) { result.add(annotationValue.getValue()); } } else { result.add(value); } } } return result; } private String getQualifiedName(Element element) { if (element != null) { TypeElement enclosingElement = getEnclosingTypeElement(element.asType()); if (enclosingElement != null) { return getQualifiedName(enclosingElement) + "$" + ((DeclaredType) element.asType()).asElement().getSimpleName() .toString(); } if (element instanceof TypeElement) { return ((TypeElement) element).getQualifiedName().toString(); } } return null; } private TypeElement getEnclosingTypeElement(TypeMirror type) { if (type instanceof DeclaredType) { DeclaredType declaredType = (DeclaredType) type; Element enclosingElement = declaredType.asElement().getEnclosingElement(); if (enclosingElement != null && enclosingElement instanceof TypeElement) { return (TypeElement) enclosingElement; } } return null; } private void writeProperties() throws IOException { if (!this.properties.isEmpty()) { FileObject file = this.processingEnv.getFiler() .createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH); OutputStream outputStream = file.openOutputStream(); try { this.properties.store(outputStream, null); } finally { outputStream.close(); } } } }