1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.config.plugins;
18
19 import java.io.BufferedInputStream;
20 import java.io.BufferedOutputStream;
21 import java.io.DataInputStream;
22 import java.io.DataOutputStream;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.net.URL;
28 import java.text.DecimalFormat;
29 import java.util.Enumeration;
30 import java.util.HashMap;
31 import java.util.Map;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.concurrent.ConcurrentMap;
34 import java.util.concurrent.CopyOnWriteArrayList;
35
36 import org.apache.logging.log4j.Logger;
37 import org.apache.logging.log4j.core.helpers.Closer;
38 import org.apache.logging.log4j.core.helpers.Loader;
39 import org.apache.logging.log4j.status.StatusLogger;
40
41
42
43
44 public class PluginManager {
45
46 private static final long NANOS_PER_SECOND = 1000000000L;
47
48 private static ConcurrentMap<String, ConcurrentMap<String, PluginType<?>>> pluginTypeMap =
49 new ConcurrentHashMap<String, ConcurrentMap<String, PluginType<?>>>();
50
51 private static final CopyOnWriteArrayList<String> PACKAGES = new CopyOnWriteArrayList<String>();
52 private static final String PATH = "org/apache/logging/log4j/core/config/plugins/";
53 private static final String FILENAME = "Log4j2Plugins.dat";
54 private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core";
55
56 private static final Logger LOGGER = StatusLogger.getLogger();
57
58 private static String rootDir;
59
60 private Map<String, PluginType<?>> plugins = new HashMap<String, PluginType<?>>();
61 private final String type;
62 private final Class<?> clazz;
63
64
65
66
67
68 public PluginManager(final String type) {
69 this.type = type;
70 this.clazz = null;
71 }
72
73
74
75
76
77
78 public PluginManager(final String type, final Class<?> clazz) {
79 this.type = type;
80 this.clazz = clazz;
81 }
82
83 public static void main(final String[] args) throws Exception {
84 if (args == null || args.length < 1) {
85 System.err.println("A target directory must be specified");
86 System.exit(-1);
87 }
88 rootDir = args[0].endsWith("/") || args[0].endsWith("\\") ? args[0] : args[0] + "/";
89
90 final PluginManager manager = new PluginManager("Core");
91 final String packages = args.length == 2 ? args[1] : null;
92
93 manager.collectPlugins(false, packages);
94 encode(pluginTypeMap);
95 }
96
97
98
99
100
101 public static void addPackage(final String p) {
102 if (PACKAGES.addIfAbsent(p))
103 {
104
105 pluginTypeMap.clear();
106 }
107 }
108
109
110
111
112
113
114 public PluginType<?> getPluginType(final String name) {
115 return plugins.get(name.toLowerCase());
116 }
117
118
119
120
121
122 public Map<String, PluginType<?>> getPlugins() {
123 return plugins;
124 }
125
126
127
128
129 public void collectPlugins() {
130 collectPlugins(true, null);
131 }
132
133
134
135
136
137
138
139 @SuppressWarnings({ "unchecked", "rawtypes" })
140 public void collectPlugins(boolean preLoad, final String pkgs) {
141 if (pluginTypeMap.containsKey(type)) {
142 plugins = pluginTypeMap.get(type);
143 preLoad = false;
144 }
145 final long start = System.nanoTime();
146 final ResolverUtil resolver = new ResolverUtil();
147 final ClassLoader classLoader = Loader.getClassLoader();
148 if (classLoader != null) {
149 resolver.setClassLoader(classLoader);
150 }
151 if (preLoad) {
152 final ConcurrentMap<String, ConcurrentMap<String, PluginType<?>>> map = decode(classLoader);
153 if (map != null) {
154 pluginTypeMap = map;
155 plugins = map.get(type);
156 } else {
157 LOGGER.warn("Plugin preloads not available from class loader {}", classLoader);
158 }
159 }
160 if (plugins == null || plugins.size() == 0) {
161 if (pkgs == null) {
162 if (!PACKAGES.contains(LOG4J_PACKAGES)) {
163 PACKAGES.add(LOG4J_PACKAGES);
164 }
165 } else {
166 final String[] names = pkgs.split(",");
167 for (final String name : names) {
168 PACKAGES.add(name);
169 }
170 }
171 }
172 final ResolverUtil.Test test = new PluginTest(clazz);
173 for (final String pkg : PACKAGES) {
174 resolver.findInPackage(test, pkg);
175 }
176 for (final Class<?> clazz : resolver.getClasses()) {
177 final Plugin plugin = clazz.getAnnotation(Plugin.class);
178 final String pluginCategory = plugin.category();
179 if (!pluginTypeMap.containsKey(pluginCategory)) {
180 pluginTypeMap.putIfAbsent(pluginCategory, new ConcurrentHashMap<String, PluginType<?>>());
181 }
182 final Map<String, PluginType<?>> map = pluginTypeMap.get(pluginCategory);
183 String type = plugin.elementType().equals(Plugin.EMPTY) ? plugin.name() : plugin.elementType();
184 PluginType pluginType = new PluginType(clazz, type, plugin.printObject(), plugin.deferChildren());
185 map.put(plugin.name().toLowerCase(), pluginType);
186 final PluginAliases pluginAliases = clazz.getAnnotation(PluginAliases.class);
187 if (pluginAliases != null) {
188 for (String alias : pluginAliases.value()) {
189 type = plugin.elementType().equals(Plugin.EMPTY) ? alias : plugin.elementType();
190 pluginType = new PluginType(clazz, type, plugin.printObject(), plugin.deferChildren());
191 map.put(alias.trim().toLowerCase(), pluginType);
192 }
193 }
194 }
195 long elapsed = System.nanoTime() - start;
196 plugins = pluginTypeMap.get(type);
197 final StringBuilder sb = new StringBuilder("Generated plugins");
198 sb.append(" in ");
199 DecimalFormat numFormat = new DecimalFormat("#0");
200 final long seconds = elapsed / NANOS_PER_SECOND;
201 elapsed %= NANOS_PER_SECOND;
202 sb.append(numFormat.format(seconds)).append('.');
203 numFormat = new DecimalFormat("000000000");
204 sb.append(numFormat.format(elapsed)).append(" seconds");
205 LOGGER.debug(sb.toString());
206 }
207
208 @SuppressWarnings({ "unchecked" })
209 private static ConcurrentMap<String, ConcurrentMap<String, PluginType<?>>> decode(final ClassLoader classLoader) {
210 Enumeration<URL> resources;
211 try {
212 resources = classLoader.getResources(PATH + FILENAME);
213 } catch (final IOException ioe) {
214 LOGGER.warn("Unable to preload plugins", ioe);
215 return null;
216 }
217 final ConcurrentMap<String, ConcurrentMap<String, PluginType<?>>> map =
218 new ConcurrentHashMap<String, ConcurrentMap<String, PluginType<?>>>();
219 while (resources.hasMoreElements()) {
220 DataInputStream dis = null;
221 try {
222 final URL url = resources.nextElement();
223 LOGGER.debug("Found Plugin Map at {}", url.toExternalForm());
224 final InputStream is = url.openStream();
225 final BufferedInputStream bis = new BufferedInputStream(is);
226 dis = new DataInputStream(bis);
227 final int count = dis.readInt();
228 for (int j = 0; j < count; ++j) {
229 final String type = dis.readUTF();
230 final int entries = dis.readInt();
231 ConcurrentMap<String, PluginType<?>> types = map.get(type);
232 if (types == null) {
233 types = new ConcurrentHashMap<String, PluginType<?>>(count);
234 }
235 for (int i = 0; i < entries; ++i) {
236 final String key = dis.readUTF();
237 final String className = dis.readUTF();
238 final String name = dis.readUTF();
239 final boolean printable = dis.readBoolean();
240 final boolean defer = dis.readBoolean();
241 final Class<?> clazz = Class.forName(className);
242 types.put(key, new PluginType(clazz, name, printable, defer));
243 }
244 map.putIfAbsent(type, types);
245 }
246 } catch (final Exception ex) {
247 LOGGER.warn("Unable to preload plugins", ex);
248 return null;
249 } finally {
250 Closer.closeSilent(dis);
251 }
252 }
253 return map.size() == 0 ? null : map;
254 }
255
256 private static void encode(final ConcurrentMap<String, ConcurrentMap<String, PluginType<?>>> map) {
257 final String fileName = rootDir + PATH + FILENAME;
258 DataOutputStream dos = null;
259 try {
260 final File file = new File(rootDir + PATH);
261 file.mkdirs();
262 final FileOutputStream fos = new FileOutputStream(fileName);
263 final BufferedOutputStream bos = new BufferedOutputStream(fos);
264 dos = new DataOutputStream(bos);
265 dos.writeInt(map.size());
266 for (final Map.Entry<String, ConcurrentMap<String, PluginType<?>>> outer : map.entrySet()) {
267 dos.writeUTF(outer.getKey());
268 dos.writeInt(outer.getValue().size());
269 for (final Map.Entry<String, PluginType<?>> entry : outer.getValue().entrySet()) {
270 dos.writeUTF(entry.getKey());
271 final PluginType<?> pt = entry.getValue();
272 dos.writeUTF(pt.getPluginClass().getName());
273 dos.writeUTF(pt.getElementName());
274 dos.writeBoolean(pt.isObjectPrintable());
275 dos.writeBoolean(pt.isDeferChildren());
276 }
277 }
278 } catch (final Exception ex) {
279 ex.printStackTrace();
280 } finally {
281 Closer.closeSilent(dos);
282 }
283 }
284
285
286
287
288
289 public static class PluginTest extends ResolverUtil.ClassTest {
290 private final Class<?> isA;
291
292
293
294
295
296 public PluginTest(final Class<?> isA) {
297 this.isA = isA;
298 }
299
300
301
302
303
304
305 @Override
306 public boolean matches(final Class<?> type) {
307 return type != null && type.isAnnotationPresent(Plugin.class) &&
308 (isA == null || isA.isAssignableFrom(type));
309 }
310
311 @Override
312 public String toString() {
313 final StringBuilder msg = new StringBuilder("annotated with @" + Plugin.class.getSimpleName());
314 if (isA != null) {
315 msg.append(" is assignable to " + isA.getSimpleName());
316 }
317 return msg.toString();
318 }
319 }
320
321 }