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.ParameterHelper; 058 059/** 060 * Get metadata set definition as JSON object 061 * 062 */ 063public class GetMetadataSetDefAction extends ServiceableAction 064{ 065 /** Content type extension point. */ 066 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 067 /** Helper for content type */ 068 protected ContentTypesHelper _contentTypesHelper; 069 /** The content helper */ 070 protected ContentHelper _contentHelper; 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 _contentHelper = (ContentHelper) serviceManager.lookup(ContentHelper.ROLE); 088 } 089 090 @SuppressWarnings("unchecked") 091 @Override 092 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 093 { 094 Request request = ObjectModelHelper.getRequest(objectModel); 095 Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 096 097 Content content = (Content) request.getAttribute(Content.class.getName()); 098 boolean isEditionMetadataSet = parameters.getParameterAsBoolean("isEditionMetadataSet", false); 099 String metadataSetName = parameters.getParameter("metadataSetName", "main"); 100 List<String> metadataNames = jsParameters != null ? (List<String>) jsParameters.get("metadata") : Collections.singletonList(request.getParameter("metadata")); 101 102 Map<String, Object> jsonObject = new HashMap<>(); 103 jsonObject.put("content", content2Json(content, metadataSetName, metadataNames, isEditionMetadataSet)); 104 105 request.setAttribute(JSonReader.OBJECT_TO_READ, jsonObject); 106 return EMPTY_MAP; 107 } 108 109 /** 110 * Convert content to JSON object 111 * @param content The content 112 * @param metadataSetName The metadata set name 113 * @param isEditionMetadataSet true if it is metadataset for edition 114 * @return The JSON object representing the content 115 * @throws ProcessingException if an error occurred 116 */ 117 protected Map<String, Object> content2Json (Content content, String metadataSetName, boolean isEditionMetadataSet) throws ProcessingException 118 { 119 return content2Json(content, metadataSetName, null, isEditionMetadataSet); 120 } 121 122 /** 123 * Convert content to JSON object 124 * @param content The content 125 * @param metadataSetName The metadata set name 126 * @param metadataNames If metadata name is empty, this is the list of metadatadef to get ('/' separated for composites) 127 * @param isEditionMetadataSet true if it is metadataset for edition 128 * @return The JSON object representing the content 129 * @throws ProcessingException if an error occurred 130 */ 131 protected Map<String, Object> content2Json (Content content, String metadataSetName, List<String> metadataNames, boolean isEditionMetadataSet) throws ProcessingException 132 { 133 Map<String, Object> jsonObject = new LinkedHashMap<>(); 134 135 jsonObject.put("id", content.getId()); 136 jsonObject.put("name", content.getName()); 137 jsonObject.put("title", _contentHelper.getTitle(content)); 138 jsonObject.put("type", content.getTypes()); 139 jsonObject.put("language", content.getLanguage()); 140 141 MetadataSet metadataSet = null; 142 143 if (StringUtils.isEmpty(metadataSetName)) 144 { 145 // Let us compute a metadataset 146 metadataSet = new MetadataSet(); 147 metadataSet.setName("__generated__"); 148 metadataSet.setLabel(new I18nizableText("Live edition metadataset")); 149 metadataSet.setDescription(new I18nizableText("Live edition metadataset")); 150 metadataSet.setSmallIcon(null); 151 metadataSet.setMediumIcon(null); 152 metadataSet.setLargeIcon(null); 153 metadataSet.setEdition(true); 154 metadataSet.setInternal(true); 155 156 for (String metadataName : metadataNames) 157 { 158 _addMetadataDefRef(metadataSet, metadataName); 159 } 160 } 161 else 162 { 163 if (isEditionMetadataSet) 164 { 165 metadataSet = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes(), content.getMixinTypes()); 166 } 167 else 168 { 169 metadataSet = _contentTypesHelper.getMetadataSetForView(metadataSetName, content.getTypes(), content.getMixinTypes()); 170 } 171 } 172 173 if (metadataSet == null) 174 { 175 throw new ProcessingException(String.format("Unknown metadata set '%s' of type '%s' for content type(s) '%s'", 176 metadataSetName, isEditionMetadataSet ? "edition" : "view", StringUtils.join(content.getTypes(), ','))); 177 } 178 179 jsonObject.put("metadataSetName", metadataSetName); 180 jsonObject.put("isEditionMetadataSet", isEditionMetadataSet); 181 182 Set<String> externalAndLocalMetadata = new HashSet<>(); 183 if (isEditionMetadataSet) 184 { 185 externalAndLocalMetadata = _externalizableMetaProvider.getExternalAndLocalMetadata(content); 186 } 187 188 jsonObject.put("metadataSet", metadataSetElement2JsonObject(content, null, metadataSet, externalAndLocalMetadata)); 189 return jsonObject; 190 } 191 192 private void _addMetadataDefRef(AbstractMetadataSetElement metadataSetElement, String metadataName) 193 { 194 String currentLevelMetadataName = StringUtils.substringBefore(metadataName, ContentConstants.METADATA_PATH_SEPARATOR); 195 MetadataDefinitionReference metaDefRef = metadataSetElement.getMetadataDefinitionReference(currentLevelMetadataName); 196 if (metaDefRef == null) 197 { 198 metaDefRef = new MetadataDefinitionReference(currentLevelMetadataName, "main"); 199 metadataSetElement.addElement(metaDefRef); 200 } 201 202 String subLevelMetadataName = StringUtils.substringAfter(metadataName, ContentConstants.METADATA_PATH_SEPARATOR); 203 if (StringUtils.isNotBlank(subLevelMetadataName)) 204 { 205 _addMetadataDefRef(metaDefRef, subLevelMetadataName); 206 } 207 } 208 209 /** 210 * Convert {@link AbstractMetadataSetElement} to JSON object 211 * @param content The content 212 * @param metadataDefinition The metadata definition 213 * @param metadataSetElement The metadataset element 214 * @param externalAndLocalMetadata The paths of externalizable metadata (with local and external value) 215 * @return The JSON object representing the metadataset element 216 * @throws ProcessingException if an error occurred 217 */ 218 protected Map<String, Object> metadataSetElement2JsonObject(Content content, MetadataDefinition metadataDefinition, AbstractMetadataSetElement metadataSetElement, Set<String> externalAndLocalMetadata) throws ProcessingException 219 { 220 Map<String, Object> jsonObject = new LinkedHashMap<>(); 221 222 for (AbstractMetadataSetElement subMetadataSetElement : metadataSetElement.getElements()) 223 { 224 if (subMetadataSetElement instanceof MetadataDefinitionReference) 225 { 226 MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) subMetadataSetElement; 227 String metadataName = metadataDefRef.getMetadataName(); 228 MetadataDefinition metaDef = _getMetadataDefinition(content, metadataDefinition, metadataName); 229 230 if (metaDef == null) 231 { 232 throw new IllegalArgumentException("Unable to get the metadata definition of metadata \"" + metadataName + "\""); 233 } 234 235 if (_contentTypesHelper.canRead(content, metaDef)) 236 { 237// String metadataPath = StringUtils.stripStart(metaDef.getId(), "/"); 238 String metadataPath = metaDef.getId(); 239 jsonObject.put(metaDef.getName(), metadataDefinition2JsonObject(content, metadataDefRef, metaDef, metadataPath, externalAndLocalMetadata)); 240 } 241 } 242 else 243 { 244 if (!jsonObject.containsKey("fieldsets")) 245 { 246 jsonObject.put("fieldsets", new ArrayList<Map<String, Object>>()); 247 } 248 249 @SuppressWarnings("unchecked") 250 List<Map<String, Object>> fieldsets = (List<Map<String, Object>>) jsonObject.get("fieldsets"); 251 252 Fieldset fieldset = (Fieldset) subMetadataSetElement; 253 254 Map<String, Object> fieldSetObject = new LinkedHashMap<>(); 255 fieldSetObject.put("role", fieldset.getRole()); 256 fieldSetObject.put("label", fieldset.getLabel()); 257 fieldSetObject.put("elements", metadataSetElement2JsonObject(content, metadataDefinition, fieldset, externalAndLocalMetadata)); 258 259 fieldsets.add(fieldSetObject); 260 } 261 } 262 263 return jsonObject; 264 } 265 266 /** 267 * Convert a metadata to JSON object 268 * @param content The content 269 * @param metadataSetElement The metadataset element 270 * @param metaDef The metadata definition 271 * @param metadataPath The path of metadata 272 * @param externalAndLocalMetadata The path of externalizable metadata (with local and external value) 273 * @return The JSON object representing the metadata 274 * @throws ProcessingException if an error occured 275 */ 276 protected Map<String, Object> metadataDefinition2JsonObject(Content content, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metaDef, String metadataPath, Set<String> externalAndLocalMetadata) throws ProcessingException 277 { 278 Map<String, Object> jsonObject = ParameterHelper.toJSON(metaDef); 279 280 jsonObject.put("multiple", metaDef.isMultiple()); 281 282 String cTypeId = metaDef.getContentType(); 283 if (cTypeId != null) 284 { 285 jsonObject.put("contentType", cTypeId); 286 } 287 288 if (externalAndLocalMetadata.contains(metadataPath)) 289 { 290 // Wrap the widget 291 jsonObject.put("widget", "edition.externalizable"); 292 293 Map<String, I18nizableText> widgetParameters = new HashMap<>(); 294 String widget = metaDef.getWidget(); 295 if (widget != null) 296 { 297 widgetParameters.put("wrapped-widget", new I18nizableText(widget)); 298 } 299 300 Map<String, I18nizableText> wrappedWidgetParameters = metaDef.getWidgetParameters(); 301 if (wrappedWidgetParameters != null && wrappedWidgetParameters.size() > 0) 302 { 303 widgetParameters.putAll(wrappedWidgetParameters); 304 } 305 jsonObject.put("widget-params", widgetParameters); 306 } 307 308 if (metaDef.getType() == MetadataType.RICH_TEXT) 309 { 310 jsonObject.put("editableSource", _rightManager.hasRight(_currentUserProvider.getUser(), "CORE_Rights_SourceEdit", content) == RightResult.RIGHT_ALLOW); 311 } 312 313 314 if (!_contentTypesHelper.canWrite(content, metaDef)) 315 { 316 jsonObject.put("can-not-write", true); 317 } 318 319 if (metaDef.getType() == MetadataType.COMPOSITE) 320 { 321 jsonObject.putAll(compositeDefinition2JsonObject(content, metadataSetElement, metaDef, externalAndLocalMetadata)); 322 } 323 324 if (metaDef instanceof RichTextMetadataDefinition) 325 { 326 jsonObject.putAll(annotableDefinition2JsonObject((RichTextMetadataDefinition) metaDef)); 327 } 328 329 return jsonObject; 330 } 331 332 private Map<String, Object> compositeDefinition2JsonObject(Content content, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metaDef, Set<String> externalAndLocalMetadata) throws ProcessingException 333 { 334 Map<String, Object> jsonObject = new LinkedHashMap<>(); 335 336 if (metaDef instanceof RepeaterDefinition) 337 { 338 RepeaterDefinition repeaterDef = (RepeaterDefinition) metaDef; 339 340 Map<String, Object> repeater = new HashMap<>(); 341 342 repeater.put("initial-size", repeaterDef.getInitialSize()); 343 repeater.put("min-size", repeaterDef.getMinSize()); 344 345 int maxSize = repeaterDef.getMaxSize(); 346 if (maxSize >= 0) 347 { 348 repeater.put("max-size", maxSize); 349 } 350 351 352 repeater.put("add-label", repeaterDef.getAddLabel()); 353 repeater.put("del-label", repeaterDef.getDeleteLabel()); 354 repeater.put("header-label", repeaterDef.getHeaderLabel()); 355 356 repeater.put("composition", metadataSetElement2JsonObject(content, metaDef, metadataSetElement, externalAndLocalMetadata)); 357 358 jsonObject.put("repeater", repeater); 359 360 } 361 else 362 { 363 jsonObject.put("composition", metadataSetElement2JsonObject(content, metaDef, metadataSetElement, externalAndLocalMetadata)); 364 } 365 366 return jsonObject; 367 } 368 369 private Map<String, Object> annotableDefinition2JsonObject(RichTextMetadataDefinition metaDef) 370 { 371 Map<String, Object> jsonObject = new LinkedHashMap<>(); 372 373 List<SemanticAnnotation> annotations = metaDef.getSemanticAnnotations(); 374 if (annotations != null && annotations.size() > 0) 375 { 376 List<Map<String, Object>> annotationsObject = new ArrayList<>(); 377 378 for (SemanticAnnotation annotation : annotations) 379 { 380 Map<String, Object> annotationObject = new LinkedHashMap<>(); 381 382 annotationObject.put("name", annotation.getId()); 383 annotationObject.put("label", annotation.getLabel()); 384 annotationObject.put("description", annotation.getDescription()); 385 386 annotationsObject.add(annotationObject); 387 } 388 389 jsonObject.put("annotations", annotationsObject); 390 391 } 392 return jsonObject; 393 } 394 395 /** 396 * Retrieves a sub metadata definition from a content type or 397 * a parent metadata definition. 398 * @param content the content. 399 * @param parentMetadataDefinition the parent metadata definition. 400 * @param metadataName the metadata name. 401 * @return the metadata definition found or <code>null</code> otherwise. 402 */ 403 protected MetadataDefinition _getMetadataDefinition(Content content, MetadataDefinition parentMetadataDefinition, String metadataName) 404 { 405 MetadataDefinition metadataDefinition = null; 406 407 if (parentMetadataDefinition == null) 408 { 409 if (content != null) 410 { 411 metadataDefinition = _contentTypesHelper.getMetadataDefinition(metadataName, content.getTypes(), content.getMixinTypes()); 412 } 413 } 414 else 415 { 416 metadataDefinition = parentMetadataDefinition.getMetadataDefinition(metadataName); 417 } 418 419 return metadataDefinition; 420 } 421 422}