/* * Copyright (c) 2010-2015 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.evolveum.midpoint.model.impl.sync; import java.util.ArrayList; import java.util.Collection; import java.util.List; import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import org.apache.commons.lang.Validate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import com.evolveum.midpoint.model.common.expression.Expression; import com.evolveum.midpoint.model.common.expression.ExpressionEvaluationContext; import com.evolveum.midpoint.model.common.expression.ExpressionFactory; import com.evolveum.midpoint.model.common.expression.ExpressionUtil; import com.evolveum.midpoint.model.common.expression.ExpressionVariables; import com.evolveum.midpoint.model.impl.util.Utils; import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.QueryJaxbConvertor; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.SchemaDebugUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.PrettyPrinter; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConditionalSearchFilterType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectSynchronizationType; 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.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; import com.evolveum.prism.xml.ns._public.query_3.PagingType; import java.util.HashSet; import java.util.Set; @Component public class CorrelationConfirmationEvaluator { private static transient Trace LOGGER = TraceManager.getTrace(CorrelationConfirmationEvaluator.class); @Autowired(required = true) @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; @Autowired(required = true) private PrismContext prismContext; @Autowired(required = true) private ExpressionFactory expressionFactory; @Autowired(required = true) private MatchingRuleRegistry matchingRuleRegistry; public <F extends FocusType> List<PrismObject<F>> findFocusesByCorrelationRule(Class<F> focusType, ShadowType currentShadow, List<ConditionalSearchFilterType> conditionalFilters, ResourceType resourceType, SystemConfigurationType configurationType, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { if (conditionalFilters == null || conditionalFilters.isEmpty()) { LOGGER.warn("Correlation rule for resource '{}' doesn't contain query, " + "returning empty list of users.", resourceType); return null; } List<PrismObject<F>> users = null; if (conditionalFilters.size() == 1){ if (satisfyCondition(currentShadow, conditionalFilters.get(0), resourceType, configurationType, "Condition expression", task, result)){ LOGGER.trace("Condition {} in correlation expression evaluated to true", conditionalFilters.get(0).getCondition()); users = findUsersByCorrelationRule(focusType, currentShadow, conditionalFilters.get(0), resourceType, configurationType, task, result); } } else { for (ConditionalSearchFilterType conditionalFilter : conditionalFilters) { //TODO: better description if (satisfyCondition(currentShadow, conditionalFilter, resourceType, configurationType, "Condition expression", task, result)) { LOGGER.trace("Condition {} in correlation expression evaluated to true", conditionalFilter.getCondition()); List<PrismObject<F>> foundUsers = findUsersByCorrelationRule(focusType, currentShadow, conditionalFilter, resourceType, configurationType, task, result); if (foundUsers == null && users == null) { continue; } if (foundUsers != null && foundUsers.isEmpty() && users == null) { users = new ArrayList<PrismObject<F>>(); } if (users == null && foundUsers != null) { users = foundUsers; } if (users != null && !users.isEmpty() && foundUsers != null && !foundUsers.isEmpty()) { for (PrismObject<F> foundUser : foundUsers) { if (!contains(users, foundUser)) { users.add(foundUser); } } } } } } if (users != null) { LOGGER.debug( "SYNCHRONIZATION: CORRELATION: expression for {} returned {} users: {}", new Object[] { currentShadow, users.size(), PrettyPrinter.prettyPrint(users, 3) }); if (users.size() > 1) { // remove duplicates Set<PrismObject<F>> usersWithoutDups = new HashSet<>(); usersWithoutDups.addAll(users); users.clear(); users.addAll(usersWithoutDups); LOGGER.debug("SYNCHRONIZATION: CORRELATION: found {} users without duplicates", users.size()); } } return users; } private boolean satisfyCondition(ShadowType currentShadow, ConditionalSearchFilterType conditionalFilter, ResourceType resourceType, SystemConfigurationType configurationType, String shortDesc, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { if (conditionalFilter.getCondition() == null){ return true; } ExpressionType condition = conditionalFilter.getCondition(); ExpressionVariables variables = Utils.getDefaultExpressionVariables(null,currentShadow, resourceType, configurationType); ItemDefinition outputDefinition = new PrismPropertyDefinitionImpl( ExpressionConstants.OUTPUT_ELEMENT_NAME, DOMUtil.XSD_BOOLEAN, prismContext); PrismPropertyValue<Boolean> satisfy = (PrismPropertyValue) ExpressionUtil.evaluateExpression(variables, outputDefinition, condition, expressionFactory, shortDesc, task, parentResult); if (satisfy.getValue() == null) { return false; } return satisfy.getValue(); } private <F extends FocusType> boolean contains(List<PrismObject<F>> users, PrismObject<F> foundUser){ for (PrismObject<F> user : users){ if (user.getOid().equals(foundUser.getOid())){ return true; } } return false; } private <F extends FocusType> List<PrismObject<F>> findUsersByCorrelationRule(Class<F> focusType, ShadowType currentShadow, ConditionalSearchFilterType conditionalFilter, ResourceType resourceType, SystemConfigurationType configurationType, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException{ if (!conditionalFilter.containsFilterClause()) { LOGGER.warn("Correlation rule for resource '{}' doesn't contain filter clause, " + "returning empty list of users.", resourceType); return null; } ObjectQuery q; try { q = QueryJaxbConvertor.createObjectQuery(focusType, conditionalFilter, prismContext); q = updateFilterWithAccountValues(currentShadow, resourceType, configurationType, q, "Correlation expression", task, result); if (q == null) { // Null is OK here, it means that the value in the filter // evaluated // to null and the processing should be skipped return null; } } catch (SchemaException ex) { LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, SchemaDebugUtil.prettyPrint(conditionalFilter)); throw new SchemaException("Couldn't convert query.", ex); } catch (ObjectNotFoundException ex) { LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, SchemaDebugUtil.prettyPrint(conditionalFilter)); throw new ObjectNotFoundException("Couldn't convert query.", ex); } catch (ExpressionEvaluationException ex) { LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, SchemaDebugUtil.prettyPrint(conditionalFilter)); throw new ExpressionEvaluationException("Couldn't convert query.", ex); } List<PrismObject<F>> users; try { LOGGER.trace("SYNCHRONIZATION: CORRELATION: expression for results in filter\n{}", q.debugDumpLazily()); users = repositoryService.searchObjects(focusType, q, null, result); } catch (RuntimeException ex) { LoggingUtils.logException(LOGGER, "Couldn't search users in repository, based on filter (simplified)\n{}.", ex, q.debugDump()); throw new SystemException( "Couldn't search users in repository, based on filter (See logs).", ex); } return users; } private <F extends FocusType> boolean matchUserCorrelationRule(Class<F> focusType, PrismObject<ShadowType> currentShadow, PrismObject<F> userType, ResourceType resourceType, SystemConfigurationType configurationType, ConditionalSearchFilterType conditionalFilter, Task task, OperationResult result) throws SchemaException { if (conditionalFilter == null) { LOGGER.warn("Correlation rule for resource '{}' doesn't contain query, " + "returning empty list of users.", resourceType); return false; } if (!conditionalFilter.containsFilterClause()) { LOGGER.warn("Correlation rule for resource '{}' doesn't contain a filter, " + "returning empty list of users.", resourceType); return false; } ObjectQuery q; try { q = QueryJaxbConvertor.createObjectQuery(focusType, conditionalFilter, prismContext); q = updateFilterWithAccountValues(currentShadow.asObjectable(), resourceType, configurationType, q, "Correlation expression", task, result); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Start matching user {} with correlation expression {}", userType, q != null ? q.debugDump() : "(null)"); } if (q == null) { // Null is OK here, it means that the value in the filter evaluated // to null and the processing should be skipped return false; } } catch (Exception ex) { LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, SchemaDebugUtil.prettyPrint(conditionalFilter)); throw new SystemException("Couldn't convert query.", ex); } if (LOGGER.isTraceEnabled()) { LOGGER.trace("SYNCHRONIZATION: CORRELATION: expression for {} results in filter\n{}", currentShadow, q); } // we assume userType is already normalized w.r.t. relations ObjectTypeUtil.normalizeFilter(q.getFilter()); return ObjectQuery.match(userType, q.getFilter(), matchingRuleRegistry); } public <F extends FocusType> boolean matchUserCorrelationRule(Class<F> focusType, PrismObject<ShadowType> currentShadow, PrismObject<F> userType, ObjectSynchronizationType synchronization, ResourceType resourceType, SystemConfigurationType configurationType, Task task, OperationResult result){ if (synchronization == null){ LOGGER.warn( "Resource does not support synchronization. Skipping evaluation correlation/confirmation for {} and {}", userType, currentShadow); return false; } List<ConditionalSearchFilterType> conditionalFilters = synchronization.getCorrelation(); try { for (ConditionalSearchFilterType conditionalFilter : conditionalFilters) { if (matchUserCorrelationRule(focusType, currentShadow, userType, resourceType, configurationType, conditionalFilter, task, result)){ LOGGER.debug("SYNCHRONIZATION: CORRELATION: expression for {} match user: {}", currentShadow, userType); return true; } } } catch (SchemaException ex){ throw new SystemException("Failed to match user using correlation rule. " + ex.getMessage(), ex); } LOGGER.debug("SYNCHRONIZATION: CORRELATION: expression for {} does not match user: {}", new Object[] { currentShadow, userType }); return false; } public <F extends FocusType> List<PrismObject<F>> findUserByConfirmationRule(Class<F> focusType, List<PrismObject<F>> users, ShadowType currentShadow, ResourceType resource, SystemConfigurationType configuration, ExpressionType expression, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { List<PrismObject<F>> list = new ArrayList<PrismObject<F>>(); for (PrismObject<F> user : users) { try { F userType = user.asObjectable(); boolean confirmedUser = evaluateConfirmationExpression(focusType, userType, currentShadow, resource, configuration, expression, task, result); if (confirmedUser) { list.add(user); } } catch (RuntimeException ex) { LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); throw new SystemException("Couldn't confirm user " + user.getElementName(), ex); } catch (ExpressionEvaluationException ex) { LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); throw new ExpressionEvaluationException("Couldn't confirm user " + user.getElementName(), ex); } catch (ObjectNotFoundException ex) { LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); throw new ObjectNotFoundException("Couldn't confirm user " + user.getElementName(), ex); } catch (SchemaException ex) { LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); throw new SchemaException("Couldn't confirm user " + user.getElementName(), ex); } } LOGGER.debug("SYNCHRONIZATION: CONFIRMATION: expression for {} matched {} users.", new Object[] { currentShadow, list.size() }); return list; } private ObjectQuery updateFilterWithAccountValues(ShadowType currentShadow, ResourceType resource, SystemConfigurationType configuration, ObjectQuery origQuery, String shortDesc, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { if (origQuery.getFilter() == null) { LOGGER.trace("No filter provided, skipping updating filter"); return origQuery; } return evaluateQueryExpressions(origQuery, currentShadow, resource, configuration, shortDesc, task, result); } private ObjectQuery evaluateQueryExpressions(ObjectQuery query, ShadowType currentShadow, ResourceType resource, SystemConfigurationType configuration, String shortDesc, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { ExpressionVariables variables = Utils.getDefaultExpressionVariables(null, currentShadow, resource, configuration); return ExpressionUtil.evaluateQueryExpressions(query, variables, expressionFactory, prismContext, shortDesc, task, result); } public <F extends FocusType> boolean evaluateConfirmationExpression(Class<F> focusType, F user, ShadowType shadow, ResourceType resource, SystemConfigurationType configuration, ExpressionType expressionType, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { Validate.notNull(user, "User must not be null."); Validate.notNull(shadow, "Resource object shadow must not be null."); Validate.notNull(expressionType, "Expression must not be null."); Validate.notNull(result, "Operation result must not be null."); ExpressionVariables variables = Utils.getDefaultExpressionVariables(user, shadow, resource, configuration); String shortDesc = "confirmation expression for "+resource.asPrismObject(); PrismPropertyDefinition<Boolean> outputDefinition = new PrismPropertyDefinitionImpl<>(ExpressionConstants.OUTPUT_ELEMENT_NAME, DOMUtil.XSD_BOOLEAN, prismContext); Expression<PrismPropertyValue<Boolean>,PrismPropertyDefinition<Boolean>> expression = expressionFactory.makeExpression(expressionType, outputDefinition, shortDesc, task, result); ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task, result); PrismValueDeltaSetTriple<PrismPropertyValue<Boolean>> outputTriple = ModelExpressionThreadLocalHolder .evaluateExpressionInContext(expression, params, task, result); Collection<PrismPropertyValue<Boolean>> nonNegativeValues = outputTriple.getNonNegativeValues(); if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); } if (nonNegativeValues.size() > 1) { throw new ExpressionEvaluationException("Expression returned more than one value ("+nonNegativeValues.size()+") in "+shortDesc); } PrismPropertyValue<Boolean> resultpval = nonNegativeValues.iterator().next(); if (resultpval == null) { throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); } Boolean resultVal = resultpval.getValue(); if (resultVal == null) { throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); } return resultVal; } }