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

History | View | Annotate | Download (21.8 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.Enumeration;
39
import java.util.HashMap;
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
 * <p>
58
 * Class loader which loads the classes requested by the plugins. It first tries
59
 * to search in the classpath, then it requests the class to the parent
60
 * classloader, then it searches in the owns plugins' library dir, and if all
61
 * these methods fail, it tries to load the class from any of the depended
62
 * plugins. Finally, if this also fails, the other classloaders provided in the
63
 * <code>addLoaders</code> method are requested to load the class.</p>
64
 *
65
 * <p>
66
 * The class loader can also be used to load resources from the plugin's
67
 * directory by using the <code>getResource()</code> method.</p>
68
 *
69
 * @author Fernando Gonz�lez Cort�s
70
 */
71
public class PluginClassLoader extends URLClassLoader {
72

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

    
78

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

    
81

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

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

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

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

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

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

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

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

    
132
                            continue;
133
                        }
134

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
332
        return bytes;
333
    }
334

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

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

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

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

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

    
384
        return null;
385
    }
386

    
387
    /**
388
     * Returns the name of the plugin (the name of the directory containing the
389
     * plugin).
390
     *
391
     * @return An String containing the plugin's name.
392
     */
393
    public String getPluginName() {
394
        return baseDir.getName();
395
    }
396

    
397
    /*
398
     * @see java.security.SecureClassLoader#getPermissions(java.security.CodeSource)
399
     */
400
    @Override
401
    protected PermissionCollection getPermissions(CodeSource codesource) {
402
        PermissionCollection perms = super.getPermissions(codesource);
403
        perms.add(new AllPermission());
404

    
405
        return perms;
406
    }
407

    
408
    /**
409
     * Gets the plugin's base Iterator<Map.Entry<Integer, Integer>>dir, the
410
     * directory which will be used to search resources.
411
     *
412
     * @return Returns the baseDir.
413
     */
414
    public String getBaseDir() {
415
        return baseDir.getAbsolutePath();
416
    }
417

    
418
    /**
419
     * Adds other classloader to use when all the normal methods fail.
420
     *
421
     * @param classLoaders An ArrayList of ClassLoaders which will be used to
422
     * load classes when all the normal methods fail.
423
     */
424
    public static void addLoaders(ArrayList classLoaders) {
425
        otherLoaders.addAll(classLoaders);
426
    }
427

    
428
    @Override
429
    public String toString() {
430
        return super.toString() + " (" + getPluginName() + ")";
431
    }
432

    
433
    public void addPluginClassLoader(PluginClassLoader pluginClassLoader) {
434
        if (!this.pluginLoaders.contains(pluginClassLoader)) {
435
            this.pluginLoaders.add(pluginClassLoader);
436
        }
437
    }
438

    
439
    private Class singleLoadClass(Set<String> pluginsVisiteds, String name) throws ClassNotFoundException {
440

    
441
        if (pluginsVisiteds.contains(this.getPluginName())) {
442
            return null;
443
        }
444
        pluginsVisiteds.add(this.getPluginName());
445

    
446
        // Buscamos en las clases de las librer�as del plugin
447
        Class<?> c = findLoadedClass(name);
448

    
449
        if (c != null) {
450
            return c;
451
        }
452

    
453
        logger.debug("Classloader {}, searching class '{}'",
454
                new Object[]{this.getPluginName(), name}
455
        );
456
        try {
457
            ZipFile jar = (ZipFile) clasesJar.get(name);
458

    
459
            //No esta en ningun jar
460
            if (jar == null) {
461
                //Buscamos en el directorio de clases
462
                String classFileName = baseDir + "/classes/"
463
                        + name.replace('.', '/') + ".class";
464
                File f = new File(classFileName);
465
                if (f.exists()) {
466
                    byte[] data = loadClassData(f);
467
                    c = defineClass(name, data, 0, data.length);
468
                    logger.debug("Classloader {}/classes-folder, found class {}",
469
                            new Object[]{this.getPluginName(), name}
470
                    );
471

    
472
                } else {
473
                    for (PluginClassLoader pluginLoader1 : pluginLoaders) {
474
                        c = null;
475
                        PluginClassLoader pluginLoader = pluginLoader1;
476
                        if (pluginLoader != null) {
477
                            try {
478
                                c = pluginLoader.singleLoadClass(pluginsVisiteds, name);
479
                            } catch (ClassNotFoundException e) {
480
                                // Si no la encontramos en el primer plugin, capturamos la exceptcion
481
                                // porque es probable que la encontremos en el resto de plugins.
482
                            }
483
                            if (c != null) {
484
                                logger.debug("Classloader {}, found class {} in plugin {}",
485
                                        new Object[]{
486
                                            this.getPluginName(),
487
                                            name,
488
                                            pluginLoader.getPluginName()
489
                                        }
490
                                );
491
                                return c;
492
                            }
493
                        }
494
                    }
495
                    
496
                    
497
                }
498
            } else {
499
                logger.debug("Classloader {}, found class {} in jar {}",
500
                        new Object[]{
501
                            this.getPluginName(),
502
                            name,
503
                            jar.getName()
504
                        }
505
                );
506
                String fileName = name.replace('.', '/') + ".class";
507
                ZipEntry classFile = jar.getEntry(fileName);
508
                byte[] data = loadClassData(classFile,
509
                        jar.getInputStream(classFile));
510

    
511
                c = defineClass(name, data, 0, data.length);
512
                if (c == null) {
513
                    logger.debug("Classloader {}, can't load class {} from jar {}",
514
                            new Object[]{
515
                                this.getPluginName(),
516
                                name,
517
                                jar.getName()
518
                            }
519
                    );
520
                }
521

    
522
            }
523

    
524
            if (c == null) {
525
                logger.debug("Classloader {}, class not found {}",
526
                        new Object[]{
527
                            this.getPluginName(),
528
                            name
529
                        }
530
                );
531
                debugDump();
532
                throw new ClassNotFoundException(name);
533
            }
534

    
535
            return c;
536
        } catch (IOException e) {
537
            logger.debug("Classloader {}, error loading class {}",
538
                    new Object[]{
539
                        this.getPluginName(),
540
                        name
541
                    },
542
                    e
543
            );
544
            throw new ClassNotFoundException(Messages.getString(
545
                    "PluginClassLoader.Error_reading_file") + name);
546
        }
547
    }
