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