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