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