001/* 002 * Copyright 2018 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.plugins.odfsync.cdmfr.components.impl; 017 018import java.text.Normalizer; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import javax.jcr.RepositoryException; 028 029import org.apache.avalon.framework.configuration.Configuration; 030import org.apache.avalon.framework.configuration.ConfigurationException; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.commons.collections4.CollectionUtils; 034import org.apache.commons.collections4.ListUtils; 035import org.apache.commons.lang3.StringUtils; 036import org.slf4j.Logger; 037import org.w3c.dom.Document; 038import org.w3c.dom.Node; 039import org.w3c.dom.NodeList; 040 041import org.ametys.cms.content.external.ExternalizableMetadataHelper; 042import org.ametys.cms.contenttype.ContentType; 043import org.ametys.cms.contenttype.MetadataDefinition; 044import org.ametys.cms.contenttype.MetadataType; 045import org.ametys.cms.contenttype.RepeaterDefinition; 046import org.ametys.cms.repository.ContentQueryHelper; 047import org.ametys.cms.repository.ContentTypeExpression; 048import org.ametys.cms.repository.LanguageExpression; 049import org.ametys.cms.repository.ModifiableDefaultContent; 050import org.ametys.odf.ProgramItem; 051import org.ametys.odf.enumeration.OdfReferenceTableEntry; 052import org.ametys.odf.enumeration.OdfReferenceTableHelper; 053import org.ametys.odf.helper.DeleteODFContentHelper; 054import org.ametys.odf.helper.DeleteODFContentHelper.DeleteMode; 055import org.ametys.odf.program.AbstractProgram; 056import org.ametys.odf.program.Program; 057import org.ametys.odf.program.ProgramFactory; 058import org.ametys.odf.program.ProgramPart; 059import org.ametys.odf.program.SubProgram; 060import org.ametys.odf.program.SubProgramFactory; 061import org.ametys.odf.program.TraversableProgramPart; 062import org.ametys.plugins.odfsync.cdmfr.MergeMetadataForSharedProgramHelper; 063import org.ametys.plugins.odfsync.cdmfr.RemoteCDMFrSynchronizableContentsCollection; 064import org.ametys.plugins.repository.AmetysObjectIterable; 065import org.ametys.plugins.repository.RepositoryConstants; 066import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata; 067import org.ametys.plugins.repository.query.expression.AndExpression; 068import org.ametys.plugins.repository.query.expression.Expression; 069import org.ametys.plugins.repository.query.expression.Expression.Operator; 070import org.ametys.plugins.repository.query.expression.StringExpression; 071 072 073/** 074 * Component to import a CDM-fr input stream from a remote server with co-accredited mode. 075 */ 076public class CoAccreditedRemoteImportCDMFrComponent extends RemoteImportCDMFrComponent 077{ 078 /** The name of the JCR node holding the shared metadata */ 079 public static final String SHARED_PROGRAMS_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":shared-programs"; 080 081 /** The merge metadata helper */ 082 protected MergeMetadataForSharedProgramHelper _mergeMetadataHelper; 083 084 /** The delete ODF content helper */ 085 protected DeleteODFContentHelper _deleteODFContent; 086 087 /** The list of metadata to copy for mention program */ 088 protected Set<String> _mentionMetadataPaths; 089 090 /** The list of metadata to merge */ 091 protected Set<String> _metadataPathsToMerge; 092 093 private ContentType _mentionContentType; 094 private String _mentionId; 095 private String _programToLinkCode; 096 private String _sharedSubProgramType; 097 098 /** 099 * Enum to define the way to detect shared subProgram 100 */ 101 public enum SharedWithType 102 { 103 /** 104 * The main subProgram set the other shared program in the metadata "shared-with" 105 */ 106 WITH_SHARED_METADATA, 107 108 /** 109 * All subProgram with the same title are shared. The first imported subProgram is the main subProgram. 110 */ 111 WITH_SAME_TITLE, 112 113 114 /** 115 * Shared subProgram are not handled 116 */ 117 NONE 118 } 119 120 @Override 121 public void service(ServiceManager manager) throws ServiceException 122 { 123 super.service(manager); 124 _mergeMetadataHelper = (MergeMetadataForSharedProgramHelper) manager.lookup(MergeMetadataForSharedProgramHelper.ROLE); 125 _deleteODFContent = (DeleteODFContentHelper) manager.lookup(DeleteODFContentHelper.ROLE); 126 } 127 128 @Override 129 public void initialize() throws Exception 130 { 131 super.initialize(); 132 _mentionContentType = _contentTypeEP.getExtension(getProgramWfDescription().getContentType()); 133 } 134 135 @Override 136 public void configure(Configuration configuration) throws ConfigurationException 137 { 138 super.configure(configuration); 139 140 _mentionMetadataPaths = new HashSet<>(); 141 _metadataPathsToMerge = new HashSet<>(); 142 if ("co-accredited".equals(configuration.getName())) 143 { 144 _configureCoAccreditedParams(configuration); 145 } 146 else 147 { 148 _configureCoAccreditedParams(configuration.getChild("co-accredited")); 149 } 150 } 151 152 /** 153 * Configure the co-accredited params 154 * @param configuration the configuration 155 * @throws ConfigurationException if an error occurred 156 */ 157 protected void _configureCoAccreditedParams(Configuration configuration) throws ConfigurationException 158 { 159 Configuration mentionConf = configuration.getChild("mention"); 160 if (mentionConf != null) 161 { 162 Configuration metadatas = mentionConf.getChild("metadata-to-copy"); 163 if (metadatas != null) 164 { 165 for (Configuration metadataConf : metadatas.getChildren()) 166 { 167 String metadataPath = metadataConf.getAttribute("path"); 168 _mentionMetadataPaths.add(metadataPath); 169 } 170 } 171 } 172 173 Configuration sharedWithConf = configuration.getChild("shared-with"); 174 if (sharedWithConf != null) 175 { 176 Configuration metadatas = sharedWithConf.getChild("metadata-to-merge"); 177 if (metadatas != null) 178 { 179 for (Configuration metadataConf : metadatas.getChildren()) 180 { 181 String metadataPath = metadataConf.getAttribute("path"); 182 _metadataPathsToMerge.add(metadataPath); 183 } 184 } 185 } 186 } 187 188 @Override 189 protected void additionalParameters(Map<String, Object> parameters) 190 { 191 _mentionId = null; 192 _sharedSubProgramType = (String) parameters.getOrDefault(RemoteCDMFrSynchronizableContentsCollection.PARAM_SHARED_WITH_TYPE, SharedWithType.NONE.name()); 193 194 super.additionalParameters(parameters); 195 } 196 197 @Override 198 protected ModifiableDefaultContent _importOrSynchronizeContent(Document doc, Node contentNode, ContentWorkflowDescription wfDescription, String title, String lang, String catalog, String syncCode, Logger logger) 199 { 200 ModifiableDefaultContent content = null; 201 ContentWorkflowDescription contentWfDescription = wfDescription; 202 if (contentNode.getLocalName().equals(_TAG_PROGRAM)) 203 { 204 String educationKind = _xPathProcessor.evaluateAsString(contentNode, AbstractProgram.EDUCATION_KIND); 205 String mention = _xPathProcessor.evaluateAsString(contentNode, AbstractProgram.MENTION); 206 207 // This is a co-accredited program 208 if (StringUtils.isNotBlank(mention) && "parcours".equals(educationKind)) 209 { 210 // Change the workflow description of the program to subprogram 211 contentWfDescription = getSubProgramWfDescription(); 212 213 // Get or create the mention from the program 214 Program mentionProgram = _getOrCreateMention(doc, contentNode, mention, lang, catalog, logger); 215 _mentionId = mentionProgram.getId(); 216 217 // Store the content to link to the mention 218 _programToLinkCode = syncCode; 219 220 // Handle shared subPrograms 221 if (_getSharedWithType() != SharedWithType.NONE) 222 { 223 content = _importOrSynchronizeSharedSubPrograms(doc, mentionProgram, contentNode, contentWfDescription, title, syncCode, lang, catalog, logger); 224 } 225 } 226 } 227 228 if (content == null) 229 { 230 content = super._importOrSynchronizeContent(doc, contentNode, contentWfDescription, title, lang, catalog, syncCode, logger); 231 } 232 233 if (_mentionId != null && content instanceof SubProgram && content.getMetadataHolder().getString(getIdField()).equals(_programToLinkCode)) 234 { 235 // Mettre les composantes de la mention à jour 236 Program mention = _resolver.resolveById(_mentionId); 237 _updateMentionAttributes(mention, (SubProgram) content); 238 } 239 240 return content; 241 } 242 243 /** 244 * Update mention attributes depending on the sub program 245 * @param mention the mention 246 * @param subProgram the subProgram 247 */ 248 protected void _updateMentionAttributes(Program mention, SubProgram subProgram) 249 { 250 // Mettre les composantes de la mention à jour 251 List<String> orgUnits = mention.getOrgUnits(); 252 List<String> orgUnitsToAdd = subProgram.getOrgUnits(); 253 _updateContentsAttribute(mention, AbstractProgram.ORG_UNITS_REFERENCES, orgUnits, orgUnitsToAdd); 254 } 255 256 /** 257 * For the attribute name of the content, add new contents (contentIdsToAdd) to existed contents (contentIds) 258 * @param content the content to modified 259 * @param attributeName the attribute name 260 * @param contentIds the content ids 261 * @param contentIdsToAdd the content ids to add 262 */ 263 protected void _updateContentsAttribute(ModifiableDefaultContent content, String attributeName, List<String> contentIds, List<String> contentIdsToAdd) 264 { 265 if (!CollectionUtils.containsAll(contentIds, contentIdsToAdd)) 266 { 267 List<String> newContentIds = ListUtils.union(contentIds, contentIdsToAdd); 268 ExternalizableMetadataHelper.setExternalMetadata(content.getMetadataHolder(), attributeName, newContentIds.toArray(new String[newContentIds.size()]), false); 269 } 270 } 271 272 /** 273 * Import or synchronized shared subPrograms 274 * @param doc the document 275 * @param mentionProgram the mention program 276 * @param contentNode the content node 277 * @param contentWfDescription the content workflow description 278 * @param title the title of the subprogram 279 * @param syncCode the synchronisation code 280 * @param lang the language 281 * @param catalog the catalog 282 * @param logger the logger 283 * @return the imported of synchronized content 284 */ 285 protected ModifiableDefaultContent _importOrSynchronizeSharedSubPrograms(Document doc, Program mentionProgram, Node contentNode, ContentWorkflowDescription contentWfDescription, String title, String syncCode, String lang, String catalog, Logger logger) 286 { 287 // Is imported program already exist as a subprogram ? 288 ModifiableDefaultContent subProgram = _getContent(lang, catalog, syncCode, contentWfDescription); 289 if (subProgram == null) 290 { 291 // The imported program does not exist as subprogram, create it except if it is a shared subprogram 292 if (_isSecondarySharedSubPrograms(mentionProgram, contentNode, lang, logger)) 293 { 294 // Search if there is a main subprogram shared with it. 295 SubProgram mainSubProgram = getMainSharedSubProgram(mentionProgram, contentNode, lang, logger); 296 if (mainSubProgram != null) 297 { 298 // The imported program is a shared subprogram, it will not be imported nor synchronized, only the main subprogram will be updated 299 try 300 { 301 // Merge shared program metadata from CDMfr 302 if (_synchronizeSharedMetadata(doc, contentNode, mainSubProgram, null, _metadataPathsToMerge, catalog, lang, logger)) 303 { 304 if (_mergeMetadataHelper.mergeSharedMetadata(mainSubProgram, contentNode, _metadataPathsToMerge, logger)) 305 { 306 _saveContentChanges(mainSubProgram, null, true, logger); 307 } 308 } 309 } 310 catch (RepositoryException e) 311 { 312 String warnMsg = "Impossible de synchronizer les métadonnées partagées pour le parcours principal \"" + mainSubProgram.getTitle() + "\""; 313 logger.warn(warnMsg); 314 } 315 316 return mainSubProgram; 317 } 318 else 319 { 320 return super._importOrSynchronizeContent(doc, contentNode, contentWfDescription, title, lang, catalog, syncCode, logger); 321 } 322 } 323 else 324 { 325 subProgram = super._importOrSynchronizeContent(doc, contentNode, contentWfDescription, title, lang, catalog, syncCode, logger); 326 327 try 328 { 329 // Synchronize the shared metadata of the main program to be merged later 330 _synchronizeSharedMetadata(doc, contentNode, (SubProgram) subProgram, null, _metadataPathsToMerge, catalog, lang, logger); 331 } 332 catch (RepositoryException e) 333 { 334 String warnMsg = "Impossible de synchronizer les métadonnées partagées du parcours principal \"" + subProgram.getTitle() + "\""; 335 logger.warn(warnMsg); 336 } 337 338 // Browse existing shared subprograms to first merge them then delete them. 339 // When we shared subprograms with the title, it can't have other created shared subPrograms because the first one is the main shared subprogram 340 SharedWithType sharedWithType = _getSharedWithType(); 341 if (SharedWithType.WITH_SHARED_METADATA == sharedWithType) 342 { 343 List<String> sharedWith = _getSharedWithAsString(contentNode, logger); 344 _synchronizeAndDeleteSharedSubPrograms(mentionProgram, (SubProgram) subProgram, sharedWith, catalog, lang, logger); 345 346 try 347 { 348 // Then merge main subprogram 349 if (_mergeMetadataHelper.mergeSharedMetadata((SubProgram) subProgram, contentNode, _metadataPathsToMerge, logger)) 350 { 351 _saveContentChanges(subProgram, null, true, logger); 352 } 353 } 354 catch (RepositoryException e) 355 { 356 String warnMsg = "Impossible de synchronizer les metadatas du programme principal \"" + subProgram.getTitle() + "\""; 357 logger.warn(warnMsg); 358 } 359 } 360 361 return subProgram; 362 } 363 } 364 else // The imported program exists as subprogram 365 { 366 ModifiableDefaultContent mainSubProgram = super._importOrSynchronizeContent(doc, contentNode, contentWfDescription, title, lang, catalog, syncCode, logger); 367 368 if (!_isSecondarySharedSubPrograms(mentionProgram, contentNode, lang, logger)) 369 { 370 // When we shared subprograms with the title, it can't have other created shared subPrograms because the first one is the main shared subprogram 371 SharedWithType sharedWithType = _getSharedWithType(); 372 if (SharedWithType.WITH_SHARED_METADATA == sharedWithType) 373 { 374 List<String> sharedWith = _getSharedWithAsString(contentNode, logger); 375 376 // Browse existing shared subprograms to first merge them then delete them. 377 _synchronizeAndDeleteSharedSubPrograms(mentionProgram, (SubProgram) subProgram, sharedWith, catalog, lang, logger); 378 } 379 380 try 381 { 382 // Synchronize the shared metadata of the main program to be merged later 383 _synchronizeSharedMetadata(doc, contentNode, (SubProgram) subProgram, null, _metadataPathsToMerge, catalog, lang, logger); 384 // Then merge main subprogram 385 if (_mergeMetadataHelper.mergeSharedMetadata((SubProgram) subProgram, contentNode, _metadataPathsToMerge, logger)) 386 { 387 _saveContentChanges(mainSubProgram, null, true, logger); 388 } 389 } 390 catch (RepositoryException e) 391 { 392 String warnMsg = "Impossible de synchronizer les metadatas du programme principal \"" + subProgram.getTitle() + "\""; 393 logger.warn(warnMsg); 394 } 395 } 396 397 return mainSubProgram; 398 } 399 } 400 401 /** 402 * Get of create the mention program 403 * @param doc the document 404 * @param contentNode the content node 405 * @param mentionCode the mention code 406 * @param lang the language 407 * @param catalog the catalog 408 * @param logger the logger 409 * @return the mention program 410 */ 411 protected Program _getOrCreateMention(Document doc, Node contentNode, String mentionCode, String lang, String catalog, Logger logger) 412 { 413 String degreeCodeCDM = _xPathProcessor.evaluateAsString(contentNode, AbstractProgram.DEGREE); 414 OdfReferenceTableEntry degreeContent = _odfRefTableHelper.getItemFromCDM(OdfReferenceTableHelper.DEGREE, degreeCodeCDM); 415 String degreeCode = degreeContent != null ? degreeContent.getCode() : degreeCodeCDM; 416 String mentionType = degreeContent != null ? _odfRefTableHelper.getMentionForDegree(degreeContent.getId()) : null; 417 if (mentionType != null) 418 { 419 String mentionId = _getIdFromCDMThenCode(mentionType, mentionCode); 420 if (mentionId != null) 421 { 422 String degreeId = _getIdFromCDMThenCode(OdfReferenceTableHelper.DEGREE, degreeCode); 423 Program mention = _getMention(mentionId, degreeId, lang, catalog); 424 if (mention == null) 425 { 426 mention = _createMention(doc, contentNode, mentionId, catalog, lang, logger); 427 _importedContents.put(mention.getId(), getProgramWfDescription().getValidationActionId()); 428 } 429 430 return mention; 431 } 432 else 433 { 434 logger.error("Il n'y a pas de code associé à la mention {}. La formation n'a pas été importée.", mentionCode); 435 _nbError++; 436 } 437 } 438 else 439 { 440 logger.error("Il n'y a pas de type de mention (licence, licence pro, master) associée au diplôme {}. La formation n'a pas été importée.", degreeCode); 441 _nbError++; 442 } 443 444 return null; 445 } 446 447 /** 448 * Create the mention 449 * @param doc the document 450 * @param contentNode the content node 451 * @param mentionId the mention id 452 * @param catalog the catalog 453 * @param lang the language 454 * @param logger the logger 455 * @return the created mention 456 */ 457 protected Program _createMention(Document doc, Node contentNode, String mentionId, String catalog, String lang, Logger logger) 458 { 459 String contentTitle = _odfRefTableHelper.getItemLabel(mentionId, lang); 460 ContentWorkflowDescription wfDescription = getProgramWfDescription(); 461 Map<String, Object> resultMap = _synchroComponent.createContentAction(wfDescription.getContentType(), wfDescription.getWorkflowName(), wfDescription.getInitialActionId(), lang, contentTitle, _contentPrefix, logger); 462 if ((boolean) resultMap.getOrDefault("error", false)) 463 { 464 _nbError++; 465 } 466 467 Program mention = (Program) resultMap.get("content"); 468 469 if (mention != null) 470 { 471 boolean hasChanges = false; 472 if (catalog != null) 473 { 474 hasChanges = ExternalizableMetadataHelper.setMetadata(mention.getMetadataHolder(), ProgramItem.CATALOG, catalog); 475 } 476 477 hasChanges = ExternalizableMetadataHelper.setExternalMetadata(mention.getMetadataHolder(), AbstractProgram.MENTION, mentionId, true) || hasChanges; 478 hasChanges = _synchronizeMentionMetadata(doc, contentNode, mention, AbstractProgram.DEGREE, lang, catalog, logger) || hasChanges; 479 hasChanges = _synchronizeMentionMetadata(doc, contentNode, mention, AbstractProgram.DOMAIN, lang, catalog, logger) || hasChanges; 480 481 for (String metadataPath : _mentionMetadataPaths) 482 { 483 hasChanges = _synchronizeMentionMetadata(doc, contentNode, mention, metadataPath, lang, catalog, logger) || hasChanges; 484 } 485 486 _saveContentChanges(mention, wfDescription.getContentType(), hasChanges, logger); 487 } 488 489 return mention; 490 } 491 492 /** 493 * Get the mention program 494 * @param mentionId the mention content id 495 * @param degreeId the degree content id 496 * @param lang the language 497 * @param catalog the catalog 498 * @return the mention program or <code>null</code> if it doesn't exist 499 */ 500 protected Program _getMention(String mentionId, String degreeId, String lang, String catalog) 501 { 502 List<Expression> expList = new ArrayList<>(); 503 expList.add(new ContentTypeExpression(Operator.EQ, ProgramFactory.PROGRAM_CONTENT_TYPE)); 504 expList.add(new LanguageExpression(Operator.EQ, lang)); 505 expList.add(new StringExpression(ProgramItem.CATALOG, Operator.EQ, catalog)); 506 expList.add(new StringExpression(AbstractProgram.DEGREE, Operator.EQ, degreeId)); 507 expList.add(new StringExpression(AbstractProgram.MENTION, Operator.EQ, mentionId)); 508 509 AndExpression andExp = new AndExpression(expList.toArray(new Expression[expList.size()])); 510 String xPathQuery = ContentQueryHelper.getContentXPathQuery(andExp); 511 512 AmetysObjectIterable<Program> contents = _resolver.query(xPathQuery); 513 514 if (contents.getSize() > 0) 515 { 516 return contents.iterator().next(); 517 } 518 519 return null; 520 } 521 522 /** 523 * Synchronize metadata in the mention program 524 * @param doc the document 525 * @param contentNode the content node 526 * @param mention the mention program 527 * @param metadataPath the metadata path to synchronize 528 * @param lang the language 529 * @param catalog the catalog 530 * @param logger the logger 531 * @return true if some changes were made 532 */ 533 protected boolean _synchronizeMentionMetadata(Document doc, Node contentNode, ModifiableDefaultContent mention, String metadataPath, String lang, String catalog, Logger logger) 534 { 535 Node metadataNode = _xPathProcessor.selectSingleNode(contentNode, metadataPath); 536 if (metadataNode != null) 537 { 538 return _synchronizeMetadata(doc, metadataNode, mention, metadataPath, metadataPath, _mentionContentType, lang, catalog, logger); 539 } 540 return false; 541 } 542 543 @Override 544 protected void additionalOperationsBeforeSave(ModifiableDefaultContent content, Logger logger) throws RepositoryException 545 { 546 if (_mentionId != null && content instanceof SubProgram && content.getMetadataHolder().getString(getIdField()).equals(_programToLinkCode)) 547 { 548 boolean hasChanges = false; 549 550 ModifiableDefaultContent mentionContent = _resolver.resolveById(_mentionId); 551 552 // Relier le programme à la mention 553 hasChanges = _synchroComponent.updateRelation(mentionContent.getMetadataHolder(), TraversableProgramPart.CHILD_PROGRAM_PARTS, content, false) || hasChanges; 554 if (_synchroComponent.updateRelation(content.getMetadataHolder(), ProgramPart.PARENT_PROGRAM_PARTS, _mentionId, false)) 555 { 556 _saveContentChanges(content, getSubProgramWfDescription().getContentType(), true, logger); 557 } 558 559 if (hasChanges) 560 { 561 _saveContentChanges(mentionContent, _mentionContentType.getId(), hasChanges, logger); 562 } 563 } 564 } 565 566 /** 567 * Get the defined way to detect shared program 568 * @return shared with type 569 */ 570 protected SharedWithType _getSharedWithType() 571 { 572 return SharedWithType.valueOf(_sharedSubProgramType); 573 } 574 575 /** 576 * Get the main subprogram shared with the program representing by the content node 577 * @param mentionProgram the root mention program 578 * @param contentNode the content node 579 * @param lang the content lang 580 * @param logger the logger 581 * @return the main subprogram if the program is a shared subprogram or <code>null</code> otherwise 582 */ 583 protected SubProgram getMainSharedSubProgram(Program mentionProgram, Node contentNode, String lang, Logger logger) 584 { 585 SharedWithType sharedWithType = _getSharedWithType(); 586 switch (sharedWithType) 587 { 588 case WITH_SHARED_METADATA: 589 return _getMainSharedWithSubProgram(mentionProgram, contentNode, logger); 590 case WITH_SAME_TITLE: 591 return _getSubProgramWithSameTitle(mentionProgram, contentNode, lang); 592 default: 593 return null; 594 } 595 } 596 597 /** 598 * True if the subProgram node is shared with a main subProgram. 599 * False if the subProgram node is not shared or if the main subProgram is not already imported 600 * @param mentionProgram the mention program 601 * @param contentNode the content node 602 * @param lang the lang 603 * @param logger the logger 604 * @return true if it's a secondary subProgram 605 */ 606 protected boolean _isSecondarySharedSubPrograms(Program mentionProgram, Node contentNode, String lang, Logger logger) 607 { 608 SharedWithType sharedWithType = _getSharedWithType(); 609 switch (sharedWithType) 610 { 611 case WITH_SHARED_METADATA: 612 List<String> sharedWith = _getSharedWithAsString(contentNode, logger); 613 return sharedWith.isEmpty(); 614 case WITH_SAME_TITLE: 615 return _hasSubProgramWithSameTitle(mentionProgram, contentNode, lang, logger); 616 default: 617 return false; 618 } 619 } 620 621 /** 622 * True if there is a subProgram with the same title of the content node 623 * @param mentionProgram the mention program 624 * @param contentNode the content node 625 * @param lang the lang 626 * @param logger the logger 627 * @return true if there is a subProgram with the same title of the content node 628 */ 629 protected boolean _hasSubProgramWithSameTitle(Program mentionProgram, Node contentNode, String lang, Logger logger) 630 { 631 SubProgram subProgram = _getSubProgramWithSameTitle(mentionProgram, contentNode, lang); 632 return subProgram != null; 633 } 634 635 636 /** 637 * Get the subProgram with the same title 638 * @param mentionProgram the mention program 639 * @param contentNode the content node 640 * @param lang the lang 641 * @return the subProgram with the same title. null if there are no subProgram with same title. 642 */ 643 protected SubProgram _getSubProgramWithSameTitle(Program mentionProgram, Node contentNode, String lang) 644 { 645 String contentTitle = _xPathProcessor.evaluateAsString(contentNode, "title"); 646 647 for (ProgramPart child : mentionProgram.getProgramPartChildren()) 648 { 649 if (child instanceof SubProgram) 650 { 651 SubProgram subProgram = (SubProgram) child; 652 if (_getNormalizeTitle(subProgram.getTitle()).equals(_getNormalizeTitle(contentTitle))) 653 { 654 return subProgram; 655 } 656 } 657 } 658 return null; 659 } 660 661 private String _getNormalizeTitle(String title) 662 { 663 // To lower case 664 // then remove accents 665 return Normalizer.normalize(title.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", ""); 666 } 667 668 /** 669 * Get the list of "shared with" program from the remote document 670 * @param contentNode The content node 671 * @param logger the logger 672 * @return The CDM code of shared program 673 */ 674 protected List<String> _getSharedWithAsString (Node contentNode, Logger logger) 675 { 676 List<String> codes = new ArrayList<>(); 677 678 Node sharedWithNode = _xPathProcessor.selectSingleNode(contentNode, AbstractProgram.SHARED_WITH); 679 680 if (sharedWithNode != null) 681 { 682 NodeList itemNodes = sharedWithNode.getChildNodes(); 683 for (int j = 0; j < itemNodes.getLength(); j++) 684 { 685 String code = itemNodes.item(j).getTextContent().trim(); 686 if (StringUtils.isNotBlank(code)) 687 { 688 codes.add(code); 689 } 690 } 691 } 692 693 return codes; 694 } 695 696 /** 697 * Get the main subprogram shared with the program representing by the content node 698 * @param mentionProgram the root mention program 699 * @param contentNode the content node 700 * @param logger the logger 701 * @return the main subprogram if the program is a shared subprogram or <code>null</code> otherwise 702 */ 703 protected SubProgram _getMainSharedWithSubProgram(Program mentionProgram, Node contentNode, Logger logger) 704 { 705 String programCode = _xPathProcessor.evaluateAsString(contentNode, ProgramItem.CODE); 706 707 for (ProgramPart child : mentionProgram.getProgramPartChildren()) 708 { 709 if (child instanceof SubProgram) 710 { 711 SubProgram subProgram = (SubProgram) child; 712 List<String> sharedProgram = Arrays.asList(subProgram.getSharedWith()); 713 if (sharedProgram.contains(programCode)) 714 { 715 return subProgram; 716 } 717 } 718 } 719 return null; 720 } 721 722 /** 723 * Get the shared subprogram with the given code 724 * @param mentionProgram The root mention program 725 * @param code The CDMfr code 726 * @param logger the logger 727 * @return The shared subprogram if exist or <code>null</code> if not found 728 */ 729 protected SubProgram _getSharedSubProgram (Program mentionProgram, String code, Logger logger) 730 { 731 for (ProgramPart child : mentionProgram.getProgramPartChildren()) 732 { 733 if (child instanceof SubProgram) 734 { 735 SubProgram subProgram = (SubProgram) child; 736 if (subProgram.getCode().equals(code)) 737 { 738 return subProgram; 739 } 740 } 741 } 742 return null; 743 } 744 745 /** 746 * Delete a shared secondary subprogram. 747 * This subprogram was created by a precede import before the main subprogram was imported 748 * @param sharedProgram the shared program 749 * @param logger the logger 750 */ 751 protected void deleteSharedSubProgram(ModifiableDefaultContent sharedProgram, Logger logger) 752 { 753 String title = sharedProgram.getTitle(); 754 String infoMsg = "Suppression du parcours partagé secondaire \"" + title + "\""; 755 logger.info(infoMsg); 756 757 String contentId = sharedProgram.getId(); 758 List<String> contentIds = Collections.singletonList(contentId); 759 760 // Delete content bypassing the rights check 761 Map<String, Object> results = _deleteODFContent.deleteContentsWithLog(contentIds, DeleteMode.FULL.name(), true); 762 763 @SuppressWarnings("unchecked") 764 Map<String, Object> result = (Map<String, Object>) results.get(contentId); 765 if (result.containsKey("check-before-deletion-failed") && (boolean) result.get("check-before-deletion-failed")) 766 { 767 logger.warn("Can't deleted shared content " + title + " (" + contentId + "). See previous logs for more information."); 768 } 769 } 770 771 /** 772 * Synchronize the shared metadata of a shared subprogram before deleting the subprogram 773 * @param rootProgram The root mention 774 * @param mainSubProgram The main subprogram 775 * @param sharedWith The CDMfr of shared subprogram 776 * @param catalog the catalog 777 * @param lang the language 778 * @param logger The logger 779 * @return <code>true</code> if changes were made 780 */ 781 protected boolean _synchronizeAndDeleteSharedSubPrograms (Program rootProgram, SubProgram mainSubProgram, List<String> sharedWith, String catalog, String lang, Logger logger) 782 { 783 boolean hasChanged = false; 784 785 // Browse existing shared subprograms to first merge them then delete them. 786 for (String code : sharedWith) 787 { 788 SubProgram sharedSubProgram = _getSharedSubProgram(rootProgram, code, logger); 789 if (sharedSubProgram != null) 790 { 791 String infoMsg = "Fusion avec le parcours partagé \"" + sharedSubProgram.getTitle() + "\""; 792 logger.info(infoMsg); 793 794 try 795 { 796 _synchronizeSharedMetadata(null, null, mainSubProgram, sharedSubProgram, _metadataPathsToMerge, catalog, lang, logger); 797 } 798 catch (RepositoryException e) 799 { 800 String warnMsg = "Impossible de synchronizer les metadatas du programme partagé \"" + sharedSubProgram.getTitle() + "\" avec le programme principal \"" + mainSubProgram.getTitle() + "\""; 801 logger.warn(warnMsg); 802 } 803 804 // Then delete the secondary shared subprogram 805 deleteSharedSubProgram(sharedSubProgram, logger); 806 hasChanged = true; 807 } 808 } 809 810 return hasChanged; 811 } 812 813 /** 814 * Synchronize the shared program node for a main subprogram, from a secondary subprogram 815 * @param doc the document. Can be null if the sharedProgram is not 816 * @param sharedProgramNode the shared program node. Can be null if the sharedProgram is not 817 * @param mainProgram the main program 818 * @param sharedProgram the shared program. Can be null if the doc and the sharedProgramNode is not 819 * @param metadataToMerge the set of metadata to merge 820 * @param catalog the catalog 821 * @param lang the language 822 * @param logger the logger 823 * @return <code>true</code> if changes were made 824 * @throws RepositoryException if an error occurred 825 */ 826 public boolean _synchronizeSharedMetadata(Document doc, Node sharedProgramNode, SubProgram mainProgram, SubProgram sharedProgram, Set<String> metadataToMerge, String catalog, String lang, Logger logger) throws RepositoryException 827 { 828 boolean hasChanged = false; 829 830 ModifiableCompositeMetadata sharedMetadataHolder = _getSharedMetadataHolder(doc, sharedProgramNode, mainProgram, sharedProgram, logger); 831 for (String metadataPath : metadataToMerge) 832 { 833 ContentType cType = _contentTypeEP.getExtension(SubProgramFactory.SUBPROGRAM_CONTENT_TYPE); 834 835 if (_mergeMetadataHelper.isValidMetadataPath(cType, metadataPath, logger)) 836 { 837 String[] pathSegments = StringUtils.split(metadataPath, '/'); 838 MetadataDefinition metadataDefinition = cType.getMetadataDefinition(pathSegments[0]); 839 840 if (metadataDefinition.isMultiple()) 841 { 842 hasChanged = _synchronizeSharedMultipleMetadata(doc, sharedProgramNode, mainProgram, sharedProgram, metadataDefinition, sharedMetadataHolder, metadataPath, catalog, lang, logger) || hasChanged; 843 } 844 else 845 { 846 hasChanged = _synchronizeSharedSingleMetadata(doc, sharedProgramNode, mainProgram, sharedProgram, metadataDefinition, sharedMetadataHolder, metadataPath, catalog, lang, logger) || hasChanged; 847 } 848 } 849 } 850 851 return hasChanged; 852 } 853 854 /** 855 * Get the metadata composite holding shared metadata of subprograms 856 * @param doc the document. Can be null if the sharedProgram is not 857 * @param sharedProgramNode the shared program node. Can be null if the sharedProgram is not 858 * @param mainProgram the main program 859 * @param sharedProgram the shared program. Can be null if the doc and the sharedProgramNode is not 860 * @param logger the logger 861 * @return the shared metadata holder 862 * @throws RepositoryException if an error occurred 863 */ 864 protected ModifiableCompositeMetadata _getSharedMetadataHolder(Document doc, Node sharedProgramNode, SubProgram mainProgram, SubProgram sharedProgram, Logger logger) throws RepositoryException 865 { 866 // The data comes from a content 867 if (doc == null) 868 { 869 return _mergeMetadataHelper.getSharedMetadataHolder(mainProgram, sharedProgram.getCode(), logger); 870 } 871 else // The data comes from the DOM 872 { 873 String sharedProgramCode = _xPathProcessor.evaluateAsString(sharedProgramNode, ProgramItem.CODE); 874 return _mergeMetadataHelper.getSharedMetadataHolder(mainProgram, sharedProgramCode, logger); 875 } 876 } 877 878 /** 879 * Synchronize a shared multiple metadata for shared program 880 * @param doc the document. Can be null if the sharedProgram is not 881 * @param sharedProgramNode the shared program node. Can be null if the sharedProgram is not 882 * @param mainProgram the main program 883 * @param sharedProgram the shared program. Can be null if the doc and the sharedProgramNode is not 884 * @param metadataDefinition the metadata definition 885 * @param sharedMetadataHolder the metadata composite holding shared metadata of subprograms 886 * @param logicalMetadataPath The logical metadata path (to retrieve the definition) 887 * @param catalog the catalog 888 * @param lang the language 889 * @param logger the logger 890 * @return <code>true</code> if changes were made 891 */ 892 protected boolean _synchronizeSharedMultipleMetadata(Document doc, Node sharedProgramNode, SubProgram mainProgram, SubProgram sharedProgram, MetadataDefinition metadataDefinition, ModifiableCompositeMetadata sharedMetadataHolder, String logicalMetadataPath, String catalog, String lang, Logger logger) 893 { 894 boolean hasChanged = false; 895 ContentType contentType = _contentTypeEP.getExtension(SubProgramFactory.SUBPROGRAM_CONTENT_TYPE); 896 Map<String, Object> params = Map.of("contentTypes", List.of(contentType.getId())); 897 boolean synchronize = getLocalAndExternalFields(params).contains(logicalMetadataPath); 898 899 MetadataType type = metadataDefinition.getType(); 900 901 // Data comes from a content 902 List<Object> metadataValues = new ArrayList<>(); 903 if (doc == null) 904 { 905 metadataValues = _getMultipleValuesFromContent(sharedProgram, logicalMetadataPath, type, logger); 906 } 907 else 908 { 909 metadataValues = _getMultipleValuesFromDOM(doc, sharedProgramNode, mainProgram, metadataDefinition, logicalMetadataPath, type, catalog, lang, logger); 910 } 911 912 913 Map<String, Boolean> resultMap = _synchroComponent.synchronizeMetadata(mainProgram, contentType, logicalMetadataPath, sharedMetadataHolder, logicalMetadataPath, metadataValues, synchronize, false, logger); 914 if (resultMap.getOrDefault("error", Boolean.FALSE)) 915 { 916 _nbError++; 917 } 918 hasChanged = resultMap.getOrDefault("hasChanges", Boolean.FALSE).booleanValue(); 919 920 return hasChanged; 921 } 922 923 /** 924 * Get multiple values from content 925 * @param sharedProgram the shared program 926 * @param logicalMetadataPath The logical metadata path (to retrieve the definition) 927 * @param type the type of the data to retrieve 928 * @param logger the logger 929 * @return the list of values 930 */ 931 protected List<Object> _getMultipleValuesFromContent(SubProgram sharedProgram, String logicalMetadataPath, MetadataType type, Logger logger) 932 { 933 List<Object> metadataValues = new ArrayList<>(); 934 ModifiableCompositeMetadata metadataHolder = sharedProgram.getMetadataHolder(); 935 switch (type) 936 { 937 case STRING: 938 case REFERENCE: 939 case CONTENT: 940 for (String value : metadataHolder.getStringArray(logicalMetadataPath, new String[0])) 941 { 942 metadataValues.add(value); 943 } 944 break; 945 case DOUBLE: 946 for (double value : metadataHolder.getDoubleArray(logicalMetadataPath, new double[0])) 947 { 948 metadataValues.add(value); 949 } 950 break; 951 case LONG: 952 for (long value : metadataHolder.getLongArray(logicalMetadataPath, new long[0])) 953 { 954 metadataValues.add(value); 955 } 956 break; 957 default: 958 String warn = "Le type de métadonnée " + type.toString() + " n'est pas supporté pour la fusion des parcours partagées. La métadonnée '" + logicalMetadataPath + "' est ignorée."; 959 logger.warn(warn); 960 break; 961 } 962 963 return metadataValues; 964 } 965 966 /** 967 * Get multiple values from DOM 968 * @param doc the document 969 * @param sharedProgramDOM the shared program node 970 * @param mainProgram the main program 971 * @param metadataDefinition the metadata definition 972 * @param logicalMetadataPath The logical metadata path (to retrieve the definition) 973 * @param type the type of the data to retrieve 974 * @param catalog the catalog 975 * @param lang the lang 976 * @param logger the logger 977 * @return the list of values 978 */ 979 protected List<Object> _getMultipleValuesFromDOM (Document doc, Node sharedProgramDOM, SubProgram mainProgram, MetadataDefinition metadataDefinition, String logicalMetadataPath, MetadataType type, String catalog, String lang, Logger logger) 980 { 981 List<String> metadataValues = new ArrayList<>(); 982 983 Node metadataNode = _xPathProcessor.selectSingleNode(sharedProgramDOM, logicalMetadataPath); 984 if (metadataNode != null) 985 { 986 switch (type) 987 { 988 case STRING: 989 case REFERENCE: 990 case CONTENT: 991 NodeList itemNodes = metadataNode.getChildNodes(); 992 for (int j = 0; j < itemNodes.getLength(); j++) 993 { 994 String metadataValue = itemNodes.item(j).getTextContent().trim(); 995 if (StringUtils.isNotEmpty(metadataValue)) 996 { 997 metadataValues.add(metadataValue); 998 } 999 } 1000 break; 1001 default: 1002 String warn = "Le type de métadonnée " + type.toString() + " n'est pas supporté pour la fusion des parcours partagées. La métadonnée '" + logicalMetadataPath + "' est ignorée."; 1003 logger.warn(warn); 1004 } 1005 } 1006 1007 return _handleMetadataValues(doc, mainProgram, metadataDefinition, metadataValues, lang, catalog, logger); 1008 } 1009 1010 /** 1011 * Synchronize single metadata for shared program 1012 * @param doc the document. Can be null if the sharedProgram is not 1013 * @param sharedProgramNode the shared program node. Can be null if the sharedProgram is not 1014 * @param mainProgram the main program 1015 * @param sharedProgram the shared program. Can be null if the doc and the sharedProgramNode is not 1016 * @param metadataDefinition the metadata definition 1017 * @param sharedMetadataHolder the metadata composite holding shared metadata of subprograms 1018 * @param logicalMetadataPath The logical metadata path (to retrieve the definition) 1019 * @param catalog the catalog 1020 * @param lang the language 1021 * @param logger the logger 1022 * @return <code>true</code> if changes were made 1023 */ 1024 protected boolean _synchronizeSharedSingleMetadata(Document doc, Node sharedProgramNode, SubProgram mainProgram, SubProgram sharedProgram, MetadataDefinition metadataDefinition, ModifiableCompositeMetadata sharedMetadataHolder, String logicalMetadataPath, String catalog, String lang, Logger logger) 1025 { 1026 MetadataType type = metadataDefinition.getType(); 1027 switch (type) 1028 { 1029 case COMPOSITE: 1030 if (metadataDefinition instanceof RepeaterDefinition) 1031 { 1032 if (doc == null) 1033 { 1034 return synchronizeRepeaterMetadataFromContent(sharedProgram, sharedMetadataHolder, logicalMetadataPath, logger); 1035 } 1036 else 1037 { 1038 return synchronizeRepeaterMetadataFromDOM(doc, sharedProgramNode, mainProgram, metadataDefinition, sharedMetadataHolder, logicalMetadataPath, catalog, lang, logger); 1039 } 1040 } 1041 else 1042 { 1043 String warn = "Le type de métadonnée " + type.toString() + " n'est pas supporté pour la fusion des parcours partagées. La métadonnée '" + logicalMetadataPath + "' est ignorée."; 1044 logger.warn(warn); 1045 break; 1046 } 1047 default: 1048 String warn = "Le type de métadonnée " + type.toString() + " n'est pas supporté pour la fusion des parcours partagées. La métadonnée '" + logicalMetadataPath + "' est ignorée."; 1049 logger.warn(warn); 1050 break; 1051 } 1052 return false; 1053 } 1054 1055 /** 1056 * Synchronize repeater from content 1057 * @param sharedProgram the shared program 1058 * @param sharedMetadataHolder the metadata composite holding shared metadata of subprograms 1059 * @param logicalMetadataPath The logical metadata path (to retrieve the definition) 1060 * @param logger the logger 1061 * @return <code>true</code> if changes were made 1062 */ 1063 protected boolean synchronizeRepeaterMetadataFromContent(SubProgram sharedProgram, ModifiableCompositeMetadata sharedMetadataHolder, String logicalMetadataPath, Logger logger) 1064 { 1065 if (sharedMetadataHolder.hasMetadata(logicalMetadataPath)) 1066 { 1067 // Remove old values 1068 sharedMetadataHolder.removeMetadata(logicalMetadataPath); 1069 } 1070 1071 if (sharedProgram.getMetadataHolder().hasMetadata(logicalMetadataPath)) 1072 { 1073 ModifiableCompositeMetadata sharedRepeater = sharedMetadataHolder.getCompositeMetadata(logicalMetadataPath, true); 1074 ModifiableCompositeMetadata repeaterToCopy = sharedProgram.getMetadataHolder().getCompositeMetadata(logicalMetadataPath); 1075 repeaterToCopy.copyTo(sharedRepeater); 1076 } 1077 1078 return true; 1079 } 1080 1081 /** 1082 * Synchronize repeater from DOM 1083 * @param doc the document 1084 * @param sharedProgramNode the shared program node 1085 * @param mainProgram the main program 1086 * @param metadataDefinition the metadata definition 1087 * @param sharedMetadataHolder the metadata composite holding shared metadata of subprograms 1088 * @param logicalMetadataPath The logical metadata path (to retrieve the definition) 1089 * @param catalog the catalog 1090 * @param lang the language 1091 * @param logger the logger 1092 * @return <code>true</code> if changes were made 1093 */ 1094 protected boolean synchronizeRepeaterMetadataFromDOM(Document doc, Node sharedProgramNode, SubProgram mainProgram, MetadataDefinition metadataDefinition, ModifiableCompositeMetadata sharedMetadataHolder, String logicalMetadataPath, String catalog, String lang, Logger logger) 1095 { 1096 boolean hasChanges = false; 1097 Node metadataNode = _xPathProcessor.selectSingleNode(sharedProgramNode, logicalMetadataPath); 1098 if (metadataNode != null) 1099 { 1100 ModifiableCompositeMetadata repeater = sharedMetadataHolder.getCompositeMetadata(logicalMetadataPath, true); 1101 1102 // Remove locale entries (unable to compare locales and remotes ...) 1103 String[] metadataNames = repeater.getMetadataNames(); 1104 for (String entryName : metadataNames) 1105 { 1106 repeater.removeMetadata(entryName); 1107 hasChanges = true; 1108 } 1109 1110 ModifiableCompositeMetadata repeaterMetadataHolder = sharedMetadataHolder.getCompositeMetadata(logicalMetadataPath, true); 1111 1112 // Create new entries from remote data 1113 NodeList entryNodes = metadataNode.getChildNodes(); 1114 for (int i = 0; i < entryNodes.getLength(); i++) 1115 { 1116 Node entryNode = entryNodes.item(i); 1117 String entryName = entryNode.getAttributes().getNamedItem("name").getTextContent().trim(); 1118 1119 NodeList childNodes = entryNode.getChildNodes(); 1120 for (int j = 0; j < childNodes.getLength(); j++) 1121 { 1122 Node childNode = childNodes.item(j); 1123 String subMetadataName = childNode.getLocalName(); 1124 1125 ModifiableCompositeMetadata entryMetadataHolder = repeaterMetadataHolder.getCompositeMetadata(entryName, true); 1126 hasChanges = synchronizeSimpleMetadataFromDOM(doc, mainProgram, childNode, metadataDefinition.getMetadataDefinition(subMetadataName), logicalMetadataPath + "/" + subMetadataName, entryMetadataHolder, subMetadataName, catalog, lang, logger) || hasChanges; 1127 } 1128 } 1129 } 1130 1131 return hasChanges; 1132 } 1133 1134 /** 1135 * Synchronize a simple metadata from DOM 1136 * @param doc the document 1137 * @param subProgram the subProgram content 1138 * @param metadataNode The metadata DOM node 1139 * @param metadataDefinition the metadata definition 1140 * @param logicalMetadataPath The logical metadata path (to retrieve the definition) 1141 * @param sharedMetadataHolder the metadata composite holding shared metadata of subprograms 1142 * @param metadataName the metadata name 1143 * @param catalog the catalog 1144 * @param lang the language 1145 * @param logger The logger 1146 * @return <code>true</code> if changes were made 1147 */ 1148 public boolean synchronizeSimpleMetadataFromDOM (Document doc, SubProgram subProgram, Node metadataNode, MetadataDefinition metadataDefinition, String logicalMetadataPath, ModifiableCompositeMetadata sharedMetadataHolder, String metadataName, String catalog, String lang, Logger logger) 1149 { 1150 List<String> metadataValues = null; 1151 ContentType contentType = _contentTypeEP.getExtension(SubProgramFactory.SUBPROGRAM_CONTENT_TYPE); 1152 1153 MetadataType type = metadataDefinition.getType(); 1154 switch (type) 1155 { 1156 case BINARY: 1157 return _handleBinaryMetadata(metadataNode, subProgram, sharedMetadataHolder, metadataName, false, logger); 1158 case FILE: 1159 return _handleFileMetadata(metadataNode, subProgram, logicalMetadataPath, sharedMetadataHolder, metadataName, false, contentType, logger); 1160 case GEOCODE: 1161 return _handleGeocodeMetadata(metadataNode, subProgram, sharedMetadataHolder, metadataName, false); 1162 case CONTENT: 1163 case STRING: 1164 case LONG: 1165 case DOUBLE: 1166 case REFERENCE: 1167 case BOOLEAN: 1168 if (metadataDefinition.isMultiple()) 1169 { 1170 NodeList itemNodes = metadataNode.getChildNodes(); 1171 metadataValues = new ArrayList<>(); 1172 for (int j = 0; j < itemNodes.getLength(); j++) 1173 { 1174 String metadataValue = itemNodes.item(j).getTextContent().trim(); 1175 if (StringUtils.isNotEmpty(metadataValue)) 1176 { 1177 metadataValues.add(metadataValue); 1178 } 1179 } 1180 } 1181 else 1182 { 1183 String metadataValue = metadataNode.getTextContent().trim(); 1184 if (StringUtils.isNotEmpty(metadataValue)) 1185 { 1186 metadataValues = List.of(metadataValue); 1187 } 1188 } 1189 break; 1190 default: 1191 String warn = "Le type de métadonnée '" + type.toString() + "' n'est pas supporté pour la fusion des entrées de repeater de parcours partagés. La métadonnée est ignorée"; 1192 logger.warn(warn); 1193 return false; 1194 } 1195 1196 List<Object> handleMetadataValues = _handleMetadataValues(doc, subProgram, metadataDefinition, metadataValues, lang, catalog, logger); 1197 Map<String, Boolean> resultMap = _synchroComponent.synchronizeMetadata(subProgram, contentType, logicalMetadataPath, sharedMetadataHolder, metadataName, handleMetadataValues, false, false, logger); 1198 if (resultMap.getOrDefault("error", Boolean.FALSE)) 1199 { 1200 _nbError++; 1201 } 1202 return resultMap.getOrDefault("hasChanges", Boolean.FALSE).booleanValue(); 1203 } 1204}