/* * Copyright (c) 2010-2014 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.wf.impl.processors.primary.aspect; import com.evolveum.midpoint.model.api.context.ModelContext; 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.ExpressionVariables; import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DOMUtil; 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.wf.impl.tasks.WfTaskUtil; import com.evolveum.midpoint.wf.impl.messages.ProcessEvent; import com.evolveum.midpoint.schema.ObjectTreeDeltas; import com.evolveum.midpoint.wf.impl.processors.primary.PcpWfTask; import com.evolveum.midpoint.wf.util.ApprovalUtils; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.velocity.util.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.xml.namespace.QName; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; /** * @author mederly */ @Component public class PrimaryChangeAspectHelper { private static final Trace LOGGER = TraceManager.getTrace(PrimaryChangeAspectHelper.class); @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; @Autowired private WfTaskUtil wfTaskUtil; @Autowired private PrismContext prismContext; @Autowired private ExpressionFactory expressionFactory; //region ========================================================================== Jobs-related methods //endregion //region ========================================================================== Default implementation of aspect methods /** * Prepares deltaOut from deltaIn, based on process instance variables. * (Default implementation of the method from PrimaryChangeAspect.) * * In the default case, mapping deltaIn -> deltaOut is extremely simple. * DeltaIn contains a delta that has to be approved. Workflow answers simply yes/no. * Therefore, we either copy DeltaIn to DeltaOut, or generate an empty list of modifications. */ public ObjectTreeDeltas prepareDeltaOut(ProcessEvent event, PcpWfTask pcpJob, OperationResult result) throws SchemaException { ObjectTreeDeltas deltaIn = pcpJob.retrieveDeltasToProcess(); if (ApprovalUtils.isApprovedFromUri(event.getOutcome())) { return deltaIn; } else { return null; } } //endregion public ShadowType resolveTargetUnchecked(ShadowAssociationType association, OperationResult result) throws SchemaException, ObjectNotFoundException { if (association == null) { return null; } ObjectReferenceType shadowRef = association.getShadowRef(); if (shadowRef == null || shadowRef.getOid() == null) { throw new IllegalStateException("None or null-OID shadowRef in " + association); } PrismObject<ShadowType> shadow = shadowRef.asReferenceValue().getObject(); if (shadow == null) { Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection( GetOperationOptions.createNoFetch()); shadow = repositoryService.getObject(ShadowType.class, shadowRef.getOid(), options, result); shadowRef.asReferenceValue().setObject(shadow); } return shadow.asObjectable(); } public <T extends ObjectType> T resolveTargetRefUnchecked(AssignmentType a, OperationResult result) throws SchemaException, ObjectNotFoundException { if (a == null) { return null; } ObjectType object = a.getTarget(); if (object == null) { if (a.getTargetRef() == null || a.getTargetRef().getOid() == null) { return null; } Class<? extends ObjectType> clazz = null; if (a.getTargetRef().getType() != null) { clazz = prismContext.getSchemaRegistry().determineCompileTimeClass(a.getTargetRef().getType()); } if (clazz == null) { clazz = ObjectType.class; } object = repositoryService.getObject(clazz, a.getTargetRef().getOid(), null, result).asObjectable(); a.setTarget(object); } return (T) object; } public <T extends ObjectType> T resolveTargetRef(AssignmentType a, OperationResult result) { try { return resolveTargetRefUnchecked(a, result); } catch (SchemaException|ObjectNotFoundException e) { throw new SystemException(e); } } public ResourceType resolveResourceRef(AssignmentType a, OperationResult result) { if (a == null) { return null; } if (a.getConstruction() == null) { return null; } if (a.getConstruction().getResourceRef() == null) { return null; } try { return repositoryService.getObject(ResourceType.class, a.getConstruction().getResourceRef().getOid(), null, result).asObjectable(); } catch (ObjectNotFoundException|SchemaException e) { throw new SystemException(e); } } public <T extends ObjectType> T resolveTargetRef(ObjectReferenceType referenceType, Class<T> defaultObjectType, OperationResult result) { if (referenceType == null) { return null; } try { Class<? extends ObjectType> clazz = null; if (referenceType.getType() != null) { clazz = prismContext.getSchemaRegistry().determineCompileTimeClass(referenceType.getType()); } if (clazz == null) { clazz = defaultObjectType; } return (T) repositoryService.getObject(clazz, referenceType.getOid(), null, result).asObjectable(); } catch (ObjectNotFoundException|SchemaException e) { throw new SystemException(e); } } public void resolveRolesAndOrgUnits(PrismObject<UserType> user, OperationResult result) { for (AssignmentType assignmentType : user.asObjectable().getAssignment()) { if (assignmentType.getTargetRef() != null && assignmentType.getTarget() == null) { QName type = assignmentType.getTargetRef().getType(); if (RoleType.COMPLEX_TYPE.equals(type) || OrgType.COMPLEX_TYPE.equals(type)) { String oid = assignmentType.getTargetRef().getOid(); try { PrismObject<ObjectType> o = repositoryService.getObject(ObjectType.class, oid, null, result); assignmentType.setTarget(o.asObjectable()); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Resolved {} to {} in {}", new Object[]{oid, o, user}); } } catch (ObjectNotFoundException e) { LoggingUtils.logException(LOGGER, "Couldn't resolve reference to {} in {}", e, oid, user); } catch (SchemaException e) { LoggingUtils.logUnexpectedException(LOGGER, "Couldn't resolve reference to {} in {}", e, oid, user); } } } } } public boolean isEnabled(PrimaryChangeProcessorConfigurationType processorConfigurationType, PrimaryChangeAspect aspect) { if (processorConfigurationType == null) { return aspect.isEnabledByDefault(); } PcpAspectConfigurationType aspectConfigurationType = getPcpAspectConfigurationType(processorConfigurationType, aspect); // result may be null return isEnabled(aspectConfigurationType, aspect.isEnabledByDefault()); } public PcpAspectConfigurationType getPcpAspectConfigurationType(WfConfigurationType wfConfigurationType, PrimaryChangeAspect aspect) { if (wfConfigurationType == null) { return null; } return getPcpAspectConfigurationType(wfConfigurationType.getPrimaryChangeProcessor(), aspect); } public PcpAspectConfigurationType getPcpAspectConfigurationType(PrimaryChangeProcessorConfigurationType processorConfigurationType, PrimaryChangeAspect aspect) { if (processorConfigurationType == null) { return null; } String aspectName = aspect.getBeanName(); String getterName = "get" + StringUtils.capitalizeFirstLetter(aspectName); Object aspectConfigurationObject; try { Method getter = processorConfigurationType.getClass().getDeclaredMethod(getterName); try { aspectConfigurationObject = getter.invoke(processorConfigurationType); } catch (IllegalAccessException|InvocationTargetException e) { throw new SystemException("Couldn't obtain configuration for aspect " + aspectName + " from the workflow configuration.", e); } if (aspectConfigurationObject != null) { return (PcpAspectConfigurationType) aspectConfigurationObject; } LOGGER.trace("Specific configuration for {} not found, trying generic configuration", aspectName); } catch (NoSuchMethodException e) { // nothing wrong with this, let's try generic configuration LOGGER.trace("Configuration getter method for {} not found, trying generic configuration", aspectName); } for (GenericPcpAspectConfigurationType genericConfig : processorConfigurationType.getOtherAspect()) { if (aspectName.equals(genericConfig.getName())) { return genericConfig; } } return null; } private boolean isEnabled(PcpAspectConfigurationType configurationType, boolean enabledByDefault) { if (configurationType == null) { return enabledByDefault; } else if (Boolean.FALSE.equals(configurationType.isEnabled())) { return false; } else { return true; } } public boolean isUserRelated(ModelContext<? extends ObjectType> context) { return isRelatedToType(context, UserType.class); } public boolean isRelatedToType(ModelContext<? extends ObjectType> context, Class<?> type) { Class<? extends ObjectType> focusClass = getFocusClass(context); if (focusClass == null) { return false; } return type.isAssignableFrom(focusClass); } public Class<? extends ObjectType> getFocusClass(ModelContext<? extends ObjectType> context) { if (context.getFocusClass() != null) { return context.getFocusClass(); } // if for some reason context.focusClass is not set... here is a fallback if (context.getFocusContext() == null) { return null; } ObjectDelta<? extends ObjectType> change = context.getFocusContext().getPrimaryDelta(); if (change == null) { return null; } return change.getObjectTypeClass(); } public boolean hasApproverInformation(PcpAspectConfigurationType config) { return config != null && (!config.getApproverRef().isEmpty() || !config.getApproverExpression().isEmpty() || config.getApprovalSchema() != null); } //endregion //region ========================================================================== Expression evaluation public boolean evaluateApplicabilityCondition(PcpAspectConfigurationType config, ModelContext modelContext, Serializable itemToApprove, ExpressionVariables additionalVariables, PrimaryChangeAspect aspect, Task task, OperationResult result) { if (config == null || config.getApplicabilityCondition() == null) { return true; } ExpressionType expressionType = config.getApplicabilityCondition(); QName resultName = new QName(SchemaConstants.NS_C, "result"); PrismPropertyDefinition<Boolean> resultDef = new PrismPropertyDefinitionImpl(resultName, DOMUtil.XSD_BOOLEAN, prismContext); ExpressionVariables expressionVariables = new ExpressionVariables(); expressionVariables.addVariableDefinition(SchemaConstants.C_MODEL_CONTEXT, modelContext); expressionVariables.addVariableDefinition(SchemaConstants.C_ITEM_TO_APPROVE, itemToApprove); if (additionalVariables != null) { expressionVariables.addVariableDefinitions(additionalVariables); } PrismValueDeltaSetTriple<PrismPropertyValue<Boolean>> exprResultTriple; try { Expression<PrismPropertyValue<Boolean>,PrismPropertyDefinition<Boolean>> expression = expressionFactory.makeExpression(expressionType, resultDef, "applicability condition expression", task, result); ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, "applicability condition expression", task, result); exprResultTriple = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, params, task, result); } catch (SchemaException|ExpressionEvaluationException|ObjectNotFoundException|RuntimeException e) { // TODO report as a specific exception? throw new SystemException("Couldn't evaluate applicability condition in aspect " + aspect.getClass().getSimpleName() + ": " + e.getMessage(), e); } Collection<PrismPropertyValue<Boolean>> exprResult = exprResultTriple.getZeroSet(); if (exprResult.size() == 0) { return false; } else if (exprResult.size() > 1) { throw new IllegalStateException("Applicability condition expression should return exactly one boolean value; it returned " + exprResult.size() + " ones"); } Boolean boolResult = exprResult.iterator().next().getValue(); return boolResult != null ? boolResult : false; } //endregion }