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

History | View | Annotate | Download (22.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 modify it under
7
 * the terms of the GNU General Public License as published by the Free Software
8
 * Foundation; either version 3 of the License, or (at your option) any later
9
 * version.
10
 *
11
 * This program is distributed in the hope that it will be useful, but WITHOUT
12
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14
 * details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with
17
 * this program; if not, write to the Free Software Foundation, Inc., 51
18
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
 *
20
 * For any additional information, do not hesitate to contact us at info AT
21
 * gvsig.com, or visit our website www.gvsig.com.
22
 */
23
package org.gvsig.andami.plugins;
24

    
25
import java.io.DataInputStream;
26
import java.io.File;
27
import java.io.FileInputStream;
28
import java.io.IOException;
29
import java.io.InputStream;
30
import java.net.MalformedURLException;
31
import java.net.URL;
32
import java.net.URLClassLoader;
33
import java.security.AllPermission;
34
import java.security.CodeSource;
35
import java.security.PermissionCollection;
36
import java.util.ArrayList;
37
import java.util.Arrays;
38
import java.util.Collections;
39
import java.util.Enumeration;
40
import java.util.HashMap;
41
import java.util.HashSet;
42
import java.util.Hashtable;
43
import java.util.Iterator;
44
import java.util.List;
45
import java.util.Map;
46
import java.util.Map.Entry;
47
import java.util.Set;
48
import java.util.StringTokenizer;
49
import java.util.zip.ZipEntry;
50
import java.util.zip.ZipException;
51
import java.util.zip.ZipFile;
52

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

    
57
/**
58
 * <p>
59
 * Class loader which loads the classes requested by the plugins. It first tries
60
 * to search in the classpath, then it requests the class to the parent
61
 * classloader, then it searches in the owns plugins' library dir, and if all
62
 * these methods fail, it tries to load the class from any of the depended
63
 * plugins. Finally, if this also fails, the other classloaders provided in the
64
 * <code>addLoaders</code> method are requested to load the class.</p>
65
 *
66
 * <p>
67
 * The class loader can also be used to load resources from the plugin's
68
 * 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

    
74
    /**
75
     * DOCUMENT ME!
76
     */
77
    private static Logger logger = LoggerFactory.getLogger(PluginClassLoader.class.getName());
78

    
79

    
80
    private Map<String, ZipFile> clasesJar = new HashMap<>();
81

    
82

    
83
    private File baseDir;
84
    private List<PluginClassLoader> pluginLoaders;
85
    private static List<ClassLoader> otherLoaders = new ArrayList<>();
86
    private boolean isOtherLoader = false;
87

    
88
    /**
89
     * Creates a new PluginClassLoader object.
90
     *
91
     * @param jars Array with the search paths where classes will be searched
92
     * @param baseDir Base directory for this plugin. This is the directory
93
     * which will be used as basedir in the <code>getResources</code> method.
94
     * @param cl The parent classloader of this classloader. It will be used to
95
     * search classes before trying to search in the plugin's directory
96
     * @param pluginLoaders The classloaders of the depended plugins.
97
     *
98
     * @throws IOException
99
     */
100
    public PluginClassLoader(URL[] jars, String baseDir, ClassLoader cl,
101
            PluginClassLoader[] pluginLoaders) throws IOException {
102
        this(jars, baseDir, cl, Arrays.asList(pluginLoaders));
103
    }
104

    
105
    public PluginClassLoader(URL[] jars, String baseDir, ClassLoader cl,
106
            List<PluginClassLoader> pluginLoaders) throws IOException {
107
        super(jars, cl);
108
        this.baseDir = new File(new File(baseDir).getAbsolutePath());
109
        try {
110
            logger.debug("Creating PluginClassLoader for {}.", this.getPluginName());
111
            this.pluginLoaders = new ArrayList();
112
            this.pluginLoaders.addAll(pluginLoaders);
113

    
114
            if (jars == null || jars.length < 1) {
115
                debugDump();
116
                return;
117
            }
118
            ZipFile[] jarFiles = new ZipFile[jars.length];
119

    
120
            for (int i = 0; i < jars.length; i++) {
121
                try {
122
                    logger.debug("Classloader {}, loading jar {}", this.getPluginName(), jars[i].getPath());
123
                    jarFiles[i] = new ZipFile(jars[i].getPath());
124

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

    
127
                    while (entradas.hasMoreElements()) {
128
                        ZipEntry file = (ZipEntry) entradas.nextElement();
129
                        String fileName = file.getName();
130

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

    
133
                            continue;
134
                        }
135

    
136
                        fileName = fileName.substring(0, fileName.length() - 6)
137
                                .replace('/', '.');
138

    
139
                        if (clasesJar.get(fileName) != null) {
140
                            logger.warn("Classloader {}, duplicated class {} in {} and {}",
141
                                    new Object[]{
142
                                        this.getPluginName(),
143
                                        fileName,
144
                                        jarFiles[i].getName(),
145
                                        clasesJar.get(fileName).getName()
146
                                    }
147
                            );
148
                        } else {
149
                            clasesJar.put(fileName, jarFiles[i]);
150
                        }
151
                    }
152
                } catch (ZipException e) {
153
                    throw new IOException(e.getMessage() + " Jar: "
154
                            + jars[i].getPath() + ": " + jarFiles[i]);
155
                } catch (IOException e) {
156
                    throw e;
157
                }
158
            }
159
        } finally {
160
            debugDump();
161
        }
162
    }
