001/* 002 * Copyright 2016 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.Arrays; 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025 026import org.apache.avalon.framework.component.Component; 027import org.apache.avalon.framework.logger.AbstractLogEnabled; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.commons.lang3.ArrayUtils; 032import org.apache.commons.lang3.StringUtils; 033 034import org.ametys.cms.ObservationConstants; 035import org.ametys.cms.contenttype.ContentConstants; 036import org.ametys.cms.contenttype.ContentType; 037import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 038import org.ametys.cms.contenttype.ContentTypesHelper; 039import org.ametys.cms.contenttype.MetadataDefinition; 040import org.ametys.cms.contenttype.MetadataManager; 041import org.ametys.cms.contenttype.MetadataType; 042import org.ametys.cms.contenttype.RepeaterDefinition; 043import org.ametys.cms.repository.Content; 044import org.ametys.cms.repository.ModifiableContent; 045import org.ametys.cms.repository.WorkflowAwareContent; 046import org.ametys.cms.workflow.AbstractContentWorkflowComponent; 047import org.ametys.core.observation.Event; 048import org.ametys.core.observation.ObservationManager; 049import org.ametys.core.ui.Callable; 050import org.ametys.core.user.CurrentUserProvider; 051import org.ametys.core.user.UserIdentity; 052import org.ametys.plugins.core.user.UserHelper; 053import org.ametys.plugins.explorer.resources.Resource; 054import org.ametys.plugins.repository.AmetysObjectIterable; 055import org.ametys.plugins.repository.AmetysObjectResolver; 056import org.ametys.plugins.repository.AmetysRepositoryException; 057import org.ametys.plugins.repository.TraversableAmetysObject; 058import org.ametys.plugins.repository.UnknownAmetysObjectException; 059import org.ametys.plugins.repository.metadata.BinaryMetadata; 060import org.ametys.plugins.repository.metadata.CompositeMetadata; 061import org.ametys.plugins.repository.metadata.RichText; 062import org.ametys.plugins.repository.metadata.UnknownMetadataException; 063import org.ametys.plugins.workflow.AbstractWorkflowComponent; 064import org.ametys.plugins.workflow.support.WorkflowProvider; 065import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 066import org.ametys.runtime.parameter.Enumerator; 067 068import com.opensymphony.workflow.WorkflowException; 069 070/** 071 * Helper for {@link Content} 072 * 073 */ 074public class ContentHelper extends AbstractLogEnabled implements Component, Serviceable 075{ 076 /** The component role. */ 077 public static final String ROLE = ContentHelper.class.getName(); 078 079 private AmetysObjectResolver _resolver; 080 private ContentTypesHelper _contentTypesHelper; 081 private ContentTypeExtensionPoint _contentTypeEP; 082 083 private ObservationManager _observationManager; 084 private WorkflowProvider _workflowProvider; 085 private CurrentUserProvider _currentUserProvider; 086 087 private UserHelper _userHelper; 088 089 @Override 090 public void service(ServiceManager smanager) throws ServiceException 091 { 092 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 093 _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 094 _userHelper = (UserHelper) smanager.lookup(UserHelper.ROLE); 095 _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 096 _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE); 097 _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE); 098 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 099 } 100 101 /** 102 * Add a content type to an existing content 103 * @param contentId The content id 104 * @param contentTypeId The content type to add 105 * @param actionId The workflow action id 106 * @return The result in a Map 107 * @throws WorkflowException if 108 * @throws AmetysRepositoryException if an error occurred 109 */ 110 @Callable 111 public Map<String, Object> addContentType (String contentId, String contentTypeId, int actionId) throws AmetysRepositoryException, WorkflowException 112 { 113 return _setContentType(contentId, contentTypeId, actionId, false); 114 } 115 116 /** 117 * Remove a content type to an existing content 118 * @param contentId The content id 119 * @param contentTypeId The content type to add 120 * @param actionId The workflow action id 121 * @return The result in a Map 122 * @throws WorkflowException if 123 * @throws AmetysRepositoryException if an error occurred 124 */ 125 @Callable 126 public Map<String, Object> removeContentType (String contentId, String contentTypeId, int actionId) throws AmetysRepositoryException, WorkflowException 127 { 128 return _setContentType(contentId, contentTypeId, actionId, true); 129 } 130 131 /** 132 * Add a mixin type to an existing content 133 * @param contentId The content id 134 * @param mixinId The mixin type to add 135 * @param actionId The workflow action id 136 * @return The result in a Map 137 * @throws WorkflowException if 138 * @throws AmetysRepositoryException if an error occurred 139 */ 140 @Callable 141 public Map<String, Object> addMixinType (String contentId, String mixinId, int actionId) throws AmetysRepositoryException, WorkflowException 142 { 143 return _setMixinType(contentId, mixinId, actionId, false); 144 } 145 146 /** 147 * Remove a mixin type to an existing content 148 * @param contentId The content id 149 * @param mixinId The mixin type to add 150 * @param actionId The workflow action id 151 * @return The result in a Map 152 * @throws WorkflowException if 153 * @throws AmetysRepositoryException if an error occurred 154 */ 155 @Callable 156 public Map<String, Object> removeMixinType (String contentId, String mixinId, int actionId) throws AmetysRepositoryException, WorkflowException 157 { 158 return _setMixinType(contentId, mixinId, actionId, true); 159 } 160 161 /** 162 * Get metadata values by their path 163 * @param contentId The id of content 164 * @param metadataPath The path of metadata to retrieve 165 * @return The metadata values 166 */ 167 @Callable 168 public Map<String, Object> getMetadataValues(String contentId, Collection<String> metadataPath) 169 { 170 Content content = _resolver.resolveById(contentId); 171 return getMetadataValues(content, metadataPath); 172 } 173 174 /** 175 * Get metadata values by their path 176 * @param content The content. 177 * @param metadataPaths The path of metadata to retrieve, slash-separated. 178 * @return The metadata values 179 */ 180 @Callable 181 public Map<String, Object> getMetadataValues(Content content, Collection<String> metadataPaths) 182 { 183 Map<String, Object> values = new HashMap<>(); 184 185 for (String path : metadataPaths) 186 { 187 String[] names = StringUtils.split(path, ContentConstants.METADATA_PATH_SEPARATOR); 188 189 try 190 { 191 MetadataDefinition metadataDefinition = _contentTypesHelper.getMetadataDefinition(names[0], content.getTypes(), content.getMixinTypes()); 192 if (metadataDefinition != null) 193 { 194 Object value = getMetadataValues(content, path); 195 values.put(path, value); 196 } 197 else 198 { 199 values.put(path, null); 200 } 201 } 202 catch (UnknownMetadataException e) 203 { 204 if (getLogger().isInfoEnabled()) 205 { 206 getLogger().info("Metadata of path '" + path + "' does not exist for content '" + content.getId() + "'.", e); 207 } 208 values.put(path, null); 209 } 210 } 211 212 return values; 213 } 214 215 /** 216 * Get the value of a metadata on a given content. The metadata can be held by a linked content. 217 * @param content The content. 218 * @param metadataPath The metadata path, slash-separated. 219 * It can represent a property on one or more linked contents, i.e. "composite/linkedContent/secondContent/title". 220 * @return The metadata value. 221 */ 222 public Object getMetadataValue(Content content, String metadataPath) 223 { 224 List<Object> values = new ArrayList<>(); 225 226 getMetadataValues(content, metadataPath, false, values); 227 228 return values.isEmpty() ? null : values.get(0); 229 } 230 231 /** 232 * Get the values of a metadata on a given content. The metadata can be held by a linked content. 233 * @param content The content. 234 * @param metadataPath The metadata path, slash-separated. 235 * It can represent a property on one or more linked contents, i.e. "composite/linkedContent/secondContent/title". 236 * @return The metadata values as a List. 237 */ 238 public List<Object> getMetadataValues(Content content, String metadataPath) 239 { 240 return getMetadataValues(content, metadataPath, false); 241 } 242 243 /** 244 * Get the values of a metadata on a given content. The metadata can be held by a linked content. 245 * @param content The content. 246 * @param metadataPath The metadata path, slash-separated. 247 * It can represent a property on one or more linked contents, i.e. "composite/linkedContent/secondContent/title". 248 * @param allowsNullValues If true, empty values will be added to the list as "null" 249 * @return The metadata values as a List. 250 */ 251 public List<Object> getMetadataValues(Content content, String metadataPath, boolean allowsNullValues) 252 { 253 List<Object> values = new ArrayList<>(); 254 255 getMetadataValues(content, metadataPath, allowsNullValues, values); 256 257 return values; 258 } 259 260 /** 261 * Get the values of a metadata reference in a specified content.<br> 262 * The metadata can be in the content or in another linked content (directly or transitively). 263 * @param content The content. 264 * @param fullMetaPath The full metadata path (separated by '/'). 265 * @param allowsNullValues If true, empty values will be added to the list as "null" 266 * @param values The list of values to be filled by this method. 267 */ 268 protected void getMetadataValues(Content content, String fullMetaPath, boolean allowsNullValues, List<Object> values) 269 { 270 try 271 { 272 CompositeMetadata metadataHolder = content.getMetadataHolder(); 273 int slashPos = fullMetaPath.indexOf(ContentConstants.METADATA_PATH_SEPARATOR); 274 String metaName = slashPos >= 0 ? fullMetaPath.substring(0, slashPos) : fullMetaPath; 275 String remainingPath = slashPos >= 0 ? fullMetaPath.substring(slashPos + 1) : ""; 276 277 MetadataDefinition definition = _contentTypesHelper.getMetadataDefinition(metaName, content); 278 279 if (definition != null) 280 { 281 getMetadataValues(content, metadataHolder, definition, metaName, remainingPath, allowsNullValues, values); 282 } 283 } 284 catch (UnknownMetadataException e) 285 { 286 // Ignore, just do not fill the value list. 287 } 288 } 289 290 /** 291 * Get the values of a metadata reference in a specified content.<br> 292 * The metadata can be in the content or in another linked content (directly or transitively). 293 * @param currentContent The content currently being browsed. 294 * @param metaHolder The current metadata holder, must in some way possess the "metaName" metadata. 295 * @param definition The current metadata definition. 296 * @param metaName The metadata name. 297 * @param remainingPath The remaining path, can be empty. 298 * @param allowsNullValues If true, empty values will be added to the list as "null" 299 * @param values The list of values to be filled by this method. 300 */ 301 @SuppressWarnings("unchecked") 302 protected void getMetadataValues(Content currentContent, CompositeMetadata metaHolder, MetadataDefinition definition, String metaName, String remainingPath, boolean allowsNullValues, List<Object> values) 303 { 304 MetadataType type = definition.getType(); 305 306 if (StringUtils.isNotBlank(remainingPath) && (type == MetadataType.CONTENT || type == MetadataType.SUB_CONTENT)) 307 { 308 // metaName represents a metadata in the target content content type. 309 getContentMetadataValues(metaHolder, metaName, remainingPath, allowsNullValues, values, type); 310 } 311 else if (StringUtils.isNotBlank(remainingPath) && type == MetadataType.COMPOSITE) 312 { 313 // The metadata is a composite, either a real one or a repeater. 314 getCompositeMetadataValues(currentContent, metaHolder, definition, metaName, remainingPath, allowsNullValues, values); 315 } 316 else 317 { 318 // metaName represents a sub-metadata. 319 Object value = getMetadataValue(metaHolder, metaName, definition); 320 321 if (value != null || allowsNullValues) 322 { 323 if (value instanceof Collection) 324 { 325 values.addAll((Collection<Object>) value); 326 } 327 else 328 { 329 values.add(value); 330 } 331 } 332 } 333 } 334 335 /** 336 * Get the values of a metadata reference in a specified content.<br> 337 * The metadata can be in the content or in another linked content (directly or transitively). 338 * @param currentContent The content currently being browsed. 339 * @param metaHolder The current metadata holder, must in some way possess the "metaName" metadata. 340 * @param definition The current metadata definition. 341 * @param metaName The metadata name. 342 * @param remainingPath The remaining path, can be empty. 343 * @param allowsNullValues If true, empty values will be added to the list as "null" 344 * @param values The list of values to be filled by this method. 345 */ 346 protected void getCompositeMetadataValues(Content currentContent, CompositeMetadata metaHolder, MetadataDefinition definition, String metaName, String remainingPath, boolean allowsNullValues, List<Object> values) 347 { 348 // Compute sub-metadata information. 349 int slashPos = remainingPath.indexOf(ContentConstants.METADATA_PATH_SEPARATOR); 350 String subMetaName = slashPos >= 0 ? remainingPath.substring(0, slashPos) : remainingPath; 351 String nextPath = slashPos >= 0 ? remainingPath.substring(slashPos + 1) : ""; 352 353 CompositeMetadata subHolder = metaHolder.getCompositeMetadata(metaName); 354 MetadataDefinition subDefinition = definition.getMetadataDefinition(subMetaName); 355 356 if (definition instanceof RepeaterDefinition) 357 { 358 // Repeater: get and sort the entry names. 359 String[] entries = subHolder.getMetadataNames(); 360 Arrays.sort(entries, MetadataManager.REPEATER_ENTRY_COMPARATOR); 361 362 for (String entryName : entries) 363 { 364 CompositeMetadata entry = subHolder.getCompositeMetadata(entryName); 365 getMetadataValues(currentContent, entry, subDefinition, subMetaName, nextPath, allowsNullValues, values); 366 } 367 } 368 else 369 { 370 // Composite. 371 getMetadataValues(currentContent, subHolder, subDefinition, subMetaName, nextPath, allowsNullValues, values); 372 } 373 } 374 375 /** 376 * Get the values of a metadata reference in a specified content.<br> 377 * The metadata can be in the content or in another linked content (directly or transitively). 378 * @param metaHolder The current metadata holder, must in some way possess the "metaName" metadata. 379 * @param metaName The metadata name. 380 * @param remainingPath The remaining path, can be empty. 381 * @param allowsNullValues If true, empty values will be added to the list as "null" 382 * @param values The list of values to be filled by this method. 383 * @param type The metadata type 384 */ 385 protected void getContentMetadataValues(CompositeMetadata metaHolder, String metaName, String remainingPath, boolean allowsNullValues, List<Object> values, MetadataType type) 386 { 387 if (type == MetadataType.CONTENT) 388 { 389 String[] refContentIds = metaHolder.getStringArray(metaName, new String[0]); 390 for (String refContentId : refContentIds) 391 { 392 try 393 { 394 Content refContent = _resolver.resolveById(refContentId); 395 getMetadataValues(refContent, remainingPath, allowsNullValues, values); 396 } 397 catch (AmetysRepositoryException e) 398 { 399 // Ignore, just do not fill the value list. 400 if (getLogger().isWarnEnabled()) 401 { 402 getLogger().warn("A Repository Exception occured while trying to get the values of the referenced content '" + refContentId + "'", e); 403 } 404 } 405 } 406 } 407 else 408 { 409 try (AmetysObjectIterable<Content> subContents = metaHolder.getObjectCollection(metaName).getChildren()) 410 { 411 for (Content subContent : subContents) 412 { 413 getMetadataValues(subContent, remainingPath, allowsNullValues, values); 414 } 415 } 416 } 417 } 418 419 /** 420 * Get content edition information. 421 * @param contentId the content ID. 422 * @return a Map containing content edition information. 423 */ 424 @Callable 425 public Map<String, Object> getContentEditionInformation(String contentId) 426 { 427 Map<String, Object> info = new HashMap<>(); 428 429 Content content = _resolver.resolveById(contentId); 430 431 info.put("hasIndexingReferences", hasIndexingReferences(content)); 432 433 return info; 434 } 435 436 /** 437 * Test if the given content has indexing references, i.e. if modifying it 438 * potentially implies reindexing other contents. 439 * @param content the content to test. 440 * @return <code>true</code> if one of the content types or mixins has indexing references, <code>false</code> otherwise. 441 */ 442 public boolean hasIndexingReferences(Content content) 443 { 444 for (String cTypeId : content.getTypes()) 445 { 446 if (_contentTypeEP.hasIndexingReferences(cTypeId)) 447 { 448 return true; 449 } 450 } 451 452 for (String mixinId : content.getMixinTypes()) 453 { 454 if (_contentTypeEP.hasIndexingReferences(mixinId)) 455 { 456 return true; 457 } 458 } 459 460 return false; 461 } 462 463 private Map<String, Object> _setContentType (String contentId, String contentTypeId, int actionId, boolean remove) throws AmetysRepositoryException, WorkflowException 464 { 465 Map<String, Object> result = new HashMap<>(); 466 467 Content content = _resolver.resolveById(contentId); 468 469 if (content instanceof ModifiableContent) 470 { 471 ModifiableContent modifiableContent = (ModifiableContent) content; 472 473 List<String> currentTypes = new ArrayList<>(Arrays.asList(content.getTypes())); 474 475 boolean hasChange = false; 476 if (remove) 477 { 478 if (currentTypes.size() > 1) 479 { 480 hasChange = currentTypes.remove(contentTypeId); 481 } 482 else 483 { 484 result.put("failure", true); 485 result.put("msg", "empty-list"); 486 } 487 } 488 else if (!currentTypes.contains(contentTypeId)) 489 { 490 ContentType cType = _contentTypeEP.getExtension(contentTypeId); 491 if (cType.isMixin()) 492 { 493 result.put("failure", true); 494 result.put("msg", "no-content-type"); 495 getLogger().error("Content type '" + contentTypeId + "' is a mixin type. It can not be added as content type."); 496 } 497 else if (!_contentTypesHelper.isCompatibleContentType(content, contentTypeId)) 498 { 499 result.put("failure", true); 500 result.put("msg", "invalid-content-type"); 501 getLogger().error("Content type '" + contentTypeId + "' is incompatible with content '" + contentId + "'."); 502 } 503 else 504 { 505 currentTypes.add(contentTypeId); 506 hasChange = true; 507 } 508 } 509 510 if (hasChange) 511 { 512 // TODO check if the content type is compatible 513 modifiableContent.setTypes(currentTypes.toArray(new String[currentTypes.size()])); 514 modifiableContent.saveChanges(); 515 516 if (content instanceof WorkflowAwareContent) 517 { 518 519 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 520 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 521 522 Map<String, Object> inputs = new HashMap<>(); 523 inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, content); 524 inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, new HashMap<String, Object>()); 525 inputs.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String>()); 526 527 workflow.doAction(waContent.getWorkflowId(), actionId, inputs); 528 } 529 530 result.put("success", true); 531 532 Map<String, Object> eventParams = new HashMap<>(); 533 eventParams.put(ObservationConstants.ARGS_CONTENT, modifiableContent); 534 eventParams.put(ObservationConstants.ARGS_CONTENT_ID, contentId); 535 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _currentUserProvider.getUser(), eventParams)); 536 } 537 } 538 else 539 { 540 result.put("failure", true); 541 result.put("msg", "no-modifiable-content"); 542 getLogger().error("Can not modified content types to a non-modifiable content '" + content.getId() + "'."); 543 } 544 545 return result; 546 } 547 548 private Map<String, Object> _setMixinType (String contentId, String mixinId, int actionId, boolean remove) throws AmetysRepositoryException, WorkflowException 549 { 550 Map<String, Object> result = new HashMap<>(); 551 552 Content content = _resolver.resolveById(contentId); 553 554 if (content instanceof ModifiableContent) 555 { 556 ModifiableContent modifiableContent = (ModifiableContent) content; 557 558 List<String> currentMixins = new ArrayList<>(Arrays.asList(content.getMixinTypes())); 559 560 boolean hasChange = false; 561 if (remove) 562 { 563 hasChange = currentMixins.remove(mixinId); 564 } 565 else if (!currentMixins.contains(mixinId)) 566 { 567 ContentType cType = _contentTypeEP.getExtension(mixinId); 568 if (!cType.isMixin()) 569 { 570 result.put("failure", true); 571 result.put("msg", "no-mixin"); 572 getLogger().error("The content type '" + mixinId + "' is not a mixin type, it can be not be added as a mixin."); 573 } 574 else if (!_contentTypesHelper.isCompatibleContentType(content, mixinId)) 575 { 576 result.put("failure", true); 577 result.put("msg", "invalid-mixin"); 578 getLogger().error("Mixin '" + mixinId + "' is incompatible with content '" + contentId + "'."); 579 } 580 else 581 { 582 currentMixins.add(mixinId); 583 hasChange = true; 584 } 585 } 586 587 if (hasChange) 588 { 589 // TODO check if the content type is compatible 590 modifiableContent.setMixinTypes(currentMixins.toArray(new String[currentMixins.size()])); 591 modifiableContent.saveChanges(); 592 593 if (content instanceof WorkflowAwareContent) 594 { 595 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 596 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 597 598 Map<String, Object> inputs = new HashMap<>(); 599 inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, content); 600 inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, new HashMap<String, Object>()); 601 inputs.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String>()); 602 603 workflow.doAction(waContent.getWorkflowId(), actionId, inputs); 604 } 605 606 result.put("success", true); 607 608 Map<String, Object> eventParams = new HashMap<>(); 609 eventParams.put(ObservationConstants.ARGS_CONTENT, modifiableContent); 610 eventParams.put(ObservationConstants.ARGS_CONTENT_ID, contentId); 611 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _currentUserProvider.getUser(), eventParams)); 612 } 613 } 614 else 615 { 616 result.put("failure", true); 617 result.put("msg", "no-modifiable-content"); 618 getLogger().error("Can not modified mixins to a non-modifiable content '" + content.getId() + "'."); 619 } 620 621 return result; 622 } 623 624 /** 625 * Get a metadata value from a composite metadata. 626 * @param metaHolder the composite metadata. 627 * @param name the metadata name (path forbidden). 628 * @param definition the metadata definition. 629 * @return the metadata value. 630 */ 631 public Object getMetadataValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 632 { 633 if (name.contains(ContentConstants.METADATA_PATH_SEPARATOR)) 634 { 635 throw new IllegalArgumentException("The metadata name cannot represent a path."); 636 } 637 638 Object value = null; 639 640 switch (definition.getType()) 641 { 642 case STRING: 643 value = getStringValue(metaHolder, name, definition); 644 break; 645 case LONG: 646 value = getLongValue(metaHolder, name, definition); 647 break; 648 case DOUBLE: 649 value = getDoubleValue(metaHolder, name, definition); 650 break; 651 case BOOLEAN: 652 value = getBooleanValue(metaHolder, name, definition); 653 break; 654 case DATE: 655 case DATETIME: 656 value = getDateValue(metaHolder, name, definition); 657 break; 658 case USER: 659 value = getUserValue(metaHolder, name, definition); 660 break; 661 case BINARY: 662 value = getBinaryValue(metaHolder, name, definition); 663 break; 664 case FILE: 665 value = getFileValue(metaHolder, name, definition); 666 break; 667 case GEOCODE: 668 value = getGeocodeValue(metaHolder, name, definition); 669 break; 670 case RICH_TEXT: 671 value = getRichTextValue(metaHolder, name, definition); 672 break; 673 case REFERENCE: 674 value = getReferenceValue(metaHolder, name, definition); 675 break; 676 case CONTENT: 677 value = getContentReferenceValue(metaHolder, name, definition); 678 break; 679 case SUB_CONTENT: 680 value = getSubcontentValue(metaHolder, name, definition); 681 break; 682 default: 683 value = getStringValue(metaHolder, name, definition); 684 break; 685 } 686 687 return value; 688 } 689 690 /** 691 * Get a string metadata value from a composite metadata. 692 * @param metaHolder the composite metadata. 693 * @param name the metadata name. 694 * @param definition the metadata definition. 695 * @return the metadata value as a String or String List. 696 */ 697 protected Object getStringValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 698 { 699 try 700 { 701 if (definition.isMultiple()) 702 { 703 return Arrays.asList(metaHolder.getStringArray(name)); 704 } 705 else 706 { 707 return metaHolder.getString(name); 708 } 709 } 710 catch (UnknownMetadataException e) 711 { 712 // Ignore, just return null. 713 return null; 714 } 715 } 716 717 /** 718 * Build a map representing an enumerator entry (value and label). 719 * @param enumerator the metadata enumerator. 720 * @param value the value to generate. 721 * @return a Map representing the enumerator entry (value and label). 722 */ 723 protected Map<String, Object> getEnumeratedValueMap(Enumerator enumerator, String value) 724 { 725 Map<String, Object> entry = new HashMap<>(); 726 727 entry.put("value", value); 728 try 729 { 730 entry.put("label", enumerator.getEntry(value)); 731 } 732 catch (Exception e) 733 { 734 entry.put("label", value); 735 } 736 return entry; 737 } 738 739 /** 740 * Get a long metadata value from a composite metadata. 741 * @param metaHolder the composite metadata. 742 * @param name the metadata name. 743 * @param definition the metadata definition. 744 * @return the metadata value as a long or Long List. 745 */ 746 protected Object getLongValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 747 { 748 try 749 { 750 if (definition.isMultiple()) 751 { 752 return Arrays.asList(ArrayUtils.toObject(metaHolder.getLongArray(name))); 753 } 754 else 755 { 756 return metaHolder.getLong(name); 757 } 758 } 759 catch (UnknownMetadataException e) 760 { 761 // Ignore, just return null. 762 return null; 763 } 764 765 } 766 767 /** 768 * Get a double metadata value from a composite metadata. 769 * @param metaHolder the composite metadata. 770 * @param name the metadata name. 771 * @param definition the metadata definition. 772 * @return the metadata value as a double or Double List. 773 */ 774 protected Object getDoubleValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 775 { 776 try 777 { 778 if (definition.isMultiple()) 779 { 780 return Arrays.asList(ArrayUtils.toObject(metaHolder.getDoubleArray(name))); 781 } 782 else 783 { 784 return metaHolder.getDouble(name); 785 } 786 } 787 catch (UnknownMetadataException e) 788 { 789 // Ignore, just return null. 790 return null; 791 } 792 } 793 794 /** 795 * Get a boolean metadata value from a composite metadata. 796 * @param metaHolder the composite metadata. 797 * @param name the metadata name. 798 * @param definition the metadata definition. 799 * @return the metadata value as a boolean or Boolean List. 800 */ 801 protected Object getBooleanValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 802 { 803 try 804 { 805 if (definition.isMultiple()) 806 { 807 return Arrays.asList(ArrayUtils.toObject(metaHolder.getBooleanArray(name))); 808 } 809 else 810 { 811 return metaHolder.getBoolean(name); 812 } 813 } 814 catch (UnknownMetadataException e) 815 { 816 // Ignore, just return null. 817 return null; 818 } 819 } 820 821 /** 822 * Get a Date metadata value from a composite metadata. 823 * @param metaHolder the composite metadata. 824 * @param name the metadata name. 825 * @param definition the metadata definition. 826 * @return the metadata value as a Date or Date List. 827 */ 828 protected Object getDateValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 829 { 830 try 831 { 832 if (definition.isMultiple()) 833 { 834 return Arrays.asList(metaHolder.getDateArray(name)); 835 } 836 else 837 { 838 return metaHolder.getDate(name); 839 } 840 } 841 catch (UnknownMetadataException e) 842 { 843 // Ignore, just return null. 844 return null; 845 } 846 } 847 848 /** 849 * Get a user metadata value from a composite metadata. 850 * @param metaHolder the composite metadata. 851 * @param name the metadata name. 852 * @param definition the metadata definition. 853 * @return the metadata value as a Map or List of Map. 854 */ 855 protected Object getUserValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 856 { 857 try 858 { 859 if (definition.isMultiple()) 860 { 861 List<Map<String, Object>> values = new ArrayList<>(); 862 863 for (UserIdentity identity : metaHolder.getUserArray(name)) 864 { 865 values.add(_userHelper.user2json(identity, true)); 866 } 867 868 return values; 869 } 870 else 871 { 872 UserIdentity identity = metaHolder.getUser(name); 873 return _userHelper.user2json(identity, true); 874 } 875 } 876 catch (UnknownMetadataException e) 877 { 878 // Ignore, just return null. 879 } 880 881 return null; 882 } 883 884 /** 885 * Get a binary metadata value from a composite metadata. 886 * @param metaHolder the composite metadata. 887 * @param name the metadata name. 888 * @param definition the metadata definition. 889 * @return the metadata value as a Map. 890 */ 891 protected Object getBinaryValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 892 { 893 Map<String, Object> info = new HashMap<>(); 894 895 BinaryMetadata value = metaHolder.getBinaryMetadata(name); 896 String filename = value.getFilename(); 897 898 info.put("type", "metadata"); 899 info.put("mime-type", value.getMimeType()); 900 info.put("name", name); 901 info.put("size", value.getLength()); 902 info.put("lastModified", value.getLastModified()); 903 904 if (filename != null) 905 { 906 info.put("filename", filename); 907 } 908 909 return info; 910 } 911 912 /** 913 * Get a file metadata value from a composite metadata. 914 * @param metaHolder the composite metadata. 915 * @param name the metadata name. 916 * @param definition the metadata definition. 917 * @return the metadata value as a Map. 918 */ 919 protected Object getFileValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 920 { 921 if (org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType.BINARY.equals(metaHolder.getType(name))) 922 { 923 return getBinaryValue(metaHolder, name, definition); 924 } 925 else 926 { 927 return getResourceFileValue(metaHolder, name, definition); 928 } 929 } 930 931 /** 932 * Get a resource-file metadata value from a composite metadata. 933 * @param metaHolder the composite metadata. 934 * @param name the metadata name. 935 * @param definition the metadata definition. 936 * @return the metadata value as a Map. 937 */ 938 protected Object getResourceFileValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 939 { 940 try 941 { 942 Map<String, Object> info = new HashMap<>(); 943 944 String value = metaHolder.getString(name); 945 946 Resource resource = (Resource) _resolver.resolveById(value); 947 948 String filename = resource.getName(); 949 950 info.put("type", "explorer"); 951 info.put("id", resource.getId()); 952 info.put("mime-type", resource.getMimeType()); 953 info.put("size", resource.getLength()); 954 info.put("lastModified", resource.getLastModified()); 955 956 if (filename != null) 957 { 958 info.put("filename", filename); 959 } 960 961 return info; 962 } 963 catch (UnknownAmetysObjectException e) 964 { 965 // Ignore, just return null. 966 return null; 967 } 968 } 969 970 /** 971 * Get a geocode metadata value from a composite metadata. 972 * @param metaHolder the composite metadata. 973 * @param name the metadata name. 974 * @param definition the metadata definition. 975 * @return the metadata value as a Map. 976 */ 977 protected Object getGeocodeValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 978 { 979 try 980 { 981 CompositeMetadata values = metaHolder.getCompositeMetadata(name); 982 983 if (values.hasMetadata("longitude") && values.hasMetadata("latitude")) 984 { 985 Double longitude = values.getDouble("longitude"); 986 Double latitude = values.getDouble("latitude"); 987 988 Map<String, Object> geocode = new LinkedHashMap<>(); 989 geocode.put("longitude", longitude); 990 geocode.put("latitude", latitude); 991 992 return geocode; 993 } 994 } 995 catch (UnknownMetadataException e) 996 { 997 // Ignore, just return null. 998 } 999 1000 return null; 1001 } 1002 1003 /** 1004 * Get a string (HTML format) reprensenting the rich text metadata value from a composite metadata. 1005 * @param metaHolder the composite metadata. 1006 * @param name the metadata name. 1007 * @param definition the metadata definition. 1008 * @return The richtext object 1009 */ 1010 protected RichText getRichTextValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 1011 { 1012 try 1013 { 1014 return metaHolder.getRichText(name); 1015 } 1016 catch (UnknownMetadataException e) 1017 { 1018 // Ignore, just return null. 1019 return null; 1020 } 1021 } 1022 1023 /** 1024 * Get a reference metadata value from a composite metadata. 1025 * @param metaHolder the composite metadata. 1026 * @param name the metadata name. 1027 * @param definition the metadata definition. 1028 * @return the metadata value as a List (if multiple) or Map (if single metadata) 1029 */ 1030 protected Object getReferenceValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 1031 { 1032 try 1033 { 1034 CompositeMetadata referencesComposite = metaHolder.getCompositeMetadata(name); 1035 1036 if (definition.isMultiple()) 1037 { 1038 List<Map<String, Object>> references = new ArrayList<>(); 1039 1040 String[] types = referencesComposite.getStringArray("types"); 1041 String[] values = referencesComposite.getStringArray("values"); 1042 1043 for (int i = 0; i < types.length; i++) 1044 { 1045 Map<String, Object> reference = new HashMap<>(2); 1046 reference.put("type", types[i]); 1047 reference.put("value", values[i]); 1048 1049 references.add(reference); 1050 } 1051 1052 return references; 1053 } 1054 else 1055 { 1056 String type = referencesComposite.getString("type"); 1057 String value = referencesComposite.getString("value"); 1058 1059 Map<String, Object> reference = new HashMap<>(2); 1060 reference.put("type", type); 1061 reference.put("value", value); 1062 1063 return reference; 1064 } 1065 } 1066 catch (UnknownMetadataException e) 1067 { 1068 // Ignore, just return null. 1069 return null; 1070 } 1071 } 1072 1073 /** 1074 * Get a content reference metadata value from a composite metadata. 1075 * @param metaHolder the composite metadata. 1076 * @param name the metadata name. 1077 * @param definition the metadata definition. 1078 * @return the metadata value as a List of content ID. 1079 */ 1080 protected Object getContentReferenceValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 1081 { 1082 try 1083 { 1084 String[] values = metaHolder.getStringArray(name); 1085 1086 if (values != null) 1087 { 1088 return Arrays.asList(values); 1089 } 1090 } 1091 catch (UnknownMetadataException e) 1092 { 1093 // Ignore, just return null. 1094 } 1095 1096 return null; 1097 } 1098 1099 /** 1100 * Get a sub-content metadata value from a composite metadata. 1101 * @param metaHolder the composite metadata. 1102 * @param name the metadata name. 1103 * @param definition the metadata definition. 1104 * @return the metadata value as a List of content ID. 1105 */ 1106 protected Object getSubcontentValue(CompositeMetadata metaHolder, String name, MetadataDefinition definition) 1107 { 1108 try 1109 { 1110 TraversableAmetysObject contentMeta = metaHolder.getObjectCollection(name); 1111 1112 List<String> ids = new ArrayList<>(); 1113 1114 try (AmetysObjectIterable<Content> contents = contentMeta.getChildren()) 1115 { 1116 for (Content refContent : contents) 1117 { 1118 ids.add(refContent.getId()); 1119 } 1120 } 1121 1122 return ids; 1123 } 1124 catch (UnknownMetadataException e) 1125 { 1126 // Ignore, just return null. 1127 } 1128 1129 return null; 1130 } 1131 1132 /** 1133 * Determines if the content is a simple content type 1134 * @param content The content 1135 * @return true if content is simple 1136 */ 1137 public boolean isSimple (Content content) 1138 { 1139 for (String cTypeId : content.getTypes()) 1140 { 1141 ContentType cType = _contentTypeEP.getExtension(cTypeId); 1142 if (cType != null) 1143 { 1144 if (!cType.isSimple()) 1145 { 1146 return false; 1147 } 1148 } 1149 else 1150 { 1151 if (getLogger().isWarnEnabled()) 1152 { 1153 getLogger().warn(String.format("Unable to determine if a content is simple, unknown content type : '%s'.", cTypeId)); 1154 } 1155 } 1156 } 1157 return true; 1158 } 1159}