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.contenttype; 017 018import java.util.Collections; 019import java.util.HashMap; 020import java.util.LinkedHashMap; 021import java.util.LinkedList; 022import java.util.List; 023import java.util.Map; 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.acting.ServiceableAction; 029import org.apache.cocoon.environment.ObjectModelHelper; 030import org.apache.cocoon.environment.Redirector; 031import org.apache.cocoon.environment.Request; 032import org.apache.cocoon.environment.SourceResolver; 033import org.apache.commons.lang.StringUtils; 034 035import org.ametys.cms.content.RootContentHelper; 036import org.ametys.cms.repository.Content; 037import org.ametys.core.cocoon.JSonReader; 038import org.ametys.core.right.RightManager; 039import org.ametys.core.right.RightManager.RightResult; 040import org.ametys.core.user.CurrentUserProvider; 041import org.ametys.core.user.UserIdentity; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043import org.ametys.runtime.parameter.Validator; 044 045/** 046 * Get information about the definition of the structure a {@link MetadataSet}.<br> 047 */ 048public class GetMetadataSetDefinitionAction extends ServiceableAction 049{ 050 /** The Ametys object resolver */ 051 protected AmetysObjectResolver _resolver; 052 /** Content type extension point. */ 053 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 054 /** The current user provider */ 055 protected CurrentUserProvider _userProvider; 056 /** The rights manager */ 057 protected RightManager _rightManager; 058 /** Helper for content types */ 059 protected ContentTypesHelper _contentTypesHelper; 060 /** Helper for root content */ 061 protected RootContentHelper _rootContentHelper; 062 063 @Override 064 public void service(ServiceManager smanager) throws ServiceException 065 { 066 super.service(smanager); 067 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 068 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 069 _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 070 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 071 _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 072 _rootContentHelper = (RootContentHelper) smanager.lookup(RootContentHelper.ROLE); 073 } 074 075 @Override 076 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 077 { 078 Request request = ObjectModelHelper.getRequest(objectModel); 079 080 String contentTypeId = StringUtils.defaultString(request.getParameter("contentType")); 081 String contentId = StringUtils.defaultString(request.getParameter("contentId")); 082 String viewName = StringUtils.defaultIfEmpty(request.getParameter("viewName"), "default-edition"); 083 String fallbackViewName = StringUtils.defaultIfEmpty(request.getParameter("fallbackViewName"), "main"); 084 String metadataSetMode = StringUtils.defaultIfEmpty(request.getParameter("metadataSetMode"), "edition"); 085 086 String[] contentTypes; 087 String[] mixins = new String[0]; 088 089 MetadataSet metadataSet = null; 090 Content content = null; 091 if (StringUtils.isNotEmpty(contentTypeId)) 092 { 093 contentTypes = contentTypeId.split(","); 094 } 095 else if (StringUtils.isNotEmpty(contentId)) 096 { 097 content = _resolver.resolveById(contentId); 098 contentTypes = content.getTypes(); 099 mixins = content.getMixinTypes(); 100 } 101 else 102 { 103 String errorMsg = String.format("Unable to get metadata set definition: missing parameter 'contentId' or 'contentTypeId'"); 104 getLogger().error(errorMsg); 105 throw new IllegalArgumentException(errorMsg); 106 } 107 108 for (String id : contentTypes) 109 { 110 ContentType cType = _contentTypeExtensionPoint.getExtension(id); 111 112 if (cType == null) 113 { 114 String errorMsg = String.format("Unable to get metadata set definition: unknown content type '" + id + "'"); 115 getLogger().error(errorMsg); 116 throw new IllegalArgumentException(errorMsg); 117 } 118 } 119 120 metadataSet = _contentTypesHelper.getMetadataSetWithFallback(viewName, fallbackViewName, contentTypes, mixins, !"view".equals(metadataSetMode)); 121 122 List<Object> metadataSetProperties = new LinkedList<>(); 123 124 if (metadataSet == null) 125 { 126 String errorMsg = String.format("Unknown metadata set '%s' of type '%s' for content types '%s'", viewName, metadataSetMode, StringUtils.join(contentTypes, ',')); 127 getLogger().error(errorMsg); 128 129 metadataSetProperties.add(metadataError2JsonObject(contentTypes, viewName, metadataSetMode)); 130 } 131 else 132 { 133 // Getting the metadata set properties. 134 for (AbstractMetadataSetElement element : metadataSet.getElements()) 135 { 136 Map<String, Object> childProperties = metadataSetElement2JsonObject(contentTypes, mixins, element); 137 if (childProperties != null) 138 { 139 metadataSetProperties.add(childProperties); 140 } 141 } 142 } 143 144 request.setAttribute(JSonReader.OBJECT_TO_READ, metadataSetProperties); 145 146 return EMPTY_MAP; 147 } 148 149 /** 150 * Get some error properties when the requested metadata set is not found. 151 * @param contentTypes The content types 152 * @param viewName The view name 153 * @param metadataSetMode The metadata set mode 154 * @return A map containing the properties. 155 */ 156 protected Map<String, Object> metadataError2JsonObject(String[] contentTypes, String viewName, String metadataSetMode) 157 { 158 HashMap<String, Object> jsonObject = new LinkedHashMap<>(); 159 160 jsonObject.put("type", "error"); 161 jsonObject.put("errorType", "metadataSet"); 162 jsonObject.put("contentType", StringUtils.join(contentTypes, ',')); 163 jsonObject.put("viewName", viewName); 164 jsonObject.put("metadataSetMode", metadataSetMode); 165 166 jsonObject.put("children", Collections.EMPTY_LIST); 167 168 return jsonObject; 169 } 170 171 /* --------------------------------------------------------------------- */ 172 /* -------------------- ABSTRACT METADATASET ELEMENT ------------------- */ 173 /* --------------------------------------------------------------------- */ 174 175 /** 176 * Get properties of the {@link AbstractMetadataSetElement} and its child element. 177 * This method is the entry point to retrieves information of a MetadataSetElement. 178 * @param contentTypes The content types 179 * @param mixins The mixins 180 * @param metadataSetElement The metadata set element 181 * @return A map containing the properties of this {@link AbstractMetadataSetElement}. 182 */ 183 protected Map<String, Object> metadataSetElement2JsonObject(String[] contentTypes, String[] mixins, AbstractMetadataSetElement metadataSetElement) 184 { 185 return metadataSet2JsonObject(contentTypes, mixins, null, metadataSetElement, true); 186 } 187 188 /** 189 * Get properties of the {@link AbstractMetadataSetElement} 190 * This method is the entry point to retrieves information of a MetadataSetElement. 191 * @param contentTypes The content types 192 * @param mixins The mixins 193 * @param metadataSetElement The metadata set element 194 * @param recurse If true, also retrieves the properties of the child elements. 195 * @return A map containing the properties of this {@link AbstractMetadataSetElement}. 196 */ 197 protected Map<String, Object> getMetadataSetElementInformation(String[] contentTypes, String[] mixins, AbstractMetadataSetElement metadataSetElement, boolean recurse) 198 { 199 return metadataSet2JsonObject(contentTypes, mixins, null, metadataSetElement, recurse); 200 } 201 202 /** 203 * Get properties of the {@link AbstractMetadataSetElement} 204 * @param contentTypes The content types 205 * @param mixins The mixins 206 * @param metadataDefinition The metadata definintion in recursive path. null at root. 207 * @param metadataSetElement The metadata set element 208 * @param recurse If true, also retrieves the properties of the child elements. 209 * @return A map containing the properties of this {@link AbstractMetadataSetElement}. 210 */ 211 protected Map<String, Object> metadataSet2JsonObject(String[] contentTypes, String[] mixins, MetadataDefinition metadataDefinition, AbstractMetadataSetElement metadataSetElement, boolean recurse) 212 { 213 if (metadataSetElement instanceof MetadataDefinitionReference) 214 { 215 return metadata2JsonObject(contentTypes, mixins, metadataDefinition, (MetadataDefinitionReference) metadataSetElement, recurse); 216 } 217 else if (metadataSetElement instanceof Fieldset) 218 { 219 return fieldset2JsonObject(contentTypes, mixins, metadataDefinition, (Fieldset) metadataSetElement, recurse); 220 } 221 222 throw new IllegalArgumentException( 223 String.format("Unexpected runtime type for the instance of the abstract class '%s' : %s", AbstractMetadataSetElement.class.getName(), metadataSetElement.getClass().getName()) 224 ); 225 } 226 227 228 /** 229 * Add informations in a map of properties for the children of a metadata set element. 230 * @param properties The map of properties to populate 231 * @param contentTypes The content types 232 * @param mixins The mixins 233 * @param metadataDefinition The metadata definition for recursive purposes. null at root. 234 * @param metadataSetElement The metadate set. 235 */ 236 protected void addChildrenInformation(Map<String, Object> properties, String[] contentTypes, String[] mixins, MetadataDefinition metadataDefinition, AbstractMetadataSetElement metadataSetElement) 237 { 238 List<Object> children = new LinkedList<>(); 239 240 for (AbstractMetadataSetElement child : metadataSetElement.getElements()) 241 { 242 Map<String, Object> childProperties = metadataSet2JsonObject(contentTypes, mixins, metadataDefinition, child, true); 243 if (childProperties != null) 244 { 245 children.add(childProperties); 246 } 247 } 248 249 properties.put("children", children); 250 } 251 252 253 /* --------------------------------------------------------------------- */ 254 /* ------------------------------ FIELDSET ----------------------------- */ 255 /* --------------------------------------------------------------------- */ 256 257 /** 258 * Get the properties of a {@link Fieldset} 259 * @param contentTypes The content types 260 * @param mixins The mixins 261 * @param metadataDefinition The metadataset definition for recusive purposes. null at root. 262 * @param fieldset The fieldset to convert 263 * @param recurse Should it be recursive 264 * @return A map containing the {@link Fieldset} properties. 265 */ 266 protected Map<String, Object> fieldset2JsonObject(String[] contentTypes, String[] mixins, MetadataDefinition metadataDefinition, Fieldset fieldset, boolean recurse) 267 { 268 HashMap<String, Object> jsonObject = new LinkedHashMap<>(); 269 270 jsonObject.put("type", "fieldset"); 271 jsonObject.put("label", fieldset.getLabel()); 272 jsonObject.put("role", fieldset.getRole()); 273 274 if (recurse) 275 { 276 addChildrenInformation(jsonObject, contentTypes, mixins, metadataDefinition, fieldset); 277 } 278 279 return jsonObject; 280 } 281 282 283 284 /* --------------------------------------------------------------------- */ 285 /* ------------------------- METADATA REFERENCE ------------------------ */ 286 /* --------------------------------------------------------------------- */ 287 288 /** 289 * Get properties of the {@link MetadataDefinition} through its {@link MetadataDefinitionReference} 290 * @param contentTypes The content types 291 * @param mixins The mixins 292 * @param parentMetadataDef The parent metadataref 293 * @param metadataDefRef The metadataref 294 * @param recurse True to convert recursively 295 * @return A map containing the {@link MetadataDefinitionReference} properties. 296 */ 297 protected Map<String, Object> metadata2JsonObject(String[] contentTypes, String[] mixins, MetadataDefinition parentMetadataDef, MetadataDefinitionReference metadataDefRef, boolean recurse) 298 { 299 MetadataDefinition metadataDefinition = _getMetadataDefinition(contentTypes, mixins, parentMetadataDef, metadataDefRef); 300 MetadataType type = metadataDefinition.getType(); 301 302 HashMap<String, Object> jsonObject = new LinkedHashMap<>(); 303 304 jsonObject.put("type", "metadata"); 305 jsonObject.put("metadataName", metadataDefRef.getMetadataName()); 306 jsonObject.put("metadataType", _getMetadataType(metadataDefinition)); 307 308 jsonObject.put("label", metadataDefinition.getLabel()); 309 jsonObject.put("description", metadataDefinition.getDescription()); 310 jsonObject.put("mandatory", _isMandatory(metadataDefinition)); 311 312 boolean isContentReference = MetadataType.CONTENT.equals(type); 313 boolean isSubContent = MetadataType.SUB_CONTENT.equals(type); 314 if (isSubContent || isContentReference) 315 { 316 jsonObject.put("contentType", metadataDefinition.getContentType()); 317 jsonObject.put("viewName", metadataDefRef.getMetadataSetName()); 318 } 319 320 if (isContentReference) 321 { 322 jsonObject.put("canCreate", _hasRight(metadataDefinition.getContentType())); 323 } 324 325 // Children information are not added in case of a metadata of type content. 326 if (recurse && !isSubContent && !isContentReference) 327 { 328 addChildrenInformation(jsonObject, contentTypes, mixins, metadataDefinition, metadataDefRef); 329 } 330 331 return jsonObject; 332 } 333 334 /** 335 * Returns the type of this {@link MetadataDefinition} 336 * @param metadataDefinition The metadata defintion 337 * @return the name of type 338 */ 339 protected String _getMetadataType(MetadataDefinition metadataDefinition) 340 { 341 if (metadataDefinition.getType().equals(MetadataType.COMPOSITE)) 342 { 343 if (metadataDefinition instanceof RepeaterDefinition) 344 { 345 return "repeater"; 346 } 347 } 348 349 return metadataDefinition.getType().getId().toLowerCase(); 350 } 351 352 353 /** 354 * Indicates if this {@link MetadataDefinition} is mandatory. 355 * @param metadataDefinition The metadata definition 356 * @return true if mandatory 357 */ 358 protected boolean _isMandatory(MetadataDefinition metadataDefinition) 359 { 360 boolean mandatory = false; 361 362 if (metadataDefinition.getType().equals(MetadataType.COMPOSITE) && metadataDefinition instanceof RepeaterDefinition) 363 { 364 mandatory = ((RepeaterDefinition) metadataDefinition).getMinSize() > 0; 365 } 366 else 367 { 368 Validator validator = metadataDefinition.getValidator(); 369 if (validator != null) 370 { 371 mandatory = (Boolean) validator.getConfiguration().get("mandatory"); 372 } 373 } 374 375 return mandatory; 376 } 377 378 /** 379 * Test if the current user has the right needed by the content type to create a content. 380 * @param contentTypeId The content type id 381 * @return true if the user has the right needed, false otherwise. 382 */ 383 protected boolean _hasRight(String contentTypeId) 384 { 385 boolean hasRight = false; 386 387 ContentType cType = null; 388 String right = null; 389 390 if (contentTypeId == null) 391 { 392 return false; 393 } 394 395 try 396 { 397 cType = _contentTypeExtensionPoint.getExtension(contentTypeId); 398 } 399 catch (Exception e) 400 { 401 getLogger().error("Cannot check user rights because the content type has not been found : " + contentTypeId, e); 402 } 403 404 if (cType != null) 405 { 406 right = cType.getRight(); 407 408 if (right == null) 409 { 410 hasRight = true; 411 } 412 else 413 { 414 UserIdentity user = _userProvider.getUser(); 415 hasRight = _rightManager.hasRight(user, right, "/cms") == RightResult.RIGHT_ALLOW || _rightManager.hasRight(user, right, _rootContentHelper.getRootContent()) == RightResult.RIGHT_ALLOW; 416 } 417 } 418 else 419 { 420 getLogger().error("Cannot check user rights because the content type has not been found : " + contentTypeId); 421 } 422 423 return hasRight; 424 } 425 426 /** 427 * Retrieves a {@link MetadataDefinition} through its {@link MetadataDefinitionReference} 428 * @param contentTypes The content types 429 * @param mixins The mixins 430 * @param parentMetadataDef The parent metadata reference 431 * @param metadataDefRef The metadata reference 432 * @return The metadata definition found 433 */ 434 private MetadataDefinition _getMetadataDefinition(String[] contentTypes, String[] mixins, MetadataDefinition parentMetadataDef, MetadataDefinitionReference metadataDefRef) 435 { 436 String metadataName = metadataDefRef.getMetadataName(); 437 438 if (parentMetadataDef == null) 439 { 440 return _contentTypesHelper.getMetadataDefinition(metadataName, contentTypes, mixins); 441 } 442 else 443 { 444 return parentMetadataDef.getMetadataDefinition(metadataName); 445 } 446 } 447}