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.web;
018
019import java.util.AbstractMap;
020import java.util.Map;
021import java.util.concurrent.locks.Lock;
022import java.util.concurrent.locks.ReentrantLock;
023import jakarta.servlet.ServletContext;
024
025import org.apache.logging.log4j.LogManager;
026import org.apache.logging.log4j.core.LoggerContext;
027import org.apache.logging.log4j.core.impl.ContextAnchor;
028
029/**
030 * Convenience methods for retrieving the {@link org.apache.logging.log4j.core.LoggerContext} associated with a
031 * particular ServletContext. These methods are most particularly useful for asynchronous servlets where the
032 * Thread Context ClassLoader (TCCL) is potentially different from the TCCL used by the
033 * Servlet container that bootstrapped Log4j.
034 *
035 * @since 2.0.1
036 */
037public final class WebLoggerContextUtils {
038    private WebLoggerContextUtils() {
039    }
040
041    private static final Lock WEB_SUPPORT_LOOKUP = new ReentrantLock();
042    private static final String SERVLET_CONTEXT = "__SERVLET_CONTEXT__";
043
044    /**
045     * Finds the main {@link org.apache.logging.log4j.core.LoggerContext} configured for the given ServletContext.
046     *
047     * @param servletContext the ServletContext to locate a LoggerContext for
048     * @return the LoggerContext for the given ServletContext
049     * @since 2.0.1
050     */
051    public static LoggerContext getWebLoggerContext(final ServletContext servletContext) {
052        return (LoggerContext) servletContext.getAttribute(Log4jWebSupport.CONTEXT_ATTRIBUTE);
053    }
054
055    /**
056     * Finds the main {@link org.apache.logging.log4j.core.LoggerContext} configured for the given ServletContext.
057     *
058     * @param servletContext the ServletContext to locate a LoggerContext for
059     * @return the LoggerContext for the given ServletContext or {@code null} if none was set
060     * @throws java.lang.IllegalStateException if no LoggerContext could be found on the given ServletContext
061     * @since 2.0.1
062     */
063    public static LoggerContext getRequiredWebLoggerContext(final ServletContext servletContext) {
064        final LoggerContext loggerContext = getWebLoggerContext(servletContext);
065        if (loggerContext == null) {
066            throw new IllegalStateException(
067                "No LoggerContext found in ServletContext attribute " + Log4jWebSupport.CONTEXT_ATTRIBUTE);
068        }
069        return loggerContext;
070    }
071
072    /**
073     * Finds or initializes the {@link org.apache.logging.log4j.web.Log4jWebLifeCycle} singleton for the given
074     * ServletContext.
075     *
076     * @param servletContext the ServletContext to get the Log4jWebLifeCycle for
077     * @return the Log4jWebLifeCycle for the given ServletContext
078     * @since 2.0.1
079     */
080    public static Log4jWebLifeCycle getWebLifeCycle(final ServletContext servletContext) {
081        WEB_SUPPORT_LOOKUP.lock();
082        try {
083            Log4jWebLifeCycle webLifeCycle = (Log4jWebLifeCycle) servletContext.getAttribute(
084                Log4jWebSupport.SUPPORT_ATTRIBUTE);
085            if (webLifeCycle == null) {
086                webLifeCycle = Log4jWebInitializerImpl.initialize(servletContext);
087            }
088            return webLifeCycle;
089        } finally {
090            WEB_SUPPORT_LOOKUP.unlock();
091        }
092    }
093
094    /**
095     * Wraps a Runnable instance by setting its thread context {@link org.apache.logging.log4j.core.LoggerContext}
096     * before execution and clearing it after execution.
097     *
098     * @param servletContext the ServletContext to locate a LoggerContext for
099     * @param runnable       the Runnable to wrap execution for
100     * @return a wrapped Runnable
101     * @since 2.0.1
102     */
103    public static Runnable wrapExecutionContext(final ServletContext servletContext, final Runnable runnable) {
104        return () -> {
105            final Log4jWebSupport webSupport = getWebLifeCycle(servletContext);
106            webSupport.setLoggerContext();
107            try {
108                runnable.run();
109            } finally {
110                webSupport.clearLoggerContext();
111            }
112        };
113    }
114
115    public static Map.Entry<String, Object> createExternalEntry(ServletContext servletContext) {
116        return new AbstractMap.SimpleEntry<>(SERVLET_CONTEXT, servletContext);
117    }
118
119    public static void setServletContext(LoggerContext lc, ServletContext servletContext) {
120        if (lc != null) {
121            lc.putObject(SERVLET_CONTEXT, servletContext);
122        }
123    }
124
125    /**
126     * Gets the current {@link ServletContext} if it has already been assigned to a LoggerContext's external context.
127     *
128     * @return the current ServletContext attached to a LoggerContext or {@code null} if none could be found
129     * @since 2.1
130     */
131    public static ServletContext getServletContext() {
132        org.apache.logging.log4j.spi.LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
133        if (lc == null) {
134            lc = LogManager.getContext(false);
135        }
136
137        Object obj = lc != null ? lc.getObject(SERVLET_CONTEXT) : null;
138        if (obj instanceof ServletContext) {
139            return (ServletContext) obj;
140        }
141        return null;
142    }
143}