163

    
164
    private void debugDump() {
165
        if (!logger.isDebugEnabled()) {
166
            return;
167
        }
168
        logger.debug(this.toString());
169
        logger.debug("  baseDir: " + this.baseDir);
170
        logger.debug("  parent: " + this.getParent());
171
        logger.debug("  depends:");
172
        for (PluginClassLoader pluginLoader : this.pluginLoaders) {
173
            logger.debug("    {}", pluginLoader.toString());
174
        }
175
        logger.debug("  urls:");
176
        URL[] urls = this.getURLs();
177
        for (URL url : urls) {
178
            logger.debug("    " + url.toString());
179
        }
180
//        logger.debug("  classes:");
181
//        Iterator<Map.Entry<String, ZipFile>> it = this.clasesJar.entrySet().iterator();
182
//        while (it.hasNext()) {
183
//            Entry<String, ZipFile> entry = it.next();
184
//            logger.debug("    " + entry.getKey() + "(" + entry.getValue().getName() + ")");
185
//        }
186
    }
187

    
188
    protected Class singleLoadClass(String name) throws ClassNotFoundException {
189
        Class<?> c;
190
        Set<String> pluginsVisiteds = new HashSet<>();
191
        c = this.singleLoadClass(pluginsVisiteds, name);
192
        return c;
193
    }
194

    
195
    /**
196
     * Carga la clase
197
     *
198
     * @param name Nombre de la clase
199
     * @param resolve Si se ha de resolver la clase o no
200
     *
201
     * @return Clase cargada
202
     *
203
     * @throws ClassNotFoundException Si no se pudo encontrar la clase
204
     */
205
    @Override
206
    protected Class loadClass(String name, boolean resolve)
207
            throws ClassNotFoundException {
208
        Class<?> c = null;
209

    
210
        // Intentamos cargar con el URLClassLoader
211
        try {
212
            if (!isOtherLoader) {
213
                c = super.loadClass(name, resolve);
214
                logger.debug("Classloader {}, found class {}.", this.getPluginName(), name);
215
            }
216
        } catch (ClassNotFoundException e1) {
217
            try {
218
                c = singleLoadClass(name);
219
            } catch (ClassNotFoundException e2) {
220
                try {
221
                    isOtherLoader = true;
222
                    c = loadOtherClass(name);
223
                } catch (ClassNotFoundException e3) {
224
                    throw new ClassNotFoundException("Class " + name
225
                            + " not found through the plugin " + baseDir, e3);
226
                } finally {
227
                    isOtherLoader = false;
228
                }
229
            }
230
        }
231
        if (c == null) {
232
            throw new ClassNotFoundException(Messages.getString(
233
                    "PluginClassLoader.Error_reading_file") + name);
234
        }
235
        if (resolve) {
236
            resolveClass(c);
237
        }
238
        return c;
239
    }
