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

History | View | Annotate | Download (17.9 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.Enumeration;
39
import java.util.Hashtable;
40
import java.util.Iterator;
41
import java.util.List;
42
import java.util.Map;
43
import java.util.Map.Entry;
44
import java.util.StringTokenizer;
45
import java.util.zip.ZipEntry;
46
import java.util.zip.ZipException;
47
import java.util.zip.ZipFile;
48

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

    
53

    
54

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

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

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

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

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

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

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

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

    
124
                        continue;
125
                    }
126

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

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

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

    
182
        if (c != null) {
183
            return c;
184
        }
185

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

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

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

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

    
232
            if (c == null) {
233
                throw new ClassNotFoundException(name);
234
            }
235

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

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

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

    
299
                    return c;
300
                }
301
    }
302
        throw new ClassNotFoundException(name);
303
    }
304

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

    
321
        // Reserve space to read
322
        byte[] buff = new byte[size];
323

    
324
        // Get stream to read from
325
        DataInputStream dis = new DataInputStream(is);
326

    
327
        // Read in data
328
        dis.readFully(buff);
329

    
330
        // close stream
331
        dis.close();
332

    
333
        // return data
334
        return buff;
335
    }
336

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

    
349
        // Get the size of the file
350
        long length = file.length();
351

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

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

    
363
        // Read in the bytes
364
        int offset = 0;
365
        int numRead = 0;
366

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

    
372
        // Close the input stream and return bytes
373
        is.close();
374

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

    
381
        return bytes;
382
    }
383

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

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

    
461
        String parte = res.get(0);
462

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

    
477
        return null;
478
    }
479

    
480
    /**
481
     * Returns the name of the plugin (the name of the directory containing
482
     * the plugin).
483
     *
484
     * @return An String containing the plugin's name.
485
     */
486
    public String getPluginName() {
487
        String ret = baseDir.getAbsolutePath().substring(baseDir.getAbsolutePath()
488
                                                                .lastIndexOf(File.separatorChar) +
489
                1);
490

    
491
        return ret;
492
    }
493

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

    
501
        return perms;
502
    }
503

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

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

    
524
    @Override
525
    public String toString() {
526
        return super.toString() + " (" + getPluginName() + ")";
527
    }
528
}