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(String type) {
68 this.type = type;
69 this.clazz = null;
70 }
71
72
73
74
75
76
77 public PluginManager(String type, Class clazz) {
78 this.type = type;
79 this.clazz = clazz;
80 }
81
82 public static void main(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 PluginManager manager = new PluginManager("Core");
90 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(String p) {
101 packages.addIfAbsent(p);
102 }
103
104
105
106
107
108
109 public PluginType getPluginType(String name) {
110 return plugins.get(name.toLowerCase());
111 }
112
113
114
115
116
117 public Map<String, PluginType> getPlugins() {
118 return plugins;
119 }
120
121
122
123
124 public void collectPlugins() {
125 collectPlugins(true, null);
126 }
127
128
129
130
131
132
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
265
266
267 public static class PluginTest extends ResolverUtil.ClassTest {
268 private final Class isA;
269
270
271
272
273
274 public PluginTest(Class isA) {
275 this.isA = isA;
276 }
277
278
279
280
281
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 }