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 package org.apache.logging.log4j.core.config.plugins; 018 019 import org.apache.logging.log4j.Logger; 020 import org.apache.logging.log4j.core.helpers.Loader; 021 import org.apache.logging.log4j.status.StatusLogger; 022 023 import java.io.BufferedInputStream; 024 import java.io.BufferedOutputStream; 025 import java.io.DataInputStream; 026 import java.io.DataOutputStream; 027 import java.io.File; 028 import java.io.FileOutputStream; 029 import java.io.IOException; 030 import java.io.InputStream; 031 import java.net.URL; 032 import java.text.DecimalFormat; 033 import java.util.Enumeration; 034 import java.util.HashMap; 035 import java.util.Map; 036 import java.util.concurrent.ConcurrentHashMap; 037 import java.util.concurrent.ConcurrentMap; 038 import java.util.concurrent.CopyOnWriteArrayList; 039 040 /** 041 * Component that loads and manages all the plugins. 042 */ 043 public class PluginManager { 044 045 private static final long NANOS_PER_SECOND = 1000000000L; 046 047 private static ConcurrentMap<String, ConcurrentMap<String, PluginType>> pluginTypeMap = 048 new ConcurrentHashMap<String, ConcurrentMap<String, PluginType>>(); 049 050 private static final CopyOnWriteArrayList<String> PACKAGES = new CopyOnWriteArrayList<String>(); 051 private static final String PATH = "org/apache/logging/log4j/core/config/plugins/"; 052 private static final String FILENAME = "Log4j2Plugins.dat"; 053 private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core"; 054 055 private static final Logger LOGGER = StatusLogger.getLogger(); 056 057 private static String rootDir; 058 059 private Map<String, PluginType> plugins = new HashMap<String, PluginType>(); 060 private final String type; 061 private final Class<?> clazz; 062 063 /** 064 * Constructor that takes only a type name. 065 * @param type The type name. 066 */ 067 public PluginManager(final String type) { 068 this.type = type; 069 this.clazz = null; 070 } 071 072 /** 073 * Constructor that takes a type name and a Class. 074 * @param type The type that must be matched. 075 * @param clazz The Class each match must be an instance of. 076 */ 077 public PluginManager(final String type, final Class<?> clazz) { 078 this.type = type; 079 this.clazz = clazz; 080 } 081 082 public static void main(final String[] args) throws Exception { 083 if (args == null || args.length < 1) { 084 System.err.println("A target directory must be specified"); 085 System.exit(-1); 086 } 087 rootDir = args[0].endsWith("/") || args[0].endsWith("\\") ? args[0] : args[0] + "/"; 088 089 final PluginManager manager = new PluginManager("Core"); 090 final String packages = args.length == 2 ? args[1] : null; 091 092 manager.collectPlugins(false, packages); 093 encode(pluginTypeMap); 094 } 095 096 /** 097 * Adds a package name to be scanned for plugins. Must be invoked prior to plugins being collected. 098 * @param p The package name. 099 */ 100 public static void addPackage(final String p) { 101 PACKAGES.addIfAbsent(p); 102 } 103 104 /** 105 * Returns the type of a specified plugin. 106 * @param name The name of the plugin. 107 * @return The plugin's type. 108 */ 109 public PluginType getPluginType(final String name) { 110 return plugins.get(name.toLowerCase()); 111 } 112 113 /** 114 * Returns all the matching plugins. 115 * @return A Map containing the name of the plugin and its type. 116 */ 117 public Map<String, PluginType> getPlugins() { 118 return plugins; 119 } 120 121 /** 122 * Locates all the plugins. 123 */ 124 public void collectPlugins() { 125 collectPlugins(true, null); 126 } 127 128 /** 129 * Collects plugins, optionally obtaining them from a preload map. 130 * @param preLoad if true, plugins will be obtained from the preload map. 131 * @param pkgs A comma separated list of package names to scan for plugins. If 132 * null the default Log4j package name will be used. 133 */ 134 public void collectPlugins(boolean preLoad, final String pkgs) { 135 if (pluginTypeMap.containsKey(type)) { 136 plugins = pluginTypeMap.get(type); 137 preLoad = false; 138 } 139 final long start = System.nanoTime(); 140 final ResolverUtil resolver = new ResolverUtil(); 141 final ClassLoader loader = Loader.getClassLoader(); 142 if (loader != null) { 143 resolver.setClassLoader(loader); 144 } 145 if (preLoad) { 146 final ConcurrentMap<String, ConcurrentMap<String, PluginType>> map = decode(loader); 147 if (map != null) { 148 pluginTypeMap = map; 149 plugins = map.get(type); 150 } else { 151 LOGGER.warn("Plugin preloads not available"); 152 } 153 } 154 if (plugins.size() == 0) { 155 if (pkgs == null) { 156 PACKAGES.add(LOG4J_PACKAGES); 157 } else { 158 final String[] names = pkgs.split(","); 159 for (final String name : names) { 160 PACKAGES.add(name); 161 } 162 } 163 } 164 final ResolverUtil.Test test = new PluginTest(clazz); 165 for (final String pkg : PACKAGES) { 166 resolver.findInPackage(test, pkg); 167 } 168 for (final Class<?> clazz : resolver.getClasses()) { 169 final Plugin plugin = clazz.getAnnotation(Plugin.class); 170 final String pluginType = plugin.type(); 171 if (!pluginTypeMap.containsKey(pluginType)) { 172 pluginTypeMap.putIfAbsent(pluginType, new ConcurrentHashMap<String, PluginType>()); 173 } 174 final Map<String, PluginType> map = pluginTypeMap.get(pluginType); 175 final String type = plugin.elementType().equals(Plugin.EMPTY) ? plugin.name() : plugin.elementType(); 176 map.put(plugin.name().toLowerCase(), new PluginType(clazz, type, plugin.printObject(), 177 plugin.deferChildren())); 178 } 179 long elapsed = System.nanoTime() - start; 180 plugins = pluginTypeMap.get(type); 181 final StringBuilder sb = new StringBuilder("Generated plugins"); 182 sb.append(" in "); 183 DecimalFormat numFormat = new DecimalFormat("#0"); 184 final long seconds = elapsed / NANOS_PER_SECOND; 185 elapsed %= NANOS_PER_SECOND; 186 sb.append(numFormat.format(seconds)).append('.'); 187 numFormat = new DecimalFormat("000000000"); 188 sb.append(numFormat.format(elapsed)).append(" seconds"); 189 LOGGER.debug(sb.toString()); 190 } 191 192 private static ConcurrentMap<String, ConcurrentMap<String, PluginType>> decode(final ClassLoader loader) { 193 Enumeration<URL> resources; 194 try { 195 resources = loader.getResources(PATH + FILENAME); 196 } catch (final IOException ioe) { 197 LOGGER.warn("Unable to preload plugins", ioe); 198 return null; 199 } 200 final ConcurrentMap<String, ConcurrentMap<String, PluginType>> map = 201 new ConcurrentHashMap<String, ConcurrentMap<String, PluginType>>(); 202 while (resources.hasMoreElements()) { 203 try { 204 final URL url = resources.nextElement(); 205 LOGGER.debug("Found Plugin Map at {}", url.toExternalForm()); 206 final InputStream is = url.openStream(); 207 final BufferedInputStream bis = new BufferedInputStream(is); 208 final DataInputStream dis = new DataInputStream(bis); 209 final int count = dis.readInt(); 210 for (int j = 0; j < count; ++j) { 211 final String type = dis.readUTF(); 212 final int entries = dis.readInt(); 213 ConcurrentMap<String, PluginType> types = map.get(type); 214 if (types == null) { 215 types = new ConcurrentHashMap<String, PluginType>(count); 216 } 217 for (int i = 0; i < entries; ++i) { 218 final String key = dis.readUTF(); 219 final String className = dis.readUTF(); 220 final String name = dis.readUTF(); 221 final boolean printable = dis.readBoolean(); 222 final boolean defer = dis.readBoolean(); 223 final Class<?> clazz = Class.forName(className); 224 types.put(key, new PluginType(clazz, name, printable, defer)); 225 } 226 map.putIfAbsent(type, types); 227 } 228 dis.close(); 229 } catch (final Exception ex) { 230 LOGGER.warn("Unable to preload plugins", ex); 231 return null; 232 } 233 } 234 return map.size() == 0 ? null : map; 235 } 236 237 private static void encode(final ConcurrentMap<String, ConcurrentMap<String, PluginType>> map) { 238 final String fileName = rootDir + PATH + FILENAME; 239 try { 240 final File file = new File(rootDir + PATH); 241 file.mkdirs(); 242 final FileOutputStream fos = new FileOutputStream(fileName); 243 final BufferedOutputStream bos = new BufferedOutputStream(fos); 244 final DataOutputStream dos = new DataOutputStream(bos); 245 dos.writeInt(map.size()); 246 for (final Map.Entry<String, ConcurrentMap<String, PluginType>> outer : map.entrySet()) { 247 dos.writeUTF(outer.getKey()); 248 dos.writeInt(outer.getValue().size()); 249 for (final Map.Entry<String, PluginType> entry : outer.getValue().entrySet()) { 250 dos.writeUTF(entry.getKey()); 251 final PluginType pt = entry.getValue(); 252 dos.writeUTF(pt.getPluginClass().getName()); 253 dos.writeUTF(pt.getElementName()); 254 dos.writeBoolean(pt.isObjectPrintable()); 255 dos.writeBoolean(pt.isDeferChildren()); 256 } 257 } 258 dos.close(); 259 } catch (final Exception ex) { 260 ex.printStackTrace(); 261 } 262 } 263 264 /** 265 * A Test that checks to see if each class is annotated with a specific annotation. If it 266 * is, then the test returns true, otherwise false. 267 */ 268 public static class PluginTest extends ResolverUtil.ClassTest { 269 private final Class<?> isA; 270 271 /** 272 * Constructs an AnnotatedWith test for the specified annotation type. 273 * @param isA The class to compare against. 274 */ 275 public PluginTest(final Class<?> isA) { 276 this.isA = isA; 277 } 278 279 /** 280 * Returns true if the type is annotated with the class provided to the constructor. 281 * @param type The type to check for. 282 * @return true if the Class is of the specified type. 283 */ 284 public boolean matches(final Class<?> type) { 285 return type != null && type.isAnnotationPresent(Plugin.class) && 286 (isA == null || isA.isAssignableFrom(type)); 287 } 288 289 @Override 290 public String toString() { 291 final StringBuilder msg = new StringBuilder("annotated with @" + Plugin.class.getSimpleName()); 292 if (isA != null) { 293 msg.append(" is assignable to " + isA.getSimpleName()); 294 } 295 return msg.toString(); 296 } 297 } 298 299 }