All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.edu.icm.unity.engine.attribute.EffectiveAttributesEvaluator Maven / Gradle / Ivy

/*
 * Copyright (c) 2013 ICM Uniwersytet Warszawski All rights reserved.
 * See LICENCE.txt file for licensing information.
 */
package pl.edu.icm.unity.engine.attribute;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
import org.mvel2.MVEL;

import pl.edu.icm.unity.base.utils.Log;
import pl.edu.icm.unity.engine.api.AttributeValueConverter;
import pl.edu.icm.unity.engine.api.attributes.AttributeClassHelper;
import pl.edu.icm.unity.engine.api.attributes.AttributeStatementMVELContextKey;
import pl.edu.icm.unity.engine.api.attributes.AttributeValueSyntax;
import pl.edu.icm.unity.engine.api.mvel.MVELGroup;
import pl.edu.icm.unity.exceptions.IllegalAttributeValueException;
import pl.edu.icm.unity.types.basic.Attribute;
import pl.edu.icm.unity.types.basic.AttributeExt;
import pl.edu.icm.unity.types.basic.AttributeStatement;
import pl.edu.icm.unity.types.basic.AttributeStatement.ConflictResolution;
import pl.edu.icm.unity.types.basic.AttributeStatement.Direction;
import pl.edu.icm.unity.types.basic.AttributeType;
import pl.edu.icm.unity.types.basic.AttributesClass;
import pl.edu.icm.unity.types.basic.Group;
import pl.edu.icm.unity.types.basic.Identity;

/**
 * One-time-use class which computes effective attributes of a given entity in a given group.
 * Do not access any DAO directly so that can be used in various caching scenarios. 
 */
class EffectiveAttributesEvaluator
{
	private static final Logger log = Log.getLogger(Log.U_SERVER_CORE, EffectiveAttributesEvaluator.class);

	private final AttributeTypeHelper atHelper;
	private final AttributeValueConverter attrConverter;
	private final List identities;
	private final Collection allUserGroups;
	private final Map> directAttributesByGroup;
	private final Map knownClasses;
	private final Function groupInfoProvider;
	private final Function attrTypeProvider;
	
	private final List mvelContextGroupPaths;

	private Map mvelContextGroupsMap;

	
	EffectiveAttributesEvaluator(AttributeTypeHelper atHelper, 
			AttributeValueConverter attrConverter,
			List identities,  
			Collection allUserGroups, 
			Map> directAttributesByGroup,
			Map knownClasses,
			Function groupInfoProvider,
			Function attrTypeProvider,
			Function mvelGroupProvider)
	{
		this.atHelper = atHelper;
		this.attrConverter = attrConverter;
		this.identities = identities;
		this.allUserGroups = allUserGroups;
		this.directAttributesByGroup = directAttributesByGroup;
		this.knownClasses = knownClasses;
		this.groupInfoProvider = groupInfoProvider;
		this.attrTypeProvider = attrTypeProvider;
		
		mvelContextGroupsMap = new HashMap<>(allUserGroups.size());
		for (Group group: allUserGroups)
		{
			String pathEncoded = group.getPathEncoded();
			mvelContextGroupsMap.put(pathEncoded, mvelGroupProvider.apply(pathEncoded));
		}
		mvelContextGroupPaths = new ArrayList<>(mvelContextGroupsMap.keySet());
	}

