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.support;
017
018import java.util.Collections;
019import java.util.Properties;
020
021import javax.jcr.Repository;
022
023import org.apache.avalon.framework.activity.Disposable;
024import org.apache.avalon.framework.activity.Initializable;
025import org.apache.avalon.framework.component.Component;
026import org.apache.avalon.framework.context.ContextException;
027import org.apache.avalon.framework.context.Contextualizable;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.cocoon.Constants;
032import org.apache.cocoon.environment.Context;
033import org.apache.excalibur.source.SourceResolver;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037import org.ametys.core.user.CurrentUserProvider;
038import org.ametys.core.user.UserIdentity;
039import org.ametys.plugins.workflow.AbstractAmetysWorkflow;
040import org.ametys.plugins.workflow.AmetysWorkflowFactory;
041import org.ametys.plugins.workflow.SimpleConfiguration;
042import org.ametys.plugins.workflow.repository.WorkflowAwareAmetysObject;
043import org.ametys.plugins.workflow.store.AmetysObjectWorkflowStore;
044import org.ametys.plugins.workflow.store.GenericWorkflowStore;
045
046import com.opensymphony.workflow.FactoryException;
047import com.opensymphony.workflow.StoreException;
048import com.opensymphony.workflow.TypeResolver;
049import com.opensymphony.workflow.Workflow;
050import com.opensymphony.workflow.WorkflowContext;
051import com.opensymphony.workflow.WorkflowException;
052import com.opensymphony.workflow.loader.WorkflowFactory;
053import com.opensymphony.workflow.spi.WorkflowStore;
054
055/**
056 * Component able to provide several workflow objects.
057 * <ul>
058 * <li>The generic workflow: A standalone workflow which can be used for various
059 * purposes. All entries are stored under a global workflow root node in the JCR
060 * repository.
061 * <li>Dedicated ametys object workflows: A workflow specific to an ametys
062 * object. The workflow is stored under the ametys object node the JCR
063 * repository.
064 * </ul>
065 */
066public class WorkflowProvider implements Component, Serviceable, Contextualizable, Initializable, Disposable 
067{
068    /** Avalon role. */
069    public static final String ROLE = WorkflowProvider.class.getName();
070    
071    /** Logger available to subclasses. */
072    protected static Logger _logger = LoggerFactory.getLogger(WorkflowProvider.class);
073
074    /** Service manager. */
075    protected ServiceManager _manager;
076    
077    /** Current user provider. */
078    protected CurrentUserProvider _currentUserProvider;
079    
080    /** The repository */
081    protected Repository _repository;
082    
083    /** The workflow helper */
084    protected WorkflowHelper _workflowHelper;
085    
086    /** Cocoon context */
087    protected Context _cocoonContext;
088    
089    /** Workflow context */
090    protected WorkflowContext _workflowContext;
091    
092    /** Workflow factory */
093    protected WorkflowFactory _workflowFactory;
094    
095    /** Type resolver */
096    protected TypeResolver _typeResolver;
097    
098    /** Generic workflow instance */
099    protected GenericWorkflow _genericWorkflow;
100
101    /** The source resolver */
102    protected SourceResolver _resolver;
103    
104    public void contextualize(org.apache.avalon.framework.context.Context ctx) throws ContextException
105    {
106        _cocoonContext = (Context) ctx.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
107    }
108    
109    public void service(ServiceManager manager) throws ServiceException
110    {
111        _manager = manager;
112        _currentUserProvider = (CurrentUserProvider) _manager.lookup(CurrentUserProvider.ROLE);
113        _repository = (Repository) _manager.lookup("javax.jcr.Repository");
114        _workflowHelper = (WorkflowHelper) _manager.lookup(WorkflowHelper.ROLE);
115        _resolver = (SourceResolver) _manager.lookup(SourceResolver.ROLE);
116        _workflowFactory = (WorkflowFactory) _manager.lookup(AmetysWorkflowFactory.ROLE);
117    }
118    
119    public void initialize() throws Exception
120    {
121        // Standard workflow context
122        _workflowContext = new WorkflowContext()
123        {
124            public String getCaller()
125            {
126                return UserIdentity.userIdentityToString(_currentUserProvider.getUser());
127            }
128            
129            public void setRollbackOnly()
130            {
131                // Transaction not supported
132            }
133        };
134
135        // Ametys workflow factory
136        try
137        {
138            _workflowFactory.init(new Properties());
139            _workflowFactory.initDone();
140        }
141        catch (FactoryException e)
142        {
143            throw new RuntimeException("Unable to init workflow factory", e);
144        }
145        
146        // Standard type resolver
147        _typeResolver = new AvalonTypeResolver(_manager);
148    }
149    
150    public void dispose()
151    {
152        _manager = null;
153        _currentUserProvider = null;
154        _cocoonContext = null;
155        
156        _workflowContext = null;
157        _workflowFactory = null;
158        _typeResolver = null;
159        _genericWorkflow = null;
160    }
161    
162    /**
163     * Workflow factory getter
164     * @return The workflow factory
165     */
166    WorkflowFactory getWorkflowFactory()
167    {
168        return _workflowFactory;
169    }
170    
171    /**
172     * Provides the generic workflow
173     * @return The generic workflow
174     */
175    public Workflow getGenericWorkflow()
176    {
177        if (_genericWorkflow == null)
178        {
179            // Setup the generic workflow
180            GenericWorkflowStore store = _createGenericWorkflowStore();
181            try
182            {
183                store.init(Collections.EMPTY_MAP);
184            }
185            catch (StoreException e)
186            {
187                throw new RuntimeException("Unable to init workflow store", e);
188            }
189            
190            _genericWorkflow = new GenericWorkflow(_workflowHelper, _workflowContext);
191            
192            _genericWorkflow.setConfiguration(new SimpleConfiguration(_workflowFactory, store));
193            _genericWorkflow.setResolver(_typeResolver);
194        }
195        
196        return _genericWorkflow;
197    }
198    
199    /**
200     * Provide the generic workflow store
201     * @return The generic workflow store
202     */
203    protected GenericWorkflowStore _createGenericWorkflowStore()
204    {
205        return new GenericWorkflowStore(_repository);
206    }
207    
208    /**
209     * The Generic worklfow 
210     */
211    public static class GenericWorkflow extends AbstractAmetysWorkflow
212    {
213        GenericWorkflow(WorkflowHelper workflowHelper, WorkflowContext workflowContext)
214        {
215            super(workflowHelper, workflowContext);
216        }
217    }
218    
219    /**
220     * Provide a local workflow to an Ametys object which do not preserve history on workflow complete
221     * Must be used to initialize a workflow that will create an ametys object.
222     * {@link AmetysObjectWorkflow#getAmetysObject()} could then be used to directly retrieves the ametys object.
223     * @return The local workflow
224     */
225    public AmetysObjectWorkflow getAmetysObjectWorkflow()
226    {
227        return getAmetysObjectWorkflow(null, false);
228    }
229    
230    /**
231     * Provide a local workflow to an Ametys object
232     * Must be used to initialize a workflow that will create an ametys object.
233     * {@link AmetysObjectWorkflow#getAmetysObject()} could then be used to directly retrieves the ametys object.
234     * @param preserveHistory true if the history should be preserve when workflow is complete
235     * @return The local workflow
236     */
237    public AmetysObjectWorkflow getAmetysObjectWorkflow(boolean preserveHistory)
238    {
239        return getAmetysObjectWorkflow(null, preserveHistory);
240    }
241    
242    /**
243     * Provide a local workflow to an Ametys object which do not preserve history on workflow complete
244     * @param ametysObject The ametys object (can be null in case of initialization)
245     * @return the local workflow to an Ametys object
246     */
247    public AmetysObjectWorkflow getAmetysObjectWorkflow(WorkflowAwareAmetysObject ametysObject)
248    {
249        return getAmetysObjectWorkflow(ametysObject, false);
250    }
251    
252    /**
253     * Provide a local workflow to an Ametys object
254     * @param ametysObject The ametys object (can be null in case of initialization)
255     * @param preserveHistory true if the history steps should be preserve when workflow is complete
256     * @return the local workflow to an Ametys object
257     */
258    public AmetysObjectWorkflow getAmetysObjectWorkflow(WorkflowAwareAmetysObject ametysObject, boolean preserveHistory)
259    {
260        AmetysObjectWorkflowStore store = _createAmetysObjectWorkflowStore(ametysObject, preserveHistory);
261        
262        try
263        {
264            store.init(Collections.EMPTY_MAP);
265        }
266        catch (StoreException e)
267        {
268            throw new RuntimeException("Unable to init workflow store", e);
269        }
270        
271        AmetysObjectWorkflow ametysObjectWorkflow = new AmetysObjectWorkflow(_workflowHelper, _workflowContext, store);
272        
273        ametysObjectWorkflow.setConfiguration(new SimpleConfiguration(_workflowFactory, store));
274        ametysObjectWorkflow.setResolver(_typeResolver);
275        
276        return ametysObjectWorkflow;
277    }
278    
279    /**
280     * Provide an ametys object workflow store instance
281     * @param ametysObject The ametys object bound to this store (can be null in case of initialization)
282     * @param preserveHistory true if the history steps should be preserve when workflow is complete
283     * @return the local workflow store of an Ametys object
284     */
285    protected AmetysObjectWorkflowStore _createAmetysObjectWorkflowStore(WorkflowAwareAmetysObject ametysObject, boolean preserveHistory)
286    {
287        return new AmetysObjectWorkflowStore(_repository, ametysObject, preserveHistory);
288    }
289    
290    /**
291     * Local workflow to an ametys object
292     */
293    public static class AmetysObjectWorkflow extends AbstractAmetysWorkflow
294    {
295        /** The ametys object */
296        protected AmetysObjectWorkflowStore _workflowStore;
297        
298        AmetysObjectWorkflow(WorkflowHelper workflowHelper, WorkflowContext workflowContext, AmetysObjectWorkflowStore store)
299        {
300            super(workflowHelper, workflowContext);
301            _workflowStore = store;
302        }
303        
304        /**
305         * Ametys object getter
306         * @param <A> A WorkflowAwareAmetysObject class
307         * @return The ametys object
308         */
309        @SuppressWarnings("unchecked")
310        public <A extends WorkflowAwareAmetysObject> A getAmetysObject()
311        {
312            return (A) _workflowStore.getAmetysObject();
313        }
314        
315        /**
316         * Delete a workflow instance
317         * @param wId The id of workflow instance
318         * @throws WorkflowException if failed to delete workflow instance
319         */
320        public void removeWorkflow (long wId) throws WorkflowException
321        {
322            _workflowStore.removeEntry(wId);
323        }
324    }
325    
326    /**
327     * Provides an external workflow
328     * @param workflowStoreRole The component role of the workflow store to use.
329     * @return The external workflow
330     */
331    public Workflow getExternalWorkflow(String workflowStoreRole)
332    {
333        WorkflowStore workflowStore;
334        try
335        {
336            workflowStore = (WorkflowStore) _manager.lookup(workflowStoreRole);
337        }
338        catch (ServiceException e)
339        {
340            throw new RuntimeException(String.format("Unable to lookup workflow store for role '%s'.", workflowStoreRole), e);
341        }
342        
343        ExternalWorkflow workflow = new ExternalWorkflow(_workflowHelper, _workflowContext, workflowStore);
344        
345        workflow.setConfiguration(new SimpleConfiguration(_workflowFactory, workflowStore));
346        workflow.setResolver(_typeResolver);
347            
348        return workflow;
349    }
350    
351    /**
352     * Provides an external workflow
353     * @param workflowStore An (already initialized) workflow store
354     * @return The external workflow
355     */
356    public Workflow getExternalWorkflow(WorkflowStore workflowStore)
357    {
358        ExternalWorkflow workflow = new ExternalWorkflow(_workflowHelper, _workflowContext, workflowStore);
359        
360        workflow.setConfiguration(new SimpleConfiguration(_workflowFactory, workflowStore));
361        workflow.setResolver(_typeResolver);
362            
363        return workflow;
364    }
365    
366    /**
367     * The External worklfow 
368     */
369    public static class ExternalWorkflow extends AbstractAmetysWorkflow
370    {
371        /** The external workflow store */
372        protected WorkflowStore _store;
373        
374        ExternalWorkflow(WorkflowHelper workflowHelper, WorkflowContext workflowContext, WorkflowStore workflowStore)
375        {
376            super(workflowHelper, workflowContext);
377            _store = workflowStore;
378        }
379    }
380}