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