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.List;
022import java.util.Map;
023import java.util.Objects;
024
025import org.apache.logging.log4j.Level;
026import org.apache.logging.log4j.Marker;
027import org.apache.logging.log4j.core.Filter;
028import org.apache.logging.log4j.core.LogEvent;
029import org.apache.logging.log4j.core.Logger;
030import org.apache.logging.log4j.core.config.Node;
031import org.apache.logging.log4j.core.config.plugins.Plugin;
032import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
033import org.apache.logging.log4j.core.config.plugins.PluginElement;
034import org.apache.logging.log4j.core.config.plugins.PluginFactory;
035import org.apache.logging.log4j.core.util.KeyValuePair;
036import org.apache.logging.log4j.message.MapMessage;
037import org.apache.logging.log4j.message.Message;
038import org.apache.logging.log4j.util.BiConsumer;
039import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
040import org.apache.logging.log4j.util.IndexedStringMap;
041import org.apache.logging.log4j.util.PerformanceSensitive;
042import org.apache.logging.log4j.util.ReadOnlyStringMap;
043import org.apache.logging.log4j.util.SortedArrayStringMap;
044
045/**
046 * A Filter that operates on a Map.
047 */
048@Plugin(name = "MapFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
049@PerformanceSensitive("allocation")
050public class MapFilter extends AbstractFilter {
051
052    private final IndexedStringMap map;
053    private final boolean isAnd;
054
055    protected MapFilter(final Map<String, List<String>> map, final boolean oper, final Result onMatch, final Result onMismatch) {
056        super(onMatch, onMismatch);
057        this.isAnd = oper;
058        Objects.requireNonNull(map, "map cannot be null");
059
060        this.map = new SortedArrayStringMap(map.size());
061        for (final Map.Entry<String, List<String>> entry : map.entrySet()) {
062            this.map.putValue(entry.getKey(), entry.getValue());
063        }
064    }
065
066    @Override
067    public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
068                         final Throwable t) {
069        if (msg instanceof MapMessage) {
070            return filter((MapMessage<?, ?>) msg) ? onMatch : onMismatch;
071        }
072        return Result.NEUTRAL;
073    }
074
075    @Override
076    public Result filter(final LogEvent event) {
077        final Message msg = event.getMessage();
078        if (msg instanceof MapMessage) {
079            return filter((MapMessage<?, ?>) msg) ? onMatch : onMismatch;
080        }
081        return Result.NEUTRAL;
082    }
083
084    protected boolean filter(final MapMessage<?, ?> mapMessage) {
085        boolean match = false;
086        for (int i = 0; i < map.size(); i++) {
087            final String toMatch = mapMessage.get(map.getKeyAt(i));
088            match = toMatch != null && ((List<String>) map.getValueAt(i)).contains(toMatch);
089
090            if ((!isAnd && match) || (isAnd && !match)) {
091                break;
092            }
093        }
094        return match;
095    }
096
097    protected boolean filter(final Map<String, String> data) {
098        boolean match = false;
099        for (int i = 0; i < map.size(); i++) {
100            final String toMatch = data.get(map.getKeyAt(i));
101            match = toMatch != null && ((List<String>) map.getValueAt(i)).contains(toMatch);
102
103            if ((!isAnd && match) || (isAnd && !match)) {
104                break;
105            }
106        }
107        return match;
108    }
109
110    protected boolean filter(final ReadOnlyStringMap data) {
111        boolean match = false;
112        for (int i = 0; i < map.size(); i++) {
113            final String toMatch = data.getValue(map.getKeyAt(i));
114            match = toMatch != null && ((List<String>) map.getValueAt(i)).contains(toMatch);
115
116            if ((!isAnd && match) || (isAnd && !match)) {
117                break;
118            }
119        }
120        return match;
121    }
122
123    @Override
124    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
125            final Object p0) {
126        return Result.NEUTRAL;
127    }
128
129    @Override
130    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
131            final Object p0, final Object p1) {
132        return Result.NEUTRAL;
133    }
134
135    @Override
136    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
137            final Object p0, final Object p1, final Object p2) {
138        return Result.NEUTRAL;
139    }
140
141    @Override
142    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
143            final Object p0, final Object p1, final Object p2, final Object p3) {
144        return Result.NEUTRAL;
145    }
146
147    @Override
148    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
149            final Object p0, final Object p1, final Object p2, final Object p3,
150            final Object p4) {
151        return Result.NEUTRAL;
152    }
153
154    @Override
155    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
156            final Object p0, final Object p1, final Object p2, final Object p3,
157            final Object p4, final Object p5) {
158        return Result.NEUTRAL;
159    }
160
161    @Override
162    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
163            final Object p0, final Object p1, final Object p2, final Object p3,
164            final Object p4, final Object p5, final Object p6) {
165        return Result.NEUTRAL;
166    }
167
168    @Override
169    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
170            final Object p0, final Object p1, final Object p2, final Object p3,
171            final Object p4, final Object p5, final Object p6,
172            final Object p7) {
173        return Result.NEUTRAL;
174    }
175
176    @Override
177    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
178            final Object p0, final Object p1, final Object p2, final Object p3,
179            final Object p4, final Object p5, final Object p6,
180            final Object p7, final Object p8) {
181        return Result.NEUTRAL;
182    }
183
184    @Override
185    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
186            final Object p0, final Object p1, final Object p2, final Object p3,
187            final Object p4, final Object p5, final Object p6,
188            final Object p7, final Object p8, final Object p9) {
189        return Result.NEUTRAL;
190    }
191
192    @Override
193    public String toString() {
194        final StringBuilder sb = new StringBuilder();
195        sb.append("isAnd=").append(isAnd);
196        if (map.size() > 0) {
197            sb.append(", {");
198            for (int i = 0; i < map.size(); i++) {
199                if (i > 0) {
200                    sb.append(", ");
201                }
202                final List<String> list = map.getValueAt(i);
203                final String value = list.size() > 1 ? list.get(0) : list.toString();
204                sb.append(map.getKeyAt(i)).append('=').append(value);
205            }
206            sb.append('}');
207        }
208        return sb.toString();
209    }
210
211    protected boolean isAnd() {
212        return isAnd;
213    }
214
215    /** @deprecated  use {@link #getStringMap()} instead */
216    @Deprecated
217    protected Map<String, List<String>> getMap() {
218        final Map<String, List<String>> result = new HashMap<>(map.size());
219        map.forEach(new BiConsumer<String, List<String>>() {
220            @Override
221            public void accept(final String key, final List<String> value) {
222                result.put(key, value);
223            }
224        });
225        return result;
226    }
227
228    /**
229     * Returns the IndexedStringMap with {@code List<String>} values that this MapFilter was constructed with.
230     * @return the IndexedStringMap with {@code List<String>} values to match against
231     * @since 2.8
232     */
233    protected IndexedReadOnlyStringMap getStringMap() {
234        return map;
235    }
236
237    // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
238    @PluginFactory
239    public static MapFilter createFilter(
240            @PluginElement("Pairs") final KeyValuePair[] pairs,
241            @PluginAttribute("operator") final String oper,
242            @PluginAttribute("onMatch") final Result match,
243            @PluginAttribute("onMismatch") final Result mismatch) {
244        if (pairs == null || pairs.length == 0) {
245            LOGGER.error("keys and values must be specified for the MapFilter");
246            return null;
247        }
248        final Map<String, List<String>> map = new HashMap<>();
249        for (final KeyValuePair pair : pairs) {
250            final String key = pair.getKey();
251            if (key == null) {
252                LOGGER.error("A null key is not valid in MapFilter");
253                continue;
254            }
255            final String value = pair.getValue();
256            if (value == null) {
257                LOGGER.error("A null value for key " + key + " is not allowed in MapFilter");
258                continue;
259            }
260            List<String> list = map.get(pair.getKey());
261            if (list != null) {
262                list.add(value);
263            } else {
264                list = new ArrayList<>();
265                list.add(value);
266                map.put(pair.getKey(), list);
267            }
268        }
269        if (map.isEmpty()) {
270            LOGGER.error("MapFilter is not configured with any valid key value pairs");
271            return null;
272        }
273        final boolean isAnd = oper == null || !oper.equalsIgnoreCase("or");
274        return new MapFilter(map, isAnd, match, mismatch);
275    }
276}