diff --git a/build-system/pom.xml b/build-system/pom.xml index 7f927f4ab37..577f77e18f8 100644 --- a/build-system/pom.xml +++ b/build-system/pom.xml @@ -1491,7 +1491,7 @@ org.apache.maven.plugins maven-dependency-plugin - 2.10 + 3.0.2 analyze diff --git a/dist/pom.xml b/dist/pom.xml index 6fae1713377..aca59e787a1 100644 --- a/dist/pom.xml +++ b/dist/pom.xml @@ -117,7 +117,7 @@ src/main/assembly/dist.xml - midpoint-${version} + midpoint-${project.version} diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/ObjectGrouping.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/ObjectGrouping.java new file mode 100644 index 00000000000..6db729e2d58 --- /dev/null +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/ObjectGrouping.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2010-2017 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.path.ItemPath; + +import java.io.Serializable; + +/** + * @author acope + */ +public class ObjectGrouping implements Serializable { + + final private ItemPath groupBy; + + ObjectGrouping(ItemPath groupBy) { + if (ItemPath.isNullOrEmpty(groupBy)) { + throw new IllegalArgumentException("Null or empty groupBy path is not supported."); + } + this.groupBy = groupBy; + } + + public static ObjectGrouping createGrouping(ItemPath groupBy) { + return new ObjectGrouping(groupBy); + } + + public ItemPath getGroupBy() { + return groupBy; + } + + + @Override + public String toString() { + return groupBy.toString(); + } + + @Override + public boolean equals(Object o) { + return equals(o, true); + } + + public boolean equals(Object o, boolean exact) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + ObjectGrouping that = (ObjectGrouping) o; + + if (groupBy != null ? !groupBy.equals(that.groupBy, exact) : that.groupBy != null) + return false; + return true; + } + + @Override + public int hashCode() { + return groupBy.hashCode(); + } +} diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/ObjectPaging.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/ObjectPaging.java index d189bdffa87..abd14154b16 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/ObjectPaging.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/ObjectPaging.java @@ -33,6 +33,8 @@ public class ObjectPaging implements DebugDumpable, Serializable { private Integer offset; private Integer maxSize; @NotNull private final List ordering = new ArrayList<>(); + private List grouping = new ArrayList<>(); + private String cookie; protected ObjectPaging() { @@ -43,15 +45,38 @@ protected ObjectPaging() { this.maxSize = maxSize; } + ObjectPaging(Integer offset, Integer maxSize, ItemPath groupBy) { + this.offset = offset; + this.maxSize = maxSize; + setGrouping(groupBy); + } + ObjectPaging(ItemPath orderBy, OrderDirection direction) { setOrdering(orderBy, direction); } - ObjectPaging(Integer offset, Integer maxSize, ItemPath orderBy, OrderDirection direction) { + ObjectPaging(ItemPath orderBy, OrderDirection direction, ItemPath groupBy) { + setOrdering(orderBy, direction); + setGrouping(groupBy); + } + + ObjectPaging(Integer offset, Integer maxSize, ItemPath orderBy, OrderDirection direction) { this.offset = offset; this.maxSize = maxSize; setOrdering(orderBy, direction); } + + ObjectPaging(Integer offset, Integer maxSize, ItemPath orderBy, OrderDirection direction, ItemPath groupBy) { + this.offset = offset; + this.maxSize = maxSize; + setOrdering(orderBy, direction); + + setGrouping(groupBy); + } + + ObjectPaging(ItemPath groupBy) { + setGrouping(groupBy); + } public static ObjectPaging createPaging(Integer offset, Integer maxSize){ return new ObjectPaging(offset, maxSize); @@ -64,13 +89,28 @@ public static ObjectPaging createPaging(Integer offset, Integer maxSize, QName o public static ObjectPaging createPaging(Integer offset, Integer maxSize, ItemPath orderBy, OrderDirection direction) { return new ObjectPaging(offset, maxSize, orderBy, direction); } - - public static ObjectPaging createPaging(Integer offset, Integer maxSize, List orderings) { + + public static ObjectPaging createPaging(Integer offset, Integer maxSize, ItemPath groupBy) { + return new ObjectPaging(offset, maxSize, groupBy); + } + + public static ObjectPaging createPaging(Integer offset, Integer maxSize, ItemPath orderBy, OrderDirection direction, ItemPath groupBy) { + return new ObjectPaging(offset, maxSize, orderBy, direction, groupBy); + } + + public static ObjectPaging createPaging(Integer offset, Integer maxSize, List orderings) { + ObjectPaging paging = new ObjectPaging(offset, maxSize); + paging.setOrdering(orderings); + return paging; + } + + public static ObjectPaging createPaging(Integer offset, Integer maxSize, List orderings, List groupings) { ObjectPaging paging = new ObjectPaging(offset, maxSize); paging.setOrdering(orderings); + paging.setGrouping(groupings); return paging; } - + public static ObjectPaging createPaging(ItemPath orderBy, OrderDirection direction) { return new ObjectPaging(orderBy, direction); } @@ -78,6 +118,22 @@ public static ObjectPaging createPaging(ItemPath orderBy, OrderDirection directi public static ObjectPaging createPaging(QName orderBy, OrderDirection direction) { return new ObjectPaging(new ItemPath(orderBy), direction); } + + public static ObjectPaging createPaging(ItemPath orderBy, OrderDirection direction, ItemPath groupBy) { + return new ObjectPaging(orderBy, direction, groupBy); + } + + public static ObjectPaging createPaging(QName orderBy, OrderDirection direction, QName groupBy) { + return new ObjectPaging(new ItemPath(orderBy), direction, new ItemPath(groupBy)); + } + + public static ObjectPaging createPaging(ItemPath groupBy) { + return new ObjectPaging(groupBy); + } + + public static ObjectPaging createPaging(QName groupBy) { + return new ObjectPaging(new ItemPath(groupBy)); + } public static ObjectPaging createEmptyPaging(){ return new ObjectPaging(); @@ -103,11 +159,28 @@ public ObjectOrdering getPrimaryOrdering() { } } + public ItemPath getGroupBy(){ + ObjectGrouping primary = getPrimaryGrouping(); + return primary != null ? primary.getGroupBy() : null; + } + + public ObjectGrouping getPrimaryGrouping() { + if (hasGrouping()) { + return grouping.get(0); + } else { + return null; + } + } + // TODO name? public List getOrderingInstructions() { return ordering; } + public List getGroupingInstructions() { + return grouping; + } + public boolean hasOrdering() { return !ordering.isEmpty(); } @@ -117,6 +190,15 @@ public void setOrdering(ItemPath orderBy, OrderDirection direction) { addOrderingInstruction(orderBy, direction); } + public boolean hasGrouping() { + return !grouping.isEmpty(); + } + + public void setGrouping(ItemPath groupBy) { + this.grouping.clear(); + addGroupingInstruction(groupBy); + } + public void addOrderingInstruction(ItemPath orderBy, OrderDirection direction) { this.ordering.add(new ObjectOrdering(orderBy, direction)); } @@ -140,6 +222,29 @@ public void setOrdering(Collection orderings) { } } + + public void addGroupingInstruction(ItemPath groupBy) { + this.grouping.add(new ObjectGrouping(groupBy)); + } + + public void addGroupingInstruction(QName groupBy) { + addGroupingInstruction(new ItemPath(groupBy)); + } + + public void setGrouping(ObjectGrouping... groupings) { + this.grouping.clear(); + if (groupings != null) { + this.grouping.addAll(Arrays.asList(groupings)); + } + } + + public void setGrouping(Collection groupings) { + this.grouping.clear(); + if (groupings != null) { + this.grouping.addAll(groupings); + } + } + public Integer getOffset() { return offset; } @@ -197,6 +302,13 @@ protected void copyTo(ObjectPaging clone) { clone.maxSize = this.maxSize; clone.ordering.clear(); clone.ordering.addAll(this.ordering); + + if (this.grouping != null) { + clone.grouping = new ArrayList<>(this.grouping); + } else { + clone.grouping = null; + } + clone.cookie = this.cookie; } @@ -224,6 +336,11 @@ public String debugDump(int indent) { DebugUtil.indentDebugDump(sb, indent + 1); sb.append("Ordering: ").append(ordering); } + if (hasGrouping()) { + sb.append("\n"); + DebugUtil.indentDebugDump(sb, indent + 1); + sb.append("Grouping: ").append(grouping); + } if (getCookie() != null) { sb.append("\n"); DebugUtil.indentDebugDump(sb, indent + 1); @@ -251,6 +368,11 @@ public String toString() { sb.append(ordering); sb.append(", "); } + if (hasGrouping()) { + sb.append("GRP: "); + sb.append(grouping); + sb.append(", "); + } if (getCookie() != null) { sb.append("C:"); sb.append(getCookie()); @@ -287,6 +409,16 @@ public boolean equals(Object o, boolean exact) { return false; } } + if (grouping.size() != that.grouping.size()) { + return false; + } + for (int i = 0; i < grouping.size(); i++) { + ObjectGrouping og1 = this.grouping.get(i); + ObjectGrouping og2 = that.grouping.get(i); + if (!og1.equals(og2, exact)) { + return false; + } + } return cookie != null ? cookie.equals(that.cookie) : that.cookie == null; } @@ -295,6 +427,7 @@ public int hashCode() { int result = offset != null ? offset.hashCode() : 0; result = 31 * result + (maxSize != null ? maxSize.hashCode() : 0); result = 31 * result + ordering.hashCode(); + result = 31 * result + (grouping != null ? grouping.hashCode() : 0); result = 31 * result + (cookie != null ? cookie.hashCode() : 0); return result; } diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/PagingConvertor.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/PagingConvertor.java index f7f9bdaaff7..e5d647c90d4 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/PagingConvertor.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/PagingConvertor.java @@ -28,10 +28,19 @@ public static ObjectPaging createObjectPaging(PagingType pagingType){ if (pagingType == null) { return null; } + if (pagingType.getOrderBy() != null && pagingType.getGroupBy() != null) { + return ObjectPaging.createPaging(pagingType.getOffset(), pagingType.getMaxSize(), + pagingType.getOrderBy().getItemPath(), toOrderDirection(pagingType.getOrderDirection()), pagingType.getGroupBy().getItemPath()); + } + if (pagingType.getOrderBy() != null) { return ObjectPaging.createPaging(pagingType.getOffset(), pagingType.getMaxSize(), pagingType.getOrderBy().getItemPath(), toOrderDirection(pagingType.getOrderDirection())); - } else { + + } if (pagingType.getGroupBy() != null) { + return ObjectPaging.createPaging(pagingType.getGroupBy().getItemPath()); + + } else { return ObjectPaging.createPaging(pagingType.getOffset(), pagingType.getMaxSize()); } } @@ -62,7 +71,9 @@ public static PagingType createPagingType(ObjectPaging paging){ if (paging.getOrderBy() != null) { pagingType.setOrderBy(new ItemPathType(paging.getOrderBy())); } - + if (paging.getGroupBy() != null) { + pagingType.setGroupBy(new ItemPathType(paging.getGroupBy())); + } return pagingType; } diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/builder/R_AtomicFilter.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/builder/R_AtomicFilter.java index 97bb7de31ce..af188a920bf 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/builder/R_AtomicFilter.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/builder/R_AtomicFilter.java @@ -345,6 +345,16 @@ public S_FilterExit desc(ItemPath path) { return finish().desc(path); } + @Override + public S_FilterExit group(QName... names) { + return finish().group(names); + } + + @Override + public S_FilterExit group(ItemPath path) { + return finish().group(path); + } + @Override public S_FilterExit offset(Integer n) { return finish().offset(n); diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/builder/R_Filter.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/builder/R_Filter.java index a6ed8635331..2f9e15107de 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/builder/R_Filter.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/builder/R_Filter.java @@ -41,6 +41,7 @@ public class R_Filter implements S_FilterEntryOrEmpty, S_AtomicFilterExit { final private QName typeRestriction; final private ItemPath existsRestriction; final private List orderingList; + final private List groupingList; final private Integer offset; final private Integer maxSize; @@ -54,12 +55,13 @@ public R_Filter(QueryBuilder queryBuilder) { this.typeRestriction = null; this.existsRestriction = null; this.orderingList = new ArrayList<>(); + this.groupingList = new ArrayList<>(); this.offset = null; this.maxSize = null; } private R_Filter(QueryBuilder queryBuilder, Class currentClass, OrFilter currentFilter, LogicalSymbol lastLogicalSymbol, - boolean isNegated, R_Filter parentFilter, QName typeRestriction, ItemPath existsRestriction, List orderingList, Integer offset, Integer maxSize) { + boolean isNegated, R_Filter parentFilter, QName typeRestriction, ItemPath existsRestriction, List orderingList, List groupingList, Integer offset, Integer maxSize) { this.queryBuilder = queryBuilder; this.currentClass = currentClass; this.currentFilter = currentFilter; @@ -73,6 +75,11 @@ private R_Filter(QueryBuilder queryBuilder, Class curre } else { this.orderingList = new ArrayList<>(); } + if (groupingList != null) { + this.groupingList = groupingList; + } else { + this.groupingList = new ArrayList<>(); + } this.offset = offset; this.maxSize = maxSize; } @@ -112,7 +119,7 @@ R_Filter addSubfilter(ObjectFilter subfilter) { subfilter)); } else { OrFilter newFilter = appendAtomicFilter(subfilter, isNegated, lastLogicalSymbol); - return new R_Filter(queryBuilder, currentClass, newFilter, null, false, parentFilter, typeRestriction, existsRestriction, orderingList, offset, maxSize); + return new R_Filter(queryBuilder, currentClass, newFilter, null, false, parentFilter, typeRestriction, existsRestriction, orderingList, groupingList, offset, maxSize); } } @@ -135,29 +142,36 @@ 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); + return new R_Filter(queryBuilder, currentClass, currentFilter, newLogicalSymbol, isNegated, parentFilter, typeRestriction, existsRestriction, orderingList, groupingList, 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); + return new R_Filter(queryBuilder, currentClass, currentFilter, lastLogicalSymbol, true, parentFilter, typeRestriction, existsRestriction, orderingList, groupingList, offset, maxSize); } private R_Filter addOrdering(ObjectOrdering ordering) { Validate.notNull(ordering); List newList = new ArrayList<>(orderingList); newList.add(ordering); - return new R_Filter(queryBuilder, currentClass, currentFilter, lastLogicalSymbol, isNegated, parentFilter, typeRestriction, existsRestriction, newList, offset, maxSize); + return new R_Filter(queryBuilder, currentClass, currentFilter, lastLogicalSymbol, isNegated, parentFilter, typeRestriction, existsRestriction, newList, groupingList, offset, maxSize); + } + + private R_Filter addGrouping(ObjectGrouping grouping) { + Validate.notNull(grouping); + List newList = new ArrayList<>(groupingList); + newList.add(grouping); + return new R_Filter(queryBuilder, currentClass, currentFilter, lastLogicalSymbol, isNegated, parentFilter, typeRestriction, existsRestriction, orderingList, newList, offset, maxSize); } private R_Filter setOffset(Integer n) { - return new R_Filter(queryBuilder, currentClass, currentFilter, lastLogicalSymbol, isNegated, parentFilter, typeRestriction, existsRestriction, orderingList, n, maxSize); + return new R_Filter(queryBuilder, currentClass, currentFilter, lastLogicalSymbol, isNegated, parentFilter, typeRestriction, existsRestriction, orderingList, groupingList, n, maxSize); } private R_Filter setMaxSize(Integer n) { - return new R_Filter(queryBuilder, currentClass, currentFilter, lastLogicalSymbol, isNegated, parentFilter, typeRestriction, existsRestriction, orderingList, offset, n); + return new R_Filter(queryBuilder, currentClass, currentFilter, lastLogicalSymbol, isNegated, parentFilter, typeRestriction, existsRestriction, orderingList, groupingList, offset, n); } @Override @@ -265,7 +279,7 @@ public S_AtomicFilterExit fullText(String... words) { @Override public S_FilterEntryOrEmpty block() { - return new R_Filter(queryBuilder, currentClass, OrFilter.createOr(), null, false, this, null, null, null, null, null); + return new R_Filter(queryBuilder, currentClass, OrFilter.createOr(), null, false, this, null, null, null, null, null, null); } @Override @@ -278,7 +292,7 @@ public S_FilterEntryOrEmpty type(Class type) { 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); + return new R_Filter(queryBuilder, type, OrFilter.createOr(), null, false, this, typeName, null, null, null, null, null); } @Override @@ -295,7 +309,7 @@ public S_FilterEntryOrEmpty exists(QName... names) { 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); + return new R_Filter(queryBuilder, clazz, OrFilter.createOr(), null, false, this, null, existsPath, null, null,null, null); } ID resolveItemPath(ItemPath itemPath, Class type) { @@ -419,6 +433,22 @@ public S_FilterExit desc(ItemPath path) { return addOrdering(ObjectOrdering.createOrdering(path, OrderDirection.DESCENDING)); } + @Override + public S_FilterExit group(QName... names) { + if (names.length == 0) { + throw new IllegalArgumentException("There must be at least one name for uniq(...) grouping"); + } + return addGrouping(ObjectGrouping.createGrouping(new ItemPath(names))); + } + + @Override + public S_FilterExit group(ItemPath path) { + if (ItemPath.isNullOrEmpty(path)) { + throw new IllegalArgumentException("There must be non-empty path for uniq(...) grouping"); + } + return addGrouping(ObjectGrouping.createGrouping(path)); + } + @Override public S_FilterExit offset(Integer n) { return setOffset(n); diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/builder/S_FilterExit.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/builder/S_FilterExit.java index 00edf4b9690..0bb2e077c8b 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/builder/S_FilterExit.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/builder/S_FilterExit.java @@ -30,6 +30,8 @@ public interface S_FilterExit extends S_QueryExit { S_FilterExit asc(ItemPath path); S_FilterExit desc(QName... names); S_FilterExit desc(ItemPath path); + S_FilterExit group(QName... names); + S_FilterExit group(ItemPath path); S_FilterExit offset(Integer n); S_FilterExit maxSize(Integer n); } diff --git a/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/query_3/PagingType.java b/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/query_3/PagingType.java index 08cc9d665c2..0a6de358b4b 100644 --- a/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/query_3/PagingType.java +++ b/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/query_3/PagingType.java @@ -45,6 +45,7 @@ * <element name="orderDirection" type="{http://prism.evolveum.com/xml/ns/public/query-2}OrderDirectionType" minOccurs="0"/> * <element name="offset" type="{http://www.w3.org/2001/XMLSchema}int" minOccurs="0"/> * <element name="maxSize" type="{http://www.w3.org/2001/XMLSchema}int" minOccurs="0"/> + * <element name="groupBy" type="{http://prism.evolveum.com/xml/ns/public/types-3}XPathType" minOccurs="0"/> * </sequence> * </restriction> * </complexContent> @@ -56,7 +57,8 @@ "orderBy", "orderDirection", "offset", - "maxSize" + "maxSize", + "groupBy" }) public class PagingType implements Serializable, Cloneable, Equals, HashCode { @@ -69,6 +71,8 @@ public class PagingType implements Serializable, Cloneable, Equals, HashCode protected Integer offset; @XmlElement(defaultValue = "2147483647") protected Integer maxSize; + protected ItemPathType groupBy; + public final static QName COMPLEX_TYPE = new QName(PrismConstants.NS_QUERY, "PagingType"); public final static QName F_ORDER_DIRECTION = new QName(PrismConstants.NS_QUERY, "orderDirection"); public final static QName F_OFFSET = new QName(PrismConstants.NS_QUERY, "offset"); @@ -105,6 +109,7 @@ public PagingType(final PagingType o) { this.offset = ((o.offset == null)?null:o.getOffset()); // CBuiltinLeafInfo: java.lang.Integer this.maxSize = ((o.maxSize == null)?null:o.getMaxSize()); + this.groupBy = (o.groupBy == null)?null:o.groupBy.clone(); } /** @@ -203,6 +208,30 @@ public void setMaxSize(Integer value) { this.maxSize = value; } + /** + * Gets the value of the groupBy property. + * + * @return + * possible object is + * {@link Element } + * + */ + public ItemPathType getGroupBy() { + return groupBy; + } + + /** + * Sets the value of the orderBy property. + * + * @param value + * allowed object is + * {@link Element } + * + */ + public void setGroupBy(ItemPathType value) { + this.groupBy = value; + } + /** * Generates a String representation of the contents of this type. * This is an extension method, produced by the 'ts' xjc plugin @@ -235,6 +264,11 @@ public int hashCode(ObjectLocator locator, HashCodeStrategy strategy) { theMaxSize = this.getMaxSize(); currentHashCode = strategy.hashCode(LocatorUtils.property(locator, "maxSize", theMaxSize), currentHashCode, theMaxSize); } + { + ItemPathType theGroupBy; + theGroupBy = this.getGroupBy(); + currentHashCode = strategy.hashCode(LocatorUtils.property(locator, "groupBy", theGroupBy), currentHashCode, theGroupBy); + } return currentHashCode; } @@ -287,6 +321,15 @@ public boolean equals(ObjectLocator thisLocator, ObjectLocator thatLocator, Obje return false; } } + { + ItemPathType lhsGroupBy; + lhsGroupBy = this.getGroupBy(); + ItemPathType rhsGroupBy; + rhsGroupBy = that.getOrderBy(); + if (!strategy.equals(LocatorUtils.property(thisLocator, "groupBy", lhsGroupBy), LocatorUtils.property(thatLocator, "groupBy", rhsGroupBy), lhsGroupBy, rhsGroupBy)) { + return false; + } + } return true; } @@ -316,6 +359,8 @@ public PagingType clone() { clone.offset = ((this.offset == null)?null:this.getOffset()); // CBuiltinLeafInfo: java.lang.Integer clone.maxSize = ((this.maxSize == null)?null:this.getMaxSize()); + // CWildcardTypeInfo: org.w3c.dom.Element + clone.groupBy = ((this.groupBy == null)?null:((this.getGroupBy() == null)?null:(this.getGroupBy().clone()))); return clone; } } catch (CloneNotSupportedException e) { diff --git a/infra/prism/src/main/resources/xml/ns/public/query-3.xsd b/infra/prism/src/main/resources/xml/ns/public/query-3.xsd index b7a09e89542..b80cf11e104 100644 --- a/infra/prism/src/main/resources/xml/ns/public/query-3.xsd +++ b/infra/prism/src/main/resources/xml/ns/public/query-3.xsd @@ -120,6 +120,17 @@ + + + + Property by which the results should be grouped. + Reference to a property (XPath). + Just one property for now. + Maybe we will change this to a list later. + If not specified, no grouping is assumed. + + + diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/internals/InternalCounters.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/internals/InternalCounters.java index e244fff5fa2..bc76a5c25fa 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/internals/InternalCounters.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/internals/InternalCounters.java @@ -23,6 +23,8 @@ public enum InternalCounters { RESOURCE_SCHEMA_PARSE_COUNT("resourceSchemaParseCount", "resource schema parse count", InternalOperationClasses.RESOURCE_SCHEMA_OPERATIONS), RESOURCE_SCHEMA_FETCH_COUNT("resourceSchemaFetchCount", "resource schema fetch count", InternalOperationClasses.REPOSITORY_OPERATIONS), + + RESOURCE_REPOSITORY_READ_COUNT("resourceRepositoryReadCount", "resource repository read count", null), CONNECTOR_INSTANCE_INITIALIZATION_COUNT("connectorInstanceInitializationCount", "connector instance initialization count", InternalOperationClasses.CONNECTOR_OPERATIONS), diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd index 9a5c77e35f5..a4ee3263847 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd @@ -703,6 +703,13 @@ + + + + Scope of serialization. The default is "object". If multiple scopes are defined, serialization occurs on each one. + + + @@ -720,6 +727,61 @@ + + + + Scope of execution task serialization. + + + + + + + + + + No two workflow execution tasks from a single operation are allowed to execute at once. + + + + + + + + + + No two workflow execution tasks on a given object are allowed to execute at once. + + + + + + + + + + No two workflow execution tasks related to give target are allowed to execute at once. + Note that the information on target is not always available (e.g. when executing changes + that do not require approval), so this may not be absolutely reliable. + + + + + + + + + + No two workflow execution tasks are allowed to execute at once. + + + + + + + + + diff --git a/infra/ws-util/pom.xml b/infra/ws-util/pom.xml index 2788e3623bc..e14330634e6 100644 --- a/infra/ws-util/pom.xml +++ b/infra/ws-util/pom.xml @@ -61,6 +61,10 @@ org.apache.wss4j wss4j-ws-security-common + + org.apache.wss4j + wss4j-ws-security-dom + diff --git a/model/model-common/pom.xml b/model/model-common/pom.xml index e95757dd3d5..496a34b9065 100644 --- a/model/model-common/pom.xml +++ b/model/model-common/pom.xml @@ -105,6 +105,10 @@ commons-lang commons-lang + + org.apache.commons + commons-collections4 + commons-io commons-io diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/AssociationFromLinkExpressionEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/AssociationFromLinkExpressionEvaluator.java index be3b7eefd8e..bf0e0d97464 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/AssociationFromLinkExpressionEvaluator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/AssociationFromLinkExpressionEvaluator.java @@ -15,7 +15,9 @@ */ package com.evolveum.midpoint.model.common.expression.evaluator; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import javax.xml.namespace.QName; @@ -25,17 +27,18 @@ import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.query.builder.QueryBuilder; +import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterExit; import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluator; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.ResultHandler; import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.util.ObjectResolver; -import com.evolveum.midpoint.schema.util.ShadowUtil; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractRoleType; @@ -46,6 +49,9 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowDiscriminatorType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import org.apache.commons.collections4.CollectionUtils; + +import static com.evolveum.midpoint.schema.GetOperationOptions.createNoFetchCollection; /** * @author Radovan Semancik @@ -110,54 +116,64 @@ public PrismValueDeltaSetTriple> eval QName assocName = context.getMappingQName(); String resourceOid = rAssocTargetDef.getResourceOid(); Collection> options = null; - + List candidateShadowOidList = new ArrayList<>(); // Always process the first role (myself) regardless of recursion setting - gatherAssociationsFromAbstractRole(thisRole, output, resourceOid, kind, intent, assocName, options, desc, context); - + gatherCandidateShadowsFromAbstractRole(thisRole, candidateShadowOidList); if (thisRole instanceof OrgType && matchesForRecursion((OrgType)thisRole)) { - gatherAssociationsFromAbstractRoleRecurse((OrgType)thisRole, output, resourceOid, kind, intent, assocName, options, desc, - context); + gatherCandidateShadowsFromAbstractRoleRecurse((OrgType)thisRole, candidateShadowOidList, options, desc, context); } + LOGGER.trace("Candidate shadow OIDs: {}", candidateShadowOidList); + selectMatchingShadows(candidateShadowOidList, output, resourceOid, kind, intent, assocName, context); return ItemDelta.toDeltaSetTriple(output, null); } - private void gatherAssociationsFromAbstractRole(AbstractRoleType thisRole, + private void selectMatchingShadows(List candidateShadowsOidList, PrismContainer output, String resourceOid, ShadowKindType kind, - String intent, QName assocName, Collection> options, - String desc, ExpressionEvaluationContext params) throws SchemaException { - for (ObjectReferenceType linkRef: thisRole.getLinkRef()) { - ShadowType shadowType; - try { - shadowType = objectResolver.resolve(linkRef, ShadowType.class, options, desc, params.getTask(), params.getResult()); - } catch (ObjectNotFoundException e) { - // Linked shadow not found. This may happen e.g. if the account is deleted and model haven't got - // the chance to react yet. Just ignore such shadow. - LOGGER.trace("Ignoring shadow "+linkRef.getOid()+" linked in "+thisRole+" because it no longer exists"); - continue; - } - if (ShadowUtil.matches(shadowType, resourceOid, kind, intent)) { - PrismContainerValue newValue = output.createNewValue(); - ShadowAssociationType shadowAssociationType = newValue.asContainerable(); - shadowAssociationType.setName(assocName); - ObjectReferenceType shadowRef = new ObjectReferenceType(); - shadowRef.setOid(linkRef.getOid()); - shadowAssociationType.setShadowRef(shadowRef); - } + String intent, QName assocName, ExpressionEvaluationContext params) + throws SchemaException { + + S_AtomicFilterExit filter = QueryBuilder.queryFor(ShadowType.class, prismContext) + .id(candidateShadowsOidList.toArray(new String[0])) + .and().item(ShadowType.F_RESOURCE_REF).ref(resourceOid) + .and().item(ShadowType.F_KIND).eq(kind); + if (intent != null) { + filter = filter.and().item(ShadowType.F_INTENT).eq(intent); + } + ObjectQuery query = filter.build(); + + ResultHandler handler = (object, parentResult) -> { + PrismContainerValue newValue = output.createNewValue(); + ShadowAssociationType shadowAssociationType = newValue.asContainerable(); + shadowAssociationType.setName(assocName); + shadowAssociationType.setShadowRef(new ObjectReferenceType().oid(object.getOid()).type(ShadowType.COMPLEX_TYPE)); + return true; + }; + try { + objectResolver.searchIterative(ShadowType.class, query, createNoFetchCollection(), handler, params.getTask(), params.getResult()); + } catch (CommonException e) { + throw new SystemException("Couldn't search for relevant shadows: " + e.getMessage(), e); } } - private void gatherAssociationsFromAbstractRoleRecurse(OrgType thisOrg, - PrismContainer output, String resourceOid, ShadowKindType kind, - String intent, QName assocName, Collection> options, + private void gatherCandidateShadowsFromAbstractRole(AbstractRoleType thisRole, List candidateShadowsOidList) { + for (ObjectReferenceType linkRef: thisRole.getLinkRef()) { + CollectionUtils.addIgnoreNull(candidateShadowsOidList, linkRef.getOid()); + } + } + + private void gatherCandidateShadowsFromAbstractRoleRecurse(OrgType thisOrg, + List< String > candidateShadowsOidList, + Collection> options, String desc, ExpressionEvaluationContext params) throws SchemaException, ObjectNotFoundException { - gatherAssociationsFromAbstractRole(thisOrg, output, resourceOid, kind, intent, assocName, options, desc, params); + for (ObjectReferenceType parentOrgRef: thisOrg.getParentOrgRef()) { OrgType parent = objectResolver.resolve(parentOrgRef, OrgType.class, options, desc, params.getTask(), params.getResult()); if (matchesForRecursion(parent)) { - gatherAssociationsFromAbstractRoleRecurse(parent, output, resourceOid, kind, intent, assocName, options, desc, params); + gatherCandidateShadowsFromAbstractRole(parent, candidateShadowsOidList); + gatherCandidateShadowsFromAbstractRoleRecurse(parent, candidateShadowsOidList, options, desc, params); } } } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestCaseIgnore.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestCaseIgnore.java index 3dc3c7124d3..9770a349081 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestCaseIgnore.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestCaseIgnore.java @@ -111,7 +111,7 @@ public void initSystem(Task initTask, OperationResult initResult) @Test public void test000Sanity() throws Exception { final String TEST_NAME="test000Sanity"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); assertShadows(5); } @@ -119,7 +119,7 @@ public void test000Sanity() throws Exception { @Test public void test131ModifyUserJackAssignAccount() throws Exception { final String TEST_NAME="test131ModifyUserJackAssignAccount"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN Task task = taskManager.createTaskInstance(TestCaseIgnore.class.getName() + "." + TEST_NAME); @@ -132,7 +132,7 @@ public void test131ModifyUserJackAssignAccount() throws Exception { XMLGregorianCalendar startTime = clock.currentTimeXMLGregorianCalendar(); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); modelService.executeChanges(deltas, null, task, result); // THEN @@ -174,7 +174,7 @@ public void test131ModifyUserJackAssignAccount() throws Exception { @Test public void test133SeachAccountShadows() throws Exception { final String TEST_NAME="test133SeachAccountShadows"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN Task task = taskManager.createTaskInstance(TestCaseIgnore.class.getName() + "." + TEST_NAME); @@ -186,7 +186,7 @@ public void test133SeachAccountShadows() throws Exception { rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); SearchResultList> foundShadows = modelService.searchObjects(ShadowType.class, query, null, task, result); // THEN @@ -228,7 +228,7 @@ public void test133SeachAccountShadows() throws Exception { @Test public void test139ModifyUserJackUnassignAccount() throws Exception { final String TEST_NAME = "test139ModifyUserJackUnassignAccount"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN Task task = taskManager.createTaskInstance(TestCaseIgnore.class.getName() + "." + TEST_NAME); @@ -268,7 +268,7 @@ public void test139ModifyUserJackUnassignAccount() throws Exception { @Test public void test150JackAssignRoleX() throws Exception { final String TEST_NAME = "test150JackAssignRoleX"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); @@ -280,7 +280,7 @@ public void test150JackAssignRoleX() throws Exception { assignRole(USER_JACK_OID, ROLE_X_OID, task, result); // THEN - TestUtil.displayThen(TEST_NAME); + displayThen(TEST_NAME); result.computeStatus(); TestUtil.assertSuccess(result); @@ -315,7 +315,7 @@ public void test150JackAssignRoleX() throws Exception { @Test public void test152GetJack() throws Exception { final String TEST_NAME = "test152GetJack"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); @@ -324,7 +324,7 @@ public void test152GetJack() throws Exception { PrismObject userJack = modelService.getObject(UserType.class, USER_JACK_OID, null, task, result); // THEN - TestUtil.displayThen(TEST_NAME); + displayThen(TEST_NAME); result.computeStatus(); TestUtil.assertSuccess(result); @@ -357,7 +357,7 @@ public void test152GetJack() throws Exception { @Test public void test159JackUnAssignRoleX() throws Exception { final String TEST_NAME = "test159JackUnAssignRoleX"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); @@ -369,7 +369,7 @@ public void test159JackUnAssignRoleX() throws Exception { unassignRole(USER_JACK_OID, ROLE_X_OID, task, result); // THEN - TestUtil.displayThen(TEST_NAME); + displayThen(TEST_NAME); result.computeStatus(); TestUtil.assertSuccess(result); @@ -389,7 +389,7 @@ public void test159JackUnAssignRoleX() throws Exception { @Test public void test160JackAssignRoleBasic() throws Exception { final String TEST_NAME = "test160JackAssignRoleBasic"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); @@ -398,11 +398,11 @@ public void test160JackAssignRoleBasic() throws Exception { display("User jack before", userBefore); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assignRole(USER_JACK_OID, ROLE_UPCASE_BASIC_OID, task, result); // THEN - TestUtil.displayThen(TEST_NAME); + displayThen(TEST_NAME); result.computeStatus(); TestUtil.assertSuccess(result); @@ -438,7 +438,7 @@ public void test160JackAssignRoleBasic() throws Exception { @Test public void test161JackAssignRoleJoker() throws Exception { final String TEST_NAME = "test161JackAssignRoleJoker"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); @@ -447,11 +447,11 @@ public void test161JackAssignRoleJoker() throws Exception { display("User jack before", userBefore); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assignRole(USER_JACK_OID, ROLE_JOKER_OID, task, result); // THEN - TestUtil.displayThen(TEST_NAME); + displayThen(TEST_NAME); result.computeStatus(); TestUtil.assertSuccess(result); @@ -491,7 +491,7 @@ public void test161JackAssignRoleJoker() throws Exception { @Test public void test165JackUnAssignRoleJoker() throws Exception { final String TEST_NAME = "test165JackUnAssignRoleJoker"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); @@ -500,11 +500,11 @@ public void test165JackUnAssignRoleJoker() throws Exception { display("User jack before", userBefore); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); unassignRole(USER_JACK_OID, ROLE_JOKER_OID, task, result); // THEN - TestUtil.displayThen(TEST_NAME); + displayThen(TEST_NAME); result.computeStatus(); TestUtil.assertSuccess(result); @@ -540,7 +540,7 @@ public void test165JackUnAssignRoleJoker() throws Exception { @Test public void test169JackUnAssignRoleBasic() throws Exception { final String TEST_NAME = "test169JackUnAssignRoleBasic"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); @@ -549,11 +549,11 @@ public void test169JackUnAssignRoleBasic() throws Exception { display("User jack before", userBefore); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); unassignRole(USER_JACK_OID, ROLE_UPCASE_BASIC_OID, task, result); // THEN - TestUtil.displayThen(TEST_NAME); + displayThen(TEST_NAME); result.computeStatus(); TestUtil.assertSuccess(result); @@ -575,7 +575,7 @@ public void test169JackUnAssignRoleBasic() throws Exception { @Test public void test170JackAssignRoleJoker() throws Exception { final String TEST_NAME = "test170JackAssignRoleJoker"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); @@ -584,13 +584,12 @@ public void test170JackAssignRoleJoker() throws Exception { display("User jack before", userBefore); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assignRole(USER_JACK_OID, ROLE_JOKER_OID, task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess(result); + displayThen(TEST_NAME); + assertSuccess(result); // Make sure this is repository so we do not destroy the "evidence" yet. PrismObject userJack = repositoryService.getObject(UserType.class, USER_JACK_OID, null, result); @@ -624,7 +623,7 @@ public void test170JackAssignRoleJoker() throws Exception { @Test public void test179JackUnAssignRoleJoker() throws Exception { final String TEST_NAME = "test179JackUnAssignRoleJoker"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); @@ -633,13 +632,12 @@ public void test179JackUnAssignRoleJoker() throws Exception { display("User jack before", userBefore); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); unassignRole(USER_JACK_OID, ROLE_JOKER_OID, task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess(result); + displayThen(TEST_NAME); + assertSuccess(result); // Make sure this is repository so we do not destroy the "evidence" yet. PrismObject userJack = repositoryService.getObject(UserType.class, USER_JACK_OID, null, result); @@ -668,7 +666,7 @@ public void test179JackUnAssignRoleJoker() throws Exception { @Test public void test200GuybrushAssignRoleFools() throws Exception { final String TEST_NAME = "test200GuybrushAssignRoleFools"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); @@ -689,13 +687,12 @@ public void test200GuybrushAssignRoleFools() throws Exception { assertShadows(4); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assignRole(USER_GUYBRUSH_OID, ROLE_FOOL_OID, task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess(result); + displayThen(TEST_NAME); + assertSuccess(result); assertShadows(6); diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestEntitlements.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestEntitlements.java index 76e97315d4c..42a49c9f536 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestEntitlements.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestEntitlements.java @@ -124,6 +124,8 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti importObjectFromFile(ROLE_CREW_OF_GUYBRUSH_FILE); assumeAssignmentPolicy(AssignmentPolicyEnforcementType.RELATIVE); + + rememberSteadyResources(); } /** @@ -136,6 +138,8 @@ public void test100AddGroupShadowSwashbucklers() throws Exception { Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); + + assertSteadyResources(); PrismObject group = prismContext.parseObject(SHADOW_GROUP_DUMMY_SWASHBUCKLERS_FILE); @@ -150,6 +154,8 @@ public void test100AddGroupShadowSwashbucklers() throws Exception { display("Group", dummyGroup); assertEquals("Wrong group description", GROUP_DUMMY_SWASHBUCKLERS_DESCRIPTION, dummyGroup.getAttributeValue(DummyResourceContoller.DUMMY_GROUP_ATTRIBUTE_DESCRIPTION)); + + assertSteadyResources(); } @Test @@ -374,6 +380,8 @@ public void test300AddRoleWimp() throws Exception { assertNotNull("No group on orange dummy resource", dummyGroupAtOrange); display("Group @orange", dummyGroupAtOrange); assertNoGroupMembers(dummyGroupAtOrange); + + assertSteadyResources(); } @Test @@ -1006,6 +1014,8 @@ public void test359UnassignOrangeAccountFromGuybrushAndRapp() throws Exception { assertNoDummyAccount(RESOURCE_DUMMY_ORANGE_NAME, ACCOUNT_GUYBRUSH_DUMMY_USERNAME); assertNoDummyAccount(RESOURCE_DUMMY_ORANGE_NAME, USER_RAPP_USERNAME); + + assertSteadyResources(); } @Test @@ -1354,6 +1364,8 @@ public void test700ReconcileGuybrush() throws Exception { // THEN dumpUserAndAccounts(getUser(USER_GUYBRUSH_OID), task, result); assertNoGroupMember(getDummyGroup(null, GROUP_DUMMY_SWASHBUCKLERS_NAME), USER_GUYBRUSH_USERNAME); + + assertSteadyResources(); } /** @@ -1485,6 +1497,8 @@ public void test800AssignRoleSwashbucklerToJackNone() throws Exception { assertAssignments(userAfter, 1); assertNoDummyAccount(ACCOUNT_JACK_DUMMY_USERNAME); + + assertSteadyResources(); } /** @@ -2082,6 +2096,8 @@ public void test899UnAssignAccountJackDummy() throws Exception { assertAssignments(userAfter, 0); assertJackNoAccountNoSwashbuckler(); + + assertSteadyResources(); } private void assertJackClean() throws SchemaViolationException, ConflictException, ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestIntent.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestIntent.java index 64cf9f33acd..f4bb40f8842 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestIntent.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestIntent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * Copyright (c) 2010-2017 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,6 @@ */ package com.evolveum.midpoint.model.intest; -import static com.evolveum.midpoint.test.IntegrationTestTools.display; - import java.io.File; import java.util.ArrayList; import java.util.Collection; @@ -82,13 +80,12 @@ public void test131ModifyUserJackAssignAccountDefault() throws Exception { XMLGregorianCalendar startTime = clock.currentTimeXMLGregorianCalendar(); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); modelService.executeChanges(deltas, null, task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess("executeChanges result", result); + displayThen(TEST_NAME); + assertSuccess(result); XMLGregorianCalendar endTime = clock.currentTimeXMLGregorianCalendar(); assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 0); @@ -143,13 +140,12 @@ public void test132ModifyUserJackAssignAccountTest() throws Exception { XMLGregorianCalendar startTime = clock.currentTimeXMLGregorianCalendar(); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); modelService.executeChanges(deltas, null, task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess("executeChanges result", result); + displayThen(TEST_NAME); + assertSuccess(result); XMLGregorianCalendar endTime = clock.currentTimeXMLGregorianCalendar(); assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 1); @@ -210,13 +206,12 @@ public void test135ModifyUserJackFullName() throws Exception { preTestCleanup(AssignmentPolicyEnforcementType.RELATIVE); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); modifyUserReplace(USER_JACK_OID, UserType.F_FULL_NAME, task, result, PrismTestUtil.createPolyString("cpt. Jack Sparrow")); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess("executeChanges result", result); + displayThen(TEST_NAME); + assertSuccess(result); assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 2); PrismObject userJack = getUser(USER_JACK_OID); @@ -280,13 +275,12 @@ public void test147ModifyUserJackUnAssignAccountDefault() throws Exception { XMLGregorianCalendar startTime = clock.currentTimeXMLGregorianCalendar(); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); modelService.executeChanges(deltas, null, task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess("executeChanges result", result); + displayThen(TEST_NAME); + assertSuccess(result); XMLGregorianCalendar endTime = clock.currentTimeXMLGregorianCalendar(); assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 1); diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java index 798190b5eae..a23f4c2b840 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java @@ -1574,7 +1574,10 @@ public void test148ModifyUserJackUnassignAccountPositiveEnforcement() throws Exc // the previous command changes resource, therefore let's explicitly re-read it before test // to refresh the cache and not affect the performance results (monitor). modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null, task, result); + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 1); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 1); + + assertSteadyResources(); // WHEN displayWhen(TEST_NAME); @@ -1634,7 +1637,9 @@ public void test148ModifyUserJackUnassignAccountPositiveEnforcement() throws Exc // the previous command changes resource, therefore let's explicitly re-read it before test // to refresh the cache and not affect the performance results (monitor). modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null, task, result); + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 1); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 1); + assertSteadyResources(); } /** diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestResources.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestResources.java index d502dd69d25..74429514022 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestResources.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestResources.java @@ -16,7 +16,6 @@ package com.evolveum.midpoint.model.intest; import static org.testng.AssertJUnit.assertNull; -import static com.evolveum.midpoint.test.IntegrationTestTools.display; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNotNull; @@ -163,6 +162,7 @@ public void test050GetResourceRaw() throws Exception { preTestCleanup(AssignmentPolicyEnforcementType.POSITIVE); // precondition + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -173,13 +173,12 @@ public void test050GetResourceRaw() throws Exception { Collection> options = SelectorOptions.createCollection(GetOperationOptions.createRaw()); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject resource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, options , task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess("getObject result", result); + displayThen(TEST_NAME); + assertSuccess(result); display("Resource", resource); @@ -189,6 +188,7 @@ public void test050GetResourceRaw() throws Exception { assertNull("Schema sneaked in", ResourceTypeUtil.getResourceXsdSchema(resource)); + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -210,6 +210,7 @@ public void test052GetResourceNoFetch() throws Exception { preTestCleanup(AssignmentPolicyEnforcementType.POSITIVE); // precondition + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -221,14 +222,13 @@ public void test052GetResourceNoFetch() throws Exception { GetOperationOptions.createNoFetch()); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject resource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, options, task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess("getObject result", result); + displayThen(TEST_NAME); + assertSuccess(result); display("Resource", resource); @@ -238,6 +238,59 @@ public void test052GetResourceNoFetch() throws Exception { assertNull("Schema sneaked in", ResourceTypeUtil.getResourceXsdSchema(resource)); + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 1); // First "real" read + assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); + assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_INSTANCE_INITIALIZATION_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT, 0); + } + + /** + * Make sure that resource caching works well even if noFetch is used. + */ + @Test + public void test053GetResourceNoFetchAgain() throws Exception { + final String TEST_NAME = "test053GetResourceNoFetchAgain"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + preTestCleanup(AssignmentPolicyEnforcementType.POSITIVE); + + // precondition + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); + assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); + assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_INSTANCE_INITIALIZATION_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT, 0); + rememberCounter(InternalCounters.PRISM_OBJECT_CLONE_COUNT); + + Collection> options = SelectorOptions.createCollection( + GetOperationOptions.createNoFetch()); + + // WHEN + displayWhen(TEST_NAME); + PrismObject resource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, options, + task, result); + + // THEN + displayThen(TEST_NAME); + assertSuccess(result); + + display("Resource", resource); + + assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 1); + + assertResourceDummy(resource, false); + + assertNull("Schema sneaked in", ResourceTypeUtil.getResourceXsdSchema(resource)); + + // Previous noFetch read did NOT place resource in the cache. Because the resource + // may not be complete. + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 1); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -249,8 +302,8 @@ public void test052GetResourceNoFetch() throws Exception { * MID-3424 */ @Test - public void test053GetResourceNoFetchReadOnly() throws Exception { - final String TEST_NAME = "test053GetResourceNoFetchReadOnly"; + public void test055GetResourceNoFetchReadOnly() throws Exception { + final String TEST_NAME = "test055GetResourceNoFetchReadOnly"; displayTestTitle(TEST_NAME); // GIVEN @@ -259,6 +312,7 @@ public void test053GetResourceNoFetchReadOnly() throws Exception { preTestCleanup(AssignmentPolicyEnforcementType.POSITIVE); // precondition + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -271,14 +325,13 @@ public void test053GetResourceNoFetchReadOnly() throws Exception { Collection> options = SelectorOptions.createCollection(option); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject resource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, options, task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess("getObject result", result); + displayThen(TEST_NAME); + assertSuccess(result); display("Resource", resource); @@ -288,6 +341,9 @@ public void test053GetResourceNoFetchReadOnly() throws Exception { assertNull("Schema sneaked in", ResourceTypeUtil.getResourceXsdSchema(resource)); + // Previous noFetch read did NOT place resource in the cache. Because the resource + // may not be complete. + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 1); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -315,17 +371,16 @@ public void test100SearchResourcesNoFetch() throws Exception { Collection> options = SelectorOptions.createCollection(GetOperationOptions.createNoFetch()); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); List> resources = modelService.searchObjects(ResourceType.class, null, options, task, result); // THEN - TestUtil.displayThen(TEST_NAME); + displayThen(TEST_NAME); assertNotNull("null search return", resources); assertFalse("Empty search return", resources.isEmpty()); assertEquals("Unexpected number of resources found", 2, resources.size()); - result.computeStatus(); - TestUtil.assertSuccess("searchObjects result", result); + assertSuccess(result); assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 2); @@ -333,6 +388,8 @@ public void test100SearchResourcesNoFetch() throws Exception { assertResource(resource, false); } + // No explicit get. Search is doing all the work. + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -364,17 +421,16 @@ public void test102SearchResourcesNoFetchReadOnly() throws Exception { Collection> options = SelectorOptions.createCollection(option); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); List> resources = modelService.searchObjects(ResourceType.class, null, options, task, result); // THEN - TestUtil.displayThen(TEST_NAME); + displayThen(TEST_NAME); assertNotNull("null search return", resources); assertFalse("Empty search return", resources.isEmpty()); assertEquals("Unexpected number of resources found", 2, resources.size()); - result.computeStatus(); - TestUtil.assertSuccess("searchObjects result", result); + assertSuccess(result); assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 0); @@ -382,6 +438,8 @@ public void test102SearchResourcesNoFetchReadOnly() throws Exception { assertResource(resource, false); } + // No explicit get. Search is doing all the work. + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -419,19 +477,20 @@ public void test105SearchResourcesIterativeNoFetch() throws Exception { Collection> options = SelectorOptions.createCollection(GetOperationOptions.createNoFetch()); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); modelService.searchObjectsIterative(ResourceType.class, null, handler, options, task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess("searchObjects result", result); + displayThen(TEST_NAME); + assertSuccess(result); assertFalse("Empty search return", resources.isEmpty()); assertEquals("Unexpected number of resources found", 2, resources.size()); assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 2); + // No explicit get. Search is doing all the work. + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -471,19 +530,20 @@ public void test107SearchResourcesIterativeNoFetchReadOnly() throws Exception { Collection> options = SelectorOptions.createCollection(option); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); modelService.searchObjectsIterative(ResourceType.class, null, handler, options, task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess("searchObjects result", result); + displayThen(TEST_NAME); + assertSuccess(result); assertFalse("Empty search return", resources.isEmpty()); assertEquals("Unexpected number of resources found", 2, resources.size()); assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 2); + // No explicit get. Search is doing all the work. + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -506,18 +566,21 @@ public void test110GetResourceDummy() throws Exception { rememberCounter(InternalCounters.PRISM_OBJECT_CLONE_COUNT); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject resource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null , task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess("getObject result", result); + displayThen(TEST_NAME); + assertSuccess(result); assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 4); assertResourceDummy(resource, true); + // TODO not sure why are there 2 read counts. Should be 1. But this is not that important right now. + // Some overhead on initial resource read is OK. What is important is that it does not increase during + // normal account operations. + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 2); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 1); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 1); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 1); @@ -545,19 +608,19 @@ public void test112GetResourceDummyReadOnly() throws Exception { GetOperationOptions.createReadOnly()); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject resource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, options , task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess("getObject result", result); + displayThen(TEST_NAME); + assertSuccess(result); assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 1); assertResourceDummy(resource, true); + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -582,22 +645,23 @@ public void test120SearchResources() throws Exception { assertSteadyResources(); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); List> resources = modelService.searchObjects(ResourceType.class, null, null, task, result); // THEN - TestUtil.displayThen(TEST_NAME); + displayThen(TEST_NAME); assertNotNull("null search return", resources); assertFalse("Empty search return", resources.isEmpty()); assertEquals("Unexpected number of resources found", 2, resources.size()); - result.computeStatus(); - TestUtil.assertSuccess("searchObjects result", result); + assertSuccess(result); for (PrismObject resource: resources) { assertResource(resource, true); } + // Obviously, there is some uninitialized resource in the system + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 1); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 1); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 1); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 1); @@ -630,8 +694,7 @@ public void test125SearchResourcesIterative() throws Exception { modelService.searchObjectsIterative(ResourceType.class, null, handler, null, task, result); // THEN - result.computeStatus(); - TestUtil.assertSuccess("searchObjects result", result); + assertSuccess(result); assertFalse("Empty search return", resources.isEmpty()); assertEquals("Unexpected number of resources found", 2, resources.size()); @@ -747,8 +810,7 @@ public void test200GetResourceRawAfterSchema() throws Exception { lastVersion = resource.getVersion(); display("Initial version", lastVersion); - result.computeStatus(); - TestUtil.assertSuccess("getObject result", result); + assertSuccess(result); IntegrationTestTools.displayXml("Initialized dummy resource", resource); } @@ -769,11 +831,11 @@ public void test210GetResourceDummyRed() throws Exception { rememberCounter(InternalCounters.PRISM_OBJECT_CLONE_COUNT); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject resource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_RED_OID, null , task, result); // THEN - TestUtil.displayThen(TEST_NAME); + displayThen(TEST_NAME); assertSuccess(result); assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 1); @@ -812,13 +874,12 @@ public void test750GetResourceRaw() throws Exception { Collection> options = SelectorOptions.createCollection(GetOperationOptions.createRaw()); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject resource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, options , task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess("getObject result", result); + displayThen(TEST_NAME); + assertSuccess(result); display("Resource", resource); IntegrationTestTools.displayXml("Initialized dummy resource", resource); @@ -847,13 +908,12 @@ public void test752GetResourceDummy() throws Exception { rememberCounter(InternalCounters.PRISM_OBJECT_CLONE_COUNT); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject resource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null , task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess(result); + displayThen(TEST_NAME); + assertSuccess(result); assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 1); @@ -1044,13 +1104,12 @@ public void test800GetResourceDummy() throws Exception { rememberCounter(InternalCounters.PRISM_OBJECT_CLONE_COUNT); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject resource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null , task, result); // THEN - TestUtil.displayThen(TEST_NAME); - result.computeStatus(); - TestUtil.assertSuccess(result); + displayThen(TEST_NAME); + assertSuccess(result); assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 1); diff --git a/model/model-intest/testng-integration.xml b/model/model-intest/testng-integration.xml index 8b31c226ace..649851ac5b7 100644 --- a/model/model-intest/testng-integration.xml +++ b/model/model-intest/testng-integration.xml @@ -63,6 +63,7 @@ + diff --git a/model/model-test/pom.xml b/model/model-test/pom.xml index 3e8a3cb97e7..b6c368a3867 100644 --- a/model/model-test/pom.xml +++ b/model/model-test/pom.xml @@ -185,6 +185,10 @@ org.jetbrains annotations-java5 + + com.h2database + h2 + com.evolveum.midpoint.provisioning diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/TextFormatter.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/TextFormatter.java index 09a6f8b2826..1648fc35056 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/TextFormatter.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/formatters/TextFormatter.java @@ -68,6 +68,7 @@ public class TextFormatter { private static final Trace LOGGER = TraceManager.getTrace(TextFormatter.class); + @SuppressWarnings("unused") public String formatObjectModificationDelta(ObjectDelta objectDelta, List hiddenPaths, boolean showOperationalAttributes) { return formatObjectModificationDelta(objectDelta, hiddenPaths, showOperationalAttributes, null, null); } @@ -166,7 +167,7 @@ private void formatItemDeltaContent(StringBuilder sb, ItemDelta itemDelta, List< private void formatItemDeltaValues(StringBuilder sb, String type, Collection values, boolean mightBeRemoved, List hiddenPaths, boolean showOperationalAttributes) { if (values != null) { for (PrismValue prismValue : values) { - sb.append(" - " + type + ": "); + sb.append(" - ").append(type).append(": "); String prefix = " "; formatPrismValue(sb, prefix, prismValue, mightBeRemoved, hiddenPaths, showOperationalAttributes); if (!(prismValue instanceof PrismContainerValue)) { // container values already end with newline @@ -254,15 +255,19 @@ private void formatContainerValue(StringBuilder sb, String prefix, PrismContaine private void formatPrismContainer(StringBuilder sb, String prefix, Item item, boolean mightBeRemoved, List hiddenPaths, boolean showOperationalAttributes) { for (PrismContainerValue subContainerValue : ((PrismContainer) item).getValues()) { - sb.append(prefix); - sb.append(" - "); - sb.append(getItemLabel(item)); - if (subContainerValue.getId() != null) { - sb.append(" #").append(subContainerValue.getId()); - } - sb.append(":\n"); String prefixSubContainer = prefix + " "; - formatContainerValue(sb, prefixSubContainer, subContainerValue, mightBeRemoved, hiddenPaths, showOperationalAttributes); + StringBuilder valueSb = new StringBuilder(); + formatContainerValue(valueSb, prefixSubContainer, subContainerValue, mightBeRemoved, hiddenPaths, showOperationalAttributes); + if (valueSb.length() > 0) { + sb.append(prefix); + sb.append(" - "); + sb.append(getItemLabel(item)); + if (subContainerValue.getId() != null) { + sb.append(" #").append(subContainerValue.getId()); + } + sb.append(":\n"); + sb.append(valueSb.toString()); + } } } @@ -274,7 +279,7 @@ private void formatPrismReference(StringBuilder sb, String prefix, Item item, bo if (item.size() > 1) { for (PrismReferenceValue referenceValue : ((PrismReference) item).getValues()) { sb.append("\n"); - sb.append(prefix + " - "); + sb.append(prefix).append(" - "); sb.append(formatReferenceValue(referenceValue, mightBeRemoved)); } } else if (item.size() == 1) { @@ -289,13 +294,13 @@ private void formatPrismProperty(StringBuilder sb, String prefix, Item item) { sb.append(getItemLabel(item)); sb.append(": "); if (item.size() > 1) { - for (PrismPropertyValue propertyValue : ((PrismProperty) item).getValues()) { + for (PrismPropertyValue propertyValue : ((PrismProperty) item).getValues()) { sb.append("\n"); - sb.append(prefix + " - "); + sb.append(prefix).append(" - "); sb.append(ValueDisplayUtil.toStringValue(propertyValue)); } } else if (item.size() == 1) { - sb.append(ValueDisplayUtil.toStringValue(((PrismProperty) item).getValue(0))); + sb.append(ValueDisplayUtil.toStringValue(((PrismProperty) item).getValue(0))); } sb.append("\n"); } @@ -354,6 +359,7 @@ private PrismObject getPrismObject(String oid, boolean mig if (!mightBeRemoved) { LoggingUtils.logException(LOGGER, "Couldn't resolve reference when displaying object name within a notification (it might be already removed)", e); } else { + // ok, accepted } } catch (SchemaException e) { LoggingUtils.logException(LOGGER, "Couldn't resolve reference when displaying object name within a notification", e); @@ -361,14 +367,6 @@ private PrismObject getPrismObject(String oid, boolean mig return null; } - private String localPartOfType(Item item) { - if (item.getDefinition() != null) { - return localPart(item.getDefinition().getTypeName()); - } else { - return null; - } - } - private String localPart(QName qname) { return qname == null ? null : qname.getLocalPart(); } @@ -441,7 +439,7 @@ private ItemPath getPathToExplain(ItemDelta itemDelta) { } private List filterAndOrderItemDeltas(ObjectDelta objectDelta, List hiddenPaths, boolean showOperationalAttributes) { - List toBeDisplayed = new ArrayList(objectDelta.getModifications().size()); + List toBeDisplayed = new ArrayList<>(objectDelta.getModifications().size()); List noDefinition = new ArrayList<>(); for (ItemDelta itemDelta: objectDelta.getModifications()) { if (itemDelta.getDefinition() != null) { @@ -457,20 +455,17 @@ private List filterAndOrderItemDeltas(ObjectDelta() { - @Override - public int compare(ItemDelta delta1, ItemDelta delta2) { - Integer order1 = delta1.getDefinition().getDisplayOrder(); - Integer order2 = delta2.getDefinition().getDisplayOrder(); - if (order1 != null && order2 != null) { - return order1 - order2; - } else if (order1 == null && order2 == null) { - return 0; - } else if (order1 == null) { - return 1; - } else { - return -1; - } + toBeDisplayed.sort((delta1, delta2) -> { + Integer order1 = delta1.getDefinition().getDisplayOrder(); + Integer order2 = delta2.getDefinition().getDisplayOrder(); + if (order1 != null && order2 != null) { + return order1 - order2; + } else if (order1 == null && order2 == null) { + return 0; + } else if (order1 == null) { + return 1; + } else { + return -1; } }); return toBeDisplayed; @@ -486,12 +481,12 @@ private List filterAndOrderItems(List items, List hiddenPa if (items == null) { return new ArrayList<>(); } - List toBeDisplayed = new ArrayList(items.size()); + List toBeDisplayed = new ArrayList<>(items.size()); List noDefinition = new ArrayList<>(); for (Item item : items) { if (item.getDefinition() != null) { boolean isHidden = NotificationFunctionsImpl.isAmongHiddenPaths(item.getPath(), hiddenPaths); - if (!isHidden && (showOperationalAttributes || !item.getDefinition().isOperational())) { + if (!isHidden && (showOperationalAttributes || !item.getDefinition().isOperational()) && !item.isEmpty()) { toBeDisplayed.add(item); } } else { @@ -502,20 +497,17 @@ private List filterAndOrderItems(List items, List hiddenPa LOGGER.error("Items {} without definition - THEY WILL NOT BE INCLUDED IN NOTIFICATION.\nAll items:\n{}", noDefinition, DebugUtil.debugDump(items)); } - Collections.sort(toBeDisplayed, new Comparator() { - @Override - public int compare(Item item1, Item item2) { - Integer order1 = item1.getDefinition().getDisplayOrder(); - Integer order2 = item2.getDefinition().getDisplayOrder(); - if (order1 != null && order2 != null) { - return order1 - order2; - } else if (order1 == null && order2 == null) { - return 0; - } else if (order1 == null) { - return 1; - } else { - return -1; - } + toBeDisplayed.sort((item1, item2) -> { + Integer order1 = item1.getDefinition().getDisplayOrder(); + Integer order2 = item2.getDefinition().getDisplayOrder(); + if (order1 != null && order2 != null) { + return order1 - order2; + } else if (order1 == null && order2 == null) { + return 0; + } else if (order1 == null) { + return 1; + } else { + return -1; } }); return toBeDisplayed; diff --git a/model/report-impl/pom.xml b/model/report-impl/pom.xml index 8e8454dd744..dd5d0fd15be 100644 --- a/model/report-impl/pom.xml +++ b/model/report-impl/pom.xml @@ -103,6 +103,11 @@ model-impl 3.7-SNAPSHOT + + com.evolveum.midpoint.model + workflow-api + 3.7-SNAPSHOT + org.jetbrains diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/BaseModelInvocationProcessingHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/BaseModelInvocationProcessingHelper.java index 18f1123fdb5..48a7814a38e 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/BaseModelInvocationProcessingHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/BaseModelInvocationProcessingHelper.java @@ -80,7 +80,7 @@ public class BaseModelInvocationProcessingHelper { */ public WfTaskCreationInstruction createInstructionForRoot(ChangeProcessor changeProcessor, ModelContext modelContext, Task taskFromModel, ModelContext contextForRoot, OperationResult result) throws SchemaException { - WfTaskCreationInstruction instruction; + WfTaskCreationInstruction instruction; if (contextForRoot != null) { instruction = WfTaskCreationInstruction.createModelOnly(changeProcessor, contextForRoot); } else { @@ -92,6 +92,7 @@ public WfTaskCreationInstruction createInstructionForRoot(ChangeProcessor change instruction.setTaskOwner(taskFromModel.getOwner()); instruction.setCreateTaskAsWaiting(); + instruction.setObjectRef(modelContext, result); instruction.setRequesterRef(getRequester(taskFromModel, result)); return instruction; } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java index 49a6c81d575..efb6bc4903a 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java @@ -271,8 +271,9 @@ private WfTask submitTask0(ModelContext context, ObjectTreeDeltas changesWithout WfConfigurationType wfConfigurationType, OperationResult result) throws SchemaException, ObjectNotFoundException { if (changesWithoutApproval != null && !changesWithoutApproval.isEmpty() && executionMode != ALL_AFTERWARDS) { ModelContext task0context = contextCopyWithDeltasReplaced(context, changesWithoutApproval); - WfTaskCreationInstruction instruction0 = WfTaskCreationInstruction.createModelOnly(rootWfTask.getChangeProcessor(), task0context); + WfTaskCreationInstruction instruction0 = WfTaskCreationInstruction.createModelOnly(rootWfTask.getChangeProcessor(), task0context); instruction0.setTaskName("Executing changes that do not require approval"); + instruction0.setObjectRef(context, result); return wfTaskController.submitWfTask(instruction0, rootWfTask, wfConfigurationType, result); } else { return null; diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/WfTaskCreationInstruction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/WfTaskCreationInstruction.java index 3edb8cb0e57..fd424af94d1 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/WfTaskCreationInstruction.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/WfTaskCreationInstruction.java @@ -410,38 +410,74 @@ public Task createTask(WfTaskController taskController, Task parentTask, WfConfi } // serialization WfExecutionTasksSerializationType serialization = tasksConfig.getSerialization(); - if (serialization != null && !Boolean.FALSE.equals(serialization.isEnabled()) && parentTask != null) { - String groupPrefix = serialization.getGroupPrefix() != null ? - serialization.getGroupPrefix() : DEFAULT_EXECUTION_GROUP_PREFIX_FOR_SERIALIZATION; - String groupName = groupPrefix + parentTask.getTaskIdentifier(); - Duration retryAfter; - if (serialization.getRetryAfter() != null) { - if (constraints != null && constraints.getRetryAfter() != null && !constraints.getRetryAfter().equals(serialization.getRetryAfter())) { - LOGGER.warn( - "Workflow configuration: task constraints retryAfter ({}) is different from serialization retryAfter ({}) -- using the latter", - constraints.getRetryAfter(), serialization.getRetryAfter()); + if (serialization != null && !Boolean.FALSE.equals(serialization.isEnabled())) { + List scopes = new ArrayList<>(serialization.getScope()); + if (scopes.isEmpty()) { + scopes.add(WfExecutionTasksSerializationScopeType.OBJECT); + } + List groups = new ArrayList<>(scopes.size()); + for (WfExecutionTasksSerializationScopeType scope : scopes) { + String groupPrefix = serialization.getGroupPrefix() != null + ? serialization.getGroupPrefix() : DEFAULT_EXECUTION_GROUP_PREFIX_FOR_SERIALIZATION; + String groupSuffix = getGroupSuffix(scope, wfContext, parentTask, task); + if (groupSuffix == null) { + continue; } - retryAfter = serialization.getRetryAfter(); - } else if (constraints != null && constraints.getRetryAfter() != null) { - retryAfter = constraints.getRetryAfter(); - } else { - retryAfter = XmlTypeConverter.createDuration(DEFAULT_SERIALIZATION_RETRY_TIME); + groups.add(groupPrefix + scope.value() + ":" + groupSuffix); } - if (taskBean.getExecutionConstraints() == null) { - taskBean.setExecutionConstraints(new TaskExecutionConstraintsType()); + if (!groups.isEmpty()) { + Duration retryAfter; + if (serialization.getRetryAfter() != null) { + if (constraints != null && constraints.getRetryAfter() != null && !constraints.getRetryAfter() + .equals(serialization.getRetryAfter())) { + LOGGER.warn( + "Workflow configuration: task constraints retryAfter ({}) is different from serialization retryAfter ({}) -- using the latter", + constraints.getRetryAfter(), serialization.getRetryAfter()); + } + retryAfter = serialization.getRetryAfter(); + } else if (constraints != null && constraints.getRetryAfter() != null) { + retryAfter = constraints.getRetryAfter(); + } else { + retryAfter = XmlTypeConverter.createDuration(DEFAULT_SERIALIZATION_RETRY_TIME); + } + TaskExecutionConstraintsType executionConstraints = taskBean.getExecutionConstraints(); + if (executionConstraints == null) { + executionConstraints = new TaskExecutionConstraintsType(); + taskBean.setExecutionConstraints(executionConstraints); + } + for (String group : groups) { + executionConstraints + .beginSecondaryGroup() + .group(group) + .groupTaskLimit(1); + } + executionConstraints.setRetryAfter(retryAfter); + LOGGER.trace("Setting groups {} with a limit of 1 for task {}", groups, task); } - taskBean.getExecutionConstraints() - .beginSecondaryGroup() - .group(groupName) - .groupTaskLimit(1) - .end() - .retryAfter(retryAfter); - LOGGER.trace("Setting group '{}' with a limit of 1 for task {}", groupName, task); } } return task; } + private String getGroupSuffix(WfExecutionTasksSerializationScopeType scope, WfContextType wfContext, Task parentTask, Task task) { + switch (scope) { + case GLOBAL: return ""; + case OBJECT: + String oid = wfContext.getObjectRef() != null ? wfContext.getObjectRef().getOid() : null; + if (oid == null) { + LOGGER.warn("No object OID present, synchronization with the scope of {} couldn't be set up for task {}", scope, task); + return null; + } + return oid; + case TARGET: + return wfContext.getTargetRef() != null ? wfContext.getTargetRef().getOid() : null; // null can occur so let's be silent then + case OPERATION: + return parentTask != null ? parentTask.getTaskIdentifier() : null; // null can occur so let's be silent then + default: + throw new AssertionError("Unknown scope: " + scope); + } + } + // FIXME brutal hack because of objectDelta should be in wfContext when evaluating auto completion expression public void createProcessorContent() { if (processorContent != null) { diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/AbstractWfTestPolicy.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/AbstractWfTestPolicy.java index fc8e1c0bbba..e63d4f52b46 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/AbstractWfTestPolicy.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/AbstractWfTestPolicy.java @@ -93,6 +93,8 @@ public class AbstractWfTestPolicy extends AbstractModelImplementationIntegration public static final File USER_ADMINISTRATOR_FILE = new File(TEST_RESOURCE_DIR, "user-administrator.xml"); protected static final File USER_JACK_FILE = new File(TEST_RESOURCE_DIR, "user-jack.xml"); + protected static final File USER_BOB_FILE = new File(TEST_RESOURCE_DIR, "user-bob.xml"); + protected static final File USER_CHUCK_FILE = new File(TEST_RESOURCE_DIR, "user-chuck.xml"); protected static final File USER_LEAD1_FILE = new File(TEST_RESOURCE_DIR, "user-lead1.xml"); protected static final File USER_LEAD1_DEPUTY_1_FILE = new File(TEST_RESOURCE_DIR, "user-lead1-deputy1.xml"); protected static final File USER_LEAD1_DEPUTY_2_FILE = new File(TEST_RESOURCE_DIR, "user-lead1-deputy2.xml"); @@ -134,6 +136,8 @@ public class AbstractWfTestPolicy extends AbstractModelImplementationIntegration protected static final String USER_ADMINISTRATOR_OID = SystemObjectsType.USER_ADMINISTRATOR.value(); protected String userJackOid; + protected String userBobOid; + protected String userChuckOid; protected String userLead1Oid; protected String userLead1Deputy1Oid; protected String userLead1Deputy2Oid; @@ -202,6 +206,8 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti metaroleApproveUnassign = repoAddObjectFromFile(METAROLE_APPROVE_UNASSIGN_FILE, initResult).getOid(); userJackOid = repoAddObjectFromFile(USER_JACK_FILE, initResult).getOid(); + userBobOid = repoAddObjectFromFile(USER_BOB_FILE, initResult).getOid(); + userChuckOid = repoAddObjectFromFile(USER_CHUCK_FILE, initResult).getOid(); roleRole1Oid = repoAddObjectFromFile(ROLE_ROLE1_FILE, initResult).getOid(); roleRole1aOid = repoAddObjectFromFile(ROLE_ROLE1A_FILE, initResult).getOid(); roleRole1bOid = repoAddObjectFromFile(ROLE_ROLE1B_FILE, initResult).getOid(); diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/other/TestParallelApprovals.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/other/TestParallelApprovals.java index 536f4b46869..0bd276162be 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/other/TestParallelApprovals.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/other/TestParallelApprovals.java @@ -32,20 +32,22 @@ import com.evolveum.midpoint.test.util.TestUtil; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.wf.impl.policy.AbstractWfTestPolicy; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.testng.annotations.Test; import java.io.File; -import java.util.Collections; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import static com.evolveum.midpoint.model.api.ModelExecuteOptions.createExecuteImmediatelyAfterApproval; import static com.evolveum.midpoint.xml.ns._public.common.common_3.TaskExecutionStatusType.CLOSED; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; /** * @author mederly @@ -54,11 +56,12 @@ @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class TestParallelApprovals extends AbstractWfTestPolicy { + private static final File ROLE_ROLE50A_FILE = new File(TEST_RESOURCE_DIR, "role-role50a-slow.xml"); private static final File ROLE_ROLE51A_FILE = new File(TEST_RESOURCE_DIR, "role-role51a-slow.xml"); private static final File ROLE_ROLE52A_FILE = new File(TEST_RESOURCE_DIR, "role-role52a-slow.xml"); private static final File ROLE_ROLE53A_FILE = new File(TEST_RESOURCE_DIR, "role-role53a-slow.xml"); - private String roleRole51aOid, roleRole52aOid, roleRole53aOid; + private String roleRole50aOid, roleRole51aOid, roleRole52aOid, roleRole53aOid; @Override protected PrismObject getDefaultActor() { @@ -69,6 +72,7 @@ protected PrismObject getDefaultActor() { public void initSystem(Task initTask, OperationResult initResult) throws Exception { super.initSystem(initTask, initResult); + roleRole50aOid = repoAddObjectFromFile(ROLE_ROLE50A_FILE, initResult).getOid(); roleRole51aOid = repoAddObjectFromFile(ROLE_ROLE51A_FILE, initResult).getOid(); roleRole52aOid = repoAddObjectFromFile(ROLE_ROLE52A_FILE, initResult).getOid(); roleRole53aOid = repoAddObjectFromFile(ROLE_ROLE53A_FILE, initResult).getOid(); @@ -85,6 +89,8 @@ protected void updateSystemConfiguration(SystemConfigurationType systemConfigura .retryAfter(XmlTypeConverter.createDuration(1000)); // makes tests run faster } + private CheckingTaskListener listener; + @Test public void test100ParallelApprovals() throws Exception { final String TEST_NAME = "test100ParallelApprovals"; @@ -98,6 +104,7 @@ public void test100ParallelApprovals() throws Exception { displayWhen(TEST_NAME); ObjectDelta assignDelta = DeltaBuilder.deltaFor(UserType.class, prismContext) .item(UserType.F_ASSIGNMENT).add( + ObjectTypeUtil.createAssignmentTo(roleRole50aOid, ObjectTypes.ROLE, prismContext), ObjectTypeUtil.createAssignmentTo(roleRole51aOid, ObjectTypes.ROLE, prismContext), ObjectTypeUtil.createAssignmentTo(roleRole52aOid, ObjectTypes.ROLE, prismContext), ObjectTypeUtil.createAssignmentTo(roleRole53aOid, ObjectTypes.ROLE, prismContext)) @@ -111,66 +118,266 @@ public void test100ParallelApprovals() throws Exception { String rootTaskOid = wfTaskUtil.getRootTaskOid(task); display("root task", getTask(rootTaskOid)); - CheckingTaskListener listener = new CheckingTaskListener(rootTaskOid); + if (listener != null) { + taskManager.unregisterTaskListener(listener); + } + listener = new CheckingTaskListener(singleton(rootTaskOid)); + taskManager.registerTaskListener(listener); + + approveAllWorkItems(task, result); + + waitForTaskCloseOrSuspend(rootTaskOid, 120000, 1000); + + // THEN + + PrismObject rootTask = getTask(rootTaskOid); + assertNull("Exception has occurred " + listener.getException(), listener.getException()); + assertEquals("Wrong root task1 status", CLOSED, rootTask.asObjectable().getExecutionStatus()); + + PrismObject jack = getUser(userJackOid); + assertAssignedRole(jack, roleRole50aOid); + assertAssignedRole(jack, roleRole51aOid); + assertAssignedRole(jack, roleRole52aOid); + assertAssignedRole(jack, roleRole53aOid); + } + + @Test + public void test110ParallelApprovalsAdd() throws Exception { + final String TEST_NAME = "test110ParallelApprovalsAdd"; + TestUtil.displayTestTitle(this, TEST_NAME); + login(userAdministrator); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + if (listener != null) { + taskManager.unregisterTaskListener(listener); + } + listener = new CheckingTaskListener(); taskManager.registerTaskListener(listener); + // WHEN + displayWhen(TEST_NAME); + UserType alice = prismContext.createObjectable(UserType.class) + .name("alice") + .assignment(ObjectTypeUtil.createAssignmentTo(roleRole50aOid, ObjectTypes.ROLE, prismContext)) + .assignment(ObjectTypeUtil.createAssignmentTo(roleRole51aOid, ObjectTypes.ROLE, prismContext)) + .assignment(ObjectTypeUtil.createAssignmentTo(roleRole52aOid, ObjectTypes.ROLE, prismContext)) + .assignment(ObjectTypeUtil.createAssignmentTo(roleRole53aOid, ObjectTypes.ROLE, prismContext)); + executeChanges(ObjectDelta.createAddDelta(alice.asPrismObject()), createExecuteImmediatelyAfterApproval(), task, result); // should start approval processes + + display("Task after operation", task); + String rootTaskOid = wfTaskUtil.getRootTaskOid(task); + display("root task", getTask(rootTaskOid)); + + listener.setTasksToSuspendOnError(singleton(rootTaskOid)); + + approveAllWorkItems(task, result); + waitForTaskCloseOrSuspend(rootTaskOid, 120000, 1000); + + // THEN + + PrismObject rootTask = getTask(rootTaskOid); + assertNull("Exception has occurred " + listener.getException(), listener.getException()); + assertEquals("Wrong root task1 status", CLOSED, rootTask.asObjectable().getExecutionStatus()); + + PrismObject aliceAfter = findUserByUsername("alice"); + assertAssignedRole(aliceAfter, roleRole50aOid); + assertAssignedRole(aliceAfter, roleRole51aOid); + assertAssignedRole(aliceAfter, roleRole52aOid); + assertAssignedRole(aliceAfter, roleRole53aOid); + } + + public void approveAllWorkItems(Task task, OperationResult result) throws Exception { List workItems = getWorkItems(task, result); display("work items", workItems); display("approving work items"); for (WorkItemType workItem : workItems) { workflowManager.completeWorkItem(workItem.getExternalId(), true, null, null, null, result); } + } - waitForTaskCloseOrSuspend(rootTaskOid, 120000, 1000); + @Test + public void test120ParallelApprovalsInTwoOperations() throws Exception { + final String TEST_NAME = "test120ParallelApprovalsInTwoOperations"; + TestUtil.displayTestTitle(this, TEST_NAME); + login(userAdministrator); + + Task task = createTask(TEST_NAME); + Task task1 = createTask(TEST_NAME); + Task task2 = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + if (listener != null) { + taskManager.unregisterTaskListener(listener); + } + listener = new CheckingTaskListener(); + taskManager.registerTaskListener(listener); + + // WHEN + displayWhen(TEST_NAME); + ObjectDelta assignDelta1 = DeltaBuilder.deltaFor(UserType.class, prismContext) + .item(UserType.F_ASSIGNMENT).add( + ObjectTypeUtil.createAssignmentTo(roleRole50aOid, ObjectTypes.ROLE, prismContext), + ObjectTypeUtil.createAssignmentTo(roleRole51aOid, ObjectTypes.ROLE, prismContext)) + .asObjectDeltaCast(userBobOid); + executeChanges(assignDelta1, createExecuteImmediatelyAfterApproval(), task1, result); // should start approval processes + ObjectDelta assignDelta2 = DeltaBuilder.deltaFor(UserType.class, prismContext) + .item(UserType.F_ASSIGNMENT).add( + ObjectTypeUtil.createAssignmentTo(roleRole50aOid, ObjectTypes.ROLE, prismContext), + ObjectTypeUtil.createAssignmentTo(roleRole52aOid, ObjectTypes.ROLE, prismContext), + ObjectTypeUtil.createAssignmentTo(roleRole53aOid, ObjectTypes.ROLE, prismContext)) + .asObjectDeltaCast(userBobOid); + executeChanges(assignDelta2, createExecuteImmediatelyAfterApproval(), task2, result); // should start approval processes + assertNotAssignedRole(userBobOid, roleRole51aOid, task, result); + assertNotAssignedRole(userBobOid, roleRole52aOid, task, result); + assertNotAssignedRole(userBobOid, roleRole53aOid, task, result); + + display("Task1 after operation", task1); + display("Task2 after operation", task2); + String rootTask1Oid = wfTaskUtil.getRootTaskOid(task1); + String rootTask2Oid = wfTaskUtil.getRootTaskOid(task2); + display("root task 1", getTask(rootTask1Oid)); + display("root task 2", getTask(rootTask2Oid)); + + assertNull("Exception has occurred " + listener.getException(), listener.getException()); + listener.setTasksToSuspendOnError(Arrays.asList(rootTask1Oid, rootTask2Oid)); + + approveAllWorkItems(task, result); + + waitForTaskCloseOrSuspend(rootTask1Oid, 120000, 1000); + waitForTaskCloseOrSuspend(rootTask2Oid, 120000, 1000); // THEN - PrismObject rootTask = getTask(rootTaskOid); - if (listener.getException() != null || rootTask.asObjectable().getExecutionStatus() != CLOSED) { - fail("root task has not completed; recorded exception = " + listener.getException()); + PrismObject rootTask1 = getTask(rootTask1Oid); + PrismObject rootTask2 = getTask(rootTask2Oid); + assertNull("Exception has occurred " + listener.getException(), listener.getException()); + assertEquals("Wrong root task1 status", CLOSED, rootTask1.asObjectable().getExecutionStatus()); + assertEquals("Wrong root task2 status", CLOSED, rootTask2.asObjectable().getExecutionStatus()); + + PrismObject bob = getUser(userBobOid); + assertAssignedRole(bob, roleRole50aOid); + assertAssignedRole(bob, roleRole51aOid); + assertAssignedRole(bob, roleRole52aOid); + assertAssignedRole(bob, roleRole53aOid); + } + + @Test + public void test130ParallelApprovalsInThreeSummarizingOperations() throws Exception { + final String TEST_NAME = "test130ParallelApprovalsInThreeSummarizingOperations"; + TestUtil.displayTestTitle(this, TEST_NAME); + login(userAdministrator); + + Task task = createTask(TEST_NAME); + Task task1 = createTask(TEST_NAME); + Task task2 = createTask(TEST_NAME); + Task task3 = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + if (listener != null) { + taskManager.unregisterTaskListener(listener); } + listener = new CheckingTaskListener(); + taskManager.registerTaskListener(listener); - PrismObject jack = getUser(userJackOid); - assertAssignedRole(jack, roleRole51aOid); - assertAssignedRole(jack, roleRole52aOid); - assertAssignedRole(jack, roleRole53aOid); + // WHEN + displayWhen(TEST_NAME); + // three separate approval contexts, "summarizing" as the deltas are executed after all approvals + assignRole(userChuckOid, roleRole51aOid, task1, result); + assignRole(userChuckOid, roleRole52aOid, task2, result); + assignRole(userChuckOid, roleRole53aOid, task3, result); + assertNotAssignedRole(userChuckOid, roleRole51aOid, task, result); + assertNotAssignedRole(userChuckOid, roleRole52aOid, task, result); + assertNotAssignedRole(userChuckOid, roleRole53aOid, task, result); + + display("Task1 after operation", task1); + display("Task2 after operation", task2); + display("Task3 after operation", task3); + String rootTask1Oid = wfTaskUtil.getRootTaskOid(task1); + String rootTask2Oid = wfTaskUtil.getRootTaskOid(task2); + String rootTask3Oid = wfTaskUtil.getRootTaskOid(task3); + display("root task 1", getTask(rootTask1Oid)); + display("root task 2", getTask(rootTask2Oid)); + display("root task 3", getTask(rootTask3Oid)); + + assertNull("Exception has occurred " + listener.getException(), listener.getException()); + listener.setTasksToSuspendOnError(Arrays.asList(rootTask1Oid, rootTask2Oid, rootTask3Oid)); + + approveAllWorkItems(task, result); + + waitForTaskCloseOrSuspend(rootTask1Oid, 120000, 1000); + waitForTaskCloseOrSuspend(rootTask2Oid, 120000, 1000); + waitForTaskCloseOrSuspend(rootTask3Oid, 120000, 1000); + + // THEN + + PrismObject rootTask1 = getTask(rootTask1Oid); + PrismObject rootTask2 = getTask(rootTask2Oid); + PrismObject rootTask3 = getTask(rootTask3Oid); + assertNull("Exception has occurred " + listener.getException(), listener.getException()); + assertEquals("Wrong root task1 status", CLOSED, rootTask1.asObjectable().getExecutionStatus()); + assertEquals("Wrong root task2 status", CLOSED, rootTask2.asObjectable().getExecutionStatus()); + assertEquals("Wrong root task3 status", CLOSED, rootTask3.asObjectable().getExecutionStatus()); + + PrismObject chuck = getUser(userChuckOid); + assertAssignedRole(chuck, roleRole51aOid); + assertAssignedRole(chuck, roleRole52aOid); + assertAssignedRole(chuck, roleRole53aOid); } private class CheckingTaskListener implements TaskListener { - private String rootTaskOid; + private Collection tasksToSuspendOnError; private Task executing; private RuntimeException exception; - public CheckingTaskListener(String rootTaskOid) { - this.rootTaskOid = rootTaskOid; + public CheckingTaskListener() { + this.tasksToSuspendOnError = emptySet(); + } + + public CheckingTaskListener(Collection tasksToSuspendOnError) { + this.tasksToSuspendOnError = tasksToSuspendOnError; } public RuntimeException getException() { return exception; } + public void setTasksToSuspendOnError(Collection tasksToSuspendOnError) { + this.tasksToSuspendOnError = tasksToSuspendOnError; + if (exception != null) { + suspendTasks(); + } + } + @Override public synchronized void onTaskStart(Task task) { if (!ModelOperationTaskHandler.MODEL_OPERATION_TASK_URI.equals(task.getHandlerUri())) { return; } - System.out.println("Starting " + task + ", handler uri " + task.getHandlerUri()); + System.out.println(Thread.currentThread().getName() + ": Starting " + task + ", handler uri " + task.getHandlerUri() + ", groups " + task.getGroups()); if (executing != null) { exception = new IllegalStateException("Started task " + task + " but another one is already executing: " + executing); System.out.println(exception.getMessage()); - // suspend root task in order to fail faster - taskManager.suspendTasks(Collections.singleton(rootTaskOid), TaskManager.DO_NOT_WAIT, new OperationResult("dummy")); + display("newly started task", task); + display("already executing task", executing); + suspendTasks(); } executing = task; } + public void suspendTasks() { + // suspend root task in order to fail faster + taskManager.suspendTasks(tasksToSuspendOnError, TaskManager.DO_NOT_WAIT, new OperationResult("dummy")); + } + @Override public synchronized void onTaskFinish(Task task, TaskRunResult runResult) { if (!ModelOperationTaskHandler.MODEL_OPERATION_TASK_URI.equals(task.getHandlerUri())) { return; } - System.out.println("Finishing " + task + ", handler uri " + task.getHandlerUri()); + System.out.println(Thread.currentThread().getName() + ": Finishing " + task + ", handler uri " + task.getHandlerUri()); assert executing.getOid().equals(task.getOid()); executing = null; } diff --git a/model/workflow-impl/src/test/resources/policy/role-role50a-slow.xml b/model/workflow-impl/src/test/resources/policy/role-role50a-slow.xml new file mode 100644 index 00000000000..b506adcb10f --- /dev/null +++ b/model/workflow-impl/src/test/resources/policy/role-role50a-slow.xml @@ -0,0 +1,43 @@ + + + + Role50a-slow + + + + + description + + + + + + description + + + + + + diff --git a/model/workflow-impl/src/test/resources/policy/user-bob.xml b/model/workflow-impl/src/test/resources/policy/user-bob.xml new file mode 100644 index 00000000000..814b7c96950 --- /dev/null +++ b/model/workflow-impl/src/test/resources/policy/user-bob.xml @@ -0,0 +1,28 @@ + + + + bob + Bob White + Bob + White + diff --git a/model/workflow-impl/src/test/resources/policy/user-chuck.xml b/model/workflow-impl/src/test/resources/policy/user-chuck.xml new file mode 100644 index 00000000000..cce1f0c551d --- /dev/null +++ b/model/workflow-impl/src/test/resources/policy/user-chuck.xml @@ -0,0 +1,28 @@ + + + + chuck + Chuck Black + Chuck + Black + diff --git a/provisioning/provisioning-impl/pom.xml b/provisioning/provisioning-impl/pom.xml index 57501f10f12..008aa2367d6 100644 --- a/provisioning/provisioning-impl/pom.xml +++ b/provisioning/provisioning-impl/pom.xml @@ -99,7 +99,6 @@ repo-common 3.7-SNAPSHOT - commons-lang commons-lang @@ -267,5 +266,13 @@ spring-aop test + + + + net.tirasa.connid + connector-framework + test + + diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceManager.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceManager.java index c9e4dbea9b9..1186e2494b8 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceManager.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceManager.java @@ -164,11 +164,15 @@ public PrismObject getResource(String oid, GetOperationOptions opt if (GetOperationOptions.isReadOnly(options)) { repoOptions = SelectorOptions.createCollection(GetOperationOptions.createReadOnly()); } - PrismObject repositoryObject = repositoryService.getObject(ResourceType.class, oid, repoOptions, parentResult); + PrismObject repositoryObject = readResourceFromRepository(oid, repoOptions, parentResult); return loadAndCacheResource(repositoryObject, options, task, parentResult); } - + + private PrismObject readResourceFromRepository(String oid, Collection> repoOptions, OperationResult parentResult) throws ObjectNotFoundException, SchemaException { + InternalMonitor.recordCount(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT); + return repositoryService.getObject(ResourceType.class, oid, repoOptions, parentResult); + } private PrismObject loadAndCacheResource(PrismObject repositoryObject, GetOperationOptions options, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException { @@ -292,7 +296,7 @@ private PrismObject completeResource(PrismObject rep // Now we need to re-read the resource from the repository and re-aply the schemas. This ensures that we will // cache the correct version and that we avoid race conditions, etc. - newResource = repositoryService.getObject(ResourceType.class, repoResource.getOid(), null, result); + newResource = readResourceFromRepository(repoResource.getOid(), null, result); applyConnectorSchemaToResource(newResource, task, result); } catch (SchemaException e) { diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyResourceAndSchemaCaching.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyResourceAndSchemaCaching.java index 7946b5f1997..7fe051f3bc0 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyResourceAndSchemaCaching.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyResourceAndSchemaCaching.java @@ -19,8 +19,6 @@ */ package com.evolveum.midpoint.provisioning.impl.dummy; -import static com.evolveum.midpoint.test.IntegrationTestTools.display; - import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -40,6 +38,8 @@ import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.util.PrismTestUtil; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.internals.InternalCounters; import com.evolveum.midpoint.schema.internals.InternalMonitor; @@ -79,39 +79,45 @@ public class TestDummyResourceAndSchemaCaching extends AbstractDummyTest { @Test public void test010GetResource() throws Exception { final String TEST_NAME = "test010GetResource"; - TestUtil.displayTestTitle(TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN OperationResult result = new OperationResult(TestDummyResourceAndSchemaCaching.class.getName() + "." + TEST_NAME); + // Check that there is no schema before test (pre-condition) + PrismObject resourceBefore = repositoryService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null, result); + ResourceType resourceTypeBefore = resourceBefore.asObjectable(); + Element resourceXsdSchemaElementBefore = ResourceTypeUtil.getResourceXsdSchema(resourceTypeBefore); + AssertJUnit.assertNull("Found schema before test connection. Bad test setup?", resourceXsdSchemaElementBefore); + + assertVersion(resourceBefore, "0"); + // Some connector initialization and other things might happen in previous tests. // The monitor is static, not part of spring context, it will not be cleared rememberCounter(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT); + rememberCounter(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT); rememberCounter(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT); rememberCounter(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT); rememberCounter(InternalCounters.CONNECTOR_INSTANCE_INITIALIZATION_COUNT); rememberCounter(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT); rememberResourceCacheStats(); - // Check that there is no schema before test (pre-condition) - PrismObject resourceBefore = repositoryService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null, result); - ResourceType resourceTypeBefore = resourceBefore.asObjectable(); - Element resourceXsdSchemaElementBefore = ResourceTypeUtil.getResourceXsdSchema(resourceTypeBefore); - AssertJUnit.assertNull("Found schema before test connection. Bad test setup?", resourceXsdSchemaElementBefore); - - assertVersion(resourceBefore, "0"); - // WHEN + displayWhen(TEST_NAME); PrismObject resourceProvisioning = provisioningService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null, null, result); // THEN + displayThen(TEST_NAME); display("Resource", resource); - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertHasSchema(resourceProvisioning, "provisioning resource"); rememberSchemaMetadata(resourceProvisioning); + // TODO not sure why are there 2 read counts. Should be 1. But this is not that important right now. + // Some overhead on initial resource read is OK. What is important is that it does not increase during + // normal account operations. + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 2); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 1); assertCounterIncrement(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT, 1); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 1); @@ -142,7 +148,7 @@ public void test010GetResource() throws Exception { @Test public void test011GetResourceAgain() throws Exception { final String TEST_NAME = "test011GetResourceAgain"; - TestUtil.displayTestTitle(TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN OperationResult result = new OperationResult(TestDummyResourceAndSchemaCaching.class.getName() + "." + TEST_NAME); @@ -152,12 +158,12 @@ public void test011GetResourceAgain() throws Exception { // THEN display("Resource(1)", resource); - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertHasSchema(resourceProvisioning, "provisioning resource(1)"); assertSchemaMetadataUnchanged(resourceProvisioning); + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -181,8 +187,7 @@ public void test011GetResourceAgain() throws Exception { // THEN display("Resource(2)", resource); - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertHasSchema(resourceProvisioning, "provisioning resource(2)"); assertSchemaMetadataUnchanged(resourceProvisioning); @@ -212,7 +217,7 @@ public void test011GetResourceAgain() throws Exception { @Test public void test012AddAccountGetResource() throws Exception { final String TEST_NAME = "test012AddAccountGetResource"; - TestUtil.displayTestTitle(TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN OperationResult result = new OperationResult(TestDummyResourceAndSchemaCaching.class.getName() + "." + TEST_NAME); @@ -227,12 +232,12 @@ public void test012AddAccountGetResource() throws Exception { PrismObject resourceProvisioning = provisioningService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null, null, result); display("Resource(2)", resource); - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertHasSchema(resourceProvisioning, "provisioning resource(2)"); assertSchemaMetadataUnchanged(resourceProvisioning); + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -251,6 +256,73 @@ public void test012AddAccountGetResource() throws Exception { assertConnectorInstanceUnchanged(resourceProvisioning); } + + @Test + public void test013GetResourceNoFetch() throws Exception { + final String TEST_NAME = "test013GetResourceNoFetch"; + displayTestTitle(TEST_NAME); + // GIVEN + OperationResult result = new OperationResult(TestDummyResourceAndSchemaCaching.class.getName() + + "." + TEST_NAME); + + Collection> options = GetOperationOptions.createNoFetchCollection(); + + // WHEN + PrismObject resourceProvisioning = provisioningService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, options, null, result); + + // THEN + display("Resource(1)", resource); + assertSuccess(result); + + assertHasSchema(resourceProvisioning, "provisioning resource(1)"); + assertSchemaMetadataUnchanged(resourceProvisioning); + + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); + assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_INSTANCE_INITIALIZATION_COUNT, 0); + assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); + + assertResourceVersionIncrement(resourceProvisioning, 0); + + display("Resource cache (1)", InternalMonitor.getResourceCacheStats()); + assertResourceCacheHitsIncrement(1); + assertResourceCacheMissesIncrement(0); + + assertResourceSchemaUnchanged(RefinedResourceSchemaImpl.getResourceSchema(resourceProvisioning, prismContext)); + assertRefinedResourceSchemaUnchanged(RefinedResourceSchemaImpl.getRefinedSchema(resourceProvisioning)); + assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); + + assertConnectorInstanceUnchanged(resourceProvisioning); + + // WHEN + resourceProvisioning = provisioningService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, options, null, result); + + // THEN + display("Resource(2)", resource); + assertSuccess(result); + + assertHasSchema(resourceProvisioning, "provisioning resource(2)"); + assertSchemaMetadataUnchanged(resourceProvisioning); + + assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_INSTANCE_INITIALIZATION_COUNT, 0); + + assertResourceVersionIncrement(resourceProvisioning, 0); + + display("Resource cache (1)", InternalMonitor.getResourceCacheStats()); + assertResourceCacheHitsIncrement(1); + assertResourceCacheMissesIncrement(0); + + assertResourceSchemaUnchanged(RefinedResourceSchemaImpl.getResourceSchema(resourceProvisioning, prismContext)); + assertRefinedResourceSchemaUnchanged(RefinedResourceSchemaImpl.getRefinedSchema(resourceProvisioning)); + assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); + + assertConnectorInstanceUnchanged(resourceProvisioning); + } /** @@ -260,7 +332,7 @@ public void test012AddAccountGetResource() throws Exception { @Test public void test020ModifyAndGetResource() throws Exception { final String TEST_NAME = "test020ModifyAndGetResource"; - TestUtil.displayTestTitle(TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN Task task = taskManager.createTaskInstance(TestDummyResourceAndSchemaCaching.class.getName() + "." + TEST_NAME); @@ -277,8 +349,7 @@ public void test020ModifyAndGetResource() throws Exception { provisioningService.modifyObject(ResourceType.class, RESOURCE_DUMMY_OID, objectDelta.getModifications(), null, null, task, result); // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); String versionAfter = repositoryService.getVersion(ResourceType.class, RESOURCE_DUMMY_OID, result); assertResourceVersionIncrement(versionAfter, 1); @@ -288,12 +359,12 @@ public void test020ModifyAndGetResource() throws Exception { // THEN display("Resource", resource); - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertHasSchema(resourceProvisioning, "provisioning resource"); assertSchemaMetadataUnchanged(resourceProvisioning); + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 1); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -321,7 +392,7 @@ public void test020ModifyAndGetResource() throws Exception { @Test public void test022GetAccountGetResource() throws Exception { final String TEST_NAME = "test012AddAccountGetResource"; - TestUtil.displayTestTitle(TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN OperationResult result = new OperationResult(TestDummyResourceAndSchemaCaching.class.getName() + "." + TEST_NAME); @@ -342,6 +413,7 @@ public void test022GetAccountGetResource() throws Exception { assertHasSchema(resourceProvisioning, "provisioning resource(2)"); assertSchemaMetadataUnchanged(resourceProvisioning); + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -371,7 +443,7 @@ public void test022GetAccountGetResource() throws Exception { @Test public void test023ModifyRepoAndGetResource() throws Exception { final String TEST_NAME = "test023ModifyRepoAndGetResource"; - TestUtil.displayTestTitle(TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN Task task = taskManager.createTaskInstance(TestDummyResourceAndSchemaCaching.class.getName() + "." + TEST_NAME); @@ -388,8 +460,7 @@ public void test023ModifyRepoAndGetResource() throws Exception { repositoryService.modifyObject(ResourceType.class, RESOURCE_DUMMY_OID, objectDelta.getModifications(), result); // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); String versionAfter = repositoryService.getVersion(ResourceType.class, RESOURCE_DUMMY_OID, result); assertResourceVersionIncrement(versionAfter, 1); @@ -399,12 +470,12 @@ public void test023ModifyRepoAndGetResource() throws Exception { // THEN display("Resource", resource); - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertHasSchema(resourceProvisioning, "provisioning resource"); assertSchemaMetadataUnchanged(resourceProvisioning); + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 1); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -432,7 +503,7 @@ public void test023ModifyRepoAndGetResource() throws Exception { @Test public void test030ModifyConnectorConfigAndGetResource() throws Exception { final String TEST_NAME = "test030ModifyConnectorConfigAndGetResource"; - TestUtil.displayTestTitle(TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN Task task = taskManager.createTaskInstance(TestDummyResourceAndSchemaCaching.class.getName() + "." + TEST_NAME); @@ -448,8 +519,7 @@ public void test030ModifyConnectorConfigAndGetResource() throws Exception { provisioningService.modifyObject(ResourceType.class, RESOURCE_DUMMY_OID, modifications, null, null, task, result); // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertConnectorConfigChanged(); } @@ -464,7 +534,7 @@ public void test030ModifyConnectorConfigAndGetResource() throws Exception { @Test public void test031ModifyConnectorConfigRepoAndGetResource() throws Exception { final String TEST_NAME = "test031ModifyConnectorConfigRepoAndGetResource"; - TestUtil.displayTestTitle(TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN Task task = taskManager.createTaskInstance(TestDummyResourceAndSchemaCaching.class.getName() + "." + TEST_NAME); @@ -489,7 +559,7 @@ public void test031ModifyConnectorConfigRepoAndGetResource() throws Exception { @Test public void test900DeleteResource() throws Exception { final String TEST_NAME = "test900DeleteResource"; - TestUtil.displayTestTitle(TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN Task task = taskManager.createTaskInstance(TestDummyResourceAndSchemaCaching.class.getName() + "." + TEST_NAME); @@ -541,12 +611,12 @@ private void assertConnectorConfigChanged() throws ObjectNotFoundException, Sche // THEN display("Resource", resource); - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertHasSchema(resourceProvisioning, "provisioning resource"); assertSchemaMetadataUnchanged(resourceProvisioning); + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 1); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -572,6 +642,7 @@ private void assertConnectorConfigChanged() throws ObjectNotFoundException, Sche assertResourceCacheHitsIncrement(1); assertResourceCacheMissesIncrement(0); + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); diff --git a/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/ConnIdConvertor.java b/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/ConnIdConvertor.java index acd418dafe4..76ab6131b3d 100644 --- a/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/ConnIdConvertor.java +++ b/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/ConnIdConvertor.java @@ -146,25 +146,25 @@ PrismObject convertToResourceObject(ConnectorObject co break; } } - - for (Attribute icfAttr : co.getAttributes()) { + + for (Attribute connIdAttr : co.getAttributes()) { if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Reading ICF attribute {}: {}", icfAttr.getName(), icfAttr.getValue()); + LOGGER.trace("Reading ICF attribute {}: {}", connIdAttr.getName(), connIdAttr.getValue()); } - if (icfAttr.getName().equals(Uid.NAME)) { + if (connIdAttr.getName().equals(Uid.NAME)) { // UID is handled specially (see above) continue; } - if (icfAttr.is(PredefinedAttributes.AUXILIARY_OBJECT_CLASS_NAME)) { + if (connIdAttr.is(PredefinedAttributes.AUXILIARY_OBJECT_CLASS_NAME)) { // Already processed continue; } - if (icfAttr.getName().equals(OperationalAttributes.PASSWORD_NAME)) { + if (connIdAttr.getName().equals(OperationalAttributes.PASSWORD_NAME)) { // password has to go to the credentials section - ProtectedStringType password = getSingleValue(icfAttr, ProtectedStringType.class); + ProtectedStringType password = getSingleValue(connIdAttr, ProtectedStringType.class); if (password == null) { // equals() instead of == is needed. The AttributeValueCompleteness enum may be loaded by different classloader - if (!AttributeValueCompleteness.INCOMPLETE.equals(icfAttr.getAttributeValueCompleteness())) { + if (!AttributeValueCompleteness.INCOMPLETE.equals(connIdAttr.getAttributeValueCompleteness())) { continue; } // There is no password value in the ConnId attribute. But it was indicated that @@ -178,8 +178,8 @@ PrismObject convertToResourceObject(ConnectorObject co } continue; } - if (icfAttr.getName().equals(OperationalAttributes.ENABLE_NAME)) { - Boolean enabled = getSingleValue(icfAttr, Boolean.class); + if (connIdAttr.getName().equals(OperationalAttributes.ENABLE_NAME)) { + Boolean enabled = getSingleValue(connIdAttr, Boolean.class); if (enabled == null) { continue; } @@ -195,9 +195,9 @@ PrismObject convertToResourceObject(ConnectorObject co LOGGER.trace("Converted activation administrativeStatus: {}", activationStatusType); continue; } - - if (icfAttr.getName().equals(OperationalAttributes.ENABLE_DATE_NAME)) { - Long millis = getSingleValue(icfAttr, Long.class); + + if (connIdAttr.getName().equals(OperationalAttributes.ENABLE_DATE_NAME)) { + Long millis = getSingleValue(connIdAttr, Long.class); if (millis == null) { continue; } @@ -206,8 +206,8 @@ PrismObject convertToResourceObject(ConnectorObject co continue; } - if (icfAttr.getName().equals(OperationalAttributes.DISABLE_DATE_NAME)) { - Long millis = getSingleValue(icfAttr, Long.class); + if (connIdAttr.getName().equals(OperationalAttributes.DISABLE_DATE_NAME)) { + Long millis = getSingleValue(connIdAttr, Long.class); if (millis == null) { continue; } @@ -215,9 +215,9 @@ PrismObject convertToResourceObject(ConnectorObject co activationType.setValidTo(XmlTypeConverter.createXMLGregorianCalendar(millis)); continue; } - - if (icfAttr.getName().equals(OperationalAttributes.LOCK_OUT_NAME)) { - Boolean lockOut = getSingleValue(icfAttr, Boolean.class); + + if (connIdAttr.getName().equals(OperationalAttributes.LOCK_OUT_NAME)) { + Boolean lockOut = getSingleValue(connIdAttr, Boolean.class); if (lockOut == null) { continue; } @@ -233,7 +233,7 @@ PrismObject convertToResourceObject(ConnectorObject co continue; } - QName qname = icfNameMapper.convertAttributeNameToQName(icfAttr.getName(), attributesContainerDefinition); + QName qname = icfNameMapper.convertAttributeNameToQName(connIdAttr.getName(), attributesContainerDefinition); ResourceAttributeDefinition attributeDefinition = attributesContainerDefinition.findAttributeDefinition(qname, caseIgnoreAttributeNames); if (attributeDefinition == null) { @@ -245,7 +245,8 @@ PrismObject convertToResourceObject(ConnectorObject co } } if (attributeDefinition == null) { - throw new SchemaException("Unknown attribute "+qname+" in definition of object class "+attributesContainerDefinition.getTypeName()+". Original ICF name: "+icfAttr.getName(), qname); + throw new SchemaException("Unknown attribute " + qname + " in definition of object class " + attributesContainerDefinition.getTypeName() + + ". Original ConnId name: " + connIdAttr.getName() + " in resource object identified by " + co.getName(), qname); } } @@ -258,12 +259,12 @@ PrismObject convertToResourceObject(ConnectorObject co // if true, we need to convert whole connector object to the // resource object also with the null-values attributes if (full) { - if (icfAttr.getValue() != null) { + if (connIdAttr.getValue() != null) { // Convert the values. While most values do not need // conversions, some // of them may need it (e.g. GuardedString) - for (Object icfValue : icfAttr.getValue()) { - Object value = convertValueFromIcf(icfValue, qname); + for (Object connIdValue : connIdAttr.getValue()) { + Object value = convertValueFromIcf(connIdValue, qname); resourceAttribute.add(new PrismPropertyValue<>(value)); } } @@ -274,13 +275,13 @@ PrismObject convertToResourceObject(ConnectorObject co // in this case when false, we need only the attributes with the // non-null values. } else { - if (icfAttr.getValue() != null && !icfAttr.getValue().isEmpty()) { + if (connIdAttr.getValue() != null && !connIdAttr.getValue().isEmpty()) { // Convert the values. While most values do not need // conversions, some of them may need it (e.g. GuardedString) boolean empty = true; - for (Object icfValue : icfAttr.getValue()) { - if (icfValue != null) { - Object value = convertValueFromIcf(icfValue, qname); + for (Object connIdValue : connIdAttr.getValue()) { + if (connIdValue != null) { + Object value = convertValueFromIcf(connIdValue, qname); empty = false; resourceAttribute.add(new PrismPropertyValue<>(value)); } diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query2/QueryInterpreter2.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query2/QueryInterpreter2.java index c9fe432ffaa..dd9912a6444 100644 --- a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query2/QueryInterpreter2.java +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query2/QueryInterpreter2.java @@ -49,6 +49,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationWorkItemType; import org.apache.commons.lang3.StringUtils; import org.hibernate.Session; +import org.hibernate.criterion.Projections; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -338,15 +339,20 @@ private void updatePagingAndSorting(InterpretationContext context, ObjectPaging hibernateQuery.setMaxResults(paging.getMaxSize()); } - if (!paging.hasOrdering()) { - return; + if (paging.hasOrdering()) { + for (ObjectOrdering ordering : paging.getOrderingInstructions()) { + addOrdering(context, ordering); + } } - for (ObjectOrdering ordering : paging.getOrderingInstructions()) { - addOrdering(context, ordering); + if (paging.hasGrouping()) { + for (ObjectGrouping grouping : paging.getGroupingInstructions()) { + addGrouping(context, grouping); + } } } + private void addOrdering(InterpretationContext context, ObjectOrdering ordering) throws QueryException { ItemPath orderByPath = ordering.getOrderBy(); @@ -397,6 +403,45 @@ private void addOrdering(InterpretationContext context, ObjectOrdering ordering) } + private void addGrouping(InterpretationContext context, ObjectGrouping grouping) throws QueryException { + + ItemPath groupByPath = grouping.getGroupBy(); + + // TODO if we'd like to have group-by extension properties, we'd need to provide itemDefinition for them + ProperDataSearchResult result = context.getItemPathResolver().findProperDataDefinition( + context.getRootEntityDefinition(), groupByPath, null, JpaDataNodeDefinition.class, context.getPrismContext()); + if (result == null) { + LOGGER.error("Unknown path '" + groupByPath + "', couldn't find definition for it, " + + "list will not be grouped by it."); + return; + } + JpaDataNodeDefinition targetDefinition = result.getLinkDefinition().getTargetDefinition(); + if (targetDefinition instanceof JpaAnyContainerDefinition) { + throw new QueryException("Grouping based on extension item or attribute is not supported yet: " + groupByPath); + } else if (targetDefinition instanceof JpaReferenceDefinition) { + throw new QueryException("Grouping based on reference is not supported: " + groupByPath); + } else if (result.getLinkDefinition().isMultivalued()) { + throw new QueryException("Grouping based on multi-valued item is not supported: " + groupByPath); + } else if (targetDefinition instanceof JpaEntityDefinition) { + throw new QueryException("Grouping based on entity is not supported: " + groupByPath); + } else if (!(targetDefinition instanceof JpaPropertyDefinition)) { + throw new IllegalStateException("Unknown item definition type: " + result.getClass()); + } + + JpaEntityDefinition baseEntityDefinition = result.getEntityDefinition(); + JpaPropertyDefinition groupByDefinition = (JpaPropertyDefinition) targetDefinition; + String hqlPropertyPath = context.getItemPathResolver() + .resolveItemPath(groupByPath, null, context.getPrimaryEntityAlias(), baseEntityDefinition, true) + .getHqlPath(); + if (RPolyString.class.equals(groupByDefinition.getJpaClass())) { + hqlPropertyPath += ".orig"; + } + + RootHibernateQuery hibernateQuery = context.getHibernateQuery(); + + hibernateQuery.addGrouping(hqlPropertyPath); + } + public Matcher findMatcher(T value) { return findMatcher(value != null ? (Class) value.getClass() : null); } diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query2/hqm/HibernateQuery.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query2/hqm/HibernateQuery.java index cadc70e3428..5f99c8b961f 100644 --- a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query2/hqm/HibernateQuery.java +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query2/hqm/HibernateQuery.java @@ -80,6 +80,23 @@ public OrderDirection getDirection() { private List orderingList = new ArrayList<>(); + + public class Grouping { + @NotNull private final String byProperty; + + Grouping(@NotNull String byProperty) { + this.byProperty = byProperty; + } + + @NotNull + public String getByProperty() { + return byProperty; + } + } + + private List groupingList = new ArrayList<>(); + + public HibernateQuery(@NotNull JpaEntityDefinition primaryEntityDef) { primaryEntity = createItemSpecification(primaryEntityDef); } @@ -161,6 +178,22 @@ public String getAsHqlText(int indent, boolean distinct) { } } } + + if (!groupingList.isEmpty()) { + sb.append("\n"); + indent(sb, indent); + sb.append("group by "); + boolean first = true; + for (Grouping grouping : groupingList) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(grouping.byProperty); + } + } + return sb.toString(); } @@ -232,6 +265,15 @@ public List getOrderingList() { return orderingList; } + public void addGrouping(String propertyPath) { + groupingList.add(new Grouping(propertyPath)); + } + + public List getGroupingList() { + return groupingList; + } + + public abstract RootHibernateQuery getRootQuery(); // used to narrow the primary entity e.g. from RObject to RUser (e.g. during ItemValueRestriction processing) diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java index 7625b1dff50..a905b2fa6b7 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java @@ -971,6 +971,7 @@ protected void assertCacheMisses(CachingStatistics lastStats, CachingStatistics } protected void assertSteadyResources() { + assertCounterIncrement(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT, 0); assertCounterIncrement(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT, 0); assertCounterIncrement(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT, 0); @@ -979,6 +980,7 @@ protected void assertSteadyResources() { } protected void rememberSteadyResources() { + rememberCounter(InternalCounters.RESOURCE_REPOSITORY_READ_COUNT); rememberCounter(InternalCounters.RESOURCE_SCHEMA_FETCH_COUNT); rememberCounter(InternalCounters.RESOURCE_SCHEMA_PARSE_COUNT); rememberCounter(InternalCounters.CONNECTOR_CAPABILITIES_FETCH_COUNT); diff --git a/repo/task-quartz-impl/pom.xml b/repo/task-quartz-impl/pom.xml index 886d8b72360..82364003aa7 100644 --- a/repo/task-quartz-impl/pom.xml +++ b/repo/task-quartz-impl/pom.xml @@ -105,6 +105,10 @@ org.jetbrains annotations-java5 + + org.hibernate + hibernate-core + com.evolveum.midpoint.repo diff --git a/samples/book/5/resource-csv-hr.xml b/samples/book/5/resource-csv-hr.xml index f8100eda51a..13bd1004c1d 100644 --- a/samples/book/5/resource-csv-hr.xml +++ b/samples/book/5/resource-csv-hr.xml @@ -16,7 +16,7 @@ --> - - HR System + HR System HR resource using CSV connector. This is the HR feed (source) resource. @@ -35,29 +35,27 @@ book, chapter 5. - - - - c:connectorType - com.evolveum.polygon.connector.csv.CsvConnector - - - - - - - - - /var/opt/midpoint-book/resources/hr.csv - utf-8 - , - ; - empno - password - - - + + + + c:connectorType + com.evolveum.polygon.connector.csv.CsvConnector + + + + + + + + /var/opt/midpoint-book/resources/hr.csv + utf-8 + , + ; + empno + password + + @@ -168,5 +166,5 @@ - + diff --git a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestTrafo.java b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestTrafo.java index 75a36f08e88..bf6c1de65bd 100644 --- a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestTrafo.java +++ b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestTrafo.java @@ -680,7 +680,7 @@ public void test150JackAssignRoleEmployee() throws Exception { dummyAuditService.assertExecutionDeltas(0,3); dummyAuditService.assertHasDelta(0,ChangeType.MODIFY, UserType.class); // primary, link (2 deltas) dummyAuditService.assertHasDelta(0,ChangeType.ADD, ShadowType.class); // AD account - dummyAuditService.assertExecutionDeltas(1,2); + dummyAuditService.assertExecutionDeltas(1,3); dummyAuditService.assertHasDelta(1,ChangeType.MODIFY, UserType.class); // link dummyAuditService.assertHasDelta(1,ChangeType.ADD, ShadowType.class); // Mail account dummyAuditService.assertExecutionDeltas(2,2); @@ -797,7 +797,7 @@ public void test160AngelicaAdd() throws Exception { dummyAuditService.assertHasDelta(0, ChangeType.ADD, UserType.class); // primary dummyAuditService.assertHasDelta(0,ChangeType.MODIFY, UserType.class); // link dummyAuditService.assertHasDelta(0,ChangeType.ADD, ShadowType.class); // AD account - dummyAuditService.assertExecutionDeltas(1, 2); + dummyAuditService.assertExecutionDeltas(1, 3); dummyAuditService.assertHasDelta(1, ChangeType.MODIFY, UserType.class); // link dummyAuditService.assertHasDelta(1,ChangeType.ADD, ShadowType.class); // Mail account dummyAuditService.assertExecutionDeltas(2, 2); diff --git a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestUnix.java b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestUnix.java index 8bbff25d710..f6c9276d6b2 100644 --- a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestUnix.java +++ b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestUnix.java @@ -804,7 +804,7 @@ protected void assertTest132Audit() { display("Audit", dummyAuditService); dummyAuditService.assertSimpleRecordSanity(); dummyAuditService.assertRecords(2); - dummyAuditService.assertExecutionDeltas(1); + dummyAuditService.assertExecutionDeltas(2); dummyAuditService.assertHasDelta(ChangeType.MODIFY, ShadowType.class); } diff --git a/tools/repo-ninja/pom.xml b/tools/repo-ninja/pom.xml index 9403ada1b96..8ceb8a6e07a 100644 --- a/tools/repo-ninja/pom.xml +++ b/tools/repo-ninja/pom.xml @@ -157,7 +157,7 @@ copy-dependencies - package + prepare-package copy-dependencies