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