/* * 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.builder; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.query.*; import org.apache.commons.lang.Validate; import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.List; /** * EXPERIMENTAL IMPLEMENTATION. * * @author mederly */ public class R_Filter implements S_FilterEntryOrEmpty, S_AtomicFilterExit { final private QueryBuilder queryBuilder; final private Class<? extends Containerable> currentClass; // object we are working on (changes on Exists filter) final private OrFilter currentFilter; final private LogicalSymbol lastLogicalSymbol; final private boolean isNegated; final private R_Filter parentFilter; final private QName typeRestriction; final private ItemPath existsRestriction; final private List<ObjectOrdering> orderingList; final private Integer offset; final private Integer maxSize; public R_Filter(QueryBuilder queryBuilder) { this.queryBuilder = queryBuilder; this.currentClass = queryBuilder.getQueryClass(); this.currentFilter = OrFilter.createOr(); this.lastLogicalSymbol = null; this.isNegated = false; this.parentFilter = null; this.typeRestriction = null; this.existsRestriction = null; this.orderingList = new ArrayList<>(); this.offset = null; this.maxSize = null; } private R_Filter(QueryBuilder queryBuilder, Class<? extends Containerable> currentClass, OrFilter currentFilter, LogicalSymbol lastLogicalSymbol, boolean isNegated, R_Filter parentFilter, QName typeRestriction, ItemPath existsRestriction, List<ObjectOrdering> orderingList, Integer offset, Integer maxSize) { this.queryBuilder = queryBuilder; this.currentClass = currentClass; this.currentFilter = currentFilter; this.lastLogicalSymbol = lastLogicalSymbol; this.isNegated = isNegated; this.parentFilter = parentFilter; this.typeRestriction = typeRestriction; this.existsRestriction = existsRestriction; if (orderingList != null) { this.orderingList = orderingList; } else { this.orderingList = new ArrayList<>(); } this.offset = offset; this.maxSize = maxSize; } public static S_FilterEntryOrEmpty create(QueryBuilder builder) { return new R_Filter(builder); } // subfilter might be null R_Filter addSubfilter(ObjectFilter subfilter) { if (!currentFilter.isEmpty() && lastLogicalSymbol == null) { throw new IllegalStateException("lastLogicalSymbol is empty but there is already some filter present: " + currentFilter); } if (typeRestriction != null && existsRestriction != null) { throw new IllegalStateException("Both type and exists restrictions present"); } if (typeRestriction != null) { if (!currentFilter.isEmpty()) { throw new IllegalStateException("Type restriction with 2 filters?"); } if (isNegated) { subfilter = new NotFilter(subfilter); } return parentFilter.addSubfilter(TypeFilter.createType(typeRestriction, subfilter)); } else if (existsRestriction != null) { if (!currentFilter.isEmpty()) { throw new IllegalStateException("Exists restriction with 2 filters?"); } if (isNegated) { subfilter = new NotFilter(subfilter); } return parentFilter.addSubfilter( ExistsFilter.createExists( existsRestriction, parentFilter.currentClass, queryBuilder.getPrismContext(), subfilter)); } else { OrFilter newFilter = appendAtomicFilter(subfilter, isNegated, lastLogicalSymbol); return new R_Filter(queryBuilder, currentClass, newFilter, null, false, parentFilter, typeRestriction, existsRestriction, orderingList, offset, maxSize); } } private OrFilter appendAtomicFilter(ObjectFilter subfilter, boolean negated, LogicalSymbol logicalSymbol) { if (negated) { subfilter = new NotFilter(subfilter); } OrFilter updatedFilter = currentFilter.clone(); if (logicalSymbol == null || logicalSymbol == LogicalSymbol.OR) { updatedFilter.addCondition(AndFilter.createAnd(subfilter)); } else if (logicalSymbol == LogicalSymbol.AND) { ((AndFilter) updatedFilter.getLastCondition()).addCondition(subfilter); } else { throw new IllegalStateException("Unknown logical symbol: " + logicalSymbol); } return updatedFilter; } private R_Filter setLastLogicalSymbol(LogicalSymbol newLogicalSymbol) { if (this.lastLogicalSymbol != null) { throw new IllegalStateException("Two logical symbols in a sequence"); } return new R_Filter(queryBuilder, currentClass, currentFilter, newLogicalSymbol, isNegated, parentFilter, typeRestriction, existsRestriction, orderingList, offset, maxSize); } private R_Filter setNegated() { if (isNegated) { throw new IllegalStateException("Double negation"); } return new R_Filter(queryBuilder, currentClass, currentFilter, lastLogicalSymbol, true, parentFilter, typeRestriction, existsRestriction, orderingList, offset, maxSize); } private R_Filter addOrdering(ObjectOrdering ordering) { Validate.notNull(ordering); List<ObjectOrdering> newList = new ArrayList<>(orderingList); newList.add(ordering); return new R_Filter(queryBuilder, currentClass, currentFilter, lastLogicalSymbol, isNegated, parentFilter, typeRestriction, existsRestriction, newList, offset, maxSize); } private R_Filter setOffset(Integer n) { return new R_Filter(queryBuilder, currentClass, currentFilter, lastLogicalSymbol, isNegated, parentFilter, typeRestriction, existsRestriction, orderingList, n, maxSize); } private R_Filter setMaxSize(Integer n) { return new R_Filter(queryBuilder, currentClass, currentFilter, lastLogicalSymbol, isNegated, parentFilter, typeRestriction, existsRestriction, orderingList, offset, n); } @Override public S_AtomicFilterExit all() { return addSubfilter(AllFilter.createAll()); } @Override public S_AtomicFilterExit none() { return addSubfilter(NoneFilter.createNone()); } @Override public S_AtomicFilterExit undefined() { return addSubfilter(UndefinedFilter.createUndefined()); } // TODO ............................................. @Override public S_AtomicFilterExit id(String... identifiers) { return addSubfilter(InOidFilter.createInOid(identifiers)); } @Override public S_AtomicFilterExit id(long... identifiers) { List<String> ids = longsToStrings(identifiers); return addSubfilter(InOidFilter.createInOid(ids)); } private List<String> longsToStrings(long[] identifiers) { List<String> ids = new ArrayList<>(identifiers.length); for (long id : identifiers) { ids.add(String.valueOf(id)); } return ids; } @Override public S_AtomicFilterExit ownerId(String... identifiers) { return addSubfilter(InOidFilter.createOwnerHasOidIn(identifiers)); } @Override public S_AtomicFilterExit ownerId(long... identifiers) { return addSubfilter(InOidFilter.createOwnerHasOidIn(longsToStrings(identifiers))); } @Override public S_AtomicFilterExit isDirectChildOf(PrismReferenceValue value) { OrgFilter orgFilter = OrgFilter.createOrg(value, OrgFilter.Scope.ONE_LEVEL); return addSubfilter(orgFilter); } @Override public S_AtomicFilterExit isChildOf(PrismReferenceValue value) { OrgFilter orgFilter = OrgFilter.createOrg(value, OrgFilter.Scope.SUBTREE); return addSubfilter(orgFilter); } @Override public S_AtomicFilterExit isParentOf(PrismReferenceValue value) { OrgFilter orgFilter = OrgFilter.createOrg(value, OrgFilter.Scope.ANCESTORS); return addSubfilter(orgFilter); } @Override public S_AtomicFilterExit isDirectChildOf(String oid) { OrgFilter orgFilter = OrgFilter.createOrg(oid, OrgFilter.Scope.ONE_LEVEL); return addSubfilter(orgFilter); } @Override public S_AtomicFilterExit isChildOf(String oid) { OrgFilter orgFilter = OrgFilter.createOrg(oid, OrgFilter.Scope.SUBTREE); return addSubfilter(orgFilter); } @Override public S_AtomicFilterExit isInScopeOf(String oid, OrgFilter.Scope scope) { return addSubfilter(OrgFilter.createOrg(oid, scope)); } @Override public S_AtomicFilterExit isInScopeOf(PrismReferenceValue value, OrgFilter.Scope scope) { return addSubfilter(OrgFilter.createOrg(value, scope)); } @Override public S_AtomicFilterExit isParentOf(String oid) { OrgFilter orgFilter = OrgFilter.createOrg(oid, OrgFilter.Scope.ANCESTORS); return addSubfilter(orgFilter); } @Override public S_AtomicFilterExit isRoot() { OrgFilter orgFilter = OrgFilter.createRootOrg(); return addSubfilter(orgFilter); } @Override public S_AtomicFilterExit fullText(String... words) { FullTextFilter fullTextFilter = FullTextFilter.createFullText(words); return addSubfilter(fullTextFilter); } @Override public S_FilterEntryOrEmpty block() { return new R_Filter(queryBuilder, currentClass, OrFilter.createOr(), null, false, this, null, null, null, null, null); } @Override public S_FilterEntryOrEmpty type(Class<? extends Containerable> type) { ComplexTypeDefinition ctd = queryBuilder.getPrismContext().getSchemaRegistry().findComplexTypeDefinitionByCompileTimeClass(type); if (ctd == null) { throw new IllegalArgumentException("Unknown type: " + type); } QName typeName = ctd.getTypeName(); if (typeName == null) { throw new IllegalStateException("No type name for " + ctd); } return new R_Filter(queryBuilder, type, OrFilter.createOr(), null, false, this, typeName, null, null, null, null); } @Override public S_FilterEntryOrEmpty exists(QName... names) { if (existsRestriction != null) { throw new IllegalStateException("Exists within exists"); } if (names.length == 0) { throw new IllegalArgumentException("Empty path in exists() filter is not allowed."); } ItemPath existsPath = new ItemPath(names); PrismContainerDefinition pcd = resolveItemPath(existsPath, PrismContainerDefinition.class); Class<? extends Containerable> clazz = pcd.getCompileTimeClass(); if (clazz == null) { throw new IllegalArgumentException("Item path of '" + existsPath + "' in " + currentClass + " does not point to a valid prism container."); } return new R_Filter(queryBuilder, clazz, OrFilter.createOr(), null, false, this, null, existsPath, null, null, null); } <ID extends ItemDefinition> ID resolveItemPath(ItemPath itemPath, Class<ID> type) { Validate.notNull(type, "type"); ComplexTypeDefinition ctd = queryBuilder.getPrismContext().getSchemaRegistry().findComplexTypeDefinitionByCompileTimeClass(currentClass); if (ctd == null) { throw new IllegalArgumentException("Definition for " + currentClass + " couldn't be found."); } ID definition = ctd.findItemDefinition(itemPath, type); if (definition == null) { throw new IllegalArgumentException("Item path of '" + itemPath + "' in " + currentClass + " does not point to a valid " + type.getSimpleName()); } return definition; } // END OF TODO ............................................. @Override public S_FilterEntry and() { return setLastLogicalSymbol(LogicalSymbol.AND); } @Override public S_FilterEntry or() { return setLastLogicalSymbol(LogicalSymbol.OR); } @Override public S_AtomicFilterEntry not() { return setNegated(); } @Override public S_ConditionEntry item(QName... names) { return item(new ItemPath(names)); } @Override public S_ConditionEntry item(ItemPath itemPath) { ItemDefinition itemDefinition = resolveItemPath(itemPath, ItemDefinition.class); return item(itemPath, itemDefinition); } @Override public S_ConditionEntry itemWithDef(ItemDefinition itemDefinition, QName... names) { ItemPath itemPath = new ItemPath(names); return item(itemPath, itemDefinition); } @Override public S_ConditionEntry item(ItemPath itemPath, ItemDefinition itemDefinition) { return R_AtomicFilter.create(itemPath, itemDefinition, this); } @Override public S_ConditionEntry item(PrismContainerDefinition containerDefinition, QName... names) { return item(containerDefinition, new ItemPath(names)); } @Override public S_ConditionEntry item(PrismContainerDefinition containerDefinition, ItemPath itemPath) { ItemDefinition itemDefinition = containerDefinition.findItemDefinition(itemPath); if (itemDefinition == null) { throw new IllegalArgumentException("No definition of " + itemPath + " in " + containerDefinition); } return item(itemPath, itemDefinition); } @Override public S_MatchingRuleEntry itemAs(PrismProperty<?> property) { return item(property.getPath(), property.getDefinition()).eq(property); } @Override public S_AtomicFilterExit endBlock() { if (parentFilter == null) { throw new IllegalStateException("endBlock() call without preceding block() one"); } if (currentFilter != null || parentFilter.hasRestriction()) { ObjectFilter simplified = simplify(currentFilter); if (simplified != null || parentFilter.hasRestriction()) { return parentFilter.addSubfilter(simplified); } } return parentFilter; } private boolean hasRestriction() { return existsRestriction != null || typeRestriction != null; } @Override public S_FilterExit asc(QName... names) { if (names.length == 0) { throw new IllegalArgumentException("There must be at least one name for asc(...) ordering"); } return addOrdering(ObjectOrdering.createOrdering(new ItemPath(names), OrderDirection.ASCENDING)); } @Override public S_FilterExit asc(ItemPath path) { if (ItemPath.isNullOrEmpty(path)) { throw new IllegalArgumentException("There must be non-empty path for asc(...) ordering"); } return addOrdering(ObjectOrdering.createOrdering(path, OrderDirection.ASCENDING)); } @Override public S_FilterExit desc(QName... names) { if (names.length == 0) { throw new IllegalArgumentException("There must be at least one name for asc(...) ordering"); } return addOrdering(ObjectOrdering.createOrdering(new ItemPath(names), OrderDirection.DESCENDING)); } @Override public S_FilterExit desc(ItemPath path) { if (ItemPath.isNullOrEmpty(path)) { throw new IllegalArgumentException("There must be non-empty path for desc(...) ordering"); } return addOrdering(ObjectOrdering.createOrdering(path, OrderDirection.DESCENDING)); } @Override public S_FilterExit offset(Integer n) { return setOffset(n); } @Override public S_FilterExit maxSize(Integer n) { return setMaxSize(n); } @Override public ObjectQuery build() { if (typeRestriction != null || existsRestriction != null) { // unfinished empty type restriction or exists restriction return addSubfilter(null).build(); } if (parentFilter != null) { throw new IllegalStateException("A block in filter definition was probably not closed."); } ObjectPaging paging = null; if (!orderingList.isEmpty()) { paging = createIfNeeded(null); paging.setOrdering(orderingList); } if (offset != null) { paging = createIfNeeded(paging); paging.setOffset(offset); } if (maxSize != null) { paging = createIfNeeded(paging); paging.setMaxSize(maxSize); } return ObjectQuery.createObjectQuery(simplify(currentFilter), paging); } private ObjectPaging createIfNeeded(ObjectPaging paging) { return paging != null ? paging : ObjectPaging.createEmptyPaging(); } @Override public ObjectFilter buildFilter() { return build().getFilter(); } private ObjectFilter simplify(OrFilter filter) { if (filter == null) { return null; } OrFilter simplified = OrFilter.createOr(); // step 1 - simplify conjunctions for (ObjectFilter condition : filter.getConditions()) { AndFilter conjunction = (AndFilter) condition; if (conjunction.getConditions().size() == 1) { simplified.addCondition(conjunction.getLastCondition()); } else { simplified.addCondition(conjunction); } } // step 2 - simplify disjunction if (simplified.getConditions().size() == 0) { return null; } else if (simplified.getConditions().size() == 1) { return simplified.getLastCondition(); } else { return simplified; } } public PrismContext getPrismContext() { return queryBuilder.getPrismContext(); } }