/* * Copyright (c) 2010-2013 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; import com.evolveum.midpoint.model.api.ProgressInformation; import com.evolveum.midpoint.model.api.context.ModelContext; import com.evolveum.midpoint.model.api.hooks.ChangeHook; import com.evolveum.midpoint.model.api.hooks.HookOperationMode; import com.evolveum.midpoint.model.api.hooks.HookRegistry; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; 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.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; 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.processors.BaseConfigurationHelper; import com.evolveum.midpoint.wf.impl.processors.ChangeProcessor; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.WfConfigurationType; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.WORKFLOWS; import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.ENTERING; /** * Provides an interface between the model and the workflow engine: * catches hook calls and delegates them to change processors. * * @author mederly */ @Component public class WfHook implements ChangeHook { private static final Trace LOGGER = TraceManager.getTrace(WfHook.class); private static final String WORKFLOW_HOOK_URI = "http://midpoint.evolveum.com/model/workflow-hook-1"; // todo @Autowired private WfConfiguration wfConfiguration; @Autowired private BaseConfigurationHelper baseConfigurationHelper; @Autowired private HookRegistry hookRegistry; private static final String DOT_CLASS = WfHook.class.getName() + "."; private static final String OPERATION_INVOKE = DOT_CLASS + "invoke"; @PostConstruct public void init() { if (wfConfiguration.isEnabled()) { hookRegistry.registerChangeHook(WfHook.WORKFLOW_HOOK_URI, this); LOGGER.info("Workflow change hook was registered."); } else { LOGGER.info("Workflow change hook is not registered, because workflows are disabled."); } } @Override public <O extends ObjectType> HookOperationMode invoke(@NotNull ModelContext<O> context, @NotNull Task taskFromModel, @NotNull OperationResult parentResult) { Validate.notNull(context); Validate.notNull(taskFromModel); Validate.notNull(parentResult); // Generally this cannot be minor as we need the "task switched to background" flag. // But if the hook does nothing (returns FOREGROUND flag), we mark the result // as minor afterwards. OperationResult result = parentResult.createSubresult(OPERATION_INVOKE); result.addParam("taskFromModel", taskFromModel.toString()); result.addContext("model state", context.getState()); try { WfConfigurationType wfConfigurationType = baseConfigurationHelper.getWorkflowConfiguration(context, result); // TODO consider this if it's secure enough if (wfConfigurationType != null && Boolean.FALSE.equals(wfConfigurationType.isModelHookEnabled())) { LOGGER.info("Workflow model hook is disabled. Proceeding with operation execution as if everything is approved."); result.recordSuccess(); return HookOperationMode.FOREGROUND; } if (SchemaConstants.CHANNEL_GUI_INIT_URI.equals(context.getChannel())) { LOGGER.debug("Skipping workflow processing because the channel is '" + SchemaConstants.CHANNEL_GUI_INIT_URI + "'."); result.recordSuccess(); return HookOperationMode.FOREGROUND; } logOperationInformation(context); HookOperationMode retval = processModelInvocation(context, wfConfigurationType, taskFromModel, result); result.computeStatus(); if (retval == HookOperationMode.FOREGROUND) { result.setMinor(true); } return retval; } catch (RuntimeException e) { result.recordFatalError("Couldn't process model invocation in workflow module: " + e.getMessage(), e); throw e; } } @Override public void invokeOnException(@NotNull ModelContext context, @NotNull Throwable throwable, @NotNull Task task, @NotNull OperationResult result) { // do nothing } private void logOperationInformation(ModelContext context) { if (LOGGER.isTraceEnabled()) { @SuppressWarnings({"unchecked", "raw"}) LensContext<?> lensContext = (LensContext<?>) context; try { LensUtil.traceContext(LOGGER, "WORKFLOW (" + context.getState() + ")", "workflow processing", true, lensContext, false); } catch (SchemaException e) { throw new IllegalStateException("SchemaException when tracing model context: " + e.getMessage(), e); } } } private HookOperationMode processModelInvocation(@NotNull ModelContext<? extends ObjectType> modelContext, WfConfigurationType wfConfigurationType, @NotNull Task taskFromModel, @NotNull OperationResult result) { try { modelContext.reportProgress(new ProgressInformation(WORKFLOWS, ENTERING)); for (ChangeProcessor changeProcessor : wfConfiguration.getChangeProcessors()) { LOGGER.trace("Trying change processor: {}", changeProcessor.getClass().getName()); try { HookOperationMode hookOperationMode = changeProcessor.processModelInvocation(modelContext, wfConfigurationType, taskFromModel, result); if (hookOperationMode != null) { return hookOperationMode; } } catch (ObjectNotFoundException|SchemaException|RuntimeException e) { LoggingUtils.logUnexpectedException(LOGGER, "Exception while running change processor {}", e, changeProcessor.getClass().getName()); result.recordFatalError("Exception while running change processor " + changeProcessor.getClass(), e); return HookOperationMode.ERROR; } } } finally { if (result.isInProgress()) { // a bit of hack: IN_PROGRESS for workflows actually means "success" OperationResult r = result.clone(); r.recordSuccess(); modelContext.reportProgress(new ProgressInformation(WORKFLOWS, r)); } else { modelContext.reportProgress(new ProgressInformation(WORKFLOWS, result)); } } LOGGER.trace("No change processor caught this request, returning the FOREGROUND flag."); return HookOperationMode.FOREGROUND; } }