001/*
002 *  Copyright 2017 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.bpm.process;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.Comparator;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.cocoon.ProcessingException;
029import org.apache.cocoon.environment.ObjectModelHelper;
030import org.apache.cocoon.environment.Request;
031import org.apache.cocoon.generation.ServiceableGenerator;
032import org.apache.cocoon.xml.AttributesImpl;
033import org.apache.cocoon.xml.XMLUtils;
034import org.apache.commons.lang.StringUtils;
035import org.xml.sax.SAXException;
036
037import org.ametys.core.right.RightManager;
038import org.ametys.core.right.RightManager.RightResult;
039import org.ametys.core.user.CurrentUserProvider;
040import org.ametys.core.user.User;
041import org.ametys.core.user.UserIdentity;
042import org.ametys.core.user.UserManager;
043import org.ametys.core.util.DateUtils;
044import org.ametys.plugins.bpm.BPMWorkflowManager;
045import org.ametys.plugins.bpm.jcr.JCRWorkflow;
046import org.ametys.plugins.bpm.jcr.JCRWorkflowProcess;
047import org.ametys.plugins.core.user.UserHelper;
048import org.ametys.plugins.explorer.resources.ModifiableResource;
049import org.ametys.plugins.explorer.resources.ResourceCollection;
050import org.ametys.plugins.repository.AmetysObjectIterable;
051import org.ametys.plugins.repository.AmetysObjectResolver;
052import org.ametys.plugins.repository.AmetysRepositoryException;
053import org.ametys.plugins.workflow.support.WorkflowProvider;
054import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
055import org.ametys.runtime.authentication.AccessDeniedException;
056import org.ametys.runtime.authentication.AuthorizationRequiredException;
057
058import com.opensymphony.workflow.loader.ActionDescriptor;
059import com.opensymphony.workflow.loader.StepDescriptor;
060import com.opensymphony.workflow.loader.WorkflowDescriptor;
061import com.opensymphony.workflow.spi.Step;
062
063/**
064 * Sax a process data
065 */
066public class ProcessGenerator extends ServiceableGenerator
067{
068    private static final String __MESSAGE_NO_ACTION = "plugin.cms:WORKFLOW_UNKNOWN_ACTION";
069    
070    private BPMWorkflowManager _bpmWorkflowManager;
071    private AmetysObjectResolver _resolver;
072    private UserManager _userManager;
073    private WorkflowProvider _workflowProvider;
074    private UserHelper _userHelper;
075    private CurrentUserProvider _currentUserProvider;
076    private RightManager _rightManager;
077    
078    @Override
079    public void service(ServiceManager smanager) throws ServiceException
080    {
081        super.service(smanager);
082        _bpmWorkflowManager = (BPMWorkflowManager) smanager.lookup(BPMWorkflowManager.ROLE);
083        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
084        _userManager = (UserManager) smanager.lookup(UserManager.ROLE);
085        _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE);
086        _userHelper = (UserHelper) smanager.lookup(UserHelper.ROLE);
087        _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
088        _rightManager = (RightManager) smanager.lookup(RightManager.ROLE);
089    }
090    
091    public void generate() throws IOException, SAXException, ProcessingException
092    {
093        String workflowName = parameters.getParameter("workflowName", null);
094        String processName = parameters.getParameter("processName", null);
095        
096        if (workflowName == null || processName == null)
097        {
098            throw new IllegalArgumentException("Missing mandator arguments for saxing a process informations");
099        }
100        
101        JCRWorkflowProcess process = _bpmWorkflowManager.getProcess(workflowName, processName);
102        
103        if (process == null)
104        {
105            throw new IllegalArgumentException("No process found for workflow name '" + workflowName + "' and process name '" + processName + "'");
106        }
107        
108        UserIdentity user = _currentUserProvider.getUser();
109        if (user == null)
110        {
111            throw new AuthorizationRequiredException("Access denied to process '" + workflowName + "/" + processName + "' for anonymous user");
112        }
113        
114        JCRWorkflow workflow = _resolver.resolveById(process.getWorkflow());
115        
116        if (_rightManager.currentUserHasRight(BPMWorkflowManager.RIGHTS_PROCESS_MANAGE_PROCESSES, null) != RightResult.RIGHT_ALLOW 
117                && !user.equals(process.getCreator()) && !_bpmWorkflowManager.isUserInWorkflowVariables(workflow))
118        {
119            throw new AccessDeniedException("Access denied to process '" + workflowName + "/" + processName + "' for user '" + user + "'");
120        }
121        
122        contentHandler.startDocument();
123        _saxProcess(processName, process, workflow);
124        contentHandler.endDocument();
125    }
126
127    private void _saxProcess(String processName, JCRWorkflowProcess process, JCRWorkflow workflow) throws SAXException
128    {
129        boolean isComplete = _bpmWorkflowManager.isComplete(process);
130        
131        AttributesImpl attrs = new AttributesImpl();
132        attrs.addCDATAAttribute("id", process.getId());
133        attrs.addCDATAAttribute("name", processName);
134        attrs.addCDATAAttribute("complete", String.valueOf(isComplete));
135        XMLUtils.startElement(contentHandler, "process", attrs);
136        
137        // Add the workflow used by the process in the request, to be used by any RegisterVariable of the workflow definition 
138        Request request = ObjectModelHelper.getRequest(objectModel);
139        request.setAttribute("workflowId", process.getWorkflow());
140        
141        AmetysObjectWorkflow aoWorkflow = _workflowProvider.getAmetysObjectWorkflow(process, true);
142        WorkflowDescriptor workflowDescriptor = _getWorkflowDescriptor(aoWorkflow, process);
143        
144        _saxProcess(process, workflow);
145        if (workflowDescriptor != null)
146        {
147            _saxWorkflowSteps(process, aoWorkflow, workflowDescriptor);
148            _saxCurrentWorkflowStep(process, workflowDescriptor);
149            if (!isComplete)
150            {
151                _saxWorkflowActions(process, workflow, aoWorkflow, workflowDescriptor);
152            }
153        }
154        
155        if (_bpmWorkflowManager.canDelete(process))
156        {
157            XMLUtils.createElement(contentHandler, "action-delete", "true");
158        }
159        
160        XMLUtils.endElement(contentHandler, "process");
161    }
162    
163    private WorkflowDescriptor _getWorkflowDescriptor(AmetysObjectWorkflow aoWorkflow, JCRWorkflowProcess process) throws AmetysRepositoryException
164    {
165        try
166        {
167            String workflowName = aoWorkflow.getWorkflowName(process.getWorkflowId());
168    
169            if (workflowName == null)
170            {
171                return null;
172            }
173    
174            return aoWorkflow.getWorkflowDescriptor(workflowName);
175        }
176        catch (AmetysRepositoryException e)
177        {
178            // no workflow node for process
179            return null;
180        }
181    }
182
183    private void _saxProcess(JCRWorkflowProcess process, JCRWorkflow workflow) throws SAXException
184    {
185        AttributesImpl attrs = new AttributesImpl();
186        attrs.addCDATAAttribute("id", workflow.getId());
187        attrs.addCDATAAttribute("name", workflow.getName());
188        XMLUtils.createElement(contentHandler, "workflow", attrs, workflow.getTitle());
189        
190        XMLUtils.createElement(contentHandler, "title", process.getTitle());
191        UserIdentity creatorIdentity = process.getCreator();
192        User creator = _userManager.getUser(creatorIdentity);
193        if (creator != null)
194        {
195            attrs = new AttributesImpl();
196            attrs.addCDATAAttribute("id", UserIdentity.userIdentityToString(creatorIdentity));
197            XMLUtils.createElement(contentHandler, "creator", attrs, creator.getFullName());
198        }
199        else if (creatorIdentity != null)
200        {
201            XMLUtils.createElement(contentHandler, "creator", UserIdentity.userIdentityToString(creatorIdentity));
202        }
203        
204        String description = process.getDescription();
205        if (description != null)
206        {
207            XMLUtils.createElement(contentHandler, "description", description);
208        }
209        
210        ResourceCollection rootAttachments = process.getRootAttachments(false);
211        if (rootAttachments != null)
212        {
213            XMLUtils.startElement(contentHandler, "attachments");
214            AmetysObjectIterable<ModifiableResource> attachments = rootAttachments.getChildren();
215            for (ModifiableResource attachment : attachments)
216            {
217                AttributesImpl attachmentAttrs = new AttributesImpl();
218                attachmentAttrs.addCDATAAttribute("name", attachment.getName());
219                XMLUtils.createElement(contentHandler, "attachment", attachmentAttrs);
220            }
221            XMLUtils.endElement(contentHandler, "attachments");
222        }
223    }
224
225    private void _saxWorkflowSteps(JCRWorkflowProcess process, AmetysObjectWorkflow aoWorkflow, WorkflowDescriptor workflowDescriptor) throws SAXException
226    {
227        List<Step> steps = _getProcessSteps(process, aoWorkflow);
228        
229        Step previousStep = null;
230        XMLUtils.startElement(contentHandler, "steps");
231        for (Step step : steps)
232        {
233            _saxStep(process, step, previousStep, workflowDescriptor);
234            previousStep = step;
235        }
236        XMLUtils.endElement(contentHandler, "steps");
237    }
238
239    private List<Step> _getProcessSteps(JCRWorkflowProcess process, AmetysObjectWorkflow aoWorkflow)
240    {
241        long workflowId = process.getWorkflowId();
242
243        @SuppressWarnings("unchecked")
244        List<Step> allSteps = new ArrayList(aoWorkflow.getCurrentSteps(workflowId));
245        allSteps.addAll(aoWorkflow.getHistorySteps(workflowId));
246
247        // Sort by start date descendant (only relevant when there is no
248        // split and nor join!)
249        Collections.sort(allSteps, new Comparator<Step>()
250        {
251            public int compare(Step s1, Step s2)
252            {
253                return s1.getStartDate().compareTo(s2.getStartDate());
254            }
255        });
256        
257        return allSteps;
258    }
259
260    private void _saxStep(JCRWorkflowProcess process, Step step, Step previousStep, WorkflowDescriptor workflowDescriptor) throws SAXException
261    {
262        XMLUtils.startElement(contentHandler, "step");
263        
264        XMLUtils.createElement(contentHandler, "startDate", DateUtils.dateToString(step.getStartDate()));
265
266        UserIdentity caller = previousStep != null ? UserIdentity.stringToUserIdentity(previousStep.getCaller()) : process.getCreator();
267        String userFullName = _userHelper.getUserFullName(caller);
268        XMLUtils.createElement(contentHandler, "user", StringUtils.isNotEmpty(userFullName) ? userFullName : UserIdentity.userIdentityToString(caller));
269
270        
271        if (previousStep != null)
272        {
273            int actionId = previousStep.getActionId();
274
275            AttributesImpl attrs = new AttributesImpl();
276            attrs.addCDATAAttribute("id", Integer.toString(actionId));
277            XMLUtils.startElement(contentHandler, "action", attrs);
278
279            ActionDescriptor actionDescriptor = workflowDescriptor.getAction(actionId);
280            XMLUtils.createElement(contentHandler, "label", actionDescriptor == null ? __MESSAGE_NO_ACTION : actionDescriptor.getName() + "_ACTION_DESCRIPTION");
281            XMLUtils.endElement(contentHandler, "action");
282        }
283        else
284        {
285            XMLUtils.startElement(contentHandler, "action");
286            XMLUtils.createElement(contentHandler, "label", "plugin.bpm:WORKFLOW_ACTION_CREATE_ACTION_DESCRIPTION");
287            XMLUtils.endElement(contentHandler, "action");
288        }
289
290        XMLUtils.endElement(contentHandler, "step");
291    }
292
293    private void _saxCurrentWorkflowStep(JCRWorkflowProcess process, WorkflowDescriptor workflowDescriptor) throws SAXException
294    {
295        StepDescriptor step = workflowDescriptor.getStep((int) process.getCurrentStepId());
296        XMLUtils.createElement(contentHandler, "currentStep", step.getName());
297    }
298
299    private void _saxWorkflowActions(JCRWorkflowProcess process, JCRWorkflow workflow, AmetysObjectWorkflow aoWorkflow, WorkflowDescriptor workflowDescriptor) throws SAXException
300    {
301        XMLUtils.startElement(contentHandler, "actions");
302
303        Map<String, Object> inputs = new HashMap<>();
304        inputs.put("workflowId", workflow.getId());
305        inputs.put("process", process);
306        int[] availableActions = aoWorkflow.getAvailableActions(process.getWorkflowId(), inputs);
307        for (int actionId : availableActions)
308        {
309            ActionDescriptor action = workflowDescriptor.getAction(actionId);
310            
311            if (action != null)
312            {
313                AttributesImpl attrs = new AttributesImpl();
314                attrs.addCDATAAttribute("id", Integer.toString(actionId));
315                attrs.addCDATAAttribute("name", action.getName());
316                XMLUtils.createElement(contentHandler, "action", attrs);
317            }
318        }
319        
320        XMLUtils.endElement(contentHandler, "actions");
321    }
322
323}