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;
020
021import org.apache.logging.log4j.core.LifeCycle;
022import org.apache.logging.log4j.core.LoggerContext;
023import org.apache.logging.log4j.core.config.Configuration;
024import org.apache.logging.log4j.core.config.ConfigurationFactory;
025import org.apache.logging.log4j.core.config.ConfigurationSource;
026import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector;
027import org.apache.logging.log4j.core.selector.ContextSelector;
028import org.apache.logging.log4j.core.util.Assert;
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.Loader;
033import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
034import org.apache.logging.log4j.spi.LoggerContextFactory;
035import org.apache.logging.log4j.status.StatusLogger;
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 = Assert.requireNonNull(selector, "No ContextSelector provided");
086        this.shutdownCallbackRegistry = Assert.requireNonNull(shutdownCallbackRegistry,
087            "No ShutdownCallbackRegistry provided");
088        LOGGER.debug("Using ShutdownCallbackRegistry {}", shutdownCallbackRegistry.getClass());
089        initializeShutdownCallbackRegistry();
090    }
091
092    private static ContextSelector createContextSelector() {
093        final String sel = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR);
094        if (sel != null) {
095            try {
096                return Loader.newCheckedInstanceOf(sel, ContextSelector.class);
097            } catch (final Exception ex) {
098                LOGGER.error("Unable to create context {}", sel, ex);
099            }
100        }
101        return new ClassLoaderContextSelector();
102    }
103
104    private static ShutdownCallbackRegistry createShutdownCallbackRegistry() {
105        // TODO: this is such a common idiom it really deserves a utility method somewhere
106        final String registry = PropertiesUtil.getProperties().getStringProperty(
107            ShutdownCallbackRegistry.SHUTDOWN_CALLBACK_REGISTRY);
108        if (registry != null) {
109            try {
110                return Loader.newCheckedInstanceOf(registry, ShutdownCallbackRegistry.class);
111            } catch (final Exception e) {
112                LOGGER.error(SHUTDOWN_HOOK_MARKER,
113                    "There was an error loading the ShutdownCallbackRegistry [{}]. "
114                        + "Falling back to DefaultShutdownCallbackRegistry.", registry, e);
115            }
116        }
117        return new DefaultShutdownCallbackRegistry();
118    }
119
120    private void initializeShutdownCallbackRegistry() {
121        if (SHUTDOWN_HOOK_ENABLED && this.shutdownCallbackRegistry instanceof LifeCycle) {
122            try {
123                ((LifeCycle) this.shutdownCallbackRegistry).start();
124            } catch (final Exception e) {
125                LOGGER.error("There was an error starting the ShutdownCallbackRegistry.", e);
126            }
127        }
128    }
129
130    /**
131     * Loads the LoggerContext using the ContextSelector.
132     * @param fqcn The fully qualified class name of the caller.
133     * @param loader The ClassLoader to use or null.
134     * @param currentContext If true returns the current Context, if false returns the Context appropriate
135     * for the caller if a more appropriate Context can be determined.
136     * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext.
137     * @return The LoggerContext.
138     */
139    @Override
140    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
141                                    final boolean currentContext) {
142        final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext);
143        if (externalContext != null && ctx.getExternalContext() == null) {
144            ctx.setExternalContext(externalContext);
145        }
146        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
147            ctx.start();
148        }
149        return ctx;
150    }
151
152    /**
153     * Loads the LoggerContext using the ContextSelector.
154     * @param fqcn The fully qualified class name of the caller.
155     * @param loader The ClassLoader to use or null.
156     * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext.
157     * @param currentContext If true returns the current Context, if false returns the Context appropriate
158     * for the caller if a more appropriate Context can be determined.
159     * @param source The configuration source.
160     * @return The LoggerContext.
161     */
162    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
163                                    final boolean currentContext, final ConfigurationSource source) {
164        final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, null);
165        if (externalContext != null && ctx.getExternalContext() == null) {
166            ctx.setExternalContext(externalContext);
167        }
168        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
169            if (source != null) {
170                ContextAnchor.THREAD_CONTEXT.set(ctx);
171                final Configuration config = ConfigurationFactory.getInstance().getConfiguration(source);
172                LOGGER.debug("Starting LoggerContext[name={}] from configuration {}", ctx.getName(), source);
173                ctx.start(config);
174                ContextAnchor.THREAD_CONTEXT.remove();
175            } else {
176                ctx.start();
177            }
178        }
179        return ctx;
180    }
181
182    /**
183     * Loads the LoggerContext using the ContextSelector.
184     * @param fqcn The fully qualified class name of the caller.
185     * @param loader The ClassLoader to use or null.
186     * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext.
187     * @param currentContext If true returns the current Context, if false returns the Context appropriate
188     * for the caller if a more appropriate Context can be determined.
189     * @param configLocation The location of the configuration for the LoggerContext.
190     * @return The LoggerContext.
191     */
192    @Override
193    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
194                                    final boolean currentContext, final URI configLocation, final String name) {
195        final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, configLocation);
196        if (externalContext != null && ctx.getExternalContext() == null) {
197            ctx.setExternalContext(externalContext);
198        }
199        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
200            if (configLocation != null || name != null) {
201                ContextAnchor.THREAD_CONTEXT.set(ctx);
202                final Configuration config = ConfigurationFactory.getInstance().getConfiguration(name, configLocation);
203                LOGGER.debug("Starting LoggerContext[name={}] from configuration at {}", ctx.getName(), configLocation);
204                ctx.start(config);
205                ContextAnchor.THREAD_CONTEXT.remove();
206            } else {
207                ctx.start();
208            }
209        }
210        return ctx;
211    }
212
213    /**
214     * Returns the ContextSelector.
215     * @return The ContextSelector.
216     */
217    public ContextSelector getSelector() {
218        return selector;
219    }
220
221    /**
222     * Removes knowledge of a LoggerContext.
223     *
224     * @param context The context to remove.
225     */
226    @Override
227    public void removeContext(final org.apache.logging.log4j.spi.LoggerContext context) {
228        if (context instanceof LoggerContext) {
229            selector.removeContext((LoggerContext) context);
230        }
231    }
232
233    @Override
234    public Cancellable addShutdownCallback(final Runnable callback) {
235        return SHUTDOWN_HOOK_ENABLED ? shutdownCallbackRegistry.addShutdownCallback(callback) : null;
236    }
237}