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

History | View | Annotate | Download (16.7 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
package org.gvsig.andami.plugins;
25

    
26
import java.io.DataInputStream;
27
import java.io.File;
28
import java.io.FileInputStream;
29
import java.io.IOException;
30
import java.io.InputStream;
31
import java.net.MalformedURLException;
32
import java.net.URL;
33
import java.net.URLClassLoader;
34
import java.security.AllPermission;
35
import java.security.CodeSource;
36
import java.security.PermissionCollection;
37
import java.util.ArrayList;
38
import java.util.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
        PluginClassLoader[] pluginLoaders) throws IOException {
96
        super(jars, cl);
97
        logger.debug("Creating PluginClassLoader for {}.", baseDir);
98
        this.baseDir = new File(new File(baseDir).getAbsolutePath());
99
        this.pluginLoaders = pluginLoaders;
100

    
101
        if( jars==null || jars.length<1 ) { 
102
            debugDump(); 
103
                return;
104
        }
105
        ZipFile[] jarFiles = new ZipFile[jars.length];
106

    
107
        for (int i = 0; i < jars.length; i++) {
108
            try {
109
                jarFiles[i] = new ZipFile(jars[i].getPath());
110

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

    
113
                while (entradas.hasMoreElements()) {
114
                    ZipEntry file = (ZipEntry) entradas.nextElement();
115
                    String fileName = file.getName();
116

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

    
119
                        continue;
120
                    }
121

    
122
                    fileName = fileName.substring(0, fileName.length() - 6)
123
                                       .replace('/', '.');
124

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

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

    
177
        if (c != null) {
178
            return c;
179
        }
180

    
181
        logger.debug("Searching class '{}' in {}", new Object[] {name, this.toString()});
182
        try {
183
            ZipFile jar = (ZipFile) clasesJar.get(name);
184

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

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

    
221
                c = defineClass(name, data, 0, data.length);
222
                
223
                logger.debug("Found class {} in jar {} of plugin {}",
224
                            new Object[] { name, jar.getName(), baseDir} );
225
            }
226

    
227
            if (c == null) {
228
                throw new ClassNotFoundException(name);
229
            }
230

    
231
            return c;
232
        } catch (IOException e) {
233
            throw new ClassNotFoundException(Messages.getString(
234
                    "PluginClassLoader.Error_reading_file") + name);
235
        }
236
    }
237

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

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

    
294
                    return c;
295
                }
296
    }
297
        throw new ClassNotFoundException(name);
298
    }
299

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

    
316
        // Reserve space to read
317
        byte[] buff = new byte[size];
318

    
319
        // Get stream to read from
320
        DataInputStream dis = new DataInputStream(is);
321

    
322
        // Read in data
323
        dis.readFully(buff);
324

    
325
        // close stream
326
        dis.close();
327

    
328
        // return data
329
        return buff;
330
    }
331

    
332
    /**
333
     * Gets the bytes of a File
334
     *
335
     * @param file File
336
     *
337
     * @return bytes of file
338
     *
339
     * @throws IOException If the operation fails
340
     */
