001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements. See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache license, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License. You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the license for the specific language governing permissions and
015     * limitations under the license.
016     */
017    package org.apache.logging.log4j.core.util;
018    
019    
020    import java.io.InputStream;
021    import java.lang.reflect.InvocationTargetException;
022    import java.lang.reflect.ReflectPermission;
023    import java.net.URL;
024    
025    import org.apache.logging.log4j.Logger;
026    import org.apache.logging.log4j.status.StatusLogger;
027    import org.apache.logging.log4j.util.LoaderUtil;
028    import org.apache.logging.log4j.util.PropertiesUtil;
029    
030    /**
031     * Load resources (or images) from various sources.
032     */
033    public final class Loader {
034    
035        private static boolean ignoreTCL = false;
036    
037        private static final Logger LOGGER = StatusLogger.getLogger();
038    
039        private static final String TSTR = "Caught Exception while in Loader.getResource. This may be innocuous.";
040    
041        static {
042            final String ignoreTCLProp = PropertiesUtil.getProperties().getStringProperty("log4j.ignoreTCL", null);
043            if (ignoreTCLProp != null) {
044                ignoreTCL = OptionConverter.toBoolean(ignoreTCLProp, true);
045            }
046            final SecurityManager sm = System.getSecurityManager();
047            if (sm != null) {
048                sm.checkPermission(new RuntimePermission("getStackTrace"));
049                sm.checkPermission(new ReflectPermission("suppressAccessChecks"));
050            }
051        }
052    
053        /**
054         * Returns the ClassLoader to use.
055         * @return the ClassLoader.
056         */
057        public static ClassLoader getClassLoader() {
058    
059            return getClassLoader(Loader.class, null);
060        }
061    
062        /**
063         * Returns the ClassLoader of current thread if possible, or falls back to the system ClassLoader if none is
064         * available.
065         *
066         * @return the TCCL.
067         */
068        public static ClassLoader getThreadContextClassLoader() {
069            return LoaderUtil.getThreadContextClassLoader();
070        }
071    
072        // TODO: this method could use some explanation
073        public static ClassLoader getClassLoader(final Class<?> class1, final Class<?> class2) {
074    
075            ClassLoader threadContextClassLoader = null;
076            try {
077                threadContextClassLoader = getTcl();
078            } catch (final Exception ex) {
079                LOGGER.warn("Caught exception locating thread ClassLoader {}", ex.getMessage());
080            }
081            final ClassLoader loader1 = class1 == null ? null : class1.getClassLoader();
082            final ClassLoader loader2 = class2 == null ? null : class2.getClassLoader();
083    
084            if (isChild(threadContextClassLoader, loader1)) {
085                return isChild(threadContextClassLoader, loader2) ? threadContextClassLoader : loader2;
086            }
087            return isChild(loader1, loader2) ? loader1 : loader2;
088        }
089    
090        /**
091         * This method will search for {@code resource} in different
092         * places. The search order is as follows:
093         * <p/>
094         * <ol>
095         * <p/>
096         * <p><li>Search for {@code resource} using the thread context
097         * class loader under Java2. If that fails, search for
098         * {@code resource} using the class loader that loaded this
099         * 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    }