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.Assert;
038    import org.apache.logging.log4j.core.helpers.NetUtils;
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 || status == Status.STOPPED) {
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        /**
164         * Start with a specific configuration.
165         * @param config The new Configuration.
166         */
167        public void start(final Configuration config) {
168            if (configLock.tryLock()) {
169                try {
170                    if (status == Status.INITIALIZED || status == Status.STOPPED) {
171                        shutdownThread = new ShutdownThread(this);
172                        try {
173                            Runtime.getRuntime().addShutdownHook(shutdownThread);
174                        } catch (SecurityException se) {
175                            LOGGER.warn("Unable to register shutdown hook due to security restrictions");
176                            shutdownThread = null;
177                        }
178                        status = Status.STARTED;
179                    }
180                } finally {
181                    configLock.unlock();
182                }
183            }
184            setConfiguration(config);
185        }
186    
187        public void stop() {
188            configLock.lock();
189            try {
190                if (status == Status.STOPPED) {
191                    return;
192                }
193                status = Status.STOPPING;
194                if (shutdownThread != null) {
195                    Runtime.getRuntime().removeShutdownHook(shutdownThread);
196                    shutdownThread = null;
197                }
198                Configuration prev = config;
199                config = new NullConfiguration();
200                updateLoggers();
201                prev.stop();
202                externalContext = null;
203                status = Status.STOPPED;
204            } finally {
205                configLock.unlock();
206            }
207        }
208    
209        /**
210         * Gets the name.
211         *
212         * @return the name.
213         */
214        public String getName() {
215            return name;
216        }
217    
218        public Status getStatus() {
219            return status;
220        }
221    
222        public boolean isStarted() {
223            return status == Status.STARTED;
224        }
225    
226        /**
227         * Set the external context.
228         * @param context The external context.
229         */
230        public void setExternalContext(final Object context) {
231            this.externalContext = context;
232        }
233    
234        /**
235         * Returns the external context.
236         * @return The external context.
237         */
238        public Object getExternalContext() {
239            return this.externalContext;
240        }
241    
242        /**
243         * Obtain a Logger from the Context.
244         * @param name The name of the Logger to return.
245         * @return The Logger.
246         */
247        public Logger getLogger(final String name) {
248            return getLogger(name, null);
249        }
250    
251        /**
252         * Obtain a Logger from the Context.
253         * @param name The name of the Logger to return.
254         * @param messageFactory The message factory is used only when creating a
255         *            logger, subsequent use does not change the logger but will log
256         *            a warning if mismatched.
257         * @return The Logger.
258         */
259        public Logger getLogger(final String name, final MessageFactory messageFactory) {
260            Logger logger = loggers.get(name);
261            if (logger != null) {
262                AbstractLogger.checkMessageFactory(logger, messageFactory);
263                return logger;
264            }
265    
266            logger = newInstance(this, name, messageFactory);
267            final Logger prev = loggers.putIfAbsent(name, logger);
268            return prev == null ? logger : prev;
269        }
270    
271        /**
272         * Determine if the specified Logger exists.
273         * @param name The Logger name to search for.
274         * @return True if the Logger exists, false otherwise.
275         */
276        public boolean hasLogger(final String name) {
277            return loggers.containsKey(name);
278        }
279    
280        /**
281         * Returns the current Configuration. The Configuration will be replaced
282         * when a reconfigure occurs.
283         *
284         * @return The Configuration.
285         */
286        public Configuration getConfiguration() {
287            return config;
288        }
289    
290        /**
291         * Add a Filter to the Configuration. Filters that are added through the API will be lost
292         * when a reconfigure occurs.
293         * @param filter The Filter to add.
294         */
295        public void addFilter(final Filter filter) {
296            config.addFilter(filter);
297        }
298    
299        /**
300         * Removes a Filter from the current Configuration.
301         * @param filter The Filter to remove.
302         */
303        public void removeFilter(final Filter filter) {
304            config.removeFilter(filter);
305        }
306    
307        /**
308         * Set the Configuration to be used.
309         * @param config The new Configuration.
310         * @return The previous Configuration.
311         */
312        private synchronized Configuration setConfiguration(final Configuration config) {
313            if (config == null) {
314                throw new NullPointerException("No Configuration was provided");
315            }
316            final Configuration prev = this.config;
317            config.addListener(this);
318            final Map<String, String> map = new HashMap<String, String>();
319            map.put("hostName", NetUtils.getLocalHostname());
320            map.put("contextName", name);
321            config.addComponent(Configuration.CONTEXT_PROPERTIES, map);
322            config.start();
323            this.config = config;
324            updateLoggers();
325            if (prev != null) {
326                prev.removeListener(this);
327                prev.stop();
328            }
329    
330            // notify listeners
331            PropertyChangeEvent evt = new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config);
332            for (PropertyChangeListener listener : propertyChangeListeners) {
333                listener.propertyChange(evt);
334            }
335            return prev;
336        }
337    
338        public void addPropertyChangeListener(PropertyChangeListener listener) {
339            propertyChangeListeners.add(Assert.isNotNull(listener, "listener"));
340        }
341    
342        public void removePropertyChangeListener(PropertyChangeListener listener) {
343            propertyChangeListeners.remove(listener);
344        }
345    
346        public synchronized URI getConfigLocation() {
347            return configLocation;
348        }
349    
350        public synchronized void setConfigLocation(URI configLocation) {
351            this.configLocation = configLocation;
352            reconfigure();
353        }
354    
355        /**
356         * Reconfigure the context.
357         */
358        public synchronized void reconfigure() {
359            LOGGER.debug("Reconfiguration started for context " + name);
360            final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation);
361            setConfiguration(instance);
362            /*
363             * instance.start(); Configuration old = setConfiguration(instance);
364             * updateLoggers(); if (old != null) { old.stop(); }
365             */
366            LOGGER.debug("Reconfiguration completed");
367        }
368    
369        /**
370         * Cause all Loggers to be updated against the current Configuration.
371         */
372        public void updateLoggers() {
373            updateLoggers(this.config);
374        }
375    
376        /**
377         * Cause all Logger to be updated against the specified Configuration.
378         * @param config The Configuration.
379         */
380        public void updateLoggers(final Configuration config) {
381            for (final Logger logger : loggers.values()) {
382                logger.updateConfiguration(config);
383            }
384        }
385    
386        /**
387         * Cause a reconfiguration to take place when the underlying configuration
388         * file changes.
389         *
390         * @param reconfigurable The Configuration that can be reconfigured.
391         */
392        public synchronized void onChange(final Reconfigurable reconfigurable) {
393            LOGGER.debug("Reconfiguration started for context " + name);
394            final Configuration config = reconfigurable.reconfigure();
395            if (config != null) {
396                setConfiguration(config);
397                LOGGER.debug("Reconfiguration completed");
398            } else {
399                LOGGER.debug("Reconfiguration failed");
400            }
401        }
402    
403        // LOG4J2-151: changed visibility from private to protected
404        protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
405            return new Logger(ctx, name, messageFactory);
406        }
407    
408        private class ShutdownThread extends Thread {
409    
410            private final LoggerContext context;
411    
412            public ShutdownThread(LoggerContext context) {
413                this.context = context;
414            }
415    
416            public void run() {
417                context.shutdownThread = null;
418                context.stop();
419            }
420        }
421    
422    }