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