1 package com.atlassian.plugin.classloader;
2
3 import org.apache.commons.io.FileUtils;
4 import org.apache.commons.io.IOUtils;
5 import org.apache.commons.lang.Validate;
6 import org.codehaus.classworlds.uberjar.protocol.jar.NonLockingJarHandler;
7
8 import java.io.File;
9 import java.io.FileOutputStream;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.net.URL;
13 import java.net.MalformedURLException;
14 import java.util.*;
15 import java.util.jar.JarEntry;
16 import java.util.jar.JarFile;
17
18
19
20
21 public final class PluginClassLoader extends ClassLoader
22 {
23 private static final String PLUGIN_INNER_JAR_PREFIX = "atlassian-plugins-innerjar";
24
25
26
27 private final List<File> pluginInnerJars;
28
29
30
31 private final Map<String, URL> entryMappings = new HashMap<String, URL>();
32
33
34
35 private final File tempDirectory;
36
37
38
39
40 public PluginClassLoader(final File pluginFile)
41 {
42 this(pluginFile, null);
43 }
44
45
46
47
48
49 public PluginClassLoader(final File pluginFile, ClassLoader parent)
50 {
51 this(pluginFile, parent, new File(System.getProperty("java.io.tmpdir")));
52 }
53
54
55
56
57
58
59
60 public PluginClassLoader(final File pluginFile, ClassLoader parent, File tempDirectory)
61 {
62 super(parent);
63 Validate.isTrue(tempDirectory.exists(), "Temp directory should exist");
64 this.tempDirectory = tempDirectory;
65 try
66 {
67 if (pluginFile == null || !pluginFile.exists())
68 {
69 throw new IllegalArgumentException("Plugin jar file must not be null and must exist.");
70 }
71 this.pluginInnerJars = new ArrayList<File>();
72 initialiseOuterJar(pluginFile);
73 }
74 catch (IOException e)
75 {
76 throw new IllegalStateException(e.getMessage());
77 }
78 }
79
80
81
82
83
84
85
86
87 private void initialiseOuterJar(File file) throws IOException
88 {
89 final JarFile jarFile = new JarFile(file);
90 try
91 {
92 for (Enumeration entries = jarFile.entries(); entries.hasMoreElements();)
93 {
94 final JarEntry jarEntry = (JarEntry) entries.nextElement();
95 if (isInnerJarPath(jarEntry.getName()))
96 {
97 initialiseInnerJar(jarFile, jarEntry);
98 }
99 else
100 {
101 addEntryMapping(jarEntry, file, true);
102 }
103 }
104 }
105 finally
106 {
107 jarFile.close();
108 }
109 }
110
111
112 private boolean isInnerJarPath(String name)
113 {
114 return name.startsWith("META-INF/lib/") && name.endsWith(".jar");
115 }
116
117 private void initialiseInnerJar(JarFile jarFile, JarEntry jarEntry) throws IOException
118 {
119 InputStream inputStream = null;
120 FileOutputStream fileOutputStream = null;
121 try
122 {
123 final File innerJarFile = File.createTempFile(PLUGIN_INNER_JAR_PREFIX, ".jar", tempDirectory);
124 inputStream = jarFile.getInputStream(jarEntry);
125 fileOutputStream = new FileOutputStream(innerJarFile);
126 IOUtils.copy(inputStream, fileOutputStream);
127 IOUtils.closeQuietly(fileOutputStream);
128
129 final JarFile innerJarJarFile = new JarFile(innerJarFile);
130 try
131 {
132 for (Enumeration entries = innerJarJarFile.entries(); entries.hasMoreElements();)
133 {
134 final JarEntry innerJarEntry = (JarEntry) entries.nextElement();
135 addEntryMapping(innerJarEntry, innerJarFile, false);
136 }
137 }
138 finally
139 {
140 innerJarJarFile.close();
141 }
142
143 pluginInnerJars.add(innerJarFile);
144 }
145 finally
146 {
147 IOUtils.closeQuietly(inputStream);
148 IOUtils.closeQuietly(fileOutputStream);
149 }
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
164 {
165
166 Class c = findLoadedClass(name);
167 if (c != null)
168 return c;
169
170
171 String path = name.replace('.', '/').concat(".class");
172 if (isEntryInPlugin(path))
173 {
174 try
175 {
176 return loadClassFromPlugin(name, path);
177 }
178 catch (IOException e)
179 {
180 throw new ClassNotFoundException("Unable to load class [ " + name + " ] from PluginClassLoader", e);
181 }
182 }
183 return super.loadClass(name, resolve);
184 }
185
186
187
188
189
190
191
192
193 public URL getResource(String name)
194 {
195 if (isEntryInPlugin(name))
196 {
197 return entryMappings.get(name);
198 }
199 else
200 {
201 return super.getResource(name);
202 }
203 }
204
205
206
207
208
209
210
211 public URL getLocalResource(String name)
212 {
213 if (isEntryInPlugin(name))
214 return getResource(name);
215 else
216 return null;
217 }
218
219 public void close()
220 {
221 for (File pluginInnerJar : pluginInnerJars)
222 {
223 FileUtils.deleteQuietly(pluginInnerJar);
224 }
225 }
226
227 List<File> getPluginInnerJars()
228 {
229 return new ArrayList<File>(pluginInnerJars);
230 }
231
232
233
234
235
236
237
238 private void initializePackage(String className)
239 {
240 int i = className.lastIndexOf('.');
241 if (i != -1)
242 {
243 String pkgname = className.substring(0, i);
244
245 Package pkg = getPackage(pkgname);
246 if (pkg == null)
247 {
248 definePackage(pkgname, null, null, null, null, null, null, null);
249 }
250 }
251 }
252
253 private Class<?> loadClassFromPlugin(String className, String path) throws IOException
254 {
255 InputStream inputStream = null;
256 try
257 {
258 URL resourceURL = entryMappings.get(path);
259 inputStream = resourceURL.openStream();
260 byte[] bytez = IOUtils.toByteArray(inputStream);
261 initializePackage(className);
262 return defineClass(className, bytez, 0, bytez.length);
263 }
264 finally
265 {
266 IOUtils.closeQuietly(inputStream);
267 }
268 }
269
270 private URL getUrlOfResourceInJar(String name, File jarFile)
271 {
272 try
273 {
274 return new URL(new URL("jar:file:" + jarFile.getAbsolutePath() + "!/"), name, NonLockingJarHandler.getInstance());
275 }
276 catch (MalformedURLException e)
277 {
278 throw new RuntimeException(e);
279 }
280 }
281
282 private boolean isEntryInPlugin(String name)
283 {
284 return entryMappings.containsKey(name);
285 }
286
287 private void addEntryMapping(JarEntry jarEntry, File jarFile, boolean overrideExistingEntries)
288 {
289 if (overrideExistingEntries)
290 {
291 addEntryUrl(jarEntry, jarFile);
292 }
293 else
294 {
295 if (!entryMappings.containsKey(jarEntry.getName()))
296 {
297 addEntryUrl(jarEntry, jarFile);
298 }
299 }
300 }
301
302 private void addEntryUrl(JarEntry jarEntry, File jarFile)
303 {
304 entryMappings.put(jarEntry.getName(), getUrlOfResourceInJar(jarEntry.getName(), jarFile));
305 }
306 }