341
    private byte[] loadClassData(File file) throws IOException {
342
        InputStream is = new FileInputStream(file);
343

    
344
        // Get the size of the file
345
        long length = file.length();
346

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

    
355
        // Create the byte array to hold the data
356
        byte[] bytes = new byte[(int) length];
357

    
358
        // Read in the bytes
359
        int offset = 0;
360
        int numRead = 0;
361

    
362
        while ((offset < bytes.length) &&
363
                ((numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)) {
364
            offset += numRead;
365
        }
366

    
367
        // Close the input stream and return bytes
368
        is.close();
369

    
370
        // Ensure all the bytes have been read in
371
        if (offset < bytes.length) {
372
            throw new IOException("Could not completely read file " +
373
                file.getName());
374
        }
375

    
376
        return bytes;
377
    }
378

    
379
    /**
380
     * Gets the requested resource. If the path is relative, its base directory
381
     * will be the one provided in the PluginClassLoader's constructor.
382
     * If the resource is not found, the parent classloader will be invoked
383
     * to try to get it. If it is not found, it will return null.
384
     *
385
     * @param res An absolute or relative path to the requested resource.
386
     *
387
     * @return Resource's URL if it was found, nul otherwise.
388
     */
389
    public URL getResource(String res) {
390
            URL ret = null;
391
        try {
392
                logger.debug("Search resource {} in {}", res, this.baseDir.toString());
393
            List<String> resource = new ArrayList<String>();
394
            StringTokenizer st = new StringTokenizer(res, "\\/");
395

    
396
            while (st.hasMoreTokens()) {
397
                String token = st.nextToken();
398
                resource.add(token);
399
            }
400

    
401
            ret = getResource(baseDir, resource);
402

    
403
            if (ret != null) {
404
                return ret;
405
            }
406
        } catch (Exception e) {
407
                logger.info("Error getting resource {} in {}'", new Object[] {res,this.baseDir.toString()}, e);
408
        }
409
        
410
        try {
411
            ret = super.getResource(res);
412
        } catch (Exception e) {
413
                logger.info("Error getting resource {} in {}'", new Object[] {res,this.baseDir.toString()}, e);
414
        }
415
        if( ret == null ) {
416
                logger.info("Error getting resource {} in {}'", new Object[] {res,this.baseDir.toString()});
417
        }
418
        return ret;
419
    }
420

    
421
    /**
422
     * Gets the requested resource. If the path is relative, its base directory
423
     * will be the one provided in the PluginClassLoader's constructor.
424
     * If the resource is not found, the parent classloader will be invoked
425
     * to try to get it. If it is not found, it will return null.
426
     *
427
     * @param res An absolute or relative path to the requested resource.
428
     *
429
     * @return Resource's URL if it was found, nul otherwise.
430
     */
431
    private URL getResource(File base, List<String> res) {
432
        File[] files = base.listFiles();
433

    
434
        String parte = res.get(0);
435

    
436
        for (int i = 0; i < files.length; i++) {
437
            if (files[i].getName().compareTo(parte) == 0) {
438
                if (res.size() == 1) {
439
                    try {
440
                        return new URL("file:" + files[i].toString());
441
                    } catch (MalformedURLException e) {
442
                        return null;
443
                    }
444
                } else {
445
                    return getResource(files[i], res.subList(1, res.size()));
446
                }
447
            }
448
        }
449

    
450
        return null;
451
    }
452

    
453
    /**
454
     * Returns the name of the plugin (the name of the directory containing
455
     * the plugin).
456
     *
457
     * @return An String containing the plugin's name.
458
     */
459
    public String getPluginName() {
460
        String ret = baseDir.getAbsolutePath().substring(baseDir.getAbsolutePath()
461
                                                                .lastIndexOf(File.separatorChar) +
462
                1);
463

    
464
        return ret;
465
    }
466

    
467
    /*
468
     * @see java.security.SecureClassLoader#getPermissions(java.security.CodeSource)
469
     */
470
    protected PermissionCollection getPermissions(CodeSource codesource) {
471
        PermissionCollection perms = super.getPermissions(codesource);
472
        perms.add(new AllPermission());
473

    
474
        return perms;
475
    }
476

    
477
    /**
478
     * Gets the plugin's base Iterator<Map.Entry<Integer, Integer>>dir, the directory which will be used to
479
     * search resources.
480
     *
481
     * @return Returns the baseDir.
482
     */
483
    public String getBaseDir() {
484
        return baseDir.getAbsolutePath();
485
    }
486

    
487
    /**
488
     * Adds other classloader to use when all the normal methods fail.
489
     * 
490
     * @param classLoaders An ArrayList of ClassLoaders which will
491
     * be used to load classes when all the normal methods fail.
492
     */
493
        public static void addLoaders(ArrayList classLoaders) {
494
                otherLoaders.addAll(classLoaders);
495
        }
496

    
497
    @Override
498
    public String toString() {
499
        return super.toString() + " (" + getPluginName() + ")";
500
    }
501
}