View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.shared.util;
20  
21  import java.io.BufferedReader;
22  import java.io.InputStream;
23  import java.io.IOException;
24  
25  import java.io.InputStreamReader;
26  
27  import java.net.URL;
28  
29  import java.security.AccessController;
30  import java.security.PrivilegedActionException;
31  import java.security.PrivilegedExceptionAction;
32  import java.util.ArrayList;
33  import java.util.Collections;
34  import java.util.Enumeration;
35  import java.util.HashSet;
36  import java.util.List;
37  
38  import java.util.Set;
39  import java.util.logging.Level;
40  import java.util.logging.Logger;
41  
42  import javax.faces.FacesException;
43  
44  /**
45   * Utility methods for accessing classes and resources using an appropriate
46   * class loader.
47   *
48   * @version $Revision$ $Date$
49   */
50  public final class ClassLoaderUtils
51  {
52    // Utility class only, no instances
53    private ClassLoaderUtils()
54    {
55    }
56    
57    /**
58     * Loads the class with the specified name.  For Java 2 callers, the
59     * current thread's context class loader is preferred, falling back on the
60     * system class loader of the caller when the current thread's context is not
61     * set, or the caller is pre Java 2.
62     *
63     * @param     name  the name of the class
64     * @return    the resulting <code>Class</code> object
65     * @exception ClassNotFoundException if the class was not found
66     */
67    public static Class<?> loadClass(
68      String name) throws ClassNotFoundException
69    {
70      return loadClass(name, null);
71    }
72  
73    /**
74     * Locates the resource with the specified name.  For Java 2 callers, the
75     * current thread's context class loader is preferred, falling back on the
76     * system class loader of the caller when the current thread's context is not
77     * set, or the caller is pre Java 2.
78     *
79     * @param     name  the name of the resource
80     * @return    the resulting <code>URL</code> object
81     */
82    public static URL getResource(
83      String name)
84    {
85      return getResource(name, null);
86    }
87  
88    /**
89     * Locates the stream resource with the specified name.  For Java 2 callers,
90     * the current thread's context class loader is preferred, falling back on
91     * the system class loader of the caller when the current thread's context is
92     * not set, or the caller is pre Java 2.
93     *
94     * @param     name  the name of the resource
95     * @return    the resulting <code>InputStream</code> object
96     */
97    public static InputStream getResourceAsStream(
98      String name)
99    {
100     return getResourceAsStream(name, null);
101   }
102 
103   /**
104    * Loads the class with the specified name.  For Java 2 callers, the
105    * current thread's context class loader is preferred, falling back on the
106    * class loader of the caller when the current thread's context is not set,
107    * or the caller is pre Java 2.  If the callerClassLoader is null, then
108    * fall back on the system class loader.
109    *
110    * @param     name  the name of the class
111    * @param     callerClassLoader  the calling class loader context
112    * @return    the resulting <code>Class</code> object
113    * @exception ClassNotFoundException if the class was not found
114    */
115   public static Class<?> loadClass(
116     String      name,
117     ClassLoader callerClassLoader) throws ClassNotFoundException
118   {
119     Class<?> clazz = null;
120 
121     try
122     {
123       ClassLoader loader = getContextClassLoader();
124 
125       if (loader != null)
126       {
127           clazz = loader.loadClass(name);
128       }
129     }
130     catch (ClassNotFoundException e)
131     {
132       // treat as though loader not set
133     }
134 
135     if (clazz == null)
136     {
137       if (callerClassLoader != null)
138       {
139           clazz = callerClassLoader.loadClass(name);
140       }
141       else
142       {
143           clazz = Class.forName(name);
144       }
145     }
146 
147     return clazz;
148   }
149 
150   /**
151    * Locates the resource with the specified name.  For Java 2 callers, the
152    * current thread's context class loader is preferred, falling back on the
153    * class loader of the caller when the current thread's context is not set,
154    * or the caller is pre Java 2.  If the callerClassLoader is null, then
155    * fall back on the system class loader.
156    *
157    * @param     name  the name of the resource
158    * @param     callerClassLoader  the calling class loader context
159    * @return    the resulting <code>URL</code> object
160    */
161   public static URL getResource(
162     String      name,
163     ClassLoader callerClassLoader)
164   {
165     _checkResourceName(name);
166 
167     URL url = null;
168 
169     ClassLoader loader = getContextClassLoader();
170 
171     if (loader != null)
172     {
173         url = loader.getResource(name);
174     }
175 
176     if (url == null)
177     {
178       if (callerClassLoader != null)
179       {
180           url = callerClassLoader.getResource(name);
181       }
182       else
183       {
184           url = ClassLoader.getSystemResource(name);
185       }
186     }
187 
188     return url;
189   }
190 
191   /**
192    * Locates the resource stream with the specified name.  For Java 2 callers,
193    * the current thread's context class loader is preferred, falling back on
194    * the class loader of the caller when the current thread's context is not
195    * set, or the caller is pre Java 2.  If the callerClassLoader is null, then
196    * fall back on the system class loader.
197    *
198    * @param     name  the name of the resource
199    * @param     callerClassLoader  the calling class loader context
200    * @return    the resulting <code>InputStream</code> object
201    */
202   public static InputStream getResourceAsStream(
203     String      name,
204     ClassLoader callerClassLoader)
205   {
206     _checkResourceName(name);
207 
208     InputStream stream = null;
209 
210     ClassLoader loader = getContextClassLoader();
211 
212     if (loader != null)
213     {
214         stream = loader.getResourceAsStream(name);
215     }
216 
217     if (stream == null)
218     {
219       if (callerClassLoader != null)
220       {
221           stream = callerClassLoader.getResourceAsStream(name);
222       }
223       else
224       {
225           stream = ClassLoader.getSystemResourceAsStream(name);
226       }
227     }
228 
229     return stream;
230   }
231 
232   /**
233    * Dynamically accesses the current context class loader. 
234    * Includes a check for priviledges against java2 security 
235    * to ensure no security related exceptions are encountered. 
236    * Returns null if there is no per-thread context class loader.
237    */
238   public static ClassLoader getContextClassLoader()
239   {
240       if (System.getSecurityManager() != null) 
241       {
242           try 
243           {
244               ClassLoader cl = AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>()
245                       {
246                           public ClassLoader run() throws PrivilegedActionException
247                           {
248                               return Thread.currentThread().getContextClassLoader();
249                           }
250                       });
251               return cl;
252           }
253           catch (PrivilegedActionException pae)
254           {
255               throw new FacesException(pae);
256           }
257       }
258       else
259       {
260           return Thread.currentThread().getContextClassLoader();
261       }
262   }
263 
264   /**
265    * Instantiate a service from a file in /META-INF/services.
266    * <P>
267    * The following is an excerpt from the JAR File specification:
268    * A service provider identifies itself by placing a provider-configuration file 
269    * in the resource directory META-INF/services. 
270    * The file's name should consist of the fully-qualified name of the abstract service class. 
271    * The file should contain a newline-separated list of unique concrete provider-class names. 
272    * Space and tab characters, as well as blank lines, are ignored. The comment character is '#' (0x23); 
273    * on each line all characters following the first comment character are ignored. 
274    * The file must be encoded in UTF-8. 
275    * 
276    * @param service the classname of the abstract service class.
277    * eg: javax.servlet.Filter
278    */
279   @SuppressWarnings("unchecked")
280   public static <T> List<T> getServices(String service)
281   {
282     String serviceUri ="META-INF/services/" + service;
283     ClassLoader loader = Thread.currentThread().getContextClassLoader();
284     try
285     {
286       Enumeration<URL> urls = loader.getResources(serviceUri);
287       if (urls.hasMoreElements())
288       {
289         List<T> services = new ArrayList<T>(1);
290         Set<String> keys = new HashSet<String>(20);
291         do
292         {
293           URL url = urls.nextElement();
294           
295           if (_LOG.isLoggable(Level.FINEST))
296           {
297             _LOG.finest("Processing: " + url);
298           }
299           try
300           {
301             BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
302             try
303             {
304               while(true)
305               {
306                 String line = in.readLine();
307                 if (line == null)
308                 {
309                     break;
310                 }
311                 
312                 String className = _parseLine(line);
313                 
314                 if(className!=null && keys.add(className))
315                 {
316                   T instance = (T) _getClass(loader, className);
317                   services.add(instance);
318                 }                
319               }
320             }
321             finally
322             {
323               in.close();
324             }
325           }
326           catch (Exception e)
327           {
328             if (_LOG.isLoggable(Level.WARNING))
329             {
330               _LOG.log(Level.WARNING, "Error parsing URL: " + url, e);
331             }
332           }
333         } 
334         while(urls.hasMoreElements());
335         
336         if (services.size() == 1)
337         {
338             return Collections.singletonList(services.get(0));
339         }
340         
341         return Collections.unmodifiableList(services);
342       }
343     }
344     catch (IOException e)
345     {
346       if (_LOG.isLoggable(Level.SEVERE))
347       {
348         _LOG.log(Level.SEVERE, "Error loading Resource: " + serviceUri, e);
349       }
350     }
351 
352     return Collections.emptyList();
353   }
354   
355   private static String _parseLine(String line)
356   {
357     // Eliminate any comments
358     int hashIndex = line.indexOf('#');
359     if (hashIndex >= 0)
360     {
361         line = line.substring(0, hashIndex);
362     }
363 
364     // and any whitespace
365     line = line.trim();
366     if (line.length() > 0)
367     {
368       return line;
369     }
370     
371     return null;
372   }
373   
374   private static Object _getClass(ClassLoader loader, String className)
375     throws ClassNotFoundException, InstantiationException,
376            IllegalAccessException
377   {
378     Class<?> clazz = loader.loadClass(className);
379     return clazz.newInstance();
380   }
381 
382   private static void _checkResourceName(String name)
383   {
384     if ((name != null) && name.startsWith("/"))
385     {
386       if (_LOG.isLoggable(Level.WARNING))
387       {
388         _LOG.log(Level.WARNING, "Resource name not portable: " +name);
389       }
390     }
391   }
392 
393   private static final Logger _LOG = Logger.getLogger(ClassLoaderUtils.class.getName());
394 }