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