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.impl;
018
019import java.net.URI;
020import java.util.Objects;
021
022import org.apache.logging.log4j.core.LifeCycle;
023import org.apache.logging.log4j.core.LoggerContext;
024import org.apache.logging.log4j.core.config.Configuration;
025import org.apache.logging.log4j.core.config.ConfigurationFactory;
026import org.apache.logging.log4j.core.config.ConfigurationSource;
027import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector;
028import org.apache.logging.log4j.core.selector.ContextSelector;
029import org.apache.logging.log4j.core.util.Cancellable;
030import org.apache.logging.log4j.core.util.Constants;
031import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
032import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
033import org.apache.logging.log4j.spi.LoggerContextFactory;
034import org.apache.logging.log4j.status.StatusLogger;
035import org.apache.logging.log4j.util.LoaderUtil;
036import org.apache.logging.log4j.util.PropertiesUtil;
037
038/**
039 * Factory to locate a ContextSelector and then load a LoggerContext.
040 */
041public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallbackRegistry {
042
043    private static final StatusLogger LOGGER = StatusLogger.getLogger();
044    private static final boolean SHUTDOWN_HOOK_ENABLED =
045        PropertiesUtil.getProperties().getBooleanProperty(ShutdownCallbackRegistry.SHUTDOWN_HOOK_ENABLED, true);
046
047    private final ContextSelector selector;
048    private final ShutdownCallbackRegistry shutdownCallbackRegistry;
049
050    /**
051     * Initializes the ContextSelector from system property {@link Constants#LOG4J_CONTEXT_SELECTOR}.
052     */
053    public Log4jContextFactory() {
054        this(createContextSelector(), createShutdownCallbackRegistry());
055    }
056
057    /**
058     * Initializes this factory's ContextSelector with the specified selector.
059     * @param selector the selector to use
060     */
061    public Log4jContextFactory(final ContextSelector selector) {
062        this(selector, createShutdownCallbackRegistry());
063    }
064
065    /**
066     * Constructs a Log4jContextFactory using the ContextSelector from {@link Constants#LOG4J_CONTEXT_SELECTOR}
067     * and the provided ShutdownRegistrationStrategy.
068     *
069     * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use
070     * @since 2.1
071     */
072    public Log4jContextFactory(final ShutdownCallbackRegistry shutdownCallbackRegistry) {
073        this(createContextSelector(), shutdownCallbackRegistry);
074    }
075
076    /**
077     * Constructs a Log4jContextFactory using the provided ContextSelector and ShutdownRegistrationStrategy.
078     *
079     * @param selector                     the selector to use
080     * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use
081     * @since 2.1
082     */
083    public Log4jContextFactory(final ContextSelector selector,
084                               final ShutdownCallbackRegistry shutdownCallbackRegistry) {
085        this.selector = Objects.requireNonNull(selector, "No ContextSelector provided");
086        this.shutdownCallbackRegistry = Objects.requireNonNull(shutdownCallbackRegistry, "No ShutdownCallbackRegistry provided");
087        LOGGER.debug("Using ShutdownCallbackRegistry {}", shutdownCallbackRegistry.getClass());
088        initializeShutdownCallbackRegistry();
089    }
090
091    private static ContextSelector createContextSelector() {
092        try {
093            final ContextSelector selector = LoaderUtil.newCheckedInstanceOfProperty(Constants.LOG4J_CONTEXT_SELECTOR,
094                ContextSelector.class);
095            if (selector != null) {
096                return selector;
097            }
098        } catch (final Exception e) {
099            LOGGER.error("Unable to create custom ContextSelector. Falling back to default.", e);
100        }
101        return new ClassLoaderContextSelector();
102    }
103
104    private static ShutdownCallbackRegistry createShutdownCallbackRegistry() {
105        try {
106            final ShutdownCallbackRegistry registry = LoaderUtil.newCheckedInstanceOfProperty(
107                ShutdownCallbackRegistry.SHUTDOWN_CALLBACK_REGISTRY, ShutdownCallbackRegistry.class
108            );
109            if (registry != null) {
110                return registry;
111            }
112        } catch (final Exception e) {
113            LOGGER.error("Unable to create custom ShutdownCallbackRegistry. Falling back to default.", e);
114        }
115        return new DefaultShutdownCallbackRegistry();
116    }
117
118    private void initializeShutdownCallbackRegistry() {
119        if (SHUTDOWN_HOOK_ENABLED && this.shutdownCallbackRegistry instanceof LifeCycle) {
120            try {
121                ((LifeCycle) this.shutdownCallbackRegistry).start();
122            } catch (final Exception e) {
123                LOGGER.error("There was an error starting the ShutdownCallbackRegistry.", e);
124            }
125        }
126    }
127
128    /**
129     * Loads the LoggerContext using the ContextSelector.
130     * @param fqcn The fully qualified class name of the caller.
131     * @param loader The ClassLoader to use or null.
132     * @param currentContext If true returns the current Context, if false returns the Context appropriate
133     * for the caller if a more appropriate Context can be determined.
134     * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext.
135     * @return The LoggerContext.
136     */
137    @Override
138    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
139                                    final boolean currentContext) {
140        final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext);
141        if (externalContext != null && ctx.getExternalContext() == null) {
142            ctx.setExternalContext(externalContext);
143        }
144        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
145            ctx.start();
146        }
147        return ctx;
148    }
149
150    /**
151     * Loads the LoggerContext using the ContextSelector.
152     * @param fqcn The fully qualified class name of the caller.
153     * @param loader The ClassLoader to use or null.
154     * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext.
155     * @param currentContext If true returns the current Context, if false returns the Context appropriate
156     * for the caller if a more appropriate Context can be determined.
157     * @param source The configuration source.
158     * @return The LoggerContext.
159     */
160    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
161                                    final boolean currentContext, final ConfigurationSource source) {
162        final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, null);
163        if (externalContext != null && ctx.getExternalContext() == null) {
164            ctx.setExternalContext(externalContext);
165        }
166        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
167            if (source != null) {
168                ContextAnchor.THREAD_CONTEXT.set(ctx);
169                final Configuration config = ConfigurationFactory.getInstance().getConfiguration(source);
170                LOGGER.debug("Starting LoggerContext[name={}] from configuration {}", ctx.getName(), source);
171                ctx.start(config);
172                ContextAnchor.THREAD_CONTEXT.remove();
173            } else {
174                ctx.start();
175            }
176        }
177        return ctx;
178    }
179
180    /**
181     * Loads the LoggerContext using the ContextSelector using the provided Configuration
182     * @param fqcn The fully qualified class name of the caller.
183     * @param loader The ClassLoader to use or null.
184     * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext.
185     * @param currentContext If true returns the current Context, if false returns the Context appropriate
186     * for the caller if a more appropriate Context can be determined.
187     * @param configuration The Configuration.
188     * @return The LoggerContext.
189     */
190    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
191            final boolean currentContext, final Configuration configuration) {
192        final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, null);
193        if (externalContext != null && ctx.getExternalContext() == null) {
194            ctx.setExternalContext(externalContext);
195        }
196        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
197            ContextAnchor.THREAD_CONTEXT.set(ctx);
198            try {
199                ctx.start(configuration);
200            } finally {
201                ContextAnchor.THREAD_CONTEXT.remove();
202            }
203        }
204        return ctx;
205    }
206
207    /**
208     * Loads the LoggerContext using the ContextSelector.
209     * @param fqcn The fully qualified class name of the caller.
210     * @param loader The ClassLoader to use or null.
211     * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext.
212     * @param currentContext If true returns the current Context, if false returns the Context appropriate
213     * for the caller if a more appropriate Context can be determined.
214     * @param configLocation The location of the configuration for the LoggerContext (or null).
215     * @return The LoggerContext.
216     */
217    @Override
218    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
219                                    final boolean currentContext, final URI configLocation, final String name) {
220        final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, configLocation);
221        if (externalContext != null && ctx.getExternalContext() == null) {
222            ctx.setExternalContext(externalContext);
223        }
224        if (name != null) {
225                ctx.setName(name);
226        }
227        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
228            if (configLocation != null || name != null) {
229                ContextAnchor.THREAD_CONTEXT.set(ctx);
230                final Configuration config = ConfigurationFactory.getInstance().getConfiguration(name, configLocation);
231                LOGGER.debug("Starting LoggerContext[name={}] from configuration at {}", ctx.getName(), configLocation);
232                ctx.start(config);
233                ContextAnchor.THREAD_CONTEXT.remove();
234            } else {
235                ctx.start();
236            }
237        }
238        return ctx;
239    }
240
241    /**
242     * Returns the ContextSelector.
243     * @return The ContextSelector.
244     */
245    public ContextSelector getSelector() {
246        return selector;
247    }
248
249        /**
250         * Returns the ShutdownCallbackRegistry
251         * 
252         * @return the ShutdownCallbackRegistry
253         * @since 2.4
254         */
255        public ShutdownCallbackRegistry getShutdownCallbackRegistry() {
256                return shutdownCallbackRegistry;
257        }
258
259    /**
260     * Removes knowledge of a LoggerContext.
261     *
262     * @param context The context to remove.
263     */
264    @Override
265    public void removeContext(final org.apache.logging.log4j.spi.LoggerContext context) {
266        if (context instanceof LoggerContext) {
267            selector.removeContext((LoggerContext) context);
268        }
269    }
270
271    @Override
272    public Cancellable addShutdownCallback(final Runnable callback) {
273        return SHUTDOWN_HOOK_ENABLED ? shutdownCallbackRegistry.addShutdownCallback(callback) : null;
274    }
275
276}