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

History | View | Annotate | Download (19.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.HashSet;
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.Set;
46
import java.util.StringTokenizer;
47
import java.util.zip.ZipEntry;
48
import java.util.zip.ZipException;
49
import java.util.zip.ZipFile;
50

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

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

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

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

    
82
    /**
83
     * DOCUMENT ME!
84
     */
85
    private File baseDir;
86
    private List<PluginClassLoader> pluginLoaders;
87
    private static List<ClassLoader> otherLoaders = new ArrayList<ClassLoader>();
88
    private boolean isOtherLoader = false;
89

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

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

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

    
121
        for (int i = 0; i < jars.length; i++) {
122
            try {
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("Duplicated class {} in {} and {}",
141
                                new Object[]{
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
            } finally {
157
                debugDump();
158
            }
159
        }
160
    }
161

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

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

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

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

    
238
    private Class<?> loadOtherClass(String name)
239
            throws ClassNotFoundException {
240
        ClassLoader[] ocl = (ClassLoader[]) otherLoaders.toArray(new ClassLoader[0]);
241
        Class<?> c = null;
242
        for (int i = 0; i < ocl.length; i++) {
243
            c = ocl[i].loadClass(name);
244
            if (c != null) {
245
                logger.debug("Found class {} by the alternative classloaders of plugin {}",
246
                        name, baseDir);
247

    
248
                return c;
249
            }
250
        }
251
        throw new ClassNotFoundException(name);
252
    }
253

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

    
270
        // Reserve space to read
271
        byte[] buff = new byte[size];
272

    
273
        // Get stream to read from
274
        DataInputStream dis = new DataInputStream(is);
275

    
276
        // Read in data
277
        dis.readFully(buff);
278

    
279
        // close stream
280
        dis.close();
281

    
282
        // return data
283
        return buff;
284
    }
285

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

    
298
        // Get the size of the file
299
        long length = file.length();
300

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

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

    
312
        // Read in the bytes
313
        int offset = 0;
314
        int numRead = 0;
315

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

    
321
        // Close the input stream and return bytes
322
        is.close();
323

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

    
330
        return bytes;
331
    }
332

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

    
347
        Set<String> pluginsVisiteds = new HashSet<String>();
348
        ret = this.getResource(pluginsVisiteds, res);
349
        return ret;
350
    }
351

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

    
365
        String parte = res.get(0);
366

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

    
381
        return null;
382
    }
383

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

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

    
401
        return perms;
402
    }
403

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

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

    
424
    @Override
425
    public String toString() {
426
        return super.toString() + " (" + getPluginName() + ")";
427
    }
428

    
429
    public void addPluginClassLoader(PluginClassLoader pluginClassLoader) {
430
        if (!this.pluginLoaders.contains(pluginClassLoader)) {
431
            this.pluginLoaders.add(pluginClassLoader);
432
        }
433
    }
434

    
435
    private Class singleLoadClass(Set<String> pluginsVisiteds, String name) throws ClassNotFoundException {
436

    
437
        if (pluginsVisiteds.contains(this.getPluginName())) {
438
            return null;
439
        }
440
        pluginsVisiteds.add(this.getPluginName());
441

    
442
        // Buscamos en las clases de las librer�as del plugin
443
        Class<?> c = findLoadedClass(name);
444

    
445
        if (c != null) {
446
            return c;
447
        }
448

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

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

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

    
488
                c = defineClass(name, data, 0, data.length);
489

    
490
                logger.debug("Found class {} in jar {} of plugin {}",
491
                        new Object[]{name, jar.getName(), baseDir});
492
            }
493

    
494
            if (c == null) {
495
                throw new ClassNotFoundException(name);
496
            }
497

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

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

    
519
        if (pluginsVisiteds.contains(this.getPluginName())) {
520
            return null;
521
        }
522
        pluginsVisiteds.add(this.getPluginName());
523

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

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

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

    
577
}