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 */
017package org.apache.logging.log4j.core.util;
018
019import java.io.InputStream;
020import java.lang.reflect.InvocationTargetException;
021import java.net.URL;
022
023import org.apache.logging.log4j.Logger;
024import org.apache.logging.log4j.status.StatusLogger;
025import org.apache.logging.log4j.util.LoaderUtil;
026import org.apache.logging.log4j.util.PropertiesUtil;
027
028/**
029 * Load resources (or images) from various sources.
030 */
031public final class Loader {
032
033    private static final Logger LOGGER = StatusLogger.getLogger();
034
035    private static final String TSTR = "Caught Exception while in Loader.getResource. This may be innocuous.";
036
037    private Loader() {
038    }
039
040    /**
041     * Returns the ClassLoader to use.
042     * @return the ClassLoader.
043     */
044    public static ClassLoader getClassLoader() {
045        return getClassLoader(Loader.class, null);
046    }
047
048    /**
049     * Returns the ClassLoader of current thread if possible, or falls back to the system ClassLoader if none is
050     * available.
051     *
052     * @return the TCCL.
053     * @see org.apache.logging.log4j.util.LoaderUtil#getThreadContextClassLoader()
054     */
055    public static ClassLoader getThreadContextClassLoader() {
056        return LoaderUtil.getThreadContextClassLoader();
057    }
058
059    // TODO: this method could use some explanation
060    public static ClassLoader getClassLoader(final Class<?> class1, final Class<?> class2) {
061        final ClassLoader threadContextClassLoader = getThreadContextClassLoader();
062        final ClassLoader loader1 = class1 == null ? null : class1.getClassLoader();
063        final ClassLoader loader2 = class2 == null ? null : class2.getClassLoader();
064
065        if (isChild(threadContextClassLoader, loader1)) {
066            return isChild(threadContextClassLoader, loader2) ? threadContextClassLoader : loader2;
067        }
068        return isChild(loader1, loader2) ? loader1 : loader2;
069    }
070
071    /**
072     * This method will search for {@code resource} in different
073     * places. The search order is as follows:
074     *
075     * <ol>
076     *
077     * <li>Search for {@code resource} using the thread context
078     * class loader under Java2. If that fails, search for
079     * {@code resource} using the class loader that loaded this
080     * class ({@code Loader}). Under JDK 1.1, only the the class
081     * loader that loaded this class ({@code Loader}) is used.</li>
082     * <li>Try one last time with
083     * {@code ClassLoader.getSystemResource(resource)}, that is is
084     * using the system class loader in JDK 1.2 and virtual machine's
085     * built-in class loader in JDK 1.1.</li>
086     * </ol>
087     * @param resource The resource to load.
088     * @param defaultLoader The default ClassLoader.
089     * @return A URL to the resource.
090     */
091    public static URL getResource(final String resource, final ClassLoader defaultLoader) {
092        try {
093            ClassLoader classLoader = getThreadContextClassLoader();
094            if (classLoader != null) {
095                LOGGER.trace("Trying to find [{}] using context class loader {}.", resource, classLoader);
096                final URL url = classLoader.getResource(resource);
097                if (url != null) {
098                    return url;
099                }
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}