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