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.filter;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.logging.log4j.Level;
026import org.apache.logging.log4j.Marker;
027import org.apache.logging.log4j.core.ContextDataInjector;
028import org.apache.logging.log4j.core.Filter;
029import org.apache.logging.log4j.core.LogEvent;
030import org.apache.logging.log4j.core.Logger;
031import org.apache.logging.log4j.core.config.Node;
032import org.apache.logging.log4j.core.config.plugins.Plugin;
033import org.apache.logging.log4j.core.config.plugins.PluginAliases;
034import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
035import org.apache.logging.log4j.core.config.plugins.PluginElement;
036import org.apache.logging.log4j.core.config.plugins.PluginFactory;
037import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory;
038import org.apache.logging.log4j.core.util.KeyValuePair;
039import org.apache.logging.log4j.message.Message;
040import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
041import org.apache.logging.log4j.util.PerformanceSensitive;
042import org.apache.logging.log4j.util.ReadOnlyStringMap;
043
044/**
045 * Filter based on a value in the Thread Context Map (MDC).
046 */
047@Plugin(name = "ThreadContextMapFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
048@PluginAliases("ContextMapFilter")
049@PerformanceSensitive("allocation")
050public class ThreadContextMapFilter extends MapFilter {
051
052    private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector();
053    private final String key;
054    private final String value;
055
056    private final boolean useMap;
057
058    public ThreadContextMapFilter(final Map<String, List<String>> pairs, final boolean oper, final Result onMatch,
059                                  final Result onMismatch) {
060        super(pairs, oper, onMatch, onMismatch);
061        if (pairs.size() == 1) {
062            final Iterator<Map.Entry<String, List<String>>> iter = pairs.entrySet().iterator();
063            final Map.Entry<String, List<String>> entry = iter.next();
064            if (entry.getValue().size() == 1) {
065                this.key = entry.getKey();
066                this.value = entry.getValue().get(0);
067                this.useMap = false;
068            } else {
069                this.key = null;
070                this.value = null;
071                this.useMap = true;
072            }
073        } else {
074            this.key = null;
075            this.value = null;
076            this.useMap = true;
077        }
078    }
079
080    @Override
081    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
082                         final Object... params) {
083        return filter();
084    }
085
086    @Override
087    public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
088                         final Throwable t) {
089        return filter();
090    }
091
092    @Override
093    public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
094                         final Throwable t) {
095        return filter();
096    }
097
098    private Result filter() {
099        boolean match = false;
100        if (useMap) {
101            ReadOnlyStringMap currentContextData = null;
102            final IndexedReadOnlyStringMap map = getStringMap();
103            for (int i = 0; i < map.size(); i++) {
104                if (currentContextData == null) {
105                    currentContextData = currentContextData();
106                }
107                final String toMatch = currentContextData.getValue(map.getKeyAt(i));
108                match = toMatch != null && ((List<String>) map.getValueAt(i)).contains(toMatch);
109                if ((!isAnd() && match) || (isAnd() && !match)) {
110                    break;
111                }
112            }
113        } else {
114            match = value.equals(currentContextData().getValue(key));
115        }
116        return match ? onMatch : onMismatch;
117    }
118
119    private ReadOnlyStringMap currentContextData() {
120        return injector.rawContextData();
121    }
122
123    @Override
124    public Result filter(final LogEvent event) {
125        return super.filter(event.getContextData()) ? onMatch : onMismatch;
126    }
127
128    @Override
129    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
130            final Object p0) {
131        return filter();
132    }
133
134    @Override
135    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
136            final Object p0, final Object p1) {
137        return filter();
138    }
139
140    @Override
141    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
142            final Object p0, final Object p1, final Object p2) {
143        return filter();
144    }
145
146    @Override
147    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
148            final Object p0, final Object p1, final Object p2, final Object p3) {
149        return filter();
150    }
151
152    @Override
153    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
154            final Object p0, final Object p1, final Object p2, final Object p3,
155            final Object p4) {
156        return filter();
157    }
158
159    @Override
160    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
161            final Object p0, final Object p1, final Object p2, final Object p3,
162            final Object p4, final Object p5) {
163        return filter();
164    }
165
166    @Override
167    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
168            final Object p0, final Object p1, final Object p2, final Object p3,
169            final Object p4, final Object p5, final Object p6) {
170        return filter();
171    }
172
173    @Override
174    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
175            final Object p0, final Object p1, final Object p2, final Object p3,
176            final Object p4, final Object p5, final Object p6,
177            final Object p7) {
178        return filter();
179    }
180
181    @Override
182    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
183            final Object p0, final Object p1, final Object p2, final Object p3,
184            final Object p4, final Object p5, final Object p6,
185            final Object p7, final Object p8) {
186        return filter();
187    }
188
189    @Override
190    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
191            final Object p0, final Object p1, final Object p2, final Object p3,
192            final Object p4, final Object p5, final Object p6,
193            final Object p7, final Object p8, final Object p9) {
194        return filter();
195    }
196
197    // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
198    @PluginFactory
199    public static ThreadContextMapFilter createFilter(
200            @PluginElement("Pairs") final KeyValuePair[] pairs,
201            @PluginAttribute("operator") final String oper,
202            @PluginAttribute("onMatch") final Result match,
203            @PluginAttribute("onMismatch") final Result mismatch) {
204        if (pairs == null || pairs.length == 0) {
205            LOGGER.error("key and value pairs must be specified for the ThreadContextMapFilter");
206            return null;
207        }
208        final Map<String, List<String>> map = new HashMap<>();
209        for (final KeyValuePair pair : pairs) {
210            final String key = pair.getKey();
211            if (key == null) {
212                LOGGER.error("A null key is not valid in MapFilter");
213                continue;
214            }
215            final String value = pair.getValue();
216            if (value == null) {
217                LOGGER.error("A null value for key " + key + " is not allowed in MapFilter");
218                continue;
219            }
220            List<String> list = map.get(pair.getKey());
221            if (list != null) {
222                list.add(value);
223            } else {
224                list = new ArrayList<>();
225                list.add(value);
226                map.put(pair.getKey(), list);
227            }
228        }
229        if (map.isEmpty()) {
230            LOGGER.error("ThreadContextMapFilter is not configured with any valid key value pairs");
231            return null;
232        }
233        final boolean isAnd = oper == null || !oper.equalsIgnoreCase("or");
234        return new ThreadContextMapFilter(map, isAnd, match, mismatch);
235    }
236}