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