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
018package org.apache.logging.log4j.core.config.plugins.util;
019
020import org.apache.logging.log4j.Logger;
021import org.apache.logging.log4j.status.StatusLogger;
022import org.apache.logging.log4j.util.Strings;
023
024import java.util.Collection;
025import java.util.HashMap;
026import java.util.LinkedHashMap;
027import java.util.List;
028import java.util.Map;
029import java.util.concurrent.CopyOnWriteArrayList;
030
031/**
032 * Loads and manages all the plugins.
033 */
034public class PluginManager {
035
036    private static final CopyOnWriteArrayList<String> PACKAGES = new CopyOnWriteArrayList<>();
037    private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core";
038
039    private static final Logger LOGGER = StatusLogger.getLogger();
040
041    private Map<String, PluginType<?>> plugins = new HashMap<>();
042    private final String category;
043
044    /**
045     * Constructs a PluginManager for the plugin category name given.
046     * 
047     * @param category The plugin category name.
048     */
049    public PluginManager(final String category) {
050        this.category = category;
051    }
052
053    /**
054     * Process annotated plugins.
055     * 
056     * @deprecated Use {@link org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor} instead. To do so,
057     *             simply include {@code log4j-core} in your dependencies and make sure annotation processing is not
058     *             disabled. By default, supported Java compilers will automatically use that plugin processor provided
059     *             {@code log4j-core} is on the classpath.
060     */
061    @Deprecated
062    // use PluginProcessor instead
063    public static void main(final String[] args) {
064        System.err.println("ERROR: this tool is superseded by the annotation processor included in log4j-core.");
065        System.err.println("If the annotation processor does not work for you, please see the manual page:");
066        System.err.println("http://logging.apache.org/log4j/2.x/manual/configuration.html#ConfigurationSyntax");
067        System.exit(-1);
068    }
069
070    /**
071     * Adds a package name to be scanned for plugins. Must be invoked prior to plugins being collected.
072     * 
073     * @param p The package name. Ignored if {@code null} or empty.
074     */
075    public static void addPackage(final String p) {
076        if (Strings.isBlank(p)) {
077            return;
078        }
079        PACKAGES.addIfAbsent(p);
080    }
081
082    /**
083     * Adds a list of package names to be scanned for plugins. Convenience method for {@link #addPackage(String)}.
084     *
085     * @param packages collection of package names to add. Empty and null package names are ignored.
086     */
087    public static void addPackages(final Collection<String> packages) {
088        for (final String pkg : packages) {
089            if (Strings.isNotBlank(pkg)) {
090                PACKAGES.addIfAbsent(pkg);
091            }
092        }
093    }
094
095    /**
096     * Returns the type of a specified plugin.
097     * 
098     * @param name The name of the plugin.
099     * @return The plugin's type.
100     */
101    public PluginType<?> getPluginType(final String name) {
102        return plugins.get(name.toLowerCase());
103    }
104
105    /**
106     * Returns all the matching plugins.
107     * 
108     * @return A Map containing the name of the plugin and its type.
109     */
110    public Map<String, PluginType<?>> getPlugins() {
111        return plugins;
112    }
113
114    /**
115     * Locates all the plugins.
116     */
117    public void collectPlugins() {
118        collectPlugins(null);
119    }
120
121    /**
122     * Locates all the plugins including search of specific packages. Warns about name collisions.
123     *
124     * @param packages the list of packages to scan for plugins
125     * @since 2.1
126     */
127    public void collectPlugins(final List<String> packages) {
128        final String categoryLowerCase = category.toLowerCase();
129        final Map<String, PluginType<?>> newPlugins = new LinkedHashMap<>();
130
131        // First, iterate the Log4j2Plugin.dat files found in the main CLASSPATH
132        Map<String, List<PluginType<?>>> builtInPlugins = PluginRegistry.getInstance().loadFromMainClassLoader();
133        if (builtInPlugins.isEmpty()) {
134            // If we didn't find any plugins above, someone must have messed with the log4j-core.jar.
135            // Search the standard package in the hopes we can find our core plugins.
136            builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES);
137        }
138        mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase));
139
140        // Next, iterate any Log4j2Plugin.dat files from OSGi Bundles
141        for (final Map<String, List<PluginType<?>>> pluginsByCategory : PluginRegistry.getInstance().getPluginsByCategoryByBundleId().values()) {
142            mergeByName(newPlugins, pluginsByCategory.get(categoryLowerCase));
143        }
144
145        // Next iterate any packages passed to the static addPackage method.
146        for (final String pkg : PACKAGES) {
147            mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
148        }
149        // Finally iterate any packages provided in the configuration (note these can be changed at runtime).
150        if (packages != null) {
151            for (final String pkg : packages) {
152                mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
153            }
154        }
155
156        LOGGER.debug("PluginManager '{}' found {} plugins", category, newPlugins.size());
157
158        plugins = newPlugins;
159    }
160
161    private static void mergeByName(final Map<String, PluginType<?>> newPlugins, final List<PluginType<?>> plugins) {
162        if (plugins == null) {
163            return;
164        }
165        for (final PluginType<?> pluginType : plugins) {
166            final String key = pluginType.getKey();
167            final PluginType<?> existing = newPlugins.get(key);
168            if (existing == null) {
169                newPlugins.put(key, pluginType);
170            } else if (!existing.getPluginClass().equals(pluginType.getPluginClass())) {
171                LOGGER.warn("Plugin [{}] is already mapped to {}, ignoring {}",
172                    key, existing.getPluginClass(), pluginType.getPluginClass());
173            }
174        }
175    }
176}