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