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  
20  import java.io.InputStream;
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.ReflectPermission;
23  import java.net.URL;
24  import java.security.AccessController;
25  import java.security.PrivilegedAction;
26  
27  import org.apache.logging.log4j.Logger;
28  import org.apache.logging.log4j.status.StatusLogger;
29  import org.apache.logging.log4j.util.PropertiesUtil;
30  
31  /**
32   * Load resources (or images) from various sources.
33   */
34  public final class Loader {
35  
36      private static boolean ignoreTCL = false;
37  
38      private static final Logger LOGGER = StatusLogger.getLogger();
39  
40      private static final String TSTR = "Caught Exception while in Loader.getResource. This may be innocuous.";
41  
42      private static final PrivilegedAction<ClassLoader> THREAD_CONTEXT_CLASS_LOADER_GETTER =
43          new ThreadContextClassLoaderGetter();
44  
45      static {
46          final String ignoreTCLProp = PropertiesUtil.getProperties().getStringProperty("log4j.ignoreTCL", null);
47          if (ignoreTCLProp != null) {
48              ignoreTCL = OptionConverter.toBoolean(ignoreTCLProp, true);
49          }
50          final SecurityManager sm = System.getSecurityManager();
51          if (sm != null) {
52              sm.checkPermission(new RuntimePermission("getClassLoader"));
53              sm.checkPermission(new RuntimePermission("getStackTrace"));
54              sm.checkPermission(new ReflectPermission("suppressAccessChecks"));
55          }
56      }
57  
58      /**
59       * Returns the ClassLoader to use.
60       * @return the ClassLoader.
61       */
62      public static ClassLoader getClassLoader() {
63  
64          return getClassLoader(Loader.class, null);
65      }
66  
67      /**
68       * Returns the ClassLoader of current thread if possible, or falls back to the system ClassLoader if none is
69       * available.
70       *
71       * @return the TCCL.
72       */
73      public static ClassLoader getThreadContextClassLoader() {
74          return getTcl();
75      }
76  
77      // TODO: this method could use some explanation
78      public static ClassLoader getClassLoader(final Class<?> class1, final Class<?> class2) {
79  
80          ClassLoader threadContextClassLoader = null;
81          try {
82              threadContextClassLoader = getTcl();
83          } catch (final Exception ex) {
84              LOGGER.warn("Caught exception locating thread ClassLoader {}", ex.getMessage());
85          }
86          final ClassLoader loader1 = class1 == null ? null : class1.getClassLoader();
87          final ClassLoader loader2 = class2 == null ? null : class2.getClassLoader();
88  
89          if (isChild(threadContextClassLoader, loader1)) {
90              return isChild(threadContextClassLoader, loader2) ? threadContextClassLoader : loader2;
91          }
92          return isChild(loader1, loader2) ? loader1 : loader2;
93      }
94  
95      /**
96       * This method will search for {@code resource} in different
97       * places. The search order is as follows:
98       * <p/>
99       * <ol>
100      * <p/>
101      * <p><li>Search for {@code resource} using the thread context
102      * class loader under Java2. If that fails, search for
103      * {@code resource} using the class loader that loaded this
104      * class ({@code Loader}). Under JDK 1.1, only the the class
105      * loader that loaded this class ({@code Loader}) is used.
106      * <p/>
107      * <p><li>Try one last time with
108      * {@code ClassLoader.getSystemResource(resource)}, that is is
109      * using the system class loader in JDK 1.2 and virtual machine's
110      * built-in class loader in JDK 1.1.
111      * <p/>
112      * </ol>
113      * @param resource The resource to load.
114      * @param defaultLoader The default ClassLoader.
115      * @return A URL to the resource.
116      */
117     public static URL getResource(final String resource, final ClassLoader defaultLoader) {
118         try {
119             ClassLoader classLoader = getTcl();
120             if (classLoader != null) {
121                 LOGGER.trace("Trying to find [{}] using context class loader {}.", resource, classLoader);
122                 final URL url = classLoader.getResource(resource);
123                 if (url != null) {
124                     return url;
125                 }
126             }
127 
128             // We could not find resource. Let us now try with the classloader that loaded this class.
129             classLoader = Loader.class.getClassLoader();
130             if (classLoader != null) {
131                 LOGGER.trace("Trying to find [{}] using {} class loader.", resource, classLoader);
132                 final URL url = classLoader.getResource(resource);
133                 if (url != null) {
134                     return url;
135                 }
136             }
137             // We could not find resource. Finally try with the default ClassLoader.
138             if (defaultLoader != null) {
139                 LOGGER.trace("Trying to find [{}] using {} class loader.", resource, defaultLoader);
140                 final URL url = defaultLoader.getResource(resource);
141                 if (url != null) {
142                     return url;
143                 }
144             }
145         } catch (final Throwable t) {
146             //
147             //  can't be InterruptedException or InterruptedIOException
148             //    since not declared, must be error or RuntimeError.
149             LOGGER.warn(TSTR, t);
150         }
151 
152         // Last ditch attempt: get the resource from the class path. It
153         // may be the case that clazz was loaded by the Extension class
154         // loader which the parent of the system class loader. Hence the
155         // code below.
156         LOGGER.trace("Trying to find [{}] using ClassLoader.getSystemResource().", resource);
157         return ClassLoader.getSystemResource(resource);
158     }
159 
160     /**
161      * This method will search for {@code resource} in different
162      * places. The search order is as follows:
163      * <p/>
164      * <ol>
165      * <p/>
166      * <p><li>Search for {@code resource} using the thread context
167      * class loader under Java2. If that fails, search for
168      * {@code resource} using the class loader that loaded this
169      * class ({@code Loader}). Under JDK 1.1, only the the class
170      * loader that loaded this class ({@code Loader}) is used.
171      * <p/>
172      * <p><li>Try one last time with
173      * {@code ClassLoader.getSystemResource(resource)}, that is is
174      * using the system class loader in JDK 1.2 and virtual machine's
175      * built-in class loader in JDK 1.1.
176      * <p/>
177      * </ol>
178      * @param resource The resource to load.
179      * @param defaultLoader The default ClassLoader.
180      * @return An InputStream to read the resouce.
181      */
182     public static InputStream getResourceAsStream(final String resource, final ClassLoader defaultLoader) {
183         try {
184             ClassLoader classLoader = getTcl();
185             InputStream is;
186             if (classLoader != null) {
187                 LOGGER.trace("Trying to find [{}] using context class loader {}.", resource, classLoader);
188                 is = classLoader.getResourceAsStream(resource);
189                 if (is != null) {
190                     return is;
191                 }
192             }
193 
194             // We could not find resource. Let us now try with the classloader that loaded this class.
195             classLoader = Loader.class.getClassLoader();
196             if (classLoader != null) {
197                 LOGGER.trace("Trying to find [{}] using {} class loader.", resource, classLoader);
198                 is = classLoader.getResourceAsStream(resource);
199                 if (is != null) {
200                     return is;
201                 }
202             }
203 
204             // We could not find resource. Finally try with the default ClassLoader.
205             if (defaultLoader != null) {
206                 LOGGER.trace("Trying to find [{}] using {} class loader.", resource, defaultLoader);
207                 is = defaultLoader.getResourceAsStream(resource);
208                 if (is != null) {
209                     return is;
210                 }
211             }
212         } catch (final Throwable t) {
213             //
214             //  can't be InterruptedException or InterruptedIOException
215             //    since not declared, must be error or RuntimeError.
216             LOGGER.warn(TSTR, t);
217         }
218 
219         // Last ditch attempt: get the resource from the class path. It
220         // may be the case that clazz was loaded by the Extension class
221         // loader which the parent of the system class loader. Hence the
222         // code below.
223         LOGGER.trace("Trying to find [{}] using ClassLoader.getSystemResource().", resource);
224         return ClassLoader.getSystemResourceAsStream(resource);
225     }
226 
227     private static ClassLoader getTcl() {
228         return System.getSecurityManager() == null
229             ? THREAD_CONTEXT_CLASS_LOADER_GETTER.run()
230             : AccessController.doPrivileged(THREAD_CONTEXT_CLASS_LOADER_GETTER);
231     }
232 
233     private static class ThreadContextClassLoaderGetter implements PrivilegedAction<ClassLoader> {
234         @Override
235         public ClassLoader run() {
236             final ClassLoader cl = Thread.currentThread().getContextClassLoader();
237             // if the TCCL is null, that means we're using the system CL
238             return cl == null ? ClassLoader.getSystemClassLoader() : cl;
239         }
240     }
241 
242     /**
243      * Determines if one ClassLoader is a child of another ClassLoader. Note that a {@code null} ClassLoader is
244      * interpreted as the system ClassLoader as per convention.
245      *
246      * @param loader1 the ClassLoader to check for childhood.
247      * @param loader2 the ClassLoader to check for parenthood.
248      * @return {@code true} if the first ClassLoader is a strict descendant of the second ClassLoader.
249      */
250     private static boolean isChild(final ClassLoader loader1, final ClassLoader loader2) {
251         if (loader1 != null && loader2 != null) {
252             ClassLoader parent = loader1.getParent();
253             while (parent != null && parent != loader2) {
254                 parent = parent.getParent();
255             }
256             // once parent is null, we're at the system CL, which would indicate they have separate ancestry
257             return parent != null;
258         }
259         return loader1 != null;
260     }
261 
262     /**
263      * Load a Class by name. Note that unlike {@link ClassLoader#loadClass(String) ClassLoader.loadClass}, this method
264      * will initialize the class as well if it hasn't been already. This is equivalent to the calling the
265      * {@link ClassLoader#loadClass(String, boolean) protected version} with the second parameter equal to {@code true}.
266      *
267      * @param className The class name.
268      * @return The Class.
269      * @throws ClassNotFoundException if the Class could not be found.
270      */
271     public static Class<?> loadClass(final String className) throws ClassNotFoundException {
272         // Just call Class.forName(className) if we are instructed to ignore the TCL.
273         if (ignoreTCL) {
274             LOGGER.trace("Ignoring TCCL. Trying Class.forName({}).", className);
275             return loadClassWithDefaultClassLoader(className);
276         }
277         try {
278             LOGGER.trace("Trying TCCL for class {}.", className);
279             // using the TCCL should work the same as the default ClassLoader (i.e., init or not)
280             return Class.forName(className, true, getTcl());
281         } catch (final Throwable e) {
282             LOGGER.trace("TCCL didn't work for class {}: {}.", className, e.toString());
283             return loadClassWithDefaultClassLoader(className);
284         }
285     }
286 
287     private static Class<?> loadClassWithDefaultClassLoader(final String className) throws ClassNotFoundException {
288         return Class.forName(className);
289     }
290 
291     /**
292      * Loads and initializes a named Class using a given ClassLoader.
293      *
294      * @param className The class name.
295      * @param loader The class loader.
296      * @return The class.
297      * @throws ClassNotFoundException if the class could not be found.
298      */
299     public static Class<?> initializeClass(final String className, final ClassLoader loader)
300             throws ClassNotFoundException {
301         return Class.forName(className, true, loader);
302     }
303 
304     /**
305      * Load a Class in the {@code java.*} namespace by name. Useful for peculiar scenarios typically involving
306      * Google App Engine.
307      *
308      * @param className The class name.
309      * @return The Class.
310      * @throws ClassNotFoundException if the Class could not be found.
311      */
312     public static Class<?> loadSystemClass(final String className) throws ClassNotFoundException {
313         try {
314             return Class.forName(className, true, ClassLoader.getSystemClassLoader());
315         } catch (final Throwable t) {
316             LOGGER.trace("Couldn't use SystemClassLoader. Trying Class.forName({}).", className, t);
317             return Class.forName(className);
318         }
319     }
320 
321     /**
322      * Loads and instantiates a Class using the default constructor.
323      *
324      * @param className The class name.
325      * @return new instance of the class.
326      * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders
327      * @throws IllegalAccessException if the class can't be instantiated through a public constructor
328      * @throws InstantiationException if there was an exception whilst instantiating the class
329      * @throws NoSuchMethodException if there isn't a no-args constructor on the class
330      * @throws InvocationTargetException if there was an exception whilst constructing the class
331      */
332     public static Object newInstanceOf(final String className)
333             throws ClassNotFoundException,
334                    IllegalAccessException,
335                    InstantiationException,
336                    NoSuchMethodException,
337                    InvocationTargetException {
338         final Class<?> clazz = loadClass(className);
339         try {
340             return clazz.getConstructor().newInstance();
341         } catch (final NoSuchMethodException e) {
342             // try the default-default constructor
343             //noinspection ClassNewInstance
344             return clazz.newInstance();
345         }
346     }
347 
348     /**
349      * Loads, instantiates, and casts a Class using the default constructor.
350      *
351      * @param className The class name.
352      * @param clazz The class to cast it to.
353      * @param <T> The type to cast it to.
354      * @return new instance of the class cast to {@code T}
355      * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders
356      * @throws IllegalAccessException if the class can't be instantiated through a public constructor
357      * @throws InstantiationException if there was an exception whilst instantiating the class
358      * @throws NoSuchMethodException if there isn't a no-args constructor on the class
359      * @throws InvocationTargetException if there was an exception whilst constructing the class
360      * @throws ClassCastException if the constructed object isn't type compatible with {@code T}
361      */
362     public static <T> T newCheckedInstanceOf(final String className, final Class<T> clazz)
363             throws ClassNotFoundException,
364                    NoSuchMethodException,
365                    IllegalAccessException,
366                    InvocationTargetException,
367                    InstantiationException {
368         return clazz.cast(newInstanceOf(className));
369     }
370 
371     /**
372      * Determines if a named Class can be loaded or not.
373      *
374      * @param className The class name.
375      * @return {@code true} if the class could be found or {@code false} otherwise.
376      */
377     public static boolean isClassAvailable(final String className) {
378         try {
379             final Class<?> clazz = loadClass(className);
380             return clazz != null;
381         } catch (final ClassNotFoundException e) {
382             return false;
383         } catch (final Throwable e) {
384             LOGGER.trace("Unknown error checking for existence of class [{}].", className, e);
385             return false;
386         }
387     }
388 
389     private Loader() {
390     }
391 }