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.trinidad.util;
20  
21  import java.io.BufferedReader;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  
26  import java.net.URL;
27  
28  import java.util.ArrayList;
29  import java.util.Collections;
30  import java.util.Enumeration;
31  import java.util.HashSet;
32  import java.util.List;
33  import java.util.Set;
34  import java.util.concurrent.ConcurrentMap;
35  
36  import org.apache.myfaces.trinidad.context.RequestContext;
37  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
38  
39  /**
40   * Utility methods for accessing classes and resources using an appropriate
41   * class loader.
42   *
43   * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/util/ClassLoaderUtils.java#0 $) $Date: 10-nov-2005.18:49:08 $
44   */
45  public final class ClassLoaderUtils
46  {
47    // Utility class only, no instances
48    private ClassLoaderUtils()
49    {
50    }
51    
52    /**
53     * Loads the class with the specified name.  For Java 2 callers, the
54     * current thread's context class loader is preferred, falling back on the
55     * system class loader of the caller when the current thread's context is not
56     * set, or the caller is pre Java 2.
57     *
58     * @param     name  the name of the class
59     * @return    the resulting <code>Class</code> object
60     * @exception ClassNotFoundException if the class was not found
61     */
62    public static Class<?> loadClass(
63      String name) throws ClassNotFoundException
64    {
65      return loadClass(name, null);
66    }
67  
68    /**
69     * Locates the resource with the specified name.  For Java 2 callers, the
70     * current thread's context class loader is preferred, falling back on the
71     * system class loader of the caller when the current thread's context is not
72     * set, or the caller is pre Java 2.
73     *
74     * @param     name  the name of the resource
75     * @return    the resulting <code>URL</code> object
76     */
77    public static URL getResource(
78      String name)
79    {
80      return getResource(name, null);
81    }
82  
83    /**
84     * Locates the stream resource with the specified name.  For Java 2 callers,
85     * the current thread's context class loader is preferred, falling back on
86     * the system class loader of the caller when the current thread's context is
87     * not set, or the caller is pre Java 2.
88     *
89     * @param     name  the name of the resource
90     * @return    the resulting <code>InputStream</code> object
91     */
92    public static InputStream getResourceAsStream(
93      String name)
94    {
95      return getResourceAsStream(name, null);
96    }
97  
98    /**
99     * Loads the class with the specified name.  For Java 2 callers, the
100    * current thread's context class loader is preferred, falling back on the
101    * class loader of the caller when the current thread's context is not set,
102    * or the caller is pre Java 2.  If the callerClassLoader is null, then
103    * fall back on the system class loader.
104    *
105    * @param     name  the name of the class
106    * @param     callerClassLoader  the calling class loader context
107    * @return    the resulting <code>Class</code> object
108    * @exception ClassNotFoundException if the class was not found
109    */
110   public static Class<?> loadClass(
111     String      name,
112     ClassLoader callerClassLoader) throws ClassNotFoundException
113   {
114     Class<?> clazz = null;
115 
116     try
117     {
118       ClassLoader loader = getContextClassLoader();
119 
120       if (loader != null)
121         clazz = loader.loadClass(name);
122     }
123     catch (ClassNotFoundException e)
124     {
125       // treat as though loader not set
126       ;
127     }
128 
129     if (clazz == null)
130     {
131       if (callerClassLoader != null)
132         clazz = callerClassLoader.loadClass(name);
133       else
134         clazz = Class.forName(name);
135     }
136 
137     return clazz;
138   }
139 
140   /**
141    * Locates the resource with the specified name.  For Java 2 callers, the
142    * current thread's context class loader is preferred, falling back on the
143    * class loader of the caller when the current thread's context is not set,
144    * or the caller is pre Java 2.  If the callerClassLoader is null, then
145    * fall back on the system class loader.
146    *
147    * @param     name  the name of the resource
148    * @param     callerClassLoader  the calling class loader context
149    * @return    the resulting <code>URL</code> object
150    */
151   public static URL getResource(
152     String      name,
153     ClassLoader callerClassLoader)
154   {
155     _checkResourceName(name);
156 
157     URL url = null;
158 
159     ClassLoader loader = getContextClassLoader();
160 
161     if (loader != null)
162       url = loader.getResource(name);
163 
164     if (url == null)
165     {
166       if (callerClassLoader != null)
167         url = callerClassLoader.getResource(name);
168       else
169         url = ClassLoader.getSystemResource(name);
170     }
171 
172     return url;
173   }
174 
175   /**
176    * Locates the resource stream with the specified name.  For Java 2 callers,
177    * the current thread's context class loader is preferred, falling back on
178    * the class loader of the caller when the current thread's context is not
179    * set, or the caller is pre Java 2.  If the callerClassLoader is null, then
180    * fall back on the system class loader.
181    *
182    * @param     name  the name of the resource
183    * @param     callerClassLoader  the calling class loader context
184    * @return    the resulting <code>InputStream</code> object
185    */
186   public static InputStream getResourceAsStream(
187     String      name,
188     ClassLoader callerClassLoader)
189   {
190     _checkResourceName(name);
191 
192     InputStream stream = null;
193 
194     ClassLoader loader = getContextClassLoader();
195 
196     if (loader != null)
197       stream = loader.getResourceAsStream(name);
198 
199     if (stream == null)
200     {
201       if (callerClassLoader != null)
202         stream = callerClassLoader.getResourceAsStream(name);
203       else
204         stream = ClassLoader.getSystemResourceAsStream(name);
205     }
206 
207     return stream;
208   }
209 
210   /**
211    * Dynamically accesses the current context class loader.
212    * Returns null if there is no per-thread context class loader.
213    */
214   public static ClassLoader getContextClassLoader()
215   {
216     return Thread.currentThread().getContextClassLoader();
217   }
218 
219   /**
220    * Instantiate the first registered services from a file in /META-INF/services.
221    * @param service the classname of the abstract service class.
222    * eg: javax.servlet.Filter
223    * @see #getService(String)
224    * @see #getServices(Class)
225    * @see #getServices(String)
226    */
227   public static <T> T getService(Class<T> service)
228   {
229     return (T) getService(service.getName());
230   }
231 
232   /**
233    * Instantiate the first registered services from a file in /META-INF/services.
234    * @param service the classname of the abstract service class.
235    * eg: javax.servlet.Filter
236    * @see #getService(Class)
237    * @see #getServices(String)
238    * @see #getServices(Class)
239    */
240   public static <T> T getService(String service)
241   {
242     List<T> services = _getServices(service, 1);
243     
244     if (services.isEmpty())
245       return null;
246     else
247       return services.get(0);
248   }
249 
250   /**
251    * Instantiate all available services from a file in /META-INF/services.
252    * @see #getServices(String)
253    * @see #getService(String)
254    * @see #getService(Class)
255    */
256   public static <T> List<T> getServices(Class<T> service)
257   {
258     return getServices(service.getName());
259   }
260   
261   /**
262    * Gets all the registered services of the supplied type. Once instantiated, the services are
263    *  cached in the application scoped storage, and any further requests to this function will
264    *  get the cached services. It is assumed that the name of the service file and the type of
265    *  the service class is the same.
266    * @param service The class type of the service
267    * @return The list of registered service instances
268    */
269   static <T> List<T> __getCachableServices(Class<T> service)
270   {
271     List<T> services = Collections.emptyList();
272     String serviceName = service.getName();
273     ConcurrentMap<String, Object> concurrentAppMap =
274       RequestContext.getCurrentInstance().getApplicationScopedConcurrentMap();
275     Object serviceObject = concurrentAppMap.get(serviceName);
276 
277     if (serviceObject == null)
278     {
279       services = ClassLoaderUtils.getServices(service);
280       Object oldValue = concurrentAppMap.putIfAbsent(serviceName, services);
281       
282       // if a different thread raced us and added one, use that
283       if (oldValue != null)
284       {
285         services = (List<T>) oldValue;
286       }
287     }
288     else
289     {
290       services = (List<T>) serviceObject;
291     }
292     
293     return services;
294   }
295   
296   /**
297    * Instantiate all available services from a file in /META-INF/services.
298    * <P>
299    * The following is an excerpt from the JAR File specification:
300    * A service provider identifies itself by placing a provider-configuration file 
301    * in the resource directory META-INF/services. 
302    * The file's name should consist of the fully-qualified name of the abstract service class. 
303    * The file should contain a newline-separated list of unique concrete provider-class names. 
304    * Space and tab characters, as well as blank lines, are ignored. The comment character is '#' (0x23); 
305    * on each line all characters following the first comment character are ignored. 
306    * The file must be encoded in UTF-8. 
307    * 
308    * @param service the classname of the abstract service class.
309    * eg: javax.servlet.Filter
310    * @see #getServices(Class)
311    * @see #getService(Class)
312    * @see #getService(String)
313    */
314   public static <T> List<T> getServices(String service)
315   {
316     return _getServices(service, Integer.MAX_VALUE);
317   }
318   
319   @SuppressWarnings("unchecked")
320   private static <T> List<T> _getServices(String service, int maxServiceCount)
321   {
322     String serviceUri ="META-INF/services/" + service;
323     ClassLoader loader = Thread.currentThread().getContextClassLoader();
324     
325     try
326     {
327       Enumeration<URL> urls = loader.getResources(serviceUri);
328       if (urls.hasMoreElements())
329       {
330         List<T> services = new ArrayList<T>(1);
331         Set<String> keys = new HashSet<String>(20);
332         do
333         {
334           URL url = urls.nextElement();
335           _LOG.finest("Processing:{0}", url);
336           try
337           {
338             BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
339             try
340             {
341               while(true)
342               {
343                 String line = in.readLine();
344                 if (line == null)
345                   break;
346                 
347                 String className = _parseLine(line);
348                 
349                 if(className!=null && keys.add(className))
350                 {
351                   T instance = (T) _newInstance(loader, className);
352                   services.add(instance);
353                   
354                   if (services.size() >= maxServiceCount)
355                   {
356                     break;
357                   }
358                 }                
359               }
360             }
361             finally
362             {
363               in.close();
364             }
365           }
366           catch (Exception e)
367           {
368             _LOG.warning("ERR_PARSING_URL",url);
369             _LOG.warning(e);
370           }
371         } 
372         while(urls.hasMoreElements());
373         
374         if (services.size() == 1)
375           return Collections.singletonList(services.get(0));
376         
377         return Collections.unmodifiableList(services);
378       }
379     }
380     catch (IOException e)
381     {
382       _LOG.severe("ERR_LOADING_RESROUCE",serviceUri);
383       _LOG.severe(e);
384     }
385 
386     return Collections.emptyList();
387   }
388   
389   private static String _parseLine(String line)
390   {
391     // Eliminate any comments
392     int hashIndex = line.indexOf('#');
393     if (hashIndex >= 0)
394       line = line.substring(0, hashIndex);
395 
396     // and any whitespace
397     line = line.trim();
398     if (line.length() > 0)
399     {
400       return line;
401     }
402     
403     return null;
404   }
405   
406   private static Object _newInstance(ClassLoader loader, String className)
407     throws ClassNotFoundException, InstantiationException,
408            IllegalAccessException
409   {
410     Class<?> clazz = loader.loadClass(className);
411     return clazz.newInstance();
412   }
413 
414   private static void _checkResourceName(String name)
415   {
416     if ((name != null) && name.startsWith("/"))
417     {
418       _LOG.warning("RESOURCE_NAME_NOT_PORTABLE", name);
419                    
420     }
421   }
422 
423   private static final TrinidadLogger _LOG =
424     TrinidadLogger.createTrinidadLogger(ClassLoaderUtils.class);
425 }