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;
018
019import java.util.HashMap;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Map;
023
024/**
025 * Adds entries to the {@link ThreadContext stack or map} and them removes them when the object is closed, e.g. as part
026 * of a try-with-resources. User code can now look like this:
027 * <pre>
028 * try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key1, value1).put(key2, value2)) {
029 *     callSomeMethodThatLogsALot();
030 *
031 * // Entries for key1 and key2 are automatically removed from the ThreadContext map when done.
032 * }
033 * </pre>
034 *
035 * @since 2.6
036 */
037public class CloseableThreadContext {
038
039    private CloseableThreadContext() {
040    }
041
042    /**
043     * Pushes new diagnostic context information on to the Thread Context Stack. The information will be popped off when
044     * the instance is closed.
045     *
046     * @param message The new diagnostic context information.
047     * @return a new instance that will back out the changes when closed.
048     */
049    public static CloseableThreadContext.Instance push(final String message) {
050        return new CloseableThreadContext.Instance().push(message);
051    }
052
053    /**
054     * Pushes new diagnostic context information on to the Thread Context Stack. The information will be popped off when
055     * the instance is closed.
056     *
057     * @param message The new diagnostic context information.
058     * @param args    Parameters for the message.
059     * @return a new instance that will back out the changes when closed.
060     */
061    public static CloseableThreadContext.Instance push(final String message, final Object... args) {
062        return new CloseableThreadContext.Instance().push(message, args);
063    }
064
065    /**
066     * Populates the Thread Context Map with the supplied key/value pair. Any existing key in the
067     * {@link ThreadContext} will be replaced with the supplied value, and restored back to their original value when
068     * the instance is closed.
069     *
070     * @param key   The  key to be added
071     * @param value The value to be added
072     * @return a new instance that will back out the changes when closed.
073     */
074    public static CloseableThreadContext.Instance put(final String key, final String value) {
075        return new CloseableThreadContext.Instance().put(key, value);
076    }
077
078    /**
079     * Populates the Thread Context Stack with the supplied stack. The information will be popped off when
080     * the instance is closed.
081     *
082     * @param messages The list of messages to be added
083     * @return a new instance that will back out the changes when closed.
084     * @since 2.8
085     */
086    public static CloseableThreadContext.Instance pushAll(final List<String> messages) {
087        return new CloseableThreadContext.Instance().pushAll(messages);
088    }
089
090    /**
091     * Populates the Thread Context Map with the supplied key/value pairs. Any existing keys in the
092     * {@link ThreadContext} will be replaced with the supplied values, and restored back to their original value when
093     * the instance is closed.
094     *
095     * @param values The map of key/value pairs to be added
096     * @return a new instance that will back out the changes when closed.
097     * @since 2.8
098     */
099    public static CloseableThreadContext.Instance putAll(final Map<String, String> values) {
100        return new CloseableThreadContext.Instance().putAll(values);
101    }
102
103    public static class Instance implements AutoCloseable {
104
105        private int pushCount = 0;
106        private final Map<String, String> originalValues = new HashMap<>();
107
108        private Instance() {
109        }
110
111        /**
112         * Pushes new diagnostic context information on to the Thread Context Stack. The information will be popped off when
113         * the instance is closed.
114         *
115         * @param message The new diagnostic context information.
116         * @return the instance that will back out the changes when closed.
117         */
118        public Instance push(final String message) {
119            ThreadContext.push(message);
120            pushCount++;
121            return this;
122        }
123
124        /**
125         * Pushes new diagnostic context information on to the Thread Context Stack. The information will be popped off when
126         * the instance is closed.
127         *
128         * @param message The new diagnostic context information.
129         * @param args    Parameters for the message.
130         * @return the instance that will back out the changes when closed.
131         */
132        public Instance push(final String message, final Object[] args) {
133            ThreadContext.push(message, args);
134            pushCount++;
135            return this;
136        }
137
138        /**
139         * Populates the Thread Context Map with the supplied key/value pair. Any existing key in the
140         * {@link ThreadContext} will be replaced with the supplied value, and restored back to their original value when
141         * the instance is closed.
142         *
143         * @param key   The  key to be added
144         * @param value The value to be added
145         * @return a new instance that will back out the changes when closed.
146         */
147        public Instance put(final String key, final String value) {
148            // If there are no existing values, a null will be stored as an old value
149            if (!originalValues.containsKey(key)) {
150                originalValues.put(key, ThreadContext.get(key));
151            }
152            ThreadContext.put(key, value);
153            return this;
154        }
155
156        /**
157         * Populates the Thread Context Map with the supplied key/value pairs. Any existing keys in the
158         * {@link ThreadContext} will be replaced with the supplied values, and restored back to their original value when
159         * the instance is closed.
160         *
161         * @param values The map of key/value pairs to be added
162         * @return a new instance that will back out the changes when closed.
163         * @since 2.8
164         */
165        public Instance putAll(final Map<String, String> values) {
166            final Map<String, String> currentValues = ThreadContext.getContext();
167            ThreadContext.putAll(values);
168            for (final String key : values.keySet()) {
169                if (!originalValues.containsKey(key)) {
170                    originalValues.put(key, currentValues.get(key));
171                }
172            }
173            return this;
174        }
175
176        /**
177         * Populates the Thread Context Stack with the supplied stack. The information will be popped off when
178         * the instance is closed.
179         *
180         * @param messages The list of messages to be added
181         * @return a new instance that will back out the changes when closed.
182         * @since 2.8
183         */
184        public Instance pushAll(final List<String> messages) {
185            for (final String message : messages) {
186                push(message);
187            }
188            return this;
189        }
190
191        /**
192         * Removes the values from the {@link ThreadContext}.
193         * <p>
194         * Values pushed to the {@link ThreadContext} <em>stack</em> will be popped off.
195         * </p>
196         * <p>
197         * Values put on the {@link ThreadContext} <em>map</em> will be removed, or restored to their original values it they already existed.
198         * </p>
199         */
200        @Override
201        public void close() {
202            closeStack();
203            closeMap();
204        }
205
206        private void closeMap() {
207            for (final Iterator<Map.Entry<String, String>> it = originalValues.entrySet().iterator(); it.hasNext(); ) {
208                final Map.Entry<String, String> entry = it.next();
209                final String key = entry.getKey();
210                final String originalValue = entry.getValue();
211                if (null == originalValue) {
212                    ThreadContext.remove(key);
213                } else {
214                    ThreadContext.put(key, originalValue);
215                }
216                it.remove();
217            }
218        }
219
220        private void closeStack() {
221            for (int i = 0; i < pushCount; i++) {
222                ThreadContext.pop();
223            }
224            pushCount = 0;
225        }
226    }
227}