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    import java.security.AccessController;
025    import java.security.PrivilegedAction;
026    
027    import org.apache.logging.log4j.Logger;
028    import org.apache.logging.log4j.status.StatusLogger;
029    import org.apache.logging.log4j.util.PropertiesUtil;
030    
031    /**
032     * Load resources (or images) from various sources.
033     */
034    public final class Loader {
035    
036        private static boolean ignoreTCL = false;
037    
038        private static final Logger LOGGER = StatusLogger.getLogger();
039    
040        private static final String TSTR = "Caught Exception while in Loader.getResource. This may be innocuous.";
041    
042        private static final PrivilegedAction<ClassLoader> THREAD_CONTEXT_CLASS_LOADER_GETTER =
043            new ThreadContextClassLoaderGetter();
044    
045        static {
046            final String ignoreTCLProp = PropertiesUtil.getProperties().getStringProperty("log4j.ignoreTCL", null);
047            if (ignoreTCLProp != null) {
048                ignoreTCL = OptionConverter.toBoolean(ignoreTCLProp, true);
049            }
050            final SecurityManager sm = System.getSecurityManager();
051            if (sm != null) {
052                sm.checkPermission(new RuntimePermission("getClassLoader"));
053                sm.checkPermission(new RuntimePermission("getStackTrace"));
054                sm.checkPermission(new ReflectPermission("suppressAccessChecks"));
055            }
056        }
057    
058        /**
059         * Returns the ClassLoader to use.
060         * @return the ClassLoader.
061         */
062        public static ClassLoader getClassLoader() {
063    
064            return getClassLoader(Loader.class, null);
065        }
066    
067        /**
068         * Returns the ClassLoader of current thread if possible, or falls back to the system ClassLoader if none is
069         * available.
070         *
071         * @return the TCCL.
072         */
073        public static ClassLoader getThreadContextClassLoader() {
074            return getTcl();
075        }
076    
077        // TODO: this method could use some explanation
078        public static ClassLoader getClassLoader(final Class<?> class1, final Class<?> class2) {
079    
080            ClassLoader threadContextClassLoader = null;
081            try {
082                threadContextClassLoader = getTcl();
083            } catch (final Exception ex) {
084                LOGGER.warn("Caught exception locating thread ClassLoader {}", ex.getMessage());
085            }
086            final ClassLoader loader1 = class1 == null ? null : class1.getClassLoader();
087            final ClassLoader loader2 = class2 == null ? null : class2.getClassLoader();
088    
089            if (isChild(threadContextClassLoader, loader1)) {
090                return isChild(threadContextClassLoader, loader2) ? threadContextClassLoader : loader2;
091            }
092            return isChild(loader1, loader2) ? loader1 : loader2;
093        }
094    
095        /**
096         * This method will search for {@code resource} in different
097         * places. The search order is as follows:
098         * <p/>
099         * <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    }