1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.logging.log4j.core.config.plugins.util;
19
20 import java.io.IOException;
21 import java.net.URI;
22 import java.net.URL;
23 import java.text.DecimalFormat;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.Enumeration;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.ConcurrentMap;
32 import java.util.concurrent.atomic.AtomicReference;
33
34 import org.apache.logging.log4j.Logger;
35 import org.apache.logging.log4j.core.config.plugins.Plugin;
36 import org.apache.logging.log4j.core.config.plugins.PluginAliases;
37 import org.apache.logging.log4j.core.config.plugins.processor.PluginCache;
38 import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry;
39 import org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor;
40 import org.apache.logging.log4j.core.util.Loader;
41 import org.apache.logging.log4j.status.StatusLogger;
42 import org.apache.logging.log4j.util.Strings;
43
44
45
46
47 public class PluginRegistry {
48
49 private static final Logger LOGGER = StatusLogger.getLogger();
50
51 private static volatile PluginRegistry INSTANCE;
52 private static final Object INSTANCE_LOCK = new Object();
53
54
55
56
57 private final AtomicReference<Map<String, List<PluginType<?>>>> pluginsByCategoryRef =
58 new AtomicReference<>();
59
60
61
62
63 private final ConcurrentMap<Long, Map<String, List<PluginType<?>>>> pluginsByCategoryByBundleId =
64 new ConcurrentHashMap<>();
65
66
67
68
69 private final ConcurrentMap<String, Map<String, List<PluginType<?>>>> pluginsByCategoryByPackage =
70 new ConcurrentHashMap<>();
71
72 private PluginRegistry() {
73 }
74
75
76
77
78
79
80
81 public static PluginRegistry getInstance() {
82 PluginRegistry result = INSTANCE;
83 if (result == null) {
84 synchronized (INSTANCE_LOCK) {
85 result = INSTANCE;
86 if (result == null) {
87 INSTANCE = result = new PluginRegistry();
88 }
89 }
90 }
91 return result;
92 }
93
94
95
96
97 public void clear() {
98 pluginsByCategoryRef.set(null);
99 pluginsByCategoryByPackage.clear();
100 pluginsByCategoryByBundleId.clear();
101 }
102
103
104
105
106 public Map<Long, Map<String, List<PluginType<?>>>> getPluginsByCategoryByBundleId() {
107 return pluginsByCategoryByBundleId;
108 }
109
110
111
112
113 public Map<String, List<PluginType<?>>> loadFromMainClassLoader() {
114 final Map<String, List<PluginType<?>>> existing = pluginsByCategoryRef.get();
115 if (existing != null) {
116
117 return existing;
118 }
119 final Map<String, List<PluginType<?>>> newPluginsByCategory = decodeCacheFiles(Loader.getClassLoader());
120
121
122
123
124 if (pluginsByCategoryRef.compareAndSet(null, newPluginsByCategory)) {
125 return newPluginsByCategory;
126 }
127 return pluginsByCategoryRef.get();
128 }
129
130
131
132
133 public void clearBundlePlugins(final long bundleId) {
134 pluginsByCategoryByBundleId.remove(bundleId);
135 }
136
137
138
139
140 public Map<String, List<PluginType<?>>> loadFromBundle(final long bundleId, final ClassLoader loader) {
141 Map<String, List<PluginType<?>>> existing = pluginsByCategoryByBundleId.get(bundleId);
142 if (existing != null) {
143
144 return existing;
145 }
146 final Map<String, List<PluginType<?>>> newPluginsByCategory = decodeCacheFiles(loader);
147
148
149
150
151 existing = pluginsByCategoryByBundleId.putIfAbsent(bundleId, newPluginsByCategory);
152 if (existing != null) {
153 return existing;
154 }
155 return newPluginsByCategory;
156 }
157
158 private Map<String, List<PluginType<?>>> decodeCacheFiles(final ClassLoader loader) {
159 final long startTime = System.nanoTime();
160 final PluginCache cache = new PluginCache();
161 try {
162 final Enumeration<URL> resources = loader.getResources(PluginProcessor.PLUGIN_CACHE_FILE);
163 if (resources == null) {
164 LOGGER.info("Plugin preloads not available from class loader {}", loader);
165 } else {
166 cache.loadCacheFiles(resources);
167 }
168 } catch (final IOException ioe) {
169 LOGGER.warn("Unable to preload plugins", ioe);
170 }
171 final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<>();
172 int pluginCount = 0;
173 for (final Map.Entry<String, Map<String, PluginEntry>> outer : cache.getAllCategories().entrySet()) {
174 final String categoryLowerCase = outer.getKey();
175 final List<PluginType<?>> types = new ArrayList<>(outer.getValue().size());
176 newPluginsByCategory.put(categoryLowerCase, types);
177 for (final Map.Entry<String, PluginEntry> inner : outer.getValue().entrySet()) {
178 final PluginEntry entry = inner.getValue();
179 final String className = entry.getClassName();
180 try {
181 final Class<?> clazz = loader.loadClass(className);
182 final PluginType<?> type = new PluginType<>(entry, clazz, entry.getName());
183 types.add(type);
184 ++pluginCount;
185 } catch (final ClassNotFoundException e) {
186 LOGGER.info("Plugin [{}] could not be loaded due to missing classes.", className, e);
187 } catch (final LinkageError e) {
188 LOGGER.info("Plugin [{}] could not be loaded due to linkage error.", className, e);
189 }
190 }
191 }
192 final int numPlugins = pluginCount;
193 LOGGER.debug(() -> {
194 final long endTime = System.nanoTime();
195 StringBuilder sb = new StringBuilder("Took ");
196 final DecimalFormat numFormat = new DecimalFormat("#0.000000");
197 sb.append(numFormat.format((endTime - startTime) * 1e-9));
198 sb.append(" seconds to load ").append(numPlugins);
199 sb.append(" plugins from ").append(loader);
200 return sb.toString();
201 });
202 return newPluginsByCategory;
203 }
204
205
206
207
208 public Map<String, List<PluginType<?>>> loadFromPackage(final String pkg) {
209 if (Strings.isBlank(pkg)) {
210
211 return Collections.emptyMap();
212 }
213 Map<String, List<PluginType<?>>> existing = pluginsByCategoryByPackage.get(pkg);
214 if (existing != null) {
215
216 return existing;
217 }
218
219 final long startTime = System.nanoTime();
220 final ResolverUtil resolver = new ResolverUtil();
221 final ClassLoader classLoader = Loader.getClassLoader();
222 if (classLoader != null) {
223 resolver.setClassLoader(classLoader);
224 }
225 resolver.findInPackage(new PluginTest(), pkg);
226
227 final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<>();
228 for (final Class<?> clazz : resolver.getClasses()) {
229 final Plugin plugin = clazz.getAnnotation(Plugin.class);
230 final String categoryLowerCase = plugin.category().toLowerCase();
231 List<PluginType<?>> list = newPluginsByCategory.get(categoryLowerCase);
232 if (list == null) {
233 newPluginsByCategory.put(categoryLowerCase, list = new ArrayList<>());
234 }
235 final PluginEntry mainEntry = new PluginEntry();
236 final String mainElementName = plugin.elementType().equals(
237 Plugin.EMPTY) ? plugin.name() : plugin.elementType();
238 mainEntry.setKey(plugin.name().toLowerCase());
239 mainEntry.setName(plugin.name());
240 mainEntry.setCategory(plugin.category());
241 mainEntry.setClassName(clazz.getName());
242 mainEntry.setPrintable(plugin.printObject());
243 mainEntry.setDefer(plugin.deferChildren());
244 final PluginType<?> mainType = new PluginType<>(mainEntry, clazz, mainElementName);
245 list.add(mainType);
246 final PluginAliases pluginAliases = clazz.getAnnotation(PluginAliases.class);
247 if (pluginAliases != null) {
248 for (final String alias : pluginAliases.value()) {
249 final PluginEntry aliasEntry = new PluginEntry();
250 final String aliasElementName = plugin.elementType().equals(
251 Plugin.EMPTY) ? alias.trim() : plugin.elementType();
252 aliasEntry.setKey(alias.trim().toLowerCase());
253 aliasEntry.setName(plugin.name());
254 aliasEntry.setCategory(plugin.category());
255 aliasEntry.setClassName(clazz.getName());
256 aliasEntry.setPrintable(plugin.printObject());
257 aliasEntry.setDefer(plugin.deferChildren());
258 final PluginType<?> aliasType = new PluginType<>(aliasEntry, clazz, aliasElementName);
259 list.add(aliasType);
260 }
261 }
262 }
263 LOGGER.debug(() -> {
264 final long endTime = System.nanoTime();
265 StringBuilder sb = new StringBuilder("Took ");
266 final DecimalFormat numFormat = new DecimalFormat("#0.000000");
267 sb.append(numFormat.format((endTime - startTime) * 1e-9));
268 sb.append(" seconds to load ").append(resolver.getClasses().size());
269 sb.append(" plugins from package ").append(pkg);
270 return sb.toString();
271 });
272
273
274
275
276 existing = pluginsByCategoryByPackage.putIfAbsent(pkg, newPluginsByCategory);
277 if (existing != null) {
278 return existing;
279 }
280 return newPluginsByCategory;
281 }
282
283
284
285
286
287
288
289 public static class PluginTest implements ResolverUtil.Test {
290 @Override
291 public boolean matches(final Class<?> type) {
292 return type != null && type.isAnnotationPresent(Plugin.class);
293 }
294
295 @Override
296 public String toString() {
297 return "annotated with @" + Plugin.class.getSimpleName();
298 }
299
300 @Override
301 public boolean matches(final URI resource) {
302 throw new UnsupportedOperationException();
303 }
304
305 @Override
306 public boolean doesMatchClass() {
307 return true;
308 }
309
310 @Override
311 public boolean doesMatchResource() {
312 return false;
313 }
314 }
315 }