Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.framework / org.gvsig.andami / src / main / java / org / gvsig / andami / plugins / PluginClassLoader.java @ 41344

History | View | Annotate | Download (18.2 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
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
 * as published by the Free Software Foundation; either version 3
9
 * 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
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
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.Arrays;
39
import java.util.Enumeration;
40
import java.util.Hashtable;
41
import java.util.Iterator;
42
import java.util.List;
43
import java.util.Map;
44
import java.util.Map.Entry;
45
import java.util.StringTokenizer;
46
import java.util.zip.ZipEntry;
47
import java.util.zip.ZipException;
48
import java.util.zip.ZipFile;
49

    
50
import org.gvsig.andami.messages.Messages;
51
import org.slf4j.Logger;
52
import org.slf4j.LoggerFactory;
53

    
54

    
55

    
56
/**
57
 * <p>Class loader which loads the classes requested by the
58
 * plugins. It first tries to search in the classpath, then it requests
59
 * the class to the parent classloader, then it searches in the owns
60
 * plugins' library dir, and if all these methods fail, it tries to load
61
 * the class from any of the depended plugins. Finally, if this also
62
 * fails, the other classloaders provided in the <code>addLoaders</code> method
63
 * are requested to load the class.</p>
64
 * 
65
 * <p>The class loader can also be used to load resources from the
66
 * plugin's directory by using the <code>getResource()</code> method.</p>
67
 *
68
 * @author Fernando Gonz�lez Cort�s
69
 */
70
public class PluginClassLoader extends URLClassLoader {
71
    /** DOCUMENT ME! */
72
    private static Logger logger = LoggerFactory.getLogger(PluginClassLoader.class.getName());
73

    
74
    /** DOCUMENT ME! */
75
    private Hashtable<String, ZipFile> clasesJar = new Hashtable<String, ZipFile>();
76

    
77
    /** DOCUMENT ME! */
78
    private File baseDir;
79
    private List<PluginClassLoader> pluginLoaders;
80
    private static List<ClassLoader> otherLoaders=new ArrayList<ClassLoader>();
81
    private boolean isOtherLoader=false;
82
 
83
    /**
84
     * Creates a new PluginClassLoader object.
85
     *
86
     * @param jars Array with the search paths where classes will be searched
87
     * @param baseDir Base directory for this plugin. This is the directory
88
     * which will be used as basedir in the <code>getResources</code> method.
89
     * @param cl The parent classloader of this classloader. It will be used to
90
     * search classes before trying to search in the plugin's directory
91
     * @param pluginLoaders The classloaders of the depended plugins.
92
     *
93
     * @throws IOException
94
     */
95
    public PluginClassLoader(URL[] jars, String baseDir, ClassLoader cl,
96
        PluginClassLoader[] pluginLoaders) throws IOException {
97
        this(jars, baseDir, cl, Arrays.asList(pluginLoaders));
98
    }
99
    
100
    public PluginClassLoader(URL[] jars, String baseDir, ClassLoader cl,
101
            List<PluginClassLoader> pluginLoaders) throws IOException {
102
        super(jars, cl);
103
        logger.debug("Creating PluginClassLoader for {}.", baseDir);
104
        this.baseDir = new File(new File(baseDir).getAbsolutePath());
105
        this.pluginLoaders = new ArrayList();
106
        this.pluginLoaders.addAll(pluginLoaders);
107

    
108
        if( jars==null || jars.length<1 ) { 
109
            debugDump(); 
110
                return;
111
        }
112
        ZipFile[] jarFiles = new ZipFile[jars.length];
113

    
114
        for (int i = 0; i < jars.length; i++) {
115
            try {
116
                jarFiles[i] = new ZipFile(jars[i].getPath());
117

    
118
                Enumeration<? extends ZipEntry> entradas = jarFiles[i].entries();
119

    
120
                while (entradas.hasMoreElements()) {
121
                    ZipEntry file = (ZipEntry) entradas.nextElement();
122
                    String fileName = file.getName();
123

    
124
                    if (!fileName.toLowerCase().endsWith(".class")) { //$NON-NLS-1$
125

    
126
                        continue;
127
                    }
128

    
129
                    fileName = fileName.substring(0, fileName.length() - 6)
130
                                       .replace('/', '.');
131

    
132
                    if (clasesJar.get(fileName) != null) {
133
                                                logger.warn("Duplicated class {} in {} and {}",
134
                                                        new Object[] {
135
                                                                fileName, 
136
                                                                jarFiles[i].getName(),
137
                                                                clasesJar.get(fileName).getName()
138
                                                        }
139
                                                );
140
                    }
141
                    else {
142
                            clasesJar.put(fileName, jarFiles[i]);
143
                    }
144
                }
145
            } catch (ZipException e) {
146
                throw new IOException(e.getMessage() + " Jar: " +
147
                    jars[i].getPath() + ": " + jarFiles[i]);
148
            } catch (IOException e) {
149
                throw e;
150
            } finally {
151
                debugDump(); 
152
            }
153
        }
154
    }
155

    
156
    private void debugDump() {
157
            if( !logger.isDebugEnabled() ) {
158
                    return;
159
            }
160
            logger.debug(this.toString());
161
            logger.debug("  baseDir: " + this.baseDir);
162
            logger.debug("  parent: " + this.getParent());
163
            logger.debug("  depends:");
164
            for( int n=0; n<this.pluginLoaders.size(); n++ ) {
165
                    logger.debug("    {}", this.pluginLoaders.get(n).toString() );
166
            }
167
            logger.debug("  urls:");
168
            URL[] urls = this.getURLs();
169
            for( int n=0 ; n<urls.length; n++ ) {
170
                    logger.debug("    " + urls[n].toString());
171
            }
172
            logger.debug("  classes:");
173
            Iterator<Map.Entry<String, ZipFile>>it = this.clasesJar.entrySet().iterator();
174
            while( it.hasNext() ) {
175
                    Entry<String, ZipFile> entry = it.next();
176
                    logger.debug("    "+ entry.getKey() + "("+entry.getValue().getName()+")");
177
            }
178
    }
179
    
180
    protected Class singleLoadClass(String name) throws ClassNotFoundException {
181
        // Buscamos en las clases de las librer�as del plugin
182
        Class<?> c = findLoadedClass(name);
183

    
184
        if (c != null) {
185
            return c;
186
        }
187

    
188
        logger.debug("Searching class '{}' in {}", new Object[] {name, this.toString()});
189
        try {
190
            ZipFile jar = (ZipFile) clasesJar.get(name);
191

    
192
            //No esta en ningun jar
193
            if (jar == null) {
194
                //Buscamos en el directorio de clases
195
                String classFileName = baseDir + "/classes/" +
196
                    name.replace('.', '/') + ".class";
197
                File f = new File(classFileName);
198
                if (f.exists()){
199
                    byte[] data = loadClassData(f);
200
                    c = defineClass(name, data, 0, data.length);
201
                    logger.debug("Found class {} in classes-folder of plugin {}",
202
                                    new Object[] {name, baseDir});
203

    
204
                } else {
205
                    //Buscamos en los otros plugins
206
                    logger.debug("Searching in depends pluginLoaders");
207
                    for (int i = 0; i < pluginLoaders.size(); i++) {
208
                                    c = null;
209
                                if (pluginLoaders.get(i) != null) {
210
                                        try {
211
                                            c = pluginLoaders.get(i).singleLoadClass(name);
212
                                } catch (ClassNotFoundException e) {
213
                                        // Si no la encontramos en el primer plugin, capturamos la exceptcion
214
                                        // porque es probable que la encontremos en el resto de plugins.
215
                                }
216
                                }
217
                        if (c != null) {
218
                            break;
219
                        }
220
                    }
221
                }
222
            } else {
223
                String fileName = name.replace('.', '/') + ".class";
224
                ZipEntry classFile = jar.getEntry(fileName);
225
                byte[] data = loadClassData(classFile,
226
                        jar.getInputStream(classFile));
227

    
228
                c = defineClass(name, data, 0, data.length);
229
                
230
                logger.debug("Found class {} in jar {} of plugin {}",
231
                            new Object[] { name, jar.getName(), baseDir} );
232
            }
233

    
234
            if (c == null) {
235
                throw new ClassNotFoundException(name);
236
            }
237

    
238
            return c;
239
        } catch (IOException e) {
240
            throw new ClassNotFoundException(Messages.getString(
241
                    "PluginClassLoader.Error_reading_file") + name);
242
        }
243
    }
244

    
245
    /**
246
     * Carga la clase
247
     *
248
     * @param name Nombre de la clase
249
     * @param resolve Si se ha de resolver la clase o no
250
     *
251
     * @return Clase cargada
252
     *
253
     * @throws ClassNotFoundException Si no se pudo encontrar la clase
254
     */
255
    protected Class loadClass(String name, boolean resolve)
256
        throws ClassNotFoundException {
257
        Class<?> c = null;
258

    
259
        // Intentamos cargar con el system classloader
260
        try {
261
            if (!isOtherLoader)
262
                    c = super.loadClass(name, resolve);
263
            logger.debug("Found class {} in system-classloader", name);
264
        } catch (ClassNotFoundException e1) {
265
                try {
266
                        c = singleLoadClass(name);
267
                } catch (ClassNotFoundException e2) {
268
                        try {
269
                                isOtherLoader=true;
270
                                c = loadOtherClass(name);
271
                        }catch (ClassNotFoundException e3) {
272
                    // throw new ClassNotFoundException(Messages.getString(
273
                    // "PluginClassLoader.Error_reading_file")
274
                    // + name, e3);
275
                    throw new ClassNotFoundException("Class " + name
276
                            + " not found through the plugin " + baseDir, e3);                                 
277
                        }finally {
278
                                isOtherLoader=false;
279
                        }
280
                }
281
        }
282
        if (c==null)
283
                 throw new ClassNotFoundException(Messages.getString(
284
             "PluginClassLoader.Error_reading_file") + name);
285
        if (resolve) {
286
            resolveClass(c);
287
        }
288
        return c;
289
    }
290
    
291
    private Class<?> loadOtherClass(String name)
292
            throws ClassNotFoundException {
293
        ClassLoader[] ocl = (ClassLoader[]) otherLoaders.toArray(new ClassLoader[0]);
294
        Class<?> c = null;
295
        for (int i = 0; i < ocl.length; i++) {
296
            c = ocl[i].loadClass(name);
297
            if (c != null) {
298
                logger.debug("Found class {} by the alternative classloaders of plugin {}",
299
                        name, baseDir);
300

    
301
                return c;
302
            }
303
        }
304
        throw new ClassNotFoundException(name);
305
    }
306

    
307
    /**
308
     * obtiene el array de bytes de la clase
309
     *
310
     * @param classFile Entrada dentro del jar contiene los bytecodes de la
311
     *        clase (el .class)
312
     * @param is InputStream para leer la entrada del jar
313
     *
314
     * @return Bytes de la clase
315
     *
316
     * @throws IOException Si no se puede obtener el .class del jar
317
     */
318
    private byte[] loadClassData(ZipEntry classFile, InputStream is)
319
        throws IOException {
320
        // Get size of class file
321
        int size = (int) classFile.getSize();
322

    
323
        // Reserve space to read
324
        byte[] buff = new byte[size];
325

    
326
        // Get stream to read from
327
        DataInputStream dis = new DataInputStream(is);
328

    
329
        // Read in data
330
        dis.readFully(buff);
331

    
332
        // close stream
333
        dis.close();
334

    
335
        // return data
336
        return buff;
337
    }
338

    
339
    /**
340
     * Gets the bytes of a File
341
     *
342
     * @param file File
343
     *
344
     * @return bytes of file
345
     *
346
     * @throws IOException If the operation fails
347
     */
348
    private byte[] loadClassData(File file) throws IOException {
349
        InputStream is = new FileInputStream(file);
350

    
351
        // Get the size of the file
352
        long length = file.length();
353

    
354
        // You cannot create an array using a long type.
355
        // It needs to be an int type.
356
        // Before converting to an int type, check
357
        // to ensure that file is not larger than Integer.MAX_VALUE.
358
        if (length > Integer.MAX_VALUE) {
359
            // File is too large
360
        }
361

    
362
        // Create the byte array to hold the data
363
        byte[] bytes = new byte[(int) length];
364

    
365
        // Read in the bytes
366
        int offset = 0;
367
        int numRead = 0;
368

    
369
        while ((offset < bytes.length) &&
370
                ((numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)) {
371
            offset += numRead;
372
        }
373

    
374
        // Close the input stream and return bytes
375
        is.close();
376

    
377
        // Ensure all the bytes have been read in
378
        if (offset < bytes.length) {
379
            throw new IOException("Could not completely read file " +
380
                file.getName());
381
        }
382

    
383
        return bytes;
384
    }
385

    
386
    /**
387
     * Gets the requested resource. If the path is relative, its base directory
388
     * will be the one provided in the PluginClassLoader's constructor.
389
     * If the resource is not found, search in dependents plugins, otherwise 
390
     * the parent classloader will be invoked to try to get it. 
391
     * If it is not found, it will return null.
392
     *
393
     * @param res An absolute or relative path to the requested resource.
394
     *
395
     * @return Resource's URL if it was found, nul otherwise.
396
     */
397
    public URL getResource(String res) {
398
            URL ret = null;
399
            //
400
            // Primero buscamos en el directorio del plugin.
401
            try {
402
                logger.debug("Plugin {}. Searching resource '{}'", res, this.getPluginName());
403
            List<String> resource = new ArrayList<String>();
404
            StringTokenizer st = new StringTokenizer(res, "\\/");
405
            while (st.hasMoreTokens()) {
406
                String token = st.nextToken();
407
                resource.add(token);
408
            }
409
            ret = getResource(baseDir, resource);
410
            if (ret != null) {
411
                return ret;
412
            }
413
        } catch (Exception e) {
414
                logger.warn("Plugin {}. Error getting resource '{}'.", new Object[] {this.getPluginName(),res}, e);
415
        }
416
        
417
            // 
418
            // Luego en los plugins de los que depende 
419
        logger.debug("Plugin {}. Searching in depends pluginLoaders", this.getPluginName());
420
        for (int i = 0; i < this.pluginLoaders.size(); i++) {
421
                PluginClassLoader pluginClassLoader = pluginLoaders.get(i);
422
                    if (pluginClassLoader != null) {
423
                            try {
424
                                    ret = pluginClassLoader.getResource(res);
425
                        if (ret != null) {
426
                                logger.trace("Plugin {}. Found resource '{}' in plugin '{}'.",
427
                                        new Object[] {
428
                                                this.getPluginName(),pluginClassLoader.getPluginName(),res
429
                                });
430
                            return ret;
431
                        }
432
                    } catch (Exception e) {
433
                            // Ignore, try in next classloader
434
                    }
435
                    }
436
        }
437
        
438
        //
439
        // Por ultimo en el class loader padre, se supone que es el del sistema.
440
        try {
441
            ret = super.getResource(res);
442
        } catch (Exception e) {
443
                logger.warn("Plugin {}. Error getting resource '{}' in parent classloader'", new Object[] {this.getPluginName(),res}, e);
444
        }
445
        
446
        
447
        if( ret == null ) {
448
                logger.debug("Plugin {}. Resource '{}' not found.", new Object[] {this.getPluginName(),res});
449
        }
450
        return ret;
451
    }
452

    
453
    /**
454
     * Gets the requested resource. If the path is relative, its base directory
455
     * will be the one provided in the PluginClassLoader's constructor.
456
     * If the resource is not found, the parent classloader will be invoked
457
     * to try to get it. If it is not found, it will return null.
458
     *
459
     * @param res An absolute or relative path to the requested resource.
460
     *
461
     * @return Resource's URL if it was found, nul otherwise.
462
     */
463
    private URL getResource(File base, List<String> res) {
464
        File[] files = base.listFiles();
465

    
466
        String parte = res.get(0);
467

    
468
        for (int i = 0; i < files.length; i++) {
469
            if (files[i].getName().compareTo(parte) == 0) {
470
                if (res.size() == 1) {
471
                    try {
472
                        return new URL("file:" + files[i].toString());
473
                    } catch (MalformedURLException e) {
474
                        return null;
475
                    }
476
                } else {
477
                    return getResource(files[i], res.subList(1, res.size()));
478
                }
479
            }
480
        }
481

    
482
        return null;
483
    }
484

    
485
    /**
486
     * Returns the name of the plugin (the name of the directory containing
487
     * the plugin).
488
     *
489
     * @return An String containing the plugin's name.
490
     */
491
    public String getPluginName() {
492
            return baseDir.getName();
493
    }
494

    
495
    /*
496
     * @see java.security.SecureClassLoader#getPermissions(java.security.CodeSource)
497
     */
498
    protected PermissionCollection getPermissions(CodeSource codesource) {
499
        PermissionCollection perms = super.getPermissions(codesource);
500
        perms.add(new AllPermission());
501

    
502
        return perms;
503
    }
504

    
505
    /**
506
     * Gets the plugin's base Iterator<Map.Entry<Integer, Integer>>dir, the directory which will be used to
507
     * search resources.
508
     *
509
     * @return Returns the baseDir.
510
     */
511
    public String getBaseDir() {
512
        return baseDir.getAbsolutePath();
513
    }
514

    
515
    /**
516
     * Adds other classloader to use when all the normal methods fail.
517
     * 
518
     * @param classLoaders An ArrayList of ClassLoaders which will
519
     * be used to load classes when all the normal methods fail.
520
     */
521
        public static void addLoaders(ArrayList classLoaders) {
522
                otherLoaders.addAll(classLoaders);
523
        }
524

    
525
    @Override
526
    public String toString() {
527
        return super.toString() + " (" + getPluginName() + ")";
528
    }
529
    
530
    public void addPluginClassLoader(PluginClassLoader pluginClassLoader) {
531
        if( !this.pluginLoaders.contains(pluginClassLoader) ) {
532
            this.pluginLoaders.add(pluginClassLoader);
533
        }
534
    }
535
}