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