diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCachePanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCachePanel.html index fb2b88afa7c..c166236d159 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCachePanel.html +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCachePanel.html @@ -17,6 +17,7 @@

+
diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCachePanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCachePanel.java index f22563fd55d..9d65637db61 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCachePanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCachePanel.java @@ -10,6 +10,8 @@ import com.evolveum.midpoint.util.Holder; import com.evolveum.midpoint.util.KeyValueTreeNode; import com.evolveum.midpoint.util.TreeNode; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.web.component.AceEditor; import com.evolveum.midpoint.web.component.AjaxButton; import com.evolveum.midpoint.web.security.MidPointApplication; @@ -24,11 +26,16 @@ import java.util.ArrayList; import java.util.List; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + public class InternalsCachePanel extends BasePanel{ private static final long serialVersionUID = 1L; + private static final Trace LOGGER = TraceManager.getTrace(InternalsCachePanel.class); + private static final String ID_CLEAR_CACHES_BUTTON = "clearCaches"; + private static final String ID_DUMP_CONTENT_BUTTON = "dumpContent"; private static final String ID_INFORMATION = "information"; public InternalsCachePanel(String id) { @@ -58,7 +65,7 @@ public void setObject(String object) { informationText.setMode(null); add(informationText); - AjaxButton clearCaches = new AjaxButton(ID_CLEAR_CACHES_BUTTON, createStringResource("InternalsCachePanel.button.clearCaches")) { + add(new AjaxButton(ID_CLEAR_CACHES_BUTTON, createStringResource("InternalsCachePanel.button.clearCaches")) { private static final long serialVersionUID = 1L; @@ -67,54 +74,89 @@ public void onClick(AjaxRequestTarget target) { getPageBase().getCacheDispatcher().dispatchInvalidation(null, null, true, null); target.add(InternalsCachePanel.this); } - }; + }); + + add(new AjaxButton(ID_DUMP_CONTENT_BUTTON, createStringResource("InternalsCachePanel.button.dumpContent")) { + + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + String cacheInformation = getCacheInformation(); + LOGGER.info("Dumping the content of the caches.\nCurrent counters:\n{}\n", cacheInformation); + MidPointApplication.get().getCacheRegistry().dumpContent(); - add(clearCaches); + getSession().success(getPageBase().getString("InternalsCachePanel.result.dumped")); + target.add(getPageBase()); + } + }); + } + + private static class SizeInformation { + private final int size; + private final int secondarySize; + + private SizeInformation(SingleCacheStateInformationType info) { + size = defaultIfNull(info.getSize(), 0); + secondarySize = defaultIfNull(info.getSecondarySize(), 0); + } + + private SizeInformation(ComponentSizeInformationType info) { + size = defaultIfNull(info.getSize(), 0); + secondarySize = defaultIfNull(info.getSecondarySize(), 0); + } } private String getCacheInformation() { StringBuilder sb = new StringBuilder(); - MidPointApplication midPointApplication = MidPointApplication.get(); - if (midPointApplication != null) { - CachesStateInformationType state = midPointApplication.getCacheRegistry().getStateInformation(); - List> trees = new ArrayList<>(); - for (SingleCacheStateInformationType entry : state.getEntry()) { - KeyValueTreeNode root = new KeyValueTreeNode<>(entry.getName(), entry.getSize()); - trees.add(root); - addComponents(root, entry.getComponent()); - } - Holder maxLabelLength = new Holder<>(0); - Holder maxSize = new Holder<>(0); - trees.forEach(tree -> tree.acceptDepthFirst(node -> { - int labelLength = node.getUserObject().getKey().length() + node.getDepth() * 2; - Integer size = node.getUserObject().getValue(); - if (labelLength > maxLabelLength.getValue()) { - maxLabelLength.setValue(labelLength); - } - if (size != null && size > maxSize.getValue()) { - maxSize.setValue(size); - } - })); - int labelSize = Math.max(maxLabelLength.getValue() + 1, 30); - int sizeSize = Math.max((int) Math.log10(maxSize.getValue()) + 2, 7); - int firstPart = labelSize + 3; - int secondPart = sizeSize + 3; - String headerFormatString = " %-" + labelSize + "s | %" + sizeSize + "s \n"; - String valueFormatString = " %-" + labelSize + "s | %" + sizeSize + "d \n"; - //System.out.println("valueFormatString = " + valueFormatString); - sb.append("\n"); - sb.append(String.format(headerFormatString, "Cache", "Size")); - sb.append(StringUtils.repeat("=", firstPart)).append("+").append(StringUtils.repeat("=", secondPart)).append("\n"); - trees.forEach(tree -> { - tree.acceptDepthFirst(node -> printNode(sb, valueFormatString, node)); - sb.append(StringUtils.repeat("-", firstPart)).append("+").append(StringUtils.repeat("-", secondPart)).append("\n"); - }); + CachesStateInformationType state = MidPointApplication.get().getCacheRegistry().getStateInformation(); + List> trees = new ArrayList<>(); + for (SingleCacheStateInformationType entry : state.getEntry()) { + KeyValueTreeNode root = new KeyValueTreeNode<>(entry.getName(), new SizeInformation(entry)); + trees.add(root); + addComponents(root, entry.getComponent()); } + Holder maxLabelLength = new Holder<>(0); + Holder maxSize = new Holder<>(1); // to avoid issues with log10 + Holder maxSecondarySize = new Holder<>(1); // to avoid issues with log10 + trees.forEach(tree -> tree.acceptDepthFirst(node -> { + int labelLength = node.getUserObject().getKey().length() + node.getDepth() * 2; + int size = node.getUserObject().getValue().size; + int secondarySize = node.getUserObject().getValue().secondarySize; + if (labelLength > maxLabelLength.getValue()) { + maxLabelLength.setValue(labelLength); + } + if (size > maxSize.getValue()) { + maxSize.setValue(size); + } + if (secondarySize > maxSecondarySize.getValue()) { + maxSecondarySize.setValue(secondarySize); + } + })); + int labelSize = Math.max(maxLabelLength.getValue() + 1, 30); + int sizeSize = Math.max((int) Math.log10(maxSize.getValue()) + 2, 7); + int secondarySizeSize = Math.max((int) Math.log10(maxSecondarySize.getValue()) + 2, 8); + int firstPart = labelSize + 3; + int secondPart = sizeSize + 2; + int thirdPart = secondarySizeSize + 3; + String headerFormatString = " %-" + labelSize + "s | %" + sizeSize + "s | %" + secondarySizeSize + "s\n"; + String valueFormatString = " %-" + labelSize + "s | %" + sizeSize + "d | %" + secondarySizeSize + "s\n"; + sb.append("\n"); + sb.append(String.format(headerFormatString, "Cache", "Size", "Sec. size")); + sb.append(StringUtils.repeat("=", firstPart)).append("+") + .append(StringUtils.repeat("=", secondPart)).append("+") + .append(StringUtils.repeat("=", thirdPart)).append("\n"); + trees.forEach(tree -> { + tree.acceptDepthFirst(node -> printNode(sb, valueFormatString, node)); + sb.append(StringUtils.repeat("-", firstPart)).append("+") + .append(StringUtils.repeat("-", secondPart)).append("+") + .append(StringUtils.repeat("-", thirdPart)).append("\n"); + }); return sb.toString(); } - private void printNode(StringBuilder sb, String formatString, TreeNode> node) { - Pair pair = node.getUserObject(); + private void printNode(StringBuilder sb, String formatString, TreeNode> node) { + Pair pair = node.getUserObject(); if (pair != null) { int depth = node.getDepth(); StringBuilder prefix = new StringBuilder(); @@ -124,13 +166,22 @@ private void printNode(StringBuilder sb, String formatString, TreeNode node, List components) { + private void addComponents(KeyValueTreeNode node, List components) { for (ComponentSizeInformationType component : components) { - KeyValueTreeNode child = node.createChild(component.getName(), component.getSize()); + KeyValueTreeNode child = node.createChild(component.getName(), new SizeInformation(component)); addComponents(child, component.getComponent()); } } diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/match/MatchingRule.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/match/MatchingRule.java index a5279fa0bde..eb8adecddbd 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/match/MatchingRule.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/match/MatchingRule.java @@ -29,7 +29,7 @@ public interface MatchingRule { /** * Returns true if the rule can be applied to the specified XSD type. */ - boolean isSupported(QName xsdType); + boolean supports(QName xsdType); /** * Matches two objects. diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/DefaultMatchingRule.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/DefaultMatchingRule.java index f4d6fed21cb..c9f161c8fc2 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/DefaultMatchingRule.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/DefaultMatchingRule.java @@ -34,7 +34,7 @@ public QName getName() { * @see com.evolveum.midpoint.model.match.MatchingRule#isSupported(java.lang.Class, javax.xml.namespace.QName) */ @Override - public boolean isSupported(QName xsdType) { + public boolean supports(QName xsdType) { // We support everything. We are the default. return true; } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/DistinguishedNameMatchingRule.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/DistinguishedNameMatchingRule.java index 04bce44cb0e..695ac646394 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/DistinguishedNameMatchingRule.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/DistinguishedNameMatchingRule.java @@ -34,7 +34,7 @@ public QName getName() { } @Override - public boolean isSupported(QName xsdType) { + public boolean supports(QName xsdType) { return (DOMUtil.XSD_STRING.equals(xsdType)); } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/ExchangeEmailAddressesMatchingRule.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/ExchangeEmailAddressesMatchingRule.java index f4dc479a76a..ef56cc019f4 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/ExchangeEmailAddressesMatchingRule.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/ExchangeEmailAddressesMatchingRule.java @@ -29,7 +29,7 @@ public QName getName() { } @Override - public boolean isSupported(QName xsdType) { + public boolean supports(QName xsdType) { return (DOMUtil.XSD_STRING.equals(xsdType)); } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/MatchingRuleRegistryImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/MatchingRuleRegistryImpl.java index 9086ca12fc8..af2fa677d8a 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/MatchingRuleRegistryImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/MatchingRuleRegistryImpl.java @@ -23,7 +23,7 @@ public class MatchingRuleRegistryImpl implements MatchingRuleRegistry { @NotNull private final MatchingRule defaultMatchingRule; - @NotNull private final Map> matchingRules = new HashMap<>(); + @NotNull private final Map> matchingRules = new HashMap<>(); MatchingRuleRegistryImpl() { this.defaultMatchingRule = new DefaultMatchingRule<>(); @@ -34,27 +34,39 @@ public class MatchingRuleRegistryImpl implements MatchingRuleRegistry { @NotNull public MatchingRule getMatchingRule(QName ruleName, QName typeQName) throws SchemaException { if (ruleName == null) { + //noinspection unchecked return (MatchingRule) defaultMatchingRule; } - MatchingRule matchingRule = (MatchingRule) matchingRules.get(ruleName); - if (matchingRule == null) { - //try match according to the localPart - if (QNameUtil.matchAny(ruleName, matchingRules.keySet())){ - ruleName = QNameUtil.resolveNs(ruleName, matchingRules.keySet()); - matchingRule = (MatchingRule) matchingRules.get(ruleName); - } - if (matchingRule == null) { - throw new SchemaException("Unknown matching rule for name " + ruleName); - } - } - if (typeQName != null && !matchingRule.isSupported(typeQName)) { - throw new SchemaException("Matching rule "+ruleName+" does not support type "+typeQName); + MatchingRule matchingRule = findMatchingRule(ruleName); + if (typeQName == null || matchingRule.supports(typeQName)) { + return matchingRule; + } else { + throw new SchemaException("Matching rule " + ruleName + " does not support type " + typeQName); } - return matchingRule; } - void registerMatchingRule(MatchingRule rule) { - ((Map)this.matchingRules).put(rule.getName(), rule); + @NotNull + private MatchingRule findMatchingRule(QName ruleName) throws SchemaException { + //noinspection unchecked + MatchingRule rule = (MatchingRule) matchingRules.get(ruleName); + if (rule != null) { + return rule; + } + + // try match according to the localPart + QName qualifiedRuleName = QNameUtil.resolveNs(ruleName, matchingRules.keySet()); + if (qualifiedRuleName != null) { + //noinspection unchecked + MatchingRule ruleAfterQualification = (MatchingRule) matchingRules.get(qualifiedRuleName); + if (ruleAfterQualification != null) { + return ruleAfterQualification; + } + } + + throw new SchemaException("Couldn't find matching rule named '" + ruleName + "'"); } + void registerMatchingRule(MatchingRule rule) { + matchingRules.put(rule.getName(), rule); + } } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/PolyStringNormMatchingRule.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/PolyStringNormMatchingRule.java index dd34480f81e..db5b6f3b431 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/PolyStringNormMatchingRule.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/PolyStringNormMatchingRule.java @@ -34,7 +34,7 @@ public QName getName() { * @see com.evolveum.midpoint.prism.match.MatchingRule#isSupported(java.lang.Class, javax.xml.namespace.QName) */ @Override - public boolean isSupported(QName xsdType) { + public boolean supports(QName xsdType) { return (PolyStringType.COMPLEX_TYPE.equals(xsdType)); } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/PolyStringOrigMatchingRule.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/PolyStringOrigMatchingRule.java index e28d387b903..384c105f865 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/PolyStringOrigMatchingRule.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/PolyStringOrigMatchingRule.java @@ -34,7 +34,7 @@ public QName getName() { * @see com.evolveum.midpoint.prism.match.MatchingRule#isSupported(java.lang.Class, javax.xml.namespace.QName) */ @Override - public boolean isSupported(QName xsdType) { + public boolean supports(QName xsdType) { return (PolyStringType.COMPLEX_TYPE.equals(xsdType)); } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/PolyStringStrictMatchingRule.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/PolyStringStrictMatchingRule.java index 767fc0f5def..07876a39936 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/PolyStringStrictMatchingRule.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/PolyStringStrictMatchingRule.java @@ -13,7 +13,6 @@ import com.evolveum.midpoint.prism.PrismConstants; import com.evolveum.midpoint.prism.match.MatchingRule; import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; /** @@ -34,7 +33,7 @@ public QName getName() { * @see com.evolveum.midpoint.prism.match.MatchingRule#isSupported(java.lang.Class, javax.xml.namespace.QName) */ @Override - public boolean isSupported(QName xsdType) { + public boolean supports(QName xsdType) { return (PolyStringType.COMPLEX_TYPE.equals(xsdType)); } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/StringIgnoreCaseMatchingRule.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/StringIgnoreCaseMatchingRule.java index a10dceffc5d..db8765ba503 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/StringIgnoreCaseMatchingRule.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/StringIgnoreCaseMatchingRule.java @@ -31,7 +31,7 @@ public QName getName() { } @Override - public boolean isSupported(QName xsdType) { + public boolean supports(QName xsdType) { return (DOMUtil.XSD_STRING.equals(xsdType)); } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/UuidMatchingRule.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/UuidMatchingRule.java index 05a6d9ede61..65a12fbd372 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/UuidMatchingRule.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/UuidMatchingRule.java @@ -33,7 +33,7 @@ public QName getName() { } @Override - public boolean isSupported(QName xsdType) { + public boolean supports(QName xsdType) { return (DOMUtil.XSD_STRING.equals(xsdType)); } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/XmlMatchingRule.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/XmlMatchingRule.java index 1ced4e948ca..a5449d2949b 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/XmlMatchingRule.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/match/XmlMatchingRule.java @@ -37,7 +37,7 @@ public QName getName() { } @Override - public boolean isSupported(QName xsdType) { + public boolean supports(QName xsdType) { return (DOMUtil.XSD_STRING.equals(xsdType)); } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/expression/ExpressionProfiles.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/expression/ExpressionProfiles.java index d3e24eabc6e..8e348f56b71 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/expression/ExpressionProfiles.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/expression/ExpressionProfiles.java @@ -6,8 +6,9 @@ */ package com.evolveum.midpoint.schema.expression; -import java.util.HashMap; +import java.util.Collections; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * @author semancik @@ -15,7 +16,7 @@ */ public class ExpressionProfiles { - private final Map profiles = new HashMap<>(); + private final Map profiles = new ConcurrentHashMap<>(); public ExpressionProfile getProfile(String identifier) { return profiles.get(identifier); @@ -28,4 +29,8 @@ public void add(ExpressionProfile profile) { public int size() { return profiles.size(); } + + public Map getProfiles() { + return Collections.unmodifiableMap(profiles); + } } diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd index 3d7be668652..32ecd3a5135 100755 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd @@ -26487,6 +26487,14 @@ + + + + Current "secondary size" of the cache, if applicable. This can mean e.g. number of cached objects + for query caches. + + + @@ -26531,6 +26539,13 @@ + + + + Component secondary size (if applicable). + + + diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/QNameUtil.java b/infra/util/src/main/java/com/evolveum/midpoint/util/QNameUtil.java index 702293bfbef..d44f0f000eb 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/QNameUtil.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/QNameUtil.java @@ -259,15 +259,14 @@ public static boolean matchWithUri(QName qname, String uri) { return match(qname, uriToQName(uri, true)); } - - public static QName resolveNs(QName a, Collection col){ + public static QName resolveNs(QName a, Collection col) { if (col == null) { return null; } QName found = null; for (QName b: col) { if (match(a, b)) { - if (found != null){ + if (found != null) { throw new IllegalStateException("Found more than one suitable qnames( "+ found + b + ") for attribute: " + a); } found = b; diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/caching/AbstractThreadLocalCache.java b/infra/util/src/main/java/com/evolveum/midpoint/util/caching/AbstractThreadLocalCache.java index 7d1993fa690..bb5f3e258cd 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/caching/AbstractThreadLocalCache.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/caching/AbstractThreadLocalCache.java @@ -168,4 +168,10 @@ public static int getTotalSize(ConcurrentHa } protected abstract int getSize(); + + public static void dumpContent(ConcurrentHashMap cacheInstances) { + cacheInstances.forEach((thread, cache) -> cache.dumpContent(thread.getName())); + } + + protected abstract void dumpContent(String threadName); } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/SystemObjectCache.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/SystemObjectCache.java index 960a24cfaf5..915bddd1b49 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/SystemObjectCache.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/SystemObjectCache.java @@ -10,6 +10,7 @@ import com.evolveum.midpoint.model.common.expression.ExpressionProfileCompiler; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.repo.cache.CacheRegistry; import com.evolveum.midpoint.repo.api.Cacheable; @@ -56,6 +57,7 @@ public class SystemObjectCache implements Cacheable { private static final Trace LOGGER = TraceManager.getTrace(SystemObjectCache.class); + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(SystemObjectCache.class.getName() + ".content"); @Autowired @Qualifier("cacheRepositoryService") @@ -69,7 +71,7 @@ public class SystemObjectCache implements Cacheable { private PrismObject securityPolicy; private Long securityPolicyCheckTimestamp; - private ExpressionProfiles expressionProfiles; + private volatile ExpressionProfiles expressionProfiles; @PostConstruct public void register() { @@ -217,6 +219,7 @@ public synchronized void invalidateCaches() { securityPolicyCheckTimestamp = null; } + @SuppressWarnings("WeakerAccess") public PrismObject getArchetype(String oid, OperationResult result) throws ObjectNotFoundException, SchemaException { // TODO: make this efficient (use cache) return cacheRepositoryService.getObject(ArchetypeType.class, oid, null, result); @@ -262,6 +265,7 @@ public void invalidate(Class type, String oid, CacheInvalidationContext conte if (type == null || type.isAssignableFrom(SystemConfigurationType.class)) { invalidateCaches(); } + expressionProfiles = null; } @NotNull @@ -274,7 +278,28 @@ public Collection getStateInformation() { } private int getSize() { + ExpressionProfiles cachedProfiles = this.expressionProfiles; + return (systemConfiguration != null ? 1 : 0) + - (expressionProfiles != null ? expressionProfiles.size() : 0); + (cachedProfiles != null ? cachedProfiles.size() : 0); + } + + @Override + public void dumpContent() { + if (LOGGER_CONTENT.isInfoEnabled()) { + PrismObject cachedConfiguration = this.systemConfiguration; + if (cachedConfiguration != null) { + LOGGER_CONTENT.info("Cached system configuration: {} (version {}); systemConfigurationCheckTimestamp: {}", + cachedConfiguration, cachedConfiguration.getVersion(), + XmlTypeConverter.createXMLGregorianCalendar(systemConfigurationCheckTimestamp)); + } else { + LOGGER_CONTENT.info("No cached system configuration"); + } + + ExpressionProfiles cachedProfiles = this.expressionProfiles; + if (cachedProfiles != null) { + cachedProfiles.getProfiles().forEach((k, v) -> LOGGER_CONTENT.info("Cached expression profile: {}: {}", k, v)); + } + } } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/caching/AbstractSearchExpressionEvaluatorCache.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/caching/AbstractSearchExpressionEvaluatorCache.java index eb5dea20b36..0fe5ac09c99 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/caching/AbstractSearchExpressionEvaluatorCache.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/caching/AbstractSearchExpressionEvaluatorCache.java @@ -41,6 +41,7 @@ public abstract class AbstractSearchExpressionEvaluatorCache LOGGER.info("Cached search expression evaluation [{}] {}: {}", threadName, qk, qr)); + } + } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/functions/FunctionLibrary.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/functions/FunctionLibrary.java index 4c3252d99f7..96780701a9e 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/functions/FunctionLibrary.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/functions/FunctionLibrary.java @@ -40,5 +40,12 @@ public void setGenericFunctions(Object genericFunctions) { this.genericFunctions = genericFunctions; } - + @Override + public String toString() { + return "FunctionLibrary{" + + "variableName='" + variableName + '\'' + + ", namespace='" + namespace + '\'' + + ", genericFunctions=" + genericFunctions + + '}'; + } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionEvaluatorFactory.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionEvaluatorFactory.java index b64e1b4934b..c84d0870164 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionEvaluatorFactory.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionEvaluatorFactory.java @@ -82,7 +82,8 @@ public ExpressionEvaluator } ScriptExpressionEvaluatorType scriptType = (ScriptExpressionEvaluatorType) evaluatorElementObject; - ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression(scriptType, outputDefinition, expressionProfile, factory, contextDescription, task, result); + ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression(scriptType, outputDefinition, + expressionProfile, factory, contextDescription, result); return new ScriptExpressionEvaluator(ELEMENT_NAME, scriptType, outputDefinition, protector, prismContext, scriptExpression, securityContextManager, localizationService); diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionFactory.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionFactory.java index 286e43881bd..24aa9e55b97 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionFactory.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionFactory.java @@ -7,6 +7,8 @@ package com.evolveum.midpoint.model.common.expression.script; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -17,7 +19,6 @@ import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary; import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.repo.api.Cacheable; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.repo.cache.CacheRegistry; @@ -34,7 +35,6 @@ import com.evolveum.midpoint.schema.expression.ScriptExpressionProfile; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.Trace; @@ -49,17 +49,18 @@ public class ScriptExpressionFactory implements Cacheable { private static final Trace LOGGER = TraceManager.getTrace(ScriptExpressionFactory.class); + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(ScriptExpressionFactory.class.getName() + ".content"); - public static final String DEFAULT_LANGUAGE = "http://midpoint.evolveum.com/xml/ns/public/expression/language#Groovy"; + private static final String DEFAULT_LANGUAGE = "http://midpoint.evolveum.com/xml/ns/public/expression/language#Groovy"; - private Map evaluatorMap = new HashMap<>(); + private final Map evaluatorMap = new HashMap<>(); private ObjectResolver objectResolver; private final PrismContext prismContext; private Collection functions; - private final Protector protector; private final RepositoryService repositoryService; // might be null during low-level testing - private Map customFunctionLibraryCache; + @NotNull private final Map customFunctionLibraryCache = new ConcurrentHashMap<>(); + private final AtomicBoolean initialized = new AtomicBoolean(false); private CacheRegistry cacheRegistry; @@ -73,9 +74,8 @@ public void unregister() { cacheRegistry.unregisterCacheableService(this); } - public ScriptExpressionFactory(PrismContext prismContext, Protector protector, RepositoryService repositoryService) { + public ScriptExpressionFactory(PrismContext prismContext, RepositoryService repositoryService) { this.prismContext = prismContext; - this.protector = protector; this.repositoryService = repositoryService; } @@ -105,10 +105,6 @@ public Map getEvaluators() { return evaluatorMap; } - public CacheRegistry geCacheRegistry() { - return cacheRegistry; - } - public void setCacheRegistry(CacheRegistry registry) { this.cacheRegistry = registry; } @@ -116,10 +112,10 @@ public void setCacheRegistry(CacheRegistry registry) { public ScriptExpression createScriptExpression( ScriptExpressionEvaluatorType expressionType, ItemDefinition outputDefinition, ExpressionProfile expressionProfile, ExpressionFactory expressionFactory, - String shortDesc, Task task, OperationResult result) + String shortDesc, OperationResult result) throws ExpressionSyntaxException, SecurityViolationException { - initializeCustomFunctionsLibraryCache(expressionFactory, result); + initializeCustomFunctionsLibraryCacheIfNeeded(expressionFactory, result); //cache cleanup method String language = getLanguage(expressionType); @@ -167,39 +163,41 @@ private ScriptExpressionProfile processScriptExpressionProfile(ExpressionProfile return scriptProfile; } - // if performance becomes an issue, replace 'synchronized' with something more elaborate - private synchronized void initializeCustomFunctionsLibraryCache(ExpressionFactory expressionFactory, - OperationResult result) throws ExpressionSyntaxException { - if (customFunctionLibraryCache != null) { - return; + private void initializeCustomFunctionsLibraryCacheIfNeeded(ExpressionFactory expressionFactory, OperationResult result) + throws ExpressionSyntaxException { + if (initialized.compareAndSet(false, true)) { + initializeCustomFunctionsLibraryCache(expressionFactory, result); } - customFunctionLibraryCache = new HashMap<>(); - if (repositoryService == null) { + } + + private void initializeCustomFunctionsLibraryCache(ExpressionFactory expressionFactory, OperationResult result) + throws ExpressionSyntaxException { + if (repositoryService != null) { + OperationResult subResult = result + .createMinorSubresult(ScriptExpressionFactory.class.getName() + ".searchCustomFunctions"); + ResultHandler functionLibraryHandler = (object, parentResult) -> { + // TODO: determine profile from function library archetype + ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); + FunctionLibrary customLibrary = new FunctionLibrary(); + customLibrary.setVariableName(object.getName().getOrig()); + customLibrary.setGenericFunctions( + new CustomFunctions(object.asObjectable(), expressionFactory, expressionProfile)); + customLibrary.setNamespace(MidPointConstants.NS_FUNC_CUSTOM); + customFunctionLibraryCache.put(object.getName().getOrig(), customLibrary); + return true; + }; + try { + repositoryService.searchObjectsIterative(FunctionLibraryType.class, null, functionLibraryHandler, + SelectorOptions.createCollection(GetOperationOptions.createReadOnly()), true, subResult); + subResult.recordSuccessIfUnknown(); + } catch (SchemaException | RuntimeException e) { + subResult.recordFatalError("Failed to initialize custom functions", e); + throw new ExpressionSyntaxException( + "An error occurred during custom libraries initialization. " + e.getMessage(), e); + } + } else { LOGGER.warn("No repository service set for ScriptExpressionFactory; custom functions will not be loaded. This" + " can occur during low-level testing; never during standard system execution."); - return; - } - OperationResult subResult = result - .createMinorSubresult(ScriptExpressionFactory.class.getName() + ".searchCustomFunctions"); - ResultHandler functionLibraryHandler = (object, parentResult) -> { - // TODO: determine profile from function library archetype - ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); - FunctionLibrary customLibrary = new FunctionLibrary(); - customLibrary.setVariableName(object.getName().getOrig()); - customLibrary.setGenericFunctions( - new CustomFunctions(object.asObjectable(), expressionFactory, expressionProfile)); - customLibrary.setNamespace(MidPointConstants.NS_FUNC_CUSTOM); - customFunctionLibraryCache.put(object.getName().getOrig(), customLibrary); - return true; - }; - try { - repositoryService.searchObjectsIterative(FunctionLibraryType.class, null, functionLibraryHandler, - SelectorOptions.createCollection(GetOperationOptions.createReadOnly()), true, subResult); - subResult.recordSuccessIfUnknown(); - } catch (SchemaException | RuntimeException e) { - subResult.recordFatalError("Failed to initialize custom functions", e); - throw new ExpressionSyntaxException( - "An error occurred during custom libraries initialization. " + e.getMessage(), e); } } @@ -229,7 +227,7 @@ private String getLanguage(ScriptExpressionEvaluatorType expressionType) { public void invalidate(Class type, String oid, CacheInvalidationContext context) { if (type == null || type.isAssignableFrom(FunctionLibraryType.class)) { // Currently we don't try to select entries to be cleared based on OID - customFunctionLibraryCache = null; + customFunctionLibraryCache.clear(); } } @@ -238,7 +236,17 @@ public void invalidate(Class type, String oid, CacheInvalidationContext conte public Collection getStateInformation() { return Collections.singleton(new SingleCacheStateInformationType(prismContext) .name(ScriptExpressionFactory.class.getName()) - .size(customFunctionLibraryCache != null ? customFunctionLibraryCache.size() : 0)); + .size(customFunctionLibraryCache.size())); } -} + @Override + public void dumpContent() { + if (LOGGER_CONTENT.isInfoEnabled()) { + if (initialized.get()) { + customFunctionLibraryCache.forEach((k, v) -> LOGGER_CONTENT.info("Cached function library: {}: {}", k, v)); + } else { + LOGGER_CONTENT.info("Custom function library cache is not yet initialized"); + } + } + } +} diff --git a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/ExpressionTestUtil.java b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/ExpressionTestUtil.java index aa033fa0a49..f7f98a2e6b0 100644 --- a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/ExpressionTestUtil.java +++ b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/ExpressionTestUtil.java @@ -90,7 +90,7 @@ public static ExpressionFactory createInitializedExpressionFactory(ObjectResolve Collection functions = new ArrayList<>(); functions.add(FunctionLibraryUtil.createBasicFunctionLibrary(prismContext, protector, clock)); functions.add(FunctionLibraryUtil.createLogFunctionLibrary(prismContext)); - ScriptExpressionFactory scriptExpressionFactory = new ScriptExpressionFactory(prismContext, protector, repositoryService); + ScriptExpressionFactory scriptExpressionFactory = new ScriptExpressionFactory(prismContext, repositoryService); scriptExpressionFactory.setObjectResolver(resolver); scriptExpressionFactory.setFunctions(functions); diff --git a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/AbstractScriptTest.java b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/AbstractScriptTest.java index 24e96436ae4..fad5b31d03f 100644 --- a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/AbstractScriptTest.java +++ b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/AbstractScriptTest.java @@ -85,7 +85,7 @@ public void setupFactory() { Clock clock = new Clock(); Collection functions = new ArrayList<>(); functions.add(FunctionLibraryUtil.createBasicFunctionLibrary(prismContext, protector, clock)); - scriptExpressionfactory = new ScriptExpressionFactory(prismContext, protector, null); + scriptExpressionfactory = new ScriptExpressionFactory(prismContext, null); scriptExpressionfactory.setObjectResolver(resolver); scriptExpressionfactory.setFunctions(functions); localizationService = LocalizationTestUtil.getLocalizationService(); diff --git a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/TestScriptCaching.java b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/TestScriptCaching.java index 21fe56a9e2b..719547c0c43 100644 --- a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/TestScriptCaching.java +++ b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/TestScriptCaching.java @@ -80,7 +80,7 @@ public void setupFactory() { Clock clock = new Clock(); Collection functions = new ArrayList<>(); functions.add(FunctionLibraryUtil.createBasicFunctionLibrary(prismContext, protector, clock)); - scriptExpressionfactory = new ScriptExpressionFactory(prismContext, protector, null); + scriptExpressionfactory = new ScriptExpressionFactory(prismContext, null); scriptExpressionfactory.setObjectResolver(resolver); scriptExpressionfactory.setFunctions(functions); evaluator = new Jsr223ScriptEvaluator("groovy", prismContext, protector, LocalizationTestUtil.getLocalizationService()); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/triggerSetter/TriggerCreatorGlobalState.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/triggerSetter/TriggerCreatorGlobalState.java index 7445cdab95e..1b0cae48c40 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/triggerSetter/TriggerCreatorGlobalState.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/triggerSetter/TriggerCreatorGlobalState.java @@ -8,6 +8,7 @@ package com.evolveum.midpoint.model.impl.expr.triggerSetter; import com.evolveum.midpoint.CacheInvalidationContext; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionFactory; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.repo.api.Cacheable; import com.evolveum.midpoint.repo.api.DeleteObjectResult; @@ -36,6 +37,7 @@ public class TriggerCreatorGlobalState implements Cacheable { private static final Trace LOGGER = TraceManager.getTrace(TriggerCreatorGlobalState.class); + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(TriggerCreatorGlobalState.class.getName() + ".content"); private AtomicLong lastExpirationCleanup = new AtomicLong(0L); @@ -107,6 +109,13 @@ public Collection getStateInformation() { ); } + @Override + public void dumpContent() { + if (LOGGER_CONTENT.isInfoEnabled()) { + state.forEach((k, v) -> LOGGER_CONTENT.info("Cached trigger creation: {}: {}", k, v)); + } + } + @PostConstruct public void register() { cacheRegistry.registerCacheableService(this); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkHookHelper.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkHookHelper.java index 42699f046b9..9ed7fd6ab1b 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkHookHelper.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkHookHelper.java @@ -153,7 +153,8 @@ private void evaluateScriptingHook(LensContext context, LOGGER.trace("Evaluating {}", shortDesc); // TODO: it would be nice to cache this // null output definition: this script has no output - ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression(scriptExpressionEvaluatorType, null, context.getPrivilegedExpressionProfile(), expressionFactory, shortDesc, task, result); + ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression(scriptExpressionEvaluatorType, null, + context.getPrivilegedExpressionProfile(), expressionFactory, shortDesc, result); ExpressionVariables variables = new ExpressionVariables(); variables.put(ExpressionConstants.VAR_PRISM_CONTEXT, prismContext, PrismContext.class); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusConstraintsChecker.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusConstraintsChecker.java index e289467da32..72e799b99f6 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusConstraintsChecker.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusConstraintsChecker.java @@ -7,7 +7,6 @@ package com.evolveum.midpoint.model.impl.lens.projector.focus; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -231,6 +230,8 @@ public static void clearCacheForDelta(Collection modificati public static class Cache extends AbstractThreadLocalCache { + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(Cache.class.getName() + ".content"); + private final Set conflictFreeNames = ConcurrentHashMap.newKeySet(); static boolean isOk(PolyStringType name, CacheConfigurationManager cacheConfigurationManager) { @@ -296,6 +297,13 @@ protected int getSize() { return conflictFreeNames.size(); } + @Override + protected void dumpContent(String threadName) { + if (LOGGER_CONTENT.isInfoEnabled()) { + conflictFreeNames.forEach(name -> LOGGER_CONTENT.info("Cached conflict-free name [{}]: {}", threadName, name)); + } + } + private static void log(String message, boolean info, Object... params) { CacheUtil.log(LOGGER, PERFORMANCE_ADVISOR, message, info, params); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ScriptExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ScriptExecutor.java index 8b22a3b285e..f24b1fc49a4 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ScriptExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ScriptExecutor.java @@ -43,10 +43,8 @@ import javax.xml.namespace.QName; import java.util.Collection; import java.util.List; -import java.util.Map; import static com.evolveum.midpoint.model.impl.scripting.VariablesUtil.cloneIfNecessary; -import static com.evolveum.midpoint.schema.constants.SchemaConstants.NS_C; /** * @author mederly @@ -87,7 +85,8 @@ public PipelineData execute(ActionExpressionType expression, PipelineData input, ScriptExpression scriptExpression; try { - scriptExpression = scriptExpressionFactory.createScriptExpression(script, outputDefinition, expressionProfile, expressionFactory, "script", context.getTask(), globalResult); + scriptExpression = scriptExpressionFactory.createScriptExpression(script, outputDefinition, expressionProfile, expressionFactory, "script", + globalResult); } catch (ExpressionSyntaxException | SecurityViolationException e) { throw new ScriptExecutionException("Couldn't parse script expression: " + e.getMessage(), e); } diff --git a/model/model-impl/src/main/resources/ctx-model.xml b/model/model-impl/src/main/resources/ctx-model.xml index fe9c2343717..2ae944e9fd9 100644 --- a/model/model-impl/src/main/resources/ctx-model.xml +++ b/model/model-impl/src/main/resources/ctx-model.xml @@ -94,7 +94,6 @@ - diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/expr/TestModelExpressions.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/expr/TestModelExpressions.java index 8d862315721..e85c441a00a 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/expr/TestModelExpressions.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/expr/TestModelExpressions.java @@ -136,7 +136,7 @@ public void testGetManagersOids() throws Exception { PROPERTY_NAME, DOMUtil.XSD_STRING); ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression( scriptType, outputDefinition, MiscSchemaUtil.getExpressionProfile(), - expressionFactory, shortTestName, task, result); + expressionFactory, shortTestName, result); ExpressionVariables variables = createVariables(ExpressionConstants.VAR_USER, chef, chef.getDefinition()); @@ -171,7 +171,7 @@ public void testIsUniquePropertyValue() throws Exception { ScriptExpressionEvaluatorType scriptType = parseScriptType("expression-" + testName + ".xml"); PrismPropertyDefinition outputDefinition = getPrismContext().definitionFactory().createPropertyDefinition(PROPERTY_NAME, DOMUtil.XSD_BOOLEAN); ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression(scriptType, outputDefinition, - MiscSchemaUtil.getExpressionProfile(), expressionFactory, testName, task, result); + MiscSchemaUtil.getExpressionProfile(), expressionFactory, testName, result); ExpressionVariables variables = createVariables( ExpressionConstants.VAR_USER, chef, chef.getDefinition(), @@ -292,7 +292,7 @@ private String executeScriptExpressionString(ExpressionVariables variables) PROPERTY_NAME, DOMUtil.XSD_STRING); ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression( scriptType, outputDefinition, MiscSchemaUtil.getExpressionProfile(), - expressionFactory, shortTestName, task, result); + expressionFactory, shortTestName, result); if (variables == null) { variables = new ExpressionVariables(); } diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportServiceImpl.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportServiceImpl.java index 8252ad38d67..f8459141719 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportServiceImpl.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportServiceImpl.java @@ -1,440 +1,441 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.report.impl; - -import java.util.*; -import java.util.Map.Entry; -import javax.xml.namespace.QName; - -import org.apache.commons.lang.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.audit.api.AuditEventRecord; -import com.evolveum.midpoint.audit.api.AuditService; -import com.evolveum.midpoint.model.api.ModelAuthorizationAction; -import com.evolveum.midpoint.model.api.ModelService; -import com.evolveum.midpoint.model.common.ArchetypeManager; -import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; -import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpression; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluationContext; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluatorFactory; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionFactory; -import com.evolveum.midpoint.model.common.expression.script.groovy.GroovyScriptEvaluator; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.query.ObjectFilter; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.query.TypeFilter; -import com.evolveum.midpoint.repo.common.ObjectResolver; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.report.api.ReportService; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.SchemaHelper; -import com.evolveum.midpoint.schema.SelectorOptions; -import com.evolveum.midpoint.schema.expression.*; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ObjectQueryUtil; -import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; -import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.task.api.TaskManager; -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.*; -import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; - -@Component -public class ReportServiceImpl implements ReportService { - - private static final Trace LOGGER = TraceManager.getTrace(ReportServiceImpl.class); - - @Autowired private ModelService model; - @Autowired private TaskManager taskManager; - @Autowired private PrismContext prismContext; - @Autowired private SchemaHelper schemaHelper; - @Autowired private ExpressionFactory expressionFactory; - @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; - @Autowired private AuditService auditService; - @Autowired private FunctionLibrary logFunctionLibrary; - @Autowired private FunctionLibrary basicFunctionLibrary; - @Autowired private FunctionLibrary midpointFunctionLibrary; - @Autowired private SecurityEnforcer securityEnforcer; - @Autowired private ScriptExpressionFactory scriptExpressionFactory; - @Autowired private ArchetypeManager archetypeManager; - - @Override - public ObjectQuery parseQuery(PrismObject report, String query, VariablesMap parameters, Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - if (StringUtils.isBlank(query)) { - return null; - } - - ObjectQuery parsedQuery; - try { - - ExpressionProfile expressionProfile = determineExpressionProfile(report, result); - - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); - SearchFilterType filterType = prismContext.parserFor(query).parseRealValue(SearchFilterType.class); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("filter(SearchFilterType)\n{}", filterType.debugDump(1)); - } - ObjectFilter filter = prismContext.getQueryConverter().parseFilter(filterType, UserType.class); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("filter(ObjectFilter)\n{}", filter.debugDump(1)); - } - if (!(filter instanceof TypeFilter)) { - throw new IllegalArgumentException( - "Defined query must contain type. Use 'type filter' in your report query."); - } - - ObjectFilter subFilter = ((TypeFilter) filter).getFilter(); - ObjectQuery q = prismContext.queryFactory().createQuery(subFilter); - - ExpressionVariables variables = new ExpressionVariables(); - variables.putAll(parameters); - - q = ExpressionUtil.evaluateQueryExpressions(q, variables, expressionProfile, expressionFactory, prismContext, - "parsing expression values for report", task, result); - ((TypeFilter) filter).setFilter(q.getFilter()); - ObjectQueryUtil.simplify(filter, prismContext); - parsedQuery = prismContext.queryFactory().createQuery(filter); - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("report query (parsed):\n{}", parsedQuery.debugDump(1)); - } - } catch (SchemaException | ObjectNotFoundException | ExpressionEvaluationException - | CommunicationException | ConfigurationException | SecurityViolationException e) { - LOGGER.error("Cannot convert query, reason: {}", e.getMessage()); - throw e; - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - return parsedQuery; - - } - - @Override - public Collection> searchObjects(ObjectQuery query, Collection> options, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - // List> results = new ArrayList<>(); - - // GetOperationOptions options = GetOperationOptions.createRaw(); - - if (!(query.getFilter() instanceof TypeFilter)) { - throw new IllegalArgumentException("Query must contain type filter."); - } - - TypeFilter typeFilter = (TypeFilter) query.getFilter(); - QName type = typeFilter.getType(); - Class clazz = prismContext.getSchemaRegistry().determineCompileTimeClass(type); - if (clazz == null) { - PrismObjectDefinition objectDef = prismContext.getSchemaRegistry().findObjectDefinitionByType(type); - if (objectDef == null) { - throw new SchemaException("Undefined object type used in query, type: " + type); - } - clazz = objectDef.getCompileTimeClass(); - } - - ObjectQuery queryForSearch = prismContext.queryFactory().createQuery(typeFilter.getFilter()); - - // options.add(new - // SelectorOptions(GetOperationOptions.createResolveNames())); - GetOperationOptions getOptions = GetOperationOptions.createResolveNames(); - if (ShadowType.class.isAssignableFrom(clazz) && securityEnforcer.isAuthorized(ModelAuthorizationAction.RAW_OPERATION.getUrl(), null, AuthorizationParameters.EMPTY, null, task, parentResult)) { - LOGGER.trace("Setting searching in raw mode."); - getOptions.setRaw(Boolean.TRUE); // shadows in non-raw mode require specifying resource OID and kind (at least) - todo research this further - } else { - LOGGER.trace("Setting searching in noFetch mode. Shadows in non-raw mode require specifying resource OID and objectClass (kind) at least."); - getOptions.setNoFetch(Boolean.TRUE); - } - options = SelectorOptions.createCollection(getOptions); - List> results; - try { - results = model.searchObjects(clazz, queryForSearch, options, task, parentResult); - return results; - } catch (SchemaException | ObjectNotFoundException | SecurityViolationException - | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { - // TODO Auto-generated catch block - throw e; - } - - } - - @Override - public Collection> evaluateScript(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - - ExpressionVariables variables = new ExpressionVariables(); - variables.putAll(parameters); - - TypedValue auditParams = getConvertedParams(parameters); - // special variable for audit report - variables.put("auditParams", auditParams); - - ScriptExpressionEvaluationContext context = new ScriptExpressionEvaluationContext(); - context.setVariables(variables); - context.setContextDescription("report script"); // TODO: improve - context.setTask(task); - context.setResult(result); - setupExpressionProfiles(context, report); - - Object o = evaluateReportScript(script, context, report); - - List> results = new ArrayList<>(); - if (o != null) { - - if (Collection.class.isAssignableFrom(o.getClass())) { - Collection resultSet = (Collection) o; - if (resultSet != null && !resultSet.isEmpty()) { - for (Object obj : resultSet) { - results.add(convertResultingObject(obj)); - } - } - - } else { - results.add(convertResultingObject(o)); - } - } - - return results; - } - - - private Collection runAuditQuery(String sqlWhereClause, TypedValue jasperAuditParams, OperationResult result) { - if (StringUtils.isBlank(sqlWhereClause)) { - return new ArrayList<>(); - } - - String query = "select * from m_audit_event as aer " + sqlWhereClause; - LOGGER.trace("AAAAAAA: query: {}", query); - Map auditParams = ReportUtils.jasperParamsToAuditParams((VariablesMap)jasperAuditParams.getValue()); - LOGGER.trace("AAAAAAA: auditParams:\n{}", auditParams); - List auditRecords = auditService.listRecords(query, auditParams, result); - LOGGER.trace("AAAAAAA: {} records", auditRecords==null?null:auditRecords.size()); - return auditRecords; - } - - @Override - public Object evaluate(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, - ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - - ExpressionVariables variables = new ExpressionVariables(); - variables.addVariableDefinitions(parameters); - - // special variable for audit report - variables.put("auditParams", getConvertedParams(parameters)); - - ScriptExpressionEvaluationContext context = new ScriptExpressionEvaluationContext(); - context.setVariables(variables); - context.setContextDescription("report script"); // TODO: improve - context.setTask(task); - context.setResult(result); - setupExpressionProfiles(context, report); - - Object o = evaluateReportScript(script, context, report); - - return o; - - } - - protected PrismContainerValue convertResultingObject(Object obj) { - if (obj instanceof PrismObject) { - return ((PrismObject) obj).asObjectable().asPrismContainerValue(); - } else if (obj instanceof Objectable) { - return ((Objectable) obj).asPrismContainerValue(); - } else if (obj instanceof PrismContainerValue) { - return (PrismContainerValue) obj; - } else if (obj instanceof Containerable) { - return ((Containerable) obj).asPrismContainerValue(); - } else { - throw new IllegalStateException("Reporting script should return something compatible with PrismContainerValue, not a " + obj.getClass()); - } - } - - @Override - public Collection evaluateAuditScript(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - Collection results = new ArrayList<>(); - - TypedValue auditParams = getConvertedParams(parameters); - ExpressionVariables variables = new ExpressionVariables(); - variables.put("auditParams", auditParams); - - ScriptExpressionEvaluationContext context = new ScriptExpressionEvaluationContext(); - context.setVariables(variables); - - context.setContextDescription("report script"); // TODO: improve - context.setTask(task); - context.setResult(result); - setupExpressionProfiles(context, report); - - Object o = evaluateReportScript(script, context, report); - - // HACK to allow audit reports where query is just a plain string. - // Oh my, this code is a mess. This needs a real rewrite. MID-5572 - if (o instanceof String) { - JasperReportEngineConfigurationType jasperConfig = report.asObjectable().getJasper(); - if (jasperConfig == null) { - throw new SchemaException("Jasper reportType not set, cannot determine how to use string query"); - } - JasperReportTypeType reportType = jasperConfig.getReportType(); - if (reportType == null) { - throw new SchemaException("Jasper reportType not set, cannot determine how to use string query"); - } - if (reportType.equals(JasperReportTypeType.AUDIT_SQL)) { - return runAuditQuery((String)o, auditParams, result); - } else { - throw new SchemaException("Jasper reportType is not set to auditSql, cannot determine how to use string query"); - } - } - - if (o != null) { - - if (Collection.class.isAssignableFrom(o.getClass())) { - Collection resultSet = (Collection) o; - if (resultSet != null && !resultSet.isEmpty()) { - for (Object obj : resultSet) { - if (!(obj instanceof AuditEventRecord)) { - LOGGER.warn("Skipping result, not an audit event record " + obj); - continue; - } - results.add((AuditEventRecord) obj); - } - - } - - } else { - results.add((AuditEventRecord) o); - } - } - - return results; - } - - private TypedValue getConvertedParams(VariablesMap parameters) { - if (parameters == null) { - return new TypedValue<>(null, VariablesMap.class); - } - - VariablesMap resultParamMap = new VariablesMap(); - Set> paramEntries = parameters.entrySet(); - for (Entry e : paramEntries) { - Object value = e.getValue().getValue(); - if (value instanceof PrismPropertyValue) { - resultParamMap.put(e.getKey(), e.getValue().createTransformed(((PrismPropertyValue) value).getValue())); - } else { - resultParamMap.put(e.getKey(), e.getValue()); - } - } - - return new TypedValue<>(resultParamMap, VariablesMap.class); - } - - private Collection createFunctionLibraries() { - FunctionLibrary midPointLib = new FunctionLibrary(); - midPointLib.setVariableName("report"); - midPointLib.setNamespace("http://midpoint.evolveum.com/xml/ns/public/function/report-3"); - ReportFunctions reportFunctions = new ReportFunctions(prismContext, schemaHelper, model, taskManager, auditService); - midPointLib.setGenericFunctions(reportFunctions); - - Collection functions = new ArrayList<>(); - functions.add(basicFunctionLibrary); - functions.add(logFunctionLibrary); - functions.add(midpointFunctionLibrary); - functions.add(midPointLib); - return functions; - } - - @Override - public PrismContext getPrismContext() { - return prismContext; - } - - public Object evaluateReportScript(String codeString, ScriptExpressionEvaluationContext context, PrismObject report) throws ExpressionEvaluationException, - ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, SchemaException { - - ScriptExpressionEvaluatorConfigurationType defaultScriptConfiguration = report.asObjectable().getDefaultScriptConfiguration(); - ScriptExpressionEvaluatorType expressionType = new ScriptExpressionEvaluatorType(); - expressionType.setCode(codeString); - expressionType.setObjectVariableMode(defaultScriptConfiguration == null ? ObjectVariableModeType.OBJECT : defaultScriptConfiguration.getObjectVariableMode()); - context.setExpressionType(expressionType); - // Be careful about output definition here. We really do NOT want to set it. - // Not setting the definition means that we want raw value without any conversion. - // This is what we really want, because there may be exotic things such as JRTemplate going through those expressions - // We do not have any reasonable prism definitions for those. - - context.setFunctions(createFunctionLibraries()); - context.setObjectResolver(objectResolver); - - ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression( - expressionType, context.getOutputDefinition(), context.getExpressionProfile(), expressionFactory, context.getContextDescription(), context.getTask(), context.getResult()); - - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(context.getTask(), context.getResult())); - List expressionResult; - try { - expressionResult = scriptExpression.evaluate(context); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - - if (expressionResult == null || expressionResult.isEmpty()) { - return null; - } - if (expressionResult.size() > 1) { - throw new ExpressionEvaluationException("Too many results from expression "+context.getContextDescription()); - } - return expressionResult.get(0).getRealValue(); - } - - private ExpressionProfile determineExpressionProfile(PrismObject report, OperationResult result) throws SchemaException, ConfigurationException { - if (report == null) { - throw new IllegalArgumentException("No report defined, cannot determine profile"); - } - return archetypeManager.determineExpressionProfile(report, result); - } - - private void setupExpressionProfiles(ScriptExpressionEvaluationContext context, PrismObject report) throws SchemaException, ConfigurationException { - ExpressionProfile expressionProfile = determineExpressionProfile(report, context.getResult()); - LOGGER.trace("Using expression profile '"+(expressionProfile==null?null:expressionProfile.getIdentifier())+"' for report evaluation, determined from: {}", report); - context.setExpressionProfile(expressionProfile); - context.setScriptExpressionProfile(findScriptExpressionProfile(expressionProfile, report)); - } - - private ScriptExpressionProfile findScriptExpressionProfile(ExpressionProfile expressionProfile, PrismObject report) { - if (expressionProfile == null) { - return null; - } - ExpressionEvaluatorProfile scriptEvaluatorProfile = expressionProfile.getEvaluatorProfile(ScriptExpressionEvaluatorFactory.ELEMENT_NAME); - if (scriptEvaluatorProfile == null) { - return null; - } - return scriptEvaluatorProfile.getScriptExpressionProfile(getScriptLanguageName(report)); - } - - private String getScriptLanguageName(PrismObject report) { - // Hardcoded for now - return GroovyScriptEvaluator.LANGUAGE_NAME; - } - - @Override - public PrismObject getReportDefinition(String reportOid, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - return model.getObject(ReportType.class, reportOid, null, task, result); - } - - @Override - public boolean isAuthorizedToRunReport(PrismObject report, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - AuthorizationParameters params = AuthorizationParameters.Builder.buildObject(report); - return securityEnforcer.isAuthorized(ModelAuthorizationAction.RUN_REPORT.getUrl(), null, params, null, task, result); - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.report.impl; + +import java.util.*; +import java.util.Map.Entry; +import javax.xml.namespace.QName; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.audit.api.AuditEventRecord; +import com.evolveum.midpoint.audit.api.AuditService; +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; +import com.evolveum.midpoint.model.api.ModelService; +import com.evolveum.midpoint.model.common.ArchetypeManager; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpression; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluationContext; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluatorFactory; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionFactory; +import com.evolveum.midpoint.model.common.expression.script.groovy.GroovyScriptEvaluator; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.query.TypeFilter; +import com.evolveum.midpoint.repo.common.ObjectResolver; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.report.api.ReportService; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SchemaHelper; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.expression.*; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectQueryUtil; +import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; +import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskManager; +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.*; +import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; + +@Component +public class ReportServiceImpl implements ReportService { + + private static final Trace LOGGER = TraceManager.getTrace(ReportServiceImpl.class); + + @Autowired private ModelService model; + @Autowired private TaskManager taskManager; + @Autowired private PrismContext prismContext; + @Autowired private SchemaHelper schemaHelper; + @Autowired private ExpressionFactory expressionFactory; + @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; + @Autowired private AuditService auditService; + @Autowired private FunctionLibrary logFunctionLibrary; + @Autowired private FunctionLibrary basicFunctionLibrary; + @Autowired private FunctionLibrary midpointFunctionLibrary; + @Autowired private SecurityEnforcer securityEnforcer; + @Autowired private ScriptExpressionFactory scriptExpressionFactory; + @Autowired private ArchetypeManager archetypeManager; + + @Override + public ObjectQuery parseQuery(PrismObject report, String query, VariablesMap parameters, Task task, OperationResult result) throws SchemaException, + ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + if (StringUtils.isBlank(query)) { + return null; + } + + ObjectQuery parsedQuery; + try { + + ExpressionProfile expressionProfile = determineExpressionProfile(report, result); + + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); + SearchFilterType filterType = prismContext.parserFor(query).parseRealValue(SearchFilterType.class); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("filter(SearchFilterType)\n{}", filterType.debugDump(1)); + } + ObjectFilter filter = prismContext.getQueryConverter().parseFilter(filterType, UserType.class); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("filter(ObjectFilter)\n{}", filter.debugDump(1)); + } + if (!(filter instanceof TypeFilter)) { + throw new IllegalArgumentException( + "Defined query must contain type. Use 'type filter' in your report query."); + } + + ObjectFilter subFilter = ((TypeFilter) filter).getFilter(); + ObjectQuery q = prismContext.queryFactory().createQuery(subFilter); + + ExpressionVariables variables = new ExpressionVariables(); + variables.putAll(parameters); + + q = ExpressionUtil.evaluateQueryExpressions(q, variables, expressionProfile, expressionFactory, prismContext, + "parsing expression values for report", task, result); + ((TypeFilter) filter).setFilter(q.getFilter()); + ObjectQueryUtil.simplify(filter, prismContext); + parsedQuery = prismContext.queryFactory().createQuery(filter); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("report query (parsed):\n{}", parsedQuery.debugDump(1)); + } + } catch (SchemaException | ObjectNotFoundException | ExpressionEvaluationException + | CommunicationException | ConfigurationException | SecurityViolationException e) { + LOGGER.error("Cannot convert query, reason: {}", e.getMessage()); + throw e; + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + return parsedQuery; + + } + + @Override + public Collection> searchObjects(ObjectQuery query, Collection> options, Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + // List> results = new ArrayList<>(); + + // GetOperationOptions options = GetOperationOptions.createRaw(); + + if (!(query.getFilter() instanceof TypeFilter)) { + throw new IllegalArgumentException("Query must contain type filter."); + } + + TypeFilter typeFilter = (TypeFilter) query.getFilter(); + QName type = typeFilter.getType(); + Class clazz = prismContext.getSchemaRegistry().determineCompileTimeClass(type); + if (clazz == null) { + PrismObjectDefinition objectDef = prismContext.getSchemaRegistry().findObjectDefinitionByType(type); + if (objectDef == null) { + throw new SchemaException("Undefined object type used in query, type: " + type); + } + clazz = objectDef.getCompileTimeClass(); + } + + ObjectQuery queryForSearch = prismContext.queryFactory().createQuery(typeFilter.getFilter()); + + // options.add(new + // SelectorOptions(GetOperationOptions.createResolveNames())); + GetOperationOptions getOptions = GetOperationOptions.createResolveNames(); + if (ShadowType.class.isAssignableFrom(clazz) && securityEnforcer.isAuthorized(ModelAuthorizationAction.RAW_OPERATION.getUrl(), null, AuthorizationParameters.EMPTY, null, task, parentResult)) { + LOGGER.trace("Setting searching in raw mode."); + getOptions.setRaw(Boolean.TRUE); // shadows in non-raw mode require specifying resource OID and kind (at least) - todo research this further + } else { + LOGGER.trace("Setting searching in noFetch mode. Shadows in non-raw mode require specifying resource OID and objectClass (kind) at least."); + getOptions.setNoFetch(Boolean.TRUE); + } + options = SelectorOptions.createCollection(getOptions); + List> results; + try { + results = model.searchObjects(clazz, queryForSearch, options, task, parentResult); + return results; + } catch (SchemaException | ObjectNotFoundException | SecurityViolationException + | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { + // TODO Auto-generated catch block + throw e; + } + + } + + @Override + public Collection> evaluateScript(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + + ExpressionVariables variables = new ExpressionVariables(); + variables.putAll(parameters); + + TypedValue auditParams = getConvertedParams(parameters); + // special variable for audit report + variables.put("auditParams", auditParams); + + ScriptExpressionEvaluationContext context = new ScriptExpressionEvaluationContext(); + context.setVariables(variables); + context.setContextDescription("report script"); // TODO: improve + context.setTask(task); + context.setResult(result); + setupExpressionProfiles(context, report); + + Object o = evaluateReportScript(script, context, report); + + List> results = new ArrayList<>(); + if (o != null) { + + if (Collection.class.isAssignableFrom(o.getClass())) { + Collection resultSet = (Collection) o; + if (resultSet != null && !resultSet.isEmpty()) { + for (Object obj : resultSet) { + results.add(convertResultingObject(obj)); + } + } + + } else { + results.add(convertResultingObject(o)); + } + } + + return results; + } + + + private Collection runAuditQuery(String sqlWhereClause, TypedValue jasperAuditParams, OperationResult result) { + if (StringUtils.isBlank(sqlWhereClause)) { + return new ArrayList<>(); + } + + String query = "select * from m_audit_event as aer " + sqlWhereClause; + LOGGER.trace("AAAAAAA: query: {}", query); + Map auditParams = ReportUtils.jasperParamsToAuditParams((VariablesMap)jasperAuditParams.getValue()); + LOGGER.trace("AAAAAAA: auditParams:\n{}", auditParams); + List auditRecords = auditService.listRecords(query, auditParams, result); + LOGGER.trace("AAAAAAA: {} records", auditRecords==null?null:auditRecords.size()); + return auditRecords; + } + + @Override + public Object evaluate(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, + ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + + ExpressionVariables variables = new ExpressionVariables(); + variables.addVariableDefinitions(parameters); + + // special variable for audit report + variables.put("auditParams", getConvertedParams(parameters)); + + ScriptExpressionEvaluationContext context = new ScriptExpressionEvaluationContext(); + context.setVariables(variables); + context.setContextDescription("report script"); // TODO: improve + context.setTask(task); + context.setResult(result); + setupExpressionProfiles(context, report); + + Object o = evaluateReportScript(script, context, report); + + return o; + + } + + protected PrismContainerValue convertResultingObject(Object obj) { + if (obj instanceof PrismObject) { + return ((PrismObject) obj).asObjectable().asPrismContainerValue(); + } else if (obj instanceof Objectable) { + return ((Objectable) obj).asPrismContainerValue(); + } else if (obj instanceof PrismContainerValue) { + return (PrismContainerValue) obj; + } else if (obj instanceof Containerable) { + return ((Containerable) obj).asPrismContainerValue(); + } else { + throw new IllegalStateException("Reporting script should return something compatible with PrismContainerValue, not a " + obj.getClass()); + } + } + + @Override + public Collection evaluateAuditScript(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + Collection results = new ArrayList<>(); + + TypedValue auditParams = getConvertedParams(parameters); + ExpressionVariables variables = new ExpressionVariables(); + variables.put("auditParams", auditParams); + + ScriptExpressionEvaluationContext context = new ScriptExpressionEvaluationContext(); + context.setVariables(variables); + + context.setContextDescription("report script"); // TODO: improve + context.setTask(task); + context.setResult(result); + setupExpressionProfiles(context, report); + + Object o = evaluateReportScript(script, context, report); + + // HACK to allow audit reports where query is just a plain string. + // Oh my, this code is a mess. This needs a real rewrite. MID-5572 + if (o instanceof String) { + JasperReportEngineConfigurationType jasperConfig = report.asObjectable().getJasper(); + if (jasperConfig == null) { + throw new SchemaException("Jasper reportType not set, cannot determine how to use string query"); + } + JasperReportTypeType reportType = jasperConfig.getReportType(); + if (reportType == null) { + throw new SchemaException("Jasper reportType not set, cannot determine how to use string query"); + } + if (reportType.equals(JasperReportTypeType.AUDIT_SQL)) { + return runAuditQuery((String)o, auditParams, result); + } else { + throw new SchemaException("Jasper reportType is not set to auditSql, cannot determine how to use string query"); + } + } + + if (o != null) { + + if (Collection.class.isAssignableFrom(o.getClass())) { + Collection resultSet = (Collection) o; + if (resultSet != null && !resultSet.isEmpty()) { + for (Object obj : resultSet) { + if (!(obj instanceof AuditEventRecord)) { + LOGGER.warn("Skipping result, not an audit event record " + obj); + continue; + } + results.add((AuditEventRecord) obj); + } + + } + + } else { + results.add((AuditEventRecord) o); + } + } + + return results; + } + + private TypedValue getConvertedParams(VariablesMap parameters) { + if (parameters == null) { + return new TypedValue<>(null, VariablesMap.class); + } + + VariablesMap resultParamMap = new VariablesMap(); + Set> paramEntries = parameters.entrySet(); + for (Entry e : paramEntries) { + Object value = e.getValue().getValue(); + if (value instanceof PrismPropertyValue) { + resultParamMap.put(e.getKey(), e.getValue().createTransformed(((PrismPropertyValue) value).getValue())); + } else { + resultParamMap.put(e.getKey(), e.getValue()); + } + } + + return new TypedValue<>(resultParamMap, VariablesMap.class); + } + + private Collection createFunctionLibraries() { + FunctionLibrary midPointLib = new FunctionLibrary(); + midPointLib.setVariableName("report"); + midPointLib.setNamespace("http://midpoint.evolveum.com/xml/ns/public/function/report-3"); + ReportFunctions reportFunctions = new ReportFunctions(prismContext, schemaHelper, model, taskManager, auditService); + midPointLib.setGenericFunctions(reportFunctions); + + Collection functions = new ArrayList<>(); + functions.add(basicFunctionLibrary); + functions.add(logFunctionLibrary); + functions.add(midpointFunctionLibrary); + functions.add(midPointLib); + return functions; + } + + @Override + public PrismContext getPrismContext() { + return prismContext; + } + + public Object evaluateReportScript(String codeString, ScriptExpressionEvaluationContext context, PrismObject report) throws ExpressionEvaluationException, + ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, SchemaException { + + ScriptExpressionEvaluatorConfigurationType defaultScriptConfiguration = report.asObjectable().getDefaultScriptConfiguration(); + ScriptExpressionEvaluatorType expressionType = new ScriptExpressionEvaluatorType(); + expressionType.setCode(codeString); + expressionType.setObjectVariableMode(defaultScriptConfiguration == null ? ObjectVariableModeType.OBJECT : defaultScriptConfiguration.getObjectVariableMode()); + context.setExpressionType(expressionType); + // Be careful about output definition here. We really do NOT want to set it. + // Not setting the definition means that we want raw value without any conversion. + // This is what we really want, because there may be exotic things such as JRTemplate going through those expressions + // We do not have any reasonable prism definitions for those. + + context.setFunctions(createFunctionLibraries()); + context.setObjectResolver(objectResolver); + + ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression( + expressionType, context.getOutputDefinition(), context.getExpressionProfile(), expressionFactory, context.getContextDescription(), + context.getResult()); + + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(context.getTask(), context.getResult())); + List expressionResult; + try { + expressionResult = scriptExpression.evaluate(context); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + + if (expressionResult == null || expressionResult.isEmpty()) { + return null; + } + if (expressionResult.size() > 1) { + throw new ExpressionEvaluationException("Too many results from expression "+context.getContextDescription()); + } + return expressionResult.get(0).getRealValue(); + } + + private ExpressionProfile determineExpressionProfile(PrismObject report, OperationResult result) throws SchemaException, ConfigurationException { + if (report == null) { + throw new IllegalArgumentException("No report defined, cannot determine profile"); + } + return archetypeManager.determineExpressionProfile(report, result); + } + + private void setupExpressionProfiles(ScriptExpressionEvaluationContext context, PrismObject report) throws SchemaException, ConfigurationException { + ExpressionProfile expressionProfile = determineExpressionProfile(report, context.getResult()); + LOGGER.trace("Using expression profile '"+(expressionProfile==null?null:expressionProfile.getIdentifier())+"' for report evaluation, determined from: {}", report); + context.setExpressionProfile(expressionProfile); + context.setScriptExpressionProfile(findScriptExpressionProfile(expressionProfile, report)); + } + + private ScriptExpressionProfile findScriptExpressionProfile(ExpressionProfile expressionProfile, PrismObject report) { + if (expressionProfile == null) { + return null; + } + ExpressionEvaluatorProfile scriptEvaluatorProfile = expressionProfile.getEvaluatorProfile(ScriptExpressionEvaluatorFactory.ELEMENT_NAME); + if (scriptEvaluatorProfile == null) { + return null; + } + return scriptEvaluatorProfile.getScriptExpressionProfile(getScriptLanguageName(report)); + } + + private String getScriptLanguageName(PrismObject report) { + // Hardcoded for now + return GroovyScriptEvaluator.LANGUAGE_NAME; + } + + @Override + public PrismObject getReportDefinition(String reportOid, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + return model.getObject(ReportType.class, reportOid, null, task, result); + } + + @Override + public boolean isAuthorizedToRunReport(PrismObject report, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + AuthorizationParameters params = AuthorizationParameters.Builder.buildObject(report); + return securityEnforcer.isAuthorized(ModelAuthorizationAction.RUN_REPORT.getUrl(), null, params, null, task, result); + } +} diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConfiguredConnectorInstanceEntry.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConfiguredConnectorInstanceEntry.java index ab7b3d429b0..fe27d7aa9e7 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConfiguredConnectorInstanceEntry.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConfiguredConnectorInstanceEntry.java @@ -48,4 +48,11 @@ public void setConnectorInstance(ConnectorInstance connectorInstance) { this.connectorInstance = connectorInstance; } + @Override + public String toString() { + return "ConfiguredConnectorInstanceEntry{" + + "connectorOid='" + connectorOid + '\'' + + ", connectorInstance=" + connectorInstance + + '}'; + } } diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConnectorManager.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConnectorManager.java index 36ab2500462..938641ce982 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConnectorManager.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConnectorManager.java @@ -97,6 +97,7 @@ public void unregister() { } private static final Trace LOGGER = TraceManager.getTrace(ConnectorManager.class); + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(ConnectorManager.class.getName() + ".content"); private static final String CONNECTOR_INSTANCE_CACHE_NAME = ConnectorManager.class.getName() + ".connectorInstanceCache"; private static final String CONNECTOR_TYPE_CACHE_NAME = ConnectorManager.class.getName() + ".connectorTypeCache"; @@ -691,4 +692,12 @@ public Collection getStateInformation() { .size(connectorBeanCache.size()) ); } + + @Override + public void dumpContent() { + if (LOGGER_CONTENT.isInfoEnabled()) { + connectorInstanceCache.forEach((k, v) -> LOGGER_CONTENT.info("Cached connector instance: {}: {}", k, v)); + connectorBeanCache.forEach((k, v) -> LOGGER_CONTENT.info("Cached connector bean: {}: {}", k, v)); + } + } } diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConstraintsChecker.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConstraintsChecker.java index 388e784e247..ff4abd54817 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConstraintsChecker.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConstraintsChecker.java @@ -43,7 +43,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ConstraintsCheckingStrategyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; + import org.jetbrains.annotations.NotNull; /** @@ -383,6 +383,8 @@ public String toString() { public static class Cache extends AbstractThreadLocalCache { + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(Cache.class.getName() + ".content"); + private final Set conflictFreeSituations = ConcurrentHashMap.newKeySet(); private static boolean isOk(String resourceOid, String knownShadowOid, QName objectClassName, QName attributeName, @@ -455,14 +457,6 @@ private static Cache getCache() { return cacheInstances.get(Thread.currentThread()); } - public static void remove(PolyStringType name) { - Cache cache = getCache(); - if (name != null && cache != null) { - log("Cache REMOVE for {}", false, name); - cache.conflictFreeSituations.remove(name.getOrig()); - } - } - @Override public String description() { return "conflict-free situations: " + conflictFreeSituations; @@ -472,6 +466,14 @@ public String description() { protected int getSize() { return conflictFreeSituations.size(); } + + @Override + protected void dumpContent(String threadName) { + if (LOGGER_CONTENT.isInfoEnabled()) { + conflictFreeSituations.forEach(situation -> + LOGGER_CONTENT.info("Cached conflict-free situation [{}]: {}", threadName, situation)); + } + } } private static void log(String message, boolean info, Object... params) { diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceCache.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceCache.java index 3b9cb3ec1da..956b4d7c7f8 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceCache.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceCache.java @@ -48,6 +48,7 @@ public class ResourceCache implements Cacheable { private static final Trace LOGGER = TraceManager.getTrace(ResourceCache.class); + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(ResourceCache.class.getName() + ".content"); @Autowired private PrismContext prismContext; @Autowired private CacheRegistry cacheRegistry; @@ -65,8 +66,8 @@ public void unregister() { } /** - * Note that prism objects in this map are always immutable. And they must remain immutable after getting them - * from the cache. + * Note that prism objects in this map are always not null and immutable. + * And they must remain immutable after getting them from the cache. * * As for ConcurrentHashMap: Although we use synchronization whenever possible, let's be extra cautious here. */ @@ -199,4 +200,12 @@ public synchronized Collection getStateInformat .size(cache.size()) ); } + + @Override + public void dumpContent() { + if (LOGGER_CONTENT.isInfoEnabled()) { + cache.forEach((oid, resource) -> LOGGER_CONTENT.info("Cached resource: {}: {} (version: {})", + oid, resource, resource.getVersion())); + } + } } diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/Cacheable.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/Cacheable.java index 5ba577e05ab..79c2edddc39 100644 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/Cacheable.java +++ b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/Cacheable.java @@ -18,4 +18,6 @@ public interface Cacheable { @NotNull Collection getStateInformation(); + + void dumpContent(); } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/AbstractGlobalCacheValue.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/AbstractGlobalCacheValue.java new file mode 100644 index 00000000000..8107d4b2652 --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/AbstractGlobalCacheValue.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache; + +class AbstractGlobalCacheValue { + + /** + * When the value was crated. Used only for diagnostic purposes. + * (Cache eviction is managed by cache2k itself!) + */ + private final long createdAt = System.currentTimeMillis(); + + long getAge() { + return System.currentTimeMillis() - createdAt; + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheDispatcherImpl.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheDispatcherImpl.java index 3b161aef63a..cb659c14ef6 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheDispatcherImpl.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheDispatcherImpl.java @@ -12,7 +12,6 @@ import com.evolveum.midpoint.repo.api.CacheListener; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import java.util.ArrayList; @@ -26,7 +25,7 @@ public class CacheDispatcherImpl implements CacheDispatcher { private static final Trace LOGGER = TraceManager.getTrace(CacheDispatcherImpl.class); - private List cacheListeners = new ArrayList<>(); + private final List cacheListeners = new ArrayList<>(); @Override public synchronized void registerCacheListener(CacheListener cacheListener) { @@ -53,5 +52,4 @@ public void dispatchInvalidation(Class type, String oi listener.invalidate(type, oid, clusterwide, context); } } - } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheRegistry.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheRegistry.java index 727346f8b90..591f779ba30 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheRegistry.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheRegistry.java @@ -25,7 +25,7 @@ @Component public class CacheRegistry implements CacheListener { - private List cacheableServices = new ArrayList<>(); + private final List cacheableServices = new ArrayList<>(); @Autowired private CacheDispatcher dispatcher; @Autowired private PrismContext prismContext; @@ -69,5 +69,9 @@ public CachesStateInformationType getStateInformation() { cacheableServices.forEach(cacheable -> rv.getEntry().addAll(cacheable.getStateInformation())); return rv; } + + public void dumpContent() { + cacheableServices.forEach(Cacheable::dumpContent); + } } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectValue.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectValue.java index 9a0a6744f25..d8d40023ebf 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectValue.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectValue.java @@ -10,48 +10,49 @@ import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import org.jetbrains.annotations.NotNull; + /** * Created by Viliam Repan (lazyman). */ -public class GlobalCacheObjectValue { +class GlobalCacheObjectValue extends AbstractGlobalCacheValue { - private PrismObject object; + @NotNull private final PrismObject object; - private long timeToLive; + private long timeToCheckVersion; - public GlobalCacheObjectValue(PrismObject object, long timeToLive) { + GlobalCacheObjectValue(@NotNull PrismObject object, long timeToCheckVersion) { this.object = object; - this.timeToLive = timeToLive; + this.timeToCheckVersion = timeToCheckVersion; } - public long getTimeToLive() { - return timeToLive; + long getTimeToCheckVersion() { + return timeToCheckVersion; } - public String getObjectOid() { + String getObjectOid() { return object.getOid(); } - public Class getObjectType() { + Class getObjectType() { return object.getCompileTimeClass(); } - public String getObjectVersion() { + String getObjectVersion() { return object.getVersion(); } - public PrismObject getObject() { + @NotNull PrismObject getObject() { return object; // cloning is done in RepositoryCache } - public void setTimeToLive(long timeToLive) { - this.timeToLive = timeToLive; + void setTimeToCheckVersion(long timeToCheckVersion) { + this.timeToCheckVersion = timeToCheckVersion; } @Override public String toString() { - return "CacheObject{" + "ttl=" + timeToLive - + ", object=" + object - + '}'; + return "GlobalCacheObjectValue{" + "timeToCheckVersion=" + timeToCheckVersion + " (" + (timeToCheckVersion-System.currentTimeMillis()) + " left)" + + ", object=" + object + " (version " + object.getVersion() + ")}"; } } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectVersionValue.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectVersionValue.java index 70ceb0f02e8..11dc9491122 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectVersionValue.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectVersionValue.java @@ -10,10 +10,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import org.jetbrains.annotations.NotNull; -/** - * - */ -public class GlobalCacheObjectVersionValue { +public class GlobalCacheObjectVersionValue extends AbstractGlobalCacheValue { @NotNull private final Class objectType; private final String version; diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheQueryValue.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheQueryValue.java new file mode 100644 index 00000000000..051d2a71bca --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheQueryValue.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache; + +import com.evolveum.midpoint.schema.SearchResultList; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +public class GlobalCacheQueryValue extends AbstractGlobalCacheValue { + + @NotNull private final SearchResultList result; + + GlobalCacheQueryValue(@NotNull SearchResultList result) { + this.result = result; + } + + public @NotNull SearchResultList getResult() { + return result; + } + + @Override + public String toString() { + return "GlobalCacheQueryValue{" + + "result=" + result + + '}'; + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalObjectCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalObjectCache.java index ac738f397e3..c785f245596 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalObjectCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalObjectCache.java @@ -31,6 +31,7 @@ public class GlobalObjectCache extends AbstractGlobalCache { private static final Trace LOGGER = TraceManager.getTrace(GlobalObjectCache.class); + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(GlobalObjectCache.class.getName() + ".content"); private static final String CACHE_NAME = "objectCache"; @@ -140,4 +141,15 @@ Collection getStateInformation() { return Collections.emptySet(); } } + + void dumpContent() { + if (cache != null && LOGGER_CONTENT.isInfoEnabled()) { + cache.invokeAll(cache.keys(), e -> { + String key = e.getKey(); + GlobalCacheObjectValue value = e.getValue(); + LOGGER_CONTENT.info("Cached object: {}: {} (cached {} ms ago)", key, value, value.getAge()); + return null; + }); + } + } } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalQueryCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalQueryCache.java index fa20f1281fa..3136bfa1784 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalQueryCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalQueryCache.java @@ -14,9 +14,12 @@ import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.SingleCacheStateInformationType; + +import org.apache.commons.lang3.tuple.MutablePair; import org.cache2k.Cache2kBuilder; import org.cache2k.expiry.ExpiryPolicy; import org.cache2k.processor.EntryProcessor; +import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; import javax.annotation.PreDestroy; @@ -33,10 +36,11 @@ public class GlobalQueryCache extends AbstractGlobalCache { private static final Trace LOGGER = TraceManager.getTrace(GlobalQueryCache.class); + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(GlobalQueryCache.class.getName() + ".content"); private static final String CACHE_NAME = "queryCache"; - private org.cache2k.Cache cache; + private org.cache2k.Cache cache; public void initialize() { if (cache != null) { @@ -48,7 +52,7 @@ public void initialize() { LOGGER.warn("Capacity for " + getCacheType() + " is set to 0; this cache will be disabled (until system restart)"); cache = null; } else { - cache = new Cache2kBuilder() {} + cache = new Cache2kBuilder() {} .name(CACHE_NAME) .entryCapacity(capacity) .expiryPolicy(getExpirePolicy()) @@ -57,7 +61,7 @@ public void initialize() { } } - private ExpiryPolicy getExpirePolicy() { + private ExpiryPolicy getExpirePolicy() { return (key, value, loadTime, oldEntry) -> getExpiryTime(key.getType()); } @@ -74,8 +78,13 @@ public boolean isAvailable() { } public SearchResultList> get(QueryKey key) { - //noinspection unchecked - return cache != null ? cache.peek(key) : null; + if (cache != null) { + GlobalCacheQueryValue value = cache.peek(key); + //noinspection unchecked + return value != null ? value.getResult() : null; + } else { + return null; + } } public void remove(QueryKey cacheKey) { @@ -84,13 +93,14 @@ public void remove(QueryKey cacheKey) { } } - public void put(QueryKey key, SearchResultList> cacheObject) { + public void put(QueryKey key, @NotNull SearchResultList> cacheObject) { if (cache != null) { - cache.put(key, cacheObject); + //noinspection unchecked + cache.put(key, new GlobalCacheQueryValue(cacheObject)); } } - public void invokeAll(EntryProcessor entryProcessor) { + void invokeAll(EntryProcessor entryProcessor) { if (cache != null) { cache.invokeAll(cache.keys(), entryProcessor); } @@ -113,25 +123,50 @@ public void clear() { } Collection getStateInformation() { - Map, Integer> counts = new HashMap<>(); - AtomicInteger size = new AtomicInteger(0); + Map, MutablePair> counts = new HashMap<>(); + AtomicInteger queries = new AtomicInteger(0); + AtomicInteger objects = new AtomicInteger(0); if (cache != null) { cache.invokeAll(cache.keys(), e -> { - Class objectType = e.getKey().getType(); - counts.compute(objectType, (type, count) -> count != null ? count+1 : 1); - size.incrementAndGet(); + QueryKey queryKey = e.getKey(); + Class objectType = queryKey.getType(); + int resultingObjects = e.getValue().getResult().size(); + MutablePair value = counts.get(objectType); + if (value == null) { + value = new MutablePair<>(0, 0); + counts.put(objectType, value); + } + value.setLeft(value.getLeft() + 1); + value.setRight(value.getRight() + resultingObjects); + queries.incrementAndGet(); + objects.addAndGet(resultingObjects); return null; }); SingleCacheStateInformationType info = new SingleCacheStateInformationType(prismContext) .name(GlobalQueryCache.class.getName()) - .size(size.get()); - counts.forEach((type, count) -> + .size(queries.get()) + .secondarySize(objects.get()); + counts.forEach((type, pair) -> info.beginComponent() .name(type.getSimpleName()) - .size(count)); + .size(pair.getLeft()) + .secondarySize(pair.getRight())); return Collections.singleton(info); } else { return Collections.emptySet(); } } + + void dumpContent() { + if (cache != null && LOGGER_CONTENT.isInfoEnabled()) { + cache.invokeAll(cache.keys(), e -> { + QueryKey key = e.getKey(); + GlobalCacheQueryValue value = e.getValue(); + @NotNull SearchResultList queryResult = value.getResult(); + LOGGER_CONTENT.info("Cached query of {} ({} object(s), cached {} ms ago): {}: {}", + key.getType().getSimpleName(), queryResult.size(), value.getAge(), key.getQuery(), queryResult); + return null; + }); + } + } } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalVersionCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalVersionCache.java index a44ab4a027a..7ee451c4b61 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalVersionCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalVersionCache.java @@ -32,6 +32,7 @@ public class GlobalVersionCache extends AbstractGlobalCache { private static final Trace LOGGER = TraceManager.getTrace(GlobalVersionCache.class); + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(GlobalVersionCache.class.getName() + ".content"); private static final String CACHE_NAME = "versionCache"; @@ -149,4 +150,13 @@ public void put(String oid, Class type, String version) { cache.put(oid, new GlobalCacheObjectVersionValue<>(type, version)); } } + + void dumpContent() { + if (cache != null && LOGGER_CONTENT.isInfoEnabled()) { + cache.invokeAll(cache.keys(), e -> { + LOGGER_CONTENT.info("Cached version: {}: {} (cached {} ms ago)", e.getKey(), e.getValue(), e.getValue().getAge()); + return null; + }); + } + } } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalObjectCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalObjectCache.java index d62f0418194..ea959110a17 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalObjectCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalObjectCache.java @@ -9,6 +9,8 @@ import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.util.caching.AbstractThreadLocalCache; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import java.util.Map; @@ -19,6 +21,8 @@ */ public class LocalObjectCache extends AbstractThreadLocalCache { + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(LocalObjectCache.class.getName() + ".content"); + private final Map> data = new ConcurrentHashMap<>(); public PrismObject get(String oid) { @@ -42,4 +46,10 @@ public String description() { protected int getSize() { return data.size(); } + + public void dumpContent(String threadName) { + if (LOGGER_CONTENT.isInfoEnabled()) { + data.forEach((k, v) -> LOGGER_CONTENT.info("Cached object [{}] {}: {}", threadName, k, v)); + } + } } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalQueryCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalQueryCache.java index 68debd8eac5..cba25e2edbe 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalQueryCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalQueryCache.java @@ -9,6 +9,10 @@ import com.evolveum.midpoint.schema.SearchResultList; import com.evolveum.midpoint.util.caching.AbstractThreadLocalCache; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; + +import org.jetbrains.annotations.NotNull; import java.util.Iterator; import java.util.Map; @@ -19,13 +23,15 @@ */ public class LocalQueryCache extends AbstractThreadLocalCache { + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(LocalQueryCache.class.getName() + ".content"); + private final Map data = new ConcurrentHashMap<>(); public SearchResultList get(QueryKey key) { return data.get(key); } - public void put(QueryKey key, SearchResultList objects) { + public void put(QueryKey key, @NotNull SearchResultList objects) { data.put(key, objects); } @@ -43,7 +49,31 @@ protected int getSize() { return data.size(); } - public Iterator> getEntryIterator() { + public void dumpContent(String threadName) { + if (LOGGER_CONTENT.isInfoEnabled()) { + data.forEach((k, v) -> LOGGER_CONTENT.info("Cached query [{}] of {} ({} object(s)): {}: {}", threadName, + k.getType(), v.size(), k.getQuery(), v)); + } + } + + @SuppressWarnings("SameParameterValue") + static int getTotalCachedObjects(ConcurrentHashMap cacheInstances) { + int rv = 0; + for (LocalQueryCache cacheInstance : cacheInstances.values()) { + rv += cacheInstance.getCachedObjects(); + } + return rv; + } + + private int getCachedObjects() { + int rv = 0; + for (SearchResultList value : data.values()) { + rv += value.size(); + } + return rv; + } + + Iterator> getEntryIterator() { return data.entrySet().iterator(); } } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalVersionCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalVersionCache.java index f5c8c6b323e..25a87ac5f13 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalVersionCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalVersionCache.java @@ -8,6 +8,8 @@ package com.evolveum.midpoint.repo.cache; import com.evolveum.midpoint.util.caching.AbstractThreadLocalCache; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -17,6 +19,8 @@ */ public class LocalVersionCache extends AbstractThreadLocalCache { + private static final Trace LOGGER_CONTENT = TraceManager.getTrace(LocalVersionCache.class.getName() + ".content"); + private final Map data = new ConcurrentHashMap<>(); public String get(String oid) { @@ -40,4 +44,10 @@ public String description() { protected int getSize() { return data.size(); } + + public void dumpContent(String threadName) { + if (LOGGER_CONTENT.isInfoEnabled()) { + data.forEach((k, v) -> LOGGER_CONTENT.info("Cached version [{}] {}: {}", threadName, k, v)); + } + } } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/QueryKey.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/QueryKey.java index f4f4a00f526..807e230dd08 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/QueryKey.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/QueryKey.java @@ -10,6 +10,8 @@ import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import org.jetbrains.annotations.NotNull; + import java.util.Objects; /** @@ -18,11 +20,11 @@ */ public class QueryKey { - private final Class type; + @NotNull private final Class type; private final ObjectQuery query; private Integer cachedHashCode; - QueryKey(Class type, ObjectQuery query) { + QueryKey(@NotNull Class type, ObjectQuery query) { this.type = type; this.query = query != null ? query.clone() : null; } @@ -48,7 +50,7 @@ public int hashCode() { return cachedHashCode; } - public Class getType() { + @NotNull public Class getType() { return type; } @@ -59,7 +61,7 @@ public ObjectQuery getQuery() { @Override public String toString() { return "QueryKey{" + - "type=" + type + + "type=" + type.getSimpleName() + ", query=" + query + '}'; } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java index d47f5b524dd..d283ff1a244 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java @@ -329,7 +329,7 @@ public PrismObject getObject(Class type, String oid } objectToReturn = loadAndCacheObject(type, oid, options, readOnly, localObjectsCache, local.supports, result); } else { - cacheObject.setTimeToLive(System.currentTimeMillis() + getTimeToVersionCheck(global.typeConfig, + cacheObject.setTimeToCheckVersion(System.currentTimeMillis() + getTimeToVersionCheck(global.typeConfig, global.cacheConfig)); // version matches, renew ttl collector.registerWeakHit(GlobalObjectCache.class, type, global.statisticsLevel); log("Cache (global): HIT with version check - getObject {} ({})", global.traceMiss, oid, @@ -1212,7 +1212,7 @@ private void clearQueryResultsGlobally(Class type, Str globalQueryCache.invokeAll(entry -> { QueryKey queryKey = entry.getKey(); all.incrementAndGet(); - if (change.mayAffect(queryKey, entry.getValue(), matchingRuleRegistry)) { + if (change.mayAffect(queryKey, entry.getValue().getResult(), matchingRuleRegistry)) { LOGGER.trace("Removing (from global cache) query for type={}, change={}: {}", type, change, queryKey.getQuery()); entry.remove(); removed.incrementAndGet(); @@ -1721,7 +1721,7 @@ private boolean hasVersionChanged(Class objectType, String } private boolean shouldCheckVersion(GlobalCacheObjectValue object) { - return object.getTimeToLive() < System.currentTimeMillis(); + return object.getTimeToCheckVersion() < System.currentTimeMillis(); } private PrismObject loadAndCacheObject(Class objectClass, String oid, @@ -1917,15 +1917,26 @@ public Collection getStateInformation() { rv.add(new SingleCacheStateInformationType(prismContext) .name(LocalObjectCache.class.getName()) .size(LocalObjectCache.getTotalSize(LOCAL_OBJECT_CACHE_INSTANCE))); - rv.add(new SingleCacheStateInformationType(prismContext) - .name(LocalQueryCache.class.getName()) - .size(LocalQueryCache.getTotalSize(LOCAL_QUERY_CACHE_INSTANCE))); rv.add(new SingleCacheStateInformationType(prismContext) .name(LocalVersionCache.class.getName()) .size(LocalVersionCache.getTotalSize(LOCAL_VERSION_CACHE_INSTANCE))); + rv.add(new SingleCacheStateInformationType(prismContext) + .name(LocalQueryCache.class.getName()) + .size(LocalQueryCache.getTotalSize(LOCAL_QUERY_CACHE_INSTANCE)) + .secondarySize(LocalQueryCache.getTotalCachedObjects(LOCAL_QUERY_CACHE_INSTANCE))); rv.addAll(globalObjectCache.getStateInformation()); rv.addAll(globalVersionCache.getStateInformation()); rv.addAll(globalQueryCache.getStateInformation()); return rv; } + + @Override + public void dumpContent() { + LocalObjectCache.dumpContent(LOCAL_OBJECT_CACHE_INSTANCE); + LocalVersionCache.dumpContent(LOCAL_VERSION_CACHE_INSTANCE); + LocalQueryCache.dumpContent(LOCAL_QUERY_CACHE_INSTANCE); + globalObjectCache.dumpContent(); + globalVersionCache.dumpContent(); + globalQueryCache.dumpContent(); + } } diff --git a/repo/repo-cache/src/test/java/com/evolveum/midpoint/repo/cache/CacheInvalidationPerformanceTest.java b/repo/repo-cache/src/test/java/com/evolveum/midpoint/repo/cache/CacheInvalidationPerformanceTest.java new file mode 100644 index 00000000000..984299a213e --- /dev/null +++ b/repo/repo-cache/src/test/java/com/evolveum/midpoint/repo/cache/CacheInvalidationPerformanceTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.repo.cache; + +import static com.evolveum.midpoint.prism.util.PrismTestUtil.displayCollection; + +import static com.evolveum.midpoint.prism.util.PrismTestUtil.getPrismContext; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import javax.annotation.PostConstruct; + +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.Test; +import org.xml.sax.SAXException; + +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.util.PrismTestUtil; +import com.evolveum.midpoint.schema.*; +import com.evolveum.midpoint.schema.constants.MidPointConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.test.util.AbstractSpringTest; +import com.evolveum.midpoint.test.util.InfraTestMixin; +import com.evolveum.midpoint.util.PrettyPrinter; +import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; + +/** + * Currently not a part of automated test suite. + */ +@ContextConfiguration(locations = { "classpath:ctx-repo-cache-test.xml" }) +public class CacheInvalidationPerformanceTest extends AbstractSpringTest implements InfraTestMixin { + + private static final String CLASS_DOT = CacheInvalidationPerformanceTest.class.getName() + "."; + + @Autowired RepositoryCache repositoryCache; + + @BeforeSuite + public void setup() throws SchemaException, SAXException, IOException { + PrettyPrinter.setDefaultNamespacePrefix(MidPointConstants.NS_MIDPOINT_PUBLIC_PREFIX); + PrismTestUtil.resetPrismContext(MidPointPrismContextFactory.FACTORY); + } + + @PostConstruct + public void initialize() throws SchemaException, ObjectAlreadyExistsException { + OperationResult initResult = new OperationResult(CLASS_DOT + "setup"); + repositoryCache.postInit(initResult); + } + + @Test + public void test100InvalidationPerformance() throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { + + final int CACHED_SEARCHES = 10000; + + given(); + OperationResult result = createOperationResult(); + + // create the archetype - we should create reasonably sized object, as + ArchetypeType archetype = new ArchetypeType(getPrismContext()) + .name("name-initial") + .displayName("some display name") + .locality("some locality") + .costCenter("some cost center") + .beginActivation() + .administrativeStatus(ActivationStatusType.ENABLED) + .end(); + repositoryCache.addObject(archetype.asPrismObject(), null, result); + + modifyArchetypeName(archetype, "name-intermediate", "Initial modification duration", result); + modifyArchetypeName(archetype, "name", "Initial modification duration (repeated)", result); + + // fill-in cache with queries + for (int i = 0; i < CACHED_SEARCHES; i++) { + ObjectQuery query = getPrismContext().queryFor(ArchetypeType.class) + .item(ArchetypeType.F_NAME).eq(PolyString.fromOrig("name-" + i)).matchingOrig() + .or().item(ArchetypeType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS).eq(ActivationStatusType.ARCHIVED) + .or().item(ArchetypeType.F_COST_CENTER).eq("cc100").matchingCaseIgnore() + .build(); + repositoryCache.searchObjects(ArchetypeType.class, query, null, result); + } + + repositoryCache.dumpContent(); + Collection stateInformation = repositoryCache.getStateInformation(); + displayCollection("cache state information", stateInformation); + + when(); + modifyArchetypeName(archetype, "name-0", "Second modification duration (with cached searches)", result); + + then(); + } + + private void modifyArchetypeName(ArchetypeType archetype, String name, String label, OperationResult result) + throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { + List> itemDeltas = getPrismContext().deltaFor(ArchetypeType.class) + .item(ArchetypeType.F_NAME) + .replace(PolyString.fromOrig(name)) + .asItemDeltas(); + + long start = System.currentTimeMillis(); + repositoryCache.modifyObject(ArchetypeType.class, archetype.getOid(), itemDeltas, result); + long duration = System.currentTimeMillis() - start; + displayValue(label, duration); + } +} diff --git a/repo/repo-cache/src/test/resources/logback-test.xml b/repo/repo-cache/src/test/resources/logback-test.xml index cde591929fc..2163ef393d1 100644 --- a/repo/repo-cache/src/test/resources/logback-test.xml +++ b/repo/repo-cache/src/test/resources/logback-test.xml @@ -15,7 +15,7 @@ - + diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/SystemConfigurationCacheableAdapter.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/SystemConfigurationCacheableAdapter.java index b1e4896040a..b3327950b81 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/SystemConfigurationCacheableAdapter.java +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/SystemConfigurationCacheableAdapter.java @@ -66,4 +66,9 @@ public void invalidate(Class type, String oid, CacheInvalidationContext conte public Collection getStateInformation() { return Collections.emptySet(); } + + @Override + public void dumpContent() { + // nothing to do here + } } diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java index 3cfbacc5066..a5fc736084f 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java @@ -210,4 +210,9 @@ public Collection getStateInformation() { .size(cache.size()) ); } + + @Override + public void dumpContent() { + // Implement eventually + } } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractHigherUnitTest.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractHigherUnitTest.java index 491f25722f0..db46faf68f0 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractHigherUnitTest.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractHigherUnitTest.java @@ -229,8 +229,8 @@ public QName getName() { } @Override - public boolean isSupported(QName xsdType) { - return nameMatchingRule.isSupported(xsdType); + public boolean supports(QName xsdType) { + return nameMatchingRule.supports(xsdType); } @Override 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 4934230d87d..53f1dac46f0 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 @@ -964,8 +964,8 @@ public QName getName() { } @Override - public boolean isSupported(QName xsdType) { - return nameMatchingRule.isSupported(xsdType); + public boolean supports(QName xsdType) { + return nameMatchingRule.supports(xsdType); } @Override