View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.util;
18  
19  import java.io.InputStream;
20  import java.lang.reflect.InvocationTargetException;
21  import java.net.URL;
22  
23  import org.apache.logging.log4j.Logger;
24  import org.apache.logging.log4j.status.StatusLogger;
25  import org.apache.logging.log4j.util.LoaderUtil;
26  import org.apache.logging.log4j.util.PropertiesUtil;
27  
28  /**
29   * Load resources (or images) from various sources.
30   */
31  public final class Loader {
32  
33      private static final Logger LOGGER = StatusLogger.getLogger();
34  
35      private static final String TSTR = "Caught Exception while in Loader.getResource. This may be innocuous.";
36  
37      private Loader() {
38      }
39  
40      /**
41       * Returns the ClassLoader to use.
42       * @return the ClassLoader.
43       */
44      public static ClassLoader getClassLoader() {
45          return getClassLoader(Loader.class, null);
46      }
47  
48      /**
49       * Returns the ClassLoader of current thread if possible, or falls back to the system ClassLoader if none is
50       * available.
51       *
52       * @return the TCCL.
53       * @see org.apache.logging.log4j.util.LoaderUtil#getThreadContextClassLoader()
54       */
55      public static ClassLoader getThreadContextClassLoader() {
56          return LoaderUtil.getThreadContextClassLoader();
57      }
58  
59      // TODO: this method could use some explanation
60      public static ClassLoader getClassLoader(final Class<?> class1, final Class<?> class2) {
61          final ClassLoader threadContextClassLoader = getThreadContextClassLoader();
62          final ClassLoader loader1 = class1 == null ? null : class1.getClassLoader();
63          final ClassLoader loader2 = class2 == null ? null : class2.getClassLoader();
64  
65          if (isChild(threadContextClassLoader, loader1)) {
66              return isChild(threadContextClassLoader, loader2) ? threadContextClassLoader : loader2;
67          }
68          return isChild(loader1, loader2) ? loader1 : loader2;
69      }
70  
71      /**
72       * This method will search for {@code resource} in different
73       * places. The search order is as follows:
74       *
75       * <ol>
76       *
77       * <li>Search for {@code resource} using the thread context
78       * class loader under Java2. If that fails, search for
79       * {@code resource} using the class loader that loaded this
80       * class ({@code Loader}). Under JDK 1.1, only the the class
81       * loader that loaded this class ({@code Loader}) is used.</li>
82       * <li>Try one last time with
83       * {@code ClassLoader.getSystemResource(resource)}, that is is
84       * using the system class loader in JDK 1.2 and virtual machine's
85       * built-in class loader in JDK 1.1.</li>
86       * </ol>
87       * @param resource The resource to load.
88       * @param defaultLoader The default ClassLoader.
89       * @return A URL to the resource.
90       */
91      public static URL getResource(final String resource, final ClassLoader defaultLoader) {
92          try {
93              ClassLoader classLoader = getThreadContextClassLoader();
94              if (classLoader != null) {
95                  LOGGER.trace("Trying to find [{}] using context class loader {}.", resource, classLoader);
96                  final URL url = classLoader.getResource(resource);
97                  if (url != null) {
98                      return url;
99                  }
100             }
101 
102             // We could not find resource. Let us now try with the classloader that loaded this class.
103             classLoader = Loader.class.getClassLoader();
104             if (classLoader != null) {
105                 LOGGER.trace("Trying to find [{}] using {} class loader.", resource, classLoader);
106                 final URL url = classLoader.getResource(resource);
107                 if (url != null) {
108                     return url;
109                 }
110             }
111             // We could not find resource. Finally try with the default ClassLoader.
112             if (defaultLoader != null) {
113                 LOGGER.trace("Trying to find [{}] using {} class loader.", resource, defaultLoader);
114                 final URL url = defaultLoader.getResource(resource);
115                 if (url != null) {
116                     return url;
117                 }
118             }
119         } catch (final Throwable t) {
120             //
121             //  can't be InterruptedException or InterruptedIOException
122             //    since not declared, must be error or RuntimeError.
123             LOGGER.warn(TSTR, t);
124         }
125 
126         // Last ditch attempt: get the resource from the class path. It
127         // may be the case that clazz was loaded by the Extension class
128         // loader which the parent of the system class loader. Hence the
129         // code below.
130         LOGGER.trace("Trying to find [{}] using ClassLoader.getSystemResource().", resource);
131         return ClassLoader.getSystemResource(resource);
132     }
133 
134     /**
135      * This method will search for {@code resource} in different
136      * places. The search order is as follows:
137      *
138      * <ol>
139      * <li>Search for {@code resource} using the thread context
140      * class loader under Java2. If that fails, search for
141      * {@code resource} using the class loader that loaded this
142      * class ({@code Loader}). Under JDK 1.1, only the the class
143      * loader that loaded this class ({@code Loader}) is used.</li>
144      * <li>Try one last time with
145      * {@code ClassLoader.getSystemResource(resource)}, that is is
146      * using the system class loader in JDK 1.2 and virtual machine's
147      * built-in class loader in JDK 1.1.</li>
148      * </ol>
149      * @param resource The resource to load.
150      * @param defaultLoader The default ClassLoader.
151      * @return An InputStream to read the resouce.
152      */
153     public static InputStream getResourceAsStream(final String resource, final ClassLoader defaultLoader) {
154         try {
155             ClassLoader classLoader = getThreadContextClassLoader();
156             InputStream is;
157             if (classLoader != null) {
158                 LOGGER.trace("Trying to find [{}] using context class loader {}.", resource, classLoader);
159                 is = classLoader.getResourceAsStream(resource);
160                 if (is != null) {
161                     return is;
162                 }
163             }
164 
165             // We could not find resource. Let us now try with the classloader that loaded this class.
166             classLoader = Loader.class.getClassLoader();
167             if (classLoader != null) {
168                 LOGGER.trace("Trying to find [{}] using {} class loader.", resource, classLoader);
169                 is = classLoader.getResourceAsStream(resource);
170                 if (is != null) {
171                     return is;
172                 }
173             }
174 
175             // We could not find resource. Finally try with the default ClassLoader.
176             if (defaultLoader != null) {
177                 LOGGER.trace("Trying to find [{}] using {} class loader.", resource, defaultLoader);
178                 is = defaultLoader.getResourceAsStream(resource);
179                 if (is != null) {
180                     return is;
181                 }
182             }
183         } catch (final Throwable t) {
184             //
185             //  can't be InterruptedException or InterruptedIOException
186             //    since not declared, must be error or RuntimeError.
187             LOGGER.warn(TSTR, t);
188         }
189 
190         // Last ditch attempt: get the resource from the class path. It
191         // may be the case that clazz was loaded by the Extension class
192         // loader which the parent of the system class loader. Hence the
193         // code below.
194         LOGGER.trace("Trying to find [{}] using ClassLoader.getSystemResource().", resource);
195         return ClassLoader.getSystemResourceAsStream(resource);
196     }
197 
198     /**
199      * Determines if one ClassLoader is a child of another ClassLoader. Note that a {@code null} ClassLoader is
200      * interpreted as the system ClassLoader as per convention.
201      *
202      * @param loader1 the ClassLoader to check for childhood.
203      * @param loader2 the ClassLoader to check for parenthood.
204      * @return {@code true} if the first ClassLoader is a strict descendant of the second ClassLoader.
205      */
206     private static boolean isChild(final ClassLoader loader1, final ClassLoader loader2) {
207         if (loader1 != null && loader2 != null) {
208             ClassLoader parent = loader1.getParent();
209             while (parent != null && parent != loader2) {
210                 parent = parent.getParent();
211             }
212             // once parent is null, we're at the system CL, which would indicate they have separate ancestry
213             return parent != null;
214         }
215         return loader1 != null;
216     }
217 
218     /**
219      * Loads and initializes a named Class using a given ClassLoader.
220      *
221      * @param className The class name.
222      * @param loader The class loader.
223      * @return The class.
224      * @throws ClassNotFoundException if the class could not be found.
225      */
226     public static Class<?> initializeClass(final String className, final ClassLoader loader)
227             throws ClassNotFoundException {
228         return Class.forName(className, true, loader);
229     }
230 
231     /**
232      * Loads a named Class using a given ClassLoader.
233      *
234      * @param className The class name.
235      * @param loader The class loader.
236      * @return The class, or null if loader is null.
237      * @throws ClassNotFoundException if the class could not be found.
238      */
239     public static Class<?> loadClass(final String className, final ClassLoader loader)
240             throws ClassNotFoundException {
241         return loader != null ? loader.loadClass(className) : null;
242     }
243 
244     /**
245      * Load a Class in the {@code java.*} namespace by name. Useful for peculiar scenarios typically involving
246      * Google App Engine.
247      *
248      * @param className The class name.
249      * @return The Class.
250      * @throws ClassNotFoundException if the Class could not be found.
251      */
252     public static Class<?> loadSystemClass(final String className) throws ClassNotFoundException {
253         try {
254             return Class.forName(className, true, ClassLoader.getSystemClassLoader());
255         } catch (final Throwable t) {
256             LOGGER.trace("Couldn't use SystemClassLoader. Trying Class.forName({}).", className, t);
257             return Class.forName(className);
258         }
259     }
260 
261     /**
262      * Loads and instantiates a Class using the default constructor.
263      *
264      * @param className The class name.
265      * @return new instance of the class.
266      * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders
267      * @throws IllegalAccessException if the class can't be instantiated through a public constructor
268      * @throws InstantiationException if there was an exception whilst instantiating the class
269      * @throws NoSuchMethodException if there isn't a no-args constructor on the class
270      * @throws InvocationTargetException if there was an exception whilst constructing the class
271      */
272     @SuppressWarnings("unchecked")
273     public static <T> T newInstanceOf(final String className)
274         throws ClassNotFoundException,
275         IllegalAccessException,
276         InstantiationException,
277         NoSuchMethodException,
278         InvocationTargetException {
279         final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
280         try {
281             Thread.currentThread().setContextClassLoader(getClassLoader());
282             return LoaderUtil.newInstanceOf(className);
283         } finally {
284             Thread.currentThread().setContextClassLoader(contextClassLoader);
285         }
286     }
287 
288     /**
289      * Loads, instantiates, and casts a Class using the default constructor.
290      *
291      * @param className The class name.
292      * @param clazz The class to cast it to.
293      * @param <T> The type to cast it to.
294      * @return new instance of the class cast to {@code T}
295      * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders
296      * @throws IllegalAccessException if the class can't be instantiated through a public constructor
297      * @throws InstantiationException if there was an exception whilst instantiating the class
298      * @throws NoSuchMethodException if there isn't a no-args constructor on the class
299      * @throws InvocationTargetException if there was an exception whilst constructing the class
300      * @throws ClassCastException if the constructed object isn't type compatible with {@code T}
301      */
302     public static <T> T newCheckedInstanceOf(final String className, final Class<T> clazz)
303         throws ClassNotFoundException,
304         NoSuchMethodException,
305         IllegalAccessException,
306         InvocationTargetException,
307         InstantiationException {
308         final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
309         try {
310             Thread.currentThread().setContextClassLoader(getClassLoader());
311             return LoaderUtil.newCheckedInstanceOf(className, clazz);
312         } finally {
313             Thread.currentThread().setContextClassLoader(contextClassLoader);
314         }
315     }
316 
317     /**
318      * Loads and instantiates a class given by a property name.
319      *
320      * @param propertyName The property name to look up a class name for.
321      * @param clazz        The class to cast it to.
322      * @param <T>          The type to cast it to.
323      * @return new instance of the class given in the property or {@code null} if the property was unset.
324      * @throws ClassNotFoundException    if the class isn't available to the usual ClassLoaders
325      * @throws IllegalAccessException    if the class can't be instantiated through a public constructor
326      * @throws InstantiationException    if there was an exception whilst instantiating the class
327      * @throws NoSuchMethodException     if there isn't a no-args constructor on the class
328      * @throws InvocationTargetException if there was an exception whilst constructing the class
329      * @throws ClassCastException        if the constructed object isn't type compatible with {@code T}
330      */
331     public static <T> T newCheckedInstanceOfProperty(final String propertyName, final Class<T> clazz)
332         throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException,
333         IllegalAccessException {
334         final String className = PropertiesUtil.getProperties().getStringProperty(propertyName);
335         final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
336         try {
337             Thread.currentThread().setContextClassLoader(getClassLoader());
338             return LoaderUtil.newCheckedInstanceOfProperty(propertyName, clazz);
339         } finally {
340             Thread.currentThread().setContextClassLoader(contextClassLoader);
341         }
342     }
343 
344     /**
345      * Determines if a named Class can be loaded or not.
346      *
347      * @param className The class name.
348      * @return {@code true} if the class could be found or {@code false} otherwise.
349      */
350     public static boolean isClassAvailable(final String className) {
351         final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
352         try {
353             Thread.currentThread().setContextClassLoader(getClassLoader());
354             return LoaderUtil.isClassAvailable(className);
355         } finally {
356             Thread.currentThread().setContextClassLoader(contextClassLoader);
357         }
358     }
359 
360     public static boolean isJansiAvailable() {
361         return isClassAvailable("org.fusesource.jansi.AnsiRenderer");
362     }
363 
364     /**
365      * Loads a class by name. This method respects the {@link #IGNORE_TCCL_PROPERTY} Log4j property. If this property is
366      * specified and set to anything besides {@code false}, then the default ClassLoader will be used.
367      *
368      * @param className The class name.
369      * @return the Class for the given name.
370      * @throws ClassNotFoundException if the specified class name could not be found
371      */
372     public static Class<?> loadClass(final String className) throws ClassNotFoundException {
373 
374         final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
375         try {
376             Thread.currentThread().setContextClassLoader(getClassLoader());
377             return LoaderUtil.loadClass(className);
378         } finally {
379             Thread.currentThread().setContextClassLoader(contextClassLoader);
380         }
381     }
382 }