	/**
	 * Collects all attributes
	 * The algorithm is as follows:
	 * 
    *
  1. effective attributes are collected in all subgroups, which are mentioned in attribute statements * conditions based on subgroup attributes. This process is recursive, but all statements related to * parent groups are ignored. *
  2. effective attributes are collected in the parent group, if it is mentioned in at least one * attribute statement condition, based on parent group attributes. This process is recursive, * but all statements related to subgroups groups are ignored. *
  3. statements for this group are processed. For conditions evaluation data from the above steps * and method arguments is used. *
* @return collected attributes in a map form. Map keys are attribute names. */ Map getEffectiveAttributes(String group, String queriedAttribute) { Map> downwardsAttributes = new HashMap<>(); collectUpOrDownAttributes(Direction.downwards, group, null, downwardsAttributes); Map> upwardsAttributes = new HashMap<>(); collectUpOrDownAttributes(Direction.upwards, group, null, upwardsAttributes); AttributeStatement[] statements = getGroupStatements(group); Map fromStatemants = processAttributeStatements(Direction.undirected, upwardsAttributes, downwardsAttributes, group, queriedAttribute, statements); return "/".equals(group) ? fromStatemants : addGlobal(fromStatemants, queriedAttribute); } private Map addGlobal(Map fromStatemants, String queriedAttribute) { Map rootDirectAttributes = directAttributesByGroup.getOrDefault("/", Collections.emptyMap()); if (queriedAttribute != null && !rootDirectAttributes.containsKey(queriedAttribute)) return fromStatemants; Set interestingRootAttributes = queriedAttribute == null ? rootDirectAttributes.keySet() : Collections.singleton(queriedAttribute); interestingRootAttributes.stream() .filter(attribute -> attrTypeProvider.apply(attribute).isGlobal()) .filter(globalA -> !fromStatemants.keySet().contains(globalA)) .forEach(globalToAdd -> { AttributeExt cloned = new AttributeExt(rootDirectAttributes.get(globalToAdd)); cloned.setDirect(false); fromStatemants.put(globalToAdd, cloned); }); return fromStatemants; } /** * Resolves group path and returns group's attribute statements */ private AttributeStatement[] getGroupStatements(String group) { return groupInfoProvider.apply(group).getAttributeStatements(); } /** * Recursive method collecting attributes in down or up direction. Works as follows: *
    *
  1. statements of the group are established *
  2. for each statement which has condition related to an attribute in a other group in the * direction of the mode, the group is recorded to a set. *
  3. for each group from the set recursive call is made *
  4. normal processing of the statements of this group is performed, however only the input * for rules related to the groups in the mode is provided. Statements in opposite direction are ignored. *
*/ private void collectUpOrDownAttributes(Direction mode, String groupPath, String queriedAttribute, Map> upOrDownAttributes) { AttributeStatement[] statements = getGroupStatements(groupPath); Set interestingGroups = new HashSet<>(); for (AttributeStatement as: statements) { if (as.isSuitableForDirectedEvaluation(mode, groupPath) && isForInterestingAttribute(queriedAttribute, as)) { String groupPath2 = as.getExtraAttributesGroup(); if (groupPath2 != null) interestingGroups.add(groupPath2); } } for (String interestingGroup: interestingGroups) { if (!allUserGroups.stream().filter(g -> g.getPathEncoded().equals(interestingGroup)).findAny().isPresent()) continue; collectUpOrDownAttributes(mode, interestingGroup, queriedAttribute, upOrDownAttributes); } Map ret = (mode == Direction.upwards) ? processAttributeStatements(mode, upOrDownAttributes, null, groupPath, null, statements): processAttributeStatements(mode, null, upOrDownAttributes, groupPath, null, statements); upOrDownAttributes.put(groupPath, ret); } private boolean isForInterestingAttribute(String attribute, AttributeStatement as) { if (attribute == null) return true; String assigned = as.getAssignedAttributeName(); if (assigned == null || assigned.equals(attribute)) return true; return false; } private Map processAttributeStatements(Direction direction, Map> upwardsAttributesByGroup, Map> downwardsAttributesByGroup, String group, String queriedAttribute, AttributeStatement[] statements) { Map collectedAttributes = new HashMap<>(); Map regularAttributesInGroup = directAttributesByGroup.get(group); if (regularAttributesInGroup == null) regularAttributesInGroup = new HashMap<>(); AttributeExt acAttribute = null; if (queriedAttribute == null) { for (Map.Entry a: regularAttributesInGroup.entrySet()) collectedAttributes.put(a.getKey(), new AttributeExt(a.getValue())); } else { AttributeExt at = regularAttributesInGroup.get(queriedAttribute); if (at != null) collectedAttributes.put(queriedAttribute, new AttributeExt(at)); } acAttribute = regularAttributesInGroup.get(AttributeClassUtil.ATTRIBUTE_CLASSES_ATTRIBUTE); AttributeClassHelper acHelper = acAttribute == null ? new AttributeClassHelper() : new AttributeClassHelper(knownClasses, acAttribute.getValues()); int i=1; for (AttributeStatement as: statements) { Map extraAttributes = null; String extraAttributesGroup = as.getExtraAttributesGroup(); if (extraAttributesGroup != null) { if (extraAttributesGroup.startsWith(group) && downwardsAttributesByGroup != null) extraAttributes = downwardsAttributesByGroup.get(extraAttributesGroup); if (group.startsWith(extraAttributesGroup) && upwardsAttributesByGroup != null) extraAttributes = upwardsAttributesByGroup.get(extraAttributesGroup); } ThreadContext.push("[AttrStmnt " + i + " in " + group + " for entity " + identities.get(0).getEntityId() + "]"); try { processAttributeStatement(direction, group, as, queriedAttribute, collectedAttributes, regularAttributesInGroup, extraAttributes, acHelper); } catch (Exception e) { log.error("Error processing statement " + as + " is skipped", e); } finally { ThreadContext.pop(); } } return collectedAttributes; } /** * Checks all conditions. If all are true, then the attribute of the statement is added to the map. * In case when the attribute is already in the map, conflict resolution of the statement is taken into * account. */ @SuppressWarnings({ "rawtypes", "unchecked" }) private void processAttributeStatement(Direction direction, String group, AttributeStatement statement, String queriedAttribute, Map collectedAttributes, Map regularGroupAttributes, Map extraGroupAttributes, AttributeClassHelper acHelper) { //we are in the recursive process of establishing downwards or upwards attributes and the // statement is oppositely directed. if (direction != Direction.undirected && !statement.isSuitableForDirectedEvaluation(direction, group)) return; if (!acHelper.isAllowed(queriedAttribute)) return; if (!isForInterestingAttribute(queriedAttribute, statement)) return; Map context = createMvelContext(group, regularGroupAttributes, extraGroupAttributes); boolean condition = evaluateStatementCondition(statement, context); if (!condition) return; Attribute ret = statement.dynamicAttributeMode() ? evaluateStatementValue(statement, group, context, attrTypeProvider) : statement.getFixedAttribute(); if (ret == null) return; AttributeExt existing = collectedAttributes.get(ret.getName()); if (existing != null) { ConflictResolution resolution = statement.getConflictResolution(); switch (resolution) { case skip: log.trace("Conflict detected, skipping dynamic attribute"); return; case overwrite: if (!existing.isDirect()) { log.trace("Conflict detected, overwritting the previous dynamic attribute"); collectedAttributes.put(ret.getName(), new AttributeExt(ret, false)); } else { log.trace("Conflict detected, NOT overwritting the existing regular attribute"); } return; case merge: log.trace("Conflict detected, will try to merge values"); AttributeType at = attrTypeProvider.apply(ret.getName()); if (at.getMaxElements() == Integer.MAX_VALUE) { ((List)existing.getValues()).addAll(ret.getValues()); log.trace("Merge of values was performed"); } else { log.trace("Merge of values was skipped as " + "attribute type has a values number limit"); } return; } } else { collectedAttributes.put(ret.getName(), new AttributeExt(ret, false)); } } private boolean evaluateStatementCondition(AttributeStatement statement, Map context) { Boolean result = null; try { result = (Boolean) MVEL.executeExpression(statement.getCompiledCondition(), context, new HashMap<>()); } catch (Exception e) { log.warn("Error during attribute statement condition evaluation, condition '" + statement.getCondition() + "' is invalid. Skipping statement.\n" + e.toString()); if (log.isTraceEnabled()) log.trace("Full stack trace of the problematic attribute statement error", e); return false; } if (result == null) { log.debug("Condition evaluated to null value, assuming false"); return false; } if (log.isTraceEnabled()) { log.trace("Condition " + statement.getCondition() + " evaluated to " + result + " for " + context.get(AttributeStatementMVELContextKey.entityId.name()) + " in " + context.get(AttributeStatementMVELContextKey.groupName.name())); } return result.booleanValue(); } private Attribute evaluateStatementValue(AttributeStatement statement, String group, Map context, Function attrTypeProvider) { Object value; try { value = MVEL.executeExpression(statement.getCompiledDynamicAttributeExpression(), context, new HashMap<>()); } catch (Exception e) { log.warn("Error during attribute statement value evaluation, expression '" + statement.getDynamicAttributeExpression() + "' is invalid. Skipping statement.\n" + e.toString()); if (log.isTraceEnabled()) log.trace("Full stack trace of the problematic attribute statement error", e); return null; } if (value == null) { log.debug("Attribute value evaluated to null, skipping"); return null; } String attributeName = statement.getDynamicAttributeType(); AttributeType attributeType = attrTypeProvider.apply(attributeName); AttributeValueSyntax valueSyntax = atHelper.getSyntax(attributeType); List typedValues; try { typedValues = convertValues(value, valueSyntax); } catch (IllegalAttributeValueException e) { log.debug("Can't convert attribute values returned by the statement's expression " + "to the type of attribute " + attributeName + ", skipping it", e); return null; } Attribute attribute = new Attribute(attributeName, valueSyntax.getValueSyntaxId(), group, typedValues); if (log.isTraceEnabled()) { log.trace("Evaluated values of attribute " + attributeName + ":" + typedValues); } return attribute; } private static List convertValues(Object value, AttributeValueSyntax syntax) throws IllegalAttributeValueException { List aValues = value instanceof List ? (List)value : Collections.singletonList(value); List ret = new ArrayList<>(aValues.size()); for (Object o: aValues) { T converted = syntax.deserializeSimple(o.toString()); String converted2 = syntax.convertToString(converted); ret.add(converted2); } return ret; } private Map createMvelContext(String groupName, Map directAttributes, Map extraAttributes) { Map ret = new HashMap<>(); ret.put(AttributeStatementMVELContextKey.entityId.name(), identities.get(0).getEntityId()); ret.put(AttributeStatementMVELContextKey.groupName.name(), groupName); ret.put(AttributeStatementMVELContextKey.groups.name(), mvelContextGroupPaths); ret.put(AttributeStatementMVELContextKey.groupsObj.name(), mvelContextGroupsMap); Map> idsByType = new HashMap<>(); for (Identity id: identities) { List vals = idsByType.get(id.getTypeId()); if (vals == null) { vals = new ArrayList<>(); idsByType.put(id.getTypeId(), vals); } vals.add(id.getValue()); } ret.put(AttributeStatementMVELContextKey.idsByType.name(), idsByType); addAttributesToContext(directAttributes, AttributeStatementMVELContextKey.attrs, AttributeStatementMVELContextKey.attr, ret); if (extraAttributes != null) { addAttributesToContext(extraAttributes, AttributeStatementMVELContextKey.eattrs, AttributeStatementMVELContextKey.eattr, ret); } else { ret.put(AttributeStatementMVELContextKey.eattrs.name(), null); ret.put(AttributeStatementMVELContextKey.eattr.name(), null); } return ret; } private void addAttributesToContext(Map attributes, AttributeStatementMVELContextKey fullKey, AttributeStatementMVELContextKey oneValueKey, Map target) { Map attr = new HashMap<>(); Map> attrs = new HashMap<>(); for (AttributeExt a: attributes.values()) { List values = internalValuesToExternal(a.getValueSyntax(), a.getValues()); String v = values.isEmpty() ? "" : values.get(0); attr.put(a.getName(), v); attrs.put(a.getName(), values); } target.put(oneValueKey.name(), attr); target.put(fullKey.name(), attrs); } private List internalValuesToExternal(String valueSyntax, List internalValues) { AttributeValueSyntax syntax = atHelper.getUnconfiguredSyntax(valueSyntax); return attrConverter.internalValuesToExternal(syntax, internalValues); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy