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.store;
017
018import javax.jcr.Node;
019import javax.jcr.Repository;
020import javax.jcr.RepositoryException;
021import javax.jcr.Session;
022
023import org.ametys.plugins.repository.version.VersionAwareAmetysObject;
024import org.ametys.plugins.workflow.repository.WorkflowAwareAmetysObject;
025
026import com.opensymphony.workflow.StoreException;
027import com.opensymphony.workflow.spi.WorkflowEntry;
028
029/**
030 * Workflow store that creates local entries for an Ametys object.
031 * Each entry belong to an ametys object as soon as the entry is created.
032 */
033public class AmetysObjectWorkflowStore extends AbstractJackrabbitWorkflowStore
034{
035    /** The ametys object */
036    protected WorkflowAwareAmetysObject _ametysObject;
037    
038    /** Temporary entry created in memory */
039    protected WorkflowEntry _inMemoryEntry;
040    
041    
042    private boolean _preserveHistory;
043    
044    /**
045     * Creates a workflow store for Ametys object. 
046     * The history steps will be clear on workflow completion.
047     * @param repository the JCR Repository to use.
048     * @param ametysObject The ametys object for this store. Can be null in case of an object creation.
049     */
050    public AmetysObjectWorkflowStore(Repository repository, WorkflowAwareAmetysObject ametysObject)
051    {
052        this(repository, ametysObject, false);
053    }
054    
055    /**
056     * Creates a workflow store for Ametys object.
057     * @param repository the JCR Repository to use.
058     * @param ametysObject The ametys object for this store. Can be null in case of an object creation.
059     * @param preserveHistory Set to true to preserve history steps when workflow is complete.
060     */
061    public AmetysObjectWorkflowStore(Repository repository, WorkflowAwareAmetysObject ametysObject, boolean preserveHistory)
062    {
063        super(repository);
064        _ametysObject = _ensureBaseVersion(ametysObject);
065        _preserveHistory = preserveHistory;
066    }
067    
068    @Override
069    public boolean shouldClearHistory()
070    {
071        return !_preserveHistory;
072    }
073    
074    /**
075     * Ametys object getter
076     * @return The ametys object
077     */
078    public WorkflowAwareAmetysObject getAmetysObject()
079    {
080        return _ametysObject;
081    }
082    
083    @Override
084    protected Session _getSession() throws RepositoryException
085    {
086        return _ametysObject != null ? _ametysObject.getNode().getSession() : null;
087    }
088    
089    @Override
090    protected void _release(Session session)
091    {
092        // do nothing since the store use the ametys object session
093    }
094    
095    /**
096     * Provides the ametys object node in the current session
097     * @return node
098     * @throws RepositoryException on repository error
099     */
100    protected Node _getAmetysObjectNode() throws RepositoryException
101    {
102        return _ametysObject.getNode();
103    }
104    
105    @Override
106    protected void _createRootNode() throws RepositoryException
107    {
108        if (_ametysObject == null)
109        {
110            if (_log.isDebugEnabled())
111            {
112                _log.debug("Ametys object does not currently exist. Root node creation will be delayed. The creation will be done once the ametys object is available.");
113            }
114            
115            return;
116        }
117        
118        Session session = null;
119        try
120        {
121            session = _getSession();
122                
123            Node contentNode = _getAmetysObjectNode();
124            
125            if (!contentNode.hasNode("ametys-internal:workflows"))
126            {
127                if (_log.isDebugEnabled())
128                {
129                    _log.debug(String.format("Creating osworkflow root node for the ametys object '%s'.", _ametysObject.getId()));
130                }
131                
132                contentNode.addNode("ametys-internal:workflows");
133            }
134            else
135            {
136                if (_log.isDebugEnabled())
137                {
138                    _log.debug(String.format("Existing osworkflow root node of the ametys object '%s', skipping creation.", _ametysObject.getId()));
139                }
140            }
141        }
142        finally
143        {
144            _release(session);
145        }
146    }
147    
148    @Override
149    protected Node _getRootNode(Session session) throws RepositoryException
150    {
151        Node contentNode = _getAmetysObjectNode();
152        return contentNode.getNode("ametys-internal:workflows");
153    }
154    
155    @Override
156    protected Node _getOrCreateParentEntryNode(Node root, long id) throws RepositoryException
157    {
158        return root;
159    }
160    
161    @Override
162    protected synchronized long _getNextEntryId() throws RepositoryException
163    {
164        if (_ametysObject == null)
165        {
166            // default workflow entry id
167            return 0;
168        }
169        
170        return super._getNextEntryId();
171    }
172    
173    /**
174     * Store a new workflow entry into the JCR repository
175     * @param workflowEntry The new entry
176     * @throws StoreException on error
177     */
178    @Override
179    protected void storeNewEntry(WorkflowEntry workflowEntry) throws StoreException
180    {
181        // Temporary store in memory if ametys object does not exist currently.
182        // Otherwise persist the entry into the JCR repository
183        if (_ametysObject == null)
184        {
185            _inMemoryEntry = workflowEntry;
186            return;
187        }
188        
189        super.storeNewEntry(workflowEntry);
190    }
191    
192    /**
193     * Bind an ametys object to this store. Must be done in case of an object
194     * creation. As soon as the ametys object is created, the store must be
195     * notified to persist the workflow entry into the repository.
196     * @param ametysObject The ametys object to bind
197     * @throws StoreException on error
198     */
199    public void bindAmetysObject(WorkflowAwareAmetysObject ametysObject) throws StoreException
200    {
201        _ametysObject = _ensureBaseVersion(ametysObject);
202        
203        if (_inMemoryEntry != null)
204        {
205            // Create root node
206            try
207            {
208                _createRootNode();
209            }
210            catch (RepositoryException e)
211            {
212                throw new StoreException("Unable to create the root node", e);
213            }
214            
215            // Persist the memory entry into the repository
216            storeNewEntry(_inMemoryEntry);
217            _inMemoryEntry = null;
218        }
219    }
220    
221    private WorkflowAwareAmetysObject _ensureBaseVersion(WorkflowAwareAmetysObject ametysObject)
222    {
223        if (ametysObject instanceof VersionAwareAmetysObject)
224        {
225            VersionAwareAmetysObject vao = (VersionAwareAmetysObject) ametysObject;
226            
227            if (vao.getRevision() != null)
228            {
229                // switch to base
230                vao.switchToRevision(null);
231            }
232            
233        }
234        
235        return ametysObject;
236    }
237    
238}