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