View Javadoc

1   /*
2   Copyright (C) 2003 Grid Systems, S.A.
3   
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2, as
6   published by the Free Software Foundation.
7   
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17  package com.gridsystems.launcher;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.lang.reflect.Method;
22  import java.lang.reflect.Modifier;
23  import java.net.JarURLConnection;
24  import java.net.MalformedURLException;
25  import java.net.URL;
26  import java.net.URLClassLoader;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Enumeration;
30  import java.util.jar.JarEntry;
31  import java.util.jar.JarFile;
32  import java.util.logging.Level;
33  import java.util.logging.Logger;
34  
35  /**
36   * Builds an URLClassLoader with a "dynamically" constructed classpath.
37   * <p>
38   * <b>IMPORTANT</b>: Do not use classes from external libraries here, as they will
39   * probably not be in the classpath.
40   *
41   * @author Rodrigo Ruiz
42   * @version 1.0
43   */
44  public class CLBuilder {
45    /**
46     * For this class logs.
47     */
48    private static Logger log = Logger.getLogger("CLBuilder");
49  
50    /**
51     * The parent class loader. Specified on construction
52     */
53    private ClassLoader parent;
54  
55    /**
56     * The set of URLs that will conform the classloader classpath.
57     */
58    private Collection classpath = new ArrayList();
59  
60    /**
61     * Creates a new instance with null parent class loader.
62     */
63    public CLBuilder() {
64      this(null);
65    }
66  
67    /**
68     * Creates a new instance with the specified class loader.
69     *
70     * @param parent The parent classloader for the generated one
71     */
72    public CLBuilder(ClassLoader parent) {
73      this.parent = parent;
74    }
75  
76    /**
77     * Sets the parent classloader.
78     *
79     * @param parent The new parent classloader
80     */
81    public synchronized void setParent(ClassLoader parent) {
82      this.parent = parent;
83    }
84  
85    /**
86     * Adds the classpath of the specified classloader instance.
87     *
88     * @param cl The classloader whose classpath we want to obtain
89     */
90    public synchronized void addClassPath(ClassLoader cl) {
91      try {
92        Enumeration enumeration = cl.getResources("");
93        addEnumeration(enumeration);
94  
95        enumeration = cl.getResources("META-INF");
96        addEnumeration(enumeration);
97      } catch (Exception ignore) { }
98    }
99  
100   /**
101    * Adds the classpath of the current context classloader.
102    */
103   public synchronized void addContextClassPath() {
104     addClassPath(Thread.currentThread().getContextClassLoader());
105   }
106 
107   /**
108    * Adds the specified directory to the classpath.
109    *
110    * @param dir The directory to add
111    */
112   public synchronized void addDir(File dir) {
113     try {
114       if (check(dir) && dir.isDirectory()) {
115         addUrl(dir.toURI().toURL());
116       }
117     } catch (MalformedURLException e) {
118       log.log(Level.WARNING, "Malformed path: {0}", dir);
119     }
120   }
121 
122   /**
123    * Adds all URLs in the specified enumeration.
124    *
125    * @param enumeration An Enumeration of URLs
126    * @throws Exception In case of error during URLs handling
127    */
128   private void addEnumeration(Enumeration enumeration) throws Exception {
129     while (enumeration.hasMoreElements()) {
130       URL url = (URL)enumeration.nextElement();
131       if (url.getProtocol().startsWith("jar")) {
132         JarURLConnection con = (JarURLConnection)url.openConnection();
133         URL jar = con.getJarFileURL();
134         addUrl(jar);
135       } else if (url.getProtocol().startsWith("file")) {
136         addUrl(url);
137       }
138     }
139   }
140 
141   /**
142    * Adds the specified file to the classpath. Currently, only jar files are
143    * admitted.
144    *
145    * @param f The file to add
146    */
147   public synchronized void addFile(File f) {
148     try {
149       if (check(f)) {
150         if (f.isFile() && f.getName().endsWith(".jar")) {
151           addUrl(f.toURI().toURL());
152         }
153       }
154     } catch (MalformedURLException e) {
155       log.log(Level.WARNING, "Malformed path: {0}", f);
156     }
157   }
158 
159   /**
160    * Adds all jar files in the specified directory to the classpath. If recursive is
161    * true, jar files in subdirectories are also added.
162    *
163    * @param f          The directory containing the jars to add
164    * @param recursive  flag indicating if subdirectories must be scanned too
165    */
166   public synchronized void addJars(File f, boolean recursive) {
167     try {
168       if (!check(f)) {
169         return;
170       }
171 
172       File[] contents = f.listFiles();
173       for (int i = 0; i < contents.length; i++) {
174         File ff = contents[i];
175         if (ff.getName().endsWith(".jar")) {
176           addUrl(ff.toURI().toURL());
177         } else if (recursive && ff.isDirectory()) {
178           addJars(ff, true);
179         }
180       }
181     } catch (MalformedURLException e) { }
182   }
183 
184   /**
185    * Adds a single URL to the path set. All other "add" methods are based on this one.
186    *
187    * @param url The URL to add
188    */
189   public synchronized void addUrl(URL url) {
190     if (!classpath.contains(url)) {
191       log.log(Level.CONFIG, "Adding {0}", url);
192       classpath.add(url);
193     }
194   }
195 
196   /**
197    * Adds <code>WEB-INF/classes</code> and <code>WEB-INF/lib/*.jar</code> to
198    * the classpath.
199    *
200    * @param f The webapp context directory
201    */
202   public synchronized void addWebApp(File f) {
203     if (!check(f) || !f.isDirectory()) {
204       return;
205     }
206 
207     File webinf = new File(f, "WEB-INF");
208     if (webinf.exists()) {
209       File classes = new File(webinf, "classes");
210       if (classes.exists()) {
211         addDir(classes);
212       }
213 
214       File libs = new File(webinf, "lib");
215       if (libs.exists()) {
216         addJars(libs, false);
217       }
218     }
219   }
220 
221   /**
222    * Adds a war to the classpath.
223    *
224    * @param f The war file
225    */
226   public synchronized void addWar(File f) {
227     if (!check(f) || f.isDirectory() || !f.getName().endsWith(".war")) {
228       return;
229     }
230 
231     try {
232       String urlBase = "jar:" + f.toURI().toURL() + "!/";
233 
234       JarFile jf = new JarFile(f, true);
235       JarEntry entry = jf.getJarEntry("WEB-INF/classes");
236       if (entry != null) {
237         URL url = new URL(urlBase + "WEB-INF/classes/");
238         addUrl(url);
239       }
240 
241       Enumeration enumeration = jf.entries();
242       while (enumeration.hasMoreElements()) {
243         entry = (JarEntry)enumeration.nextElement();
244         String entryName = entry.getName();
245         if (entryName.startsWith("WEB-INF/lib/") && entryName.endsWith(".jar")) {
246           URL url = new URL(urlBase + entryName);
247           addUrl(url);
248         }
249       }
250     } catch (IOException e) {
251       log.log(Level.WARNING, "Error reading {0}", f.getPath());
252     }
253   }
254 
255   /**
256    * Checks that the specified file exists, and logs a warning message if not.
257    *
258    * @param f The file to check
259    * @return true if it exists, false otherwise
260    */
261   private boolean check(File f) {
262     if (f == null) {
263       return false;
264     } else if (!f.exists()) {
265       log.log(Level.CONFIG, "Path not found: {0}", f);
266       return false;
267     }
268     return true;
269   }
270 
271   /**
272    * Constructs a classloader with the current classpath.
273    *
274    * @return An URLClassLoader instance with the current path set as its classpath
275    */
276   public synchronized ClassLoader getClassLoader() {
277     URL[] urls = new URL[classpath.size()];
278     classpath.toArray(urls);
279 
280     return new URLClassLoader(urls, parent);
281   }
282 
283   /**
284    * Invokes the specified method within the context of the currently built
285    * classloader.
286    * <p>
287    * If the method is not static, it will attempt to create a new instance before
288    * invoking it.
289    *
290    * @param clazz   The class name
291    * @param method  The method name.
292    * @param types   The argument types list
293    * @param args    The arguments list
294    * @return        The method invocation returned object
295    * @throws Exception In case of any reflection error
296    */
297   public Object invoke(String clazz, String method, Class[] types, Object[] args)
298     throws Exception {
299     ClassLoader cl = getClassLoader();
300     Thread.currentThread().setContextClassLoader(cl);
301 
302     Class c = cl.loadClass(clazz);
303     Method m = c.getMethod(method, types);
304     Object target = null;
305     if (!Modifier.isStatic(m.getModifiers())) {
306       target = c.newInstance();
307     }
308     return c.getMethod(method, types).invoke(target, args);
309   }
310 }