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.util.HashMap;
020import java.util.List;
021import java.util.Map;
022
023import org.apache.logging.log4j.ThreadContext;
024import org.apache.logging.log4j.core.ContextDataInjector;
025import org.apache.logging.log4j.core.config.Property;
026import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
027import org.apache.logging.log4j.util.ReadOnlyStringMap;
028import org.apache.logging.log4j.util.StringMap;
029
030/**
031 * {@code ThreadContextDataInjector} contains a number of strategies for copying key-value pairs from the various
032 * {@code ThreadContext} map implementations into a {@code StringMap}. In the case of duplicate keys,
033 * thread context values overwrite configuration {@code Property} values.
034 * <p>
035 * These are the default {@code ContextDataInjector} objects returned by the {@link ContextDataInjectorFactory}.
036 * </p>
037 *
038 * @see org.apache.logging.log4j.ThreadContext
039 * @see Property
040 * @see ReadOnlyStringMap
041 * @see ContextDataInjector
042 * @see ContextDataInjectorFactory
043 * @since 2.7
044 */
045public class ThreadContextDataInjector {
046
047    /**
048     * Default {@code ContextDataInjector} for the legacy {@code Map<String, String>}-based ThreadContext (which is
049     * also the ThreadContext implementation used for web applications).
050     * <p>
051     * This injector always puts key-value pairs into the specified reusable StringMap.
052     */
053    public static class ForDefaultThreadContextMap implements ContextDataInjector {
054
055        /**
056         * Puts key-value pairs from both the specified list of properties as well as the thread context into the
057         * specified reusable StringMap.
058         *
059         * @param props list of configuration properties, may be {@code null}
060         * @param ignore a {@code StringMap} instance from the log event
061         * @return a {@code StringMap} combining configuration properties with thread context data
062         */
063        @Override
064        public StringMap injectContextData(final List<Property> props, final StringMap ignore) {
065
066            final Map<String, String> copy = ThreadContext.getImmutableContext();
067
068            // The DefaultThreadContextMap stores context data in a Map<String, String>.
069            // This is a copy-on-write data structure so we are sure ThreadContext changes will not affect our copy.
070            // If there are no configuration properties returning a thin wrapper around the copy
071            // is faster than copying the elements into the LogEvent's reusable StringMap.
072            if (props == null || props.isEmpty()) {
073                // this will replace the LogEvent's context data with the returned instance.
074                // NOTE: must mark as frozen or downstream components may attempt to modify (UnsupportedOperationEx)
075                return copy.isEmpty() ? ContextDataFactory.emptyFrozenContextData() : frozenStringMap(copy);
076            }
077            // If the list of Properties is non-empty we need to combine the properties and the ThreadContext
078            // data. Note that we cannot reuse the specified StringMap: some Loggers may have properties defined
079            // and others not, so the LogEvent's context data may have been replaced with an immutable copy from
080            // the ThreadContext - this will throw an UnsupportedOperationException if we try to modify it.
081            final StringMap result = new JdkMapAdapterStringMap(new HashMap<>(copy));
082            for (int i = 0; i < props.size(); i++) {
083                final Property prop = props.get(i);
084                if (!copy.containsKey(prop.getName())) {
085                    result.putValue(prop.getName(), prop.getValue());
086                }
087            }
088            result.freeze();
089            return result;
090        }
091
092        private static JdkMapAdapterStringMap frozenStringMap(final Map<String, String> copy) {
093            final JdkMapAdapterStringMap result = new JdkMapAdapterStringMap(copy);
094            result.freeze();
095            return result;
096        }
097
098        @Override
099        public ReadOnlyStringMap rawContextData() {
100            final ReadOnlyThreadContextMap map = ThreadContext.getThreadContextMap();
101            if (map instanceof ReadOnlyStringMap) {
102                return (ReadOnlyStringMap) map;
103            }
104            // note: default ThreadContextMap is null
105            final Map<String, String> copy = ThreadContext.getImmutableContext();
106            return copy.isEmpty() ? ContextDataFactory.emptyFrozenContextData() : new JdkMapAdapterStringMap(copy);
107        }
108    }
109
110    /**
111     * The {@code ContextDataInjector} used when the ThreadContextMap implementation is a garbage-free
112     * StringMap-based data structure.
113     * <p>
114     * This injector always puts key-value pairs into the specified reusable StringMap.
115     */
116    public static class ForGarbageFreeThreadContextMap implements ContextDataInjector {
117        /**
118         * Puts key-value pairs from both the specified list of properties as well as the thread context into the
119         * specified reusable StringMap.
120         *
121         * @param props list of configuration properties, may be {@code null}
122         * @param reusable a {@code StringMap} instance that may be reused to avoid creating temporary objects
123         * @return a {@code StringMap} combining configuration properties with thread context data
124         */
125        @Override
126        public StringMap injectContextData(final List<Property> props, final StringMap reusable) {
127            // When the ThreadContext is garbage-free, we must copy its key-value pairs into the specified reusable
128            // StringMap. We cannot return the ThreadContext's internal data structure because it may be modified later
129            // and such modifications should not be reflected in the log event.
130            copyProperties(props, reusable);
131
132            final ReadOnlyStringMap immutableCopy = ThreadContext.getThreadContextMap().getReadOnlyContextData();
133            reusable.putAll(immutableCopy);
134            return reusable;
135        }
136
137        @Override
138        public ReadOnlyStringMap rawContextData() {
139            return ThreadContext.getThreadContextMap().getReadOnlyContextData();
140        }
141    }
142
143    /**
144     * The {@code ContextDataInjector} used when the ThreadContextMap implementation is a copy-on-write
145     * StringMap-based data structure.
146     * <p>
147     * If there are no configuration properties, this injector will return the thread context's internal data
148     * structure. Otherwise the configuration properties are combined with the thread context key-value pairs into the
149     * specified reusable StringMap.
150     */
151    public static class ForCopyOnWriteThreadContextMap implements ContextDataInjector {
152        /**
153         * If there are no configuration properties, this injector will return the thread context's internal data
154         * structure. Otherwise the configuration properties are combined with the thread context key-value pairs into the
155         * specified reusable StringMap.
156         *
157         * @param props list of configuration properties, may be {@code null}
158         * @param ignore a {@code StringMap} instance from the log event
159         * @return a {@code StringMap} combining configuration properties with thread context data
160         */
161        @Override
162        public StringMap injectContextData(final List<Property> props, final StringMap ignore) {
163            // If there are no configuration properties we want to just return the ThreadContext's StringMap:
164            // it is a copy-on-write data structure so we are sure ThreadContext changes will not affect our copy.
165            final StringMap immutableCopy = ThreadContext.getThreadContextMap().getReadOnlyContextData();
166            if (props == null || props.isEmpty()) {
167                return immutableCopy; // this will replace the LogEvent's context data with the returned instance
168            }
169            // However, if the list of Properties is non-empty we need to combine the properties and the ThreadContext
170            // data. Note that we cannot reuse the specified StringMap: some Loggers may have properties defined
171            // and others not, so the LogEvent's context data may have been replaced with an immutable copy from
172            // the ThreadContext - this will throw an UnsupportedOperationException if we try to modify it.
173            final StringMap result = ContextDataFactory.createContextData(props.size() + immutableCopy.size());
174            copyProperties(props, result);
175            result.putAll(immutableCopy);
176            return result;
177        }
178
179        @Override
180        public ReadOnlyStringMap rawContextData() {
181            return ThreadContext.getThreadContextMap().getReadOnlyContextData();
182        }
183    }
184
185    /**
186     * Copies key-value pairs from the specified property list into the specified {@code StringMap}.
187     *
188     * @param properties list of configuration properties, may be {@code null}
189     * @param result the {@code StringMap} object to add the key-values to. Must be non-{@code null}.
190     */
191    public static void copyProperties(final List<Property> properties, final StringMap result) {
192        if (properties != null) {
193            for (int i = 0; i < properties.size(); i++) {
194                final Property prop = properties.get(i);
195                result.putValue(prop.getName(), prop.getValue());
196            }
197        }
198    }
199}