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