240

    
241
    private Class<?> loadOtherClass(String name)
242
            throws ClassNotFoundException {
243
        ClassLoader[] ocls = (ClassLoader[]) otherLoaders.toArray(new ClassLoader[0]);
244
        Class<?> c;
245
        for (ClassLoader ocl : ocls) {
246
            c = ocl.loadClass(name);
247
            if (c != null) {
248
                logger.debug("Classloader {}, found class {} in classloader {}", 
249
                        new Object[]{this, getPluginName(), name, ocl.toString()}
250
                );
251
                return c;
252
            }
253
        }
254
        throw new ClassNotFoundException(name);
255
    }
256

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

    
273
        // Reserve space to read
274
        byte[] buff = new byte[size];
275

    
276
        // Get stream to read from
277
        DataInputStream dis = new DataInputStream(is);
278

    
279
        // Read in data
280
        dis.readFully(buff);
281

    
282
        // close stream
283
        dis.close();
284

    
285
        // return data
286
        return buff;
287
    }
288

    
289
    /**
290
     * Gets the bytes of a File
291
     *
292
     * @param file File
293
     *
294
     * @return bytes of file
295
     *
296
     * @throws IOException If the operation fails
297
     */
298
    private byte[] loadClassData(File file) throws IOException {
299
        InputStream is = new FileInputStream(file);
300

    
301
        // Get the size of the file
302
        long length = file.length();
303

    
304
        // You cannot create an array using a long type.
305
        // It needs to be an int type.
306
        // Before converting to an int type, check
307
        // to ensure that file is not larger than Integer.MAX_VALUE.
308
        if (length > Integer.MAX_VALUE) {
309
            // File is too large
310
        }
311

    
312
        // Create the byte array to hold the data
313
        byte[] bytes = new byte[(int) length];
314

    
315
        // Read in the bytes
316
        int offset = 0;
317
        int numRead = 0;
318

    
319
        while ((offset < bytes.length)
320
                && ((numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)) {
321
            offset += numRead;
322
        }
323

    
324
        // Close the input stream and return bytes
325
        is.close();
326

    
327
        // Ensure all the bytes have been read in
328
        if (offset < bytes.length) {
329
            throw new IOException("Could not completely read file "
330
                    + file.getName());
331
        }
332

    
333
        return bytes;
334
    }
335

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

    
351
        Set<String> pluginsVisiteds = new HashSet<>();
352
        ret = this.getResource(pluginsVisiteds, res);
353
        return ret;
354
    }
355

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

    
369
        String parte = res.get(0);
370

    
371
        for (File file : files) {
372
            if (file.getName().compareTo(parte) == 0) {
373
                if (res.size() == 1) {
374
                    try {
375
                        return new URL("file:" + file.toString());
376
                    }catch (MalformedURLException e) {
377
                        return null;
378
                    }
379
                } else {
380
                    return getResource(file, res.subList(1, res.size()));
381
                }
382
            }
383
        }
384

    
385
        return null;
386
    }
387
    
388
    @Override
389
    public Enumeration<URL> getResources(String name) throws IOException {
390
            HashSet visitedPlugins = new HashSet();
391
            return getResources(name, visitedPlugins);
392
    }
393
    
394
    protected Enumeration<URL> getResources(String name, HashSet<PluginClassLoader> visitedPlugins) throws IOException {        
395
            List<URL> resources = new ArrayList<>();
396
            Enumeration<URL> aux = super.getResources(name);
397
            while(aux.hasMoreElements()){
398
                    URL url = aux.nextElement();
399
                    resources.add(url);
400
            }
401
        visitedPlugins.add(this);
402
            for(PluginClassLoader loader: this.pluginLoaders){
403
                    if (!visitedPlugins.contains(loader)) {
404
                            aux = loader.getResources(name, visitedPlugins);
405
                        while(aux.hasMoreElements()){
406
                                URL url = aux.nextElement();
407
                                resources.add(url);
408
                        }
409
                    }
410
            }
411
            return Collections.enumeration(resources);
412
    }
