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.forms.data;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.Iterator;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Optional;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028import org.apache.avalon.framework.parameters.Parameters;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.cocoon.ProcessingException;
032import org.apache.cocoon.acting.ServiceableAction;
033import org.apache.cocoon.environment.ObjectModelHelper;
034import org.apache.cocoon.environment.Redirector;
035import org.apache.cocoon.environment.Request;
036import org.apache.cocoon.environment.SourceResolver;
037
038import org.ametys.core.cocoon.JSonReader;
039import org.ametys.core.util.DateUtils;
040import org.ametys.core.util.I18nUtils;
041import org.ametys.plugins.forms.Field;
042import org.ametys.plugins.forms.Field.FieldType;
043import org.ametys.plugins.forms.Form;
044import org.ametys.plugins.forms.FormsException;
045import org.ametys.plugins.forms.jcr.FormPropertiesManager;
046import org.ametys.plugins.forms.table.FormTableManager;
047import org.ametys.plugins.repository.AmetysObjectResolver;
048import org.ametys.plugins.workflow.store.JdbcWorkflowStore;
049import org.ametys.plugins.workflow.support.WorkflowProvider;
050import org.ametys.runtime.i18n.I18nizableText;
051
052import com.opensymphony.workflow.Workflow;
053import com.opensymphony.workflow.loader.StepDescriptor;
054import com.opensymphony.workflow.loader.WorkflowDescriptor;
055import com.opensymphony.workflow.spi.Step;
056
057/**
058 * Get the submitted entries of a form
059 *
060 */
061public class GetFormEntriesAction extends ServiceableAction
062{
063    /** Pattern for options value */
064    protected static final Pattern __OPTION_VALUE_PATTERN = Pattern.compile("^option-([0-9]+)-value$");
065            
066    /** The internationalizable text symbolizing the absence of workflow step */
067    protected static final I18nizableText __MESSAGE_NO_STEP = new I18nizableText("plugin.forms", "PLUGINS_FORMS_UITOOL_ENTRY_WORKFLOW_NO_WORKFLOW");
068    
069    /** The form properties manager. */
070    protected FormPropertiesManager _formPropertiesManager;
071    
072    /** The form data manager. */
073    protected FormTableManager _formTableManager;
074    
075    /** The ametys object resolver. */
076    protected AmetysObjectResolver _resolver;
077    
078    /** The workflow provider */
079    protected WorkflowProvider _workflowProvider;
080    
081    /** Utility component for internationalizable text */
082    protected I18nUtils _i18nUtils;
083    
084    @Override
085    public void service(ServiceManager smanager) throws ServiceException
086    {
087        super.service(smanager);
088        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
089        _formPropertiesManager = (FormPropertiesManager) smanager.lookup(FormPropertiesManager.ROLE);
090        _formTableManager = (FormTableManager) smanager.lookup(FormTableManager.ROLE);
091        _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE);
092        _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE);
093    }
094    
095    @Override
096    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
097    {
098        @SuppressWarnings("unchecked")
099        Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
100        
101        String siteName = (String) jsParameters.get("siteName");
102        String formId = (String) jsParameters.get("id");
103        
104        int start = jsParameters.containsKey("start") ? (int) jsParameters.get("start") : 0;
105        int limit = jsParameters.containsKey("limit") ? (int) jsParameters.get("limit") : Integer.MAX_VALUE;
106        
107        Form form = _formPropertiesManager.getForm(siteName, formId);
108        
109        if (form == null)
110        {
111            throw new ProcessingException("The form of ID '" + formId + " can't be found in the site '" + siteName + "'.");
112        }
113        
114        Workflow workflow = _workflowProvider.getExternalWorkflow(JdbcWorkflowStore.ROLE);
115        Map<String, Object> result = new HashMap<>();
116        List<Map<String, Object>> entries2json = new ArrayList<>();
117        try
118        {
119            Map<String, FieldValue> columns = _formTableManager.getColumns(form);
120            List<UserEntry> entries = _formTableManager.getSubmissions(form, columns, start, limit, null);
121            int totalSubmissions = _formTableManager.getTotalSubmissions(form.getId());
122            
123            result.put("id", form.getId());
124            result.put("label", form.getLabel());
125            result.put("total", totalSubmissions);
126            
127            for (UserEntry entry : entries)
128            {
129                entries2json.add(_entry2json(entry, workflow));
130            }
131        }
132        catch (FormsException e)
133        {
134            getLogger().error("Failed to get SQL table for form '" + form.getId() + "' for content of id.", e);
135        }
136        
137        result.put("entries", entries2json);
138        
139        Request request = ObjectModelHelper.getRequest(objectModel);
140        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
141        return EMPTY_MAP;
142    }
143    
144    private Map<String, Object> _entry2json(UserEntry entry, Workflow workflow)
145    {
146        Map<String, Object> entryAsMap = new HashMap<>();
147        
148        entryAsMap.put("id", entry.getId());
149        entryAsMap.put("submission-date", DateUtils.dateToString(entry.getCreationDate()));
150        
151        if (entry.getWorkflowId() != null)
152        {
153            entryAsMap.put("workflowStep", _workflow2json (entry, workflow));
154        }
155        
156        for (FieldValue fdValue : entry.getValues())
157        {
158            Object rawValue = fdValue.getValue();
159            if (rawValue != null)
160            {
161                entryAsMap.put(fdValue.getColumnName() + "_raw", rawValue);
162                entryAsMap.put(fdValue.getColumnName(), _getReadableValue(fdValue.getField(), rawValue));
163            }
164        }
165        
166        return entryAsMap;
167    }
168    
169    private Map<String, Object> _workflow2json (UserEntry entry, Workflow workflow)
170    {
171        Map<String, Object> workflowInfos = new LinkedHashMap<>();
172        
173        int currentStepId = 0;
174        
175        long workflowId = entry.getWorkflowId();
176        Iterator<Step> currentSteps = workflow.getCurrentSteps(workflowId).iterator();
177        
178        while (currentSteps.hasNext())
179        {
180            Step step = currentSteps.next();
181            currentStepId = step.getStepId();
182        }
183        
184        String workflowName = workflow.getWorkflowName(workflowId);
185        WorkflowDescriptor workflowDescriptor = workflow.getWorkflowDescriptor(workflowName);
186        StepDescriptor stepDescriptor = workflowDescriptor.getStep(currentStepId);
187        
188        if (stepDescriptor != null)
189        {
190            I18nizableText workflowStepName = new I18nizableText("application", stepDescriptor.getName());
191            
192            workflowInfos.put("stepId", currentStepId);
193            workflowInfos.put("name", workflowStepName);
194            
195            String[] icons = new String[] {"small", "medium", "large"};
196            for (String icon : icons)
197            {
198                if ("application".equals(workflowStepName.getCatalogue()))
199                {
200                    workflowInfos.put(icon + "Icon", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-" + icon + ".png");
201                }
202                else
203                {
204                    String pluginName = workflowStepName.getCatalogue().substring("plugin.".length());
205                    workflowInfos.put(icon + "Icon", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-" + icon + ".png");
206                }
207            }
208        }
209        
210        return workflowInfos;
211    }
212    
213    /**
214     * Get label to display for field
215     * @param field The field
216     * @param value The value
217     * @return The value to display
218     */
219    protected String _getReadableValue (Field field, Object value)
220    {
221        if (field.getType().equals(FieldType.SELECT) || field.getType().equals(FieldType.RADIO))
222        {
223            Map<String, String> properties = field.getProperties();
224            for (String key : properties.keySet())
225            {
226                Matcher matcher = __OPTION_VALUE_PATTERN.matcher(key);
227                if (matcher.matches())
228                {
229                    if (value.equals(properties.get(key)))
230                    {
231                        String index = matcher.group(1);
232                        if (properties.containsKey("option-" + index + "-label"))
233                        {
234                            return properties.get("option-" + index + "-label");
235                        }
236                    }
237                }
238            }
239        }
240        
241        return Optional.ofNullable(value)
242                       .map(Object::toString)
243                       .orElse(null);
244    }
245}