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 @ 41788

History | View | Annotate | Download (19.7 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.HashSet;
41
import java.util.Hashtable;
42
import java.util.Iterator;
43
import java.util.List;
44
import java.util.Map;
45
import java.util.Map.Entry;
46
import java.util.Set;
47
import java.util.StringTokenizer;
48
import java.util.zip.ZipEntry;
49
import java.util.zip.ZipException;
50
import java.util.zip.ZipFile;
51

    
52
import org.gvsig.andami.messages.Messages;
53
import org.slf4j.Logger;
54
import org.slf4j.LoggerFactory;
55

    
56

    
57

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

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

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

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

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

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

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

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

    
128
                        continue;
129
                    }
130

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

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

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

    
189
    /**
190
     * Carga la clase
191
     *
192
     * @param name Nombre de la clase
193
     * @param resolve Si se ha de resolver la clase o no
194
     *
195
     * @return Clase cargada
196
     *
197
     * @throws ClassNotFoundException Si no se pudo encontrar la clase
198
     */
199
    protected Class loadClass(String name, boolean resolve)
200
        throws ClassNotFoundException {
201
        Class<?> c = null;
202

    
203
        // Intentamos cargar con el system classloader
204
        try {
205
            if (!isOtherLoader)
206
                    c = super.loadClass(name, resolve);
207
            logger.debug("Found class {} in system-classloader", name);
208
        } catch (ClassNotFoundException e1) {
209
                try {
210
                        c = singleLoadClass(name);
211
                } catch (ClassNotFoundException e2) {
212
                        try {
213
                                isOtherLoader=true;
214
                                c = loadOtherClass(name);
215
                        }catch (ClassNotFoundException e3) {
216
                    // throw new ClassNotFoundException(Messages.getString(
217
                    // "PluginClassLoader.Error_reading_file")
218
                    // + name, e3);
219
                    throw new ClassNotFoundException("Class " + name
220
                            + " not found through the plugin " + baseDir, e3);                                 
221
                        }finally {
222
                                isOtherLoader=false;
223
                        }
224
                }
225
        }
226
        if (c==null)
227
                 throw new ClassNotFoundException(Messages.getString(
228
             "PluginClassLoader.Error_reading_file") + name);
229
        if (resolve) {
230
            resolveClass(c);
231
        }
232
        return c;
233
    }
234
    
235
    private Class<?> loadOtherClass(String name)
236
            throws ClassNotFoundException {
237
        ClassLoader[] ocl = (ClassLoader[]) otherLoaders.toArray(new ClassLoader[0]);
238
        Class<?> c = null;
239
        for (int i = 0; i < ocl.length; i++) {
240
            c = ocl[i].loadClass(name);
241
            if (c != null) {
242
                logger.debug("Found class {} by the alternative classloaders of plugin {}",
243
                        name, baseDir);
244

    
245
                return c;
246
            }
247
        }
248
        throw new ClassNotFoundException(name);
249
    }
250

    
251
    /**
252
     * obtiene el array de bytes de la clase
253
     *
254
     * @param classFile Entrada dentro del jar contiene los bytecodes de la
255
     *        clase (el .class)
256
     * @param is InputStream para leer la entrada del jar
257
     *
258
     * @return Bytes de la clase
259
     *
260
     * @throws IOException Si no se puede obtener el .class del jar
261
     */
262
    private byte[] loadClassData(ZipEntry classFile, InputStream is)
263
        throws IOException {
264
        // Get size of class file
265
        int size = (int) classFile.getSize();
266

    
267
        // Reserve space to read
268
        byte[] buff = new byte[size];
269

    
270
        // Get stream to read from
271
        DataInputStream dis = new DataInputStream(is);
272

    
273
        // Read in data
274
        dis.readFully(buff);
275

    
276
        // close stream
277
        dis.close();
278

    
279
        // return data
280
        return buff;
281
    }
282

    
283
    /**
284
     * Gets the bytes of a File
285
     *
286
     * @param file File
287
     *
288
     * @return bytes of file
289
     *
290
     * @throws IOException If the operation fails
291
     */
