001/* 002 * Copyright 2022 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.actions; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.LinkedHashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.stream.Collectors; 024 025import org.apache.avalon.framework.parameters.Parameters; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.cocoon.ProcessingException; 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.lang3.StringUtils; 035 036import org.ametys.core.cocoon.JSonReader; 037import org.ametys.core.user.User; 038import org.ametys.core.user.UserIdentity; 039import org.ametys.core.user.UserManager; 040import org.ametys.core.util.I18nUtils; 041import org.ametys.core.util.JSONUtils; 042import org.ametys.plugins.core.user.UserHelper; 043import org.ametys.plugins.forms.dao.FormEntryDAO; 044import org.ametys.plugins.forms.dao.FormEntryDAO.Sort; 045import org.ametys.plugins.forms.helper.LimitedEntriesHelper; 046import org.ametys.plugins.forms.question.types.MatrixQuestionType; 047import org.ametys.plugins.forms.repository.Form; 048import org.ametys.plugins.forms.repository.FormEntry; 049import org.ametys.plugins.forms.repository.FormQuestion; 050import org.ametys.plugins.repository.AmetysObjectResolver; 051import org.ametys.plugins.repository.model.RepositoryDataContext; 052import org.ametys.plugins.workflow.support.WorkflowProvider; 053import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 054import org.ametys.runtime.i18n.I18nizableText; 055import org.ametys.runtime.model.Model; 056import org.ametys.runtime.model.ModelItem; 057import org.ametys.runtime.model.type.DataContext; 058import org.ametys.runtime.model.type.ElementType; 059 060import com.opensymphony.workflow.loader.StepDescriptor; 061import com.opensymphony.workflow.loader.WorkflowDescriptor; 062import com.opensymphony.workflow.spi.Step; 063 064/** 065 * Get the submitted entries of a form 066 * 067 */ 068public class GetFormEntriesAction extends ServiceableAction 069{ 070 /** The id of the column for entry status */ 071 public static final String FORM_ENTRY_STATUS_ID = "workflowStatus"; 072 073 /** Constant for whether an entry is in a queue or not */ 074 public static final String QUEUE_STATUS = "queue-status"; 075 076 /** The id of the column for entry active or not */ 077 public static final String FORM_ENTRY_ACTIVE = "active"; 078 079 /** The ametys object resolver. */ 080 protected AmetysObjectResolver _resolver; 081 082 /** The form entry DAO */ 083 protected FormEntryDAO _formEntryDAO; 084 085 /** The handle limited entries helper */ 086 protected LimitedEntriesHelper _handleLimitedEntriesHelper; 087 088 /** The user helper */ 089 protected UserHelper _userHelper; 090 091 /** The user manager */ 092 protected UserManager _userManager; 093 094 /** The workflow provider */ 095 protected WorkflowProvider _workflowProvider; 096 097 /** The json utils */ 098 protected JSONUtils _jsonUtils; 099 100 /** The I18n utils */ 101 protected I18nUtils _i18nUtils; 102 103 @Override 104 public void service(ServiceManager smanager) throws ServiceException 105 { 106 super.service(smanager); 107 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 108 _formEntryDAO = (FormEntryDAO) smanager.lookup(FormEntryDAO.ROLE); 109 _userHelper = (UserHelper) smanager.lookup(UserHelper.ROLE); 110 _userManager = (UserManager) smanager.lookup(UserManager.ROLE); 111 _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE); 112 _handleLimitedEntriesHelper = (LimitedEntriesHelper) smanager.lookup(LimitedEntriesHelper.ROLE); 113 _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE); 114 _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE); 115 } 116 117 @Override 118 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 119 { 120 @SuppressWarnings("unchecked") 121 Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 122 123 String formId = (String) jsParameters.get("formId"); 124 Form form = _resolver.resolveById(formId); 125 if (form == null) 126 { 127 throw new ProcessingException("The form of ID '" + formId + " can't be found."); 128 } 129 130 _formEntryDAO.checkHandleDataRight(form); 131 132 Integer offset = _getIntValue(jsParameters, "start", 0); 133 Integer limit = _getIntValue(jsParameters, "limit", Integer.MAX_VALUE); 134 135 String sortInfo = (String) jsParameters.get("sort"); 136 String groupInfo = (String) jsParameters.get("group"); 137 List<Sort> sorts = _getSorts(formId, sortInfo, groupInfo); 138 139 Map<String, Object> matrixLabels = _getMatrixInfos(form); 140 141 Map<String, Object> result = new HashMap<>(); 142 List<Map<String, Object>> entries2json = new ArrayList<>(); 143 try 144 { 145 List<FormEntry> entries = _getEntries(form, sorts); 146 147 int totalSubmissions = entries.size(); 148 result.put("total", totalSubmissions); 149 150 int currentLimit = 0; 151 while (currentLimit < limit && offset < totalSubmissions) 152 { 153 FormEntry entry = entries.get(offset); 154 Model entryModel = (Model) (entry.getModel().toArray())[0]; 155 Map<String, Object> entryData = new LinkedHashMap<>(); 156 for (ModelItem modelItem : entryModel.getModelItems()) 157 { 158 String name = modelItem.getName(); 159 FormQuestion question = form.getQuestion(name); 160 if (question != null) 161 { 162 Object value = question.getType().valueToJSONForClient(entry.getValue(name), question, entry, modelItem); 163 if (value != null) 164 { 165 entryData.put(name, value); 166 } 167 } 168 else 169 { 170 DataContext context = RepositoryDataContext.newInstance() 171 .withObject(entry) 172 .withDataPath(modelItem.getPath()); 173 Object value = ((ElementType) modelItem.getType()).valueToJSONForClient(entry.getValue(name), context); 174 if (value != null) 175 { 176 entryData.put(name, value); 177 } 178 } 179 180 if (matrixLabels.containsKey(name)) 181 { 182 entryData.put(name + "matrice-labels", matrixLabels.get(name)); 183 } 184 } 185 186 entryData.put(FormEntry.ATTRIBUTE_USER, _userHelper.user2json(entry.getUser(), true)); 187 188 if (form.hasWorkflow()) 189 { 190 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(entry); 191 WorkflowDescriptor workflowDescriptor = workflow.getWorkflowDescriptor(form.getWorkflowName()); 192 Step currentStep = (Step) workflow.getCurrentSteps(entry.getWorkflowId()).iterator().next(); 193 194 StepDescriptor stepDescriptor = workflowDescriptor.getStep(currentStep.getStepId()); 195 I18nizableText workflowStepName = new I18nizableText("application", stepDescriptor.getName()); 196 entryData.put(FormEntry.SYSTEM_ATTRIBUTE_PREFIX + FORM_ENTRY_STATUS_ID, workflowStepName); 197 } 198 199 if (form.isQueueEnabled() && entry.isActive()) 200 { 201 entryData.put(FormEntry.SYSTEM_ATTRIBUTE_PREFIX + QUEUE_STATUS, _handleLimitedEntriesHelper.isInQueue(entry)); 202 } 203 204 entryData.put(FormEntry.SYSTEM_ATTRIBUTE_PREFIX + FORM_ENTRY_ACTIVE, entry.isActive()); 205 entryData.put(FormEntry.SYSTEM_ATTRIBUTE_PREFIX + "entryId", entries.get(offset).getId()); 206 entries2json.add(entryData); 207 currentLimit++; 208 offset++; 209 } 210 } 211 catch (Exception e) 212 { 213 getLogger().error("Failed to get entries for form '" + form.getId() + "'.", e); 214 } 215 216 result.put("entries", entries2json); 217 218 Request request = ObjectModelHelper.getRequest(objectModel); 219 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 220 return EMPTY_MAP; 221 } 222 223 /** 224 * Get entries and sort 225 * @param form the form 226 * @param sorts the sorts 227 * @return the list of entries 228 */ 229 protected List<FormEntry> _getEntries(Form form, List<Sort> sorts) 230 { 231 if (sorts.isEmpty()) 232 { 233 return form.getEntries(); 234 } 235 else 236 { 237 if (sorts.size() == 1 && ("user".equals(sorts.get(0).attributeName()) || FORM_ENTRY_STATUS_ID.equals(sorts.get(0).attributeName()))) 238 { 239 String attributeName = sorts.get(0).attributeName(); 240 String direction = sorts.get(0).direction(); 241 if ("user".equals(attributeName)) 242 { 243 return form.getEntries() 244 .stream() 245 .sorted((e1, e2) -> "ascending".equals(direction) 246 ? StringUtils.compare(_getUserSortedName(e1), _getUserSortedName(e2)) 247 : StringUtils.compare(_getUserSortedName(e2), _getUserSortedName(e1)) 248 ) 249 .collect(Collectors.toList()); 250 } 251 else if (FORM_ENTRY_STATUS_ID.equals(attributeName)) 252 { 253 return form.getEntries() 254 .stream() 255 .sorted((e1, e2) -> "ascending".equals(direction) 256 ? StringUtils.compare(_getWorkflowLabel(e1), _getWorkflowLabel(e2)) 257 : StringUtils.compare(_getWorkflowLabel(e2), _getWorkflowLabel(e1)) 258 ) 259 .collect(Collectors.toList()); 260 } 261 262 return form.getEntries(); 263 } 264 else 265 { 266 return _formEntryDAO.getFormEntries(form, false, sorts); 267 } 268 } 269 } 270 271 private String _getUserSortedName(FormEntry entry) 272 { 273 UserIdentity userId = entry.getUser(); 274 if (userId != null) 275 { 276 User user = _userManager.getUser(userId); 277 return user != null ? user.getSortableName() : null; 278 } 279 280 return null; 281 } 282 283 private String _getWorkflowLabel(FormEntry entry) 284 { 285 Form form = entry.getForm(); 286 if (form.hasWorkflow()) 287 { 288 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(entry); 289 WorkflowDescriptor workflowDescriptor = workflow.getWorkflowDescriptor(form.getWorkflowName()); 290 Step currentStep = (Step) workflow.getCurrentSteps(entry.getWorkflowId()).iterator().next(); 291 292 StepDescriptor stepDescriptor = workflowDescriptor.getStep(currentStep.getStepId()); 293 I18nizableText workflowStepName = new I18nizableText("application", stepDescriptor.getName()); 294 return _i18nUtils.translate(workflowStepName); 295 } 296 297 return null; 298 } 299 300 /** 301 * Get sorts of search form entry 302 * @param formId the form id 303 * @param sortString the sort as string 304 * @param groupString the group as string 305 * @return the list of sort 306 */ 307 protected List<Sort> _getSorts(String formId, String sortString, String groupString) 308 { 309 List<Sort> sort = new ArrayList<>(); 310 311 List<Object> sortList = new ArrayList<>(_jsonUtils.convertJsonToList(sortString)); 312 if (StringUtils.isNotEmpty(groupString)) 313 { 314 // Grouping will be treated server side as a sort. It just needs to be before all the sorters 315 sortList.add(0, _jsonUtils.convertJsonToMap(groupString)); 316 } 317 318 for (Object object : sortList) 319 { 320 if (object instanceof Map) 321 { 322 Map map = (Map) object; 323 String fieldId = (String) map.get("property"); 324 boolean ascending = "ASC".equals(map.get("direction")); 325 326 sort.add(new Sort( 327 StringUtils.contains(fieldId, formId) ? StringUtils.substringAfter(fieldId, formId) : fieldId, 328 ascending ? "ascending" : "descending" 329 )); 330 } 331 } 332 333 return sort; 334 } 335 336 /** 337 * Get informations of matrix questions 338 * @param form the form 339 * @return the map of informations 340 */ 341 protected Map<String, Object> _getMatrixInfos(Form form) 342 { 343 Map<String, Object> matrixLabels = new HashMap<>(); 344 List<FormQuestion> matrixQuestions = form.getQuestions() 345 .stream() 346 .filter(q -> q.getType() instanceof MatrixQuestionType) 347 .toList(); 348 for (FormQuestion matrixQ : matrixQuestions) 349 { 350 MatrixQuestionType type = (MatrixQuestionType) matrixQ.getType(); 351 Map<String, String> columns = type.getColumns(matrixQ); 352 Map<String, String> rows = type.getRows(matrixQ); 353 Map<String, Map<String, String>> matrixInfo = (columns == null || rows == null) 354 ? Map.of() 355 : Map.of( 356 "columns", type.getColumns(matrixQ), 357 "rows", type.getRows(matrixQ) 358 ); 359 matrixLabels.put(matrixQ.getNameForForm(), matrixInfo); 360 } 361 return matrixLabels; 362 } 363 364 private int _getIntValue(Map<String, Object> values, String key, int defaultValue) 365 { 366 if (values.containsKey(key)) 367 { 368 return Integer.valueOf(values.get(key).toString()).intValue(); 369 } 370 371 return defaultValue; 372 } 373}