1 package com.atlassian.plugin.classloader;
2
3 import static com.atlassian.plugin.util.Assertions.notNull;
4
5 import com.atlassian.plugin.Plugin;
6 import com.atlassian.plugin.PluginAccessor;
7
8 import java.net.URL;
9 import java.util.Collection;
10 import java.util.HashMap;
11 import java.util.HashSet;
12 import java.util.Iterator;
13 import java.util.Map;
14 import java.util.Set;
15
16 import com.atlassian.plugin.event.PluginEventListener;
17 import com.atlassian.plugin.event.PluginEventManager;
18 import com.atlassian.plugin.event.events.PluginEnabledEvent;
19 import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
20 import com.atlassian.plugin.event.impl.DefaultPluginEventManager;
21 import org.slf4j.LoggerFactory;
22 import org.slf4j.Logger;
23
24
25
26
27
28
29
30 public class PluginsClassLoader extends AbstractClassLoader
31 {
32 private static final Logger log = LoggerFactory.getLogger(PluginsClassLoader.class);
33
34 private final PluginAccessor pluginAccessor;
35
36 private final Map<String, Plugin> pluginResourceIndex = new HashMap<String, Plugin>();
37 private final Map<String, Plugin> pluginClassIndex = new HashMap<String, Plugin>();
38
39 private final Set<String> missedPluginResource = new HashSet<String>();
40 private final Set<String> missedPluginClass = new HashSet<String>();
41 private ClassLoader parentClassLoader;
42
43 public PluginsClassLoader(final PluginAccessor pluginAccessor)
44 {
45 this(null, pluginAccessor, new DefaultPluginEventManager());
46 }
47
48
49
50
51 public PluginsClassLoader(final ClassLoader parent, final PluginAccessor pluginAccessor)
52 {
53 this(parent, pluginAccessor, new DefaultPluginEventManager());
54 }
55
56
57
58
59
60
61
62 public PluginsClassLoader(final ClassLoader parent, final PluginAccessor pluginAccessor, PluginEventManager pluginEventManager)
63 {
64 super(parent);
65 this.parentClassLoader = parent;
66 this.pluginAccessor = notNull("pluginAccessor", pluginAccessor);
67 pluginEventManager.register(this);
68 }
69
70 @Override
71 protected URL findResource(final String name)
72 {
73 final Plugin indexedPlugin;
74 synchronized (this)
75 {
76 indexedPlugin = pluginResourceIndex.get(name);
77 }
78 final URL result;
79 if (isPluginEnabled(indexedPlugin))
80 {
81 result = indexedPlugin.getClassLoader().getResource(name);
82 }
83 else
84 {
85 result = getResourceFromPlugins(name);
86 }
87 if (log.isDebugEnabled())
88 {
89 log.debug("Find resource [ " + name + " ], found [ " + result + " ]");
90 }
91 return result;
92 }
93
94 @Override
95 protected Class<?> findClass(final String className) throws ClassNotFoundException
96 {
97 final Plugin indexedPlugin;
98 synchronized (this)
99 {
100 indexedPlugin = pluginClassIndex.get(className);
101 }
102
103 final Class<?> result;
104 if (isPluginEnabled(indexedPlugin))
105 {
106 result = indexedPlugin.getClassLoader().loadClass(className);
107 }
108 else
109 {
110 result = loadClassFromPlugins(className);
111 }
112 if (log.isDebugEnabled())
113 {
114 log.debug("Find class [ " + className + " ], found [ " + result + " ]");
115 }
116 if (result != null)
117 {
118 return result;
119 }
120 else
121 {
122 throw new ClassNotFoundException(className);
123 }
124 }
125
126 private Class<?> loadClassFromPlugins(final String className)
127 {
128 final boolean isMissedClassName;
129 synchronized (this)
130 {
131 isMissedClassName = missedPluginClass.contains(className);
132 }
133 if (isMissedClassName)
134 {
135 return null;
136 }
137 final Collection<Plugin> plugins = pluginAccessor.getEnabledPlugins();
138 if (log.isDebugEnabled())
139 {
140 log.debug("loadClassFromPlugins (" + className + ") looping through plugins...");
141 }
142 for (final Plugin plugin : plugins)
143 {
144 if (log.isDebugEnabled())
145 {
146 log.debug("loadClassFromPlugins (" + className + ") looking in plugin '" + plugin.getKey() + "'.");
147 }
148 try
149 {
150 final Class<?> result = plugin.getClassLoader().loadClass(className);
151
152 synchronized (this)
153 {
154 pluginClassIndex.put(className, plugin);
155 }
156 if (log.isDebugEnabled())
157 {
158 log.debug("loadClassFromPlugins (" + className + ") found in plugin '" + plugin.getKey() + "'.");
159 }
160 return result;
161 }
162 catch (final ClassNotFoundException e)
163 {
164
165 }
166 }
167 if (log.isDebugEnabled())
168 {
169 log.debug("loadClassFromPlugins (" + className + ") not found - caching the miss.");
170 }
171 synchronized (this)
172 {
173 missedPluginClass.add(className);
174 }
175 return null;
176 }
177
178 private URL getResourceFromPlugins(final String name)
179 {
180 final boolean isMissedResource;
181 synchronized (this)
182 {
183 isMissedResource = missedPluginResource.contains(name);
184 }
185 if (isMissedResource)
186 {
187 return null;
188 }
189 final Collection<Plugin> plugins = pluginAccessor.getEnabledPlugins();
190 for (final Plugin plugin : plugins)
191 {
192 final URL resource = plugin.getClassLoader().getResource(name);
193 if (resource != null)
194 {
195 synchronized (this)
196 {
197 pluginResourceIndex.put(name, plugin);
198 }
199 return resource;
200 }
201 }
202 synchronized (this)
203 {
204 missedPluginResource.add(name);
205 }
206 return null;
207 }
208
209 private boolean isPluginEnabled(final Plugin plugin)
210 {
211 return (plugin != null) && pluginAccessor.isPluginEnabled(plugin.getKey());
212 }
213
214 public synchronized void notifyUninstallPlugin(final Plugin plugin)
215 {
216 flushMissesCaches();
217 for (final Iterator<Map.Entry<String, Plugin>> it = pluginResourceIndex.entrySet().iterator(); it.hasNext();)
218 {
219 final Map.Entry<String, Plugin> resourceEntry = it.next();
220 final Plugin pluginForResource = resourceEntry.getValue();
221 if (plugin.getKey().equals(pluginForResource.getKey()))
222 {
223 it.remove();
224 }
225 }
226 for (final Iterator<Map.Entry<String, Plugin>> it = pluginClassIndex.entrySet().iterator(); it.hasNext();)
227 {
228 final Map.Entry<String, Plugin> pluginClassEntry = it.next();
229 final Plugin pluginForClass = pluginClassEntry.getValue();
230 if (plugin.getKey().equals(pluginForClass.getKey()))
231 {
232 it.remove();
233 }
234 }
235 }
236
237
238
239
240
241
242
243
244
245
246 public Plugin getPluginForClass(String className)
247 {
248 Plugin indexedPlugin;
249 synchronized (this)
250 {
251 indexedPlugin = pluginClassIndex.get(className);
252 }
253
254 if (isPluginEnabled(indexedPlugin))
255 {
256 return indexedPlugin;
257 }
258
259 if (isSystemClass(className))
260 {
261 return null;
262 }
263
264
265 Class clazz = loadClassFromPlugins(className);
266 if (clazz == null)
267 {
268
269 return null;
270 }
271 synchronized (this)
272 {
273
274
275 indexedPlugin = pluginClassIndex.get(className);
276 }
277 return indexedPlugin;
278 }
279
280 private boolean isSystemClass(final String className)
281 {
282 try
283 {
284
285 getClass().getClassLoader().loadClass(className);
286
287 return true;
288 }
289 catch (ClassNotFoundException ex)
290 {
291
292 if (parentClassLoader != null)
293 {
294 try
295 {
296 parentClassLoader.loadClass(className);
297
298 return true;
299 }
300 catch (ClassNotFoundException ex2)
301 {
302 return false;
303 }
304 }
305 else
306 {
307
308 return false;
309 }
310 }
311 }
312
313 @PluginEventListener
314 public void onPluginEnabled(PluginEnabledEvent event)
315 {
316 notifyPluginOrModuleEnabled();
317 }
318
319 @PluginEventListener
320 public void onPluginModuleEnabled(PluginModuleEnabledEvent event)
321 {
322 notifyPluginOrModuleEnabled();
323 }
324
325 public synchronized void notifyPluginOrModuleEnabled()
326 {
327 flushMissesCaches();
328 }
329
330 private void flushMissesCaches()
331 {
332 missedPluginClass.clear();
333 missedPluginResource.clear();
334 }
335 }