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