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(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(String type, Class clazz) { 078 this.type = type; 079 this.clazz = clazz; 080 } 081 082 public static void main(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 PluginManager manager = new PluginManager("Core"); 090 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(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(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, String pkgs) { 135 if (pluginTypeMap.containsKey(type)) { 136 plugins = pluginTypeMap.get(type); 137 preLoad = false; 138 } 139 long start = System.nanoTime(); 140 ResolverUtil<?> resolver = new ResolverUtil(); 141 ClassLoader loader = Loader.getClassLoader(); 142 if (loader != null) { 143 resolver.setClassLoader(loader); 144 } 145 if (preLoad) { 146 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 String[] names = pkgs.split(","); 159 for (String name : names) { 160 packages.add(name); 161 } 162 } 163 } 164 ResolverUtil.Test test = new PluginTest(clazz); 165 for (String pkg : packages) { 166 resolver.findInPackage(test, pkg); 167 } 168 for (Class<?> item : resolver.getClasses()) { 169 Plugin p = item.getAnnotation(Plugin.class); 170 String pluginType = p.type(); 171 if (!pluginTypeMap.containsKey(pluginType)) { 172 pluginTypeMap.putIfAbsent(pluginType, new ConcurrentHashMap<String, PluginType>()); 173 } 174 Map<String, PluginType> map = pluginTypeMap.get(pluginType); 175 String type = p.elementType().equals(Plugin.EMPTY) ? p.name() : p.elementType(); 176 map.put(p.name().toLowerCase(), new PluginType(item, type, p.printObject(), p.deferChildren())); 177 } 178 long elapsed = System.nanoTime() - start; 179 plugins = pluginTypeMap.get(type); 180 StringBuilder sb = new StringBuilder("Generated plugins"); 181 sb.append(" in "); 182 DecimalFormat numFormat = new DecimalFormat("#0"); 183 long seconds = elapsed / NANOS_PER_SECOND; 184 elapsed %= NANOS_PER_SECOND; 185 sb.append(numFormat.format(seconds)).append('.'); 186 numFormat = new DecimalFormat("000000000"); 187 sb.append(numFormat.format(elapsed)).append(" seconds"); 188 LOGGER.debug(sb.toString()); 189 } 190 191 private static ConcurrentMap<String, ConcurrentMap<String, PluginType>> decode(ClassLoader loader) { 192 Enumeration<URL> resources; 193 try { 194 resources = loader.getResources(PATH + FILENAME); 195 } catch (IOException ioe) { 196 LOGGER.warn("Unable to preload plugins", ioe); 197 return null; 198 } 199 ConcurrentMap<String, ConcurrentMap<String, PluginType>> map = 200 new ConcurrentHashMap<String, ConcurrentMap<String, PluginType>>(); 201 while (resources.hasMoreElements()) { 202 try { 203 URL url = resources.nextElement(); 204 LOGGER.debug("Found Plugin Map at {}", url.toExternalForm()); 205 InputStream is = url.openStream(); 206 BufferedInputStream bis = new BufferedInputStream(is); 207 DataInputStream dis = new DataInputStream(bis); 208 int count = dis.readInt(); 209 for (int j = 0; j < count; ++j) { 210 String type = dis.readUTF(); 211 int entries = dis.readInt(); 212 ConcurrentMap<String, PluginType> types = map.get(type); 213 if (types == null) { 214 types = new ConcurrentHashMap<String, PluginType>(count); 215 } 216 for (int i = 0; i < entries; ++i) { 217 String key = dis.readUTF(); 218 String className = dis.readUTF(); 219 String name = dis.readUTF(); 220 boolean printable = dis.readBoolean(); 221 boolean defer = dis.readBoolean(); 222 Class clazz = Class.forName(className); 223 types.put(key, new PluginType(clazz, name, printable, defer)); 224 } 225 map.putIfAbsent(type, types); 226 } 227 dis.close(); 228 } catch (Exception ex) { 229 LOGGER.warn("Unable to preload plugins", ex); 230 return null; 231 } 232 } 233 return map.size() == 0 ? null : map; 234 } 235 236 private static void encode(ConcurrentMap<String, ConcurrentMap<String, PluginType>> map) { 237 String fileName = rootDir + PATH + FILENAME; 238 try { 239 File file = new File(rootDir + PATH); 240 file.mkdirs(); 241 FileOutputStream fos = new FileOutputStream(fileName); 242 BufferedOutputStream bos = new BufferedOutputStream(fos); 243 DataOutputStream dos = new DataOutputStream(bos); 244 dos.writeInt(map.size()); 245 for (Map.Entry<String, ConcurrentMap<String, PluginType>> outer : map.entrySet()) { 246 dos.writeUTF(outer.getKey()); 247 dos.writeInt(outer.getValue().size()); 248 for (Map.Entry<String, PluginType> entry : outer.getValue().entrySet()) { 249 dos.writeUTF(entry.getKey()); 250 PluginType pt = entry.getValue(); 251 dos.writeUTF(pt.getPluginClass().getName()); 252 dos.writeUTF(pt.getElementName()); 253 dos.writeBoolean(pt.isObjectPrintable()); 254 dos.writeBoolean(pt.isDeferChildren()); 255 } 256 } 257 dos.close(); 258 } catch (Exception ex) { 259 ex.printStackTrace(); 260 } 261 } 262 263 /** 264 * A Test that checks to see if each class is annotated with a specific annotation. If it 265 * is, then the test returns true, otherwise false. 266 */ 267 public static class PluginTest extends ResolverUtil.ClassTest { 268 private final Class isA; 269 270 /** 271 * Constructs an AnnotatedWith test for the specified annotation type. 272 * @param isA The class to compare against. 273 */ 274 public PluginTest(Class isA) { 275 this.isA = isA; 276 } 277 278 /** 279 * Returns true if the type is annotated with the class provided to the constructor. 280 * @param type The type to check for. 281 * @return true if the Class is of the specified type. 282 */ 283 public boolean matches(Class type) { 284 return type != null && type.isAnnotationPresent(Plugin.class) && 285 (isA == null || isA.isAssignableFrom(type)); 286 } 287 288 @Override 289 public String toString() { 290 StringBuilder msg = new StringBuilder("annotated with @" + Plugin.class.getSimpleName()); 291 if (isA != null) { 292 msg.append(" is assignable to " + isA.getSimpleName()); 293 } 294 return msg.toString(); 295 } 296 } 297 298 }