001/* 002 * Copyright 2013 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.content; 017 018import java.io.InputStream; 019import java.io.OutputStream; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Locale; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.Properties; 031import java.util.Set; 032 033import javax.xml.transform.OutputKeys; 034import javax.xml.transform.TransformerFactory; 035import javax.xml.transform.sax.SAXTransformerFactory; 036import javax.xml.transform.sax.TransformerHandler; 037import javax.xml.transform.stream.StreamResult; 038 039import org.apache.avalon.framework.component.Component; 040import org.apache.avalon.framework.service.ServiceException; 041import org.apache.avalon.framework.service.ServiceManager; 042import org.apache.avalon.framework.service.Serviceable; 043import org.apache.cocoon.ProcessingException; 044import org.apache.commons.lang3.BooleanUtils; 045import org.apache.commons.lang3.StringUtils; 046import org.apache.excalibur.xml.sax.ContentHandlerProxy; 047import org.apache.excalibur.xml.sax.SAXParser; 048import org.apache.xml.serializer.OutputPropertiesFactory; 049import org.slf4j.Logger; 050import org.xml.sax.Attributes; 051import org.xml.sax.ContentHandler; 052import org.xml.sax.InputSource; 053import org.xml.sax.SAXException; 054 055import org.ametys.cms.content.CopyReport.CopyMode; 056import org.ametys.cms.content.CopyReport.CopyState; 057import org.ametys.cms.content.references.OutgoingReferences; 058import org.ametys.cms.content.references.OutgoingReferencesExtractor; 059import org.ametys.cms.contenttype.AbstractMetadataSetElement; 060import org.ametys.cms.contenttype.ContentType; 061import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 062import org.ametys.cms.contenttype.ContentTypesHelper; 063import org.ametys.cms.contenttype.MetadataDefinition; 064import org.ametys.cms.contenttype.MetadataDefinitionReference; 065import org.ametys.cms.contenttype.MetadataManager; 066import org.ametys.cms.contenttype.MetadataSet; 067import org.ametys.cms.contenttype.MetadataType; 068import org.ametys.cms.contenttype.RepeaterDefinition; 069import org.ametys.cms.contenttype.RichTextUpdater; 070import org.ametys.cms.repository.Content; 071import org.ametys.cms.repository.DefaultContent; 072import org.ametys.cms.repository.ModifiableContent; 073import org.ametys.cms.repository.WorkflowAwareContent; 074import org.ametys.cms.workflow.AbstractContentWorkflowComponent; 075import org.ametys.cms.workflow.CreateContentFunction; 076import org.ametys.cms.workflow.EditContentFunction; 077import org.ametys.cms.workflow.copy.CreateContentByCopyFunction; 078import org.ametys.cms.workflow.copy.EditContentByCopyFunction; 079import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 080import org.ametys.plugins.explorer.resources.ResourceCollection; 081import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollectionFactory; 082import org.ametys.plugins.repository.AmetysObject; 083import org.ametys.plugins.repository.AmetysObjectIterable; 084import org.ametys.plugins.repository.AmetysObjectResolver; 085import org.ametys.plugins.repository.AmetysRepositoryException; 086import org.ametys.plugins.repository.CopiableAmetysObject; 087import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 088import org.ametys.plugins.repository.TraversableAmetysObject; 089import org.ametys.plugins.repository.metadata.BinaryMetadata; 090import org.ametys.plugins.repository.metadata.CompositeMetadata; 091import org.ametys.plugins.repository.metadata.File; 092import org.ametys.plugins.repository.metadata.Folder; 093import org.ametys.plugins.repository.metadata.ModifiableBinaryMetadata; 094import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata; 095import org.ametys.plugins.repository.metadata.ModifiableFile; 096import org.ametys.plugins.repository.metadata.ModifiableFolder; 097import org.ametys.plugins.repository.metadata.ModifiableResource; 098import org.ametys.plugins.repository.metadata.ModifiableRichText; 099import org.ametys.plugins.repository.metadata.MultilingualString; 100import org.ametys.plugins.repository.metadata.Resource; 101import org.ametys.plugins.repository.metadata.RichText; 102import org.ametys.plugins.workflow.AbstractWorkflowComponent; 103import org.ametys.plugins.workflow.component.CheckRightsCondition; 104import org.ametys.plugins.workflow.support.WorkflowProvider; 105import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 106import org.ametys.runtime.i18n.I18nizableText; 107import org.ametys.runtime.plugin.component.AbstractLogEnabled; 108 109/** 110 * <p> 111 * This component is used to copy a content (either totally or partially). 112 * </p><p> 113 * In this whole file a Map named <em>copyMap</em> is regularly used. This map 114 * provide the name of the metadata to copy as well as some optional parameters. 115 * It has the following form (JSON) : 116 * </p> 117 * <pre> 118 * { 119 * "$param1": value, 120 * "metadataA": null, 121 * "metadataB": { 122 * "subMetadataB1": null, 123 * "subMetadataB2": { 124 * "$param1": value, 125 * "$param2": value, 126 * "subSubMetadataB21": {...} 127 * }, 128 * ... 129 * } 130 * } 131 * </pre> 132 * <p> 133 * Each metadata that should be copied must be present as a key in the map. 134 * Composite metadata can contains child metadata but as seen on the example the 135 * map must be well structured, it is not a flat map. Parameters in the map must 136 * always start with the reserved character '$', in order to be differentiated 137 * from metadata name. 138 * </p><p> 139 * There are two main entry points for this helper component: 140 * </p> 141 * <ul> 142 * <li>copyContent and editContent are methods that run a dedicated workflow 143 * function (createByCopy or editByCopy) that will later call the 144 * copyMetadataMap function (see below).</li> 145 * <li>copyMetadataMap is the method that is responsible for the recursive copy 146 * of the metadata (by following the copyMap structure). During this process, 147 * underlying content creation can be requested in which case the copyContent 148 * method will be called.</li> 149 * </ul> 150 */ 151public class CopyContentMetadataComponent extends AbstractLogEnabled implements Serviceable, Component 152{ 153 /** Avalon ROLE. */ 154 public static final String ROLE = CopyContentMetadataComponent.class.getName(); 155 156 /** Workflow provider. */ 157 protected WorkflowProvider _workflowProvider; 158 159 /** Ametys object resolver available to subclasses. */ 160 protected AmetysObjectResolver _resolver; 161 162 /** The Excalibur SAX parser */ 163 protected SAXParser _saxParser; 164 165 /** Content type extension point. */ 166 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 167 168 /** Helper for content types */ 169 protected ContentTypesHelper _contentTypesHelper; 170 171 /** The outgoing references extractor */ 172 protected OutgoingReferencesExtractor _outgoingReferencesExtractor; 173 174 /** The content helper */ 175 protected ContentHelper _contentHelper; 176 177 @Override 178 public void service(ServiceManager manager) throws ServiceException 179 { 180 _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE); 181 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 182 _saxParser = (SAXParser) manager.lookup(SAXParser.ROLE); 183 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 184 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 185 _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE); 186 _outgoingReferencesExtractor = (OutgoingReferencesExtractor) manager.lookup(OutgoingReferencesExtractor.ROLE); 187 } 188 189 /** 190 * Copy a content by creating a new content and copying the value of the 191 * metadata of a source content into the new one. 192 * The title of the new content will the one from the base content. 193 * 194 * @param baseContentId The identifier of the base content 195 * @param copyMap The map of properties as described in 196 * {@link CopyContentMetadataComponent}. Can be null in which 197 * case the map will be constructed from a metadataSet. 198 * @param metadataSetName The name of the metadata set to be used to 199 * construct to copyMap if not provided. This will also be the 200 * default name for possible inner copies (if not provided as a 201 * copyMap parameter). 202 * @param metadataSetType The type of the metadata set to be used to 203 * construct to copyMap if not provided. This will also be the 204 * default type for possible inner copies. 205 * 206 * @param initActionId The workflow action id to use to create the new content 207 * @param editActionId The workflow action id to use to edit the newly created content 208 * 209 * @return The copy report containing valuable information about the copy 210 * and the possible encountered errors. 211 */ 212 public CopyReport copyContent(String baseContentId, Map<String, Object> copyMap, String metadataSetName, String metadataSetType, int initActionId, int editActionId) 213 { 214 return copyContent(baseContentId, null, copyMap, metadataSetName, metadataSetType, initActionId, editActionId); 215 } 216 217 /** 218 * Copy a content by creating a new content and copying the value of the 219 * metadata of a source content into the new one. 220 * 221 * @param baseContentId The identifier of the base content 222 * @param title Desired title for the new content. 223 * @param copyMap The map of properties as described in 224 * {@link CopyContentMetadataComponent}. Can be null in which 225 * case the map will be constructed from a metadataSet. 226 * @param metadataSetName The name of the metadata set to be used to 227 * construct to copyMap if not provided. This will also be the 228 * default name for possible inner copies (if not provided as a 229 * copyMap parameter). 230 * @param metadataSetType The type of the metadata set to be used to 231 * construct to copyMap if not provided. This will also be the 232 * default type for possible inner copies. 233 * @param initActionId The init workflow action id for main content only 234 * @param editActionId The workflow action for editing main content only 235 * @return The copy report containing valuable information about the copy 236 * and the possible encountered errors. 237 */ 238 public CopyReport copyContent(String baseContentId, String title, Map<String, Object> copyMap, String metadataSetName, String metadataSetType, int initActionId, int editActionId) 239 { 240 return copyContent(baseContentId, title, copyMap, metadataSetName, metadataSetType, null, initActionId, editActionId); 241 } 242 243 /** 244 * Create a new content by copy of another. The type of created content can be different of the source content. 245 * The source and target contents must have a common content type ancestor. 246 * @param baseContentId The id of content to copy 247 * @param title The title of the new created content 248 * @param copyMap The map of metadata to copy. Can be null to copy the whole metadata set. 249 * @param metadataSetName The name of metadata set to copy 250 * @param metadataSetType The type of metadata set to copy 251 * @param targetContentType The type of content to create. If null the type(s) of created content will be those of base content. 252 * @param initActionId The init workflow action id for main content only 253 * @param editActionId The workflow action for editing main content only 254 * @return The copy report 255 */ 256 public CopyReport copyContent(String baseContentId, String title, Map<String, Object> copyMap, String metadataSetName, String metadataSetType, String targetContentType, int initActionId, int editActionId) 257 { 258 try 259 { 260 Content baseContent = _resolver.resolveById(baseContentId); 261 return copyContent(baseContent, title, copyMap, metadataSetName, metadataSetType, targetContentType, initActionId, editActionId); 262 } 263 catch (AmetysRepositoryException e) 264 { 265 getLogger().error("An error has been encountered during the content copy, or the copy is not allowed (base content identifier : " + baseContentId + ").", e); 266 267 boolean isSimple = true; 268 269 CopyReport report = new CopyReport(baseContentId, isSimple, metadataSetName, metadataSetType, CopyMode.CREATION); 270 report.notifyContentCopyError(); 271 272 return report; 273 } 274 } 275 276 /** 277 * Copy a content by creating a new content and copying the value of the 278 * metadata of a source content into the new one. 279 * 280 * @param baseContent The base content. 281 * @param copyMap The map of properties as described in 282 * {@link CopyContentMetadataComponent}. Can be null in which 283 * case the map will be constructed from a metadataSet. 284 * @param metadataSetName The name of the metadata set to be used to 285 * construct to copyMap if not provided. This will also be the 286 * default name for possible inner copies (if not provided as a 287 * copyMap parameter). 288 * @param metadataSetType The type of the metadata set to be used to 289 * construct to copyMap if not provided. This will also be the 290 * default type for possible inner copies. 291 * @return The copy report containing valuable information about the copy 292 * and the possible encountered errors. 293 */ 294 public CopyReport copyContent(Content baseContent, Map<String, Object> copyMap, String metadataSetName, String metadataSetType) 295 { 296 return copyContent(baseContent, copyMap, metadataSetName, metadataSetType, getDefaultInitActionId(), getDefaultActionIdForEditingContentReferences()); 297 } 298 299 /** 300 * Copy a content by creating a new content and copying the value of the 301 * metadata of a source content into the new one. 302 * 303 * @param baseContent The base content. 304 * @param copyMap The map of properties as described in 305 * {@link CopyContentMetadataComponent}. Can be null in which 306 * case the map will be constructed from a metadataSet. 307 * @param metadataSetName The name of the metadata set to be used to 308 * construct to copyMap if not provided. This will also be the 309 * default name for possible inner copies (if not provided as a 310 * copyMap parameter). 311 * @param metadataSetType The type of the metadata set to be used to 312 * construct to copyMap if not provided. This will also be the 313 * default type for possible inner copies. 314 * @param initActionId The init workflow action id for main content only 315 * @param editRefActionId The workflow action for editing references 316 * @return The copy report containing valuable information about the copy 317 * and the possible encountered errors. 318 */ 319 public CopyReport copyContent(Content baseContent, Map<String, Object> copyMap, String metadataSetName, String metadataSetType, int initActionId, int editRefActionId) 320 { 321 return copyContent(baseContent, null, copyMap, metadataSetName, metadataSetType, initActionId, editRefActionId); 322 } 323 324 /** 325 * Copy a content by creating a new content and copying the value of the 326 * metadata of a source content into the new one. 327 * 328 * @param baseContent The base content. 329 * @param title Desired title for the new content. 330 * @param copyMap The map of properties as described in 331 * {@link CopyContentMetadataComponent}. Can be null in which 332 * case the map will be constructed from a metadataSet. 333 * @param metadataSetName The name of the metadata set to be used to 334 * construct to copyMap if not provided. This will also be the 335 * default name for possible inner copies (if not provided as a 336 * copyMap parameter). 337 * @param metadataSetType The type of the metadata set to be used to 338 * construct to copyMap if not provided. This will also be the 339 * default type for possible inner copies. 340 * @param initActionId The init workflow action id for main content only 341 * @param editRefActionId The workflow action for editing references 342 * @return The copy report containing valuable information about the copy 343 * and the possible encountered errors. 344 */ 345 public CopyReport copyContent(Content baseContent, String title, Map<String, Object> copyMap, String metadataSetName, String metadataSetType, int initActionId, int editRefActionId) 346 { 347 return copyContent(baseContent, title, copyMap, metadataSetName, metadataSetName, null, initActionId, editRefActionId); 348 } 349 350 /** 351 * Copy a content by creating a new content and copying the value of the 352 * metadata of a source content into the new one. 353 * 354 * @param baseContent The base content. 355 * @param title Desired title for the new content. 356 * @param copyMap The map of properties as described in 357 * {@link CopyContentMetadataComponent}. Can be null in which 358 * case the map will be constructed from a metadataSet. 359 * @param metadataSetName The name of the metadata set to be used to 360 * construct to copyMap if not provided. This will also be the 361 * default name for possible inner copies (if not provided as a 362 * copyMap parameter). 363 * @param metadataSetType The type of the metadata set to be used to 364 * construct to copyMap if not provided. This will also be the 365 * default type for possible inner copies. 366 * @param targetContentType The type of content to create. If null the type(s) of created content will be those of base content. 367 * @param initActionId The init workflow action id for main content only 368 * @param editRefActionId The workflow action for editing references 369 * @return The copy report containing valuable information about the copy 370 * and the possible encountered errors. 371 */ 372 public CopyReport copyContent(Content baseContent, String title, Map<String, Object> copyMap, String metadataSetName, String metadataSetType, String targetContentType, int initActionId, int editRefActionId) 373 { 374 return copyContent(baseContent, title, copyMap, metadataSetName, metadataSetType, targetContentType, null, null, initActionId, editRefActionId); 375 } 376 377 /** 378 * Copy a content by creating a new content and copying the value of the 379 * metadata of a source content into the new one. 380 * 381 * @param baseContent The base content. 382 * @param title Desired title for the new content. 383 * @param copyMap The map of properties as described in 384 * {@link CopyContentMetadataComponent}. Can be null in which 385 * case the map will be constructed from a metadataSet. 386 * @param metadataSetName The name of the metadata set to be used to 387 * construct to copyMap if not provided. This will also be the 388 * default name for possible inner copies (if not provided as a 389 * copyMap parameter). 390 * @param metadataSetType The type of the metadata set to be used to 391 * construct to copyMap if not provided. This will also be the 392 * default type for possible inner copies. 393 * @param parentContentId The target content ID. 394 * @param parentMetadataPath the parent metadata path, if a sub-content is being created. 395 * @param initActionId The init workflow action id for main content only 396 * @param editActionId The workflow action for editing main content only 397 * @return The copy report containing valuable information about the copy 398 * and the possible encountered errors. 399 */ 400 public CopyReport copyContent(Content baseContent, String title, Map<String, Object> copyMap, String metadataSetName, String metadataSetType, String parentContentId, String parentMetadataPath, int initActionId, int editActionId) 401 { 402 return copyContent(baseContent, title, copyMap, metadataSetName, metadataSetType, null, parentContentId, parentMetadataPath, initActionId, editActionId); 403 } 404 405 /** 406 * Copy a content by creating a new content and copying the value of the 407 * metadata of a source content into the new one. 408 * 409 * @param baseContent The base content. 410 * @param title Desired title for the new content. 411 * @param copyMap The map of properties as described in 412 * {@link CopyContentMetadataComponent}. Can be null in which 413 * case the map will be constructed from a metadataSet. 414 * @param metadataSetName The name of the metadata set to be used to 415 * construct to copyMap if not provided. This will also be the 416 * default name for possible inner copies (if not provided as a 417 * copyMap parameter). 418 * @param metadataSetType The type of the metadata set to be used to 419 * construct to copyMap if not provided. This will also be the 420 * default type for possible inner copies. 421 * @param targetContentType The type of content to create. If null the type(s) of created content will be those of base content. 422 * @param parentContentId The target content ID. 423 * @param parentMetadataPath the parent metadata path, if a sub-content is being created. 424 * @param initActionId The init workflow action id for main content only 425 * @param editRefActionId The workflow action for editing references 426 * @return The copy report containing valuable information about the copy 427 * and the possible encountered errors. 428 */ 429 public CopyReport copyContent(Content baseContent, String title, Map<String, Object> copyMap, String metadataSetName, String metadataSetType, String targetContentType, String parentContentId, String parentMetadataPath, int initActionId, int editRefActionId) 430 { 431 String baseContentId = baseContent.getId(); 432 String auxMetadataSetName = StringUtils.defaultIfEmpty(metadataSetName, "main"); 433 String auxMetadataSetType = StringUtils.defaultIfEmpty(metadataSetType, "edition"); 434 CopyReport report = new CopyReport(baseContentId, _contentHelper.getTitle(baseContent), true, auxMetadataSetName, auxMetadataSetType, CopyMode.CREATION); 435 436 try 437 { 438 report.setReferenceTable(_contentHelper.isReferenceTable(baseContent)); 439 440 Map<String, Object> internalCopyMap = copyMap; 441 if (internalCopyMap == null || BooleanUtils.isTrue((Boolean) internalCopyMap.get("$forceBuild"))) 442 { 443 internalCopyMap = _buildCopyMap(baseContent, auxMetadataSetName, auxMetadataSetType, targetContentType, internalCopyMap); 444 } 445 446 // Title metadata must never be copied in case of a content copy, the title is set during the content creation. 447 internalCopyMap.remove("title"); 448 449 Map<String, Object> inputs = _getInputsForCopy(baseContent, title, internalCopyMap, targetContentType, parentContentId, parentMetadataPath, report); 450 String workflowName = _getWorklowName(baseContent, inputs); 451 452 // Create the content and copy metadata from the base content. 453 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(); 454 workflow.initialize(workflowName, initActionId, inputs); 455 456 // Manual call to the edit content function to edit the content 457 // references stored by the copy report through the duplication process 458 _runContentReferencesEdition(baseContent, workflow, editRefActionId, true, report); 459 460 report.notifyContentCopySuccess(); 461 } 462 catch (Exception e) 463 { 464 getLogger().error("An error has been encountered during the content copy, or the copy is not allowed (base content identifier : " + baseContentId + ").", e); 465 466 report.notifyContentCopyError(); 467 } 468 469 return report; 470 } 471 472 /** 473 * Retrieve the inputs for the copy workflow function. 474 * @param baseContent The content to copy 475 * @param title The title to set 476 * @param copyMap The map with properties to copy 477 * @param targetContentType The type of content to create. If null the type(s) of created content will be those of base content. 478 * @param parentContentId The parent content ID, when copying a sub-content. 479 * @param parentMetadataPath The parent metadata path, when copying a sub-content. 480 * @param copyReport The report of the copy 481 * @return The map of inputs. 482 */ 483 @SuppressWarnings("unchecked") 484 protected Map<String, Object> _getInputsForCopy(Content baseContent, String title, Map<String, Object> copyMap, String targetContentType, String parentContentId, String parentMetadataPath, CopyReport copyReport) 485 { 486 Map<String, Object> inputs = new HashMap<>(); 487 488 // Add copy map inputs 489 Map<String, Object> copyMapInputs = (Map<String, Object>) copyMap.get("$inputs"); 490 if (copyMapInputs != null) 491 { 492 inputs.putAll(copyMapInputs); 493 } 494 495 inputs.put(CreateContentByCopyFunction.BASE_CONTENT_KEY, baseContent); 496 inputs.put(CreateContentByCopyFunction.COPY_MAP_KEY, copyMap); 497 inputs.put(CreateContentByCopyFunction.COPY_REPORT_KEY, copyReport); 498 499 if (StringUtils.isNotBlank(parentContentId) && StringUtils.isNotBlank(parentMetadataPath)) 500 { 501 // Provide the parent content ID and metadata path to the CreateContentFunction (removing the leading slash). 502 inputs.put(CreateContentFunction.PARENT_CONTENT_ID_KEY, parentContentId); 503 inputs.put(CreateContentFunction.PARENT_CONTENT_METADATA_PATH_KEY, StringUtils.stripStart(parentMetadataPath, "/")); 504 } 505 506 if (StringUtils.isNoneBlank(title)) 507 { 508 inputs.put(CreateContentFunction.CONTENT_TITLE_KEY, title); 509 } 510 511 inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, new HashMap<String, Object>()); 512 inputs.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String>()); 513 514 if (targetContentType != null) 515 { 516 inputs.put(CreateContentFunction.CONTENT_TYPES_KEY, new String[] {targetContentType}); 517 } 518 519 return inputs; 520 } 521 522 /** 523 * Retrieve the workflow name of a content. 524 * @param content The content to consider 525 * @param inputs The inputs that will be provided to the workflow function 526 * @return The name of the workflow. 527 * @throws IllegalArgumentException if the content is not workflow aware. 528 */ 529 protected String _getWorklowName(Content content, Map<String, Object> inputs) throws IllegalArgumentException 530 { 531 String workflowName = null; 532 533 if (content instanceof WorkflowAwareContent) 534 { 535 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 536 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 537 workflowName = workflow.getWorkflowName(waContent.getWorkflowId()); 538 } 539 540 if (workflowName == null) 541 { 542 String errorMsg = "Unable to retrieve the workflow name for the content with identifier '" + content.getId() + "'."; 543 544 getLogger().error(errorMsg); 545 throw new IllegalArgumentException(errorMsg); 546 } 547 548 return workflowName; 549 } 550 551 /** 552 * Build the copy map from given content and metadata set. 553 * @param baseContent the content to copy 554 * @param metadataSetName Name of the metadata set. 555 * @param metadataSetType Type of the metadata set. 556 * @return A map with properties to copy. 557 */ 558 public Map<String, Object> buildCopyMap (Content baseContent, String metadataSetName, String metadataSetType) 559 { 560 return _buildCopyMap(baseContent, metadataSetName, metadataSetType, new HashMap<String, Object>()); 561 } 562 563 /** 564 * Build the copy map from a metadata set. 565 * @param baseContent the content to copy 566 * @param metadataSetName Name of the metadata set. 567 * @param metadataSetType Type of the metadata set. 568 * @param baseCopyMap The copy map being constructed 569 * @return The copy map. 570 */ 571 protected Map<String, Object> _buildCopyMap(Content baseContent, String metadataSetName, String metadataSetType, Map<String, Object> baseCopyMap) 572 { 573 return _buildCopyMap(baseContent, metadataSetName, metadataSetType, null, baseCopyMap); 574 } 575 576 /** 577 * Build the copy map from a metadata set. 578 * @param baseContent The content to be copied 579 * @param metadataSetName Name of the metadata set to be copied. 580 * @param metadataSetType Type of the metadata set to be copied (view or edition). 581 * @param targetContentType The content type of content to create. Can be null. 582 * @param baseCopyMap The copy map being constructed 583 * @return The copy map. 584 */ 585 protected Map<String, Object> _buildCopyMap(Content baseContent, String metadataSetName, String metadataSetType, String targetContentType, Map<String, Object> baseCopyMap) 586 { 587 MetadataSet metadataSet = null; 588 if (targetContentType != null) 589 { 590 Set<String> types = new HashSet<>(); 591 Collections.addAll(types, baseContent.getTypes()); 592 types.add(targetContentType); 593 String commonAncestor = _contentTypesHelper.getCommonAncestor(types); 594 metadataSet = "view".equals(metadataSetType) ? _contentTypesHelper.getMetadataSetForView(metadataSetName, new String[] {commonAncestor}, new String[0]) : _contentTypesHelper.getMetadataSetForEdition(metadataSetName, new String[] {commonAncestor}, new String[0]); 595 } 596 else 597 { 598 metadataSet = "view".equals(metadataSetType) ? _contentTypesHelper.getMetadataSetForView(metadataSetName, baseContent.getTypes(), baseContent.getMixinTypes()) : _contentTypesHelper.getMetadataSetForEdition(metadataSetName, baseContent.getTypes(), baseContent.getMixinTypes()); 599 } 600 601 return _buildCopyMap(metadataSet, baseCopyMap); 602 603 } 604 605 /** 606 * Recursive auxiliary function used to build the copy map. 607 * @param metadataSetElement The current metadata set element 608 * @param copyMap The copy map being constructed or null 609 * @return The copy map corresponding to this metadata set element. 610 */ 611 protected Map<String, Object> _buildCopyMap(AbstractMetadataSetElement metadataSetElement, Map<String, Object> copyMap) 612 { 613 Map<String, Object> map = copyMap; 614 615 for (AbstractMetadataSetElement metadataElement : metadataSetElement.getElements()) 616 { 617 if (metadataElement instanceof MetadataDefinitionReference) 618 { 619 MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) metadataElement; 620 String name = metadataDefRef.getMetadataName(); 621 622 map = map != null ? map : new HashMap<>(); 623 map.put(name, _buildCopyMap(metadataDefRef, null)); 624 } 625 else 626 { 627 map = map != null ? map : new HashMap<>(); 628 map.putAll(_buildCopyMap(metadataElement, map)); 629 } 630 } 631 632 return map; 633 } 634 635 /** 636 * Edit a content by copying the value of the 637 * metadata of a source content into a target content. 638 * 639 * @param baseContentId The identifier of the base content 640 * @param targetContentId The identifier of the target content 641 * @param copyMap The map of properties as described in 642 * {@link CopyContentMetadataComponent}. Can be null in which 643 * case the map will be constructed from a metadataSet. 644 * @param metadataSetName The name of the metadata set to be used to 645 * construct to copyMap if not provided. This will also be the 646 * default name for possible inner copies (if not provided as a 647 * copyMap parameter). 648 * @param metadataSetType The type of the metadata set to be used to 649 * construct to copyMap if not provided. This will also be the 650 * default type for possible inner copies. 651 * @return The copy report containing valuable information about the copy 652 * and the possible encountered errors. 653 */ 654 public CopyReport editContent(String baseContentId, String targetContentId, Map<String, Object> copyMap, String metadataSetName, String metadataSetType) 655 { 656 return editContent(baseContentId, targetContentId, copyMap, metadataSetName, metadataSetType, getDefaultActionIdForContentEdition(), getDefaultActionIdForEditingContentReferences()); 657 } 658 659 /** 660 * Edit a content by copying the value of the 661 * metadata of a source content into a target content. 662 * 663 * @param baseContentId The identifier of the base content 664 * @param targetContentId The identifier of the target content 665 * @param copyMap The map of properties as described in 666 * {@link CopyContentMetadataComponent}. Can be null in which 667 * case the map will be constructed from a metadataSet. 668 * @param metadataSetName The name of the metadata set to be used to 669 * construct to copyMap if not provided. This will also be the 670 * default name for possible inner copies (if not provided as a 671 * copyMap parameter). 672 * @param metadataSetType The type of the metadata set to be used to 673 * construct to copyMap if not provided. This will also be the 674 * default type for possible inner copies. 675 * @param actionId the edit workflow action id 676 * @param editRefActionId the workflow action id for editing references 677 * @return The copy report containing valuable information about the copy 678 * and the possible encountered errors. 679 */ 680 public CopyReport editContent(String baseContentId, String targetContentId, Map<String, Object> copyMap, String metadataSetName, String metadataSetType, int actionId, int editRefActionId) 681 { 682 Content baseContent = null; 683 CopyReport report = null; 684 685 String auxMetadataSetName = StringUtils.defaultIfEmpty(metadataSetName, "main"); 686 String auxMetadataSetType = StringUtils.defaultIfEmpty(metadataSetType, "edition"); 687 688 try 689 { 690 baseContent = _resolver.resolveById(baseContentId); 691 report = new CopyReport(baseContentId, _contentHelper.getTitle(baseContent), _contentHelper.isReferenceTable(baseContent), auxMetadataSetName, auxMetadataSetType, CopyMode.EDITION); 692 693 Map<String, Object> internalCopyMap = copyMap; 694 if (internalCopyMap == null || BooleanUtils.isTrue((Boolean) internalCopyMap.get("$forceBuild"))) 695 { 696 internalCopyMap = _buildCopyMap(baseContent, auxMetadataSetName, auxMetadataSetType, internalCopyMap); 697 } 698 699 WorkflowAwareContent targetContent = _retrieveTargetContent(targetContentId); 700 Map<String, Object> inputs = _getInputsForEdition(baseContent, targetContent, internalCopyMap, report); 701 702 // Edit the content by copying metadata from the base content. 703 // This is done in a workflow function. 704 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(targetContent); 705 workflow.doAction(targetContent.getWorkflowId(), actionId, inputs); 706 707 // Manual call to the edit content function to edit the content 708 // references stored by the copy report through the duplication process 709 _runContentReferencesEdition(baseContent, workflow, editRefActionId, false, report); 710 711 report.notifyContentCopySuccess(); 712 } 713 catch (Exception e) 714 { 715 getLogger().error( 716 "An error has been encountered during the content edition, or the edition is not allowed (base content identifier : " + baseContentId 717 + ", target content identifier : " + targetContentId + ").", e); 718 719 if (report != null) 720 { 721 report.notifyContentCopyError(); 722 } 723 else 724 { 725 report = new CopyReport(baseContentId, _contentHelper.isReferenceTable(baseContent), auxMetadataSetName, auxMetadataSetType, CopyMode.EDITION); 726 report.notifyContentCopyError(); 727 } 728 } 729 730 return report; 731 } 732 733 /** 734 * Retrieve the target content from its id. 735 * Also ensure that it is a workflow aware content. 736 * @param targetContentId The target content identifer. 737 * @return the retrieved workflow aware content 738 * @throws IllegalArgumentException if the content is not workflow aware. 739 */ 740 protected WorkflowAwareContent _retrieveTargetContent(String targetContentId) throws IllegalArgumentException 741 { 742 Content content = _resolver.resolveById(targetContentId); 743 744 if (!(content instanceof WorkflowAwareContent)) 745 { 746 throw new IllegalArgumentException("Content with identifier '" + targetContentId + "' is not workflow aware."); 747 } 748 749 return (WorkflowAwareContent) content; 750 } 751 752 /** 753 * Retrieve the inputs for the edition workflow function. 754 * @param baseContent The content to copy 755 * @param targetContent The target of the copy 756 * @param copyMap The properties to copy 757 * @param copyReport The report of the copy 758 * @return The map of inputs. 759 */ 760 protected Map<String, Object> _getInputsForEdition(Content baseContent, WorkflowAwareContent targetContent, Map<String, Object> copyMap, CopyReport copyReport) 761 { 762 Map<String, Object> inputs = new HashMap<>(); 763 764 // Add copy map inputs 765 @SuppressWarnings("unchecked") 766 Map<String, Object> copyMapInputs = (Map<String, Object>) copyMap.get("$inputs"); 767 if (copyMapInputs != null) 768 { 769 inputs.putAll(copyMapInputs); 770 } 771 772 inputs.put(EditContentByCopyFunction.BASE_CONTENT_KEY, baseContent); 773 inputs.put(EditContentByCopyFunction.COPY_MAP_KEY, copyMap); 774 inputs.put(EditContentByCopyFunction.COPY_REPORT_KEY, copyReport); 775 776 inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, targetContent); 777 inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, new HashMap<String, Object>()); 778 inputs.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String>()); 779 780 return inputs; 781 } 782 783 784 /* ******************************* 785 * * 786 * Copy of the metadata * 787 * * 788 * ******************************/ 789 790 /** 791 * Copy the specified set of metadata from a base content to a target content by iterating over the copyMap. 792 * 793 * @param baseContent The original content 794 * @param targetContent The copy content 795 * @param copyMap The properties to copy 796 * @param copyReport The copy report being populated during the copy. 797 */ 798 @SuppressWarnings("unchecked") 799 public void copyMetadataMap(Content baseContent, ModifiableContent targetContent, Map<String, Object> copyMap, CopyReport copyReport) 800 { 801 copyReport.notifyContentCreation(targetContent.getId(), _contentHelper.getTitle(targetContent), _contentHelper.isReferenceTable(baseContent)); 802 803 Map<String, Object> innerCopyMapInputs = copyMap != null ? (Map<String, Object>) copyMap.get("$inputs") : null; 804 _copyMetadataMap(baseContent, baseContent.getMetadataHolder(), targetContent.getMetadataHolder(), "", null, copyMap, innerCopyMapInputs, copyReport); 805 806 copyReport.setTargetContentTitle(_contentHelper.getTitle(targetContent)); 807 808 _updateRichTextMetadata(baseContent, targetContent, copyReport); 809 _extractOutgoingReferences(targetContent); 810 } 811 812 /** 813 * Copy the specified set of metadata from a base composite metadata to a target composite metadata by iterating over the copyMap. 814 * @param baseContent The original content 815 * @param baseMetaHolder The metadata holder of the baseContent 816 * @param targetMetaHolder The metadata holder of the target content 817 * @param metaPrefix The metadata prefix. 818 * @param parentMetadataDefinition The parent metadata definition. Can be null 819 * @param copyMap The properties to copy 820 * @param innerCopyMapInputs The properties to copy for sub objects 821 * @param copyReport The copy report being populated during the copy. 822 */ 823 @SuppressWarnings("unchecked") 824 protected void _copyMetadataMap(Content baseContent, CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metaPrefix, MetadataDefinition parentMetadataDefinition, Map<String, Object> copyMap, Map<String, Object> innerCopyMapInputs, CopyReport copyReport) 825 { 826 if (copyMap == null) 827 { 828 return; 829 } 830 831 for (String metadataName : copyMap.keySet()) 832 { 833 // Ignore key starting with the $ (denotes a parameter) 834 if (StringUtils.startsWith(metadataName, "$")) 835 { 836 continue; 837 } 838 839 MetadataDefinition metadataDefinition = null; 840 try 841 { 842 metadataDefinition = _getMetadataDefinition(baseContent, parentMetadataDefinition, metadataName); 843 if (metadataDefinition != null) 844 { 845 copyMetadata(baseContent, baseMetaHolder, targetMetaHolder, metadataDefinition, metaPrefix, (Map<String, Object>) copyMap.get(metadataName), innerCopyMapInputs, copyReport); 846 } 847 } 848 catch (Exception e) 849 { 850 _reportMetadataException(baseMetaHolder, targetMetaHolder, metadataName, metadataDefinition, copyReport, e); 851 } 852 } 853 } 854 855 /** 856 * Retrieves a {@link MetadataDefinition} through its 857 * parent definition or the {@link ContentType} of the 858 * current content if at the root level of the metadataset. 859 * @param content The content 860 * @param metadataDefinition The parent metadata definition. Can be null. 861 * @param metadataName The metadata name 862 * @return The retrieved metadata defintion or null if not found 863 */ 864 protected MetadataDefinition _getMetadataDefinition(Content content, MetadataDefinition metadataDefinition, String metadataName) 865 { 866 if (metadataDefinition != null) 867 { 868 return metadataDefinition.getMetadataDefinition(metadataName); 869 } 870 else 871 { 872 return _contentTypesHelper.getMetadataDefinition(metadataName, content.getTypes(), content.getMixinTypes()); 873 } 874 } 875 876 /** 877 * Add a metadata exception to the report. 878 * @param baseMetaHolder The metadata holder to copy 879 * @param targetMetaHolder The metadata holder where to copy 880 * @param metadataName The metadata to copy 881 * @param metadataDefinition The associated definition 882 * @param copyReport The report of the copy 883 * @param e The raised exception. 884 */ 885 protected void _reportMetadataException(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataName, MetadataDefinition metadataDefinition, CopyReport copyReport, Exception e) 886 { 887 String type = metadataDefinition != null ? metadataDefinition.getType().toString() : null; 888 889 String errorMsg = "Copy of the metadata '" + metadataName + "' of type '" + (type != null ? type : "unknown type") + "' has failed."; 890 getLogger().error(errorMsg, e); 891 892 copyReport.notifyMetadataCopyError(metadataDefinition != null ? metadataDefinition.getLabel() : new I18nizableText(metadataName)); 893 } 894 895 /** 896 * Copy the specified metadata from a base composite metadata to a target composite metadata. 897 * @param baseContent The original content 898 * @param baseMetaHolder The metadata holder to copy 899 * @param targetMetaHolder The metadata holder where to copy 900 * @param metadataDefinition The associated definition 901 * @param metaPrefix The metadata prefix. 902 * @param copyMap The properties to copy 903 * @param innerCopyMapInputs The properties to copy for sub objects 904 * @param copyReport The report of the copy 905 */ 906 public void copyMetadata(Content baseContent, CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataDefinition metadataDefinition, String metaPrefix, Map<String, Object> copyMap, Map<String, Object> innerCopyMapInputs, CopyReport copyReport) 907 { 908 String metadataName = metadataDefinition.getName(); 909 String metadataPath = metaPrefix + "/" + metadataName; 910 911 switch (metadataDefinition.getType()) 912 { 913 case GEOCODE: 914 copyGeocodeMetadata(baseMetaHolder, targetMetaHolder, metadataName); 915 break; 916 case USER: 917 copyUserMetadata(baseMetaHolder, targetMetaHolder, metadataName); 918 break; 919 case MULTILINGUAL_STRING: 920 copyMultilingualStringMetadata(baseMetaHolder, targetMetaHolder, metadataName); 921 break; 922 case REFERENCE: 923 copyReferenceMetadata(baseMetaHolder, targetMetaHolder, metadataDefinition, metadataName); 924 break; 925 case BINARY: 926 copyBinaryMetadata(baseMetaHolder, targetMetaHolder, metadataName); 927 break; 928 case FILE: 929 copyFileMetadata(baseMetaHolder, targetMetaHolder, metadataDefinition, metadataName); 930 break; 931 case RICH_TEXT: 932 copyRichTextMetadata(baseMetaHolder, targetMetaHolder, metadataDefinition, metadataName, copyReport); 933 break; 934 case CONTENT: 935 copyContentReferenceMetadata(baseMetaHolder, targetMetaHolder, metadataDefinition, metadataPath, copyMap, innerCopyMapInputs, copyReport); 936 break; 937 case SUB_CONTENT: 938 copySubContentMetadata(baseMetaHolder, targetMetaHolder, metadataName, metadataPath, copyMap, innerCopyMapInputs, copyReport); 939 break; 940 case COMPOSITE: 941 copyCompositeMetadata(baseContent, baseMetaHolder, targetMetaHolder, metadataPath, metadataDefinition, copyMap, innerCopyMapInputs, copyReport); 942 break; 943 default: 944 copyBasicMetadata(baseMetaHolder, targetMetaHolder, metadataDefinition, metadataName); 945 break; 946 } 947 } 948 949 /** 950 * Copy a 'basic' metadata. 951 * This is used to copy metadata of type : 952 * {@link MetadataType#STRING}, {@link MetadataType#DATE}, {@link MetadataType#DATETIME}, 953 * {@link MetadataType#DOUBLE}, {@link MetadataType#LONG}, {@link MetadataType#BOOLEAN}, 954 * {@link MetadataType#USER} 955 * 956 * @param baseMetaHolder The metadata holder to copy 957 * @param targetMetaHolder The metadata holder where to copy 958 * @param metadataDef The metadata definition 959 * @param metadataName The metadata name 960 */ 961 public void copyBasicMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataDefinition metadataDef, String metadataName) 962 { 963 if (baseMetaHolder.hasMetadata(metadataName)) 964 { 965 switch (metadataDef.getType()) 966 { 967 case DATE: 968 case DATETIME: 969 _setDateMetadata(baseMetaHolder, targetMetaHolder, metadataName, metadataDef.isMultiple()); 970 break; 971 case DOUBLE: 972 _setDoubleMetadata(baseMetaHolder, targetMetaHolder, metadataName, metadataDef.isMultiple()); 973 break; 974 case LONG: 975 _setLongMetadata(baseMetaHolder, targetMetaHolder, metadataName, metadataDef.isMultiple()); 976 break; 977 case BOOLEAN: 978 _setBooleanMetadata(baseMetaHolder, targetMetaHolder, metadataName, metadataDef.isMultiple()); 979 break; 980 case STRING: 981 case USER: 982 default: 983 _setStringMetadata(baseMetaHolder, targetMetaHolder, metadataName, metadataDef.isMultiple()); 984 break; 985 } 986 } 987 else if (targetMetaHolder.hasMetadata(metadataName)) 988 { 989 targetMetaHolder.removeMetadata(metadataName); 990 } 991 } 992 993 /** 994 * Set a metadata of type string from a base composite metadata to a target composite metadata. 995 * @param baseMetaHolder The metadata holder to copy 996 * @param targetMetaHolder The metadata holder where to copy 997 * @param metadataName The metadata to consider 998 * @param isMultiple Is the metadata multiple 999 */ 1000 protected void _setStringMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataName, Boolean isMultiple) 1001 { 1002 if (isMultiple) 1003 { 1004 targetMetaHolder.setMetadata(metadataName, baseMetaHolder.getStringArray(metadataName)); 1005 } 1006 else 1007 { 1008 targetMetaHolder.setMetadata(metadataName, baseMetaHolder.getString(metadataName)); 1009 } 1010 } 1011 1012 /** 1013 * Set a metadata of type date/datetime from a base composite metadata to a target composite metadata. 1014 * @param baseMetaHolder The metadata holder to copy 1015 * @param targetMetaHolder The metadata holder where to copy 1016 * @param metadataName The metadata to consider 1017 * @param isMultiple Is the metadata multiple 1018 */ 1019 protected void _setDateMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataName, Boolean isMultiple) 1020 { 1021 if (isMultiple) 1022 { 1023 targetMetaHolder.setMetadata(metadataName, baseMetaHolder.getDateArray(metadataName)); 1024 } 1025 else 1026 { 1027 targetMetaHolder.setMetadata(metadataName, baseMetaHolder.getDate(metadataName)); 1028 } 1029 } 1030 1031 /** 1032 * Set a metadata of type double from a base composite metadata to a target composite metadata. 1033 * @param baseMetaHolder The metadata holder to copy 1034 * @param targetMetaHolder The metadata holder where to copy 1035 * @param metadataName The metadata to consider 1036 * @param isMultiple Is the metadata multiple 1037 */ 1038 protected void _setDoubleMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataName, Boolean isMultiple) 1039 { 1040 if (isMultiple) 1041 { 1042 targetMetaHolder.setMetadata(metadataName, baseMetaHolder.getDoubleArray(metadataName)); 1043 } 1044 else 1045 { 1046 targetMetaHolder.setMetadata(metadataName, baseMetaHolder.getDouble(metadataName)); 1047 } 1048 } 1049 1050 /** 1051 * Set a metadata of type long from a base composite metadata to a target composite metadata. 1052 * @param baseMetaHolder The metadata holder to copy 1053 * @param targetMetaHolder The metadata holder where to copy 1054 * @param metadataName The metadata to consider 1055 * @param isMultiple Is the metadata multiple 1056 */ 1057 protected void _setLongMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataName, Boolean isMultiple) 1058 { 1059 if (isMultiple) 1060 { 1061 targetMetaHolder.setMetadata(metadataName, baseMetaHolder.getLongArray(metadataName)); 1062 } 1063 else 1064 { 1065 targetMetaHolder.setMetadata(metadataName, baseMetaHolder.getLong(metadataName)); 1066 } 1067 } 1068 1069 /** 1070 * Set a metadata of type boolean from a base composite metadata to a target composite metadata. 1071 * @param baseMetaHolder The metadata holder to copy 1072 * @param targetMetaHolder The metadata holder where to copy 1073 * @param metadataName The metadata to consider 1074 * @param isMultiple Is the metadata multiple 1075 */ 1076 protected void _setBooleanMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataName, Boolean isMultiple) 1077 { 1078 if (isMultiple) 1079 { 1080 targetMetaHolder.setMetadata(metadataName, baseMetaHolder.getBooleanArray(metadataName)); 1081 } 1082 else 1083 { 1084 targetMetaHolder.setMetadata(metadataName, baseMetaHolder.getBoolean(metadataName)); 1085 } 1086 } 1087 1088 /** 1089 * Duplicate a metadata of type {@link MetadataType#GEOCODE}. 1090 * @param baseMetaHolder The metadata holder to copy 1091 * @param targetMetaHolder The metadata holder where to copy 1092 * @param metadataName The metadata to consider 1093 */ 1094 public void copyGeocodeMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataName) 1095 { 1096 if (baseMetaHolder.hasMetadata(metadataName)) 1097 { 1098 CompositeMetadata baseGeoCode = baseMetaHolder.getCompositeMetadata(metadataName); 1099 ModifiableCompositeMetadata targetGeoCode = targetMetaHolder.getCompositeMetadata(metadataName, true); 1100 1101 targetGeoCode.setMetadata("longitude", baseGeoCode.getDouble("longitude")); 1102 targetGeoCode.setMetadata("latitude", baseGeoCode.getDouble("latitude")); 1103 } 1104 else if (targetMetaHolder.hasMetadata(metadataName)) 1105 { 1106 targetMetaHolder.removeMetadata(metadataName); 1107 } 1108 } 1109 1110 /** 1111 * Duplicate a metadata of type {@link MetadataType#MULTILINGUAL_STRING}. 1112 * @param baseMetaHolder The metadata holder to copy 1113 * @param targetMetaHolder The metadata holder where to copy 1114 * @param metadataName The metadata to consider 1115 */ 1116 public void copyMultilingualStringMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataName) 1117 { 1118 if (baseMetaHolder.hasMetadata(metadataName)) 1119 { 1120 MultilingualString baseMultilingualString = baseMetaHolder.getMultilingualString(metadataName); 1121 for (Locale locale : baseMultilingualString.getLocales()) 1122 { 1123 targetMetaHolder.setMetadata(metadataName, baseMultilingualString.getValue(locale), locale); 1124 } 1125 } 1126 else if (targetMetaHolder.hasMetadata(metadataName)) 1127 { 1128 targetMetaHolder.removeMetadata(metadataName); 1129 } 1130 } 1131 1132 /** 1133 * Duplicate a metadata of type {@link MetadataType#USER}. 1134 * @param baseMetaHolder The metadata holder to copy 1135 * @param targetMetaHolder The metadata holder where to copy 1136 * @param metadataName The metadata to consider 1137 */ 1138 public void copyUserMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataName) 1139 { 1140 if (baseMetaHolder.hasMetadata(metadataName)) 1141 { 1142 CompositeMetadata baseUser = baseMetaHolder.getCompositeMetadata(metadataName); 1143 ModifiableCompositeMetadata targetUser = targetMetaHolder.getCompositeMetadata(metadataName, true); 1144 1145 targetUser.setMetadata("login", baseUser.getString("login")); 1146 targetUser.setMetadata("population", baseUser.getString("population")); 1147 } 1148 else if (targetMetaHolder.hasMetadata(metadataName)) 1149 { 1150 targetMetaHolder.removeMetadata(metadataName); 1151 } 1152 } 1153 1154 /** 1155 * Duplicate a metadata of type {@link MetadataType#REFERENCE}. 1156 * @param baseMetaHolder The metadata holder to copy 1157 * @param targetMetaHolder The metadata holder where to copy 1158 * @param metadataName The metadata to consider 1159 * @param metadataDefinition The associated definition 1160 */ 1161 public void copyReferenceMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataDefinition metadataDefinition, String metadataName) 1162 { 1163 if (baseMetaHolder.hasMetadata(metadataName)) 1164 { 1165 CompositeMetadata baseReferenceMeta = baseMetaHolder.getCompositeMetadata(metadataName); 1166 ModifiableCompositeMetadata targetReferenceMeta = targetMetaHolder.getCompositeMetadata(metadataName, true); 1167 1168 targetReferenceMeta.setMetadata("type", baseReferenceMeta.getString("type")); 1169 targetReferenceMeta.setMetadata("value", baseReferenceMeta.getString("value")); 1170 } 1171 else if (targetMetaHolder.hasMetadata(metadataName)) 1172 { 1173 targetMetaHolder.removeMetadata(metadataName); 1174 } 1175 } 1176 1177 /** 1178 * Duplicate a metadata of type {@link MetadataType#BINARY}. 1179 * @param baseMetaHolder The metadata holder to copy 1180 * @param targetMetaHolder The metadata holder where to copy 1181 * @param metadataName The metadata to consider 1182 */ 1183 public void copyBinaryMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataName) 1184 { 1185 if (baseMetaHolder.hasMetadata(metadataName)) 1186 { 1187 BinaryMetadata baseBinaryMetadata = baseMetaHolder.getBinaryMetadata(metadataName); 1188 ModifiableBinaryMetadata targetBinaryMetadata = targetMetaHolder.getBinaryMetadata(metadataName, true); 1189 1190 targetBinaryMetadata.setFilename(baseBinaryMetadata.getFilename()); 1191 targetBinaryMetadata.setMimeType(baseBinaryMetadata.getMimeType()); 1192 targetBinaryMetadata.setLastModified(baseBinaryMetadata.getLastModified()); 1193 targetBinaryMetadata.setEncoding(baseBinaryMetadata.getEncoding()); 1194 targetBinaryMetadata.setInputStream(baseBinaryMetadata.getInputStream()); 1195 } 1196 else if (targetMetaHolder.hasMetadata(metadataName)) 1197 { 1198 targetMetaHolder.removeMetadata(metadataName); 1199 } 1200 } 1201 1202 /** 1203 * Copy a file metadata 1204 * @param baseMetaHolder The copied composite metadata 1205 * @param targetMetaHolder The target composite metadata 1206 * @param metadataDef The metadata definition 1207 * @param metadataName The metadata name 1208 */ 1209 public void copyFileMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataDefinition metadataDef, String metadataName) 1210 { 1211 if (baseMetaHolder.hasMetadata(metadataName)) 1212 { 1213 // Could be a binary or a string. 1214 if (CompositeMetadata.MetadataType.BINARY.equals(baseMetaHolder.getType(metadataName))) 1215 { 1216 copyBinaryMetadata(baseMetaHolder, targetMetaHolder, metadataName); 1217 } 1218 else 1219 { 1220 targetMetaHolder.setMetadata(metadataName, baseMetaHolder.getString(metadataName)); 1221 copyBasicMetadata(baseMetaHolder, targetMetaHolder, metadataDef, metadataName); 1222 } 1223 } 1224 else if (targetMetaHolder.hasMetadata(metadataName)) 1225 { 1226 targetMetaHolder.removeMetadata(metadataName); 1227 } 1228 } 1229 1230 /** 1231 * Copy a rich-text metadata 1232 * @param baseMetaHolder The copied composite metadata 1233 * @param targetMetaHolder The target composite metadata 1234 * @param metadataName The metadata name 1235 * @param metadataDef The metadata definition 1236 * @param copyReport The copy report 1237 */ 1238 protected void copyRichTextMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataDefinition metadataDef, String metadataName, CopyReport copyReport) 1239 { 1240 if (baseMetaHolder.hasMetadata(metadataName)) 1241 { 1242 RichText baseRichText = baseMetaHolder.getRichText(metadataName); 1243 ModifiableRichText targetRichText = targetMetaHolder.getRichText(metadataName, true); 1244 1245 // Notify the report that a rich text has been copied. 1246 copyReport.addRichText(targetRichText, metadataDef); 1247 1248 targetRichText.setEncoding(baseRichText.getEncoding()); 1249 targetRichText.setMimeType(baseRichText.getMimeType()); 1250 targetRichText.setLastModified(baseRichText.getLastModified()); 1251 targetRichText.setInputStream(baseRichText.getInputStream()); 1252 1253 // Copy additional data 1254 _copyFolder(baseRichText.getAdditionalDataFolder(), targetRichText.getAdditionalDataFolder()); 1255 } 1256 else if (targetMetaHolder.hasMetadata(metadataName)) 1257 { 1258 targetMetaHolder.removeMetadata(metadataName); 1259 } 1260 } 1261 1262 /** 1263 * Folder copy during the copy of a rich text metadata. 1264 * @param baseFolder The folder to copy 1265 * @param targetFolder The folder where to copy 1266 */ 1267 protected void _copyFolder(Folder baseFolder, ModifiableFolder targetFolder) 1268 { 1269 // Files and folders removal 1270 targetFolder.removeAll(); 1271 1272 // Copy folders 1273 Collection< ? extends Folder> baseSubFolders = baseFolder.getFolders(); 1274 for (Folder baseSubFolder : baseSubFolders) 1275 { 1276 ModifiableFolder targetSubFolder = targetFolder.addFolder(baseSubFolder.getName()); 1277 _copyFolder(baseSubFolder, targetSubFolder); 1278 } 1279 1280 // Copy files 1281 Collection< ? extends File> baseFiles = baseFolder.getFiles(); 1282 for (File baseFile : baseFiles) 1283 { 1284 _copyFile(baseFile, targetFolder); 1285 } 1286 } 1287 1288 /** 1289 * File copy during the copy of a folder. 1290 * @param baseFile The file to copy 1291 * @param targetFolder The folder where to copy 1292 */ 1293 protected void _copyFile(File baseFile, ModifiableFolder targetFolder) 1294 { 1295 ModifiableFile file = targetFolder.addFile(baseFile.getName()); 1296 1297 Resource baseResource = baseFile.getResource(); 1298 ModifiableResource targetResource = file.getResource(); 1299 1300 targetResource.setLastModified(baseResource.getLastModified()); 1301 targetResource.setMimeType(baseResource.getMimeType()); 1302 targetResource.setEncoding(baseResource.getEncoding()); 1303 targetResource.setInputStream(baseResource.getInputStream()); 1304 } 1305 1306 /** 1307 * Duplicate a metadata of type {@link MetadataType#CONTENT}. 1308 * If the copy map has a '$mode' parameter set to 'create', a new content will be created for each referenced content in the base metadata. 1309 * The referenced contents are created if needed but content references are not set here. 1310 * It should be done manually as done in {@link #_runContentReferencesEdition} 1311 * @param baseMetaHolder The metadata holder of the content to copy 1312 * @param targetMetaHolder The metadata holder of the target content 1313 * @param metadataDefinition The definition of the metadata 1314 * @param metadataPath The content metadata path. 1315 * @param copyMap The properties to copy 1316 * @param innerCopyMapInputs The properties of sub objects 1317 * @param copyReport The report of the copy 1318 */ 1319 public void copyContentReferenceMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataDefinition metadataDefinition, String metadataPath, Map<String, Object> copyMap, Map<String, Object> innerCopyMapInputs, CopyReport copyReport) 1320 { 1321 String metadataName = metadataDefinition.getName(); 1322 1323 if (baseMetaHolder.hasMetadata(metadataName)) 1324 { 1325 String[] baseContentIds = baseMetaHolder.getStringArray(metadataName); 1326 String[] targetContentIds = baseContentIds; 1327 1328 if (copyMap != null && StringUtils.equals((String) copyMap.get("$mode"), "create")) 1329 { 1330 targetContentIds = _copyReferencedContents(baseContentIds, copyMap, innerCopyMapInputs, copyReport); 1331 } 1332 1333 if (targetContentIds.length > 0) 1334 { 1335 Object values = metadataDefinition.isMultiple() ? Arrays.asList(targetContentIds) : targetContentIds[0]; 1336 copyReport.addContentReferenceValues(metadataPath, values); 1337 } 1338 else if (targetMetaHolder.hasMetadata(metadataName)) 1339 { 1340 Object values = metadataDefinition.isMultiple() ? Collections.EMPTY_LIST : null; 1341 copyReport.addContentReferenceValues(metadataPath, values); 1342 } 1343 } 1344 else if (targetMetaHolder.hasMetadata(metadataName)) 1345 { 1346 Object values = metadataDefinition.isMultiple() ? Collections.EMPTY_LIST : null; 1347 copyReport.addContentReferenceValues(metadataPath, values); 1348 } 1349 } 1350 1351 /** 1352 * Duplicate base contents by creating new contents. If a specific metadata 1353 * set must be used, a '$metadataSetName' parameter must be specified. If 1354 * the copyMap for each inner duplication is already present in the current 1355 * copyMap, a '$loaded' parameter must be set to true, if not the copyMap 1356 * will be constructed from the requested metadata set. 1357 * @param baseContentIds The ids of contents to copy 1358 * @param copyMap The properties to copy 1359 * @param innerCopyMapInputs The properties of sub objects 1360 * @param copyReport The report of the copy 1361 * @return The array of identifiers of the new contents. 1362 */ 1363 protected String[] _copyReferencedContents(String[] baseContentIds, Map<String, Object> copyMap, Map<String, Object> innerCopyMapInputs, CopyReport copyReport) 1364 { 1365 String defaultMetadataSetName = StringUtils.defaultString((String) copyMap.get("$metadataSetName"), copyReport._metadataSetName); 1366 String defaultMetadataSetType = copyReport._metadataSetType; 1367 1368 Map<String, Object> innerCopyMap = _handleInnerCopyMap(copyMap, innerCopyMapInputs); 1369 List<String> targetContentIds = new ArrayList<>(); 1370 1371 for (String baseContentId : baseContentIds) 1372 { 1373 CopyReport innerReport = copyContent(baseContentId, innerCopyMap, defaultMetadataSetName, defaultMetadataSetType, getDefaultInitActionId(), getDefaultActionIdForEditingContentReferences()); 1374 1375 if (innerReport._state == CopyState.SUCCESS) 1376 { 1377 targetContentIds.add(innerReport._targetContentId); 1378 } 1379 1380 copyReport.addReport(innerReport); 1381 } 1382 1383 return targetContentIds.toArray(new String[targetContentIds.size()]); 1384 } 1385 1386 /** 1387 * Handle the creation of the inner copy map given the current context. 1388 * The inner copy map will act as the copy map for underlying content copies. 1389 * @param copyMap The properties to copy 1390 * @param innerCopyMapInputs The properties of sub objects 1391 * @return The inner copy map 1392 */ 1393 protected Map<String, Object> _handleInnerCopyMap(Map<String, Object> copyMap, Map<String, Object> innerCopyMapInputs) 1394 { 1395 // If the copy map does not contain the data for the inner duplications, 1396 // a null copy map should be passed to force the copy process to build 1397 // it. 1398 // If inner inputs for the copy map are supplied, they will be added to 1399 // the copy map. 1400 // In case of a null copy map, the copy map will no be null anymore but 1401 // a special parameters $forceBuild is added. 1402 1403 Map<String, Object> innerCopyMap = null; 1404 if (copyMap != null && BooleanUtils.isTrue((Boolean) copyMap.get("$loaded"))) 1405 { 1406 innerCopyMap = copyMap; 1407 } 1408 1409 if (innerCopyMapInputs != null) 1410 { 1411 if (innerCopyMap == null) 1412 { 1413 innerCopyMap = new HashMap<>(); 1414 innerCopyMap.put("$forceBuild", true); 1415 } 1416 1417 innerCopyMap.put("$inputs", innerCopyMapInputs); 1418 } 1419 1420 return innerCopyMap; 1421 } 1422 1423 /** 1424 * Duplicate a metadata of type {@link MetadataType#SUB_CONTENT}. 1425 * If the copy map has a '$mode' parameter set to 'create', a new content will be created for each referenced content in the base metadata. 1426 * @param baseMetaHolder The metadata holder of the content to copy 1427 * @param targetMetaHolder The metadata holder of the target content 1428 * @param metadataName The name of the metadata 1429 * @param metadataPath The content metadata path. 1430 * @param copyMap The properties to copy 1431 * @param innerCopyMapInputs The properties of sub objects 1432 * @param copyReport The report of the copy 1433 */ 1434 public void copySubContentMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataName, String metadataPath, Map<String, Object> copyMap, Map<String, Object> innerCopyMapInputs, CopyReport copyReport) 1435 { 1436 if (baseMetaHolder.hasMetadata(metadataName)) 1437 { 1438 String defaultMetadataSetType = copyReport._metadataSetType; 1439 String defaultMetadataSetName = copyReport._metadataSetName; 1440 if (copyMap != null && copyMap.containsKey("$metadataSetName")) 1441 { 1442 defaultMetadataSetName = (String) copyMap.get("$metadataSetName"); 1443 } 1444 1445 Map<String, Object> innerCopyMap = _handleInnerCopyMap(copyMap, innerCopyMapInputs); 1446 TraversableAmetysObject objectCollection = baseMetaHolder.getObjectCollection(metadataName); 1447 AmetysObjectIterable<Content> subContents = objectCollection.getChildren(); 1448 1449 for (Content content : subContents) 1450 { 1451 CopyReport innerReport = copyContent(content, null, innerCopyMap, defaultMetadataSetName, defaultMetadataSetType, copyReport.getTargetContentId(), metadataPath, getDefaultInitActionId(), getDefaultActionIdForEditingContentReferences()); 1452 1453 copyReport.addReport(innerReport); 1454 } 1455 } 1456 else if (targetMetaHolder.hasMetadata(metadataName)) 1457 { 1458 targetMetaHolder.removeMetadata(metadataName); 1459 } 1460 } 1461 1462 /** 1463 * Copy composite metadata 1464 * @param baseContent The copied content 1465 * @param baseMetaHolder The original composite metadata 1466 * @param targetMetaHolder The target composite metadata 1467 * @param metadataPath The metadata path 1468 * @param metadataDefinition The metadata definition 1469 * @param copyMap The map for copy 1470 * @param innerCopyMapInputs The properties of sub objects 1471 * @param copyReport The copy report 1472 */ 1473 public void copyCompositeMetadata(Content baseContent, CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataPath, MetadataDefinition metadataDefinition, Map<String, Object> copyMap, Map<String, Object> innerCopyMapInputs, CopyReport copyReport) 1474 { 1475 if (metadataDefinition instanceof RepeaterDefinition) 1476 { 1477 _copyRepeater(baseContent, baseMetaHolder, targetMetaHolder, metadataPath, metadataDefinition, copyMap, innerCopyMapInputs, copyReport); 1478 } 1479 else 1480 { 1481 String metadataName = metadataDefinition.getName(); 1482 1483 if (baseMetaHolder.hasMetadata(metadataName)) 1484 { 1485 CompositeMetadata baseCompositeMetadata = baseMetaHolder.getCompositeMetadata(metadataName); 1486 ModifiableCompositeMetadata targetCompositeMetadata = targetMetaHolder.getCompositeMetadata(metadataName, true); 1487 1488 _copyMetadataMap(baseContent, baseCompositeMetadata, targetCompositeMetadata, metadataPath, metadataDefinition, copyMap, innerCopyMapInputs, copyReport); 1489 } 1490 else if (targetMetaHolder.hasMetadata(metadataName)) 1491 { 1492 targetMetaHolder.removeMetadata(metadataName); 1493 } 1494 } 1495 } 1496 1497 /** 1498 * Copy a repeater 1499 * @param baseContent The copied content 1500 * @param baseMetaHolder The original composite metadata 1501 * @param targetMetaHolder The target composite metadata 1502 * @param metadataPath The metadata path 1503 * @param metadataDefinition The metadata definition 1504 * @param copyMap The map for copy 1505 * @param innerCopyMapInputs The properties of sub objects 1506 * @param copyReport The copy report 1507 */ 1508 protected void _copyRepeater(Content baseContent, CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataPath, MetadataDefinition metadataDefinition, Map<String, Object> copyMap, Map<String, Object> innerCopyMapInputs, CopyReport copyReport) 1509 { 1510 String metadataName = metadataDefinition.getName(); 1511 1512 if (baseMetaHolder.hasMetadata(metadataName)) 1513 { 1514 CompositeMetadata baseRepeaterMetadata = baseMetaHolder.getCompositeMetadata(metadataName); 1515 ModifiableCompositeMetadata targetRepeaterMetadata = targetMetaHolder.getCompositeMetadata(metadataName, true); 1516 1517 // Find the higher index for the entries of the base repeater 1518 String[] baseEntryNames = baseRepeaterMetadata.getMetadataNames(); 1519 Arrays.sort(baseEntryNames, MetadataManager.REPEATER_ENTRY_COMPARATOR); 1520 1521 int maxEntryName = 0; 1522 for (String entryName : baseEntryNames) 1523 { 1524 if (baseRepeaterMetadata.getType(entryName) == CompositeMetadata.MetadataType.COMPOSITE) 1525 { 1526 maxEntryName = Math.max(maxEntryName, Integer.parseInt(entryName)); 1527 } 1528 } 1529 1530 // Rename every repeater entry to ensure their removal later because index of the entry will be higher that the repeater size. 1531 // Sorted by reverse name order to avoid same name conflicts while renaming entries. 1532 if (maxEntryName > 0) 1533 { 1534 String[] targetEntryNames = targetRepeaterMetadata.getMetadataNames(); 1535 Arrays.sort(targetEntryNames, Collections.reverseOrder(MetadataManager.REPEATER_ENTRY_COMPARATOR)); 1536 1537 for (String entryName : targetEntryNames) 1538 { 1539 if (targetRepeaterMetadata.getType(entryName) == CompositeMetadata.MetadataType.COMPOSITE) 1540 { 1541 // Warning! Rename expects the full jcr name ('ametys:name'), not the metadata name as usual. 1542 // FIXME REPOSITORY-277 1543 targetRepeaterMetadata.getCompositeMetadata(entryName).rename("ametys:" + String.valueOf(maxEntryName + Integer.parseInt(entryName))); 1544 } 1545 } 1546 } 1547 1548 // Duplication base repeater entries 1549 // Fix target entry names if necessary (1,3,4 -> 1,2,3) 1550 int targetEntryName = 1; 1551 for (String entryName : baseEntryNames) 1552 { 1553 if (baseRepeaterMetadata.getType(entryName) == CompositeMetadata.MetadataType.COMPOSITE) 1554 { 1555 CompositeMetadata baseRepeaterEntry = baseRepeaterMetadata.getCompositeMetadata(entryName); 1556 ModifiableCompositeMetadata targetRepeaterEntry = targetRepeaterMetadata.getCompositeMetadata(String.valueOf(targetEntryName), true); 1557 1558 String metaPrefix = metadataPath + "/" + targetEntryName; 1559 1560 _copyMetadataMap(baseContent, baseRepeaterEntry, targetRepeaterEntry, metaPrefix, metadataDefinition, copyMap, innerCopyMapInputs, copyReport); 1561 1562 targetEntryName++; 1563 } 1564 } 1565 1566 // Sync repeater by storing base entry names. Remaining entries of 1567 // the target repeater metadata will be deleted with this (the job 1568 // is done via _runContentReferencesEdition) 1569 copyReport.addRepeaterInfo(metadataPath, baseEntryNames.length); 1570 } 1571 else /* if (targetMetaHolder.hasMetadata(metadataName)) */ 1572 { 1573 // Info are mandatory even if repeater does not currently exists. Otherwise, the EditContentFunction called via _runContentReferencesEdition will fails. 1574 copyReport.addRepeaterInfo(metadataPath, 0); 1575 } 1576 } 1577 1578 /** 1579 * Analyse the content to find outgoing references and store them 1580 * @param content The content to analyze 1581 */ 1582 protected void _extractOutgoingReferences(ModifiableContent content) 1583 { 1584 Map<String, OutgoingReferences> outgoingReferencesByPath = _outgoingReferencesExtractor.getOutgoingReferences(content); 1585 content.setOutgoingReferences(outgoingReferencesByPath); 1586 } 1587 1588 1589 /** 1590 * This method analyzes content rich texts and update their content to 1591 * ensure consistency. (link to image, attachments...) 1592 * @param baseContent The copied content 1593 * @param targetContent The target content 1594 * @param copyReport The copy report 1595 */ 1596 protected void _updateRichTextMetadata(final Content baseContent, final ModifiableContent targetContent, CopyReport copyReport) 1597 { 1598 try 1599 { 1600 Map<ModifiableRichText, MetadataDefinition> copiedRichTexts = copyReport.getCopiedRichTexts(); 1601 1602 for (Entry<ModifiableRichText, MetadataDefinition> entry : copiedRichTexts.entrySet()) 1603 { 1604 ModifiableRichText richText = entry.getKey(); 1605 MetadataDefinition metadataDef = entry.getValue(); 1606 1607 String referenceContentType = metadataDef.getReferenceContentType(); 1608 ContentType contentType = _contentTypeExtensionPoint.getExtension(referenceContentType); 1609 1610 RichTextUpdater richTextUpdater = contentType.getRichTextUpdater(); 1611 if (richTextUpdater != null) 1612 { 1613 Map<String, Object> params = new HashMap<>(); 1614 params.put("initialContent", baseContent); 1615 params.put("createdContent", targetContent); 1616 params.put("initialAO", baseContent); 1617 params.put("createdAO", targetContent); 1618 1619 // create the transformer instance 1620 TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); 1621 1622 // create the format of result 1623 Properties format = new Properties(); 1624 format.put(OutputKeys.METHOD, "xml"); 1625 format.put(OutputKeys.INDENT, "yes"); 1626 format.put(OutputKeys.ENCODING, "UTF-8"); 1627 format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "2"); 1628 th.getTransformer().setOutputProperties(format); 1629 1630 // Update rich text contents 1631 // Copy the needed original attachments. 1632 try (InputStream is = richText.getInputStream(); OutputStream os = richText.getOutputStream()) 1633 { 1634 StreamResult result = new StreamResult(os); 1635 th.setResult(result); 1636 1637 ContentHandler richTextHandler = richTextUpdater.getContentHandler(th, th, params); 1638 1639 // Copy attachments handler. 1640 ContentHandlerProxy copyAttachmentsHandler = new CopyAttachmentsHandler(richTextHandler, baseContent, targetContent, copyReport, _resolver, getLogger()); 1641 1642 // Rich text update. 1643 _saxParser.parse(new InputSource(is), copyAttachmentsHandler); 1644 } 1645 finally 1646 { 1647 th.getTransformer().reset(); 1648 } 1649 } 1650 } 1651 } 1652 catch (Exception e) 1653 { 1654 getLogger().error( 1655 "An error occurred while updating rich text metadata for content '" + targetContent.getId() + " after copy from initial content '" + baseContent.getId() + "'", 1656 e); 1657 } 1658 } 1659 1660 /** 1661 * Run the edition of the content references that has been stored during the copy of the metadata. 1662 * It is done via the EditContentFunction at the end of the process for technical purposes. 1663 * @param baseContent The base content to copy 1664 * @param targetContentWorkflow the target content local workflow 1665 * @param editActionId The edit action id to edit references 1666 * @param forceEdition true to force edition regardless user's rights 1667 * @param copyReport the copy report 1668 * @throws Exception If an error occured 1669 */ 1670 protected void _runContentReferencesEdition(Content baseContent, AmetysObjectWorkflow targetContentWorkflow, int editActionId, boolean forceEdition, CopyReport copyReport) throws Exception 1671 { 1672 Map<String, Object> editionValues = new HashMap<>(); 1673 1674 Map<String, Object> contentReferenceValues = copyReport.getContentReferenceValuesMap(); 1675 for (Entry<String, Object> entry : contentReferenceValues.entrySet()) 1676 { 1677 String valueKey = StringUtils.replaceChars(StringUtils.stripStart(entry.getKey(), "/"), '/', '.'); 1678 editionValues.put(EditContentFunction.FORM_ELEMENTS_PREFIX + valueKey, entry.getValue()); 1679 } 1680 1681 Map<String, Integer> repeatersInfo = copyReport.getRepeatersInfo(); 1682 for (Entry<String, Integer> entry : repeatersInfo.entrySet()) 1683 { 1684 String valueKey = StringUtils.replaceChars(StringUtils.stripStart(entry.getKey(), "/"), '/', '.'); 1685 Integer entryCount = entry.getValue(); 1686 1687 editionValues.put(EditContentFunction.FORM_ELEMENTS_PREFIX + valueKey, null); 1688 editionValues.put('_' + EditContentFunction.FORM_ELEMENTS_PREFIX + valueKey + ".size", String.valueOf(entryCount)); 1689 1690 for (int i = 1; i <= entryCount; i++) 1691 { 1692 String pos = String.valueOf(i); 1693 editionValues.put('_' + EditContentFunction.FORM_ELEMENTS_PREFIX + valueKey + "." + i + ".previous-position", pos); 1694 editionValues.put('_' + EditContentFunction.FORM_ELEMENTS_PREFIX + valueKey + "." + i + ".position", pos); 1695 } 1696 } 1697 1698 if (editionValues.size() > 0) 1699 { 1700 WorkflowAwareContent targetContent = targetContentWorkflow.getAmetysObject(); 1701 Map<String, Object> inputs = _getInputsForContentReferencesEdition(targetContent, targetContent, editionValues, copyReport); 1702 if (forceEdition) 1703 { 1704 inputs.put(CheckRightsCondition.FORCE, forceEdition); 1705 } 1706 1707 try 1708 { 1709 targetContentWorkflow.doAction(targetContent.getWorkflowId(), editActionId, inputs); 1710 } 1711 catch (Exception e) 1712 { 1713 throw new ProcessingException("Error during content references edition. (base content identifier : " + baseContent.getId() 1714 + ", target content identifier : " + targetContent.getId() + "). ", e); 1715 } 1716 } 1717 } 1718 1719 /** 1720 * Retrieve the inputs for the content references edition workflow function. 1721 * @param baseContent The content to copy 1722 * @param targetContent The content where to copy 1723 * @param editionValues The values representing the content references to be edited (also contains special repeater size values) 1724 * @param copyReport The report of the copy 1725 * @return The map of inputs. 1726 */ 1727 protected Map<String, Object> _getInputsForContentReferencesEdition(Content baseContent, WorkflowAwareContent targetContent, Map<String, Object> editionValues, CopyReport copyReport) 1728 { 1729 Map<String, Object> inputs = new HashMap<>(); 1730 1731 inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, targetContent); 1732 inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, new HashMap<String, Object>()); 1733 inputs.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String>()); 1734 1735 Map<String, Object> contextParameters = new HashMap<>(); 1736 contextParameters.put(EditContentFunction.QUIT, Boolean.TRUE); 1737 contextParameters.put(EditContentFunction.FORM_RAW_VALUES, editionValues); 1738 inputs.put(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY, contextParameters); 1739 1740 return inputs; 1741 } 1742 1743 /** 1744 * Get the default workflow action id for initialization of main content 1745 * @return the default action id 1746 */ 1747 public int getDefaultInitActionId () 1748 { 1749 return 111; 1750 } 1751 1752 1753 /** 1754 * Get the default workflow action id for editing content metadata of main content 1755 * @return the default action id 1756 */ 1757 public int getDefaultActionIdForEditingContentReferences () 1758 { 1759 return 2; 1760 } 1761 1762 /** 1763 * Get the default workflow action id for editing content by copy 1764 * @return the default action id 1765 */ 1766 public int getDefaultActionIdForContentEdition() 1767 { 1768 return 222; 1769 } 1770 1771 /** 1772 * Retrieve the action id to execute for the content copy. 1773 * @return The action id 1774 */ 1775 public int getActionIdForCopy2() 1776 { 1777 return 111; 1778 } 1779 1780 /** 1781 * A copy attachments content handler. 1782 * To be used to copy the attachments linked in a rich text metadata. 1783 */ 1784 protected static class CopyAttachmentsHandler extends ContentHandlerProxy 1785 { 1786 /** base content */ 1787 protected Content _baseContent; 1788 /** target content */ 1789 protected ModifiableContent _targetContent; 1790 /** copy report */ 1791 protected CopyReport _copyReport; 1792 /** Ametys object resolver */ 1793 protected AmetysObjectResolver _resolver; 1794 /** logger */ 1795 protected Logger _logger; 1796 1797 1798 /** 1799 * Ctor 1800 * @param contentHandler The content handler to delegate to. 1801 * @param baseContent The content to copy 1802 * @param targetContent The content where to copy 1803 * @param copyReport The report of the copy 1804 * @param resolver The ametys object resolver 1805 * @param logger A logger to log informations 1806 */ 1807 protected CopyAttachmentsHandler(ContentHandler contentHandler, Content baseContent, ModifiableContent targetContent, CopyReport copyReport, AmetysObjectResolver resolver, Logger logger) 1808 { 1809 super(contentHandler); 1810 _baseContent = baseContent; 1811 _targetContent = targetContent; 1812 _copyReport = copyReport; 1813 _resolver = resolver; 1814 _logger = logger; 1815 } 1816 1817 @Override 1818 public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException 1819 { 1820 if ("link".equals(loc)) 1821 { 1822 // Copy attachment 1823 _copyIfAttachment(attrs.getValue("xlink:href")); 1824 } 1825 1826 super.startElement(uri, loc, raw, attrs); 1827 } 1828 1829 /** 1830 * Copy the linked resource to the new content if it is an attachment. 1831 * @param href link href attribute 1832 */ 1833 protected void _copyIfAttachment(String href) 1834 { 1835 try 1836 { 1837 if (_baseContent.getId().equals(href) || _targetContent.getId().equals(href)) 1838 { 1839 // nothing to do 1840 return; 1841 } 1842 else if (_resolver.hasAmetysObjectForId(href)) 1843 { 1844 AmetysObject ametysObject = _resolver.resolveById(href); 1845 1846 ResourceCollection baseRootAttachments = _baseContent.getRootAttachments(); 1847 if (!(ametysObject instanceof org.ametys.plugins.explorer.resources.Resource) || baseRootAttachments == null) 1848 { 1849 // nothing to do 1850 return; 1851 } 1852 1853 String baseAttachmentsPath = _baseContent.getRootAttachments().getPath(); 1854 String resourcePath = ametysObject.getPath(); 1855 1856 if (resourcePath.startsWith(baseAttachmentsPath + '/')) 1857 { 1858 // Is in attachments path 1859 String relPath = StringUtils.removeStart(resourcePath, baseAttachmentsPath + '/'); 1860 _copyAttachment(ametysObject, relPath); 1861 } 1862 } 1863 } 1864 catch (AmetysRepositoryException e) 1865 { 1866 // the reference was not <protocol>://<protocol-specific-part> (for example : mailto:mymail@example.com ) 1867 _logger.debug("The link '{}' is not recognized as Ametys object. It will be ignored", href); 1868 return; 1869 } 1870 } 1871 1872 /** 1873 * Copy an attachment 1874 * @param baseResource The resource to copy 1875 * @param relPath The path where to copy 1876 */ 1877 protected void _copyAttachment(AmetysObject baseResource, String relPath) 1878 { 1879 boolean success = false; 1880 Exception exception = null; 1881 1882 try 1883 { 1884 if (_targetContent instanceof ModifiableTraversableAmetysObject) 1885 { 1886 ModifiableTraversableAmetysObject mtaoTargetContent = (ModifiableTraversableAmetysObject) _targetContent; 1887 ModifiableResourceCollection targetParentCollection = mtaoTargetContent.getChild(DefaultContent.ATTACHMENTS_NODE_NAME); 1888 1889 String[] parts = StringUtils.split(relPath, '/'); 1890 if (parts.length > 0) 1891 { 1892 // Traverse the path and create necessary resources collections 1893 for (int i = 0; i < parts.length - 1; i++) 1894 { 1895 String childName = parts[i]; 1896 if (!targetParentCollection.hasChild(childName)) 1897 { 1898 targetParentCollection = targetParentCollection.createChild(childName, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE); 1899 } 1900 else 1901 { 1902 targetParentCollection = targetParentCollection.getChild(childName); 1903 } 1904 } 1905 1906 // Copy the attachment resource. 1907 String resourceName = parts[parts.length - 1]; 1908 if (baseResource instanceof CopiableAmetysObject) 1909 { 1910 ((CopiableAmetysObject) baseResource).copyTo(targetParentCollection, resourceName); 1911 success = true; 1912 _copyReport.addAttachment(relPath); 1913 } 1914 } 1915 } 1916 } 1917 catch (Exception e) 1918 { 1919 exception = e; 1920 } 1921 1922 if (!success) 1923 { 1924 String warnMsg = "Unable to copy attachment from base path '" + baseResource.getPath() + "' to the content at path : '" + _targetContent.getPath() + "'."; 1925 1926 if (_logger.isWarnEnabled()) 1927 { 1928 if (exception != null) 1929 { 1930 _logger.warn(warnMsg, exception); 1931 } 1932 else 1933 { 1934 _logger.warn(warnMsg); 1935 } 1936 } 1937 } 1938 } 1939 } 1940}