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.content; 017 018import java.util.ArrayList; 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.avalon.framework.parameters.Parameters; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.cocoon.ProcessingException; 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.StringUtils; 037 038import org.ametys.cms.content.external.ExternalizableMetadataProviderExtensionPoint; 039import org.ametys.cms.contenttype.AbstractMetadataSetElement; 040import org.ametys.cms.contenttype.ContentConstants; 041import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 042import org.ametys.cms.contenttype.ContentTypesHelper; 043import org.ametys.cms.contenttype.Fieldset; 044import org.ametys.cms.contenttype.MetadataDefinition; 045import org.ametys.cms.contenttype.MetadataDefinitionReference; 046import org.ametys.cms.contenttype.MetadataSet; 047import org.ametys.cms.contenttype.MetadataType; 048import org.ametys.cms.contenttype.RepeaterDefinition; 049import org.ametys.cms.contenttype.RichTextMetadataDefinition; 050import org.ametys.cms.contenttype.SemanticAnnotation; 051import org.ametys.cms.repository.Content; 052import org.ametys.core.cocoon.JSonReader; 053import org.ametys.core.right.RightManager; 054import org.ametys.core.right.RightManager.RightResult; 055import org.ametys.core.user.CurrentUserProvider; 056import org.ametys.runtime.i18n.I18nizableText; 057import org.ametys.runtime.parameter.Enumerator; 058import org.ametys.runtime.parameter.ParameterHelper; 059import org.ametys.runtime.parameter.Validator; 060 061/** 062 * Get metadata set definition as JSON object 063 * 064 */ 065public class GetMetadataSetDefAction extends ServiceableAction 066{ 067 /** Content type extension point. */ 068 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 069 /** Helper for content type */ 070 protected ContentTypesHelper _contentTypesHelper; 071 /** Rights manager */ 072 protected RightManager _rightManager; 073 /** The current user provider */ 074 protected CurrentUserProvider _currentUserProvider; 075 /** The component to retrieve externalizable metadata provider */ 076 protected ExternalizableMetadataProviderExtensionPoint _externalizableMetaProvider; 077 078 @Override 079 public void service(ServiceManager serviceManager) throws ServiceException 080 { 081 super.service(serviceManager); 082 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); 083 _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); 084 _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE); 085 _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE); 086 _externalizableMetaProvider = (ExternalizableMetadataProviderExtensionPoint) serviceManager.lookup(ExternalizableMetadataProviderExtensionPoint.ROLE); 087 } 088 089 @SuppressWarnings("unchecked") 090 @Override 091 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 092 { 093 Request request = ObjectModelHelper.getRequest(objectModel); 094 Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 095 096 Content content = (Content) request.getAttribute(Content.class.getName()); 097 boolean isEditionMetadataSet = parameters.getParameterAsBoolean("isEditionMetadataSet", false); 098 String metadataSetName = parameters.getParameter("metadataSetName", "main"); 099 List<String> metadataNames = jsParameters != null ? (List<String>) jsParameters.get("metadata") : Collections.singletonList(request.getParameter("metadata")); 100 101 Map<String, Object> jsonObject = new HashMap<>(); 102 jsonObject.put("content", content2Json(content, metadataSetName, metadataNames, isEditionMetadataSet)); 103 104 request.setAttribute(JSonReader.OBJECT_TO_READ, jsonObject); 105 return EMPTY_MAP; 106 } 107 108 /** 109 * Convert content to JSON object 110 * @param content The content 111 * @param metadataSetName The metadata set name 112 * @param isEditionMetadataSet true if it is metadataset for edition 113 * @return The JSON object representing the content 114 * @throws ProcessingException if an error occurred 115 */ 116 protected Map<String, Object> content2Json (Content content, String metadataSetName, boolean isEditionMetadataSet) throws ProcessingException 117 { 118 return content2Json(content, metadataSetName, null, isEditionMetadataSet); 119 } 120 121 /** 122 * Convert content to JSON object 123 * @param content The content 124 * @param metadataSetName The metadata set name 125 * @param metadataNames If metadata name is empty, this is the list of metadatadef to get ('/' separated for composites) 126 * @param isEditionMetadataSet true if it is metadataset for edition 127 * @return The JSON object representing the content 128 * @throws ProcessingException if an error occurred 129 */ 130 protected Map<String, Object> content2Json (Content content, String metadataSetName, List<String> metadataNames, boolean isEditionMetadataSet) throws ProcessingException 131 { 132 Map<String, Object> jsonObject = new LinkedHashMap<>(); 133 134 jsonObject.put("id", content.getId()); 135 jsonObject.put("name", content.getName()); 136 jsonObject.put("title", content.getTitle()); 137 jsonObject.put("type", content.getTypes()); 138 jsonObject.put("language", content.getLanguage()); 139 140 MetadataSet metadataSet = null; 141 142 if (StringUtils.isEmpty(metadataSetName)) 143 { 144 // Let us compute a metadataset 145 metadataSet = new MetadataSet(); 146 metadataSet.setName("__generated__"); 147 metadataSet.setLabel(new I18nizableText("Live edition metadataset")); 148 metadataSet.setDescription(new I18nizableText("Live edition metadataset")); 149 metadataSet.setSmallIcon(null); 150 metadataSet.setMediumIcon(null); 151 metadataSet.setLargeIcon(null); 152 metadataSet.setEdition(true); 153 metadataSet.setInternal(true); 154 155 for (String metadataName : metadataNames) 156 { 157 _addMetadataDefRef(metadataSet, metadataName); 158 } 159 } 160 else 161 { 162 if (isEditionMetadataSet) 163 { 164 metadataSet = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes(), content.getMixinTypes()); 165 } 166 else 167 { 168 metadataSet = _contentTypesHelper.getMetadataSetForView(metadataSetName, content.getTypes(), content.getMixinTypes()); 169 } 170 } 171 172 if (metadataSet == null) 173 { 174 throw new ProcessingException(String.format("Unknown metadata set '%s' of type '%s' for content type(s) '%s'", 175 metadataSetName, isEditionMetadataSet ? "edition" : "view", StringUtils.join(content.getTypes(), ','))); 176 } 177 178 jsonObject.put("metadataSetName", metadataSetName); 179 jsonObject.put("isEditionMetadataSet", isEditionMetadataSet); 180 181 Set<String> externalAndLocalMetadata = new HashSet<>(); 182 if (isEditionMetadataSet) 183 { 184 externalAndLocalMetadata = _externalizableMetaProvider.getExternalAndLocalMetadata(content); 185 } 186 187 jsonObject.put("metadataSet", metadataSetElement2JsonObject(content, null, metadataSet, externalAndLocalMetadata)); 188 return jsonObject; 189 } 190 191 private void _addMetadataDefRef(AbstractMetadataSetElement metadataSetElement, String metadataName) 192 { 193 String currentLevelMetadataName = StringUtils.substringBefore(metadataName, ContentConstants.METADATA_PATH_SEPARATOR); 194 MetadataDefinitionReference metaDefRef = metadataSetElement.getMetadataDefinitionReference(currentLevelMetadataName); 195 if (metaDefRef == null) 196 { 197 metaDefRef = new MetadataDefinitionReference(currentLevelMetadataName, "main"); 198 metadataSetElement.addElement(metaDefRef); 199 } 200 201 String subLevelMetadataName = StringUtils.substringAfter(metadataName, ContentConstants.METADATA_PATH_SEPARATOR); 202 if (StringUtils.isNotBlank(subLevelMetadataName)) 203 { 204 _addMetadataDefRef(metaDefRef, subLevelMetadataName); 205 } 206 } 207 208 /** 209 * Convert {@link AbstractMetadataSetElement} to JSON object 210 * @param content The content 211 * @param metadataDefinition The metadata definition 212 * @param metadataSetElement The metadataset element 213 * @param externalAndLocalMetadata The paths of externalizable metadata (with local and external value) 214 * @return The JSON object representing the metadataset element 215 * @throws ProcessingException if an error occurred 216 */ 217 protected Map<String, Object> metadataSetElement2JsonObject(Content content, MetadataDefinition metadataDefinition, AbstractMetadataSetElement metadataSetElement, Set<String> externalAndLocalMetadata) throws ProcessingException 218 { 219 Map<String, Object> jsonObject = new LinkedHashMap<>(); 220 221 for (AbstractMetadataSetElement subMetadataSetElement : metadataSetElement.getElements()) 222 { 223 if (subMetadataSetElement instanceof MetadataDefinitionReference) 224 { 225 MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) subMetadataSetElement; 226 String metadataName = metadataDefRef.getMetadataName(); 227 MetadataDefinition metaDef = _getMetadataDefinition(content, metadataDefinition, metadataName); 228 229 if (metaDef == null) 230 { 231 throw new IllegalArgumentException("Unable to get the metadata definition of metadata \"" + metadataName + "\""); 232 } 233 234 if (_contentTypesHelper.canRead(content, metaDef)) 235 { 236// String metadataPath = StringUtils.stripStart(metaDef.getId(), "/"); 237 String metadataPath = metaDef.getId(); 238 jsonObject.put(metaDef.getName(), metadataDefinition2JsonObject(content, metadataDefRef, metaDef, metadataPath, externalAndLocalMetadata)); 239 } 240 } 241 else 242 { 243 if (!jsonObject.containsKey("fieldsets")) 244 { 245 jsonObject.put("fieldsets", new ArrayList<Map<String, Object>>()); 246 } 247 248 @SuppressWarnings("unchecked") 249 List<Map<String, Object>> fieldsets = (List<Map<String, Object>>) jsonObject.get("fieldsets"); 250 251 Fieldset fieldset = (Fieldset) subMetadataSetElement; 252 253 Map<String, Object> fieldSetObject = new LinkedHashMap<>(); 254 fieldSetObject.put("role", fieldset.getRole()); 255 fieldSetObject.put("label", fieldset.getLabel()); 256 fieldSetObject.put("elements", metadataSetElement2JsonObject(content, metadataDefinition, fieldset, externalAndLocalMetadata)); 257 258 fieldsets.add(fieldSetObject); 259 } 260 } 261 262 return jsonObject; 263 } 264 265 /** 266 * Convert a metadata to JSON object 267 * @param content The content 268 * @param metadataSetElement The metadataset element 269 * @param metaDef The metadata definition 270 * @param metadataPath The path of metadata 271 * @param externalAndLocalMetadata The path of externalizable metadata (with local and external value) 272 * @return The JSON object representing the metadata 273 * @throws ProcessingException if an error occured 274 */ 275 protected Map<String, Object> metadataDefinition2JsonObject(Content content, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metaDef, String metadataPath, Set<String> externalAndLocalMetadata) throws ProcessingException 276 { 277 Map<String, Object> jsonObject = new LinkedHashMap<>(); 278 279 jsonObject.put("label", metaDef.getLabel()); 280 jsonObject.put("description", metaDef.getDescription()); 281 jsonObject.put("type", metaDef.getType().name()); 282 283 String cTypeId = metaDef.getContentType(); 284 if (cTypeId != null) 285 { 286 jsonObject.put("contentType", cTypeId); 287 } 288 289 _metadataDefinitionValidator2JsonObject(metaDef, jsonObject); 290 291 if (externalAndLocalMetadata.contains(metadataPath)) 292 { 293 // Wrap the widget 294 jsonObject.put("widget", "edition.externalizable"); 295 296 Map<String, I18nizableText> widgetParameters = new HashMap<>(); 297 String widget = metaDef.getWidget(); 298 if (widget != null) 299 { 300 widgetParameters.put("wrapped-widget", new I18nizableText(widget)); 301 } 302 303 Map<String, I18nizableText> wrappedWidgetParameters = metaDef.getWidgetParameters(); 304 if (wrappedWidgetParameters != null && wrappedWidgetParameters.size() > 0) 305 { 306 widgetParameters.putAll(wrappedWidgetParameters); 307 } 308 jsonObject.put("widget-params", widgetParameters); 309 } 310 else 311 { 312 String widget = metaDef.getWidget(); 313 if (widget != null) 314 { 315 jsonObject.put("widget", widget); 316 } 317 318 Map<String, I18nizableText> widgetParameters = metaDef.getWidgetParameters(); 319 if (widgetParameters != null && widgetParameters.size() > 0) 320 { 321 jsonObject.put("widget-params", metaDef.getWidgetParameters()); 322 } 323 } 324 325 326 if (metaDef.getType() == MetadataType.RICH_TEXT) 327 { 328 jsonObject.put("editableSource", _rightManager.hasRight(_currentUserProvider.getUser(), "CORE_Rights_SourceEdit", content) == RightResult.RIGHT_ALLOW); 329 } 330 331 jsonObject.put("multiple", metaDef.isMultiple()); 332 333 Object defaultValue = metaDef.getDefaultValue(); 334 if (defaultValue != null) 335 { 336 jsonObject.put("default-value", metaDef.getDefaultValue()); 337 } 338 339 _metadataDefinitionEnumerator2JsonObject(metaDef, jsonObject); 340 341 342 if (!_contentTypesHelper.canWrite(content, metaDef)) 343 { 344 jsonObject.put("can-not-write", true); 345 } 346 347 if (metaDef.getType() == MetadataType.COMPOSITE) 348 { 349 jsonObject.putAll(compositeDefinition2JsonObject(content, metadataSetElement, metaDef, externalAndLocalMetadata)); 350 } 351 352 if (metaDef instanceof RichTextMetadataDefinition) 353 { 354 jsonObject.putAll(annotableDefinition2JsonObject((RichTextMetadataDefinition) metaDef)); 355 } 356 357 return jsonObject; 358 } 359 360 private void _metadataDefinitionValidator2JsonObject(MetadataDefinition metaDef, Map<String, Object> jsonObject) 361 { 362 Validator validator = metaDef.getValidator(); 363 if (validator != null) 364 { 365 jsonObject.put("validation", validator.toJson()); 366 } 367 } 368 369 private void _metadataDefinitionEnumerator2JsonObject(MetadataDefinition metaDef, Map<String, Object> jsonObject) throws ProcessingException 370 { 371 Enumerator enumerator = metaDef.getEnumerator(); 372 373 if (enumerator != null) 374 { 375 try 376 { 377 List<Map<String, Object>> options = new ArrayList<>(); 378 379 for (Map.Entry<Object, I18nizableText> entry : enumerator.getEntries().entrySet()) 380 { 381 String valueAsString = ParameterHelper.valueToString(entry.getKey()); 382 I18nizableText entryLabel = entry.getValue(); 383 384 Map<String, Object> option = new HashMap<>(); 385 option.put("label", entryLabel != null ? entryLabel : valueAsString); 386 option.put("value", valueAsString); 387 options.add(option); 388 } 389 390 jsonObject.put("enumeration", options); 391 } 392 catch (Exception e) 393 { 394 throw new ProcessingException("Unable to enumerate entries with enumerator: " + enumerator, e); 395 } 396 } 397 } 398 399 private Map<String, Object> compositeDefinition2JsonObject(Content content, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metaDef, Set<String> externalAndLocalMetadata) throws ProcessingException 400 { 401 Map<String, Object> jsonObject = new LinkedHashMap<>(); 402 403 if (metaDef instanceof RepeaterDefinition) 404 { 405 RepeaterDefinition repeaterDef = (RepeaterDefinition) metaDef; 406 407 Map<String, Object> repeater = new HashMap<>(); 408 409 repeater.put("initial-size", repeaterDef.getInitialSize()); 410 repeater.put("min-size", repeaterDef.getMinSize()); 411 412 int maxSize = repeaterDef.getMaxSize(); 413 if (maxSize >= 0) 414 { 415 repeater.put("max-size", maxSize); 416 } 417 418 419 repeater.put("add-label", repeaterDef.getAddLabel()); 420 repeater.put("del-label", repeaterDef.getDeleteLabel()); 421 repeater.put("header-label", repeaterDef.getHeaderLabel()); 422 423 repeater.put("composition", metadataSetElement2JsonObject(content, metaDef, metadataSetElement, externalAndLocalMetadata)); 424 425 jsonObject.put("repeater", repeater); 426 427 } 428 else 429 { 430 jsonObject.put("composition", metadataSetElement2JsonObject(content, metaDef, metadataSetElement, externalAndLocalMetadata)); 431 } 432 433 return jsonObject; 434 } 435 436 private Map<String, Object> annotableDefinition2JsonObject(RichTextMetadataDefinition metaDef) 437 { 438 Map<String, Object> jsonObject = new LinkedHashMap<>(); 439 440 List<SemanticAnnotation> annotations = metaDef.getSemanticAnnotations(); 441 if (annotations != null && annotations.size() > 0) 442 { 443 List<Map<String, Object>> annotationsObject = new ArrayList<>(); 444 445 for (SemanticAnnotation annotation : annotations) 446 { 447 Map<String, Object> annotationObject = new LinkedHashMap<>(); 448 449 annotationObject.put("name", annotation.getId()); 450 annotationObject.put("label", annotation.getLabel()); 451 annotationObject.put("description", annotation.getDescription()); 452 453 annotationsObject.add(annotationObject); 454 } 455 456 jsonObject.put("annotations", annotationsObject); 457 458 } 459 return jsonObject; 460 } 461 462 /** 463 * Retrieves a sub metadata definition from a content type or 464 * a parent metadata definition. 465 * @param content the content. 466 * @param parentMetadataDefinition the parent metadata definition. 467 * @param metadataName the metadata name. 468 * @return the metadata definition found or <code>null</code> otherwise. 469 */ 470 protected MetadataDefinition _getMetadataDefinition(Content content, MetadataDefinition parentMetadataDefinition, String metadataName) 471 { 472 MetadataDefinition metadataDefinition = null; 473 474 if (parentMetadataDefinition == null) 475 { 476 if (content != null) 477 { 478 metadataDefinition = _contentTypesHelper.getMetadataDefinition(metadataName, content.getTypes(), content.getMixinTypes()); 479 } 480 } 481 else 482 { 483 metadataDefinition = parentMetadataDefinition.getMetadataDefinition(metadataName); 484 } 485 486 return metadataDefinition; 487 } 488 489}