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    }