001/* 002 * Copyright 2015 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.contenttype; 017 018import java.util.Arrays; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.LinkedList; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027 028import org.apache.avalon.framework.parameters.Parameters; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.cocoon.acting.ServiceableAction; 032import org.apache.cocoon.environment.ObjectModelHelper; 033import org.apache.cocoon.environment.Redirector; 034import org.apache.cocoon.environment.Request; 035import org.apache.cocoon.environment.SourceResolver; 036import org.apache.commons.lang.BooleanUtils; 037import org.apache.commons.lang3.ArrayUtils; 038import org.apache.commons.lang3.ObjectUtils; 039import org.apache.commons.lang3.StringUtils; 040 041import org.ametys.cms.data.type.ModelItemTypeConstants; 042import org.ametys.cms.repository.DefaultContent; 043import org.ametys.core.cocoon.JSonReader; 044import org.ametys.core.util.I18nUtils; 045import org.ametys.runtime.i18n.I18nizableText; 046import org.ametys.runtime.model.ElementDefinition; 047import org.ametys.runtime.model.ModelItem; 048 049/** 050 * Get the common attributes between given content types and/or among given contents 051 */ 052public class GetCommonAttributesAction extends ServiceableAction 053{ 054 055 /** Not sortable attribute types */ 056 @SuppressWarnings("static-access") 057 protected static final String[] __NOT_SORTABLE_TYPE_IDS = 058 { 059 ModelItemTypeConstants.BINARY_ELEMENT_TYPE_ID, 060 ModelItemTypeConstants.FILE_ELEMENT_TYPE_ID, 061 ModelItemTypeConstants.REFERENCE_ELEMENT_TYPE_ID, 062 ModelItemTypeConstants.COMPOSITE_TYPE_ID 063 }; 064 065 /** Allow enumerated attributes */ 066 protected static final String __ALLOW_ENUMERATED = "ENUMERATED"; 067 068 /** Allow not enumerated attributes */ 069 protected static final String __ALLOW_NOT_ENUMERATED = "NOT-ENUMERATED"; 070 071 /** The content type EP */ 072 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 073 074 /** The content types Helper */ 075 protected ContentTypesHelper _contentTypesHelper; 076 077 /** I18n utils */ 078 protected I18nUtils _i18nUtils; 079 080 @Override 081 public void service(ServiceManager smanager) throws ServiceException 082 { 083 super.service(smanager); 084 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 085 _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 086 _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE); 087 } 088 089 @Override 090 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 091 { 092 Request request = ObjectModelHelper.getRequest(objectModel); 093 094 String viewName = request.getParameter("viewName"); 095 List<String> acceptedTypes = Arrays.asList(ObjectUtils.defaultIfNull(request.getParameterValues("acceptedTypes"), ArrayUtils.EMPTY_STRING_ARRAY)); 096 boolean onlySortable = BooleanUtils.toBoolean(request.getParameter("onlySortable")); // false by default 097 boolean includeComposite = BooleanUtils.toBoolean(request.getParameter("includeComposite")); // false by default 098 boolean includeSubRepeaters = !"false".equals(request.getParameter("includeSubRepeaters")); // true by default 099 boolean withFullLabel = BooleanUtils.toBoolean(request.getParameter("withFullLabel")); // false by default 100 boolean withLastModified = BooleanUtils.toBoolean(request.getParameter("withLastModified")); // false by default 101 boolean withLastValidation = BooleanUtils.toBoolean(request.getParameter("withLastValidation")); // false by default 102 boolean withCreationDate = BooleanUtils.toBoolean(request.getParameter("withCreationDate")); // false by default 103 boolean withResources = BooleanUtils.toBoolean(request.getParameter("withResources")); // false by default 104 105 Set<String> contentTypeIds = getContentTypes(request, withResources); 106 boolean hasResources = contentTypeIds.contains("resource"); 107 108 Map<String, Object> result = new HashMap<>(); 109 110 List<Map<String, Object>> commonModelItemsInfo = getCommonModelItemsInfo(contentTypeIds, viewName, acceptedTypes, onlySortable, includeComposite, withFullLabel, hasResources, includeSubRepeaters); 111 handleSpecificInfo(commonModelItemsInfo, withLastModified, withLastValidation, withCreationDate, hasResources, withFullLabel); 112 result.put("attributes", commonModelItemsInfo); 113 114 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 115 return EMPTY_MAP; 116 } 117 118 /** 119 * Retrieve model items information for a list of content types 120 * @param contentTypeIds the identifiers of the content types 121 * @param viewName The view name to list model items 122 * @param acceptedTypes The types to accept. All types accepted if null or empty. 123 * @param onlySortable true to only accept sortable model items. 124 * @param includeCompositesAndRepeaters true to include the composites and repeaters as well 125 * @param withFullLabel true to use the full label of the model items, false otherwise 126 * @param hasResources true if there is at least one content type that is a resource, false otherwise. 127 * @param includeSubRepeaters true to include the children of repeaters as well 128 * @return The list of common model items properties (each set of properties is a map representing the model item) 129 */ 130 protected List<Map<String, Object>> getCommonModelItemsInfo(Collection<String> contentTypeIds, String viewName, List<String> acceptedTypes, boolean onlySortable, boolean includeCompositesAndRepeaters, boolean withFullLabel, boolean hasResources, boolean includeSubRepeaters) 131 { 132 List<Map<String, Object>> commonModelItemsInfos = new LinkedList<>(); 133 134 if (hasResources) 135 { 136 // no common if resource selected 137 return commonModelItemsInfos; 138 } 139 140 Map<String, ModelItem> modelItems = _contentTypesHelper.getCommonModelItems(contentTypeIds, viewName); 141 142 for (ModelItem modelItem : modelItems.values()) 143 { 144 if (_modelItemPassesConditions(modelItem, acceptedTypes, onlySortable, includeCompositesAndRepeaters, includeSubRepeaters)) 145 { 146 Map<String, Object> metadataInfo = getModelItemInfo(modelItem, withFullLabel); 147 commonModelItemsInfos.add(metadataInfo); 148 } 149 } 150 151 return commonModelItemsInfos; 152 } 153 154 /** 155 * Indicates if the attribute should be retrieved or filtered out given the options 156 * @param modelItem the attribute definition 157 * @param acceptedTypes The types to accept. All types accepted if null or empty. 158 * @param onlySortable true to only accept sortable attributes. 159 * @param includeCompositeAndRepeaters true to include the composites and repeaters as well 160 * @param includeSubRepeaters true to include the children of repeaters as well 161 * @return true if the model item passes the conditions 162 */ 163 @SuppressWarnings("static-access") 164 protected boolean _modelItemPassesConditions(ModelItem modelItem, List<String> acceptedTypes, boolean onlySortable, boolean includeCompositeAndRepeaters, boolean includeSubRepeaters) 165 { 166 String typeId = modelItem.getType().getId(); 167 boolean passesCondition = true; 168 169 if (acceptedTypes != null && !acceptedTypes.isEmpty() && !(acceptedTypes.size() == 1 && StringUtils.isEmpty(acceptedTypes.get(0)))) 170 { 171 passesCondition = _passesAcceptedTypesCondition(acceptedTypes, modelItem); 172 } 173 174 if (passesCondition && onlySortable) 175 { 176 passesCondition = !ArrayUtils.contains(__NOT_SORTABLE_TYPE_IDS, typeId); 177 } 178 179 if (passesCondition && !includeCompositeAndRepeaters) 180 { 181 passesCondition = !ModelItemTypeConstants.COMPOSITE_TYPE_ID.equals(typeId) && !ModelItemTypeConstants.REPEATER_TYPE_ID.equals(typeId); 182 } 183 184 if (passesCondition && !includeSubRepeaters) 185 { 186 passesCondition = !_isInARepeater(modelItem); 187 } 188 189 return passesCondition; 190 } 191 192 /** 193 * Checks if the model item passes conditions from accepted types 194 * @param acceptedTypes the accepted types 195 * @param modelItem the model item to check 196 * @return true if the model item passes conditions 197 */ 198 protected boolean _passesAcceptedTypesCondition(List<String> acceptedTypes, ModelItem modelItem) 199 { 200 boolean passesCondition = false; 201 String typeId = modelItem.getType().getId(); 202 203 Map<String, List<String>> acceptedTypesWithProperties = _getAcceptedTypesWithProperties(acceptedTypes); 204 if (acceptedTypesWithProperties.containsKey(typeId)) 205 { 206 List<String> properties = acceptedTypesWithProperties.get(typeId); 207 passesCondition = properties.isEmpty() 208 || properties.contains(__ALLOW_ENUMERATED) && modelItem instanceof ElementDefinition && ((ElementDefinition) modelItem).getEnumerator() != null 209 || properties.contains(__ALLOW_NOT_ENUMERATED) && (!(modelItem instanceof ElementDefinition) || ((ElementDefinition) modelItem).getEnumerator() == null); 210 } 211 212 return passesCondition; 213 } 214 215 /** 216 * Get the map of accepted types with its properties 217 * @param acceptedTypes the accepted types configuration 218 * @return the map of accepted type with its properties 219 */ 220 protected Map<String, List<String>> _getAcceptedTypesWithProperties(List<String> acceptedTypes) 221 { 222 Map<String, List<String>> acceptedTypesWithProperties = new HashMap<>(); 223 for (String acceptedType : acceptedTypes) 224 { 225 String propertiesAsString = StringUtils.substringBetween(acceptedType, "[", "]"); 226 if (StringUtils.isNotBlank(propertiesAsString)) 227 { 228 List<String> properties = Arrays.asList(StringUtils.split(propertiesAsString, ",")); 229 acceptedTypesWithProperties.put(StringUtils.substringBefore(acceptedType, "[").toLowerCase(), properties); 230 } 231 else 232 { 233 acceptedTypesWithProperties.put(acceptedType.toLowerCase(), Collections.EMPTY_LIST); 234 } 235 } 236 return acceptedTypesWithProperties; 237 } 238 239 /** 240 * Checks if the given model item has a parent that is a repeater 241 * @param modelItem the model item to check 242 * @return <code>true</code> if the given model item has a parent that is a repeater, <code>false</code> otherwise 243 */ 244 @SuppressWarnings("static-access") 245 protected boolean _isInARepeater(ModelItem modelItem) 246 { 247 ModelItem parent = modelItem.getParent(); 248 if (parent != null) 249 { 250 if (ModelItemTypeConstants.REPEATER_TYPE_ID.equals(parent.getType().getId())) 251 { 252 return true; 253 } 254 else 255 { 256 return _isInARepeater(parent); 257 } 258 } 259 else 260 { 261 return false; 262 } 263 } 264 265 /** 266 * Get the model item's information 267 * @param modelItem the model item 268 * @param withFullLabel true to use the full label of the model item, false otherwise 269 * @return Model item's information as a map of properties 270 */ 271 protected Map<String, Object> getModelItemInfo(ModelItem modelItem, boolean withFullLabel) 272 { 273 Map<String, Object> modelItemInfo = new HashMap<>(); 274 275 modelItemInfo.put("name", modelItem.getPath()); 276 modelItemInfo.put("type", modelItem.getType().getId()); 277 modelItemInfo.put("label", _i18nUtils.translate(modelItem.getLabel())); 278 279 if (withFullLabel) 280 { 281 modelItemInfo.put("fullLabel", getModelItemFullLabel(modelItem)); 282 } 283 284 return modelItemInfo; 285 } 286 287 /** 288 * Get the user friendly full label for the given model item 289 * @param modelItem the model items 290 * @return The full label of the model item 291 */ 292 protected String getModelItemFullLabel(ModelItem modelItem) 293 { 294 StringBuilder sb = new StringBuilder(); 295 ModelItem parent = modelItem.getParent(); 296 if (parent != null) 297 { 298 sb.append(getModelItemFullLabel(parent)); 299 sb.append(" > "); 300 } 301 302 sb.append(_i18nUtils.translate(modelItem.getLabel())); 303 return sb.toString(); 304 } 305 306 /** 307 * Handle specific information, such as adding system properties into the model list 308 * @param commonModelItemsInfo the mapping of model items' properties 309 * @param withLastModified true to add the last modification date to the model item's info 310 * @param withLastValidation true to add the last validation date to the model item's info 311 * @param withCreationDate true to add the creation date to the model item's info 312 * @param hasResources true if there is at least one content type that is a resource, false otherwise. 313 * @param withFullLabel true to use the full label of the model items, false otherwise 314 */ 315 @SuppressWarnings("static-access") 316 protected void handleSpecificInfo(List<Map<String, Object>> commonModelItemsInfo, boolean withLastModified, boolean withLastValidation, boolean withCreationDate, boolean hasResources, boolean withFullLabel) 317 { 318 if (withLastModified) 319 { 320 Map<String, Object> metadataInfo = new HashMap<>(); 321 322 // FIXME web...? 323 metadataInfo.put("name", DefaultContent.METADATA_MODIFIED); 324 metadataInfo.put("type", ModelItemTypeConstants.DATE_TYPE_ID); 325 326 String label = _i18nUtils.translate(new I18nizableText("plugin.cms", "PLUGINS_CMS_LAST_MODIFICATION_DATE")); 327 metadataInfo.put("label", label); 328 329 if (withFullLabel) 330 { 331 metadataInfo.put("fullLabel", label); 332 } 333 334 commonModelItemsInfo.add(metadataInfo); 335 } 336 337 if (!hasResources) 338 { 339 if (withLastValidation) 340 { 341 Map<String, Object> metadataInfo = new HashMap<>(); 342 343 // FIXME web...? 344 metadataInfo.put("name", DefaultContent.METADATA_LAST_VALIDATION); 345 metadataInfo.put("type", ModelItemTypeConstants.DATE_TYPE_ID); 346 347 String label = _i18nUtils.translate(new I18nizableText("plugin.cms", "PLUGINS_CMS_LAST_VALIDATION_DATE")); 348 metadataInfo.put("label", label); 349 350 if (withFullLabel) 351 { 352 metadataInfo.put("fullLabel", label); 353 } 354 355 commonModelItemsInfo.add(metadataInfo); 356 } 357 358 if (withCreationDate) 359 { 360 Map<String, Object> metadataInfo = new HashMap<>(); 361 362 // FIXME web...? 363 metadataInfo.put("name", DefaultContent.METADATA_CREATION); 364 metadataInfo.put("type", ModelItemTypeConstants.DATE_TYPE_ID); 365 366 String label = _i18nUtils.translate(new I18nizableText("plugin.cms", "PLUGINS_CMS_CREATION_DATE")); 367 metadataInfo.put("label", label); 368 369 if (withFullLabel) 370 { 371 metadataInfo.put("fullLabel", label); 372 } 373 374 commonModelItemsInfo.add(metadataInfo); 375 } 376 } 377 378 } 379 380 /** 381 * Get the content types id to search for 382 * @param request the request 383 * @param withResources is there at least one resource ? 384 * @return the content types 385 */ 386 protected Set<String> getContentTypes(Request request, boolean withResources) 387 { 388 Set<String> cTypeIds = new HashSet<>(); 389 String[] ids = request.getParameterValues("ids"); 390 391 if (ids != null) 392 { 393 for (String id : ids) 394 { 395 if (StringUtils.isNotEmpty(id)) 396 { 397 // id can contains comma (when allOption is selected for example). 398 for (String idPart : StringUtils.split(id, ',')) 399 { 400 cTypeIds.add(idPart); 401 } 402 } 403 } 404 } 405 406 if (cTypeIds.isEmpty()) 407 { 408 cTypeIds.addAll(getAllAvailablesContentTypes(request, true, withResources)); 409 } 410 411 return cTypeIds; 412 } 413 414 /** 415 * Get all the available content types 416 * @param request the request 417 * @param publicOnly Only the non private content types will be returned 418 * @param withResources True to add the resources to the list (which is not a content type) 419 * @return all the available content types 420 */ 421 protected Set<String> getAllAvailablesContentTypes (Request request, boolean publicOnly, boolean withResources) 422 { 423 Set<String> types = new HashSet<>(); 424 425 for (String id : _contentTypeExtensionPoint.getExtensionsIds()) 426 { 427 if (!publicOnly || !_contentTypeExtensionPoint.getExtension(id).isPrivate()) 428 { 429 types.add(id); 430 } 431 } 432 433 if (withResources) 434 { 435 types.add("resource"); 436 } 437 438 return types; 439 } 440}