svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.framework / org.gvsig.andami / src / main / java / org / gvsig / andami / plugins / PluginClassLoader.java @ 40596
History | View | Annotate | Download (16.7 KB)
1 | 40559 | jjdelcerro | /**
|
---|---|---|---|
2 | * gvSIG. Desktop Geographic Information System.
|
||
3 | 40435 | jjdelcerro | *
|
4 | 40559 | jjdelcerro | * Copyright (C) 2007-2013 gvSIG Association.
|
5 | 40435 | jjdelcerro | *
|
6 | * This program is free software; you can redistribute it and/or
|
||
7 | * modify it under the terms of the GNU General Public License
|
||
8 | 40559 | jjdelcerro | * as published by the Free Software Foundation; either version 3
|
9 | 40435 | jjdelcerro | * of the License, or (at your option) any later version.
|
10 | *
|
||
11 | * This program is distributed in the hope that it will be useful,
|
||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
14 | * GNU General Public License for more details.
|
||
15 | *
|
||
16 | * You should have received a copy of the GNU General Public License
|
||
17 | * along with this program; if not, write to the Free Software
|
||
18 | 40559 | jjdelcerro | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19 | * MA 02110-1301, USA.
|
||
20 | 40435 | jjdelcerro | *
|
21 | 40559 | jjdelcerro | * For any additional information, do not hesitate to contact us
|
22 | * at info AT gvsig.com, or visit our website www.gvsig.com.
|
||
23 | 40435 | jjdelcerro | */
|
24 | package org.gvsig.andami.plugins; |
||
25 | |||
26 | import java.io.DataInputStream; |
||
27 | import java.io.File; |
||
28 | import java.io.FileInputStream; |
||
29 | import java.io.IOException; |
||
30 | import java.io.InputStream; |
||
31 | import java.net.MalformedURLException; |
||
32 | import java.net.URL; |
||
33 | import java.net.URLClassLoader; |
||
34 | import java.security.AllPermission; |
||
35 | import java.security.CodeSource; |
||
36 | import java.security.PermissionCollection; |
||
37 | import java.util.ArrayList; |
||
38 | import java.util.Enumeration; |
||
39 | import java.util.Hashtable; |
||
40 | 40566 | jjdelcerro | import java.util.Iterator; |
41 | 40435 | jjdelcerro | import java.util.List; |
42 | 40566 | jjdelcerro | import java.util.Map; |
43 | import java.util.Map.Entry; |
||
44 | 40435 | jjdelcerro | import java.util.StringTokenizer; |
45 | import java.util.zip.ZipEntry; |
||
46 | import java.util.zip.ZipException; |
||
47 | import java.util.zip.ZipFile; |
||
48 | |||
49 | 40566 | jjdelcerro | import org.gvsig.andami.messages.Messages; |
50 | 40435 | jjdelcerro | import org.slf4j.Logger; |
51 | import org.slf4j.LoggerFactory; |
||
52 | |||
53 | |||
54 | |||
55 | /**
|
||
56 | * <p>Class loader which loads the classes requested by the
|
||
57 | * plugins. It first tries to search in the classpath, then it requests
|
||
58 | * the class to the parent classloader, then it searches in the owns
|
||
59 | * plugins' library dir, and if all these methods fail, it tries to load
|
||
60 | * the class from any of the depended plugins. Finally, if this also
|
||
61 | * fails, the other classloaders provided in the <code>addLoaders</code> method
|
||
62 | * are requested to load the class.</p>
|
||
63 | *
|
||
64 | * <p>The class loader can also be used to load resources from the
|
||
65 | * plugin's directory by using the <code>getResource()</code> method.</p>
|
||
66 | *
|
||
67 | * @author Fernando Gonz�lez Cort�s
|
||
68 | */
|
||
69 | public class PluginClassLoader extends URLClassLoader { |
||
70 | /** DOCUMENT ME! */
|
||
71 | private static Logger logger = LoggerFactory.getLogger(PluginClassLoader.class.getName()); |
||
72 | |||
73 | /** DOCUMENT ME! */
|
||
74 | 40566 | jjdelcerro | private Hashtable<String, ZipFile> clasesJar = new Hashtable<String, ZipFile>(); |
75 | 40435 | jjdelcerro | |
76 | /** DOCUMENT ME! */
|
||
77 | private File baseDir; |
||
78 | private PluginClassLoader[] pluginLoaders; |
||
79 | 40566 | jjdelcerro | private static List<ClassLoader> otherLoaders=new ArrayList<ClassLoader>(); |
80 | 40435 | jjdelcerro | private boolean isOtherLoader=false; |
81 | |||
82 | /**
|
||
83 | * Creates a new PluginClassLoader object.
|
||
84 | *
|
||
85 | * @param jars Array with the search paths where classes will be searched
|
||
86 | * @param baseDir Base directory for this plugin. This is the directory
|
||
87 | * which will be used as basedir in the <code>getResources</code> method.
|
||
88 | * @param cl The parent classloader of this classloader. It will be used to
|
||
89 | * search classes before trying to search in the plugin's directory
|
||
90 | * @param pluginLoaders The classloaders of the depended plugins.
|
||
91 | *
|
||
92 | * @throws IOException
|
||
93 | */
|
||
94 | public PluginClassLoader(URL[] jars, String baseDir, ClassLoader cl, |
||
95 | PluginClassLoader[] pluginLoaders) throws IOException { |
||
96 | super(jars, cl);
|
||
97 | 40566 | jjdelcerro | logger.debug("Creating PluginClassLoader for {}.", baseDir);
|
98 | 40435 | jjdelcerro | this.baseDir = new File(new File(baseDir).getAbsolutePath()); |
99 | this.pluginLoaders = pluginLoaders;
|
||
100 | |||
101 | 40566 | jjdelcerro | if( jars==null || jars.length<1 ) { |
102 | debugDump(); |
||
103 | 40562 | jjdelcerro | return;
|
104 | } |
||
105 | 40435 | jjdelcerro | ZipFile[] jarFiles = new ZipFile[jars.length]; |
106 | |||
107 | for (int i = 0; i < jars.length; i++) { |
||
108 | try {
|
||
109 | jarFiles[i] = new ZipFile(jars[i].getPath()); |
||
110 | |||
111 | 40566 | jjdelcerro | Enumeration<? extends ZipEntry> entradas = jarFiles[i].entries(); |
112 | 40435 | jjdelcerro | |
113 | while (entradas.hasMoreElements()) {
|
||
114 | ZipEntry file = (ZipEntry) entradas.nextElement(); |
||
115 | String fileName = file.getName();
|
||
116 | |||
117 | if (!fileName.toLowerCase().endsWith(".class")) { //$NON-NLS-1$ |
||
118 | |||
119 | continue;
|
||
120 | } |
||
121 | |||
122 | fileName = fileName.substring(0, fileName.length() - 6) |
||
123 | .replace('/', '.'); |
||
124 | |||
125 | if (clasesJar.get(fileName) != null) { |
||
126 | 40566 | jjdelcerro | logger.warn("Duplicated class {} in {} and {}",
|
127 | new Object[] { |
||
128 | fileName, |
||
129 | jarFiles[i].getName(), |
||
130 | clasesJar.get(fileName).getName() |
||
131 | } |
||
132 | ); |
||
133 | 40435 | jjdelcerro | } |
134 | else {
|
||
135 | clasesJar.put(fileName, jarFiles[i]); |
||
136 | } |
||
137 | } |
||
138 | } catch (ZipException e) { |
||
139 | throw new IOException(e.getMessage() + " Jar: " + |
||
140 | jars[i].getPath() + ": " + jarFiles[i]);
|
||
141 | } catch (IOException e) { |
||
142 | throw e;
|
||
143 | 40566 | jjdelcerro | } finally {
|
144 | debugDump(); |
||
145 | 40435 | jjdelcerro | } |
146 | } |
||
147 | } |
||
148 | |||
149 | 40566 | jjdelcerro | private void debugDump() { |
150 | if( !logger.isDebugEnabled() ) {
|
||
151 | return;
|
||
152 | } |
||
153 | logger.debug(this.toString());
|
||
154 | logger.debug(" baseDir: " + this.baseDir); |
||
155 | logger.debug(" parent: " + this.getParent()); |
||
156 | logger.debug(" depends:");
|
||
157 | for( int n=0; n<this.pluginLoaders.length; n++ ) { |
||
158 | logger.debug(" {}", this.pluginLoaders[n].toString() ); |
||
159 | } |
||
160 | logger.debug(" urls:");
|
||
161 | URL[] urls = this.getURLs(); |
||
162 | for( int n=0 ; n<urls.length; n++ ) { |
||
163 | logger.debug(" " + urls[n].toString());
|
||
164 | } |
||
165 | logger.debug(" classes:");
|
||
166 | Iterator<Map.Entry<String, ZipFile>>it = this.clasesJar.entrySet().iterator(); |
||
167 | while( it.hasNext() ) {
|
||
168 | Entry<String, ZipFile> entry = it.next(); |
||
169 | logger.debug(" "+ entry.getKey() + "("+entry.getValue().getName()+")"); |
||
170 | } |
||
171 | } |
||
172 | |||
173 | 40435 | jjdelcerro | protected Class singleLoadClass(String name) throws ClassNotFoundException { |
174 | // Buscamos en las clases de las librer�as del plugin
|
||
175 | 40566 | jjdelcerro | Class<?> c = findLoadedClass(name);
|
176 | 40435 | jjdelcerro | |
177 | if (c != null) { |
||
178 | return c;
|
||
179 | } |
||
180 | |||
181 | 40566 | jjdelcerro | logger.debug("Searching class '{}' in {}", new Object[] {name, this.toString()}); |
182 | 40435 | jjdelcerro | try {
|
183 | ZipFile jar = (ZipFile) clasesJar.get(name); |
||
184 | |||
185 | 40566 | jjdelcerro | //No esta en ningun jar
|
186 | 40435 | jjdelcerro | if (jar == null) { |
187 | //Buscamos en el directorio de clases
|
||
188 | String classFileName = baseDir + "/classes/" + |
||
189 | name.replace('.', '/') + ".class"; |
||
190 | File f = new File(classFileName); |
||
191 | if (f.exists()){
|
||
192 | byte[] data = loadClassData(f); |
||
193 | c = defineClass(name, data, 0, data.length);
|
||
194 | 40566 | jjdelcerro | logger.debug("Found class {} in classes-folder of plugin {}",
|
195 | new Object[] {name, baseDir}); |
||
196 | 40435 | jjdelcerro | |
197 | 40566 | jjdelcerro | } else {
|
198 | 40435 | jjdelcerro | //Buscamos en los otros plugins
|
199 | 40566 | jjdelcerro | logger.debug("Searching in depends pluginLoaders");
|
200 | 40435 | jjdelcerro | for (int i = 0; i < pluginLoaders.length; i++) { |
201 | 40566 | jjdelcerro | c = null;
|
202 | if (pluginLoaders[i] != null) { |
||
203 | try {
|
||
204 | 40435 | jjdelcerro | c = pluginLoaders[i].singleLoadClass(name); |
205 | 40566 | jjdelcerro | } catch (ClassNotFoundException e) { |
206 | // Si no la encontramos en el primer plugin, capturamos la exceptcion
|
||
207 | // porque es probable que la encontremos en el resto de plugins.
|
||
208 | } |
||
209 | } |
||
210 | 40435 | jjdelcerro | if (c != null) { |
211 | break;
|
||
212 | } |
||
213 | } |
||
214 | } |
||
215 | } else {
|
||
216 | String fileName = name.replace('.', '/') + ".class"; |
||
217 | ZipEntry classFile = jar.getEntry(fileName);
|
||
218 | byte[] data = loadClassData(classFile, |
||
219 | jar.getInputStream(classFile)); |
||
220 | |||
221 | c = defineClass(name, data, 0, data.length);
|
||
222 | |||
223 | 40566 | jjdelcerro | logger.debug("Found class {} in jar {} of plugin {}",
|
224 | new Object[] { name, jar.getName(), baseDir} ); |
||
225 | 40435 | jjdelcerro | } |
226 | |||
227 | if (c == null) { |
||
228 | throw new ClassNotFoundException(name); |
||
229 | } |
||
230 | |||
231 | return c;
|
||
232 | } catch (IOException e) { |
||
233 | throw new ClassNotFoundException(Messages.getString( |
||
234 | "PluginClassLoader.Error_reading_file") + name);
|
||
235 | } |
||
236 | } |
||
237 | |||
238 | /**
|
||
239 | * Carga la clase
|
||
240 | *
|
||
241 | * @param name Nombre de la clase
|
||
242 | * @param resolve Si se ha de resolver la clase o no
|
||
243 | *
|
||
244 | * @return Clase cargada
|
||
245 | *
|
||
246 | * @throws ClassNotFoundException Si no se pudo encontrar la clase
|
||
247 | */
|
||
248 | protected Class loadClass(String name, boolean resolve) |
||
249 | throws ClassNotFoundException { |
||
250 | 40566 | jjdelcerro | Class<?> c = null; |
251 | 40435 | jjdelcerro | |
252 | // Intentamos cargar con el system classloader
|
||
253 | try {
|
||
254 | if (!isOtherLoader)
|
||
255 | c = super.loadClass(name, resolve);
|
||
256 | 40566 | jjdelcerro | logger.debug("Found class {} in system-classloader", name);
|
257 | 40435 | jjdelcerro | } catch (ClassNotFoundException e1) { |
258 | try {
|
||
259 | c = singleLoadClass(name); |
||
260 | } catch (ClassNotFoundException e2) { |
||
261 | try {
|
||
262 | isOtherLoader=true;
|
||
263 | c = loadOtherClass(name); |
||
264 | }catch (ClassNotFoundException e3) { |
||
265 | // throw new ClassNotFoundException(Messages.getString(
|
||
266 | // "PluginClassLoader.Error_reading_file")
|
||
267 | // + name, e3);
|
||
268 | throw new ClassNotFoundException("Class " + name |
||
269 | + " not found through the plugin " + baseDir, e3);
|
||
270 | }finally {
|
||
271 | isOtherLoader=false;
|
||
272 | } |
||
273 | } |
||
274 | } |
||
275 | if (c==null) |
||
276 | throw new ClassNotFoundException(Messages.getString( |
||
277 | "PluginClassLoader.Error_reading_file") + name);
|
||
278 | if (resolve) {
|
||
279 | resolveClass(c); |
||
280 | } |
||
281 | return c;
|
||
282 | } |
||
283 | 40566 | jjdelcerro | private Class<?> loadOtherClass(String name) |
284 | 40435 | jjdelcerro | throws ClassNotFoundException |
285 | { |
||
286 | ClassLoader[] ocl=(ClassLoader[])otherLoaders.toArray(new ClassLoader[0]); |
||
287 | 40566 | jjdelcerro | Class<?> c=null; |
288 | 40435 | jjdelcerro | for (int i=0;i<ocl.length;i++) { |
289 | c=ocl[i].loadClass(name); |
||
290 | if (c != null) { |
||
291 | 40566 | jjdelcerro | logger.debug("Found class {} by the alternative classloaders of plugin {}",
|
292 | 40435 | jjdelcerro | name, baseDir); |
293 | |||
294 | return c;
|
||
295 | } |
||
296 | } |
||
297 | throw new ClassNotFoundException(name); |
||
298 | } |
||
299 | |||
300 | /**
|
||
301 | * obtiene el array de bytes de la clase
|
||
302 | *
|
||
303 | * @param classFile Entrada dentro del jar contiene los bytecodes de la
|
||
304 | * clase (el .class)
|
||
305 | * @param is InputStream para leer la entrada del jar
|
||
306 | *
|
||
307 | * @return Bytes de la clase
|
||
308 | *
|
||
309 | * @throws IOException Si no se puede obtener el .class del jar
|
||
310 | */
|
||
311 | private byte[] loadClassData(ZipEntry classFile, InputStream is) |
||
312 | throws IOException { |
||
313 | // Get size of class file
|
||
314 | int size = (int) classFile.getSize(); |
||
315 | |||
316 | // Reserve space to read
|
||
317 | byte[] buff = new byte[size]; |
||
318 | |||
319 | // Get stream to read from
|
||
320 | DataInputStream dis = new DataInputStream(is); |
||
321 | |||
322 | // Read in data
|
||
323 | dis.readFully(buff); |
||
324 | |||
325 | // close stream
|
||
326 | dis.close(); |
||
327 | |||
328 | // return data
|
||
329 | return buff;
|
||
330 | } |
||
331 | |||
332 | /**
|
||
333 | * Gets the bytes of a File
|
||
334 | *
|
||
335 | * @param file File
|
||
336 | *
|
||
337 | * @return bytes of file
|
||
338 | *
|
||
339 | * @throws IOException If the operation fails
|
||
340 | */
|
||
341 | private byte[] loadClassData(File file) throws IOException { |
||
342 | InputStream is = new FileInputStream(file); |
||
343 | |||
344 | // Get the size of the file
|
||
345 | long length = file.length();
|
||
346 | |||
347 | // You cannot create an array using a long type.
|
||
348 | // It needs to be an int type.
|
||
349 | // Before converting to an int type, check
|
||
350 | // to ensure that file is not larger than Integer.MAX_VALUE.
|
||
351 | if (length > Integer.MAX_VALUE) { |
||
352 | // File is too large
|
||
353 | } |
||
354 | |||
355 | // Create the byte array to hold the data
|
||
356 | byte[] bytes = new byte[(int) length]; |
||
357 | |||
358 | // Read in the bytes
|
||
359 | int offset = 0; |
||
360 | int numRead = 0; |
||
361 | |||
362 | while ((offset < bytes.length) &&
|
||
363 | ((numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)) {
|
||
364 | offset += numRead; |
||
365 | } |
||
366 | |||
367 | 40566 | jjdelcerro | // Close the input stream and return bytes
|
368 | is.close(); |
||
369 | |||
370 | 40435 | jjdelcerro | // Ensure all the bytes have been read in
|
371 | if (offset < bytes.length) {
|
||
372 | throw new IOException("Could not completely read file " + |
||
373 | file.getName()); |
||
374 | } |
||
375 | |||
376 | return bytes;
|
||
377 | } |
||
378 | |||
379 | /**
|
||
380 | * Gets the requested resource. If the path is relative, its base directory
|
||
381 | * will be the one provided in the PluginClassLoader's constructor.
|
||
382 | * If the resource is not found, the parent classloader will be invoked
|
||
383 | * to try to get it. If it is not found, it will return null.
|
||
384 | *
|
||
385 | * @param res An absolute or relative path to the requested resource.
|
||
386 | *
|
||
387 | * @return Resource's URL if it was found, nul otherwise.
|
||
388 | */
|
||
389 | public URL getResource(String res) { |
||
390 | 40566 | jjdelcerro | URL ret = null; |
391 | 40435 | jjdelcerro | try {
|
392 | 40566 | jjdelcerro | logger.debug("Search resource {} in {}", res, this.baseDir.toString()); |
393 | List<String> resource = new ArrayList<String>(); |
||
394 | 40435 | jjdelcerro | StringTokenizer st = new StringTokenizer(res, "\\/"); |
395 | |||
396 | while (st.hasMoreTokens()) {
|
||
397 | String token = st.nextToken();
|
||
398 | resource.add(token); |
||
399 | } |
||
400 | |||
401 | 40566 | jjdelcerro | ret = getResource(baseDir, resource); |
402 | 40435 | jjdelcerro | |
403 | if (ret != null) { |
||
404 | return ret;
|
||
405 | } |
||
406 | } catch (Exception e) { |
||
407 | 40566 | jjdelcerro | logger.info("Error getting resource {} in {}'", new Object[] {res,this.baseDir.toString()}, e); |
408 | 40435 | jjdelcerro | } |
409 | 40566 | jjdelcerro | |
410 | try {
|
||
411 | ret = super.getResource(res);
|
||
412 | } catch (Exception e) { |
||
413 | logger.info("Error getting resource {} in {}'", new Object[] {res,this.baseDir.toString()}, e); |
||
414 | } |
||
415 | if( ret == null ) { |
||
416 | logger.info("Error getting resource {} in {}'", new Object[] {res,this.baseDir.toString()}); |
||
417 | } |
||
418 | return ret;
|
||
419 | 40435 | jjdelcerro | } |
420 | |||
421 | /**
|
||
422 | * Gets the requested resource. If the path is relative, its base directory
|
||
423 | * will be the one provided in the PluginClassLoader's constructor.
|
||
424 | * If the resource is not found, the parent classloader will be invoked
|
||
425 | * to try to get it. If it is not found, it will return null.
|
||
426 | *
|
||
427 | * @param res An absolute or relative path to the requested resource.
|
||
428 | *
|
||
429 | * @return Resource's URL if it was found, nul otherwise.
|
||
430 | */
|
||
431 | 40566 | jjdelcerro | private URL getResource(File base, List<String> res) { |
432 | 40435 | jjdelcerro | File[] files = base.listFiles(); |
433 | |||
434 | 40566 | jjdelcerro | String parte = res.get(0); |
435 | 40435 | jjdelcerro | |
436 | for (int i = 0; i < files.length; i++) { |
||
437 | if (files[i].getName().compareTo(parte) == 0) { |
||
438 | if (res.size() == 1) { |
||
439 | try {
|
||
440 | return new URL("file:" + files[i].toString()); |
||
441 | } catch (MalformedURLException e) { |
||
442 | return null; |
||
443 | } |
||
444 | } else {
|
||
445 | return getResource(files[i], res.subList(1, res.size())); |
||
446 | } |
||
447 | } |
||
448 | } |
||
449 | |||
450 | return null; |
||
451 | } |
||
452 | |||
453 | /**
|
||
454 | * Returns the name of the plugin (the name of the directory containing
|
||
455 | * the plugin).
|
||
456 | *
|
||
457 | * @return An String containing the plugin's name.
|
||
458 | */
|
||
459 | public String getPluginName() { |
||
460 | String ret = baseDir.getAbsolutePath().substring(baseDir.getAbsolutePath()
|
||
461 | .lastIndexOf(File.separatorChar) +
|
||
462 | 1);
|
||
463 | |||
464 | return ret;
|
||
465 | } |
||
466 | |||
467 | /*
|
||
468 | * @see java.security.SecureClassLoader#getPermissions(java.security.CodeSource)
|
||
469 | */
|
||
470 | protected PermissionCollection getPermissions(CodeSource codesource) { |
||
471 | PermissionCollection perms = super.getPermissions(codesource); |
||
472 | perms.add(new AllPermission()); |
||
473 | |||
474 | return perms;
|
||
475 | } |
||
476 | |||
477 | /**
|
||
478 | 40566 | jjdelcerro | * Gets the plugin's base Iterator<Map.Entry<Integer, Integer>>dir, the directory which will be used to
|
479 | 40435 | jjdelcerro | * search resources.
|
480 | *
|
||
481 | * @return Returns the baseDir.
|
||
482 | */
|
||
483 | public String getBaseDir() { |
||
484 | return baseDir.getAbsolutePath();
|
||
485 | } |
||
486 | |||
487 | /**
|
||
488 | * Adds other classloader to use when all the normal methods fail.
|
||
489 | *
|
||
490 | * @param classLoaders An ArrayList of ClassLoaders which will
|
||
491 | * be used to load classes when all the normal methods fail.
|
||
492 | */
|
||
493 | public static void addLoaders(ArrayList classLoaders) { |
||
494 | otherLoaders.addAll(classLoaders); |
||
495 | } |
||
496 | |||
497 | @Override
|
||
498 | public String toString() { |
||
499 | return super.toString() + " (" + getPluginName() + ")"; |
||
500 | } |
||
501 | } |