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;
018    
019    import java.beans.PropertyChangeEvent;
020    import java.beans.PropertyChangeListener;
021    import java.io.File;
022    import java.net.URI;
023    import java.util.HashMap;
024    import java.util.Map;
025    import java.util.concurrent.ConcurrentHashMap;
026    import java.util.concurrent.ConcurrentMap;
027    import java.util.concurrent.CopyOnWriteArrayList;
028    import java.util.concurrent.locks.Lock;
029    import java.util.concurrent.locks.ReentrantLock;
030    
031    import org.apache.logging.log4j.core.config.Configuration;
032    import org.apache.logging.log4j.core.config.ConfigurationFactory;
033    import org.apache.logging.log4j.core.config.ConfigurationListener;
034    import org.apache.logging.log4j.core.config.DefaultConfiguration;
035    import org.apache.logging.log4j.core.config.NullConfiguration;
036    import org.apache.logging.log4j.core.config.Reconfigurable;
037    import org.apache.logging.log4j.core.helpers.NetUtils;
038    import org.apache.logging.log4j.core.jmx.Assert;
039    import org.apache.logging.log4j.message.MessageFactory;
040    import org.apache.logging.log4j.spi.AbstractLogger;
041    import org.apache.logging.log4j.status.StatusLogger;
042    
043    /**
044     * The LoggerContext is the anchor for the logging system. It maintains a list
045     * of all the loggers requested by applications and a reference to the
046     * Configuration. The Configuration will contain the configured loggers,
047     * appenders, filters, etc and will be atomically updated whenever a reconfigure
048     * occurs.
049     */
050    public class LoggerContext implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener, LifeCycle {
051    
052        public static final String PROPERTY_CONFIG = "config";
053        private static final StatusLogger LOGGER = StatusLogger.getLogger();
054    
055        private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<String, Logger>();
056        private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<PropertyChangeListener>();
057    
058        /**
059         * The Configuration is volatile to guarantee that initialization of the
060         * Configuration has completed before the reference is updated.
061         */
062        private volatile Configuration config = new DefaultConfiguration();
063        private Object externalContext;
064        private final String name;
065        private URI configLocation;
066    
067        private ShutdownThread shutdownThread = null;
068    
069        /**
070         * Status of the LoggerContext.
071         */
072        public enum Status {
073            /** Initialized but not yet started. */
074            INITIALIZED,
075            /** In the process of starting. */
076            STARTING,
077            /** Is active. */
078            STARTED,
079            /** Shutdown is in progress. */
080            STOPPING,
081            /** Has shutdown. */
082            STOPPED
083        }
084    
085        private volatile Status status = Status.INITIALIZED;
086    
087        private final Lock configLock = new ReentrantLock();
088    
089        /**
090         * Constructor taking only a name.
091         * @param name The context name.
092         */
093        public LoggerContext(final String name) {
094            this(name, null, (URI) null);
095        }
096    
097        /**
098         * Constructor taking a name and a reference to an external context.
099         * @param name The context name.
100         * @param externalContext The external context.
101         */
102        public LoggerContext(final String name, final Object externalContext) {
103            this(name, externalContext, (URI) null);
104        }
105    
106        /**
107         * Constructor taking a name, external context and a configuration URI.
108         * @param name The context name.
109         * @param externalContext The external context.
110         * @param configLocn The location of the configuration as a URI.
111         */
112        public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
113            this.name = name;
114            this.externalContext = externalContext;
115            this.configLocation = configLocn;
116        }
117    
118        /**
119         * Constructor taking a name external context and a configuration location
120         * String. The location must be resolvable to a File.
121         * 
122         * @param name The configuration location.
123         * @param externalContext The external context.
124         * @param configLocn The configuration location.
125         */
126        public LoggerContext(final String name, final Object externalContext, final String configLocn) {
127            this.name = name;
128            this.externalContext = externalContext;
129            if (configLocn != null) {
130                URI uri;
131                try {
132                    uri = new File(configLocn).toURI();
133                } catch (final Exception ex) {
134                    uri = null;
135                }
136                configLocation = uri;
137            } else {
138                configLocation = null;
139            }
140        }
141    
142        public void start() {
143            if (configLock.tryLock()) {
144                try {
145                    if (status == Status.INITIALIZED) {
146                        status = Status.STARTING;
147                        reconfigure();
148                        shutdownThread = new ShutdownThread(this);
149                        try {
150                            Runtime.getRuntime().addShutdownHook(shutdownThread);
151                        } catch (SecurityException se) {
152                            LOGGER.warn("Unable to register shutdown hook due to security restrictions");
153                            shutdownThread = null;
154                        }
155                        status = Status.STARTED;
156                    }
157                } finally {
158                    configLock.unlock();
159                }
160            }
161        }
162    
163        public void stop() {
164            configLock.lock();
165            try {
166                status = Status.STOPPING;
167                if (shutdownThread != null) {
168                    Runtime.getRuntime().removeShutdownHook(shutdownThread);
169                    shutdownThread = null;
170                }
171                updateLoggers(new NullConfiguration());
172                config.stop();
173                externalContext = null;
174                status = Status.STOPPED;
175            } finally {
176                configLock.unlock();
177            }
178        }
179    
180        /**
181         * Gets the name.
182         *
183         * @return the name.
184         */
185        public String getName() {
186            return name;
187        }
188    
189        public Status getStatus() {
190            return status;
191        }
192    
193        public boolean isStarted() {
194            return status == Status.STARTED;
195        }
196    
197        /**
198         * Set the external context.
199         * @param context The external context.
200         */
201        public void setExternalContext(final Object context) {
202            this.externalContext = context;
203        }
204    
205        /**
206         * Returns the external context.
207         * @return The external context.
208         */
209        public Object getExternalContext() {
210            return this.externalContext;
211        }
212    
213        /**
214         * Obtain a Logger from the Context.
215         * @param name The name of the Logger to return.
216         * @return The Logger.
217         */
218        public Logger getLogger(final String name) {
219            return getLogger(name, null);
220        }
221    
222        /**
223         * Obtain a Logger from the Context.
224         * @param name The name of the Logger to return.
225         * @param messageFactory The message factory is used only when creating a
226         *            logger, subsequent use does not change the logger but will log
227         *            a warning if mismatched.
228         * @return The Logger.
229         */
230        public Logger getLogger(final String name, final MessageFactory messageFactory) {
231            Logger logger = loggers.get(name);
232            if (logger != null) {
233                AbstractLogger.checkMessageFactory(logger, messageFactory);
234                return logger;
235            }
236    
237            logger = newInstance(this, name, messageFactory);
238            final Logger prev = loggers.putIfAbsent(name, logger);
239            return prev == null ? logger : prev;
240        }
241    
242        /**
243         * Determine if the specified Logger exists.
244         * @param name The Logger name to search for.
245         * @return True if the Logger exists, false otherwise.
246         */
247        public boolean hasLogger(final String name) {
248            return loggers.containsKey(name);
249        }
250    
251        /**
252         * Returns the current Configuration. The Configuration will be replaced
253         * when a reconfigure occurs.
254         * 
255         * @return The Configuration.
256         */
257        public Configuration getConfiguration() {
258            return config;
259        }
260    
261        /**
262         * Add a Filter to the Configuration. Filters that are added through the API will be lost
263         * when a reconfigure occurs.
264         * @param filter The Filter to add.
265         */
266        public void addFilter(final Filter filter) {
267            config.addFilter(filter);
268        }
269    
270        /**
271         * Removes a Filter from the current Configuration.
272         * @param filter The Filter to remove.
273         */
274        public void removeFilter(final Filter filter) {
275            config.removeFilter(filter);
276        }
277    
278        /**
279         * Set the Configuration to be used.
280         * @param config The new Configuration.
281         * @return The previous Configuration.
282         */
283        public synchronized Configuration setConfiguration(final Configuration config) {
284            if (config == null) {
285                throw new NullPointerException("No Configuration was provided");
286            }
287            final Configuration prev = this.config;
288            config.addListener(this);
289            final Map<String, String> map = new HashMap<String, String>();
290            map.put("hostName", NetUtils.getLocalHostname());
291            map.put("contextName", name);
292            config.addComponent(Configuration.CONTEXT_PROPERTIES, map);
293            config.start();
294            this.config = config;
295            updateLoggers();
296            if (prev != null) {
297                prev.removeListener(this);
298                prev.stop();
299            }
300    
301            // notify listeners
302            PropertyChangeEvent evt = new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config);
303            for (PropertyChangeListener listener : propertyChangeListeners) {
304                listener.propertyChange(evt);
305            }
306            return prev;
307        }
308    
309        public void addPropertyChangeListener(PropertyChangeListener listener) {
310            propertyChangeListeners.add(Assert.isNotNull(listener, "listener"));
311        }
312    
313        public void removePropertyChangeListener(PropertyChangeListener listener) {
314            propertyChangeListeners.remove(listener);
315        }
316    
317        public synchronized URI getConfigLocation() {
318            return configLocation;
319        }
320    
321        public synchronized void setConfigLocation(URI configLocation) {
322            this.configLocation = configLocation;
323            reconfigure();
324        }
325    
326        /**
327         * Reconfigure the context.
328         */
329        public synchronized void reconfigure() {
330            LOGGER.debug("Reconfiguration started for context " + name);
331            final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation);
332            setConfiguration(instance);
333            /*
334             * instance.start(); Configuration old = setConfiguration(instance);
335             * updateLoggers(); if (old != null) { old.stop(); }
336             */
337            LOGGER.debug("Reconfiguration completed");
338        }
339    
340        /**
341         * Cause all Loggers to be updated against the current Configuration.
342         */
343        public void updateLoggers() {
344            updateLoggers(this.config);
345        }
346    
347        /**
348         * Cause all Logger to be updated against the specified Configuration.
349         * @param config The Configuration.
350         */
351        public void updateLoggers(final Configuration config) {
352            for (final Logger logger : loggers.values()) {
353                logger.updateConfiguration(config);
354            }
355        }
356    
357        /**
358         * Cause a reconfiguration to take place when the underlying configuration
359         * file changes.
360         * 
361         * @param reconfigurable The Configuration that can be reconfigured.
362         */
363        public synchronized void onChange(final Reconfigurable reconfigurable) {
364            LOGGER.debug("Reconfiguration started for context " + name);
365            final Configuration config = reconfigurable.reconfigure();
366            if (config != null) {
367                setConfiguration(config);
368                LOGGER.debug("Reconfiguration completed");
369            } else {
370                LOGGER.debug("Reconfiguration failed");
371            }
372        }
373    
374        // LOG4J2-151: changed visibility from private to protected
375        protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
376            return new Logger(ctx, name, messageFactory);
377        }
378    
379        private class ShutdownThread extends Thread {
380    
381            private final LoggerContext context;
382    
383            public ShutdownThread(LoggerContext context) {
384                this.context = context;
385            }
386    
387            public void run() {
388                context.shutdownThread = null;
389                context.stop();
390            }
391        }
392    
393    }