/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.addthis.hydra.data.filter.value; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Iterator; import com.addthis.ahocorasick.AhoCorasick; import com.addthis.ahocorasick.SearchResult; import com.addthis.bundle.util.ValueUtil; import com.addthis.bundle.value.ValueFactory; import com.addthis.bundle.value.ValueMap; import com.addthis.bundle.value.ValueObject; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; /** * This {@link AbstractValueFilter ValueFilter} <span class="hydra-summary">checks for strings, arrays or maps * that contain the target keys or values</span>. * <p/> * <p>If the input is a string then return the input if an element in {@link #value value} is a substring of the * input. otherwise return null. * <p>If the input is an array and {@link #once once} is {@code false} (the default value of the once * parameter is {@code false}), then iterate over each element of the array and * test if it is a substring of an element in {@link #value value}. If the input is an array and * once is {@code true}, then return the input if at least one element of the input exactly matches * an element in {@link #value value}. If the input is an array and once is {@code true}, * then return either the entire input or otherwise return null. * <p>If the input is a map and once is either {@code true} or {@code false}, then return the entire map * if at least one key exactly matches an element of {@link #key key} or * one value exactly matches an element of {@link #value value}. * <p/> * <p>Example:</p> * <pre> * {from:"EVT", to:"VALID", contains.key:["90_typ","90_rsc","90_rsi"]} * </pre> * * @user-reference */ public class ValueFilterContains extends AbstractValueFilter { /** * The set of values to match against. */ final private String[] value; /** * The set of keys to match against. Only applicable for map inputs. */ final private String[] key; /** * If true then return values that do not match. Default is false. */ final private boolean not; /** * If true then matched value is returned v/s the input */ final private boolean returnMatch; final private AhoCorasick dictionary; @JsonCreator public ValueFilterContains(@JsonProperty("value") String[] value, @JsonProperty("key") String[] key, @JsonProperty("not") boolean not, @JsonProperty("returnMatch") boolean returnMatch) { this.value = value; this.key = key; this.not = not; this.returnMatch = returnMatch; this.dictionary = (value != null) ? AhoCorasick.builder().build() : null; if (dictionary != null) { for (String pattern : value) { dictionary.add(pattern); } dictionary.prepare(); } } @Override public ValueObject filterValue(ValueObject input) { if (input == null) { return null; } String match; ValueObject.TYPE type = input.getObjectType(); if (type == ValueObject.TYPE.MAP) { ValueMap inputAsMap = input.asMap(); for (String cmp : inputAsMap.keySet()) { if (key != null && (match = checkContains(cmp, key)) != null) { return not ? null : returnMatch ? ValueFactory.create(match) : input; } if (value != null && (match = checkContains(cmp, value)) != null) { return not ? null : returnMatch ? ValueFactory.create(match) : input; } } } else if (type == ValueObject.TYPE.ARRAY && value != null) { for (ValueObject el : input.asArray()) { String cmp = ValueUtil.asNativeString(el); if ((match = checkContains(cmp, value)) != null) { return not ? null : returnMatch ? ValueFactory.create(match) : input; } } } else if (value != null) { return compareInputAsString(input); } return not ? input : null; } @Nullable private ValueObject compareInputAsString(@Nonnull ValueObject input) { assert (value != null); String cmp = ValueUtil.asNativeString(input); if (cmp == null) { return null; } Iterator<SearchResult> matcher = dictionary.progressiveSearch(cmp); if (matcher.hasNext()) { if (not) { return null; } else if (returnMatch) { return ValueFactory.create((String) matcher.next().getOutputs().iterator().next()); } else { return input; } } return not ? input : null; } private String checkContains(String cmp, String[] arr) { for (String val : arr) { if (cmp.equals(val)) { return val; } } return null; } }