548

    
549
    /**
550
     * Este metodo busca en este class loader y en el de los plugins de los que
551
     * depende. Se cerciora de que no se queda bucleado cuando hay una relacion
552
     * recursiva entre los plugins.
553
     *
554
     * @param pluginsVisiteds, set con los plugins que va visitando para evitar
555
     * que se quede bucleado cuando hay referencia ciclicas entre plugins.
556
     * @param res
557
     *
558
     * @return
559
     */
560
    private URL getResource(Set<String> pluginsVisiteds, String res) {
561
        URL ret = null;
562

    
563
        if (pluginsVisiteds.contains(this.getPluginName())) {
564
            return null;
565
        }
566
        pluginsVisiteds.add(this.getPluginName());
567

    
568
        //
569
        // Primero buscamos en el directorio del plugin.
570
        try {
571
            logger.debug("Classloader {}, searching resource '{}'", this.getPluginName(), res);
572
            List<String> resource = new ArrayList<>();
573
            StringTokenizer st = new StringTokenizer(res, "\\/");
574
            while (st.hasMoreTokens()) {
575
                String token = st.nextToken();
576
                resource.add(token);
577
            }
578
            ret = getResource(baseDir, resource);
579
            if (ret != null) {
580
                return ret;
581
            }
582
        } catch (Exception e) {
583
            logger.warn("Classloader {}, Error getting resource '{}'.",
584
                    new Object[]{
585
                        this.getPluginName(), res
586
                    },
587
                    e
588
            );
589
        }
590

    
591
        logger.debug("Classloader {}, searching in depends pluginLoaders", this.getPluginName());
592
        for (PluginClassLoader pluginClassLoader : this.pluginLoaders) {
593
            if (pluginClassLoader != null) {
594
                try {
595
                    pluginsVisiteds.add(pluginClassLoader.getPluginName());
596
                    ret = pluginClassLoader.getResource(pluginsVisiteds, res);
597
                    if (ret != null) {
598
                        logger.debug("Classloader {}, Found resource '{}' in plugin '{}'.",
599
                                new Object[]{
600
                                    this.getPluginName(), res, pluginClassLoader.getPluginName(),});
601
                        return ret;
602
                    }
603
                } catch (Exception e) {
604
                    // Ignore, try in next classloader
605
                }
606
            }
607
        }
608

    
609
        if (ret == null) {
610
            //
611
            // Por ultimo en el class loader padre, se supone que es el del sistema.
612
            try {
613
                ret = super.getResource(res);
614
            } catch (Exception e) {
615
                logger.warn("Classloader {}, error getting resource '{}' in parent classloader'",
616
                        new Object[]{this.getPluginName(), res},
617
                        e
618
                );
619
            }
620
            if (ret == null) {
621
                logger.debug("Classloader {}, Resource '{}' not found.",
622
                        new Object[]{this.getPluginName(), res}
623
                );
624
            }
625
        }
626
        return ret;
627
    }
628

    
629
}