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 org.apache.commons.logging.Log;
9 import org.apache.commons.logging.LogFactory;
10
11 import java.net.URL;
12 import java.util.Collection;
13 import java.util.HashMap;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.Map;
17 import java.util.Set;
18
19
20
21
22 public class PluginsClassLoader extends AbstractClassLoader
23 {
24 private static final Log log = LogFactory.getLog(PluginsClassLoader.class);
25
26 private final PluginAccessor pluginAccessor;
27
28 private final Map<String, Plugin> pluginResourceIndex = new HashMap<String, Plugin>();
29 private final Map<String, Plugin> pluginClassIndex = new HashMap<String, Plugin>();
30
31 private final Set<String> missedPluginResource = new HashSet<String>();
32 private final Set<String> missedPluginClass = new HashSet<String>();
33
34 public PluginsClassLoader(final PluginAccessor pluginAccessor)
35 {
36 this(null, pluginAccessor);
37 }
38
39 public PluginsClassLoader(final ClassLoader parent, final PluginAccessor pluginAccessor)
40 {
41 super(parent);
42 this.pluginAccessor = notNull("pluginAccessor", pluginAccessor);
43 }
44
45 @Override
46 protected URL findResource(final String name)
47 {
48 final Plugin indexedPlugin;
49 synchronized (this)
50 {
51 indexedPlugin = pluginResourceIndex.get(name);
52 }
53 final URL result;
54 if (isPluginEnabled(indexedPlugin))
55 {
56 result = indexedPlugin.getClassLoader().getResource(name);
57 }
58 else
59 {
60 result = getResourceFromPlugins(name);
61 }
62 if (log.isDebugEnabled())
63 {
64 log.debug("Find resource [ " + name + " ], found [ " + result + " ]");
65 }
66 return result;
67 }
68
69 @Override
70 protected Class<?> findClass(final String className) throws ClassNotFoundException
71 {
72 final Plugin indexedPlugin;
73 synchronized (this)
74 {
75 indexedPlugin = pluginClassIndex.get(className);
76 }
77
78 final Class<?> result;
79 if (isPluginEnabled(indexedPlugin))
80 {
81 result = indexedPlugin.getClassLoader().loadClass(className);
82 }
83 else
84 {
85 result = loadClassFromPlugins(className);
86 }
87 if (log.isDebugEnabled())
88 {
89 log.debug("Find class [ " + className + " ], found [ " + result + " ]");
90 }
91 if (result != null)
92 {
93 return result;
94 }
95 else
96 {
97 throw new ClassNotFoundException(className);
98 }
99 }
100
101 private Class<?> loadClassFromPlugins(final String className)
102 {
103 final boolean isMissedClassName;
104 synchronized (this)
105 {
106 isMissedClassName = missedPluginClass.contains(className);
107 }
108 if (isMissedClassName)
109 {
110 return null;
111 }
112 final Collection<Plugin> plugins = pluginAccessor.getEnabledPlugins();
113 for (final Plugin plugin : plugins)
114 {
115 try
116 {
117 final Class<?> result = plugin.getClassLoader().loadClass(className);
118
119 synchronized (this)
120 {
121 pluginClassIndex.put(className, plugin);
122 }
123 return result;
124 }
125 catch (final ClassNotFoundException e)
126 {
127
128 }
129 }
130 synchronized (this)
131 {
132 missedPluginClass.add(className);
133 }
134 return null;
135 }
136
137 private URL getResourceFromPlugins(final String name)
138 {
139 final boolean isMissedResource;
140 synchronized (this)
141 {
142 isMissedResource = missedPluginResource.contains(name);
143 }
144 if (isMissedResource)
145 {
146 return null;
147 }
148 final Collection<Plugin> plugins = pluginAccessor.getEnabledPlugins();
149 for (final Plugin plugin : plugins)
150 {
151 final URL resource = plugin.getClassLoader().getResource(name);
152 if (resource != null)
153 {
154 synchronized (this)
155 {
156 pluginResourceIndex.put(name, plugin);
157 }
158 return resource;
159 }
160 }
161 synchronized (this)
162 {
163 missedPluginResource.add(name);
164 }
165 return null;
166 }
167
168 private boolean isPluginEnabled(final Plugin plugin)
169 {
170 return (plugin != null) && pluginAccessor.isPluginEnabled(plugin.getKey());
171 }
172
173 public synchronized void notifyUninstallPlugin(final Plugin plugin)
174 {
175 flushMissesCaches();
176 for (final Iterator<Map.Entry<String, Plugin>> it = pluginResourceIndex.entrySet().iterator(); it.hasNext();)
177 {
178 final Map.Entry<String, Plugin> resourceEntry = it.next();
179 final Plugin pluginForResource = resourceEntry.getValue();
180 if (plugin.getKey().equals(pluginForResource.getKey()))
181 {
182 it.remove();
183 }
184 }
185 for (final Iterator<Map.Entry<String, Plugin>> it = pluginClassIndex.entrySet().iterator(); it.hasNext();)
186 {
187 final Map.Entry<String, Plugin> pluginClassEntry = it.next();
188 final Plugin pluginForClass = pluginClassEntry.getValue();
189 if (plugin.getKey().equals(pluginForClass.getKey()))
190 {
191 it.remove();
192 }
193 }
194 }
195
196 public synchronized void notifyPluginOrModuleEnabled()
197 {
198 flushMissesCaches();
199 }
200
201 private void flushMissesCaches()
202 {
203 missedPluginClass.clear();
204 missedPluginResource.clear();
205 }
206 }