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