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