001/*
002 *  Copyright 2015 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.cart.actions;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.Comparator;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import org.apache.avalon.framework.parameters.Parameters;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.cocoon.acting.ServiceableAction;
030import org.apache.cocoon.environment.ObjectModelHelper;
031import org.apache.cocoon.environment.Redirector;
032import org.apache.cocoon.environment.Request;
033import org.apache.cocoon.environment.SourceResolver;
034import org.apache.commons.lang.ArrayUtils;
035
036import org.ametys.cms.repository.Content;
037import org.ametys.cms.repository.WorkflowAwareContent;
038import org.ametys.core.cocoon.JSonReader;
039import org.ametys.core.user.CurrentUserProvider;
040import org.ametys.core.user.UserIdentity;
041import org.ametys.core.user.UserManager;
042import org.ametys.core.util.DateUtils;
043import org.ametys.plugins.cart.Cart;
044import org.ametys.plugins.cart.CartElement;
045import org.ametys.plugins.cart.ContentElement;
046import org.ametys.plugins.core.user.UserHelper;
047import org.ametys.plugins.repository.AmetysObjectResolver;
048import org.ametys.plugins.repository.AmetysRepositoryException;
049import org.ametys.plugins.repository.UnknownAmetysObjectException;
050import org.ametys.plugins.repository.version.VersionAwareAmetysObject;
051import org.ametys.plugins.workflow.store.AmetysStep;
052import org.ametys.plugins.workflow.support.WorkflowHelper;
053import org.ametys.plugins.workflow.support.WorkflowProvider;
054import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
055import org.ametys.runtime.i18n.I18nizableText;
056
057import com.opensymphony.workflow.WorkflowException;
058import com.opensymphony.workflow.spi.Step;
059
060/**
061 * SAX a {@link Cart}
062 *
063 */
064public class GetCartElementsAction extends ServiceableAction
065{
066    /** The Ametys object resolver */
067    protected AmetysObjectResolver _resolver;
068    /** The workflow */
069    protected WorkflowProvider _workflowProvider;
070    /** The workflow helper */
071    protected WorkflowHelper _workflowHelper;
072    /** The current user provider */
073    protected CurrentUserProvider _userProvider;
074    /** The user manager */
075    protected UserManager _userManager;
076    /** The user helper */
077    protected UserHelper _userHelper;
078    
079    @Override
080    public void service(ServiceManager serviceManager) throws ServiceException
081    {
082        super.service(serviceManager);
083        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
084        _workflowProvider = (WorkflowProvider) serviceManager.lookup(WorkflowProvider.ROLE);
085        _workflowHelper = (WorkflowHelper) serviceManager.lookup(WorkflowHelper.ROLE);
086        _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE);
087        _userProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
088        _userHelper = (UserHelper) serviceManager.lookup(UserHelper.ROLE);
089    }
090    
091    @Override
092    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
093    {
094        @SuppressWarnings("unchecked")
095        Map jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
096        
097        String id = (String) jsParameters.get("cartId");
098        
099        Map<String, Object> result = new HashMap<>();
100        List<Map<String, Object>> nodes = new ArrayList<>();
101        int total = 0;
102        
103        try
104        {
105            Cart cart = _resolver.resolveById(id);
106            
107            UserIdentity user = _userProvider.getUser();
108            if (cart.canRead(user))
109            {
110                result.put("label", cart.getTitle());
111                result.put("description", cart.getDescription());
112                result.put("visibility", cart.getVisibility().toString());
113                
114                List<CartElement> elements = cart.getElements();
115                for (CartElement elmt : elements)
116                {
117                    nodes.addAll(elementToJSON(elmt));
118                }
119                total = elements.size();
120            }
121            
122            result.put("cartElements", nodes);
123            result.put("total", total);
124        }
125        catch (UnknownAmetysObjectException e)
126        {
127            result.put("unknown-cart", true);
128        }
129        
130        Request request = ObjectModelHelper.getRequest(objectModel);
131        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
132        
133        return EMPTY_MAP;
134    }
135    
136    /**
137     * Gets a cart element to JSON format
138     * @param elmt The elemetnt
139     * @return The element's properties
140     */
141    protected List<Map<String, Object>> elementToJSON (CartElement elmt)
142    {
143        List<Map<String, Object>> elmts = new ArrayList<>();
144        
145        HashMap<String, Object> infos = new HashMap<>();
146        
147        infos.put("elmtId", elmt.getId());
148        infos.put("title", elmt.getTitle());
149        infos.put("description", elmt.getDescription());
150        
151        infos.put("lastModification", DateUtils.dateToString(elmt.getLastModified()));
152        infos.put("lastContributor", _userHelper.user2json(elmt.getLastContributor()));
153        
154        infos.put("glyphIcon", elmt.getGlyphIcon());
155        infos.put("smallIcon", elmt.getSmallIcon());
156        infos.put("mediumIcon", elmt.getMediumIcon());
157        
158        infos.put("type", elmt.getType());
159        
160        if (elmt instanceof ContentElement)
161        {
162            Content content = _resolver.resolveById(elmt.getId());
163            infos.put("workflow-step", workflowStepToJSON(content));
164        }
165        
166        List<I18nizableText> groups = elmt.getGroups();
167        for (I18nizableText group : groups)
168        {
169            @SuppressWarnings("unchecked")
170            HashMap<String, Object> clonedInfos = (HashMap<String, Object>) infos.clone();
171            clonedInfos.put("group", group);
172            elmts.add(clonedInfos);
173        }
174        
175        return elmts;
176    }
177    
178    /**
179     * Gets the workflow step if the content is a <code>WorkflowAwareContent</code>
180     * @param content The content
181     * @return The workflow step's properties
182     */
183    protected Map<String, Object> workflowStepToJSON (Content content)
184    {
185        Map<String, Object> infos = new HashMap<>();
186        
187        if (content instanceof WorkflowAwareContent)
188        {
189            WorkflowAwareContent waContent = (WorkflowAwareContent) content;
190            
191            try
192            {
193                AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent);
194                long workflowId = waContent.getWorkflowId();
195                String workflowName = workflow.getWorkflowName(workflowId);
196                
197                Step currentStep = _getCurrentStep(waContent, workflow);
198                
199                int currentStepId = currentStep.getStepId();
200                
201                I18nizableText workflowStepName = new I18nizableText("application",  _workflowHelper.getStepName(workflowName, currentStepId));
202                
203                infos.put("id", String.valueOf(currentStepId));
204                infos.put("name", workflowStepName);
205                if ("application".equals(workflowStepName.getCatalogue()))
206                {
207                    infos.put("icon-small", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-small.png");
208                    infos.put("icon-medium", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-medium.png");
209                    infos.put("icon-large", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-large.png");
210                }
211                else
212                {
213                    String pluginName = workflowStepName.getCatalogue().substring("plugin.".length());
214                    infos.put("icon-small", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-small.png");
215                    infos.put("icon-medium", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-medium.png");
216                    infos.put("icon-large", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-large.png");
217                }
218            }
219            catch (AmetysRepositoryException e)
220            {
221                // Current step id was not positioned
222            }
223            catch (WorkflowException e)
224            {
225                // Ignore, just don't SAX the workflow step.
226            }
227        }
228        
229        return infos;
230    }
231    
232    /**
233     * Get a content's step, wherever it works on the base version or not.
234     * @param content the content.
235     * @param workflow the workflow instance bound to this content
236     * @return the content's workflow step.
237     * @throws WorkflowException if an error occurs.
238     */
239    protected Step _getCurrentStep(WorkflowAwareContent content, AmetysObjectWorkflow workflow) throws WorkflowException
240    {
241        long workflowId = content.getWorkflowId();
242        Step currentStep = (Step) workflow.getCurrentSteps(workflowId).get(0);
243        
244        if (content instanceof VersionAwareAmetysObject)
245        {
246            VersionAwareAmetysObject vaContent = (VersionAwareAmetysObject) content;
247            String currentRevision = vaContent.getRevision();
248            
249            if (currentRevision != null)
250            {
251                
252                String[] allRevisions = vaContent.getAllRevisions();
253                int currentRevIndex = ArrayUtils.indexOf(allRevisions, currentRevision);
254                
255                if (currentRevIndex > -1 && currentRevIndex < (allRevisions.length - 1))
256                {
257                    String nextRevision = allRevisions[currentRevIndex + 1];
258                    
259                    Date currentRevTimestamp = vaContent.getRevisionTimestamp();
260                    Date nextRevTimestamp = vaContent.getRevisionTimestamp(nextRevision);
261                    
262                    // Get all steps between the two revisions. 
263                    List<Step> steps = _workflowHelper.getStepsBetween(workflow, workflowId, currentRevTimestamp, nextRevTimestamp);
264                    
265                    // In the old workflow structure
266                    // We take the second, which is current revision's last step.
267                    if (steps.size() > 0 && steps.get(0) instanceof AmetysStep)
268                    {
269                        AmetysStep amStep = (AmetysStep) steps.get(0);
270                        if (amStep.getProperty("actionFinishDate") != null)
271                        {
272                            // New workflow structure detected: cut the first workflow step
273                            // in the list, as it belongs to the next version.
274                            steps = steps.subList(1, steps.size());
275                        }
276                    }
277                    
278                    // Order by step descendant.
279                    Collections.sort(steps, new Comparator<Step>()
280                    {
281                        public int compare(Step step1, Step step2)
282                        {
283                            return -Long.valueOf(step1.getId()).compareTo(step2.getId());
284                        }
285                    });
286                    
287                    // The first step in the list is the current version's last workflow step.
288                    if (steps.size() > 0)
289                    {
290                        currentStep = steps.get(0);
291                    }
292                }
293            }
294        }
295        return currentStep;
296    }
297
298}