/*
* Copyright (c) 2010-2016 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.schema.util;
import java.util.*;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.query.*;
import com.evolveum.midpoint.prism.query.Visitor;
import com.evolveum.midpoint.prism.query.builder.QueryBuilder;
import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterExit;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.mutable.MutableBoolean;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.polystring.PolyStringNormalizer;
import com.evolveum.midpoint.prism.polystring.PrismDefaultPolyStringNormalizer;
import com.evolveum.midpoint.schema.ResourceShadowDiscriminator;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
import com.evolveum.prism.xml.ns._public.query_3.QueryType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import org.jetbrains.annotations.NotNull;
public class ObjectQueryUtil {
public static ObjectQuery createNameQuery(String name, PrismContext prismContext) throws SchemaException {
PolyString polyName = new PolyString(name);
return createNameQuery(polyName, prismContext);
}
public static ObjectQuery createOrigNameQuery(String name, PrismContext prismContext) throws SchemaException {
PolyString polyName = new PolyString(name);
return createOrigNameQuery(polyName, prismContext);
}
public static ObjectQuery createNormNameQuery(String name, PrismContext prismContext) throws SchemaException {
PolyString polyName = new PolyString(name);
return createNormNameQuery(polyName, prismContext);
}
public static ObjectQuery createNameQuery(PolyStringType name, PrismContext prismContext) throws SchemaException {
return createNameQuery(name.toPolyString(), prismContext);
}
public static ObjectQuery createOrigNameQuery(PolyStringType name, PrismContext prismContext) throws SchemaException {
return createOrigNameQuery(name.toPolyString(), prismContext);
}
public static ObjectQuery createNameQuery(PolyString name, PrismContext prismContext) throws SchemaException {
return QueryBuilder.queryFor(ObjectType.class, prismContext)
.item(ObjectType.F_NAME).eq(name)
.build();
}
public static ObjectQuery createOrigNameQuery(PolyString name, PrismContext prismContext) throws SchemaException {
return QueryBuilder.queryFor(ObjectType.class, prismContext)
.item(ObjectType.F_NAME).eq(name).matchingOrig()
.build();
}
public static ObjectQuery createNormNameQuery(PolyString name, PrismContext prismContext) throws SchemaException {
PolyStringNormalizer normalizer = new PrismDefaultPolyStringNormalizer();
name.recompute(normalizer);
return QueryBuilder.queryFor(ObjectType.class, prismContext)
.item(ObjectType.F_NAME).eq(name).matchingNorm()
.build();
}
public static ObjectQuery createNameQuery(ObjectType object) throws SchemaException {
return createNameQuery(object.getName(), object.asPrismObject().getPrismContext());
}
public static <O extends ObjectType> ObjectQuery createNameQuery(PrismObject<O> object) throws SchemaException {
return createNameQuery(object.asObjectable().getName(), object.getPrismContext());
}
public static ObjectQuery createResourceAndObjectClassQuery(String resourceOid, QName objectClass, PrismContext prismContext) throws SchemaException {
return ObjectQuery.createObjectQuery(createResourceAndObjectClassFilter(resourceOid, objectClass, prismContext));
}
public static ObjectFilter createResourceAndObjectClassFilter(String resourceOid, QName objectClass, PrismContext prismContext) throws SchemaException {
Validate.notNull(resourceOid, "Resource where to search must not be null.");
Validate.notNull(objectClass, "Object class to search must not be null.");
Validate.notNull(prismContext, "Prism context must not be null.");
AndFilter and = AndFilter.createAnd(
createResourceFilter(resourceOid, prismContext),
createObjectClassFilter(objectClass, prismContext));
return and;
}
public static S_AtomicFilterExit createResourceAndObjectClassFilterPrefix(String resourceOid, QName objectClass, PrismContext prismContext) throws SchemaException {
Validate.notNull(resourceOid, "Resource where to search must not be null.");
Validate.notNull(objectClass, "Object class to search must not be null.");
Validate.notNull(prismContext, "Prism context must not be null.");
return QueryBuilder.queryFor(ShadowType.class, prismContext)
.item(ShadowType.F_RESOURCE_REF).ref(resourceOid)
.and().item(ShadowType.F_OBJECT_CLASS).eq(objectClass);
}
public static ObjectQuery createResourceAndKindIntent(String resourceOid, ShadowKindType kind, String intent, PrismContext prismContext) throws SchemaException {
return ObjectQuery.createObjectQuery(createResourceAndKindIntentFilter(resourceOid, kind, intent, prismContext));
}
public static ObjectQuery createResourceAndKind(String resourceOid, ShadowKindType kind, PrismContext prismContext) throws SchemaException {
return ObjectQuery.createObjectQuery(createResourceAndKindFilter(resourceOid, kind, prismContext));
}
public static ObjectFilter createResourceAndKindIntentFilter(String resourceOid, ShadowKindType kind, String intent, PrismContext prismContext) throws SchemaException {
Validate.notNull(resourceOid, "Resource where to search must not be null.");
Validate.notNull(kind, "Kind to search must not be null.");
Validate.notNull(prismContext, "Prism context must not be null.");
return QueryBuilder.queryFor(ShadowType.class, prismContext)
.item(ShadowType.F_RESOURCE_REF).ref(resourceOid)
.and().item(ShadowType.F_KIND).eq(kind)
.and().item(ShadowType.F_INTENT).eq(intent)
.buildFilter();
}
private static ObjectFilter createResourceAndKindFilter(String resourceOid, ShadowKindType kind, PrismContext prismContext) throws SchemaException {
Validate.notNull(resourceOid, "Resource where to search must not be null.");
Validate.notNull(kind, "Kind to search must not be null.");
Validate.notNull(prismContext, "Prism context must not be null.");
return QueryBuilder.queryFor(ShadowType.class, prismContext)
.item(ShadowType.F_RESOURCE_REF).ref(resourceOid)
.and().item(ShadowType.F_KIND).eq(kind)
.buildFilter();
}
public static ObjectQuery createResourceQuery(String resourceOid, PrismContext prismContext) throws SchemaException {
Validate.notNull(resourceOid, "Resource where to search must not be null.");
Validate.notNull(prismContext, "Prism context must not be null.");
return ObjectQuery.createObjectQuery(createResourceFilter(resourceOid, prismContext));
}
public static ObjectFilter createResourceFilter(String resourceOid, PrismContext prismContext) throws SchemaException {
return QueryBuilder.queryFor(ShadowType.class, prismContext)
.item(ShadowType.F_RESOURCE_REF).ref(resourceOid)
.buildFilter();
}
public static ObjectFilter createObjectClassFilter(QName objectClass, PrismContext prismContext) {
return QueryBuilder.queryFor(ShadowType.class, prismContext)
.item(ShadowType.F_OBJECT_CLASS).eq(objectClass)
.buildFilter();
}
public static <T extends ObjectType> ObjectQuery createNameQuery(Class<T> clazz, PrismContext prismContext, String name) throws SchemaException {
return QueryBuilder.queryFor(clazz, prismContext)
.item(ObjectType.F_NAME).eqPoly(name)
.build();
}
public static ObjectQuery createRootOrgQuery(PrismContext prismContext) throws SchemaException {
return QueryBuilder.queryFor(ObjectType.class, prismContext).isRoot().build();
}
public static boolean hasAllDefinitions(ObjectQuery query) {
return hasAllDefinitions(query.getFilter());
}
public static boolean hasAllDefinitions(ObjectFilter filter) {
final MutableBoolean hasAllDefinitions = new MutableBoolean(true);
Visitor visitor = f -> {
if (f instanceof ValueFilter) {
ItemDefinition definition = ((ValueFilter<?,?>) f).getDefinition();
if (definition == null) {
hasAllDefinitions.setValue(false);
}
}
};
filter.accept(visitor);
return hasAllDefinitions.booleanValue();
}
// TODO what about OidIn here?
public static void assertPropertyOnly(ObjectFilter filter, final String message) {
Visitor visitor = f -> {
if (f instanceof OrgFilter) {
if (message == null) {
throw new IllegalArgumentException(f.toString());
} else {
throw new IllegalArgumentException(message+": "+ f);
}
}
};
filter.accept(visitor);
}
public static void assertNotRaw(ObjectFilter filter, final String message) {
Visitor visitor = f -> {
if (f instanceof ValueFilter && ((ValueFilter) f).isRaw()) {
if (message == null) {
throw new IllegalArgumentException(f.toString());
} else {
throw new IllegalArgumentException(message+": "+ f);
}
}
};
filter.accept(visitor);
}
public static String dump(QueryType query, @NotNull PrismContext prismContext) throws SchemaException {
if (query == null) {
return "null";
}
StringBuilder sb = new StringBuilder("Query(");
sb.append(query.getDescription()).append("):\n");
if (query.getFilter() != null && query.getFilter().containsFilterClause())
sb.append(DOMUtil.serializeDOMToString(query.getFilter().getFilterClauseAsElement(prismContext)));
else
sb.append("(no filter)");
return sb.toString();
}
/**
* Merges the two provided arguments into one AND filter in the most efficient way.
*/
public static ObjectFilter filterAnd(ObjectFilter origFilter, ObjectFilter additionalFilter) {
if (origFilter == additionalFilter) {
// AND with itself
return origFilter;
}
if (origFilter == null) {
return additionalFilter;
}
if (additionalFilter == null) {
return origFilter;
}
if (origFilter instanceof NoneFilter) {
return origFilter;
}
if (additionalFilter instanceof NoneFilter) {
return additionalFilter;
}
if (origFilter instanceof AllFilter) {
return additionalFilter;
}
if (additionalFilter instanceof AllFilter) {
return origFilter;
}
if (origFilter instanceof AndFilter) {
if (!((AndFilter)origFilter).contains(additionalFilter)) {
((AndFilter)origFilter).addCondition(additionalFilter);
}
return origFilter;
}
return AndFilter.createAnd(origFilter, additionalFilter);
}
/**
* Merges the two provided arguments into one OR filter in the most efficient way.
*/
public static ObjectFilter filterOr(ObjectFilter origFilter, ObjectFilter additionalFilter) {
if (origFilter == additionalFilter) {
// OR with itself
return origFilter;
}
if (origFilter == null) {
return additionalFilter;
}
if (additionalFilter == null) {
return origFilter;
}
if (origFilter instanceof AllFilter) {
return origFilter;
}
if (additionalFilter instanceof AllFilter) {
return additionalFilter;
}
if (origFilter instanceof NoneFilter) {
return additionalFilter;
}
if (additionalFilter instanceof NoneFilter) {
return origFilter;
}
if (origFilter instanceof OrFilter) {
if (!((OrFilter)origFilter).contains(additionalFilter)) {
((OrFilter)origFilter).addCondition(additionalFilter);
}
return origFilter;
}
return OrFilter.createOr(origFilter, additionalFilter);
}
public static boolean isAll(ObjectFilter filter) {
return filter == null || filter instanceof AllFilter;
}
public static boolean isNone(ObjectFilter filter) {
return filter instanceof NoneFilter;
}
// returns ALL, NONE only at the top level (never inside the filter)
// never returns UNDEFINED
public static ObjectFilter simplify(ObjectFilter filter) {
if (filter == null) {
return null;
}
if (filter instanceof AndFilter) {
List<ObjectFilter> conditions = ((AndFilter)filter).getConditions();
AndFilter simplifiedFilter = ((AndFilter)filter).cloneEmpty();
for (ObjectFilter subfilter: conditions) {
if (subfilter instanceof NoneFilter) {
// AND with "false"
return NoneFilter.createNone();
} else if (subfilter instanceof AllFilter || subfilter instanceof UndefinedFilter) {
// AND with "true", just skip it
} else {
ObjectFilter simplifiedSubfilter = simplify(subfilter);
if (simplifiedSubfilter instanceof NoneFilter) {
return NoneFilter.createNone();
} else if (simplifiedSubfilter == null || simplifiedSubfilter instanceof AllFilter) {
// skip
} else {
simplifiedFilter.addCondition(simplifiedSubfilter);
}
}
}
if (simplifiedFilter.isEmpty()) {
return AllFilter.createAll();
} else if (simplifiedFilter.getConditions().size() == 1) {
return simplifiedFilter.getConditions().iterator().next();
} else {
return simplifiedFilter;
}
} else if (filter instanceof OrFilter) {
List<ObjectFilter> conditions = ((OrFilter)filter).getConditions();
OrFilter simplifiedFilter = ((OrFilter)filter).cloneEmpty();
for (ObjectFilter subfilter: conditions) {
if (subfilter instanceof NoneFilter || subfilter instanceof UndefinedFilter) {
// OR with "false", just skip it
} else if (subfilter instanceof AllFilter) {
// OR with "true"
return AllFilter.createAll();
} else {
ObjectFilter simplifiedSubfilter = simplify(subfilter);
if (simplifiedSubfilter instanceof NoneFilter) {
// skip
} else if (simplifiedSubfilter == null || simplifiedSubfilter instanceof AllFilter) {
return NoneFilter.createNone();
} else {
simplifiedFilter.addCondition(simplifiedSubfilter);
}
}
}
if (simplifiedFilter.isEmpty()) {
return NoneFilter.createNone();
} else if (simplifiedFilter.getConditions().size() == 1) {
return simplifiedFilter.getConditions().iterator().next();
} else {
return simplifiedFilter;
}
} else if (filter instanceof NotFilter) {
ObjectFilter subfilter = ((NotFilter)filter).getFilter();
ObjectFilter simplifiedSubfilter = simplify(subfilter);
if (simplifiedSubfilter instanceof NoneFilter) {
return AllFilter.createAll();
} else if (simplifiedSubfilter == null || simplifiedSubfilter instanceof AllFilter) {
return NoneFilter.createNone();
} else {
NotFilter simplifiedFilter = ((NotFilter)filter).cloneEmpty();
simplifiedFilter.setFilter(simplifiedSubfilter);
return simplifiedFilter;
}
} else if (filter instanceof TypeFilter) {
ObjectFilter subFilter = ((TypeFilter) filter).getFilter();
ObjectFilter simplifiedSubfilter = simplify(subFilter);
if (simplifiedSubfilter instanceof AllFilter) {
simplifiedSubfilter = null;
} else if (simplifiedSubfilter instanceof NoneFilter) {
return NoneFilter.createNone();
}
TypeFilter simplifiedFilter = ((TypeFilter) filter).cloneEmpty();
simplifiedFilter.setFilter(simplifiedSubfilter);
return simplifiedFilter;
} else if (filter instanceof ExistsFilter) {
ObjectFilter subFilter = ((ExistsFilter) filter).getFilter();
ObjectFilter simplifiedSubfilter = simplify(subFilter);
if (simplifiedSubfilter instanceof AllFilter) {
simplifiedSubfilter = null;
} else if (simplifiedSubfilter instanceof NoneFilter) {
return NoneFilter.createNone();
}
ExistsFilter simplifiedFilter = ((ExistsFilter) filter).cloneEmpty();
simplifiedFilter.setFilter(simplifiedSubfilter);
return simplifiedFilter;
} else if (filter instanceof UndefinedFilter || filter instanceof AllFilter) {
return null;
} else {
// Cannot simplify
return filter.clone();
}
}
public static PrismValue getValueFromQuery(ObjectQuery query, QName itemName) throws SchemaException {
if (query != null) {
return getValueFromFilter(query.getFilter(), itemName);
} else {
return null;
}
}
public static <T extends PrismValue> Collection<T> getValuesFromQuery(ObjectQuery query, QName itemName) throws SchemaException {
if (query != null) {
return getValuesFromFilter(query.getFilter(), itemName);
} else {
return null;
}
}
private static PrismValue getValueFromFilter(ObjectFilter filter, QName itemName) throws SchemaException {
Collection<PrismValue> values = getValuesFromFilter(filter, itemName);
if (values == null || values.size() == 0) {
return null;
} else if (values.size() > 1) {
throw new SchemaException("More than one " + itemName + " defined in the search query.");
} else {
return values.iterator().next();
}
}
private static <T extends PrismValue> Collection<T> getValuesFromFilter(ObjectFilter filter, QName itemName) throws SchemaException {
ItemPath propertyPath = new ItemPath(itemName);
if (filter instanceof EqualFilter && propertyPath.equivalent(((EqualFilter) filter).getFullPath())) {
return ((EqualFilter) filter).getValues();
} else if (filter instanceof RefFilter && propertyPath.equivalent(((RefFilter) filter).getFullPath())) {
return (Collection<T>) ((RefFilter) filter).getValues();
} else if (filter instanceof AndFilter) {
return getValuesFromFilter(((NaryLogicalFilter) filter).getConditions(), itemName);
} else if (filter instanceof TypeFilter) {
return getValuesFromFilter(((TypeFilter) filter).getFilter(), itemName);
} else {
return null;
}
}
private static <T extends PrismValue> Collection<T> getValuesFromFilter(List<? extends ObjectFilter> conditions, QName propertyName)
throws SchemaException {
for (ObjectFilter f : conditions) {
Collection<T> values = getValuesFromFilter(f, propertyName);
if (values != null) {
return values;
}
}
return null;
}
private static String getResourceOidFromFilter(ObjectFilter filter) throws SchemaException {
PrismReferenceValue referenceValue = (PrismReferenceValue) getValueFromFilter(filter, ShadowType.F_RESOURCE_REF);
return referenceValue != null ? referenceValue.getOid() : null;
}
private static <T> T getPropertyRealValueFromFilter(ObjectFilter filter, QName propertyName) throws SchemaException {
PrismPropertyValue<T> propertyValue = (PrismPropertyValue<T>) getValueFromFilter(filter, propertyName);
return propertyValue != null ? propertyValue.getValue() : null;
}
public static ResourceShadowDiscriminator getCoordinates(ObjectFilter filter) throws SchemaException {
String resourceOid = getResourceOidFromFilter(filter);
QName objectClass = getPropertyRealValueFromFilter(filter, ShadowType.F_OBJECT_CLASS);
ShadowKindType kind = getPropertyRealValueFromFilter(filter, ShadowType.F_KIND);
String intent = getPropertyRealValueFromFilter(filter, ShadowType.F_INTENT);
if (resourceOid == null) {
throw new SchemaException("Resource not defined in a search query");
}
if (objectClass == null && kind == null) {
throw new SchemaException("Neither objectclass not kind is specified in a search query");
}
ResourceShadowDiscriminator coordinates = new ResourceShadowDiscriminator(resourceOid, kind, intent, false);
coordinates.setObjectClass(objectClass);
return coordinates;
}
public static FilterComponents factorOutQuery(ObjectQuery query, QName... names) {
return factorOutQuery(query, ItemPath.asPathArray(names));
}
public static FilterComponents factorOutQuery(ObjectQuery query, ItemPath... paths) {
return factorOutFilter(query != null ? query.getFilter() : null, paths);
}
public static FilterComponents factorOutFilter(ObjectFilter filter, ItemPath... paths) {
FilterComponents components = new FilterComponents();
factorOutFilter(components, simplify(filter), Arrays.asList(paths), true);
return components;
}
// TODO better API
public static FilterComponents factorOutOrFilter(ObjectFilter filter, ItemPath... paths) {
FilterComponents components = new FilterComponents();
factorOutFilter(components, simplify(filter), Arrays.asList(paths), false);
return components;
}
private static void factorOutFilter(FilterComponents filterComponents, ObjectFilter filter, List<ItemPath> paths, boolean connectedByAnd) {
if (filter instanceof EqualFilter) {
EqualFilter equalFilter = (EqualFilter) filter;
if (ItemPath.containsEquivalent(paths, equalFilter.getPath())) {
filterComponents.addToKnown(equalFilter.getPath(), equalFilter.getValues());
} else {
filterComponents.addToRemainder(equalFilter);
}
} else if (filter instanceof RefFilter) {
RefFilter refFilter = (RefFilter) filter;
if (ItemPath.containsEquivalent(paths, refFilter.getPath())) {
filterComponents.addToKnown(refFilter.getPath(), refFilter.getValues());
} else {
filterComponents.addToRemainder(refFilter);
}
} else if (connectedByAnd && filter instanceof AndFilter) {
for (ObjectFilter condition : ((AndFilter) filter).getConditions()) {
factorOutFilter(filterComponents, condition, paths, true);
}
} else if (!connectedByAnd && filter instanceof OrFilter) {
for (ObjectFilter condition : ((OrFilter) filter).getConditions()) {
factorOutFilter(filterComponents, condition, paths, false);
}
} else if (filter instanceof TypeFilter) {
// this is a bit questionable...
factorOutFilter(filterComponents, ((TypeFilter) filter).getFilter(), paths, connectedByAnd);
} else if (filter != null) {
filterComponents.addToRemainder(filter);
} else {
// nothing to do with a null filter
}
}
public static class FilterComponents {
private Map<ItemPath,Collection<? extends PrismValue>> knownComponents = new HashMap<>();
private List<ObjectFilter> remainderClauses = new ArrayList<>();
public Map<ItemPath, Collection<? extends PrismValue>> getKnownComponents() {
return knownComponents;
}
public ObjectFilter getRemainder() {
if (remainderClauses.size() == 0) {
return null;
} else if (remainderClauses.size() == 1) {
return remainderClauses.get(0);
} else {
return AndFilter.createAnd(remainderClauses);
}
}
public void addToKnown(ItemPath path, List values) {
Map.Entry<ItemPath, Collection<? extends PrismValue>> entry = getKnownComponent(path);
if (entry != null) {
entry.setValue(CollectionUtils.intersection(entry.getValue(), values));
} else {
knownComponents.put(path, values);
}
}
public Map.Entry<ItemPath, Collection<? extends PrismValue>> getKnownComponent(ItemPath path) {
for (Map.Entry<ItemPath, Collection<? extends PrismValue>> entry : knownComponents.entrySet()) {
if (path.equivalent(entry.getKey())) {
return entry;
}
}
return null;
}
public void addToRemainder(ObjectFilter filter) {
remainderClauses.add(filter);
}
public boolean hasRemainder() {
return !remainderClauses.isEmpty();
}
public List<ObjectFilter> getRemainderClauses() {
return remainderClauses;
}
}
}