diff --git a/dotCMS/src/enterprise/java/com/dotcms/enterprise/priv/HostAssetsJobImpl.java b/dotCMS/src/enterprise/java/com/dotcms/enterprise/priv/HostAssetsJobImpl.java index 0899a68fa366..cf5bf2e4b7f4 100644 --- a/dotCMS/src/enterprise/java/com/dotcms/enterprise/priv/HostAssetsJobImpl.java +++ b/dotCMS/src/enterprise/java/com/dotcms/enterprise/priv/HostAssetsJobImpl.java @@ -110,6 +110,7 @@ import com.dotmarketing.util.contentet.pagination.PaginatedContentlets; import com.liferay.portal.model.User; import com.liferay.util.StringPool; +import io.vavr.Lazy; import io.vavr.control.Try; import org.apache.commons.beanutils.BeanUtils; import org.quartz.JobExecutionContext; @@ -117,7 +118,6 @@ import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -162,6 +162,24 @@ public class HostAssetsJobImpl extends ParentProxy{ private final Host SYSTEM_HOST; private final Folder SYSTEM_FOLDER; + /** + * This feature flag allows you to enable/disable copying Content Types when you copy a Site. + */ + public static final String ENABLE_CONTENT_TYPE_COPY = "FEATURE_FLAG_ENABLE_CONTENT_TYPE_COPY"; + /** + * This property allows you to fall back to NOT copy related content whose parent is a + * Contentlet living in System Host. Such type of relationship was being ignored because + * contents living in System Host are NEVER copied, and were not taken into account when + * copying relationship data. + */ + public static final String COPY_RELATED_CONTENT_IN_SYSTEM_HOST_CONTENTS = + "COPY_RELATED_CONTENT_IN_SYSTEM_HOST_CONTENTS"; + + private static final Lazy CONTENT_TYPE_COPY_FLAG = + Lazy.of(() -> Config.getBooleanProperty(ENABLE_CONTENT_TYPE_COPY, false)); + private static final Lazy COPY_RELATED_CONTENT_IN_SYSTEM_HOST_CONTENTS_FLAG = Lazy.of(() -> + Config.getBooleanProperty(COPY_RELATED_CONTENT_IN_SYSTEM_HOST_CONTENTS, true)); + private static final boolean DONT_RESPECT_FRONTEND_ROLES = Boolean.FALSE; private static final boolean RESPECT_FRONTEND_ROLES = Boolean.TRUE; @@ -255,24 +273,6 @@ public void run(final JobExecutionContext jobContext) throws JobExecutionExcepti this.sendNotification(String.format(successMsg, destinationSite.getHostname()), userId, NotificationLevel.INFO); } - private HTMLPageAssetAPI.TemplateContainersReMap getMirrorTemplateContainersReMap(Template sourceTemplate) - throws DotDataException, DotSecurityException { - - User user = userAPI.getSystemUser(); - boolean respectFrontendRoles = false; - - List sourceContainers = templateAPI.getContainersInTemplate(sourceTemplate, user, - respectFrontendRoles); - List containerMappings = new LinkedList<>(); - for (Container sourceContainer : sourceContainers) { - HTMLPageAssetAPI.TemplateContainersReMap.ContainerRemapTuple containerMapping = new HTMLPageAssetAPI.TemplateContainersReMap.ContainerRemapTuple( - sourceContainer, sourceContainer); - containerMappings.add(containerMapping); - } - return new HTMLPageAssetAPI.TemplateContainersReMap(sourceTemplate, - sourceTemplate, containerMappings); - } - /** * Performs the copy process of the user-specified elements from the source site to the * destination site. @@ -292,20 +292,20 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f try { HibernateUtil.startTransaction(); // Global Vars - double progressIncrement = 0; - double currentProgress = 0; + double progressIncrement; + double currentProgress; this.siteCopyStatus.addMessage("copying-templates"); // ====================================================================== // Copying templates and containers // ====================================================================== - final Map templatesMappingBySourceId = new HashMap<>(); + final Map copiedTemplatesBySourceId = new HashMap<>(); final Map copiedContainersBySourceId = new HashMap<>(); - final Map folderMappingsBySourceId = new HashMap<>(); - final Map contentMappingsBySourceId = new HashMap<>(); - final List contentsToCopyDependencies = new ArrayList<>(); - final Map copiedContentTypes = new HashMap<>(); - final Map copiedRelationships = new HashMap<>(); + final Map copiedFoldersBySourceId = new HashMap<>(); + final Map copiedContentsBySourceId = new HashMap<>(); + final List contentsWithRelationships = new ArrayList<>(); + final Map copiedContentTypesBySourceId = new HashMap<>(); + final Map copiedRelationshipsBySourceId = new HashMap<>(); if (copyOptions.isCopyTemplatesAndContainers()) { Logger.info(this, "----------------------------------------------------------------------"); Logger.info(this, String.format(":::: Copying Templates and Containers to new Site '%s'", destinationSite.getHostname())); @@ -338,7 +338,7 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f DONT_RESPECT_FRONTEND_ROLES); final Folder sourceFolder = this.folderAPI.find(sourceContent.getFolder(), this.SYSTEM_USER, DONT_RESPECT_FRONTEND_ROLES); // This should store the new container destination folder into the map. - final Folder destinationFolder = copyFolder(sourceFolder, destinationSite, folderMappingsBySourceId); + final Folder destinationFolder = copyFolder(sourceFolder, destinationSite, copiedFoldersBySourceId); Logger.debug(HostAssetsJobImpl.class, () -> String.format("---> Container-As-File destination folder path is '%s'", destinationFolder.getPath())); // now create the Copy of the container as file and all its assets @@ -350,12 +350,12 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f processedContentletsList.add( processCopyOfContentlet(asset, copyOptions, destinationSite, - contentMappingsBySourceId, - folderMappingsBySourceId, + copiedContentsBySourceId, + copiedFoldersBySourceId, copiedContainersBySourceId, - templatesMappingBySourceId, - contentsToCopyDependencies, - copiedContentTypes + copiedTemplatesBySourceId, + contentsWithRelationships, + copiedContentTypesBySourceId ) ); } @@ -400,7 +400,7 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f final Template newTemplate = copyTemplate(sourceTemplate, destinationSite, copiedContainersBySourceId); HTMLPageAssetAPI.TemplateContainersReMap templateMapping = new HTMLPageAssetAPI.TemplateContainersReMap( sourceTemplate, newTemplate, containerMappings); - templatesMappingBySourceId.put(sourceTemplate.getIdentifier(), templateMapping); + copiedTemplatesBySourceId.put(sourceTemplate.getIdentifier(), templateMapping); } catch (final Exception e) { Logger.error(this, String.format("An error occurred when copying data from Template '%s' [%s] " + "from Site '%s' to Site '%s'. The process will continue...", sourceTemplate.getTitle() @@ -446,7 +446,7 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f } final HTMLPageAssetAPI.TemplateContainersReMap templateMapping = new HTMLPageAssetAPI.TemplateContainersReMap( sourceTemplate, sourceTemplate, containerMappings); - templatesMappingBySourceId.put(sourceTemplate.getIdentifier(), templateMapping); + copiedTemplatesBySourceId.put(sourceTemplate.getIdentifier(), templateMapping); } catch (final Exception e) { Logger.error(this, String.format("An error occurred when copying data from Template '%s' [%s] " + "from Site '%s' to Site '%s'. The process will continue...", sourceTemplate.getTitle() @@ -470,7 +470,7 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f Logger.info(this, String.format("-> Copying %d Folders", allSourceFolders.size())); for (final Folder sourceFolder : allSourceFolders) { try { - copyFolder(sourceFolder, destinationSite, folderMappingsBySourceId); + copyFolder(sourceFolder, destinationSite, copiedFoldersBySourceId); } catch (final Exception e) { Logger.error(this, String.format("An error occurred when copying folder '%s' from Site '%s' to" + " Site '%s'. The process will continue...", sourceFolder.getPath(), sourceSite @@ -482,7 +482,7 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f this.siteCopyStatus.updateProgress(10); if (copyOptions.isCopyLinks()) { - final Collection folders = folderMappingsBySourceId.values(); + final Collection folders = copiedFoldersBySourceId.values(); // ====================================================================== // Copying Menu Links // ====================================================================== @@ -510,12 +510,12 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f if(copyOptions.isCopyTemplatesAndContainers()){ Logger.info(this, "----------------------------------------------------------------------"); Logger.info(this, String.format(":::: Pointing %d Templates to copied themes for new Site '%s'", - templatesMappingBySourceId.size(), destinationSite.getHostname())); - for (final String sourceTemplateId : templatesMappingBySourceId.keySet()) { - final Template srcTemplate = templatesMappingBySourceId.get(sourceTemplateId).getSourceTemplate(); - if(UtilMethods.isSet(srcTemplate.getTheme()) && folderMappingsBySourceId.containsKey(srcTemplate.getTheme())){ - final String destTemplateInode = templatesMappingBySourceId.get(sourceTemplateId).getDestinationTemplate().getInode(); - final String destTheme = folderMappingsBySourceId.get(srcTemplate.getTheme()).destinationFolder.getInode(); + copiedTemplatesBySourceId.size(), destinationSite.getHostname())); + for (final String sourceTemplateId : copiedTemplatesBySourceId.keySet()) { + final Template srcTemplate = copiedTemplatesBySourceId.get(sourceTemplateId).getSourceTemplate(); + if(UtilMethods.isSet(srcTemplate.getTheme()) && copiedFoldersBySourceId.containsKey(srcTemplate.getTheme())){ + final String destTemplateInode = copiedTemplatesBySourceId.get(sourceTemplateId).getDestinationTemplate().getInode(); + final String destTheme = copiedFoldersBySourceId.get(srcTemplate.getTheme()).destinationFolder.getInode(); this.templateAPI.updateThemeWithoutVersioning(destTemplateInode, destTheme); } } @@ -526,7 +526,7 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f HibernateUtil.startTransaction(); this.siteCopyStatus.updateProgress(70); - if (Config.getBooleanProperty("FEATURE_FLAG_ENABLE_CONTENT_TYPE_COPY", false) && copyOptions.isCopyContentTypes()) { + if (CONTENT_TYPE_COPY_FLAG.get() && copyOptions.isCopyContentTypes()) { Logger.info(this, "----------------------------------------------------------------------"); Logger.info(this, String.format(":::: Copying Content Types to new Site '%s'", destinationSite.getHostname())); final List sourceContentTypes = this.contentTypeAPI.search("", @@ -542,7 +542,7 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f // Copy the Content Type objects with NONE of their relationship fields final ContentType copiedContentType = this.contentTypeAPI.copyFromAndDependencies(builder.build(), destinationSite, false); - copiedContentTypes.put(sourceContentType.id(), new ContentTypeMapping(sourceContentType, copiedContentType)); + copiedContentTypesBySourceId.put(sourceContentType.id(), new ContentTypeMapping(sourceContentType, copiedContentType)); } final List childRelationships = new ArrayList<>(); // Now, copy the relationship fields back, but EXCLUDE all relationship fields that are pointing @@ -558,8 +558,8 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f sourceRelationshipField.name(), sourceContentType.name())); continue; } - final ContentTypeMapping parentContentTypeMapping = copiedContentTypes.get(sourceRelationship.getParentStructure().id()); - final ContentTypeMapping childContentTypeMapping = copiedContentTypes.get(sourceRelationship.getChildStructure().id()); + final ContentTypeMapping parentContentTypeMapping = copiedContentTypesBySourceId.get(sourceRelationship.getParentStructure().id()); + final ContentTypeMapping childContentTypeMapping = copiedContentTypesBySourceId.get(sourceRelationship.getChildStructure().id()); checkNotNull(parentContentTypeMapping, "Parent Content Type ID in Relationship Field " + "'%s' in Content Type '%s' is null", sourceRelationshipField.name(), sourceContentType.name()); checkNotNull(childContentTypeMapping, "Child Content Type ID in Relationship Field " + @@ -567,21 +567,21 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f // If this Relationship Field represents the parent of the relationship, or if the relationship is between // the same Content Types, just copy the field with the new IDs and data if (this.relationshipAPI.isChildField(sourceRelationship, sourceRelationshipField) || this.relationshipAPI.sameParentAndChild(sourceRelationship)) { - final String copiedContentTypeId = copiedContentTypes.get(sourceContentType.id()).destinationContentType.id(); + final String copiedContentTypeId = copiedContentTypesBySourceId.get(sourceContentType.id()).destinationContentType.id(); final String copiedContentTypeVarName = childContentTypeMapping.destinationContentType.variable(); this.createRelationshipField(copiedContentTypeId, copiedContentTypeVarName, sourceRelationshipField, - sourceRelationship, copiedRelationships); + sourceRelationship, copiedRelationshipsBySourceId); } else { // Here, the Relationship Field is the child of the current Relationship and its parent Relationship has // already been copied. Therefore, we can create the child Relationship Field now and reference the // existing relationship - if (copiedRelationships.containsKey(sourceRelationship.getInode())) { - final Relationship copiedRelationship = copiedRelationships.get(sourceRelationship.getInode()).destinationRelationship; - final String copiedContentTypeId = copiedContentTypes.get(sourceContentType.id()).destinationContentType.id(); + if (copiedRelationshipsBySourceId.containsKey(sourceRelationship.getInode())) { + final Relationship copiedRelationship = copiedRelationshipsBySourceId.get(sourceRelationship.getInode()).destinationRelationship; + final String copiedContentTypeId = copiedContentTypesBySourceId.get(sourceContentType.id()).destinationContentType.id(); this.createRelationshipField(copiedContentTypeId, copiedRelationship.getRelationTypeValue(), sourceRelationshipField, - sourceRelationship, copiedRelationships); + sourceRelationship, copiedRelationshipsBySourceId); } else { // If the Relationship Field points to a relationship that hasn't been copied yet, we'll wait until // the parent Relationship is created and store its data in a list @@ -598,8 +598,8 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f for (final RelationshipMapping childRelationshipMapping : childRelationships) { this.copyChildRelationship(childRelationshipMapping.sourceRelationship, childRelationshipMapping.sourceContentType, - childRelationshipMapping.sourceField, copiedRelationships, - copiedContentTypes); + childRelationshipMapping.sourceField, copiedRelationshipsBySourceId, + copiedContentTypesBySourceId); } } HibernateUtil.closeAndCommitTransaction(); @@ -618,31 +618,33 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f Logger.info(this, "----------------------------------------------------------------------"); Logger.info(this, String.format(":::: Copying HTML Pages - but NOT their contents - to new Site '%s'", destinationSite.getHostname())); final PaginatedContentlets sourceContentlets = this.contentAPI.findContentletsPaginatedByHost(sourceSite, - Arrays.asList(BaseContentType.HTMLPAGE.getType()), null, this.SYSTEM_USER, + List.of(BaseContentType.HTMLPAGE.getType()), null, this.SYSTEM_USER, DONT_RESPECT_FRONTEND_ROLES); currentProgress = 70; progressIncrement = (95 - 70) / (double) sourceContentlets.size(); Logger.info(this, String.format("-> Copying %d HTML Pages", sourceContentlets.size())); for (final Contentlet sourceContent : sourceContentlets) { - processCopyOfContentlet(sourceContent, copyOptions, - destinationSite, contentMappingsBySourceId, folderMappingsBySourceId, - copiedContainersBySourceId, templatesMappingBySourceId, - contentsToCopyDependencies, copiedContentTypes); + if (null != sourceContent) { + this.processCopyOfContentlet(sourceContent, copyOptions, + destinationSite, copiedContentsBySourceId, copiedFoldersBySourceId, + copiedContainersBySourceId, copiedTemplatesBySourceId, + contentsWithRelationships, copiedContentTypesBySourceId); - currentProgress += progressIncrement; + currentProgress += progressIncrement; - this.siteCopyStatus.updateProgress((int) currentProgress); - if (contentCount % 100 == 0) { - HibernateUtil.closeAndCommitTransaction(); - HibernateUtil.startTransaction(); - } - contentCount++; + this.siteCopyStatus.updateProgress((int) currentProgress); + if (contentCount % 100 == 0) { + HibernateUtil.closeAndCommitTransaction(); + HibernateUtil.startTransaction(); + } + contentCount++; + } } // Copy contentlet dependencies - this.copyRelatedContentlets(contentsToCopyDependencies, - contentMappingsBySourceId, copiedRelationships, copyOptions); + this.copyRelatedContentlets(contentsWithRelationships, + copiedContentsBySourceId, copiedRelationshipsBySourceId, copyOptions); } // Option 2: Copy all content on site @@ -660,11 +662,11 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f Logger.info(this, "-> Copying simple contents first"); while (ite.hasNext()) { final Contentlet sourceContent = ite.next(); - if (!sourceContent.isHTMLPage()) { - processCopyOfContentlet(sourceContent, copyOptions, - destinationSite, contentMappingsBySourceId, folderMappingsBySourceId, - copiedContainersBySourceId, templatesMappingBySourceId, - contentsToCopyDependencies, copiedContentTypes); + if (null != sourceContent && !sourceContent.isHTMLPage()) { + this.processCopyOfContentlet(sourceContent, copyOptions, + destinationSite, copiedContentsBySourceId, copiedFoldersBySourceId, + copiedContainersBySourceId, copiedTemplatesBySourceId, + contentsWithRelationships, copiedContentTypesBySourceId); // Update progress ONLY if the record is processed currentProgress += progressIncrement; this.siteCopyStatus.updateProgress((int) currentProgress); @@ -683,22 +685,24 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f ite = sourceContentlets.iterator(); while (ite.hasNext()) { final Contentlet sourceContent = ite.next(); - processCopyOfContentlet(sourceContent, copyOptions, - destinationSite, contentMappingsBySourceId, folderMappingsBySourceId, - copiedContainersBySourceId, templatesMappingBySourceId, - contentsToCopyDependencies, copiedContentTypes); - currentProgress += progressIncrement; - siteCopyStatus.updateProgress((int) currentProgress); - if (contentCount % 100 == 0) { - HibernateUtil.closeAndCommitTransaction(); - HibernateUtil.startTransaction(); - } - contentCount++; + if (null != sourceContent && sourceContent.isHTMLPage()) { + this.processCopyOfContentlet(sourceContent, copyOptions, + destinationSite, copiedContentsBySourceId, copiedFoldersBySourceId, + copiedContainersBySourceId, copiedTemplatesBySourceId, + contentsWithRelationships, copiedContentTypesBySourceId); + currentProgress += progressIncrement; + siteCopyStatus.updateProgress((int) currentProgress); + if (contentCount % 100 == 0) { + HibernateUtil.closeAndCommitTransaction(); + HibernateUtil.startTransaction(); + } + contentCount++; + } } Logger.info(this, String.format("-> A total of %d contents have been copied", contentCount)); // Copy contentlet dependencies - this.copyRelatedContentlets(contentsToCopyDependencies, - contentMappingsBySourceId, copiedRelationships, copyOptions); + this.copyRelatedContentlets(contentsWithRelationships, + copiedContentsBySourceId, copiedRelationshipsBySourceId, copyOptions); } this.siteCopyStatus.updateProgress(95); @@ -975,63 +979,70 @@ private Folder copyFolder(final Folder sourceFolder, final Host destinationSite, * If the content to copy is a Content Page, then its multi-tree structure needs to be updated, * which involves updating the child references to point to the recently copied contentlets. *

- * - * @param sourceContent The {@link Contentlet} whose data will be copied. - * @param copyOptions The preferences selected by the user regarding what - * elements of the source site will be copied over to the new - * site. - * @param destinationSite The new {@link Host} object that will contain the - * information from the source site. - * @param copiedContentlets A {@link Map} containing the association between the - * Contentlet's Identifier in the source site and the - * Contentlet's Identifier in the destination site. This - * map keeps both the source and the destination - * {@link Contentlet} objects. - * @param copiedFolders A {@link Map} containing the association between the - * folder's Identifier from the source site with the folder's - * Identifier from the destination site. This map keeps both - * the source and the destination {@link Folder} objects. - * @param copiedContainers A {@link Map} that says what containerId from the source - * site has become what in the new copy-site - * @param copiedTemplates A {@link Map} that says what templateId from the source - * site has become what in the new copy-site - * @param contentletsWithRelationships The dependencies of the contentlet to copy. - * @param copiedContentTypes + * + * @param sourceContent The {@link Contentlet} whose data will be copied. + * @param copyOptions The preferences selected by the user regarding what + * elements of the source site will be copied over to the + * new site. + * @param destinationSite The new {@link Host} object that will contain the + * information from the source site. + * @param copiedContentletsBySourceId A {@link Map} containing the association between the + * Contentlet's Identifier in the source site and the + * Contentlet's Identifier in the destination site. This + * map keeps both the source and the destination + * {@link Contentlet} objects. + * @param copiedFoldersBySourceId A {@link Map} containing the association between the + * folder's Identifier from the source site with the + * folder's Identifier from the destination site. This map + * keeps both the source and the destination {@link Folder} + * objects. + * @param copiedContainersBySourceId A {@link Map} that says what containerId from the source + * site has become what in the new copy-site. + * @param copiedTemplatesBySourceId A {@link Map} that says what templateId from the source + * site has become what in the new copy-site. + * @param contentsWithRelationships The list of Contentlets that have other content related + * to them. + * @param copiedContentTypesBySourceId A {@link Map} containing the association between the + * Content Type's Identifier in the source site and the + * Content Type's Identifier in the destination site. This + * is relevant ONLY if the + * {@code FEATURE_FLAG_ENABLE_CONTENT_TYPE_COPY} property + * is enabled. */ private Contentlet processCopyOfContentlet(final Contentlet sourceContent, final HostCopyOptions copyOptions, final Host destinationSite, - final Map contentMappingsBySourceId, - final Map folderMappingsBySourceId, + final Map copiedContentletsBySourceId, + final Map copiedFoldersBySourceId, final Map copiedContainersBySourceId, - final Map templatesMappingBySourceId, - final List contentsToCopyDependencies, - final Map copiedContentTypes) { + final Map copiedTemplatesBySourceId, + final List contentsWithRelationships, + final Map copiedContentTypesBySourceId) { - //Since certain properties are modified here we're gonna use a defensive copy to avoid cache issue. + //Since certain properties are modified here we're going to use a defensive copy to avoid cache issue. final Contentlet sourceCopy = new Contentlet(sourceContent); Contentlet newContent = null; try { - if (contentMappingsBySourceId.containsKey(sourceCopy.getIdentifier())) { + if (copiedContentletsBySourceId.containsKey(sourceCopy.getIdentifier())) { // The content has already been copied Logger.debug(HostAssetsJobImpl.class,()->String.format("---> Content identified by `%s` has been copied already.", sourceCopy.getIdentifier())); - return contentMappingsBySourceId.get(sourceCopy.getIdentifier()).destinationContent; + return copiedContentletsBySourceId.get(sourceCopy.getIdentifier()).destinationContent; } sourceCopy.getMap().put(Contentlet.DONT_VALIDATE_ME, true); sourceCopy.getMap().put(Contentlet.DISABLE_WORKFLOW, true); sourceCopy.setLowIndexPriority(true); if (copyOptions.isCopyTemplatesAndContainers() && sourceCopy.isHTMLPage()) { - //If we're dealing with pages, need pass template mappings to the copyContentlet - //such method deals with all versions of the contentlet and it needs to know about the mapping info internally. - sourceCopy.getMap().put(Contentlet.TEMPLATE_MAPPINGS, templatesMappingBySourceId); + //If we're dealing with pages, we need to pass template mappings to the copyContentlet + //such a method deals with all versions of the contentlet, and it needs to know about the mapping info internally. + sourceCopy.getMap().put(Contentlet.TEMPLATE_MAPPINGS, copiedTemplatesBySourceId); } - final ContentType destinationContentType = Try.of(() -> copiedContentTypes.get(sourceCopy.getContentTypeId()).destinationContentType).getOrNull(); + final ContentType destinationContentType = Try.of(() -> copiedContentTypesBySourceId.get(sourceCopy.getContentTypeId()).destinationContentType).getOrNull(); if (InodeUtils.isSet(sourceCopy.getFolder()) && !sourceCopy.getFolder().equals(this.SYSTEM_FOLDER.getInode())) { // The source content has a folder assigned in the source Site we copy it to the // same destination folder final Folder sourceFolder = this.folderAPI.find(sourceCopy.getFolder(), this.SYSTEM_USER, DONT_RESPECT_FRONTEND_ROLES); - final Folder destinationFolder = folderMappingsBySourceId.get(sourceFolder.getInode()) != null ? folderMappingsBySourceId + final Folder destinationFolder = copiedFoldersBySourceId.get(sourceFolder.getInode()) != null ? copiedFoldersBySourceId .get(sourceFolder.getInode()).destinationFolder : null; if (!copyOptions.isCopyFolders()) { return null; @@ -1063,15 +1074,15 @@ private Contentlet processCopyOfContentlet(final Contentlet sourceContent, // Copy page-associated contentlets final List pageContents = APILocator.getMultiTreeAPI().getMultiTrees(sourceCopy.getIdentifier()); for (final MultiTree sourceMultiTree : pageContents) { - String newChild = sourceMultiTree.getChild(); + String newChild = sourceMultiTree.getContentlet(); // Update the child reference to point to the previously copied content - if (contentMappingsBySourceId.containsKey(sourceMultiTree.getChild())) { - newChild = contentMappingsBySourceId.get(sourceMultiTree.getChild()).destinationContent.getIdentifier(); + if (copiedContentletsBySourceId.containsKey(sourceMultiTree.getContentlet())) { + newChild = copiedContentletsBySourceId.get(sourceMultiTree.getContentlet()).destinationContent.getIdentifier(); } - String newContainer = sourceMultiTree.getParent2(); - if(copiedContainersBySourceId.containsKey(sourceMultiTree.getParent2())){ - newContainer = copiedContainersBySourceId.get(sourceMultiTree.getParent2()).getIdentifier(); + String newContainer = sourceMultiTree.getContainer(); + if(copiedContainersBySourceId.containsKey(sourceMultiTree.getContainer())){ + newContainer = copiedContainersBySourceId.get(sourceMultiTree.getContainer()).getIdentifier(); } final MultiTree multiTree = new MultiTree(newContent.getIdentifier(), @@ -1086,14 +1097,12 @@ private Contentlet processCopyOfContentlet(final Contentlet sourceContent, }// Pages are a big deal. - contentMappingsBySourceId.put(sourceCopy.getIdentifier(), new ContentMapping(sourceCopy, newContent)); + copiedContentletsBySourceId.put(sourceCopy.getIdentifier(), new ContentMapping(sourceCopy, newContent)); final Contentlet finalNewContent = newContent; Logger.debug(HostAssetsJobImpl.class,()->String.format("---> Re-Mapping content: Identifier `%s` now points to `%s`.", sourceCopy.getIdentifier(), finalNewContent .getIdentifier())); - if (doesRelatedContentExists(sourceCopy)) { - contentsToCopyDependencies.add(sourceCopy); - } + this.checkRelatedContentToCopy(sourceCopy, contentsWithRelationships); } catch (final Exception e) { Logger.error(this, String.format("An error occurred when copying content '%s' from Site '%s' to Site '%s'." + " The process will continue...", sourceCopy.getIdentifier(), sourceCopy.getHost(), @@ -1111,61 +1120,75 @@ private Contentlet processCopyOfContentlet(final Contentlet sourceContent, * the copied Contentlets to the new Relationship records as they reflect the new IDs for the * copied Content Types as well.

* - * @param contentsToCopyDependencies The list of {@link Contentlet} objects that are the - * parents of a given relationship. - * @param contentMappingsBySourceId The {@link Map} containing the association between the - * contentlet's Identifier from the source site and the - * contentlet's Identifier from the destination site. This - * map keeps both the source and the destination - * {@link Contentlet} objects. - * @param copiedRelationships The {@link Map} containing the copied Relationships from - * the source Site. This way, the copied Contentlets will - * be saved just like the original ones. - * @param copyOptions The {@link HostCopyOptions} object containing what objects - * from the source Site must be copied to the destination - * Site. + * @param contentsWithRelationships The list of {@link Contentlet} objects that are the + * parents of a given relationship. + * @param copiedContentsBySourceId The {@link Map} containing the association between the + * contentlet's Identifier from the source site and the + * contentlet's Identifier from the destination site. This + * map keeps both the source and the destination + * {@link Contentlet} objects. + * @param copiedRelationshipsBySourceId The {@link Map} containing the copied Relationships + * from the source Site. This way, the copied Contentlets + * will be saved just like the original ones. + * @param copyOptions The {@link HostCopyOptions} object containing what + * objects from the source Site must be copied to the + * destination Site. * * @throws DotDataException An error occurred when updating records in the database. * @throws DotSecurityException The {@link User} accessing the APIs doesn't have the required * permissions to perform this action. */ - private void copyRelatedContentlets(final List contentsToCopyDependencies, - final Map contentMappingsBySourceId, final Map copiedRelationships, final HostCopyOptions copyOptions) throws DotDataException, DotSecurityException { - for (final Contentlet sourceContent : contentsToCopyDependencies) { - final Contentlet destinationContent = contentMappingsBySourceId.get(sourceContent.getIdentifier()).destinationContent; + private void copyRelatedContentlets(final List contentsWithRelationships, + final Map copiedContentsBySourceId, final Map copiedRelationshipsBySourceId, final HostCopyOptions copyOptions) throws DotDataException, DotSecurityException { + for (final Contentlet sourceContent : contentsWithRelationships) { + boolean isDestinationContentInSystemHost = false; + Contentlet destinationContent; + if (Host.SYSTEM_HOST.equals(sourceContent.getHost())) { + destinationContent = sourceContent; + isDestinationContentInSystemHost = true; + } else { + destinationContent = copiedContentsBySourceId.get(sourceContent.getIdentifier()).destinationContent; + } final Map> contentRelationships = new HashMap<>(); - final List rels = this.relationshipAPI.byContentType(sourceContent.getContentType()); - for (final Relationship r : rels) { - if (!contentRelationships.containsKey(r)) { - contentRelationships.put(r, new ArrayList<>()); + final List relationshipsInContentType = this.relationshipAPI.byContentType(sourceContent.getContentType()); + for (final Relationship relationship : relationshipsInContentType) { + if (!contentRelationships.containsKey(relationship)) { + contentRelationships.put(relationship, new ArrayList<>()); } - final List cons = this.contentAPI.getRelatedContent(sourceContent, r, this.SYSTEM_USER, RESPECT_FRONTEND_ROLES); + final List relatedContentlets = + this.contentAPI.getRelatedContent(sourceContent, relationship, this.SYSTEM_USER, RESPECT_FRONTEND_ROLES); List records = new ArrayList<>(); - for (final Contentlet c : cons) { - records = contentRelationships.get(r); - if (UtilMethods.isSet(contentMappingsBySourceId.get(c.getIdentifier()))) { - final Tree relationshipData = TreeFactory.getTree(c.getIdentifier(), sourceContent.getIdentifier(), r.getRelationTypeValue()); + for (final Contentlet relatedContentlet : relatedContentlets) { + records = contentRelationships.get(relationship); + if (UtilMethods.isSet(copiedContentsBySourceId.get(relatedContentlet.getIdentifier()))) { + final Tree relationshipData = + TreeFactory.getTree(relatedContentlet.getIdentifier(), + sourceContent.getIdentifier(), relationship.getRelationTypeValue()); // In self-related Relationships, we need to make sure that the related Contentlet is NOT the // parent in the relationship in order to NOT create a duplicate relationship - if (this.relationshipAPI.sameParentAndChild(r) && UtilMethods.isSet(relationshipData.getParent())) { + if (this.relationshipAPI.sameParentAndChild(relationship) && UtilMethods.isSet(relationshipData.getParent())) { continue; } - records.add(contentMappingsBySourceId.get(c.getIdentifier()).destinationContent); + records.add(copiedContentsBySourceId.get(relatedContentlet.getIdentifier()).destinationContent); + if (isDestinationContentInSystemHost) { + records.add(relatedContentlet); + } } else { - records.add(c); + records.add(relatedContentlet); } } if (!records.isEmpty()) { ContentletRelationshipRecords related; - if (!Config.getBooleanProperty("FEATURE_FLAG_ENABLE_CONTENT_TYPE_COPY", false) || !copyOptions.isCopyContentTypes()) { + if (!CONTENT_TYPE_COPY_FLAG.get() || !copyOptions.isCopyContentTypes()) { related = new ContentletRelationships( - destinationContent).new ContentletRelationshipRecords(r, true); + destinationContent).new ContentletRelationshipRecords(relationship, true); } else { - if (!copiedRelationships.containsKey(r.getInode())) { + if (!copiedRelationshipsBySourceId.containsKey(relationship.getInode())) { continue; } - final Relationship copiedRelationship = copiedRelationships.get(r.getInode()).destinationRelationship; - related = new ContentletRelationships(destinationContent).new ContentletRelationshipRecords(copiedRelationship, true); + final Relationship copiedRelationship = copiedRelationshipsBySourceId.get(relationship.getInode()).destinationRelationship; + related = new ContentletRelationships(destinationContent) + .new ContentletRelationshipRecords(copiedRelationship, true); } related.setRecords(records); this.contentAPI.relateContent(destinationContent, related, this.SYSTEM_USER, DONT_RESPECT_FRONTEND_ROLES); @@ -1180,28 +1203,36 @@ private void copyRelatedContentlets(final List contentsToCopyDepende * in mind that, in the case of self-related Content Types, the parent and child point to each * other, so the Tree data must be accessed and read accordingly. * - * @param contentlet The Contentlet whose relationships will be verified. - * - * @return If the Contentlet is the parent of one or more Contentlets, returns {@code true}. - * Otherwise, returns {@code false}. + * @param contentlet The Contentlet whose relationships will be verified. + * @param contentletsWithRelationships The list of Contentlets that have relationships, and + * need to be processed later. * * @throws DotDataException An error occurred when accessing the data source. * @throws DotSecurityException The specified user does not have the required permissions to * perform this action. */ - private boolean doesRelatedContentExists(final Contentlet contentlet) throws DotDataException, DotSecurityException{ - if(contentlet == null) { - return false; + private void checkRelatedContentToCopy(final Contentlet contentlet, + final List contentletsWithRelationships) throws DotDataException, DotSecurityException { + if (contentlet == null) { + return; + } + final List relationshipsByCT = + this.relationshipAPI.byContentType(contentlet.getContentType()); + for (final Relationship relationship : relationshipsByCT) { + final List relatedContents = this.contentAPI.getRelatedContent(contentlet, + relationship, this.SYSTEM_USER, DONT_RESPECT_FRONTEND_ROLES); + if (COPY_RELATED_CONTENT_IN_SYSTEM_HOST_CONTENTS_FLAG.get()) { + for (final Contentlet relatedContent : relatedContents) { + if (Host.SYSTEM_HOST.equals(relatedContent.getHost())) { + contentletsWithRelationships.add(relatedContent); + } + } + } + if (!relatedContents.isEmpty() && this.relationshipAPI.isParent(relationship, + contentlet.getContentType())) { + contentletsWithRelationships.add(contentlet); + } } - - final List rels = APILocator.getRelationshipAPI().byContentType(contentlet.getContentType()); - for (final Relationship r : rels) { - final List cons = this.contentAPI.getRelatedContent(contentlet, r, this.SYSTEM_USER, DONT_RESPECT_FRONTEND_ROLES); - if(cons.size() > 0 && APILocator.getRelationshipAPI().isParent(r, contentlet.getContentType())){ - return true; - } - } - return false; } @Override