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