413

    
414
    /**
415
     * Returns the name of the plugin (the name of the directory containing the
416
     * plugin).
417
     *
418
     * @return An String containing the plugin's name.
419
     */
420
    public String getPluginName() {
421
        return baseDir.getName();
422
    }
423

    
424
    /*
425
     * @see java.security.SecureClassLoader#getPermissions(java.security.CodeSource)
426
     */
427
    @Override
428
    protected PermissionCollection getPermissions(CodeSource codesource) {
429
        PermissionCollection perms = super.getPermissions(codesource);
430
        perms.add(new AllPermission());
431

    
432
        return perms;
433
    }
434

    
435
    /**
436
     * Gets the plugin's base Iterator<Map.Entry<Integer, Integer>>dir, the
437
     * directory which will be used to search resources.
438
     *
439
     * @return Returns the baseDir.
440
     */
441
    public String getBaseDir() {
442
        return baseDir.getAbsolutePath();
443
    }
444

    
445
    /**
446
     * Adds other classloader to use when all the normal methods fail.
447
     *
448
     * @param classLoaders An ArrayList of ClassLoaders which will be used to
449
     * load classes when all the normal methods fail.
450
     */
451
    public static void addLoaders(ArrayList classLoaders) {
452
        otherLoaders.addAll(classLoaders);
453
    }
454

    
455
    @Override
456
    public String toString() {
457
        return super.toString() + " (" + getPluginName() + ")";
458
    }
459

    
460
    public void addPluginClassLoader(PluginClassLoader pluginClassLoader) {
461
        if (!this.pluginLoaders.contains(pluginClassLoader)) {
462
            this.pluginLoaders.add(pluginClassLoader);
463
        }
464
    }
465

    
466
    private Class singleLoadClass(Set<String> pluginsVisiteds, String name) throws ClassNotFoundException {
467

    
468
        if (pluginsVisiteds.contains(this.getPluginName())) {
469
            return null;
470
        }
471
        pluginsVisiteds.add(this.getPluginName());
472

    
473
        // Buscamos en las clases de las librer�as del plugin
474
        Class<?> c = findLoadedClass(name);
475

    
476
        if (c != null) {
477
            return c;
478
        }
479

    
480
        logger.debug("Classloader {}, searching class '{}'",
481
                new Object[]{this.getPluginName(), name}
482
        );
483
        try {
484
            ZipFile jar = (ZipFile) clasesJar.get(name);
485

    
486
            //No esta en ningun jar
487
            if (jar == null) {
488
                //Buscamos en el directorio de clases
489
                String classFileName = baseDir + "/classes/"
490
                        + name.replace('.', '/') + ".class";
491
                File f = new File(classFileName);
492
                if (f.exists()) {
493
                    byte[] data = loadClassData(f);
494
                    c = defineClass(name, data, 0, data.length);
495
                    logger.debug("Classloader {}/classes-folder, found class {}",
496
                            new Object[]{this.getPluginName(), name}
497
                    );
498

    
499
                } else {
500
                    for (PluginClassLoader pluginLoader1 : pluginLoaders) {
501
                        c = null;
502
                        PluginClassLoader pluginLoader = pluginLoader1;
503
                        if (pluginLoader != null) {
504
                            try {
505
                                c = pluginLoader.singleLoadClass(pluginsVisiteds, name);
506
                            } catch (ClassNotFoundException e) {
507
                                // Si no la encontramos en el primer plugin, capturamos la exceptcion
508
                                // porque es probable que la encontremos en el resto de plugins.
509
                            }
510
                            if (c != null) {
511
                                logger.debug("Classloader {}, found class {} in plugin {}",
512
                                        new Object[]{
513
                                            this.getPluginName(),
514
                                            name,
515
                                            pluginLoader.getPluginName()
516
                                        }
517
                                );
518
                                return c;
519
                            }
520
                        }
521
                    }
522
                    
523
                    
524
                }
525
            } else {
526
                logger.debug("Classloader {}, found class {} in jar {}",
527
                        new Object[]{
528
                            this.getPluginName(),
529
                            name,
530
                            jar.getName()
531
                        }
532
                );
533
                String fileName = name.replace('.', '/') + ".class";
534
                ZipEntry classFile = jar.getEntry(fileName);
535
                byte[] data = loadClassData(classFile,
536
                        jar.getInputStream(classFile));
537

    
538
                c = defineClass(name, data, 0, data.length);
539
                if (c == null) {
540
                    logger.debug("Classloader {}, can't load class {} from jar {}",
541
                            new Object[]{
542
                                this.getPluginName(),
543
                                name,
544
                                jar.getName()
545
                            }
546
                    );
547
                }
548

    
549
            }
550

    
551
            if (c == null) {
552
                logger.debug("Classloader {}, class not found {}",
553
                        new Object[]{
554
                            this.getPluginName(),
555
                            name
556
                        }
557
                );
558
                debugDump();
559
                throw new ClassNotFoundException(name);
560
            }
561

    
562
            return c;
563
        } catch (IOException e) {
564
            logger.debug("Classloader {}, error loading class {}",
565
                    new Object[]{
566
                        this.getPluginName(),
567
                        name
568
                    },
569
                    e
570
            );
571
            throw new ClassNotFoundException(Messages.getString(
572
                    "PluginClassLoader.Error_reading_file") + name);
573
        }
574
    }
