/* * Copyright (c) 2010-2015 Evolveum * * 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.evolveum.midpoint.prism.query; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.match.MatchingRule; import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.ItemPathSegment; import com.evolveum.midpoint.prism.path.NameItemPathSegment; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.SchemaException; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.List; import java.util.Objects; public abstract class ValueFilter<V extends PrismValue, D extends ItemDefinition> extends ObjectFilter implements Itemable { private static final long serialVersionUID = 1L; @NotNull private final ItemPath fullPath; // This is a definition of the item pointed to by "fullPath" // (not marked as @NotNull, because it can be filled-in after creation of the filter - e.g. in provisioning) @Nullable private D definition; @Nullable private QName matchingRule; @Nullable private List<V> values; @Nullable private ExpressionWrapper expression; @Nullable private ItemPath rightHandSidePath; // alternative to values/expression; can be provided later @Nullable private ItemDefinition rightHandSideDefinition; // optional (needed only if path points to dynamically defined item) // At most one of values, expression, rightHandSidePath can be non-null. // It is a responsibility of the client to ensure it. /** * TODO decide whether to make these fields final. It makes the code simpler, but maybe not that much * that it is worth the discomfort of the clients (they cannot change they if the would wish). * Some of them like definition, matchingRule, and right-hand things are filled-in later in some cases (provisioning, query builder). */ protected ValueFilter(@NotNull ItemPath fullPath, @Nullable D definition) { this(fullPath, definition, null, null, null, null, null); } protected ValueFilter(@NotNull ItemPath fullPath, @Nullable D definition, @Nullable QName matchingRule, @Nullable List<V> values, @Nullable ExpressionWrapper expression, @Nullable ItemPath rightHandSidePath, @Nullable ItemDefinition rightHandSideDefinition) { Validate.isTrue(!ItemPath.isNullOrEmpty(fullPath), "path in filter is null or empty"); this.fullPath = fullPath; this.definition = definition; this.matchingRule = matchingRule; this.expression = expression; this.values = values; this.rightHandSidePath = rightHandSidePath; this.rightHandSideDefinition = rightHandSideDefinition; if (values != null) { for (V value : values) { value.setParent(this); } } checkConsistence(false); } @NotNull public ItemPath getFullPath() { return fullPath; } @NotNull public ItemPath getParentPath() { return fullPath.allExceptLast(); } @NotNull public QName getElementName() { if (definition != null) { return definition.getName(); // this is more precise, as the name in path can be unqualified } ItemPathSegment lastPathSegement = fullPath.last(); if (lastPathSegement instanceof NameItemPathSegment) { return ((NameItemPathSegment)lastPathSegement).getName(); } else if (lastPathSegement == null) { throw new IllegalStateException("Empty full path in filter "+this); } else { throw new IllegalStateException("Got "+lastPathSegement+" as a last path segment in value filter "+this); } } @Nullable public D getDefinition() { return definition; } public void setDefinition(@Nullable D definition) { this.definition = definition; checkConsistence(false); } @Nullable public QName getMatchingRule() { return matchingRule; } public void setMatchingRule(@Nullable QName matchingRule) { this.matchingRule = matchingRule; } @NotNull MatchingRule getMatchingRuleFromRegistry(MatchingRuleRegistry matchingRuleRegistry, Item filterItem) { try { return matchingRuleRegistry.getMatchingRule(matchingRule, filterItem.getDefinition().getTypeName()); } catch (SchemaException ex){ throw new IllegalArgumentException(ex.getMessage(), ex); } } @Nullable public List<V> getValues() { return values; } @Nullable List<V> getClonedValues() { if (values == null) { return null; } else { List<V> clonedValues = new ArrayList<>(values.size()); for (V value : values) { @SuppressWarnings("unchecked") V cloned = (V) value.clone(); clonedValues.add(cloned); } return clonedValues; } } @Nullable V getClonedValue() { V value = getSingleValue(); if (value == null) { return null; } else { @SuppressWarnings("unchecked") V cloned = (V) value.clone(); return cloned; } } @Nullable public V getSingleValue() { if (values == null || values.isEmpty()) { return null; } else if (values.size() > 1) { throw new IllegalArgumentException("Filter '" + this + "' should contain at most one value, but it has " + values.size() + " of them."); } else { return values.iterator().next(); } } /** * @pre value has to be parent-less */ public void setValue(V value) { this.values = new ArrayList<>(); if (value != null) { value.setParent(this); values.add(value); } } @Nullable public ExpressionWrapper getExpression() { return expression; } public void setExpression(@Nullable ExpressionWrapper expression) { this.expression = expression; } @Nullable public ItemPath getRightHandSidePath() { return rightHandSidePath; } public void setRightHandSidePath(@Nullable ItemPath rightHandSidePath) { this.rightHandSidePath = rightHandSidePath; } @Nullable public ItemDefinition getRightHandSideDefinition() { return rightHandSideDefinition; } public void setRightHandSideDefinition(@Nullable ItemDefinition rightHandSideDefinition) { this.rightHandSideDefinition = rightHandSideDefinition; } @Override public PrismContext getPrismContext() { if (super.getPrismContext() != null) { return super.getPrismContext(); } D def = getDefinition(); if (def != null && def.getPrismContext() != null) { PrismContext prismContext = def.getPrismContext(); super.setPrismContext(prismContext); return prismContext; } else { return null; } } @Override public ItemPath getPath() { return getFullPath(); } public boolean isRaw() { if (values != null) { for (V value: values) { if (value.isRaw()) { return true; } } } return false; } // TODO revise @Override public boolean match(PrismContainerValue cvalue, MatchingRuleRegistry matchingRuleRegistry) throws SchemaException { Item objectItem = getObjectItem(cvalue); boolean filterItemIsEmpty = getValues() == null || getValues().isEmpty(); boolean objectItemIsEmpty = objectItem == null || objectItem.isEmpty(); if (filterItemIsEmpty && !objectItemIsEmpty) { return false; } if (!filterItemIsEmpty && objectItemIsEmpty) { return false; } return true; } // TODO revise Item getObjectItem(PrismContainerValue value) { ItemPath path = getFullPath(); return value.findItem(path); } // TODO revise @NotNull Item getFilterItem() throws SchemaException { if (getDefinition() == null){ throw new SchemaException("Could not find definition for item " + getPath()); } Item filterItem = getDefinition().instantiate(); if (getValues() != null && !getValues().isEmpty()) { try { for (PrismValue v : getValues()){ filterItem.add(v.clone()); } } catch (SchemaException e) { throw new IllegalArgumentException(e.getMessage(), e); } } return filterItem; } @Override public abstract ValueFilter clone(); @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(Object o) { return equals(o, true); } @Override public boolean equals(Object o, boolean exact) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ValueFilter<?, ?> that = (ValueFilter<?, ?>) o; return fullPath.equals(that.fullPath, exact) && (!exact || Objects.equals(definition, that.definition)) && Objects.equals(matchingRule, that.matchingRule) && MiscUtil.nullableCollectionsEqual(values, that.values) && Objects.equals(expression, that.expression) && (rightHandSidePath == null && that.rightHandSidePath == null || rightHandSidePath != null && rightHandSidePath.equals(that.rightHandSidePath, exact)) && (!exact || Objects.equals(rightHandSideDefinition, that.rightHandSideDefinition)); } @Override public int hashCode() { return Objects.hash(fullPath, matchingRule, values, expression, rightHandSidePath); } @Override public String debugDump() { return debugDump(0); } @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); DebugUtil.indentDebugDump(sb, indent); sb.append(getFilterName()).append(":"); return debugDump(indent, sb); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getFilterName()).append(": "); return toString(sb); } protected abstract String getFilterName(); protected String debugDump(int indent, StringBuilder sb) { sb.append("\n"); DebugUtil.indentDebugDump(sb, indent+1); sb.append("PATH: "); sb.append(getFullPath().toString()); sb.append("\n"); DebugUtil.indentDebugDump(sb, indent+1); sb.append("DEF: "); if (getDefinition() != null) { sb.append(getDefinition().toString()); } else { sb.append("null"); } List<V> values = getValues(); if (values != null) { sb.append("\n"); DebugUtil.indentDebugDump(sb, indent+1); sb.append("VALUE:"); for (PrismValue val : getValues()) { sb.append("\n"); sb.append(DebugUtil.debugDump(val, indent + 2)); } } ExpressionWrapper expression = getExpression(); if (expression != null && expression.getExpression() != null) { sb.append("\n"); DebugUtil.indentDebugDump(sb, indent+1); sb.append("EXPRESSION:"); sb.append("\n"); sb.append(DebugUtil.debugDump(expression.getExpression(), indent + 2)); } if (getRightHandSidePath() != null) { sb.append("\n"); DebugUtil.indentDebugDump(sb, indent+1); sb.append("RIGHT SIDE PATH: "); sb.append(getFullPath().toString()); sb.append("\n"); DebugUtil.indentDebugDump(sb, indent+1); sb.append("RIGHT SIDE DEF: "); if (getRightHandSideDefinition() != null) { sb.append(getRightHandSideDefinition().toString()); } else { sb.append("null"); } } QName matchingRule = getMatchingRule(); if (matchingRule != null) { sb.append("\n"); DebugUtil.indentDebugDump(sb, indent+1); sb.append("MATCHING: "); sb.append(matchingRule); } return sb.toString(); } protected String toString(StringBuilder sb){ sb.append(getFullPath().toString()); sb.append(","); if (getValues() != null){ for (int i = 0; i< getValues().size() ; i++){ PrismValue value = getValues().get(i); if (value == null) { sb.append("null"); } else { sb.append(value.toString()); } if ( i != getValues().size() -1){ sb.append(","); } } } if (getRightHandSidePath() != null) { sb.append(getRightHandSidePath()); } return sb.toString(); } @Override public void checkConsistence(boolean requireDefinitions) { if (requireDefinitions && definition == null) { throw new IllegalArgumentException("Null definition in "+this); } if (fullPath.isEmpty()) { throw new IllegalArgumentException("Empty path in "+this); } if (!(fullPath.last() instanceof NameItemPathSegment)) { //noinspection ConstantConditions throw new IllegalArgumentException("Last segment of item path is not a name segment: " + fullPath + " (it is " + fullPath.last().getClass().getName() + ")"); } if (rightHandSidePath != null && rightHandSidePath.isEmpty()) { throw new IllegalArgumentException("Not-null but empty right side path in "+this); } int count = 0; if (values != null) { count++; } if (expression != null) { count++; } if (rightHandSidePath != null) { count++; } if (count > 1) { throw new IllegalStateException("Two or more of the following are non-null: values (" + values + "), expression (" + expression + "), rightHandSidePath (" + rightHandSidePath + ") in " + this); } if (values != null) { for (V value: values) { if (value == null) { throw new IllegalArgumentException("Null value in "+this); } if (value.getParent() != this) { throw new IllegalArgumentException("Value "+value+" in "+this+" has a bad parent "+value.getParent()); } if (value.isEmpty() && !value.isRaw()) { throw new IllegalArgumentException("Empty value in "+this); } } } if (definition != null) { if (!QNameUtil.match(definition.getName(), fullPath.lastNamed().getName())) { throw new IllegalArgumentException("Last segment of item path (" + fullPath.lastNamed().getName() + ") " + "does not match item name from the definition: " + definition); } } } }