001/* 002 * Copyright 2016 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.workflow; 017 018import java.util.Date; 019import java.util.HashMap; 020import java.util.LinkedHashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.concurrent.locks.ReentrantLock; 024 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028import org.ametys.plugins.workflow.store.AmetysStep; 029import org.ametys.plugins.workflow.store.AmetysWorkflowStore; 030import org.ametys.plugins.workflow.support.WorkflowHelper; 031 032import com.opensymphony.module.propertyset.PropertySet; 033import com.opensymphony.workflow.AbstractWorkflow; 034import com.opensymphony.workflow.StoreException; 035import com.opensymphony.workflow.WorkflowContext; 036import com.opensymphony.workflow.WorkflowException; 037import com.opensymphony.workflow.loader.ActionDescriptor; 038import com.opensymphony.workflow.loader.WorkflowDescriptor; 039import com.opensymphony.workflow.spi.Step; 040import com.opensymphony.workflow.spi.WorkflowEntry; 041import com.opensymphony.workflow.spi.WorkflowStore; 042 043/** 044 * Base Ametys class for manipulating workflow instances. 045 */ 046public abstract class AbstractAmetysWorkflow extends AbstractWorkflow 047{ 048 /** Logger available to subclasses. */ 049 protected static Logger _logger = LoggerFactory.getLogger(AbstractAmetysWorkflow.class); 050 051 /** Reentrant locks for synchronizing access to each instance */ 052 protected Map<Long, ReentrantLock> _instancesLocks = new HashMap<>(); 053 054 /** workflow helper */ 055 protected WorkflowHelper _workflowHelper; 056 057 /** 058 * Constructor 059 * @param workflowHelper The workflow helper 060 * @param workflowContext The workflow context 061 */ 062 protected AbstractAmetysWorkflow(WorkflowHelper workflowHelper, WorkflowContext workflowContext) 063 { 064 _workflowHelper = workflowHelper; 065 context = workflowContext; 066 067 _instancesLocks = new HashMap<>(); 068 } 069 070 /** 071 * Creates a new workflow instance of a particular type of workflow. 072 * @param workflowName the type of the workflow. 073 * @param initialActionId the initial action to perform or 074 * <code>Integer.MIN_VALUE</code> in order to use the first initial action. 075 * @param inputs the arguments to transmit to the workflow. Can not be null. 076 * @return the workflow instance id. 077 */ 078 @Override 079 public long initialize(String workflowName, int initialActionId, Map inputs) throws WorkflowException 080 { 081 int actionId = initialActionId; 082 083 if (initialActionId == Integer.MIN_VALUE) 084 { 085 actionId = _workflowHelper.getInitialAction(workflowName); 086 } 087 088 Map<String, Object> initInputs = inputs; 089 if (initInputs == null) 090 { 091 initInputs = new HashMap<>(); 092 } 093 094 if (!initInputs.containsKey(AbstractWorkflowComponent.RESULT_MAP_KEY)) 095 { 096 initInputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, new LinkedHashMap<>()); 097 } 098 099 // TODO delete workflow entry if initialize has failed 100 return super.initialize(workflowName, actionId, initInputs); 101 } 102 103 @Override 104 public int[] getAvailableActions(long id, Map inputs) 105 { 106 Map<String, Object> doInputs = inputs; 107 if (doInputs == null) 108 { 109 doInputs = new HashMap<>(); 110 } 111 112 if (!doInputs.containsKey(AbstractWorkflowComponent.RESULT_MAP_KEY)) 113 { 114 doInputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, new LinkedHashMap<>()); 115 } 116 117 return super.getAvailableActions(id, inputs); 118 } 119 120 @Override 121 @Deprecated 122 @SuppressWarnings("unchecked") 123 public int[] getAvailableActions(long id) 124 { 125 Map inputs = new HashMap(); 126 inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, new LinkedHashMap<>()); 127 return super.getAvailableActions(id, inputs); 128 } 129 130 @Override 131 public void doAction(long id, int actionId, Map inputs) throws WorkflowException 132 { 133 // Do not perform an action on the same workflow instance in the same time 134 ReentrantLock instanceLock = null; 135 136 // Récupérer le verrou sur l'instance 137 synchronized (_instancesLocks) 138 { 139 instanceLock = _instancesLocks.get(id); 140 141 // Check is a lock already exists 142 if (instanceLock == null) 143 { 144 instanceLock = new ReentrantLock(true); 145 // Store new lock 146 _instancesLocks.put(id, instanceLock); 147 } 148 } 149 150 try 151 { 152 Map<String, Object> doInputs = inputs; 153 if (doInputs == null) 154 { 155 doInputs = new HashMap<>(); 156 } 157 158 if (!doInputs.containsKey(AbstractWorkflowComponent.RESULT_MAP_KEY)) 159 { 160 doInputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, new LinkedHashMap<>()); 161 } 162 163 // Only one instance can perform an action on a given workflow instance 164 instanceLock.lock(); 165 super.doAction(id, actionId, doInputs); 166 } 167 finally 168 { 169 // Allow another thread to perform an action on this workflow instance 170 instanceLock.unlock(); 171 } 172 173 // Vérifier si l'instance du workflow est terminée 174 if (getEntryState(id) == WorkflowEntry.COMPLETED) 175 { 176 try 177 { 178 WorkflowStore store = getConfiguration().getWorkflowStore(); 179 180 if (store instanceof AmetysWorkflowStore) 181 { 182 if (((AmetysWorkflowStore) store).shouldClearHistory()) 183 { 184 // Supprimer l'instance de la base de données 185 // PERTE DE L'HISTORIQUE ! 186 ((AmetysWorkflowStore) store).deleteInstance(id); 187 } 188 } 189 } 190 catch (StoreException e) 191 { 192 _logger.error("Error while getting the workflow store", e); 193 } 194 } 195 } 196 197 @Override 198 protected boolean transitionWorkflow(WorkflowEntry entry, List currentSteps, WorkflowStore store, WorkflowDescriptor wf, ActionDescriptor action, Map transientVars, Map inputs, PropertySet ps) throws WorkflowException 199 { 200 Date actionStartDate = new Date(); 201 202 // Mark all current steps before the action. 203 for (Object step : currentSteps) 204 { 205 if (step instanceof AmetysStep) 206 { 207 AmetysStep theStep = (AmetysStep) step; 208 theStep.setProperty("actionStartDate", actionStartDate); 209 theStep.save(); 210 } 211 } 212 213 boolean complete = super.transitionWorkflow(entry, currentSteps, store, wf, action, transientVars, inputs, ps); 214 215 // Mark the newly created step. 216 Step createdStep = (Step) transientVars.get("createdStep"); 217 if (createdStep != null && createdStep instanceof AmetysStep) 218 { 219 AmetysStep theStep = (AmetysStep) createdStep; 220 theStep.setProperty("actionFinishDate", new Date()); 221 theStep.save(); 222 } 223 224 return complete; 225 } 226 227 @Override 228 protected void checkImplicitFinish(ActionDescriptor action, long id) throws WorkflowException 229 { 230 // Do nothing ... we don't want to call completeEntry if the currentStep has no actions (so its a final step) 231 } 232} 233