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