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.util;
18
19 import java.io.BufferedInputStream;
20 import java.io.DataInputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.net.URI;
24 import java.net.URL;
25 import java.text.DecimalFormat;
26 import java.util.Collection;
27 import java.util.Enumeration;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.concurrent.ConcurrentMap;
31 import java.util.concurrent.CopyOnWriteArrayList;
32
33 import org.apache.logging.log4j.Logger;
34 import org.apache.logging.log4j.core.config.plugins.Plugin;
35 import org.apache.logging.log4j.core.config.plugins.PluginAliases;
36 import org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor;
37 import org.apache.logging.log4j.core.util.ClassLoaderResourceLoader;
38 import org.apache.logging.log4j.core.util.Closer;
39 import org.apache.logging.log4j.core.util.Loader;
40 import org.apache.logging.log4j.core.util.ResourceLoader;
41 import org.apache.logging.log4j.status.StatusLogger;
42 import org.apache.logging.log4j.util.Strings;
43
44
45
46
47 public class PluginManager {
48
49
50 private static final PluginRegistry<PluginType<?>> REGISTRY = new PluginRegistry<PluginType<?>>();
51 private static final CopyOnWriteArrayList<String> PACKAGES = new CopyOnWriteArrayList<String>();
52 private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core";
53
54 private static final Logger LOGGER = StatusLogger.getLogger();
55
56 private Map<String, PluginType<?>> plugins = new HashMap<String, PluginType<?>>();
57 private final String category;
58
59
60
61
62
63
64 public PluginManager(final String category) {
65 this.category = category;
66 }
67
68
69
70
71
72
73
74
75
76 @Deprecated
77
78 public static void main(final String[] args) {
79 System.err.println("ERROR: this tool is superseded by the annotation processor included in log4j-core.");
80 System.err.println("If the annotation processor does not work for you, please see the manual page:");
81 System.err.println("http://logging.apache.org/log4j/2.x/manual/configuration.html#ConfigurationSyntax");
82 System.exit(-1);
83 }
84
85
86
87
88
89
90 public static void addPackage(final String p) {
91 if (Strings.isBlank(p)) {
92 return;
93 }
94 if (PACKAGES.addIfAbsent(p)) {
95
96 REGISTRY.clear();
97 }
98 }
99
100
101
102
103
104
105 public static void addPackages(final Collection<String> packages) {
106 for (String pkg : packages) {
107 if (Strings.isNotBlank(pkg)) {
108 PACKAGES.addIfAbsent(pkg);
109 }
110 }
111 }
112
113
114
115
116
117
118
119 public PluginType<?> getPluginType(final String name) {
120 return plugins.get(name.toLowerCase());
121 }
122
123
124
125
126
127
128 public Map<String, PluginType<?>> getPlugins() {
129 return plugins;
130 }
131
132
133
134
135 public void collectPlugins() {
136 collectPlugins(true);
137 }
138
139
140
141
142
143
144
145 public void collectPlugins(boolean preLoad) {
146 if (REGISTRY.hasCategory(category)) {
147 plugins = REGISTRY.getCategory(category);
148 preLoad = false;
149 }
150 long start = System.nanoTime();
151 if (preLoad) {
152 final ResourceLoader loader = new ClassLoaderResourceLoader(Loader.getClassLoader());
153 loadPlugins(loader);
154 }
155 plugins = REGISTRY.getCategory(category);
156 loadFromPackages(start, preLoad);
157
158 long elapsed = System.nanoTime() - start;
159 reportPluginLoadDuration(preLoad, elapsed);
160 }
161
162 @SuppressWarnings({ "unchecked", "rawtypes" })
163 private void loadFromPackages(final long start, final boolean preLoad) {
164 if (plugins == null || plugins.size() == 0) {
165 if (!PACKAGES.contains(LOG4J_PACKAGES)) {
166 PACKAGES.add(LOG4J_PACKAGES);
167 }
168 }
169 final ResolverUtil resolver = new ResolverUtil();
170 final ClassLoader classLoader = Loader.getClassLoader();
171 if (classLoader != null) {
172 resolver.setClassLoader(classLoader);
173 }
174 final Class<?> cls = null;
175 final ResolverUtil.Test test = new PluginTest(cls);
176 for (final String pkg : PACKAGES) {
177 resolver.findInPackage(test, pkg);
178 }
179 for (final Class<?> clazz : resolver.getClasses()) {
180 final Plugin plugin = clazz.getAnnotation(Plugin.class);
181 final String pluginCategory = plugin.category();
182 final Map<String, PluginType<?>> map = REGISTRY.getCategory(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 plugins = REGISTRY.getCategory(category);
196 }
197
198 private void reportPluginLoadDuration(final boolean preLoad, long elapsed) {
199 final StringBuilder sb = new StringBuilder("Generated plugins in ");
200 DecimalFormat numFormat = new DecimalFormat("#0.000000");
201 final double seconds = elapsed / (1000.0 * 1000.0 * 1000.0);
202 sb.append(numFormat.format(seconds)).append(" seconds, packages: ");
203 sb.append(PACKAGES);
204 sb.append(", preload: ");
205 sb.append(preLoad);
206 sb.append(".");
207 LOGGER.debug(sb.toString());
208 }
209
210 public static void loadPlugins(final ResourceLoader loader) {
211 final PluginRegistry<PluginType<?>> registry = decode(loader);
212 if (registry != null) {
213 for (final Map.Entry<String, ConcurrentMap<String, PluginType<?>>> entry : registry.getCategories()) {
214 REGISTRY.getCategory(entry.getKey()).putAll(entry.getValue());
215 }
216 } else {
217 LOGGER.info("Plugin preloads not available from class loader {}", loader);
218 }
219 }
220
221 private static PluginRegistry<PluginType<?>> decode(final ResourceLoader loader) {
222 final Enumeration<URL> resources;
223 try {
224 resources = loader.getResources(PluginProcessor.PLUGIN_CACHE_FILE);
225 if (resources == null) {
226 return null;
227 }
228 } catch (final IOException ioe) {
229 LOGGER.warn("Unable to preload plugins", ioe);
230 return null;
231 }
232 final PluginRegistry<PluginType<?>> map = new PluginRegistry<PluginType<?>>();
233 while (resources.hasMoreElements()) {
234 final URL url = resources.nextElement();
235 LOGGER.debug("Found Plugin Map at {}", url.toExternalForm());
236 final InputStream is;
237 try {
238 is = url.openStream();
239 } catch (final IOException e) {
240 LOGGER.warn("Unable to open {}", url.toExternalForm(), e);
241 continue;
242 }
243 final DataInputStream dis = new DataInputStream(new BufferedInputStream(is));
244 try {
245 final int count = dis.readInt();
246 for (int j = 0; j < count; ++j) {
247 final String category = dis.readUTF();
248 final int entries = dis.readInt();
249 final Map<String, PluginType<?>> types = map.getCategory(category);
250 for (int i = 0; i < entries; ++i) {
251 final String key = dis.readUTF();
252 final String className = dis.readUTF();
253 final String name = dis.readUTF();
254 final boolean printable = dis.readBoolean();
255 final boolean defer = dis.readBoolean();
256 try {
257 final Class<?> clazz = loader.loadClass(className);
258 @SuppressWarnings({ "unchecked", "rawtypes" })
259 final PluginType<?> pluginType = new PluginType(clazz, name, printable, defer);
260 types.put(key, pluginType);
261 } catch (final ClassNotFoundException e) {
262 LOGGER.info("Plugin [{}] could not be loaded due to missing classes.", className, e);
263 } catch (final VerifyError e) {
264 LOGGER.info("Plugin [{}] could not be loaded due to verification error.", className, e);
265 }
266 }
267 }
268 } catch (final IOException ex) {
269 LOGGER.warn("Unable to preload plugins", ex);
270 } finally {
271 Closer.closeSilently(dis);
272 }
273 }
274 return map.isEmpty() ? null : map;
275 }
276
277
278
279
280
281 public static class PluginTest implements ResolverUtil.Test {
282 private final Class<?> isA;
283
284
285
286
287
288 public PluginTest(final Class<?> isA) {
289 this.isA = isA;
290 }
291
292
293
294
295
296
297 @Override
298 public boolean matches(final Class<?> type) {
299 return type != null && type.isAnnotationPresent(Plugin.class) &&
300 (isA == null || isA.isAssignableFrom(type));
301 }
302
303 @Override
304 public String toString() {
305 final StringBuilder msg = new StringBuilder("annotated with @" + Plugin.class.getSimpleName());
306 if (isA != null) {
307 msg.append(" is assignable to " + isA.getSimpleName());
308 }
309 return msg.toString();
310 }
311
312 @Override
313 public boolean matches(final URI resource) {
314 throw new UnsupportedOperationException();
315 }
316
317 @Override
318 public boolean doesMatchClass() {
319 return true;
320 }
321
322 @Override
323 public boolean doesMatchResource() {
324 return false;
325 }
326 }
327
328 }