View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18  package org.apache.logging.log4j.core.config.plugins.util;
    19
    20  import java.io.IOException;
    21  import java.net.URI;
    22  import java.net.URL;
    23  import java.text.DecimalFormat;
    24  import java.util.ArrayList;
    25  import java.util.Collections;
    26  import java.util.Enumeration;
    27  import java.util.HashMap;
    28  import java.util.List;
    29  import java.util.Map;
    30  import java.util.concurrent.ConcurrentHashMap;
    31  import java.util.concurrent.ConcurrentMap;
    32  import java.util.concurrent.atomic.AtomicReference;
    33
    34  import org.apache.logging.log4j.Logger;
    35  import org.apache.logging.log4j.core.config.plugins.Plugin;
    36  import org.apache.logging.log4j.core.config.plugins.PluginAliases;
    37  import org.apache.logging.log4j.core.config.plugins.processor.PluginCache;
    38  import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry;
    39  import org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor;
    40  import org.apache.logging.log4j.core.util.Loader;
    41  import org.apache.logging.log4j.status.StatusLogger;
    42  import org.apache.logging.log4j.util.Strings;
    43
    44  /**
    45   * Registry singleton for PluginType maps partitioned by source type and then by category names.
    46   */
    47  public class PluginRegistry {
    48
    49      private static final Logger LOGGER = StatusLogger.getLogger();
    50
    51      private static volatile PluginRegistry INSTANCE;
    52      private static final Object INSTANCE_LOCK = new Object();
    53
    54      /**
    55       * Contains plugins found in Log4j2Plugins.dat cache files in the main CLASSPATH.
    56       */
    57      private final AtomicReference<Map<String, List<PluginType<?>>>> pluginsByCategoryRef =
    58          new AtomicReference<Map<String, List<PluginType<?>>>>();
    59
    60      /**
    61       * Contains plugins found in Log4j2Plugins.dat cache files in OSGi Bundles.
    62       */
    63      private final ConcurrentMap<Long, Map<String, List<PluginType<?>>>> pluginsByCategoryByBundleId =
    64          new ConcurrentHashMap<Long, Map<String, List<PluginType<?>>>>();
    65
    66      /**
    67       * Contains plugins found by searching for annotated classes at runtime.
    68       */
    69      private final ConcurrentMap<String, Map<String, List<PluginType<?>>>> pluginsByCategoryByPackage =
    70          new ConcurrentHashMap<String, Map<String, List<PluginType<?>>>>();
    71
    72      private PluginRegistry() {
    73      }
    74
    75      /**
    76       * Returns the global PluginRegistry instance.
    77       *
    78       * @return the global PluginRegistry instance.
    79       * @since 2.1
    80       */
    81      public static PluginRegistry getInstance() {
    82          PluginRegistry result = INSTANCE;
    83          if (result == null) {
    84              synchronized (INSTANCE_LOCK) {
    85                  result = INSTANCE;
    86                  if (result == null) {
    87                      INSTANCE = result = new PluginRegistry();
    88                  }
    89              }
    90          }
    91          return result;
    92      }
    93
    94      /**
    95       * Resets the registry to an empty state.
    96       */
    97      public void clear() {
    98          pluginsByCategoryRef.set(null);
    99          pluginsByCategoryByPackage.clear();
    100         pluginsByCategoryByBundleId.clear();
    101     }
    102
    103     /**
    104      * @since 2.1
    105      */
    106     public Map<Long, Map<String, List<PluginType<?>>>> getPluginsByCategoryByBundleId() {
    107         return pluginsByCategoryByBundleId;
    108     }
    109
    110     /**
    111      * @since 2.1
    112      */
    113     public Map<String, List<PluginType<?>>> loadFromMainClassLoader() {
    114         final Map<String, List<PluginType<?>>> existing = pluginsByCategoryRef.get();
    115         if (existing != null) {
    116             // already loaded
    117             return existing;
    118         }
    119         final Map<String, List<PluginType<?>>> newPluginsByCategory = decodeCacheFiles(Loader.getClassLoader());
    120
    121         // Note multiple threads could be calling this method concurrently. Both will do the work,
    122         // but only one will be allowed to store the result in the AtomicReference.
    123         // Return the map produced by whichever thread won the race, so all callers will get the same result.
    124         if (pluginsByCategoryRef.compareAndSet(null, newPluginsByCategory)) {
    125             return newPluginsByCategory;
    126         }
    127         return pluginsByCategoryRef.get();
    128     }
    129
    130     /**
    131      * @since 2.1
    132      */
    133     public void clearBundlePlugins(final long bundleId) {
    134         pluginsByCategoryByBundleId.remove(bundleId);
    135     }
    136
    137     /**
    138      * @since 2.1
    139      */
    140     public Map<String, List<PluginType<?>>> loadFromBundle(final long bundleId, final ClassLoader loader) {
    141         Map<String, List<PluginType<?>>> existing = pluginsByCategoryByBundleId.get(bundleId);
    142         if (existing != null) {
    143             // already loaded from this classloader
    144             return existing;
    145         }
    146         final Map<String, List<PluginType<?>>> newPluginsByCategory = decodeCacheFiles(loader);
    147
    148         // Note multiple threads could be calling this method concurrently. Both will do the work,
    149         // but only one will be allowed to store the result in the outer map.
    150         // Return the inner map produced by whichever thread won the race, so all callers will get the same result.
    151         existing = pluginsByCategoryByBundleId.putIfAbsent(bundleId, newPluginsByCategory);
    152         if (existing != null) {
    153             return existing;
    154         }
    155         return newPluginsByCategory;
    156     }
    157
    158     private Map<String, List<PluginType<?>>> decodeCacheFiles(final ClassLoader loader) {
    159         final long startTime = System.nanoTime();
    160         final PluginCache cache = new PluginCache();
    161         try {
    162             final Enumeration<URL> resources = loader.getResources(PluginProcessor.PLUGIN_CACHE_FILE);
    163             if (resources == null) {
    164                 LOGGER.info("Plugin preloads not available from class loader {}", loader);
    165             } else {
    166                 cache.loadCacheFiles(resources);
    167             }
    168         } catch (final IOException ioe) {
    169             LOGGER.warn("Unable to preload plugins", ioe);
    170         }
    171         final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<String, List<PluginType<?>>>();
    172         int pluginCount = 0;
    173         for (final Map.Entry<String, Map<String, PluginEntry>> outer : cache.getAllCategories().entrySet()) {
    174             final String categoryLowerCase = outer.getKey();
    175             final List<PluginType<?>> types = new ArrayList<PluginType<?>>(outer.getValue().size());
    176             newPluginsByCategory.put(categoryLowerCase, types);
    177             for (final Map.Entry<String, PluginEntry> inner : outer.getValue().entrySet()) {
    178                 final PluginEntry entry = inner.getValue();
    179                 final String className = entry.getClassName();
    180                 try {
    181                     final Class<?> clazz = loader.loadClass(className);
    182                     @SuppressWarnings({"unchecked","rawtypes"})
    183                     final PluginType<?> type = new PluginType(entry, clazz, entry.getName());
    184                     types.add(type);
    185                     ++pluginCount;
    186                 } catch (final ClassNotFoundException e) {
    187                     LOGGER.info("Plugin [{}] could not be loaded due to missing classes.", className, e);
    188                 } catch (final VerifyError e) {
    189                     LOGGER.info("Plugin [{}] could not be loaded due to verification error.", className, e);
    190                 }
    191             }
    192         }
    193
    194         final long endTime = System.nanoTime();
    195         final DecimalFormat numFormat = new DecimalFormat("#0.000000");
    196         final double seconds = (endTime - startTime) * 1e-9;
    197         LOGGER.debug("Took {} seconds to load {} plugins from {}",
    198             numFormat.format(seconds), pluginCount, loader);
    199         return newPluginsByCategory;
    200     }
    201
    202     /**
    203      * @since 2.1
    204      */
    205     public Map<String, List<PluginType<?>>> loadFromPackage(final String pkg) {
    206         if (Strings.isBlank(pkg)) {
    207             // happens when splitting an empty string
    208             return Collections.emptyMap();
    209         }
    210         Map<String, List<PluginType<?>>> existing = pluginsByCategoryByPackage.get(pkg);
    211         if (existing != null) {
    212             // already loaded this package
    213             return existing;
    214         }
    215
    216         final long startTime = System.nanoTime();
    217         final ResolverUtil resolver = new ResolverUtil();
    218         final ClassLoader classLoader = Loader.getClassLoader();
    219         if (classLoader != null) {
    220             resolver.setClassLoader(classLoader);
    221         }
    222         resolver.findInPackage(new PluginTest(), pkg);
    223
    224         final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<String, List<PluginType<?>>>();
    225         for (final Class<?> clazz : resolver.getClasses()) {
    226             final Plugin plugin = clazz.getAnnotation(Plugin.class);
    227             final String categoryLowerCase = plugin.category().toLowerCase();
    228             List<PluginType<?>> list = newPluginsByCategory.get(categoryLowerCase);
    229             if (list == null) {
    230                 newPluginsByCategory.put(categoryLowerCase, list = new ArrayList<PluginType<?>>());
    231             }
    232             final PluginEntry mainEntry = new PluginEntry();
    233             final String mainElementName = plugin.elementType().equals(
    234                 Plugin.EMPTY) ? plugin.name() : plugin.elementType();
    235             mainEntry.setKey(plugin.name().toLowerCase());
    236             mainEntry.setName(plugin.name());
    237             mainEntry.setCategory(plugin.category());
    238             mainEntry.setClassName(clazz.getName());
    239             mainEntry.setPrintable(plugin.printObject());
    240             mainEntry.setDefer(plugin.deferChildren());
    241             @SuppressWarnings({"unchecked","rawtypes"})
    242             final PluginType<?> mainType = new PluginType(mainEntry, clazz, mainElementName);
    243             list.add(mainType);
    244             final PluginAliases pluginAliases = clazz.getAnnotation(PluginAliases.class);
    245             if (pluginAliases != null) {
    246                 for (final String alias : pluginAliases.value()) {
    247                     final PluginEntry aliasEntry = new PluginEntry();
    248                     final String aliasElementName = plugin.elementType().equals(
    249                         Plugin.EMPTY) ? alias.trim() : plugin.elementType();
    250                     aliasEntry.setKey(alias.trim().toLowerCase());
    251                     aliasEntry.setName(plugin.name());
    252                     aliasEntry.setCategory(plugin.category());
    253                     aliasEntry.setClassName(clazz.getName());
    254                     aliasEntry.setPrintable(plugin.printObject());
    255                     aliasEntry.setDefer(plugin.deferChildren());
    256                     @SuppressWarnings({"unchecked","rawtypes"})
    257                     final PluginType<?> aliasType = new PluginType(aliasEntry, clazz, aliasElementName);
    258                     list.add(aliasType);
    259                 }
    260             }
    261         }
    262
    263         final long endTime = System.nanoTime();
    264         final DecimalFormat numFormat = new DecimalFormat("#0.000000");
    265         final double seconds = (endTime - startTime) * 1e-9;
    266         LOGGER.debug("Took {} seconds to load {} plugins from package {}",
    267             numFormat.format(seconds), resolver.getClasses().size(), pkg);
    268
    269         // Note multiple threads could be calling this method concurrently. Both will do the work,
    270         // but only one will be allowed to store the result in the outer map.
    271         // Return the inner map produced by whichever thread won the race, so all callers will get the same result.
    272         existing = pluginsByCategoryByPackage.putIfAbsent(pkg, newPluginsByCategory);
    273         if (existing != null) {
    274             return existing;
    275         }
    276         return newPluginsByCategory;
    277     }
    278
    279     /**
    280      * A Test that checks to see if each class is annotated with the 'Plugin' annotation. If it
    281      * is, then the test returns true, otherwise false.
    282      *
    283      * @since 2.1
    284      */
    285     public static class PluginTest implements ResolverUtil.Test {
    286         @Override
    287         public boolean matches(final Class<?> type) {
    288             return type != null && type.isAnnotationPresent(Plugin.class);
    289         }
    290
    291         @Override
    292         public String toString() {
    293             return "annotated with @" + Plugin.class.getSimpleName();
    294         }
    295
    296         @Override
    297         public boolean matches(final URI resource) {
    298             throw new UnsupportedOperationException();
    299         }
    300
    301         @Override
    302         public boolean doesMatchClass() {
    303             return true;
    304         }
    305
    306         @Override
    307         public boolean doesMatchResource() {
    308             return false;
    309         }
    310     }
    311 }