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.Collections; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.LinkedHashMap; 025import java.util.List; 026import java.util.Locale; 027import java.util.Map; 028 029import javax.jcr.Node; 030import javax.jcr.NodeIterator; 031import javax.jcr.RepositoryException; 032 033import org.apache.avalon.framework.component.Component; 034import org.apache.avalon.framework.context.Context; 035import org.apache.avalon.framework.context.ContextException; 036import org.apache.avalon.framework.context.Contextualizable; 037import org.apache.avalon.framework.logger.AbstractLogEnabled; 038import org.apache.avalon.framework.service.ServiceException; 039import org.apache.avalon.framework.service.ServiceManager; 040import org.apache.avalon.framework.service.Serviceable; 041import org.apache.cocoon.components.ContextHelper; 042import org.apache.commons.collections.CollectionUtils; 043import org.apache.commons.lang3.ArrayUtils; 044import org.apache.commons.lang3.StringUtils; 045import org.apache.commons.lang3.tuple.ImmutablePair; 046import org.apache.commons.lang3.tuple.Pair; 047 048import org.ametys.cms.ObservationConstants; 049import org.ametys.cms.content.references.OutgoingReferencesHelper; 050import org.ametys.cms.contenttype.ContentConstants; 051import org.ametys.cms.contenttype.ContentType; 052import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 053import org.ametys.cms.contenttype.ContentTypesHelper; 054import org.ametys.cms.contenttype.MetadataDefinition; 055import org.ametys.cms.contenttype.MetadataManager; 056import org.ametys.cms.contenttype.MetadataType; 057import org.ametys.cms.contenttype.RepeaterDefinition; 058import org.ametys.cms.repository.Content; 059import org.ametys.cms.repository.DefaultContent; 060import org.ametys.cms.repository.ModifiableContent; 061import org.ametys.cms.repository.WorkflowAwareContent; 062import org.ametys.cms.search.model.SystemProperty; 063import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 064import org.ametys.cms.workflow.AbstractContentWorkflowComponent; 065import org.ametys.core.observation.Event; 066import org.ametys.core.observation.ObservationManager; 067import org.ametys.core.ui.Callable; 068import org.ametys.core.user.CurrentUserProvider; 069import org.ametys.plugins.explorer.resources.Resource; 070import org.ametys.plugins.repository.AmetysObjectResolver; 071import org.ametys.plugins.repository.AmetysRepositoryException; 072import org.ametys.plugins.repository.RepositoryConstants; 073import org.ametys.plugins.repository.UnknownAmetysObjectException; 074import org.ametys.plugins.repository.jcr.JCRAmetysObject; 075import org.ametys.plugins.repository.metadata.CompositeMetadata; 076import org.ametys.plugins.repository.metadata.MultilingualString; 077import org.ametys.plugins.repository.metadata.MultilingualStringHelper; 078import org.ametys.plugins.repository.metadata.UnknownMetadataException; 079import org.ametys.plugins.workflow.AbstractWorkflowComponent; 080import org.ametys.plugins.workflow.support.WorkflowProvider; 081import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 082import org.ametys.runtime.model.ModelItem; 083 084import com.opensymphony.workflow.WorkflowException; 085 086/** 087 * Helper for {@link Content} 088 * 089 */ 090public class ContentHelper extends AbstractLogEnabled implements Component, Serviceable, Contextualizable 091{ 092 /** The component role. */ 093 public static final String ROLE = ContentHelper.class.getName(); 094 095 private AmetysObjectResolver _resolver; 096 private ContentTypesHelper _contentTypesHelper; 097 private ContentTypeExtensionPoint _contentTypeEP; 098 099 private ObservationManager _observationManager; 100 private WorkflowProvider _workflowProvider; 101 private CurrentUserProvider _currentUserProvider; 102 private SystemPropertyExtensionPoint _systemPropertyExtensionPoint; 103 104 private Context _context; 105 106 @Override 107 public void service(ServiceManager smanager) throws ServiceException 108 { 109 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 110 _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 111 _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 112 _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE); 113 _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE); 114 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 115 _systemPropertyExtensionPoint = (SystemPropertyExtensionPoint) smanager.lookup(SystemPropertyExtensionPoint.ROLE); 116 } 117 118 @Override 119 public void contextualize(Context context) throws ContextException 120 { 121 _context = context; 122 } 123 124 /** 125 * Add a content type to an existing content 126 * @param contentId The content id 127 * @param contentTypeId The content type to add 128 * @param actionId The workflow action id 129 * @return The result in a Map 130 * @throws WorkflowException if 131 * @throws AmetysRepositoryException if an error occurred 132 */ 133 @Callable 134 public Map<String, Object> addContentType (String contentId, String contentTypeId, int actionId) throws AmetysRepositoryException, WorkflowException 135 { 136 return _setContentType(contentId, contentTypeId, actionId, false); 137 } 138 139 /** 140 * Remove a content type to an existing content 141 * @param contentId The content id 142 * @param contentTypeId The content type to add 143 * @param actionId The workflow action id 144 * @return The result in a Map 145 * @throws WorkflowException if 146 * @throws AmetysRepositoryException if an error occurred 147 */ 148 @Callable 149 public Map<String, Object> removeContentType (String contentId, String contentTypeId, int actionId) throws AmetysRepositoryException, WorkflowException 150 { 151 return _setContentType(contentId, contentTypeId, actionId, true); 152 } 153 154 /** 155 * Add a mixin type to an existing content 156 * @param contentId The content id 157 * @param mixinId The mixin type to add 158 * @param actionId The workflow action id 159 * @return The result in a Map 160 * @throws WorkflowException if 161 * @throws AmetysRepositoryException if an error occurred 162 */ 163 @Callable 164 public Map<String, Object> addMixinType (String contentId, String mixinId, int actionId) throws AmetysRepositoryException, WorkflowException 165 { 166 return _setMixinType(contentId, mixinId, actionId, false); 167 } 168 169 /** 170 * Remove a mixin type to an existing content 171 * @param contentId The content id 172 * @param mixinId The mixin type to add 173 * @param actionId The workflow action id 174 * @return The result in a Map 175 * @throws WorkflowException if 176 * @throws AmetysRepositoryException if an error occurred 177 */ 178 @Callable 179 public Map<String, Object> removeMixinType (String contentId, String mixinId, int actionId) throws AmetysRepositoryException, WorkflowException 180 { 181 return _setMixinType(contentId, mixinId, actionId, true); 182 } 183 184 /** 185 * Get content edition information. 186 * @param contentId the content ID. 187 * @return a Map containing content edition information. 188 */ 189 @Callable 190 public Map<String, Object> getContentEditionInformation(String contentId) 191 { 192 Map<String, Object> info = new HashMap<>(); 193 194 Content content = _resolver.resolveById(contentId); 195 196 info.put("hasIndexingReferences", hasIndexingReferences(content)); 197 198 return info; 199 } 200 201 /** 202 * Test if the given content has indexing references, i.e. if modifying it 203 * potentially implies reindexing other contents. 204 * @param content the content to test. 205 * @return <code>true</code> if one of the content types or mixins has indexing references, <code>false</code> otherwise. 206 */ 207 public boolean hasIndexingReferences(Content content) 208 { 209 for (String cTypeId : content.getTypes()) 210 { 211 if (_contentTypeEP.hasIndexingReferences(cTypeId)) 212 { 213 return true; 214 } 215 } 216 217 for (String mixinId : content.getMixinTypes()) 218 { 219 if (_contentTypeEP.hasIndexingReferences(mixinId)) 220 { 221 return true; 222 } 223 } 224 225 return false; 226 } 227 228 private Map<String, Object> _setContentType (String contentId, String contentTypeId, int actionId, boolean remove) throws AmetysRepositoryException, WorkflowException 229 { 230 Map<String, Object> result = new HashMap<>(); 231 232 Content content = _resolver.resolveById(contentId); 233 234 if (content instanceof ModifiableContent) 235 { 236 ModifiableContent modifiableContent = (ModifiableContent) content; 237 238 List<String> currentTypes = new ArrayList<>(Arrays.asList(content.getTypes())); 239 240 boolean hasChange = false; 241 if (remove) 242 { 243 if (currentTypes.size() > 1) 244 { 245 hasChange = currentTypes.remove(contentTypeId); 246 } 247 else 248 { 249 result.put("failure", true); 250 result.put("msg", "empty-list"); 251 } 252 } 253 else if (!currentTypes.contains(contentTypeId)) 254 { 255 ContentType cType = _contentTypeEP.getExtension(contentTypeId); 256 if (cType.isMixin()) 257 { 258 result.put("failure", true); 259 result.put("msg", "no-content-type"); 260 getLogger().error("Content type '" + contentTypeId + "' is a mixin type. It can not be added as content type."); 261 } 262 else if (!_contentTypesHelper.isCompatibleContentType(content, contentTypeId)) 263 { 264 result.put("failure", true); 265 result.put("msg", "invalid-content-type"); 266 getLogger().error("Content type '" + contentTypeId + "' is incompatible with content '" + contentId + "'."); 267 } 268 else 269 { 270 currentTypes.add(contentTypeId); 271 hasChange = true; 272 } 273 } 274 275 if (hasChange) 276 { 277 // TODO check if the content type is compatible 278 modifiableContent.setTypes(currentTypes.toArray(new String[currentTypes.size()])); 279 modifiableContent.saveChanges(); 280 281 if (content instanceof WorkflowAwareContent) 282 { 283 284 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 285 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 286 287 Map<String, Object> inputs = new HashMap<>(); 288 inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, content); 289 inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, new HashMap<String, Object>()); 290 inputs.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String>()); 291 292 workflow.doAction(waContent.getWorkflowId(), actionId, inputs); 293 } 294 295 result.put("success", true); 296 297 Map<String, Object> eventParams = new HashMap<>(); 298 eventParams.put(ObservationConstants.ARGS_CONTENT, modifiableContent); 299 eventParams.put(ObservationConstants.ARGS_CONTENT_ID, contentId); 300 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _currentUserProvider.getUser(), eventParams)); 301 } 302 } 303 else 304 { 305 result.put("failure", true); 306 result.put("msg", "no-modifiable-content"); 307 getLogger().error("Can not modified content types to a non-modifiable content '" + content.getId() + "'."); 308 } 309 310 return result; 311 } 312 313 private Map<String, Object> _setMixinType (String contentId, String mixinId, int actionId, boolean remove) throws AmetysRepositoryException, WorkflowException 314 { 315 Map<String, Object> result = new HashMap<>(); 316 317 Content content = _resolver.resolveById(contentId); 318 319 if (content instanceof ModifiableContent) 320 { 321 ModifiableContent modifiableContent = (ModifiableContent) content; 322 323 List<String> currentMixins = new ArrayList<>(Arrays.asList(content.getMixinTypes())); 324 325 boolean hasChange = false; 326 if (remove) 327 { 328 hasChange = currentMixins.remove(mixinId); 329 } 330 else if (!currentMixins.contains(mixinId)) 331 { 332 ContentType cType = _contentTypeEP.getExtension(mixinId); 333 if (!cType.isMixin()) 334 { 335 result.put("failure", true); 336 result.put("msg", "no-mixin"); 337 getLogger().error("The content type '" + mixinId + "' is not a mixin type, it can be not be added as a mixin."); 338 } 339 else if (!_contentTypesHelper.isCompatibleContentType(content, mixinId)) 340 { 341 result.put("failure", true); 342 result.put("msg", "invalid-mixin"); 343 getLogger().error("Mixin '" + mixinId + "' is incompatible with content '" + contentId + "'."); 344 } 345 else 346 { 347 currentMixins.add(mixinId); 348 hasChange = true; 349 } 350 } 351 352 if (hasChange) 353 { 354 // TODO check if the content type is compatible 355 modifiableContent.setMixinTypes(currentMixins.toArray(new String[currentMixins.size()])); 356 modifiableContent.saveChanges(); 357 358 if (content instanceof WorkflowAwareContent) 359 { 360 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 361 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 362 363 Map<String, Object> inputs = new HashMap<>(); 364 inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, content); 365 inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, new HashMap<String, Object>()); 366 inputs.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String>()); 367 368 workflow.doAction(waContent.getWorkflowId(), actionId, inputs); 369 } 370 371 result.put("success", true); 372 373 Map<String, Object> eventParams = new HashMap<>(); 374 eventParams.put(ObservationConstants.ARGS_CONTENT, modifiableContent); 375 eventParams.put(ObservationConstants.ARGS_CONTENT_ID, contentId); 376 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _currentUserProvider.getUser(), eventParams)); 377 } 378 } 379 else 380 { 381 result.put("failure", true); 382 result.put("msg", "no-modifiable-content"); 383 getLogger().error("Can not modified mixins to a non-modifiable content '" + content.getId() + "'."); 384 } 385 386 return result; 387 } 388 389 /** 390 * Determines if the content is a reference table content type 391 * @param content The content 392 * @return true if content is a reference table 393 */ 394 public boolean isReferenceTable(Content content) 395 { 396 for (String cTypeId : content.getTypes()) 397 { 398 ContentType cType = _contentTypeEP.getExtension(cTypeId); 399 if (cType != null) 400 { 401 if (!cType.isReferenceTable()) 402 { 403 return false; 404 } 405 } 406 else 407 { 408 if (getLogger().isWarnEnabled()) 409 { 410 getLogger().warn(String.format("Unable to determine if a content is a reference table, unknown content type : '%s'.", cTypeId)); 411 } 412 } 413 } 414 return true; 415 } 416 417 /** 418 * Determines if a content is a multilingual content 419 * @param content The content 420 * @return <code>true</code> if the content is an instance of content type 421 */ 422 public boolean isMultilingual(Content content) 423 { 424 for (String cTypeId : content.getTypes()) 425 { 426 ContentType cType = _contentTypeEP.getExtension(cTypeId); 427 if (cType != null && cType.isMultilingual()) 428 { 429 return true; 430 } 431 } 432 return false; 433 } 434 435 /** 436 * Determines if the content is a simple content type 437 * @param content The content 438 * @return true if content is simple 439 */ 440 public boolean isSimple (Content content) 441 { 442 for (String cTypeId : content.getTypes()) 443 { 444 ContentType cType = _contentTypeEP.getExtension(cTypeId); 445 if (cType != null) 446 { 447 if (!cType.isSimple()) 448 { 449 return false; 450 } 451 } 452 else 453 { 454 if (getLogger().isWarnEnabled()) 455 { 456 getLogger().warn(String.format("Unable to determine if a content is simple, unknown content type : '%s'.", cTypeId)); 457 } 458 } 459 } 460 return true; 461 } 462 463 /** 464 * Get the typed value(s) of a content at given path. 465 * The path can represent a system property id or a path of a metadata into the content or a metadata on one or more linked contents, such as 'composite/linkedContent/secondContent/composite/metadata'. 466 * The returned value is typed. 467 * @param content The content 468 * @param fieldPath The field id or the path to the metadata, separated by '/' 469 * @param defaultLocale The default locale to resolve localized values if the content's language is null. Can be null. 470 * @param resolveReferences <code>true</code> true to resolve references (such as resource or content) 471 * @return The typed final value. If the final field is multiple, or contains into a repeater or multiple 'CONTENT' metadata, the returned value will be a Collection 472 */ 473 public Object getValue(Content content, String fieldPath, Locale defaultLocale, boolean resolveReferences) 474 { 475 return getValue(content, fieldPath, defaultLocale, resolveReferences, false); 476 } 477 478 /** 479 * Get the typed value(s) of a content at given path. 480 * The path can represent a system property id or a path of an attribute into the content or an attribute on one or more linked contents, such as 'composite/linkedContent/secondContent/composite/attribute'. 481 * The returned value is typed. 482 * @param content The content 483 * @param fieldPath The field id or the path to the attribute, separated by '/' 484 * @param defaultLocale The default locale to resolve localized values if the content's language is null. Can be null. 485 * @param resolveReferences <code>true</code> true to resolve references (such as resource or content) 486 * @param returnNullValues <code>true</code> true to return null values when the attribute does not exists in a repeater or linked content. 487 * @return The typed final value. If the final field is multiple, or contained into a repeater or multiple 'CONTENT' attribute, the returned value will be a Collection 488 */ 489 public Object getValue(Content content, String fieldPath, Locale defaultLocale, boolean resolveReferences, boolean returnNullValues) 490 { 491 if (StringUtils.isEmpty(fieldPath)) 492 { 493 return null; 494 } 495 496 // Manage System Properties 497 String[] pathSegments = fieldPath.split(ModelItem.ITEM_PATH_SEPARATOR); 498 String propertyName = pathSegments[pathSegments.length - 1]; 499 500 if (_systemPropertyExtensionPoint.hasExtension(propertyName)) 501 { 502 if (_systemPropertyExtensionPoint.isDisplayable(propertyName)) 503 { 504 SystemProperty systemProperty = _systemPropertyExtensionPoint.getExtension(propertyName); 505 return _getSystemPropertyValue(content, pathSegments, systemProperty); 506 } 507 else 508 { 509 throw new IllegalArgumentException("The system property '" + propertyName + "' is not displayable."); 510 } 511 } 512 else 513 { 514 return getMetadataValue(content, fieldPath, defaultLocale, resolveReferences, returnNullValues); 515 } 516 } 517 518 private Object _getSystemPropertyValue(Content content, String[] pathSegments, SystemProperty systemProperty) 519 { 520 String contentFieldPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 0, pathSegments.length - 1); 521 List<Content> contentsContainingSystemProperty = getTargetContents(content, contentFieldPath); 522 if (contentsContainingSystemProperty.size() == 1) 523 { 524 Object value = systemProperty.getValue(contentsContainingSystemProperty.get(0)); 525 526 if (value instanceof Object[]) 527 { 528 return Arrays.asList((Object[]) value); 529 } 530 else 531 { 532 return value; 533 } 534 } 535 else 536 { 537 List<Object> values = new ArrayList<>(); 538 for (Content contentContainingSystemProperty : contentsContainingSystemProperty) 539 { 540 Object value = systemProperty.getValue(contentContainingSystemProperty); 541 542 if (value instanceof Object[]) 543 { 544 values.addAll(Arrays.asList((Object[]) value)); 545 } 546 else 547 { 548 values.add(value); 549 } 550 } 551 return values; 552 } 553 } 554 555 /** 556 * Get the content from which to get the system property. 557 * @param sourceContent The source content. 558 * @param fieldPath The field path 559 * @return The target content. 560 */ 561 public Content getTargetContent(Content sourceContent, String fieldPath) 562 { 563 if (StringUtils.isBlank(fieldPath)) 564 { 565 return sourceContent; 566 } 567 else 568 { 569 Object value = getMetadataValue(sourceContent, fieldPath, null, true); 570 if (value != null && value instanceof Content) 571 { 572 return (Content) value; 573 } 574 else if (value != null && value instanceof Collection<?> && ((Collection) value).size() > 0) 575 { 576 Object first = ((Collection) value).iterator().next(); 577 if (first instanceof Content) 578 { 579 return (Content) first; 580 } 581 } 582 } 583 584 return null; 585 } 586 587 /** 588 * Get the contents from which to get the system property. 589 * @param sourceContent The source content. 590 * @param fieldPath The field path 591 * @return The target contents. 592 */ 593 public List<Content> getTargetContents(Content sourceContent, String fieldPath) 594 { 595 List<Content> targetContents = new ArrayList<>(); 596 597 if (StringUtils.isBlank(fieldPath)) 598 { 599 targetContents.add(sourceContent); 600 } 601 else 602 { 603 Object value = getMetadataValue(sourceContent, fieldPath, null, true); 604 if (value != null && value instanceof Content) 605 { 606 targetContents.add((Content) value); 607 } 608 else if (value != null && value instanceof Collection<?>) 609 { 610 Iterator it = ((Collection) value).iterator(); 611 while (it.hasNext()) 612 { 613 Object object = it.next(); 614 if (object instanceof Content) 615 { 616 targetContents.add((Content) object); 617 } 618 619 } 620 } 621 } 622 623 return targetContents; 624 } 625 626 /** 627 * Get the typed metadata value(s) of a content at given path. 628 * The path can represent a path of a metadata into the content or a metadata on one or more linked contents, such as 'composite/linkedContent/secondContent/composite/metadata'. 629 * The returned value is typed. 630 * @param content The content 631 * @param metadataPath The path to the metadata, separated by '/' 632 * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. 633 * Only to be valued if initial content's language is null, otherwise set this parameter to null. 634 * @param resolveReferences <code>true</code> true to resolve references (such as resource or content) 635 * @return The typed final value. If the final metadata is multiple, or contains into a repeater or multiple 'CONTENT' metadata, the returned value will be a Collection 636 * @deprecated Use {@link Content#getValue(String, boolean)} instead 637 */ 638 @Deprecated 639 public Object getMetadataValue(Content content, String metadataPath, Locale defaultLocale, boolean resolveReferences) 640 { 641 return getMetadataValue(content, metadataPath, defaultLocale, resolveReferences, false); 642 } 643 644 /** 645 * Get the typed metadata value(s) of a content at given path. 646 * The path can represent a path of a metadata into the content or a metadata on one or more linked contents, such as 'composite/linkedContent/secondContent/composite/metadata'. 647 * The returned value is typed. 648 * @param content The content 649 * @param metadataPath The path to the metadata, separated by '/' 650 * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. 651 * Only to be valued if initial content's language is null, otherwise set this parameter to null. 652 * @param resolveReferences <code>true</code> true to resolve references (such as resource or content) 653 * @param returnNullValues <code>true</code> true to return null values when the metadata does not exists in a repeater or linked content. 654 * @return The typed final value. If the final metadata is multiple, or contains into a repeater or multiple 'CONTENT' metadata, the returned value will be a Collection 655 * @deprecated Use {@link Content#getValue(String, boolean)} instead 656 */ 657 @Deprecated 658 public Object getMetadataValue(Content content, String metadataPath, Locale defaultLocale, boolean resolveReferences, boolean returnNullValues) 659 { 660 String[] pathSegments = metadataPath.split(ContentConstants.METADATA_PATH_SEPARATOR); 661 662 MetadataDefinition definition = _contentTypesHelper.getMetadataDefinition(pathSegments[0], content); 663 if (definition != null) 664 { 665 Locale contentLocale = content.getLanguage() != null ? new Locale(content.getLanguage()) : defaultLocale; 666 return getMetadataValue(content.getMetadataHolder(), definition, metadataPath, contentLocale, resolveReferences, returnNullValues); 667 } 668 669 getLogger().warn("Unknown metadata definition at path '" + metadataPath + "' for content '" + content.getId()); 670 return null; 671 } 672 673 /** 674 * Get the typed values of a content at given path. 675 * The value is always returned into a collection of object event if the metadata is a single metadata. 676 * The path can represent a path of a metadata into the content or a metadata on one or more linked contents, such as 'composite/linkedContent/secondContent/composite/metadata'. 677 * The returned value is typed. 678 * @param contentId The ID of the content 679 * @param metadataPath The Path to the metadata, separated by '/' 680 * @return The typed final value. If the final metadata is single, the returned value will be a Collection of one element 681 * @deprecated Use {@link Content#getValue(String)} instead 682 */ 683 @Callable 684 @Deprecated 685 public List<Object> getMetadataValues(String contentId, String metadataPath) 686 { 687 return getMetadataValues(_resolver.resolveById(contentId), metadataPath, null, false, true); 688 } 689 690 /** 691 * Get the typed values of a content at given path. 692 * The value is always returned into a collection of object event if the metadata is a single metadata. 693 * The path can represent a path of a metadata into the content or a metadata on one or more linked contents, such as 'composite/linkedContent/secondContent/composite/metadata'. 694 * The returned value is typed. 695 * @param content The content 696 * @param metadataPath The path to the metadata, separated by '/' 697 * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. 698 * Only to be valued if initial content's language is null, otherwise set this parameter to null. 699 * @param resolveReferences <code>true</code> true to resolve references (such as resource or content) 700 * @param returnNullValues <code>true</code> true to return null values when the metadata does not exists in a repeater or linked content. 701 * @return The typed final value. If the final metadata is single, the returned value will be a Collection of one element 702 * @deprecated Use {@link Content#getValue(String)} instead 703 */ 704 @Deprecated 705 public List<Object> getMetadataValues(Content content, String metadataPath, Locale defaultLocale, boolean resolveReferences, boolean returnNullValues) 706 { 707 String[] pathSegments = metadataPath.split(ContentConstants.METADATA_PATH_SEPARATOR); 708 709 MetadataDefinition definition = _contentTypesHelper.getMetadataDefinition(pathSegments[0], content); 710 if (definition != null) 711 { 712 Locale locale = content.getLanguage() != null ? new Locale(content.getLanguage()) : defaultLocale; 713 Object values = getMetadataValue(content.getMetadataHolder(), definition, metadataPath, locale, resolveReferences, returnNullValues); 714 if (values instanceof Collection<?>) 715 { 716 return new ArrayList<>((Collection<?>) values); 717 } 718 else if (values != null || returnNullValues) 719 { 720 return Arrays.asList(values); 721 } 722 else 723 { 724 return Collections.EMPTY_LIST; 725 } 726 } 727 728 getLogger().warn("Unknown metadata definition at path '" + metadataPath + "' for content '" + content.getId()); 729 730 return null; 731 } 732 733 /** 734 * Get the title of a content.<br> 735 * If the content is a multilingual content, the title will be retrieved for the current locale if exists, or for default locale 'en' if exists, or for the first found locale. 736 * @param content The content 737 * @return The title of the content 738 */ 739 public String getTitle(Content content) 740 { 741 Locale defaultLocale = null; 742 743 try 744 { 745 Map objectModel = (Map) _context.get(ContextHelper.CONTEXT_OBJECT_MODEL); 746 if (objectModel != null) 747 { 748 // The object model can be null if #getTitle(content) is called outside a request 749 defaultLocale = org.apache.cocoon.i18n.I18nUtils.findLocale(objectModel, "locale", null, Locale.getDefault(), true); 750 } 751 } 752 catch (ContextException e) 753 { 754 // There is no context 755 } 756 757 // TODO Use user preference language ? 758 return content.getTitle(defaultLocale); 759 } 760 761 /** 762 * Get the title variants of a multilingual content 763 * @param content The multilingual content 764 * @return the content's title for each locale 765 * @throws IllegalArgumentException if the content is not a multilingual content 766 */ 767 public Map<String, String> getTitleVariants(Content content) 768 { 769 if (!isMultilingual(content)) 770 { 771 throw new IllegalArgumentException("Can not get title variants for a non-multilingual content " + content.getId()); 772 } 773 774 Map<String, String> variants = new HashMap<>(); 775 776 MultilingualString value = content.getValue(Content.ATTRIBUTE_TITLE); 777 for (Locale locale : value.getLocales()) 778 { 779 variants.put(locale.getLanguage(), value.getValue(locale)); 780 } 781 782 return variants; 783 } 784 785 /** 786 * Get the typed value(s) at given path. 787 * The path can represent a path of a metadata in the parent composite metadata or a path of a metadata into a linked content. 788 * The returned value is typed. 789 * @param metadataHolder The parent composite metadata 790 * @param definition The definition of the first metadata in path 791 * @param metadataPath The path to the metadata, separated by '/' 792 * @param locale The locale to used to resolve localized metadata 793 * @param resolveReferences <code>true</code> true to resolve references (such as resource or content) 794 * @param returnNullValues <code>true</code> true to return null values when metadata does not exists. 795 * @return The typed final value. If the final metadata is multiple, or contains into a repeater or multiple 'CONTENT' metadata, the returned value will be a Collection 796 * @deprecated Use {@link Content#getValue(String, boolean)} instead 797 */ 798 @Deprecated 799 public Object getMetadataValue(CompositeMetadata metadataHolder, MetadataDefinition definition, String metadataPath, Locale locale, boolean resolveReferences, boolean returnNullValues) 800 { 801 String[] pathSegments = metadataPath.split(ContentConstants.METADATA_PATH_SEPARATOR); 802 803 String metadataName = pathSegments[0]; 804 805 if (!metadataHolder.hasMetadata(metadataName)) 806 { 807 return null; 808 } 809 810 MetadataType type = definition.getType(); 811 switch (type) 812 { 813 case COMPOSITE: 814 return _getCompositeMetadataValue(metadataHolder, definition, locale, resolveReferences, returnNullValues, pathSegments, metadataName); 815 816 case CONTENT: 817 return _getContentMetadataValue(metadataHolder, definition, locale, resolveReferences, returnNullValues, pathSegments, metadataName); 818 default: 819 if (pathSegments.length == 1) 820 { 821 return getSimpleMetadataValue(metadataHolder, definition, metadataName, locale, resolveReferences); 822 } 823 824 throw new IllegalArgumentException("Metadata at path '" + definition.getId() + "' is a simple metadata : can not invoked get sub metadata values at path " + StringUtils.join(pathSegments, ContentConstants.METADATA_PATH_SEPARATOR, 1, pathSegments.length)); 825 } 826 } 827 828 @Deprecated 829 private Object _getContentMetadataValue(CompositeMetadata metadataHolder, MetadataDefinition definition, Locale defaultLocale, boolean resolveReferences, boolean returnNullValues, 830 String[] pathSegments, String metadataName) 831 { 832 if (pathSegments.length > 1) 833 { 834 if (definition.isMultiple()) 835 { 836 List<Object> values = new ArrayList<>(); 837 String[] refContentIds = metadataHolder.getStringArray(metadataName, new String[0]); 838 for (String refContentId : refContentIds) 839 { 840 Content refContent = _resolver.resolveById(refContentId); 841 Locale locale = refContent.getLanguage() != null ? new Locale(refContent.getLanguage()) : defaultLocale; 842 Object remoteValue = getMetadataValue(refContent, StringUtils.join(pathSegments, ContentConstants.METADATA_PATH_SEPARATOR, 1, pathSegments.length), locale, resolveReferences, returnNullValues); 843 if (remoteValue != null && remoteValue instanceof Collection<?>) 844 { 845 values.addAll((Collection<?>) remoteValue); 846 } 847 else if (remoteValue != null || returnNullValues) 848 { 849 values.add(remoteValue); 850 } 851 } 852 return values; 853 } 854 else 855 { 856 String refContentId = metadataHolder.getString(metadataName); 857 Content refContent = _resolver.resolveById(refContentId); 858 Locale locale = refContent.getLanguage() != null ? new Locale(refContent.getLanguage()) : defaultLocale; 859 return getMetadataValue(refContent, StringUtils.join(pathSegments, ContentConstants.METADATA_PATH_SEPARATOR, 1, pathSegments.length), locale, resolveReferences, returnNullValues); 860 } 861 } 862 else 863 { 864 return getSimpleMetadataValue(metadataHolder, definition, metadataName, defaultLocale, resolveReferences); 865 } 866 } 867 868 @Deprecated 869 private Object _getCompositeMetadataValue(CompositeMetadata metadataHolder, MetadataDefinition definition, Locale locale, boolean resolveReferences, boolean returnNullValues, String[] pathSegments, String metadataName) 870 { 871 if (pathSegments.length > 1) 872 { 873 if (definition instanceof RepeaterDefinition) 874 { 875 // Repeater: get and sort the entry names. 876 CompositeMetadata repeater = metadataHolder.getCompositeMetadata(metadataName); 877 String[] entries = repeater.getMetadataNames(); 878 Arrays.sort(entries, MetadataManager.REPEATER_ENTRY_COMPARATOR); 879 880 List<Object> values = new ArrayList<>(); 881 882 for (String entryName : entries) 883 { 884 CompositeMetadata entry = repeater.getCompositeMetadata(entryName); 885 886 Object entryValue = getMetadataValue(entry, definition.getMetadataDefinition(pathSegments[1]), StringUtils.join(pathSegments, ContentConstants.METADATA_PATH_SEPARATOR, 1, pathSegments.length), locale, resolveReferences, returnNullValues); 887 if (entryValue != null && entryValue instanceof Collection<?>) 888 { 889 values.addAll((Collection<?>) entryValue); 890 } 891 else if (entryValue != null || returnNullValues) 892 { 893 values.add(entryValue); 894 } 895 } 896 897 return values; 898 } 899 else 900 { 901 // Composite. 902 CompositeMetadata subMetadataHolder = metadataHolder.getCompositeMetadata(metadataName); 903 MetadataDefinition subMetadataDef = definition.getMetadataDefinition(pathSegments[1]); 904 return getMetadataValue(subMetadataHolder, subMetadataDef, StringUtils.join(pathSegments, ContentConstants.METADATA_PATH_SEPARATOR, 1, pathSegments.length), locale, resolveReferences, returnNullValues); 905 } 906 } 907 908 throw new IllegalArgumentException("Metadata at path '" + definition.getId() + "' is a composite metadata : can not invoked #getMetadataValue"); 909 } 910 911 /** 912 * Get the typed value(s) of a simple metadata. 913 * @param metadataHolder The parent composite metadata 914 * @param definition The definition of the first metadata in path 915 * @param metadataName The name of the metadata 916 * @param locale The locale to used to resolve localized metadata 917 * @param resolveReferences <code>true</code> true to resolve references (such as resource or content) 918 * @return The typed final value. 919 * @deprecated Use {@link Content#getValue(String)} instead 920 */ 921 @Deprecated 922 public Object getSimpleMetadataValue(CompositeMetadata metadataHolder, MetadataDefinition definition, String metadataName, Locale locale, boolean resolveReferences) 923 { 924 if (metadataName.contains(ContentConstants.METADATA_PATH_SEPARATOR)) 925 { 926 throw new IllegalArgumentException("The metadata name cannot represent a path : " + metadataName); 927 } 928 929 Object value = null; 930 931 switch (definition.getType()) 932 { 933 case LONG: 934 value = _getLongValue(metadataHolder, metadataName, definition); 935 break; 936 case DOUBLE: 937 value = _getDoubleValue(metadataHolder, metadataName, definition); 938 break; 939 case BOOLEAN: 940 value = _getBooleanValue(metadataHolder, metadataName, definition); 941 break; 942 case DATE: 943 case DATETIME: 944 value = _getDateValue(metadataHolder, metadataName, definition); 945 break; 946 case USER: 947 value = _getUserValue(metadataHolder, metadataName, definition); 948 break; 949 case BINARY: 950 value = _getBinaryValue(metadataHolder, metadataName); 951 break; 952 case FILE: 953 value = _getFileValue(metadataHolder, metadataName, definition, resolveReferences); 954 break; 955 case GEOCODE: 956 value = _getGeocodeValue(metadataHolder, metadataName); 957 break; 958 case RICH_TEXT: 959 value = _getRichTextValue(metadataHolder, metadataName); 960 break; 961 case CONTENT: 962 value = _getContentValue(metadataHolder, metadataName, definition, resolveReferences); 963 break; 964 case SUB_CONTENT: 965 // TODO or ignore ? 966 break; 967 case REFERENCE: 968 value = _getReferenceValue(metadataHolder, metadataName, definition); 969 break; 970 case MULTILINGUAL_STRING: 971 value = _getMultilingualStringValue(metadataHolder, metadataName, locale, resolveReferences); 972 break; 973 case STRING: 974 default: 975 value = _getStringValue(metadataHolder, metadataName, definition); 976 } 977 978 return value; 979 } 980 981 @Deprecated 982 private Object _getStringValue(CompositeMetadata metadataHolder, String metadataName, MetadataDefinition definition) 983 { 984 try 985 { 986 if (definition.isMultiple()) 987 { 988 return Arrays.asList(metadataHolder.getStringArray(metadataName)); 989 } 990 else 991 { 992 return metadataHolder.getString(metadataName); 993 } 994 } 995 catch (UnknownMetadataException e) 996 { 997 // Ignore, just return null. 998 return null; 999 } 1000 } 1001 1002 @Deprecated 1003 private Object _getMultilingualStringValue(CompositeMetadata metadataHolder, String metadataName, Locale locale, boolean resolve) 1004 { 1005 if (resolve) 1006 { 1007 return metadataHolder.getMultilingualString(metadataName); 1008 } 1009 else 1010 { 1011 return MultilingualStringHelper.getValue(metadataHolder, metadataName, locale); 1012 } 1013 } 1014 1015 @Deprecated 1016 private Object _getContentValue(CompositeMetadata metadataHolder, String metadataName, MetadataDefinition definition, boolean resolve) 1017 { 1018 try 1019 { 1020 if (definition.isMultiple()) 1021 { 1022 String[] refContentIds = metadataHolder.getStringArray(metadataName); 1023 if (resolve) 1024 { 1025 List<Content> contents = new ArrayList<>(); 1026 for (String refContentId : refContentIds) 1027 { 1028 try 1029 { 1030 contents.add(_resolver.resolveById(refContentId)); 1031 } 1032 catch (UnknownAmetysObjectException e) 1033 { 1034 // Ignore 1035 if (getLogger().isWarnEnabled()) 1036 { 1037 getLogger().warn("Metadata '" + definition.getId() + "' refers a non-existing content of id '" + refContentId + "'", e); 1038 } 1039 } 1040 } 1041 return contents; 1042 } 1043 else 1044 { 1045 return Arrays.asList(refContentIds); 1046 } 1047 } 1048 else 1049 { 1050 if (resolve) 1051 { 1052 try 1053 { 1054 return _resolver.resolveById(metadataHolder.getString(metadataName)); 1055 } 1056 catch (UnknownAmetysObjectException e) 1057 { 1058 return null; 1059 } 1060 } 1061 else 1062 { 1063 return metadataHolder.getString(metadataName); 1064 } 1065 } 1066 } 1067 catch (UnknownMetadataException e) 1068 { 1069 // Ignore, just return null. 1070 return null; 1071 } 1072 } 1073 1074 @Deprecated 1075 private Object _getUserValue(CompositeMetadata metadataHolder, String metadataName, MetadataDefinition definition) 1076 { 1077 try 1078 { 1079 if (definition.isMultiple()) 1080 { 1081 return Arrays.asList(metadataHolder.getUserArray(metadataName)); 1082 } 1083 else 1084 { 1085 return metadataHolder.getUser(metadataName); 1086 } 1087 } 1088 catch (UnknownMetadataException e) 1089 { 1090 // Ignore, just return null. 1091 return null; 1092 } 1093 } 1094 1095 @Deprecated 1096 private Object _getDateValue(CompositeMetadata metadataHolder, String metadataName, MetadataDefinition definition) 1097 { 1098 try 1099 { 1100 if (definition.isMultiple()) 1101 { 1102 return Arrays.asList(metadataHolder.getDateArray(metadataName)); 1103 } 1104 else 1105 { 1106 return metadataHolder.getDate(metadataName); 1107 } 1108 } 1109 catch (UnknownMetadataException e) 1110 { 1111 // Ignore, just return null. 1112 return null; 1113 } 1114 } 1115 1116 @Deprecated 1117 private Object _getLongValue(CompositeMetadata metadataHolder, String metadataName, MetadataDefinition definition) 1118 { 1119 try 1120 { 1121 if (definition.isMultiple()) 1122 { 1123 return Arrays.asList(ArrayUtils.toObject(metadataHolder.getLongArray(metadataName))); 1124 } 1125 else 1126 { 1127 return metadataHolder.getLong(metadataName); 1128 } 1129 } 1130 catch (UnknownMetadataException e) 1131 { 1132 // Ignore, just return null. 1133 return null; 1134 } 1135 } 1136 1137 @Deprecated 1138 private Object _getDoubleValue(CompositeMetadata metadataHolder, String metadataName, MetadataDefinition definition) 1139 { 1140 try 1141 { 1142 if (definition.isMultiple()) 1143 { 1144 return Arrays.asList(ArrayUtils.toObject(metadataHolder.getDoubleArray(metadataName))); 1145 } 1146 else 1147 { 1148 return metadataHolder.getDouble(metadataName); 1149 } 1150 } 1151 catch (UnknownMetadataException e) 1152 { 1153 // Ignore, just return null. 1154 return null; 1155 } 1156 } 1157 1158 @Deprecated 1159 private Object _getBooleanValue(CompositeMetadata metadataHolder, String metadataName, MetadataDefinition definition) 1160 { 1161 try 1162 { 1163 if (definition.isMultiple()) 1164 { 1165 return Arrays.asList(ArrayUtils.toObject(metadataHolder.getBooleanArray(metadataName))); 1166 } 1167 else 1168 { 1169 return metadataHolder.getBoolean(metadataName); 1170 } 1171 } 1172 catch (UnknownMetadataException e) 1173 { 1174 // Ignore, just return null. 1175 return null; 1176 } 1177 } 1178 1179 @Deprecated 1180 private Object _getRichTextValue(CompositeMetadata metadataHolder, String metadataName) 1181 { 1182 try 1183 { 1184 return metadataHolder.getRichText(metadataName); 1185 } 1186 catch (UnknownMetadataException e) 1187 { 1188 // Ignore, just return null. 1189 return null; 1190 } 1191 } 1192 1193 @Deprecated 1194 private Object _getBinaryValue(CompositeMetadata metadataHolder, String metadataName) 1195 { 1196 try 1197 { 1198 return metadataHolder.getBinaryMetadata(metadataName); 1199 } 1200 catch (UnknownMetadataException e) 1201 { 1202 // Ignore, just return null. 1203 return null; 1204 } 1205 } 1206 1207 @Deprecated 1208 private Object _getResourceValue(CompositeMetadata metadataHolder, String metadataName, MetadataDefinition definition, boolean resolve) 1209 { 1210 try 1211 { 1212 if (definition.isMultiple()) 1213 { 1214 String[] resourceIds = metadataHolder.getStringArray(metadataName); 1215 if (resolve) 1216 { 1217 List<Resource> resources = new ArrayList<>(); 1218 for (String resourceId : resourceIds) 1219 { 1220 try 1221 { 1222 resources.add(_resolver.resolveById(resourceId)); 1223 } 1224 catch (UnknownAmetysObjectException e) 1225 { 1226 // Ignore 1227 } 1228 } 1229 return resources; 1230 } 1231 else 1232 { 1233 return Arrays.asList(resourceIds); 1234 } 1235 } 1236 else 1237 { 1238 if (resolve) 1239 { 1240 try 1241 { 1242 return _resolver.resolveById(metadataHolder.getString(metadataName)); 1243 } 1244 catch (UnknownAmetysObjectException e) 1245 { 1246 return null; 1247 } 1248 } 1249 else 1250 { 1251 return metadataHolder.getString(metadataName); 1252 } 1253 } 1254 } 1255 catch (UnknownMetadataException e) 1256 { 1257 // Ignore, just return null. 1258 return null; 1259 } 1260 } 1261 1262 @Deprecated 1263 private Object _getGeocodeValue(CompositeMetadata metadataHolder, String metadataName) 1264 { 1265 try 1266 { 1267 // FIXME should return a GeoCode object 1268 CompositeMetadata geoMetadata = metadataHolder.getCompositeMetadata(metadataName); 1269 1270 if (geoMetadata.hasMetadata("longitude") && geoMetadata.hasMetadata("latitude")) 1271 { 1272 Double longitude = geoMetadata.getDouble("longitude"); 1273 Double latitude = geoMetadata.getDouble("latitude"); 1274 1275 Map<String, Double> geocode = new LinkedHashMap<>(); 1276 geocode.put("longitude", longitude); 1277 geocode.put("latitude", latitude); 1278 1279 return geocode; 1280 } 1281 } 1282 catch (UnknownMetadataException e) 1283 { 1284 // Ignore, just return null. 1285 } 1286 1287 return null; 1288 } 1289 1290 @Deprecated 1291 private Object _getReferenceValue(CompositeMetadata metadataHolder, String metadataName, MetadataDefinition definition) 1292 { 1293 try 1294 { 1295 // FIXME should return a Reference object 1296 1297 CompositeMetadata referencesComposite = metadataHolder.getCompositeMetadata(metadataName); 1298 1299 if (definition.isMultiple()) 1300 { 1301 // FIXME It doesn't exist with the old API, multiple reference values are not supported and "types" and "values" metadata name neither 1302 1303 List<Map<String, Object>> references = new ArrayList<>(); 1304 1305 String[] types = referencesComposite.getStringArray("types"); 1306 String[] values = referencesComposite.getStringArray("values"); 1307 1308 for (int i = 0; i < types.length; i++) 1309 { 1310 Map<String, Object> reference = new HashMap<>(2); 1311 reference.put("type", types[i]); 1312 reference.put("value", values[i]); 1313 1314 references.add(reference); 1315 } 1316 1317 return references; 1318 } 1319 else 1320 { 1321 String type = referencesComposite.getString("type"); 1322 String value = referencesComposite.getString("value"); 1323 1324 Map<String, Object> reference = new HashMap<>(2); 1325 reference.put("type", type); 1326 reference.put("value", value); 1327 1328 return reference; 1329 } 1330 } 1331 catch (UnknownMetadataException e) 1332 { 1333 // Ignore, just return null. 1334 return null; 1335 } 1336 } 1337 1338 @Deprecated 1339 private Object _getFileValue(CompositeMetadata metadataHolder, String metadataName, MetadataDefinition definition, boolean resolveReference) 1340 { 1341 if (org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType.BINARY.equals(metadataHolder.getType(metadataName))) 1342 { 1343 return _getBinaryValue(metadataHolder, metadataName); 1344 } 1345 else 1346 { 1347 return _getResourceValue(metadataHolder, metadataName, definition, resolveReference); 1348 } 1349 } 1350 1351 /** 1352 * Determines if the content has referencing contents other than whose type is in content types to ignore. 1353 * @param content The content to check 1354 * @param ignoreContentTypes The content types to ignore for referencing contents 1355 * @param includeSubTypes True if sub content types are take into account in ignore content types 1356 * @return <code>true</code> if there is at least one Content referencing the content 1357 */ 1358 public boolean hasReferencingContents(Content content, List<String> ignoreContentTypes, boolean includeSubTypes) 1359 { 1360 List<String> newIgnoreContentTypes = new ArrayList<>(); 1361 newIgnoreContentTypes.addAll(ignoreContentTypes); 1362 if (includeSubTypes) 1363 { 1364 for (String contentType : ignoreContentTypes) 1365 { 1366 newIgnoreContentTypes.addAll(_contentTypeEP.getSubTypes(contentType)); 1367 } 1368 } 1369 1370 for (Content refContent : content.getReferencingContents()) 1371 { 1372 List<String> contentTypes = Arrays.asList(refContent.getTypes()); 1373 if (!CollectionUtils.containsAny(contentTypes, newIgnoreContentTypes)) 1374 { 1375 return true; 1376 } 1377 } 1378 1379 return false; 1380 } 1381 1382 /** 1383 * Returns all Contents referencing the given content with their value path 1384 * @param content The content to get references 1385 * @return the list of pair path / contents 1386 */ 1387 public List<Pair<String, Content>> getReferencingContents(Content content) 1388 { 1389 List<Pair<String, Content>> incomingReferences = new ArrayList<>(); 1390 try 1391 { 1392 NodeIterator results = OutgoingReferencesHelper.getContentOutgoingReferences((JCRAmetysObject) content); 1393 while (results.hasNext()) 1394 { 1395 Node node = results.nextNode(); 1396 1397 Node outgoingRefsNode = node.getParent(); // go up towards node 'ametys-internal:outgoing-references; 1398 String path = outgoingRefsNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + DefaultContent.METADATA_OUTGOING_REFERENCES_PATH_PROPERTY).getString(); 1399 1400 Node contentNode = outgoingRefsNode.getParent() // go up towards node 'ametys-internal:root-outgoing-references 1401 .getParent(); // go up towards node of the content 1402 Content refContent = _resolver.resolve(contentNode, false); 1403 1404 incomingReferences.add(new ImmutablePair<>(path, refContent)); 1405 } 1406 } 1407 catch (RepositoryException e) 1408 { 1409 throw new AmetysRepositoryException("Unable to resolve references for content " + content.getId(), e); 1410 } 1411 1412 return incomingReferences; 1413 } 1414}