575

    
576
    /**
577
     * Este metodo busca en este class loader y en el de los plugins de los que
578
     * depende. Se cerciora de que no se queda bucleado cuando hay una relacion
579
     * recursiva entre los plugins.
580
     *
581
     * @param pluginsVisiteds, set con los plugins que va visitando para evitar
582
     * que se quede bucleado cuando hay referencia ciclicas entre plugins.
583
     * @param res
584
     *
585
     * @return
586
     */
587
    private URL getResource(Set<String> pluginsVisiteds, String res) {
588
        URL ret = null;
589

    
590
        if (pluginsVisiteds.contains(this.getPluginName())) {
591
            return null;
592
        }
593
        pluginsVisiteds.add(this.getPluginName());
594

    
595
        //
596
        // Primero buscamos en el directorio del plugin.
597
        try {
598
            logger.debug("Classloader {}, searching resource '{}'", this.getPluginName(), res);
599
            List<String> resource = new ArrayList<>();
600
            StringTokenizer st = new StringTokenizer(res, "\\/");
601
            while (st.hasMoreTokens()) {
602
                String token = st.nextToken();
603
                resource.add(token);
604
            }
605
            ret = getResource(baseDir, resource);
606
            if (ret != null) {
607
                return ret;
608
            }
609
        } catch (Exception e) {
610
            logger.warn("Classloader {}, Error getting resource '{}'.",
611
                    new Object[]{
612
                        this.getPluginName(), res
613
                    },
614
                    e
615
            );
616
        }
617

    
618
        logger.debug("Classloader {}, searching in depends pluginLoaders", this.getPluginName());
619
        for (PluginClassLoader pluginClassLoader : this.pluginLoaders) {
620
            if (pluginClassLoader != null) {
621
                try {
622
                    pluginsVisiteds.add(pluginClassLoader.getPluginName());
623
                    ret = pluginClassLoader.getResource(pluginsVisiteds, res);
624
                    if (ret != null) {
625
                        logger.debug("Classloader {}, Found resource '{}' in plugin '{}'.",
626
                                new Object[]{
627
                                    this.getPluginName(), res, pluginClassLoader.getPluginName(),});
628
                        return ret;
629
                    }
630
                } catch (Exception e) {
631
                    // Ignore, try in next classloader
632
                }
633
            }
634
        }
635

    
636
        if (ret == null) {
637
            //
638
            // Por ultimo en el class loader padre, se supone que es el del sistema.
639
            try {
640
                ret = super.getResource(res);
641
            } catch (Exception e) {
642
                logger.warn("Classloader {}, error getting resource '{}' in parent classloader'",
643
                        new Object[]{this.getPluginName(), res},
644
                        e
645
                );
646
            }
647
            if (ret == null) {
648
                logger.debug("Classloader {}, Resource '{}' not found.",
649
                        new Object[]{this.getPluginName(), res}
650
                );
651
            }
652
        }
653
        return ret;
654
    }
655

    
656
}