292
    private byte[] loadClassData(File file) throws IOException {
293
        InputStream is = new FileInputStream(file);
294

    
295
        // Get the size of the file
296
        long length = file.length();
297

    
298
        // You cannot create an array using a long type.
299
        // It needs to be an int type.
300
        // Before converting to an int type, check
301
        // to ensure that file is not larger than Integer.MAX_VALUE.
302
        if (length > Integer.MAX_VALUE) {
303
            // File is too large
304
        }
305

    
306
        // Create the byte array to hold the data
307
        byte[] bytes = new byte[(int) length];
308

    
309
        // Read in the bytes
310
        int offset = 0;
311
        int numRead = 0;
312

    
313
        while ((offset < bytes.length) &&
314
                ((numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)) {
315
            offset += numRead;
316
        }
317

    
318
        // Close the input stream and return bytes
319
        is.close();
320

    
321
        // Ensure all the bytes have been read in
322
        if (offset < bytes.length) {
323
            throw new IOException("Could not completely read file " +
324
                file.getName());
325
        }
326

    
327
        return bytes;
328
    }
329

    
330
    /**
331
     * Gets the requested resource. If the path is relative, its base directory
332
     * will be the one provided in the PluginClassLoader's constructor.
333
     * If the resource is not found, search in dependents plugins, otherwise 
334
     * the parent classloader will be invoked to try to get it. 
335
     * If it is not found, it will return null.
336
     *
337
     * @param res An absolute or relative path to the requested resource.
338
     *
339
     * @return Resource's URL if it was found, nul otherwise.
340
     */
341
    public URL getResource(String res) {
342
        URL ret = null;
343

    
344
        Set<String>pluginsVisiteds = new HashSet<String>();
345
        ret = this.getResource(pluginsVisiteds, res);
346
        return ret;
347
    }
348

    
349
    /**
350
     * Gets the requested resource. If the path is relative, its base directory
351
     * will be the one provided in the PluginClassLoader's constructor.
352
     * If the resource is not found, the parent classloader will be invoked
353
     * to try to get it. If it is not found, it will return null.
354
     *
355
     * @param res An absolute or relative path to the requested resource.
356
     *
357
     * @return Resource's URL if it was found, nul otherwise.
358
     */
359
    private URL getResource(File base, List<String> res) {
360
        File[] files = base.listFiles();
361

    
362
        String parte = res.get(0);
363

    
364
        for (int i = 0; i < files.length; i++) {
365
            if (files[i].getName().compareTo(parte) == 0) {
366
                if (res.size() == 1) {
367
                    try {
368
                        return new URL("file:" + files[i].toString());
369
                    } catch (MalformedURLException e) {
370
                        return null;
371
                    }
372
                } else {
373
                    return getResource(files[i], res.subList(1, res.size()));
374
                }
375
            }
376
        }
377

    
378
        return null;
379
    }
380

    
381
    /**
382
     * Returns the name of the plugin (the name of the directory containing
383
     * the plugin).
384
     *
385
     * @return An String containing the plugin's name.
386
     */
387
    public String getPluginName() {
388
            return baseDir.getName();
389
    }
390

    
391
    /*
392
     * @see java.security.SecureClassLoader#getPermissions(java.security.CodeSource)
393
     */
394
    protected PermissionCollection getPermissions(CodeSource codesource) {
395
        PermissionCollection perms = super.getPermissions(codesource);
396
        perms.add(new AllPermission());
397

    
398
        return perms;
399
    }
400

    
401
    /**
402
     * Gets the plugin's base Iterator<Map.Entry<Integer, Integer>>dir, the directory which will be used to
403
     * search resources.
404
     *
405
     * @return Returns the baseDir.
406
     */
407
    public String getBaseDir() {
408
        return baseDir.getAbsolutePath();
409
    }
410

    
411
    /**
412
     * Adds other classloader to use when all the normal methods fail.
413
     * 
414
     * @param classLoaders An ArrayList of ClassLoaders which will
415
     * be used to load classes when all the normal methods fail.
416
     */
417
        public static void addLoaders(ArrayList classLoaders) {
418
                otherLoaders.addAll(classLoaders);
419
        }
420

    
421
    @Override
422
    public String toString() {
423
        return super.toString() + " (" + getPluginName() + ")";
424
    }
425
    
426
    public void addPluginClassLoader(PluginClassLoader pluginClassLoader) {
427
        if( !this.pluginLoaders.contains(pluginClassLoader) ) {
428
            this.pluginLoaders.add(pluginClassLoader);
429
        }
430
    }
431
    
432
    private Class singleLoadClass(Set<String> pluginsVisiteds, String name) throws ClassNotFoundException {
433
        
434
        if ( pluginsVisiteds.contains(this.getPluginName()) ) {
435
            return null;
436
        }
437
        pluginsVisiteds.add(this.getPluginName());
438

    
439
        // Buscamos en las clases de las librer�as del plugin
440
        Class<?> c = findLoadedClass(name);
441

    
442
        if (c != null) {
443
            return c;
444
        }
445

    
446
        logger.debug("Searching class '{}' in {}", new Object[] {name, this.toString()});
447
        try {
448
            ZipFile jar = (ZipFile) clasesJar.get(name);
449

    
450
            //No esta en ningun jar
451
            if (jar == null) {
452
                //Buscamos en el directorio de clases
453
                String classFileName = baseDir + "/classes/" +
454
                    name.replace('.', '/') + ".class";
455
                File f = new File(classFileName);
456
                if (f.exists()){
457
                    byte[] data = loadClassData(f);
458
                    c = defineClass(name, data, 0, data.length);
459
                    logger.debug("Found class {} in classes-folder of plugin {}",
460
                                    new Object[] {name, baseDir});
461

    
462
                } else {
463
                    //Buscamos en los otros plugins
464
                    for ( int i = 0; i < pluginLoaders.size(); i++ ) {
465
                        c = null;
466
                        if ( pluginLoaders.get(i) != null ) {
467
                            try {
468
                                c = pluginLoaders.get(i).singleLoadClass(pluginsVisiteds, name);
469
                            } catch (ClassNotFoundException e) {
470
                                // Si no la encontramos en el primer plugin, capturamos la exceptcion
471
                                // porque es probable que la encontremos en el resto de plugins.
472
                            }
473
                        }
474
                        if ( c != null ) {
475
                            return c;
476
                        }
477
                    }
478
                }
479
            } else {
480
                String fileName = name.replace('.', '/') + ".class";
481
                ZipEntry classFile = jar.getEntry(fileName);
482
                byte[] data = loadClassData(classFile,
483
                        jar.getInputStream(classFile));
484

    
485
                c = defineClass(name, data, 0, data.length);
486
                
487
                logger.debug("Found class {} in jar {} of plugin {}",
488
                            new Object[] { name, jar.getName(), baseDir} );
489
            }
490

    
491
            if (c == null) {
492
                throw new ClassNotFoundException(name);
493
            }
494

    
495
            return c;
496
        } catch (IOException e) {
497
            throw new ClassNotFoundException(Messages.getString(
498
                    "PluginClassLoader.Error_reading_file") + name);
499
        }        
500
    }
501

    
502
    /**
503
     * Este metodo busca en este class loader y en el de los plugins
504
     * de los que depende. Se cerciora de que no se queda
505
     * bucleado cuando hay una relacion recursiva entre los plugins.
506
     * 
507
     * @param pluginsVisiteds, set con los plugins que va visitando para evitar 
508
     *      que se quede bucleado cuando hay referencia ciclicas entre plugins.
509
     * @param res
510
     * 
511
     * @return 
512
     */
513
     private URL getResource(Set<String> pluginsVisiteds, String res) {
514
        URL ret = null;
515

    
516
        if ( pluginsVisiteds.contains(this.getPluginName()) ) {
517
            return null;
518
        }
519
        pluginsVisiteds.add(this.getPluginName());
520

    
521
        //
522
        // Primero buscamos en el directorio del plugin.
523
        try {
524
            logger.debug("Plugin {}. Searching resource '{}'", res, this.getPluginName());
525
            List<String> resource = new ArrayList<String>();
526
            StringTokenizer st = new StringTokenizer(res, "\\/");
527
            while ( st.hasMoreTokens() ) {
528
                String token = st.nextToken();
529
                resource.add(token);
530
            }
531
            ret = getResource(baseDir, resource);
532
            if ( ret != null ) {
533
                return ret;
534
            }
535
        } catch (Exception e) {
536
            logger.warn("Plugin {}. Error getting resource '{}'.", new Object[]{this.getPluginName(), res}, e);
537
        }
538

    
539
        logger.debug("Plugin {}. Searching in depends pluginLoaders", this.getPluginName());
540
        for ( int i = 0; i < this.pluginLoaders.size(); i++ ) {
541
            PluginClassLoader pluginClassLoader = pluginLoaders.get(i);
542
            if ( pluginClassLoader != null ) {
543
                try {
544
                    pluginsVisiteds.add(pluginClassLoader.getPluginName());
545
                    ret = pluginClassLoader.getResource(pluginsVisiteds, res);
546
                    if ( ret != null ) {
547
                        logger.trace("Plugin {}. Found resource '{}' in plugin '{}'.",
548
                                new Object[]{
549
                                    this.getPluginName(), pluginClassLoader.getPluginName(), res
550
                                });
551
                        return ret;
552
                    }
553
                } catch (Exception e) {
554
                    // Ignore, try in next classloader
555
                }
556
            }
557
        }
558

    
559
        if ( ret == null ) {
560
            //
561
            // Por ultimo en el class loader padre, se supone que es el del sistema.
562
            try {
563
                ret = super.getResource(res);
564
            } catch (Exception e) {
565
                logger.warn("Plugin {}. Error getting resource '{}' in parent classloader'", new Object[]{this.getPluginName(), res}, e);
566
            }
567
            if ( ret == null ) {
568
                logger.debug("Plugin {}. Resource '{}' not found.", new Object[]{this.getPluginName(), res});
569
            }
570
        }
571
        return ret;
572
    }
573

    
574
}