| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.logging.log4j.plugins.processor; |
| |
| import org.apache.logging.log4j.LoggingException; |
| import org.apache.logging.log4j.plugins.Plugin; |
| import org.apache.logging.log4j.plugins.PluginAliases; |
| import org.apache.logging.log4j.util.Strings; |
| |
| import javax.annotation.processing.AbstractProcessor; |
| import javax.annotation.processing.Messager; |
| import javax.annotation.processing.RoundEnvironment; |
| import javax.annotation.processing.SupportedAnnotationTypes; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementVisitor; |
| import javax.lang.model.element.Name; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.util.Elements; |
| import javax.lang.model.util.SimpleElementVisitor7; |
| import javax.tools.Diagnostic.Kind; |
| import javax.tools.FileObject; |
| import javax.tools.JavaFileObject; |
| import javax.tools.StandardLocation; |
| import java.io.BufferedWriter; |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| |
| /** |
| * Annotation processor for pre-scanning Log4j 2 plugins. |
| */ |
| @SupportedAnnotationTypes({"org.apache.logging.log4j.plugins.*", "org.apache.logging.log4j.core.config.plugins.*"}) |
| public class PluginProcessor extends AbstractProcessor { |
| |
| // TODO: this could be made more abstract to allow for compile-time and run-time plugin processing |
| |
| /** |
| * The location of the plugin cache data file. This file is written to by this processor, and read from by |
| * {@link org.apache.logging.log4j.plugins.util.PluginManager}. |
| */ |
| public static final String PLUGIN_CACHE_FILE = |
| "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat"; |
| private static final String SERVICE_FILE_NAME = |
| "META-INF/services/org.apache.logging.log4j.plugins.processor.PluginService"; |
| |
| @Override |
| public SourceVersion getSupportedSourceVersion() { |
| return SourceVersion.latest(); |
| } |
| |
| @Override |
| public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) { |
| Map<String, String> options = processingEnv.getOptions(); |
| String packageName = options.get("pluginPackage"); |
| Messager messager = processingEnv.getMessager(); |
| messager.printMessage(Kind.NOTE, "Processing Log4j annotations"); |
| try { |
| final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Plugin.class); |
| if (elements.isEmpty()) { |
| messager.printMessage(Kind.NOTE, "No elements to process"); |
| return false; |
| } |
| List<PluginEntry> list = new ArrayList<>(); |
| packageName = collectPlugins(packageName, elements, list); |
| writeClassFile(packageName, list); |
| writeServiceFile(packageName); |
| messager.printMessage(Kind.NOTE, "Annotations processed"); |
| return true; |
| } catch (final IOException e) { |
| e.printStackTrace(); |
| error(e.getMessage()); |
| return false; |
| } catch (final Exception ex) { |
| ex.printStackTrace(); |
| error(ex.getMessage()); |
| return false; |
| } |
| } |
| |
| private void error(final CharSequence message) { |
| processingEnv.getMessager().printMessage(Kind.ERROR, message); |
| } |
| |
| private String collectPlugins(String packageName, final Iterable<? extends Element> elements, List<PluginEntry> list) { |
| boolean calculatePackage = packageName == null; |
| final Elements elementUtils = processingEnv.getElementUtils(); |
| final ElementVisitor<PluginEntry, Plugin> pluginVisitor = new PluginElementVisitor(elementUtils); |
| final ElementVisitor<Collection<PluginEntry>, Plugin> pluginAliasesVisitor = new PluginAliasesElementVisitor( |
| elementUtils); |
| for (final Element element : elements) { |
| final Plugin plugin = element.getAnnotation(Plugin.class); |
| if (plugin == null) { |
| continue; |
| } |
| final PluginEntry entry = element.accept(pluginVisitor, plugin); |
| list.add(entry); |
| if (calculatePackage) { |
| packageName = calculatePackage(elementUtils, element, packageName); |
| } |
| final Collection<PluginEntry> entries = element.accept(pluginAliasesVisitor, plugin); |
| for (final PluginEntry pluginEntry : entries) { |
| list.add(pluginEntry); |
| } |
| } |
| return packageName; |
| } |
| |
| private String calculatePackage(Elements elements, Element element, String packageName) { |
| Name name = elements.getPackageOf(element).getQualifiedName(); |
| if (name == null) { |
| return null; |
| } |
| String pkgName = name.toString(); |
| if (packageName == null) { |
| return pkgName; |
| } |
| if (pkgName.length() == packageName.length()) { |
| return packageName; |
| } |
| if (pkgName.length() < packageName.length() && packageName.startsWith(pkgName)) { |
| return pkgName; |
| } |
| |
| return commonPrefix(pkgName, packageName); |
| } |
| |
| private void writeServiceFile(String pkgName) throws IOException { |
| final FileObject fileObject = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, Strings.EMPTY, |
| SERVICE_FILE_NAME); |
| try (final PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(fileObject.openOutputStream(), UTF_8)))) { |
| writer.println(createFqcn(pkgName)); |
| } |
| } |
| |
| private void writeClassFile(String pkg, List<PluginEntry> list) { |
| String fqcn = createFqcn(pkg); |
| try (final PrintWriter writer = createSourceFile(fqcn)) { |
| writer.println("package " + pkg + ".plugins;"); |
| writer.println(""); |
| writer.println("import org.apache.logging.log4j.plugins.processor.PluginEntry;"); |
| writer.println("import org.apache.logging.log4j.plugins.processor.PluginService;"); |
| writer.println(""); |
| writer.println("public class Log4jPlugins extends PluginService {"); |
| writer.println(""); |
| writer.println(" private static PluginEntry[] entries = new PluginEntry[] {"); |
| StringBuilder sb = new StringBuilder(); |
| int max = list.size() - 1; |
| for (int i = 0; i < list.size(); ++i) { |
| PluginEntry entry = list.get(i); |
| sb.append(" ").append("new PluginEntry(\""); |
| sb.append(entry.getKey()).append("\", \""); |
| sb.append(entry.getClassName()).append("\", \""); |
| sb.append(entry.getName()).append("\", "); |
| sb.append(entry.isPrintable()).append(", "); |
| sb.append(entry.isDefer()).append(", \""); |
| sb.append(entry.getCategory()).append("\")"); |
| if (i < max) { |
| sb.append(","); |
| } |
| writer.println(sb.toString()); |
| sb.setLength(0); |
| } |
| writer.println(" };"); |
| writer.println(" @Override"); |
| writer.println(" public PluginEntry[] getEntries() { return entries;}"); |
| writer.println("}"); |
| } |
| } |
| |
| private PrintWriter createSourceFile(String fqcn) { |
| try { |
| JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(fqcn); |
| return new PrintWriter(sourceFile.openWriter()); |
| } catch (IOException e) { |
| throw new LoggingException("Unable to create Plugin Service Class " + fqcn, e); |
| } |
| } |
| |
| private String createFqcn(String packageName) { |
| return packageName + ".plugins.Log4jPlugins"; |
| } |
| |
| /** |
| * ElementVisitor to scan the Plugin annotation. |
| */ |
| private static class PluginElementVisitor extends SimpleElementVisitor7<PluginEntry, Plugin> { |
| |
| private final Elements elements; |
| |
| private PluginElementVisitor(final Elements elements) { |
| this.elements = elements; |
| } |
| |
| @Override |
| public PluginEntry visitType(final TypeElement e, final Plugin plugin) { |
| Objects.requireNonNull(plugin, "Plugin annotation is null."); |
| final PluginEntry entry = new PluginEntry(); |
| entry.setKey(plugin.name().toLowerCase(Locale.US)); |
| entry.setClassName(elements.getBinaryName(e).toString()); |
| entry.setName(Plugin.EMPTY.equals(plugin.elementType()) ? plugin.name() : plugin.elementType()); |
| entry.setPrintable(plugin.printObject()); |
| entry.setDefer(plugin.deferChildren()); |
| entry.setCategory(plugin.category()); |
| return entry; |
| } |
| } |
| |
| private String commonPrefix(String str1, String str2) { |
| int minLength = str1.length() < str2.length() ? str1.length() : str2.length(); |
| for (int i = 0; i < minLength; i++) { |
| if (str1.charAt(i) != str2.charAt(i)) { |
| if (i > 1 && str1.charAt(i-1) == '.') { |
| return str1.substring(0, i-1); |
| } else { |
| return str1.substring(0, i); |
| } |
| } |
| } |
| return str1.substring(0, minLength); |
| } |
| |
| /** |
| * ElementVisitor to scan the PluginAliases annotation. |
| */ |
| private static class PluginAliasesElementVisitor extends SimpleElementVisitor7<Collection<PluginEntry>, Plugin> { |
| |
| private final Elements elements; |
| |
| private PluginAliasesElementVisitor(final Elements elements) { |
| super(Collections.<PluginEntry> emptyList()); |
| this.elements = elements; |
| } |
| |
| @Override |
| public Collection<PluginEntry> visitType(final TypeElement e, final Plugin plugin) { |
| final PluginAliases aliases = e.getAnnotation(PluginAliases.class); |
| if (aliases == null) { |
| return DEFAULT_VALUE; |
| } |
| final Collection<PluginEntry> entries = new ArrayList<>(aliases.value().length); |
| for (final String alias : aliases.value()) { |
| final PluginEntry entry = new PluginEntry(); |
| entry.setKey(alias.toLowerCase(Locale.US)); |
| entry.setClassName(elements.getBinaryName(e).toString()); |
| entry.setName(Plugin.EMPTY.equals(plugin.elementType()) ? alias : plugin.elementType()); |
| entry.setPrintable(plugin.printObject()); |
| entry.setDefer(plugin.deferChildren()); |
| entry.setCategory(plugin.category()); |
| entries.add(entry); |
| } |
| return entries; |
| } |
| } |
| } |