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 org.apache.logging.log4j.Logger;
20 import org.apache.logging.log4j.core.helpers.Loader;
21 import org.apache.logging.log4j.status.StatusLogger;
22
23 import java.io.BufferedInputStream;
24 import java.io.BufferedOutputStream;
25 import java.io.DataInputStream;
26 import java.io.DataOutputStream;
27 import java.io.File;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.net.URL;
32 import java.text.DecimalFormat;
33 import java.util.Enumeration;
34 import java.util.HashMap;
35 import java.util.Map;
36 import java.util.concurrent.ConcurrentHashMap;
37 import java.util.concurrent.ConcurrentMap;
38 import java.util.concurrent.CopyOnWriteArrayList;
39
40
41
42
43 public class PluginManager {
44
45 private static final long NANOS_PER_SECOND = 1000000000L;
46
47 private static ConcurrentMap<String, ConcurrentMap<String, PluginType>> pluginTypeMap =
48 new ConcurrentHashMap<String, ConcurrentMap<String, PluginType>>();
49
50 private static final CopyOnWriteArrayList<String> PACKAGES = new CopyOnWriteArrayList<String>();
51 private static final String PATH = "org/apache/logging/log4j/core/config/plugins/";
52 private static final String FILENAME = "Log4j2Plugins.dat";
53 private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core";
54
55 private static final Logger LOGGER = StatusLogger.getLogger();
56
57 private static String rootDir;
58
59 private Map<String, PluginType> plugins = new HashMap<String, PluginType>();
60 private final String type;
61 private final Class<?> clazz;
62
63
64
65
66
67 public PluginManager(final String type) {
68 this.type = type;
69 this.clazz = null;
70 }
71
72
73
74
75
76
77 public PluginManager(final String type, final Class<?> clazz) {
78 this.type = type;
79 this.clazz = clazz;
80 }
81
82 public static void main(final String[] args) throws Exception {
83 if (args == null || args.length < 1) {
84 System.err.println("A target directory must be specified");
85 System.exit(-1);
86 }
87 rootDir = args[0].endsWith("/") || args[0].endsWith("\\") ? args[0] : args[0] + "/";
88
89 final PluginManager manager = new PluginManager("Core");
90 final String packages = args.length == 2 ? args[1] : null;
91
92 manager.collectPlugins(false, packages);
93 encode(pluginTypeMap);
94 }
95
96
97
98
99
100 public static void addPackage(final String p) {
101 if (PACKAGES.addIfAbsent(p))
102 {
103
104 pluginTypeMap.clear();
105 }
106 }
107
108
109
110
111
112
113 public PluginType getPluginType(final String name) {
114 return plugins.get(name.toLowerCase());
115 }
116
117
118
119
120
121 public Map<String, PluginType> getPlugins() {
122 return plugins;
123 }
124
125
126
127
128 public void collectPlugins() {
129 collectPlugins(true, null);
130 }
131
132
133
134
135
136
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
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
280 }
281 }
282 }
283
284
285
286
287
288 public static class PluginTest extends ResolverUtil.ClassTest {
289 private final Class<?> isA;
290
291
292
293
294
295 public PluginTest(final Class<?> isA) {
296 this.isA = isA;
297 }
298
299
300
301
302
303
304 public boolean matches(final Class<?> type) {
305 return type != null && type.isAnnotationPresent(Plugin.class) &&
306 (isA == null || isA.isAssignableFrom(type));
307 }
308
309 @Override
310 public String toString() {
311 final StringBuilder msg = new StringBuilder("annotated with @" + Plugin.class.getSimpleName());
312 if (isA != null) {
313 msg.append(" is assignable to " + isA.getSimpleName());
314 }
315 return msg.toString();
316 }
317 }
318
319 }