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