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