001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core;
015
016import static ch.qos.logback.core.CoreConstants.CONTEXT_NAME_KEY;
017import static ch.qos.logback.core.CoreConstants.FA_FILENAME_COLLISION_MAP;
018import static ch.qos.logback.core.CoreConstants.HOSTNAME_KEY;
019import static ch.qos.logback.core.CoreConstants.RFA_FILENAME_PATTERN_COLLISION_MAP;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.concurrent.ExecutorService;
026import java.util.concurrent.ScheduledExecutorService;
027import java.util.concurrent.ScheduledFuture;
028
029import ch.qos.logback.core.rolling.helper.FileNamePattern;
030import ch.qos.logback.core.spi.LifeCycle;
031import ch.qos.logback.core.spi.LogbackLock;
032import ch.qos.logback.core.status.StatusManager;
033import ch.qos.logback.core.util.ExecutorServiceUtil;
034import ch.qos.logback.core.util.NetworkAddressUtil;
035
036public class ContextBase implements Context, LifeCycle {
037
038    private long birthTime = System.currentTimeMillis();
039
040    private String name;
041    private StatusManager sm = new BasicStatusManager();
042    // TODO propertyMap should be observable so that we can be notified
043    // when it changes so that a new instance of propertyMap can be
044    // serialized. For the time being, we ignore this shortcoming.
045    Map<String, String> propertyMap = new HashMap<String, String>();
046    Map<String, Object> objectMap = new HashMap<String, Object>();
047
048    LogbackLock configurationLock = new LogbackLock();
049
050    private ScheduledExecutorService scheduledExecutorService;
051    protected List<ScheduledFuture<?>> scheduledFutures = new ArrayList<ScheduledFuture<?>>(1);
052    private LifeCycleManager lifeCycleManager;
053    private boolean started;
054
055    public ContextBase() {
056        initCollisionMaps();
057    }
058
059    public StatusManager getStatusManager() {
060        return sm;
061    }
062
063    /**
064     * Set the {@link StatusManager} for this context. Note that by default this
065     * context is initialized with a {@link BasicStatusManager}. A null value for
066     * the 'statusManager' argument is not allowed.
067     * 
068     * <p> A malicious attacker can set the status manager to a dummy instance,
069     * disabling internal error reporting.
070     *
071     * @param statusManager the new status manager
072     */
073    public void setStatusManager(StatusManager statusManager) {
074        // this method was added in response to http://jira.qos.ch/browse/LBCORE-35
075        if (statusManager == null) {
076            throw new IllegalArgumentException("null StatusManager not allowed");
077        }
078        this.sm = statusManager;
079    }
080
081    public Map<String, String> getCopyOfPropertyMap() {
082        return new HashMap<String, String>(propertyMap);
083    }
084
085    public void putProperty(String key, String val) {
086        if (HOSTNAME_KEY.equalsIgnoreCase(key)) {
087            putHostnameProperty(val);
088        } else {
089            this.propertyMap.put(key, val);
090        }
091    }
092
093    protected void initCollisionMaps() {
094        putObject(FA_FILENAME_COLLISION_MAP, new HashMap<String, String>());
095        putObject(RFA_FILENAME_PATTERN_COLLISION_MAP, new HashMap<String, FileNamePattern>());
096    }
097
098    /**
099     * Given a key, return the corresponding property value. If invoked with
100     * the special key "CONTEXT_NAME", the name of the context is returned.
101     *
102     * @param key
103     * @return
104     */
105    public String getProperty(String key) {
106        if (CONTEXT_NAME_KEY.equals(key))
107            return getName();
108        if (HOSTNAME_KEY.equalsIgnoreCase(key)) {
109            return lazyGetHostname();
110        }
111
112        return (String) this.propertyMap.get(key);
113    }
114
115    private String lazyGetHostname() {
116        String hostname = (String) this.propertyMap.get(HOSTNAME_KEY);
117        if (hostname == null) {
118            hostname = new NetworkAddressUtil(this).safelyGetLocalHostName();
119            putHostnameProperty(hostname);
120        }
121        return hostname;
122    }
123
124    private void putHostnameProperty(String hostname) {
125        String existingHostname = (String) this.propertyMap.get(HOSTNAME_KEY);
126        if (existingHostname == null) {
127            this.propertyMap.put(CoreConstants.HOSTNAME_KEY, hostname);
128        } else {
129
130        }
131    }
132
133    public Object getObject(String key) {
134        return objectMap.get(key);
135    }
136
137    public void putObject(String key, Object value) {
138        objectMap.put(key, value);
139    }
140
141    public void removeObject(String key) {
142        objectMap.remove(key);
143    }
144
145    public String getName() {
146        return name;
147    }
148
149    public void start() {
150        // We'd like to create the executor service here, but we can't;
151        // ContextBase has not always implemented LifeCycle and there are *many*
152        // uses (mostly in tests) that would need to be modified.
153        started = true;
154    }
155
156    public void stop() {
157        // We don't check "started" here, because the executor service uses
158        // lazy initialization, rather than being created in the start method
159        stopExecutorService();
160
161        started = false;
162    }
163
164    public boolean isStarted() {
165        return started;
166    }
167
168    /**
169     * Clear the internal objectMap and all properties. Removes any registered
170     * shutdown hook.
171     */
172    public void reset() {
173
174        removeShutdownHook();
175        getLifeCycleManager().reset();
176        propertyMap.clear();
177        objectMap.clear();
178    }
179
180    /**
181     * The context name can be set only if it is not already set, or if the
182     * current name is the default context name, namely "default", or if the
183     * current name and the old name are the same.
184     *
185     * @throws IllegalStateException if the context already has a name, other than "default".
186     */
187    public void setName(String name) throws IllegalStateException {
188        if (name != null && name.equals(this.name)) {
189            return; // idempotent naming
190        }
191        if (this.name == null || CoreConstants.DEFAULT_CONTEXT_NAME.equals(this.name)) {
192            this.name = name;
193        } else {
194            throw new IllegalStateException("Context has been already given a name");
195        }
196    }
197
198    public long getBirthTime() {
199        return birthTime;
200    }
201
202    public Object getConfigurationLock() {
203        return configurationLock;
204    }
205
206    @Override
207    /**
208     * @deprecated
209     */
210    public synchronized ExecutorService getExecutorService() {
211        return getScheduledExecutorService();
212    }
213
214    @Override
215    public synchronized ScheduledExecutorService getScheduledExecutorService() {
216        if (scheduledExecutorService == null) {
217            scheduledExecutorService = ExecutorServiceUtil.newScheduledExecutorService();
218        }
219        return scheduledExecutorService;
220    }
221
222    private synchronized void stopExecutorService() {
223        if (scheduledExecutorService != null) {
224            ExecutorServiceUtil.shutdown(scheduledExecutorService);
225            scheduledExecutorService = null;
226        }
227    }
228
229    private void removeShutdownHook() {
230        Thread hook = (Thread) getObject(CoreConstants.SHUTDOWN_HOOK_THREAD);
231        if (hook != null) {
232            removeObject(CoreConstants.SHUTDOWN_HOOK_THREAD);
233            try {
234                Runtime.getRuntime().removeShutdownHook(hook);
235            } catch (IllegalStateException e) {
236                // if JVM is already shutting down, ISE is thrown
237                // no need to do anything else
238            }
239        }
240    }
241
242    public void register(LifeCycle component) {
243        getLifeCycleManager().register(component);
244    }
245
246    /**
247     * Gets the life cycle manager for this context.
248     * <p>
249     * The default implementation lazily initializes an instance of
250     * {@link LifeCycleManager}.  Subclasses may override to provide a custom 
251     * manager implementation, but must take care to return the same manager
252     * object for each call to this method.
253     * <p>
254     * This is exposed primarily to support instrumentation for unit testing.
255     * 
256     * @return manager object 
257     */
258    synchronized LifeCycleManager getLifeCycleManager() {
259        if (lifeCycleManager == null) {
260            lifeCycleManager = new LifeCycleManager();
261        }
262        return lifeCycleManager;
263    }
264
265    @Override
266    public String toString() {
267        return name;
268    }
269
270    @Override
271    public void addScheduledFuture(ScheduledFuture<?> scheduledFuture) {
272        scheduledFutures.add(scheduledFuture);
273    }
274
275    public List<ScheduledFuture<?>> getScheduledFutures() {
276        return new ArrayList<ScheduledFuture<?>>(scheduledFutures);
277    }
278
279}