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.NoHistoryWorkflowStore;
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 NoHistoryWorkflowStore)
180                {
181                    // Supprimer l'instance de la base de données
182                    // PERTE DE L'HISTORIQUE !
183                    ((NoHistoryWorkflowStore) store).deleteInstance(id);
184                }
185            }
186            catch (StoreException e)
187            {
188                _logger.error("Error while getting the workflow store", e);
189            }
190        }
191    }
192    
193    @Override
194    protected boolean transitionWorkflow(WorkflowEntry entry, List currentSteps, WorkflowStore store, WorkflowDescriptor wf, ActionDescriptor action, Map transientVars, Map inputs, PropertySet ps) throws WorkflowException
195    {
196        Date actionStartDate = new Date();
197        
198        // Mark all current steps before the action.
199        for (Object step : currentSteps)
200        {
201            if (step instanceof AmetysStep)
202            {
203                AmetysStep theStep = (AmetysStep) step;
204                theStep.setProperty("actionStartDate", actionStartDate);
205                theStep.save();
206            }
207        }
208        
209        boolean complete = super.transitionWorkflow(entry, currentSteps, store, wf, action, transientVars, inputs, ps);
210        
211        // Mark the newly created step.
212        Step createdStep = (Step) transientVars.get("createdStep");
213        if (createdStep != null && createdStep instanceof AmetysStep)
214        {
215            AmetysStep theStep = (AmetysStep) createdStep;
216            theStep.setProperty("actionFinishDate", new Date());
217            theStep.save();
218        }
219        
220        return complete;
221    }
222}
223