    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements. See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License. You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.logging.log4j.core.config.plugins.processor;
    019
    020    import org.apache.logging.log4j.core.config.plugins.Plugin;
    021    import org.apache.logging.log4j.core.config.plugins.PluginAliases;
    022    import org.apache.logging.log4j.util.Strings;
    023
    024    import javax.annotation.processing.AbstractProcessor;
    025    import javax.annotation.processing.RoundEnvironment;
    026    import javax.annotation.processing.SupportedAnnotationTypes;
    027    import javax.lang.model.SourceVersion;
    028    import javax.lang.model.element.Element;
    029    import javax.lang.model.element.ElementVisitor;
    030    import javax.lang.model.element.TypeElement;
    031    import javax.lang.model.util.Elements;
    032    import javax.lang.model.util.SimpleElementVisitor6;
    033    import javax.tools.Diagnostic.Kind;
    034    import javax.tools.FileObject;
    035    import javax.tools.StandardLocation;
    036    import java.io.IOException;
    037    import java.io.OutputStream;
    038    import java.util.ArrayList;
    039    import java.util.Collection;
    040    import java.util.Collections;
    041    import java.util.Map;
    042    import java.util.Set;
    043
    044    /**
    045     * Annotation processor for pre-scanning Log4j 2 plugins.
    046     */
    047    @SupportedAnnotationTypes("org.apache.logging.log4j.core.config.plugins.*")
    048    public class PluginProcessor extends AbstractProcessor {
    049
    050        // TODO: this could be made more abstract to allow for compile-time and run-time plugin processing
    051
    052        /**
    053         * The location of the plugin cache data file. This file is written to by this processor, and read from by
    054         * {@link org.apache.logging.log4j.core.config.plugins.util.PluginManager}.
    055         */
    056        public static final String PLUGIN_CACHE_FILE = "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat";
    057
    058        private final PluginCache pluginCache = new PluginCache();
    059
    060        @Override
    061        public SourceVersion getSupportedSourceVersion() {
    062            return SourceVersion.latest();
    063        }
    064
    065        @Override
    066        public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
    067            try {
    068                final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Plugin.class);
    069                if (elements.isEmpty()) {
    070                    return false;
    071                }
    072                collectPlugins(elements);
    073                writeCacheFile(elements.toArray(new Element[elements.size()]));
    074                return true;
    075            } catch (final IOException e) {
    076                error(e.getMessage());
    077                return false;
    078            }
    079        }
    080
    081        private void error(final CharSequence message) {
    082            processingEnv.getMessager().printMessage(Kind.ERROR, message);
    083        }
    084
    085        private void collectPlugins(final Iterable<? extends Element> elements) {
    086            final Elements elementUtils = processingEnv.getElementUtils();
    087            final ElementVisitor<PluginEntry, Plugin> pluginVisitor =
    088                    new PluginElementVisitor(elementUtils);
    089            final ElementVisitor<Collection<PluginEntry>, Plugin> pluginAliasesVisitor =
    090                    new PluginAliasesElementVisitor(elementUtils);
    091            for (final Element element : elements) {
    092                final Plugin plugin = element.getAnnotation(Plugin.class);
    093                final PluginEntry entry = element.accept(pluginVisitor, plugin);
    094                final Map<String, PluginEntry> category = pluginCache.getCategory(entry.getCategory());
    095                category.put(entry.getKey(), entry);
    096                final Collection<PluginEntry> entries = element.accept(pluginAliasesVisitor, plugin);
    097                for (final PluginEntry pluginEntry : entries) {
    098                    category.put(pluginEntry.getKey(), pluginEntry);
    099                }
    100            }
    101        }
    102
    103        private void writeCacheFile(final Element... elements) throws IOException {
    104            final FileObject fo = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT,
    105                Strings.EMPTY, PLUGIN_CACHE_FILE, elements);
    106            final OutputStream out = fo.openOutputStream();
    107            try {
    108                pluginCache.writeCache(out);
    109            } finally {
    110                out.close();
    111            }
    112        }
    113
    114        /**
    115         * ElementVisitor to scan the Plugin annotation.
    116         */
    117        private static class PluginElementVisitor extends SimpleElementVisitor6<PluginEntry, Plugin> {
    118
    119            private final Elements elements;
    120
    121            private PluginElementVisitor(final Elements elements) {
    122                this.elements = elements;
    123            }
    124
    125            @Override
    126            public PluginEntry visitType(final TypeElement e, final Plugin plugin) {
    127                if (plugin == null) {
    128                    throw new NullPointerException("Plugin annotation is null.");
    129                }
    130                final PluginEntry entry = new PluginEntry();
    131                entry.setKey(plugin.name().toLowerCase());
    132                entry.setClassName(elements.getBinaryName(e).toString());
    133                entry.setName(Plugin.EMPTY.equals(plugin.elementType()) ? plugin.name() : plugin.elementType());
    134                entry.setPrintable(plugin.printObject());
    135                entry.setDefer(plugin.deferChildren());
    136                entry.setCategory(plugin.category());
    137                return entry;
    138            }
    139        }
    140
    141        /**
    142         * ElementVisitor to scan the PluginAliases annotation.
    143         */
    144        private static class PluginAliasesElementVisitor extends SimpleElementVisitor6<Collection<PluginEntry>, Plugin> {
    145
    146            private final Elements elements;
    147
    148            private PluginAliasesElementVisitor(final Elements elements) {
    149                super(Collections.<PluginEntry>emptyList());
    150                this.elements = elements;
    151            }
    152
    153            @Override
    154            public Collection<PluginEntry> visitType(final TypeElement e, final Plugin plugin) {
    155                final PluginAliases aliases = e.getAnnotation(PluginAliases.class);
    156                if (aliases == null) {
    157                    return DEFAULT_VALUE;
    158                }
    159                final Collection<PluginEntry> entries = new ArrayList<PluginEntry>(aliases.value().length);
    160                for (final String alias : aliases.value()) {
    161                    final PluginEntry entry = new PluginEntry();
    162                    entry.setKey(alias.toLowerCase());
    163                    entry.setClassName(elements.getBinaryName(e).toString());
    164                    entry.setName(Plugin.EMPTY.equals(plugin.elementType()) ? alias : plugin.elementType());
    165                    entry.setPrintable(plugin.printObject());
    166                    entry.setDefer(plugin.deferChildren());
    167                    entry.setCategory(plugin.category());
    168                    entries.add(entry);
    169                }
    170                return entries;
    171            }
    172        }
    173    }