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 * 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 if (PACKAGES.addIfAbsent(p)) 102 { 103 //set of available plugins could have changed, reset plugin cache for newly-retrieved managers 104 pluginTypeMap.clear(); 105 } 106 } 107 108 /** 109 * Returns the type of a specified plugin. 110 * @param name The name of the plugin. 111 * @return The plugin's type. 112 */ 113 public PluginType getPluginType(final String name) { 114 return plugins.get(name.toLowerCase()); 115 } 116 117 /** 118 * Returns all the matching plugins. 119 * @return A Map containing the name of the plugin and its type. 120 */ 121 public Map<String, PluginType> getPlugins() { 122 return plugins; 123 } 124 125 /** 126 * Locates all the plugins. 127 */ 128 public void collectPlugins() { 129 collectPlugins(true, null); 130 } 131 132 /** 133 * Collects plugins, optionally obtaining them from a preload map. 134 * @param preLoad if true, plugins will be obtained from the preload map. 135 * @param pkgs A comma separated list of package names to scan for plugins. If 136 * null the default Log4j package name will be used. 137 */ 138 @SuppressWarnings("unchecked") 139 public void collectPlugins(boolean preLoad, final String pkgs) { 140 if (pluginTypeMap.containsKey(type)) { 141 plugins = pluginTypeMap.get(type); 142 preLoad = false; 143 } 144 final long start = System.nanoTime(); 145 final ResolverUtil resolver = new ResolverUtil(); 146 final ClassLoader loader = Loader.getClassLoader(); 147 if (loader != null) { 148 resolver.setClassLoader(loader); 149 } 150 if (preLoad) { 151 final ConcurrentMap<String, ConcurrentMap<String, PluginType>> map = decode(loader); 152 if (map != null) { 153 pluginTypeMap = map; 154 plugins = map.get(type); 155 } else { 156 LOGGER.warn("Plugin preloads not available"); 157 } 158 } 159 if (plugins == null || plugins.size() == 0) { 160 if (pkgs == null) { 161 if (!PACKAGES.contains(LOG4J_PACKAGES)) { 162 PACKAGES.add(LOG4J_PACKAGES); 163 } 164 } else { 165 final String[] names = pkgs.split(","); 166 for (final String name : names) { 167 PACKAGES.add(name); 168 } 169 } 170 } 171 final ResolverUtil.Test test = new PluginTest(clazz); 172 for (final String pkg : PACKAGES) { 173 resolver.findInPackage(test, pkg); 174 } 175 for (final Class<?> clazz : resolver.getClasses()) { 176 final Plugin plugin = clazz.getAnnotation(Plugin.class); 177 final String pluginType = plugin.category(); 178 if (!pluginTypeMap.containsKey(pluginType)) { 179 pluginTypeMap.putIfAbsent(pluginType, new ConcurrentHashMap<String, PluginType>()); 180 } 181 final Map<String, PluginType> map = pluginTypeMap.get(pluginType); 182 final String type = plugin.elementType().equals(Plugin.EMPTY) ? plugin.name() : plugin.elementType(); 183 map.put(plugin.name().toLowerCase(), new PluginType(clazz, type, plugin.printObject(), 184 plugin.deferChildren())); 185 } 186 long elapsed = System.nanoTime() - start; 187 plugins = pluginTypeMap.get(type); 188 final StringBuilder sb = new StringBuilder("Generated plugins"); 189 sb.append(" in "); 190 DecimalFormat numFormat = new DecimalFormat("#0"); 191 final long seconds = elapsed / NANOS_PER_SECOND; 192 elapsed %= NANOS_PER_SECOND; 193 sb.append(numFormat.format(seconds)).append('.'); 194 numFormat = new DecimalFormat("000000000"); 195 sb.append(numFormat.format(elapsed)).append(" seconds"); 196 LOGGER.debug(sb.toString()); 197 } 198 199 @SuppressWarnings("unchecked") 200 private static ConcurrentMap<String, ConcurrentMap<String, PluginType>> decode(final ClassLoader loader) { 201 Enumeration<URL> resources; 202 try { 203 resources = loader.getResources(PATH + FILENAME); 204 } catch (final IOException ioe) { 205 LOGGER.warn("Unable to preload plugins", ioe); 206 return null; 207 } 208 final ConcurrentMap<String, ConcurrentMap<String, PluginType>> map = 209 new ConcurrentHashMap<String, ConcurrentMap<String, PluginType>>(); 210 while (resources.hasMoreElements()) { 211 DataInputStream dis = null; 212 try { 213 final URL url = resources.nextElement(); 214 LOGGER.debug("Found Plugin Map at {}", url.toExternalForm()); 215 final InputStream is = url.openStream(); 216 final BufferedInputStream bis = new BufferedInputStream(is); 217 dis = new DataInputStream(bis); 218 final int count = dis.readInt(); 219 for (int j = 0; j < count; ++j) { 220 final String type = dis.readUTF(); 221 final int entries = dis.readInt(); 222 ConcurrentMap<String, PluginType> types = map.get(type); 223 if (types == null) { 224 types = new ConcurrentHashMap<String, PluginType>(count); 225 } 226 for (int i = 0; i < entries; ++i) { 227 final String key = dis.readUTF(); 228 final String className = dis.readUTF(); 229 final String name = dis.readUTF(); 230 final boolean printable = dis.readBoolean(); 231 final boolean defer = dis.readBoolean(); 232 final Class<?> clazz = Class.forName(className); 233 types.put(key, new PluginType(clazz, name, printable, defer)); 234 } 235 map.putIfAbsent(type, types); 236 } 237 } catch (final Exception ex) { 238 LOGGER.warn("Unable to preload plugins", ex); 239 return null; 240 } finally { 241 try { 242 dis.close(); 243 } catch (Exception ignored) { 244 // nothing we can do here... 245 } 246 } 247 } 248 return map.size() == 0 ? null : map; 249 } 250 251 private static void encode(final ConcurrentMap<String, ConcurrentMap<String, PluginType>> map) { 252 final String fileName = rootDir + PATH + FILENAME; 253 DataOutputStream dos = null; 254 try { 255 final File file = new File(rootDir + PATH); 256 file.mkdirs(); 257 final FileOutputStream fos = new FileOutputStream(fileName); 258 final BufferedOutputStream bos = new BufferedOutputStream(fos); 259 dos = new DataOutputStream(bos); 260 dos.writeInt(map.size()); 261 for (final Map.Entry<String, ConcurrentMap<String, PluginType>> outer : map.entrySet()) { 262 dos.writeUTF(outer.getKey()); 263 dos.writeInt(outer.getValue().size()); 264 for (final Map.Entry<String, PluginType> entry : outer.getValue().entrySet()) { 265 dos.writeUTF(entry.getKey()); 266 final PluginType pt = entry.getValue(); 267 dos.writeUTF(pt.getPluginClass().getName()); 268 dos.writeUTF(pt.getElementName()); 269 dos.writeBoolean(pt.isObjectPrintable()); 270 dos.writeBoolean(pt.isDeferChildren()); 271 } 272 } 273 } catch (final Exception ex) { 274 ex.printStackTrace(); 275 } finally { 276 try { 277 dos.close(); 278 } catch (Exception ignored) { 279 // nothing we can do here... 280 } 281 } 282 } 283 284 /** 285 * A Test that checks to see if each class is annotated with a specific annotation. If it 286 * is, then the test returns true, otherwise false. 287 */ 288 public static class PluginTest extends ResolverUtil.ClassTest { 289 private final Class<?> isA; 290 291 /** 292 * Constructs an AnnotatedWith test for the specified annotation type. 293 * @param isA The class to compare against. 294 */ 295 public PluginTest(final Class<?> isA) { 296 this.isA = isA; 297 } 298 299 /** 300 * Returns true if the type is annotated with the class provided to the constructor. 301 * @param type The type to check for. 302 * @return true if the Class is of the specified type. 303 */ 304 @Override 305 public boolean matches(final Class<?> type) { 306 return type != null && type.isAnnotationPresent(Plugin.class) && 307 (isA == null || isA.isAssignableFrom(type)); 308 } 309 310 @Override 311 public String toString() { 312 final StringBuilder msg = new StringBuilder("annotated with @" + Plugin.class.getSimpleName()); 313 if (isA != null) { 314 msg.append(" is assignable to " + isA.getSimpleName()); 315 } 316 return msg.toString(); 317 } 318 } 319 320 }