GateClassLoader.java
001 /*
002  * GateClassLoader.java
003  
004  * Copyright (c) 1995-2012, The University of Sheffield. See the file
005  * COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  
007  * This file is part of GATE (see http://gate.ac.uk/), and is free software,
008  * licenced under the GNU Library General Public License, Version 2, June 1991
009  * (in the distribution as file licence.html, and also available at
010  * http://gate.ac.uk/gate/licence.html).
011  
012  * Kalina Bontcheva, 1998
013  
014  * Revised by Hamish for 1.2 style and URL/Jar loading, June 2000
015  
016  * a new architecture, by Mark Greenwood, to allow proper plugin isolation and
017  * full unloading of class definitions when a plugin is unloaded, March 2012
018  
019  * $Id: GateClassLoader.java 18719 2015-05-28 12:06:30Z markagreenwood $
020  */
021 
022 package gate.util;
023 
024 import gate.Gate;
025 import gate.Gate.DirectoryInfo;
026 import gate.Gate.ResourceInfo;
027 import gate.Resource;
028 import gate.creole.AbstractResource;
029 
030 import java.beans.Introspector;
031 import java.net.URL;
032 import java.net.URLClassLoader;
033 import java.util.ArrayList;
034 import java.util.HashSet;
035 import java.util.LinkedHashMap;
036 import java.util.LinkedHashSet;
037 import java.util.List;
038 import java.util.Map;
039 import java.util.Set;
040 
041 import org.apache.log4j.Logger;
042 
043 /**
044  * GATE's class loader, which allows loading of classes over the net. A
045  * list of URLs is searched, which should point at .jar files or to
046  * directories containing class file hierarchies. The loader is also
047  * used for creating JAPE RHS action classes.
048  */
049 public class GateClassLoader extends URLClassLoader {
050 
051   protected static final Logger log = Logger.getLogger(GateClassLoader.class);
052 
053   /** Debug flag */
054   private static final boolean DEBUG = false;
055 
056   /** Default construction - use an empty URL list. */
057   public GateClassLoader(String name) {
058     super(new URL[0]);
059     this.id = name;
060   }
061 
062   /** Chaining constructor. */
063   public GateClassLoader(String name, ClassLoader parent) {
064     super(new URL[0], parent);
065     this.id = name;
066   }
067 
068   /** Default construction with URLs list. */
069   public GateClassLoader(String name, URL[] urls) {
070     super(urls);
071     this.id = name;
072   }
073 
074   /** Chaining constructor with URLs list. */
075   public GateClassLoader(String name, URL[] urls, ClassLoader parent) {
076     super(urls, parent);
077     this.id = name;
078   }
079 
080   public GateClassLoader(String name, URL[] urls, ClassLoader parent,
081           boolean isolated) {
082     super(urls, parent);
083     this.id = name;
084     this.isolated = isolated;
085   }
086 
087   private String id = null;
088 
089   public String getID() {
090     return id;
091   }
092 
093   private boolean isolated = false;
094 
095   public boolean isIsolated() {
096     return isolated;
097   }
098 
099   @Override
100   public String toString() {
101     return "Classloader ID: " + id;
102   }
103 
104   /**
105    * Appends the specified URL to the list of URLs to search for classes
106    * and resources.
107    */
108   @Override
109   public void addURL(URL url) {
110     super.addURL(url);
111   }
112 
113   @Override
114   public URL getResource(String name) {
115     URL result = null;
116 
117     result = super.getResource(name);
118     if(result != nullreturn result;
119 
120     if(getParent() == null) {
121       result = Gate.getClassLoader().findResource(name);
122       if(result != nullreturn result;
123     }
124 
125     Set<GateClassLoader> children;
126     synchronized(childClassLoaders) {
127       children = new LinkedHashSet<GateClassLoader>(childClassLoaders.values());
128     }
129 
130     for(GateClassLoader cl : children) {
131       if(!cl.isIsolated()) {
132         result = cl.getResource(name);
133         if(result != nullreturn result;
134       }
135     }
136 
137     return null;
138   }
139 
140   @Override
141   public Class<?> loadClass(String namethrows ClassNotFoundException {
142     return loadClass(name, false, false, new HashSet<GateClassLoader>());
143   }
144 
145   /**
146    * Delegate loading to the super class (loadClass has protected access
147    * there).
148    */
149   @Override
150   public Class<?> loadClass(String name, boolean resolve)
151           throws ClassNotFoundException {
152     return loadClass(name, resolve, false, new HashSet<GateClassLoader>());
153   }
154 
155   /**
156    * Delegate loading to the super class (loadClass has protected access
157    * there).
158    */
159   private Class<?> loadClass(String name, boolean resolve, boolean localOnly,
160           Set<GateClassLoader> visitedthrows ClassNotFoundException {
161 
162     Class<?> previous = findLoadedClass(name);
163 
164     if(previous != null) {
165       if(DEBUGSystem.out.println("CACHE HIT: " + name + " -- " + id);
166 
167       return previous;
168     }
169 
170     if(DEBUG)
171       System.out.println(name + " -- " + id + ": " + localOnly + "/" + isolated
172               "/" + getParent());
173 
174     // to ensure we don't end up looping through the same classloader
175     // twice we
176     // keep a track of which ones we have already visited
177     visited.add(this);
178 
179     if(!this.equals(Gate.getClassLoader())) {
180       try {
181         // first we see if we can find the class via the system class
182         // path
183         Class<?> found = Gate.getClassLoader().getParent().loadClass(name);
184 
185         URL url = findResource(name.replace('.''/'".class");
186         if(url != null)
187           log.warn(name
188                   " is available via both the system classpath and a plugin; the plugin classes will be ignored");
189 
190         // if we got to here then the class has been found via the
191         // system
192         // classpath so return it and stop looking
193         return found;
194 
195       catch(ClassNotFoundException e) {
196         // this can safely be ignored
197       }
198     }
199 
200     try {
201       // try loading and returning by looking within this classloader
202       return super.loadClass(name, resolve);
203     catch(ClassNotFoundException e) {
204       // this can safely be ignored
205     }
206 
207     if(this.getParent() != null && this.getParent() instanceof GateClassLoader)
208       visited.add((GateClassLoader)this.getParent());
209 
210     if(!localOnly) {
211       // if we aren't just looking locally then...
212 
213       if(getParent() == null) {
214         try {
215           // if this classloader doesn't have a parent then it must be
216           // disposable, but as we haven't found the class we need yet
217           // we should
218           // now look into the main GATE classloader
219           return Gate.getClassLoader().loadClass(name, resolve, false, visited);
220         catch(ClassNotFoundException e) {
221           // this can safely be ignored
222         }
223       }
224 
225       Set<GateClassLoader> children;
226       synchronized(childClassLoaders) {
227         children =
228                 new LinkedHashSet<GateClassLoader>(childClassLoaders.values());
229       }
230 
231       // make sure we don't visit a classloader we've already been
232       // through
233       children.removeAll(visited);
234 
235       for(GateClassLoader cl : children) {
236         // the class isn't to be found in either this classloader or the
237         // main
238         // GATE classloader so let's check all the other disposable
239         // classloaders
240         try {
241           if(!cl.isIsolated())
242             return cl.loadClass(name, resolve, true, visited);
243         catch(ClassNotFoundException e) {
244           // this can safely be ignored
245         }
246       }
247     }
248 
249     // if we got to here then no matter where we have looked we have
250     // been unable
251     // to find the class requested so throw an exception
252     throw new ClassNotFoundException(name);
253   }
254 
255   /**
256    * Forward a call to super.defineClass, which is protected and final
257    * in super. This is used by JAPE and the Jdk compiler class.
258    */
259   public Class<?> defineGateClass(String name, byte[] bytes, int offset, int len) {
260     return super.defineClass(name, bytes, offset, len);
261   }
262 
263   /**
264    * Forward a call to super.resolveClass, which is protected and final
265    * in super. This is used by JAPE and the Jdk compiler class
266    */
267   public void resolveGateClass(Class<?> c) {
268     super.resolveClass(c);
269   }
270 
271   /**
272    * Given a fully qualified class name, this method returns the
273    * instance of Class if it is already loaded using the ClassLoader or
274    * it returns null.
275    */
276   public Class<?> findExistingClass(String name) {
277     return findLoadedClass(name);
278   }
279 
280   Map<String, GateClassLoader> childClassLoaders =
281           new LinkedHashMap<String, GateClassLoader>();
282 
283   /**
284    * Returns a classloader that can, at some point in the future, be
285    * forgotton which allows the class definitions to be garbage
286    * collected.
287    
288    @param id the id of the classloader to return
289    @return either an existing classloader with the given id or a new
290    *         classloader
291    */
292   public GateClassLoader getDisposableClassLoader(String id) {
293     return getDisposableClassLoader(id, null, false);
294   }
295 
296   public GateClassLoader getDisposableClassLoader(String id, boolean isolated) {
297     return getDisposableClassLoader(id, null, isolated);
298   }
299 
300   public GateClassLoader getDisposableClassLoader(String id,
301           ClassLoader parent, boolean isolated) {
302     GateClassLoader gcl = null;
303 
304     synchronized(childClassLoaders) {
305       gcl = childClassLoaders.get(id);
306 
307       if(gcl == null) {
308         gcl = new GateClassLoader(id, new URL[0], parent, isolated);
309         childClassLoaders.put(id, gcl);
310       }
311     }
312 
313     return gcl;
314   }
315 
316   /**
317    * Causes the specified classloader to be forgotten, making it and all
318    * the class definitions loaded by it available for garbage collection
319    
320    @param id the id of the classloader to forget
321    */
322   public void forgetClassLoader(String id) {
323     
324     GateClassLoader gcl;
325     
326     synchronized(childClassLoaders) {
327        gcl = childClassLoaders.remove(id);
328     }
329   
330     if(gcl != null && !gcl.isIsolated()) {
331       // in theory this shouldn't be needed as the Introspector uses
332       // soft references if we move to requiring Java 8 it should be
333       // safe to drop this call
334       Introspector.flushCaches();
335 
336       AbstractResource.flushBeanInfoCache();
337     }
338   }
339 
340   /**
341    * Causes the specified classloader to be forgotten, making it and all
342    * the class definitions loaded by it available for garbage collection
343    
344    @param classloader the classloader to forget
345    */
346   public void forgetClassLoader(GateClassLoader classloader) {
347     if(classloader != nullforgetClassLoader(classloader.getID());
348   }
349   
350   
351   public void forgetClassLoader(String id, DirectoryInfo dInfo) {
352 
353     if(dInfo == null) {
354       forgetClassLoader(id);
355       return;
356     }
357 
358     GateClassLoader classloader = null;
359 
360     synchronized(childClassLoaders) {
361       classloader = childClassLoaders.remove(id);
362     }
363 
364     if(classloader != null && !classloader.isIsolated()) {
365       // now only remove those classes from the caches that the
366       // classloader was responsible for
367       for(ResourceInfo rInfo : dInfo.getResourceInfoList()) {
368         try {
369           @SuppressWarnings("unchecked")
370           Class<? extends Resource> c =
371                   (Class<? extends Resource>)classloader.loadClass(
372                           rInfo.getResourceClassName());
373           
374           if(c != null) {
375             // in theory this shouldn't be needed as the Introspector
376             // uses soft references if we move to requiring Java 8 it
377             // should be safe to drop this call
378             Introspector.flushFromCaches(c);
379 
380             AbstractResource.forgetBeanInfo(c);
381           }
382         catch(ClassNotFoundException e) {
383           // hmm not sure what to do now
384            e.printStackTrace();
385         }
386       }
387     }
388   }
389 
390   /**
391    * Get the child classloaders in creation order. Note that you
392    * shouldn't have any need to access the child classloaders. Holding
393    * references is very likely to lead to a memory leak as the plugins
394    * will no longer be disposable. YOU HAVE BEEN WARNED!
395    
396    @return the child classloaders in creation order
397    */
398   public List<GateClassLoader> getChildren() {
399     synchronized(childClassLoaders) {
400       return new ArrayList<GateClassLoader>(childClassLoaders.values());
401     }
402   }
403 }