/* * Copyright (c) 2016 wetransform GmbH * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * wetransform GmbH <http://www.wetransform.to> */ package eu.esdihumboldt.hale.common.headless.transform.filter; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.namespace.QName; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import eu.esdihumboldt.hale.common.instance.extension.filter.FilterDefinitionManager; import eu.esdihumboldt.hale.common.instance.model.ContextAwareFilter; import eu.esdihumboldt.hale.common.instance.model.Filter; import eu.esdihumboldt.hale.common.instance.model.Instance; import eu.esdihumboldt.hale.common.schema.model.TypeDefinition; import eu.esdihumboldt.hale.io.xsd.constraint.XmlElements; import eu.esdihumboldt.hale.io.xsd.model.XmlElement; /** * Filter definition is a group of filters, applied on {@link Instance}s one by * one. * * @author Arun */ public class InstanceFilterDefinition implements Filter, ContextAwareFilter { private boolean globalContext = false; /** * filters for specific types of source */ private final Multimap<String, Filter> typeFilters; /** * filters without types(generalized to all types) per source */ private final List<Filter> unconditionalFilters; /** * Filters applicable for any instance that can mark instances to be * excluded. */ private final List<Filter> excludeFilters = new ArrayList<>(); /** * Excluded types for */ private final Set<String> excludedTypes; /** * Default constructor */ public InstanceFilterDefinition() { this.typeFilters = ArrayListMultimap.create(); this.unconditionalFilters = new ArrayList<>(); this.excludedTypes = new HashSet<String>(); } /** * To add type filters * * @param types types of schema * @param filter {@link Filter} */ public void addTypeFilter(String types, Filter filter) { this.typeFilters.put(types, filter); } /** * To add type filters based on expression * * @param types types of schema * @param expression filter expression */ public void addTypeFilter(String types, String expression) { this.typeFilters.put(types, createFilter(expression)); } /** * To add simple filters * * @param filter {@link Filter} */ public void addUnconditionalFilter(Filter filter) { this.unconditionalFilters.add(filter); } /** * Add an exclude filter. * * @param expression the filter expression */ public void addExcludeFilter(String expression) { excludeFilters.add(createFilter(expression)); } /** * To add simple filters * * @param expression filter expression */ public void addUnconditionalFilter(String expression) { this.unconditionalFilters.add(createFilter(expression)); } /** * to get typeFilters * * @return multimap of {@link String} and {@link Filter} */ public Multimap<String, Filter> getTypeFilters() { return this.typeFilters; } /** * to get Unconditional Filters * * @return List of {@link Filter} */ public List<Filter> getUnconditionalFilters() { return Collections.unmodifiableList(unconditionalFilters); } /** * Get the defined exclude filters. * * @return the list of exclude filters */ public List<Filter> getExcludeFilters() { return Collections.unmodifiableList(excludeFilters); } /** * Add excluded types from source * * @param value type of Source */ public void addExcludedType(String value) { this.excludedTypes.add(value); } /** * @return the globalContext */ public boolean isGlobalContext() { return globalContext; } /** * @param globalContext the globalContext to set */ public void setGlobalContext(boolean globalContext) { this.globalContext = globalContext; } /** * Create a filter from the given test. * * @param expression the filter expression * @return the filter or may be <code>null</code> */ private Filter createFilter(String expression) { Filter filter = FilterDefinitionManager.getInstance().parse(expression); if (filter == null) // if parsing failed filter = FilterDefinitionManager.getInstance().from("CQL", expression); return filter; } @Override public boolean match(Instance instance) { return applyFilters(instance, null); } @Override public boolean match(Instance instance, Map<Object, Object> context) { return applyFilters(instance, context); } // Filtration using unconditional filters and Typed filters. Instance should // match any of the filter if supplied. private boolean applyFilters(Instance instance, Map<Object, Object> context) { if (this.unconditionalFilters.size() == 0 && this.typeFilters.size() == 0 && this.excludedTypes.size() == 0) return true; Boolean result = null; // applying exclude filter first if (applyExcludeFilters(instance, context)) { if (context == null) { // if there is no context we can do an early exit return false; } result = false; } // if only exclude filters will be supplied then, it should return true // for other types. if (result == null && this.unconditionalFilters.size() == 0 && this.typeFilters.size() == 0) { return true; } if (unconditionalFilters.isEmpty()) { // only type filters - anything isn't rejected may pass if (context == null) { // if there is no context we can do a direct exit return applyTypedFilter(instance, context, true); } else { // otherwise we need to make sure that every filter may be // called (and respect previous exclusion) if (applyTypedFilter(instance, context, true)) { if (result == null) { // only set to true if not excluded result = true; } } if (result == null) { result = false; } return result; } } // applying remaining filters one by one (also handling lazy evaluation) if (context == null) { // if there is no context we can do an early exit return applyTypedFilter(instance, context) || applyUnconditionalFilter(instance, context); } else { // otherwise we need to make sure that every filter may be called // (and respect previous exclusion) if (applyTypedFilter(instance, context)) { if (result == null) { // only set to true if not excluded result = true; } } if (applyUnconditionalFilter(instance, context)) { if (result == null) { // only set to true if not excluded result = true; } } if (result == null) { result = false; } return result; } } private boolean applyUnconditionalFilter(Instance instance, Map<Object, Object> context) { boolean result = false; for (Filter filter : this.unconditionalFilters) { if (filter instanceof ContextAwareFilter) { if (((ContextAwareFilter) filter).match(instance, context)) { if (context == null) { // no side effects possible return true; } result = true; } } else if (!result && filter.match(instance)) { if (context == null) { // no side effects possible return true; } result = true; } } return result; } private boolean applyTypedFilter(Instance instance, Map<Object, Object> context) { return applyTypedFilter(instance, context, false); } private boolean applyTypedFilter(Instance instance, Map<Object, Object> context, boolean acceptIfNoTypeMatch) { Boolean result = null; // Loop through all the type filters for (String type : this.typeFilters.keySet()) { if (checkType(instance.getDefinition(), type)) { // instance equals type name for (Filter filter : this.typeFilters.get(type)) { if (filter instanceof ContextAwareFilter) { if (((ContextAwareFilter) filter).match(instance, context)) { if (context == null) { return true; // no side effects possible } result = true; } } else if (result == null && filter.match(instance)) { if (context == null) { return true; // no side effects possible } result = true; } } if (context == null) { // no side effects possible // So, instance matches type and but does not match filter return false; } if (result == null) { result = false; } } } // it reaches here that means Instance does not match any type of // type filters. Method should return false for lazy evaluation. if (result == null) { result = acceptIfNoTypeMatch; } return result; } private boolean applyExcludeFilters(Instance instance, Map<Object, Object> context) { Boolean rejected = false; for (Filter filter : excludeFilters) { if (filter instanceof ContextAwareFilter) { if (((ContextAwareFilter) filter).match(instance, context)) { if (context == null) { return true; // no side effects possible } rejected = true; } } else if (!rejected && filter.match(instance)) { if (context == null) { return true; // no side effects possible } rejected = true; } } if (rejected) { return true; } // for excluded Types for (String excludedType : this.excludedTypes) { if (checkType(instance.getDefinition(), excludedType)) { return true; } } // instance does not match any excluded types. return false; } // Checking type of instance with givenType. private boolean checkType(TypeDefinition instanceType, String givenType) { // Here givenType is supplied by user QName qNameOfGivenType = QName.valueOf(givenType); if (instanceType.getName().equals(qNameOfGivenType)) return true; if (instanceType.getDisplayName().equals(givenType)) return true; for (XmlElement element : instanceType.getConstraint(XmlElements.class).getElements()) { if (element.getName().equals(qNameOfGivenType)) return true; } // does not matched with all the above cases, return false return false; } }