001/* 002 * Copyright 2013 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.cms.search.ui.model; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026import org.apache.avalon.framework.component.Component; 027import org.apache.avalon.framework.context.Context; 028import org.apache.avalon.framework.context.ContextException; 029import org.apache.avalon.framework.context.Contextualizable; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.cocoon.ProcessingException; 034import org.apache.commons.collections.MapUtils; 035 036import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 037import org.ametys.cms.search.model.SearchModel; 038import org.ametys.cms.search.model.SystemSearchCriterion; 039import org.ametys.cms.search.ui.model.impl.SystemSearchUICriterion; 040import org.ametys.core.ui.Callable; 041import org.ametys.runtime.i18n.I18nizableText; 042import org.ametys.runtime.parameter.Enumerator; 043import org.ametys.runtime.parameter.ParameterHelper; 044import org.ametys.runtime.parameter.Validator; 045import org.ametys.runtime.plugin.component.AbstractLogEnabled; 046 047/** 048 * Client interaction helper for {@link SearchUIModel}. 049 */ 050public class SearchUIModelHelper extends AbstractLogEnabled implements Component, Serviceable, Contextualizable 051{ 052 /** The component role. */ 053 public static final String ROLE = SearchUIModelHelper.class.getName(); 054 055 private SearchUIModelExtensionPoint _searchModelManager; 056 private ServiceManager _serviceManager; 057 private ContentTypeExtensionPoint _cTypeEP; 058 private Context _context; 059 060 061 @Override 062 public void service(ServiceManager smanager) throws ServiceException 063 { 064 _serviceManager = smanager; 065 _searchModelManager = (SearchUIModelExtensionPoint) smanager.lookup(SearchUIModelExtensionPoint.ROLE); 066 _cTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 067 } 068 069 @Override 070 public void contextualize(Context context) throws ContextException 071 { 072 _context = context; 073 } 074 075 /** 076 * Get all the real content types that a model works on (the included content types, minus the excluded types). 077 * @param model the search model. 078 * @param contextualParameters the contextual parameters. 079 * @return a Set of the content type IDs. 080 */ 081 public Set<String> getAllContentTypes(SearchModel model, Map<String, Object> contextualParameters) 082 { 083 Set<String> allContentTypes = new HashSet<>(); 084 085 Set<String> modelCTypes = model.getContentTypes(contextualParameters); 086 Set<String> modelExcludedCTypes = model.getExcludedContentTypes(contextualParameters); 087 088 if (modelCTypes.isEmpty()) 089 { 090 // Empty "declared" content types: the model works on all content types. 091 allContentTypes.addAll(_cTypeEP.getExtensionsIds()); 092 } 093 else 094 { 095 // Otherwise, add all declared content types and their sub-types. 096 for (String cTypeId : modelCTypes) 097 { 098 allContentTypes.add(cTypeId); 099 allContentTypes.addAll(_cTypeEP.getSubTypes(cTypeId)); 100 } 101 } 102 103 // Remove all excluded types. 104 allContentTypes.removeAll(modelExcludedCTypes); 105 106 return allContentTypes; 107 } 108 109 /** 110 * Get the search model configuration as JSON object 111 * @param modelId The id of search model 112 * @param restrictedContentTypes The restricted content types. Can be null. 113 * @param contextualParameters the contextual parameters 114 * @return The search model configuration in a Map 115 * @throws ProcessingException if an error occurred 116 */ 117 @Callable 118 public Map<String, Object> getSearchModelConfiguration(String modelId, List<String> restrictedContentTypes, Map<String, Object> contextualParameters) throws ProcessingException 119 { 120 SearchUIModel model = _getModel(modelId, restrictedContentTypes); 121 return getSearchModelInfo(model, contextualParameters); 122 123 } 124 125 /** 126 * Get the column configurations of search model as JSON object 127 * @param modelId The id of search model 128 * @param restrictedContentTypes The restricted content types. Can be null. 129 * @param contextualParameters the contextual parameters 130 * @return The column configurations in a List 131 * @throws ProcessingException if an error occurred 132 */ 133 @Callable 134 public List<Object> getColumnConfigurations(String modelId, List<String> restrictedContentTypes, Map<String, Object> contextualParameters) throws ProcessingException 135 { 136 SearchUIModel model = _getModel(modelId, restrictedContentTypes); 137 return getColumnListInfo(model.getResultFields(contextualParameters)); 138 } 139 140 private SearchUIModel _getModel(String modelId, List<String> restrictedContentTypes) 141 { 142 SearchUIModel model = _searchModelManager.getExtension(modelId); 143 if (model == null) 144 { 145 throw new IllegalArgumentException("The search model '" + modelId + "' does not exists"); 146 } 147 148 // TODO Replace DynamicWrappedSearchUIModel? 149 if (restrictedContentTypes != null) 150 { 151 model = new DynamicWrappedSearchUIModel(model, restrictedContentTypes, _cTypeEP, getLogger(), _context, _serviceManager); 152 } 153 154 return model; 155 } 156 157 /** 158 * Return information on a {@link SearchUIModel} object serialized in a Map. 159 * @param model The search model. 160 * @param contextualParameters The contextual parameters 161 * @return the detailed information serialized in a Map. 162 * @throws ProcessingException if an error occurs. 163 */ 164 public Map<String, Object> getSearchModelInfo(SearchUIModel model, Map<String, Object> contextualParameters) throws ProcessingException 165 { 166 Map<String, Object> jsonObject = new HashMap<>(); 167 168 jsonObject.put("pageSize", model.getPageSize(contextualParameters)); 169 jsonObject.put("workspace", model.getWorkspace(contextualParameters)); 170 jsonObject.put("searchUrl", model.getSearchUrl(contextualParameters)); 171 jsonObject.put("searchUrlPlugin", model.getSearchUrlPlugin(contextualParameters)); 172 jsonObject.put("exportCSVUrl", model.getExportCSVUrl(contextualParameters)); 173 jsonObject.put("exportCSVUrlPlugin", model.getExportCSVUrlPlugin(contextualParameters)); 174 jsonObject.put("exportDOCUrl", model.getExportDOCUrl(contextualParameters)); 175 jsonObject.put("exportDOCUrlPlugin", model.getExportDOCUrlPlugin(contextualParameters)); 176 jsonObject.put("exportXMLUrl", model.getExportXMLUrl(contextualParameters)); 177 jsonObject.put("exportXMLUrlPlugin", model.getExportXMLUrlPlugin(contextualParameters)); 178 jsonObject.put("printUrl", model.getPrintUrl(contextualParameters)); 179 jsonObject.put("printUrlPlugin", model.getPrintUrlPlugin(contextualParameters)); 180 jsonObject.put("summaryView", model.getSummaryView()); 181 182 jsonObject.put("simple-criteria", getCriteriaListInfo(model.getCriteria(contextualParameters))); 183 jsonObject.put("advanced-criteria", getAdvancedCriteriaListInfo(model.getAdvancedCriteria(contextualParameters))); 184 jsonObject.put("columns", getColumnListInfo(model.getResultFields(contextualParameters))); 185 186 jsonObject.put("hasFacets", !model.getFacetedCriteria(contextualParameters).isEmpty()); 187 188 return jsonObject; 189 } 190 191 /** 192 * Return information on a list of {@link SearchUIColumn}, serialized as a Map. 193 * @param columns the list of search columns. 194 * @return the detailed information serialized in a Map. 195 * @throws ProcessingException if an error occurs. 196 */ 197 public List<Object> getColumnListInfo(Map<String, ? extends SearchUIColumn> columns) throws ProcessingException 198 { 199 List<Object> jsonObject = new ArrayList<>(); 200 201 for (SearchUIColumn column : columns.values()) 202 { 203 jsonObject.add(getColumnInfo(column)); 204 } 205 206 return jsonObject; 207 } 208 209 /** 210 * Return information on a {@link SearchUIColumn}, serialized as a Map. 211 * @param column the search column. 212 * @return the detailed information serialized in a Map. 213 * @throws ProcessingException if an error occurs. 214 */ 215 public Map<String, Object> getColumnInfo(SearchUIColumn column) throws ProcessingException 216 { 217// Map<String, Object> jsonObject = _parameter2JsonObject(column); 218 Map<String, Object> jsonObject = new HashMap<>(); 219 220 // TODO Why replace . ? 221 jsonObject.put("id", column.getId().replace('.', '/')); 222 223 jsonObject.put("label", column.getLabel()); 224 jsonObject.put("description", column.getDescription()); 225 jsonObject.put("type", column.getType().name()); 226 227 jsonObject.put("hidden", column.isHidden()); 228 jsonObject.put("renderer", column.getRenderer()); 229 jsonObject.put("converter", column.getConverter()); 230 jsonObject.put("width", column.getWidth()); 231 jsonObject.put("editable", column.isEditable()); 232 jsonObject.put("contentType", column.getContentTypeId()); 233 jsonObject.put("sortable", column.isSortable()); 234 jsonObject.put("defaultSorter", column.getDefaultSorter()); 235 jsonObject.put("multiple", column.isMultiple()); 236 237 _putEnumerator(jsonObject, column.getEnumerator()); 238 _putValidator(jsonObject, column.getValidator()); 239 240 String widget = column.getWidget(); 241 if (widget != null) 242 { 243 jsonObject.put("widget", widget); 244 } 245 246 Map<String, I18nizableText> widgetParameters = column.getWidgetParameters(); 247 if (widgetParameters != null && !widgetParameters.isEmpty()) 248 { 249 jsonObject.put("widget-params", column.getWidgetParameters()); 250 } 251 252 return jsonObject; 253 } 254 255 /** 256 * Return information on a list of {@link SearchUICriterion}, serialized as a Map. 257 * @param criteria a map of search criteria. 258 * @return the detailed information serialized in a Map. 259 * @throws ProcessingException if an error occurs. 260 */ 261 @SuppressWarnings("unchecked") 262 public Map<String, Object> getCriteriaListInfo(Map<String, ? extends SearchUICriterion> criteria) throws ProcessingException 263 { 264 Map<String, Object> jsonObject = new LinkedHashMap<>(); 265 266 // TODO Test 267 Map<I18nizableText, List<SearchUICriterion>> criteriaByGroup = _getCriteriaByGroup(criteria); 268// Map<I18nizableText, List<SearchUICriterion>> criteriaByGroup = criteria.values().stream() 269// .collect(Collectors.groupingBy(SearchUICriterion::getGroup)); 270 271 for (I18nizableText group : criteriaByGroup.keySet()) 272 { 273 Map<String, Object> elements = new LinkedHashMap<>(); 274 275 for (SearchUICriterion sc : criteriaByGroup.get(group)) 276 { 277 elements.put(sc.getId(), getCriterionInfo(sc)); 278 } 279 280 Map<String, Object> fieldset = new LinkedHashMap<>(); 281 if (group != null) 282 { 283 fieldset.put("label", group); 284 } 285 fieldset.put("role", "fieldset"); 286 fieldset.put("elements", elements); 287 288 if (!jsonObject.containsKey("fieldsets")) 289 { 290 jsonObject.put("fieldsets", new ArrayList<Map<String, Object>>()); 291 } 292 293 ((List<Map<String, Object>>) jsonObject.get("fieldsets")).add(fieldset); 294 295 } 296 return jsonObject; 297 } 298 299 /** 300 * Return information on a list of advanced {@link SearchUICriterion}, serialized as a Map. 301 * @param criteria A map of advanced search criteria. 302 * @return the detailed information serialized in a Map. 303 * @throws ProcessingException if an error occurs. 304 */ 305 public Map<String, Object> getAdvancedCriteriaListInfo(Map<String, ? extends SearchUICriterion> criteria) throws ProcessingException 306 { 307 // No groups for advanced search 308 Map<String, Object> jsonObject = new LinkedHashMap<>(); 309 Map<String, Object> criteriaObject = new LinkedHashMap<>(); 310 311 for (SearchUICriterion sc : criteria.values()) 312 { 313 if (sc instanceof SystemSearchCriterion) 314 { 315 SystemSearchUICriterion sysCrit = (SystemSearchUICriterion) sc; 316 if (sysCrit.getSystemPropertyId().equals("contentLanguage")) 317 { 318 // Separate the language from the other criteria since it is mandatory 319 jsonObject.put("language", getCriterionInfo(sc)); 320 } 321 else 322 { 323 criteriaObject.put(sc.getId(), getCriterionInfo(sc)); 324 } 325 } 326 else 327 { 328 criteriaObject.put(sc.getId(), getCriterionInfo(sc)); 329 } 330 } 331 332 if (MapUtils.isNotEmpty(criteriaObject)) 333 { 334 jsonObject.put("criteria", criteriaObject); 335 } 336 337 return jsonObject; 338 } 339 340 /** 341 * Return information on a {@link SearchUICriterion}, serialized as a Map. 342 * @param criterion a search criterion. 343 * @return the detailed information serialized in a Map. 344 * @throws ProcessingException if an error occurs. 345 */ 346 public Map<String, Object> getCriterionInfo(SearchUICriterion criterion) throws ProcessingException 347 { 348 Map<String, Object> jsonObject = new HashMap<>(); 349 350 // TODO Parameter interface 351 _putCriterionParameter(jsonObject, criterion); 352 353 jsonObject.put("multiple", criterion.isMultiple()); 354 jsonObject.put("hidden", criterion.isHidden()); 355 356 String contentTypeId = criterion.getContentTypeId(); 357 if (contentTypeId != null) 358 { 359 jsonObject.put("contentType", contentTypeId); 360 } 361 362 jsonObject.put("criterionProperty", criterion.getFieldId()); 363 // TODO use operator.getName()? 364 if (criterion.getOperator() != null) 365 { 366 jsonObject.put("criterionOperator", criterion.getOperator().toString().toLowerCase()); 367 } 368 369// if (criterion.getValue() != null) 370// { 371// jsonObject.put("value", criterion.getValue()); 372// } 373 374 return jsonObject; 375 } 376 377 private Map<I18nizableText, List<SearchUICriterion>> _getCriteriaByGroup(Map<String, ? extends SearchUICriterion> criteria) 378 { 379 Map<I18nizableText, List<SearchUICriterion>> criteriaByGroup = new LinkedHashMap<>(); 380 381 for (SearchUICriterion sc : criteria.values()) 382 { 383 I18nizableText group = sc.getGroup(); 384 385 if (!criteriaByGroup.containsKey(group)) 386 { 387 criteriaByGroup.put(group, new ArrayList<SearchUICriterion>()); 388 } 389 390 criteriaByGroup.get(group).add(sc); 391 } 392 393 return criteriaByGroup; 394 } 395 396 private void _putCriterionParameter(Map<String, Object> jsonObject, SearchUICriterion criterion) throws ProcessingException 397 { 398 jsonObject.put("id", criterion.getId()); 399 jsonObject.put("label", criterion.getLabel()); 400 jsonObject.put("description", criterion.getDescription()); 401 jsonObject.put("type", criterion.getType().name()); 402 403 Object defaultValue = criterion.getDefaultValue(); 404 if (defaultValue != null) 405 { 406 jsonObject.put("default-value", defaultValue); 407 } 408 409 _putEnumerator(jsonObject, criterion.getEnumerator()); 410 _putValidator(jsonObject, criterion.getValidator()); 411 412 String widget = criterion.getWidget(); 413 if (widget != null) 414 { 415 jsonObject.put("widget", widget); 416 } 417 418 Map<String, I18nizableText> widgetParameters = criterion.getWidgetParameters(); 419 if (widgetParameters != null && !widgetParameters.isEmpty()) 420 { 421 jsonObject.put("widget-params", criterion.getWidgetParameters()); 422 } 423 } 424 425 private void _putEnumerator(Map<String, Object> jsonObject, Enumerator enumerator) throws ProcessingException 426 { 427 if (enumerator != null) 428 { 429 try 430 { 431 List<Map<String, Object>> options = new ArrayList<>(); 432 433 for (Map.Entry<Object, I18nizableText> entry : enumerator.getEntries().entrySet()) 434 { 435 String valueAsString = ParameterHelper.valueToString(entry.getKey()); 436 I18nizableText entryLabel = entry.getValue(); 437 438 Map<String, Object> option = new HashMap<>(); 439 option.put("label", entryLabel != null ? entryLabel : valueAsString); 440 option.put("value", valueAsString); 441 options.add(option); 442 } 443 444 jsonObject.put("enumeration", options); 445 jsonObject.put("enumerationConfig", enumerator.getConfiguration()); 446 } 447 catch (Exception e) 448 { 449 throw new ProcessingException("Unable to enumerate entries with enumerator: " + enumerator, e); 450 } 451 } 452 } 453 454 private void _putValidator(Map<String, Object> jsonObject, Validator validator) 455 { 456 if (validator != null) 457 { 458 Map<String, Object> val2Json = validator.getConfiguration(); 459 if (val2Json.containsKey("mandatory")) 460 { 461 // Override mandatory property 462 val2Json.put("mandatory", true); 463 } 464 jsonObject.put("validation", val2Json); 465 } 466 } 467}