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 = _searchModelManager.getExtension(modelId); 121 if (model == null) 122 { 123 throw new IllegalArgumentException("The search model '" + modelId + "' does not exists"); 124 } 125 126 // TODO Replace DynamicWrappedSearchUIModel? 127 if (restrictedContentTypes != null) 128 { 129 model = new DynamicWrappedSearchUIModel(model, restrictedContentTypes, _cTypeEP, getLogger(), _context, _serviceManager); 130 } 131 132 return getSearchModelInfo(model, contextualParameters); 133 134 } 135 136 /** 137 * Return information on a {@link SearchUIModel} object serialized in a Map. 138 * @param model The search model. 139 * @param contextualParameters The contextual parameters 140 * @return the detailed information serialized in a Map. 141 * @throws ProcessingException if an error occurs. 142 */ 143 public Map<String, Object> getSearchModelInfo(SearchUIModel model, Map<String, Object> contextualParameters) throws ProcessingException 144 { 145 Map<String, Object> jsonObject = new HashMap<>(); 146 147 jsonObject.put("pageSize", model.getPageSize(contextualParameters)); 148 jsonObject.put("workspace", model.getWorkspace(contextualParameters)); 149 jsonObject.put("searchUrl", model.getSearchUrl(contextualParameters)); 150 jsonObject.put("searchUrlPlugin", model.getSearchUrlPlugin(contextualParameters)); 151 jsonObject.put("exportCSVUrl", model.getExportCSVUrl(contextualParameters)); 152 jsonObject.put("exportCSVUrlPlugin", model.getExportCSVUrlPlugin(contextualParameters)); 153 jsonObject.put("exportXMLUrl", model.getExportXMLUrl(contextualParameters)); 154 jsonObject.put("exportXMLUrlPlugin", model.getExportXMLUrlPlugin(contextualParameters)); 155 jsonObject.put("printUrl", model.getPrintUrl(contextualParameters)); 156 jsonObject.put("printUrlPlugin", model.getPrintUrlPlugin(contextualParameters)); 157 158 jsonObject.put("simple-criteria", getCriteriaListInfo(model.getCriteria(contextualParameters))); 159 jsonObject.put("advanced-criteria", getAdvancedCriteriaListInfo(model.getAdvancedCriteria(contextualParameters))); 160 jsonObject.put("columns", getColumnListInfo(model.getResultFields(contextualParameters))); 161 162 jsonObject.put("hasFacets", !model.getFacetedCriteria(contextualParameters).isEmpty()); 163 164 return jsonObject; 165 } 166 167 /** 168 * Return information on a list of {@link SearchUIColumn}, serialized as a Map. 169 * @param columns the list of search columns. 170 * @return the detailed information serialized in a Map. 171 * @throws ProcessingException if an error occurs. 172 */ 173 public List<Object> getColumnListInfo(Map<String, ? extends SearchUIColumn> columns) throws ProcessingException 174 { 175 List<Object> jsonObject = new ArrayList<>(); 176 177 for (SearchUIColumn column : columns.values()) 178 { 179 jsonObject.add(getColumnInfo(column)); 180 } 181 182 return jsonObject; 183 } 184 185 /** 186 * Return information on a {@link SearchUIColumn}, serialized as a Map. 187 * @param column the search column. 188 * @return the detailed information serialized in a Map. 189 * @throws ProcessingException if an error occurs. 190 */ 191 public Map<String, Object> getColumnInfo(SearchUIColumn column) throws ProcessingException 192 { 193// Map<String, Object> jsonObject = _parameter2JsonObject(column); 194 Map<String, Object> jsonObject = new HashMap<>(); 195 196 // TODO Why replace . ? 197 jsonObject.put("id", column.getId().replace('.', '/')); 198 199 jsonObject.put("label", column.getLabel()); 200 jsonObject.put("description", column.getDescription()); 201 jsonObject.put("type", column.getType().name()); 202 203 jsonObject.put("hidden", column.isHidden()); 204 jsonObject.put("renderer", column.getRenderer()); 205 jsonObject.put("converter", column.getConverter()); 206 jsonObject.put("width", column.getWidth()); 207 jsonObject.put("editable", column.isEditable()); 208 jsonObject.put("contentType", column.getContentTypeId()); 209 jsonObject.put("sortable", column.isSortable()); 210 jsonObject.put("defaultSorter", column.getDefaultSorter()); 211 jsonObject.put("multiple", column.isMultiple()); 212 213 _putEnumerator(jsonObject, column.getEnumerator()); 214 _putValidator(jsonObject, column.getValidator()); 215 216 String widget = column.getWidget(); 217 if (widget != null) 218 { 219 jsonObject.put("widget", widget); 220 } 221 222 Map<String, I18nizableText> widgetParameters = column.getWidgetParameters(); 223 if (widgetParameters != null && !widgetParameters.isEmpty()) 224 { 225 jsonObject.put("widget-params", column.getWidgetParameters()); 226 } 227 228 return jsonObject; 229 } 230 231 /** 232 * Return information on a list of {@link SearchUICriterion}, serialized as a Map. 233 * @param criteria a map of search criteria. 234 * @return the detailed information serialized in a Map. 235 * @throws ProcessingException if an error occurs. 236 */ 237 @SuppressWarnings("unchecked") 238 public Map<String, Object> getCriteriaListInfo(Map<String, ? extends SearchUICriterion> criteria) throws ProcessingException 239 { 240 Map<String, Object> jsonObject = new LinkedHashMap<>(); 241 242 // TODO Test 243 Map<I18nizableText, List<SearchUICriterion>> criteriaByGroup = _getCriteriaByGroup(criteria); 244// Map<I18nizableText, List<SearchUICriterion>> criteriaByGroup = criteria.values().stream() 245// .collect(Collectors.groupingBy(SearchUICriterion::getGroup)); 246 247 for (I18nizableText group : criteriaByGroup.keySet()) 248 { 249 Map<String, Object> elements = new LinkedHashMap<>(); 250 251 for (SearchUICriterion sc : criteriaByGroup.get(group)) 252 { 253 elements.put(sc.getId(), getCriterionInfo(sc)); 254 } 255 256 Map<String, Object> fieldset = new LinkedHashMap<>(); 257 if (group != null) 258 { 259 fieldset.put("label", group); 260 } 261 fieldset.put("role", "fieldset"); 262 fieldset.put("elements", elements); 263 264 if (!jsonObject.containsKey("fieldsets")) 265 { 266 jsonObject.put("fieldsets", new ArrayList<Map<String, Object>>()); 267 } 268 269 ((List<Map<String, Object>>) jsonObject.get("fieldsets")).add(fieldset); 270 271 } 272 return jsonObject; 273 } 274 275 /** 276 * Return information on a list of advanced {@link SearchUICriterion}, serialized as a Map. 277 * @param criteria A map of advanced search criteria. 278 * @return the detailed information serialized in a Map. 279 * @throws ProcessingException if an error occurs. 280 */ 281 public Map<String, Object> getAdvancedCriteriaListInfo(Map<String, ? extends SearchUICriterion> criteria) throws ProcessingException 282 { 283 // No groups for advanced search 284 Map<String, Object> jsonObject = new LinkedHashMap<>(); 285 Map<String, Object> criteriaObject = new LinkedHashMap<>(); 286 287 for (SearchUICriterion sc : criteria.values()) 288 { 289 if (sc instanceof SystemSearchCriterion) 290 { 291 SystemSearchUICriterion sysCrit = (SystemSearchUICriterion) sc; 292 if (sysCrit.getSystemPropertyId().equals("contentLanguage")) 293 { 294 // Separate the language from the other criteria since it is mandatory 295 jsonObject.put("language", getCriterionInfo(sc)); 296 } 297 else 298 { 299 criteriaObject.put(sc.getId(), getCriterionInfo(sc)); 300 } 301 } 302 else 303 { 304 criteriaObject.put(sc.getId(), getCriterionInfo(sc)); 305 } 306 } 307 308 if (MapUtils.isNotEmpty(criteriaObject)) 309 { 310 jsonObject.put("criteria", criteriaObject); 311 } 312 313 return jsonObject; 314 } 315 316 /** 317 * Return information on a {@link SearchUICriterion}, serialized as a Map. 318 * @param criterion a search criterion. 319 * @return the detailed information serialized in a Map. 320 * @throws ProcessingException if an error occurs. 321 */ 322 public Map<String, Object> getCriterionInfo(SearchUICriterion criterion) throws ProcessingException 323 { 324 Map<String, Object> jsonObject = new HashMap<>(); 325 326 // TODO Parameter interface 327 _putCriterionParameter(jsonObject, criterion); 328 329 jsonObject.put("multiple", criterion.isMultiple()); 330 jsonObject.put("hidden", criterion.isHidden()); 331 332 String contentTypeId = criterion.getContentTypeId(); 333 if (contentTypeId != null) 334 { 335 jsonObject.put("contentType", contentTypeId); 336 } 337 338 jsonObject.put("criterionProperty", criterion.getFieldId()); 339 // TODO use operator.getName()? 340 if (criterion.getOperator() != null) 341 { 342 jsonObject.put("criterionOperator", criterion.getOperator().toString().toLowerCase()); 343 } 344 345// if (criterion.getValue() != null) 346// { 347// jsonObject.put("value", criterion.getValue()); 348// } 349 350 return jsonObject; 351 } 352 353 private Map<I18nizableText, List<SearchUICriterion>> _getCriteriaByGroup(Map<String, ? extends SearchUICriterion> criteria) 354 { 355 Map<I18nizableText, List<SearchUICriterion>> criteriaByGroup = new LinkedHashMap<>(); 356 357 for (SearchUICriterion sc : criteria.values()) 358 { 359 I18nizableText group = sc.getGroup(); 360 361 if (!criteriaByGroup.containsKey(group)) 362 { 363 criteriaByGroup.put(group, new ArrayList<SearchUICriterion>()); 364 } 365 366 criteriaByGroup.get(group).add(sc); 367 } 368 369 return criteriaByGroup; 370 } 371 372 private void _putCriterionParameter(Map<String, Object> jsonObject, SearchUICriterion criterion) throws ProcessingException 373 { 374 jsonObject.put("id", criterion.getId()); 375 jsonObject.put("label", criterion.getLabel()); 376 jsonObject.put("description", criterion.getDescription()); 377 jsonObject.put("type", criterion.getType().name()); 378 379 Object defaultValue = criterion.getDefaultValue(); 380 if (defaultValue != null) 381 { 382 jsonObject.put("default-value", defaultValue); 383 } 384 385 _putEnumerator(jsonObject, criterion.getEnumerator()); 386 _putValidator(jsonObject, criterion.getValidator()); 387 388 String widget = criterion.getWidget(); 389 if (widget != null) 390 { 391 jsonObject.put("widget", widget); 392 } 393 394 Map<String, I18nizableText> widgetParameters = criterion.getWidgetParameters(); 395 if (widgetParameters != null && !widgetParameters.isEmpty()) 396 { 397 jsonObject.put("widget-params", criterion.getWidgetParameters()); 398 } 399 } 400 401 private void _putEnumerator(Map<String, Object> jsonObject, Enumerator enumerator) throws ProcessingException 402 { 403 if (enumerator != null) 404 { 405 try 406 { 407 List<Map<String, Object>> options = new ArrayList<>(); 408 409 for (Map.Entry<Object, I18nizableText> entry : enumerator.getEntries().entrySet()) 410 { 411 String valueAsString = ParameterHelper.valueToString(entry.getKey()); 412 I18nizableText entryLabel = entry.getValue(); 413 414 Map<String, Object> option = new HashMap<>(); 415 option.put("label", entryLabel != null ? entryLabel : valueAsString); 416 option.put("value", valueAsString); 417 options.add(option); 418 } 419 420 jsonObject.put("enumeration", options); 421 } 422 catch (Exception e) 423 { 424 throw new ProcessingException("Unable to enumerate entries with enumerator: " + enumerator, e); 425 } 426 } 427 } 428 429 private void _putValidator(Map<String, Object> jsonObject, Validator validator) 430 { 431 if (validator != null) 432 { 433 Map<String, Object> val2Json = validator.toJson(); 434 if (val2Json.containsKey("mandatory")) 435 { 436 // Override mandatory property 437 val2Json.put("mandatory", true); 438 } 439 jsonObject.put("validation", val2Json); 440 } 441 } 442}