@@ -310,6 +310,7 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f
310310 final List <Contentlet > contentsWithRelationships = new ArrayList <>();
311311 final Map <String , ContentTypeMapping > copiedContentTypesBySourceId = new HashMap <>();
312312 final Map <String , RelationshipMapping > copiedRelationshipsBySourceId = new HashMap <>();
313+ final List <FailedContentInfo > failedContents = new ArrayList <>();
313314 if (copyOptions .isCopyTemplatesAndContainers ()) {
314315 Logger .info (this , "----------------------------------------------------------------------" );
315316 Logger .info (this , String .format (":::: Copying Templates and Containers to new Site '%s'" , destinationSite .getHostname ()));
@@ -359,7 +360,8 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f
359360 copiedContainersBySourceId ,
360361 copiedTemplatesBySourceId ,
361362 contentsWithRelationships ,
362- copiedContentTypesBySourceId
363+ copiedContentTypesBySourceId ,
364+ failedContents
363365 )
364366 );
365367 }
@@ -641,7 +643,7 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f
641643 this .processCopyOfContentlet (sourceContent , copyOptions ,
642644 sourceSite , destinationSite , copiedContentsBySourceId , copiedFoldersBySourceId ,
643645 copiedContainersBySourceId , copiedTemplatesBySourceId ,
644- contentsWithRelationships , copiedContentTypesBySourceId );
646+ contentsWithRelationships , copiedContentTypesBySourceId , failedContents );
645647
646648 currentProgress += progressIncrement ;
647649 contentCount ++;
@@ -717,7 +719,7 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f
717719 this .processCopyOfContentlet (sourceContent , copyOptions ,
718720 sourceSite , destinationSite , copiedContentsBySourceId , copiedFoldersBySourceId ,
719721 copiedContainersBySourceId , copiedTemplatesBySourceId ,
720- contentsWithRelationships , copiedContentTypesBySourceId );
722+ contentsWithRelationships , copiedContentTypesBySourceId , failedContents );
721723 currentProgress += progressIncrement ;
722724 contentCount ++;
723725 simpleContentCount ++;
@@ -763,7 +765,7 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f
763765 this .processCopyOfContentlet (sourceContent , copyOptions ,
764766 sourceSite , destinationSite , copiedContentsBySourceId , copiedFoldersBySourceId ,
765767 copiedContainersBySourceId , copiedTemplatesBySourceId ,
766- contentsWithRelationships , copiedContentTypesBySourceId );
768+ contentsWithRelationships , copiedContentTypesBySourceId , failedContents );
767769 currentProgress += progressIncrement ;
768770 contentCount ++;
769771
@@ -825,6 +827,35 @@ private void copySiteAssets(final Host sourceSite, final Host destinationSite, f
825827 }
826828 siteCopyStatus .updateProgress (100 );
827829
830+ // Print summary of failed contentlets
831+ if (!failedContents .isEmpty ()) {
832+ Logger .info (this , "======================================================================" );
833+ Logger .info (this , String .format (":::: Site Copy Summary - %d contentlet(s) failed to copy from '%s' to '%s'" ,
834+ failedContents .size (), sourceSite .getHostname (), destinationSite .getHostname ()));
835+ Logger .info (this , "----------------------------------------------------------------------" );
836+
837+ // Group failures by content type for better readability
838+ final Map <String , List <FailedContentInfo >> failuresByType = new HashMap <>();
839+ for (final FailedContentInfo failure : failedContents ) {
840+ failuresByType .computeIfAbsent (failure .contentType , k -> new ArrayList <>()).add (failure );
841+ }
842+
843+ // Print grouped failures
844+ for (final Map .Entry <String , List <FailedContentInfo >> entry : failuresByType .entrySet ()) {
845+ final String contentType = entry .getKey ();
846+ final List <FailedContentInfo > failures = entry .getValue ();
847+ Logger .info (this , String .format ("-> Content Type: %s (%d failures)" , contentType , failures .size ()));
848+ for (final FailedContentInfo failure : failures ) {
849+ Logger .info (this , String .format (" - ID: %s, Host: %s, Reason: %s" ,
850+ failure .sourceIdentifier , failure .host , failure .reason ));
851+ }
852+ }
853+ Logger .info (this , "======================================================================" );
854+ } else {
855+ Logger .info (this , String .format (":::: All contentlets copied successfully from '%s' to '%s'" ,
856+ sourceSite .getHostname (), destinationSite .getHostname ()));
857+ }
858+
828859 final String destinationSiteIdentifier = destinationSite .getIdentifier ();
829860 HibernateUtil .addCommitListener ("Host" +destinationSiteIdentifier , ()-> triggerEvents (destinationSiteIdentifier ));
830861 HibernateUtil .closeAndCommitTransaction ();
@@ -1117,7 +1148,8 @@ private Contentlet processCopyOfContentlet(final Contentlet sourceContent,
11171148 final Map <String , Container > copiedContainersBySourceId ,
11181149 final Map <String , HTMLPageAssetAPI .TemplateContainersReMap > copiedTemplatesBySourceId ,
11191150 final List <Contentlet > contentsWithRelationships ,
1120- final Map <String , ContentTypeMapping > copiedContentTypesBySourceId ) {
1151+ final Map <String , ContentTypeMapping > copiedContentTypesBySourceId ,
1152+ final List <FailedContentInfo > failedContents ) {
11211153
11221154 //Since certain properties are modified here we're going to use a defensive copy to avoid cache issue.
11231155 final Contentlet sourceCopy = new Contentlet (sourceContent );
@@ -1146,6 +1178,7 @@ private Contentlet processCopyOfContentlet(final Contentlet sourceContent,
11461178 final Folder destinationFolder = copiedFoldersBySourceId .get (sourceFolder .getInode ()) != null ? copiedFoldersBySourceId
11471179 .get (sourceFolder .getInode ()).destinationFolder : null ;
11481180 if (!copyOptions .isCopyFolders ()) {
1181+ Logger .debug (HostAssetsJobImpl .class , () -> String .format ("Source content '%s' in a folder, skipped because folders are not included in the copy" , sourceCopy .getIdentifier ()));
11491182 return null ;
11501183 }
11511184
@@ -1205,16 +1238,66 @@ private Contentlet processCopyOfContentlet(final Contentlet sourceContent,
12051238
12061239 }// Pages are a big deal.
12071240
1208- copiedContentletsBySourceId .put (sourceCopy .getIdentifier (), new ContentMapping (sourceCopy , newContent ));
1209- final Contentlet finalNewContent = newContent ;
1210- Logger .debug (HostAssetsJobImpl .class ,()->String .format ("---> Re-Mapping content: Identifier `%s` now points to `%s`." , sourceCopy .getIdentifier (), finalNewContent
1211- .getIdentifier ()));
1241+ if (newContent != null ) {
1242+ copiedContentletsBySourceId .put (sourceCopy .getIdentifier (), new ContentMapping (sourceCopy , newContent ));
1243+ final Contentlet finalNewContent = newContent ;
1244+
1245+ // Verify the contentlet was created in the database
1246+ try {
1247+ final Contentlet dbContentlet = contentAPI .findInDb (finalNewContent .getInode ()).orElse (null );
1248+ if (dbContentlet == null ) {
1249+ Logger .warn (this , String .format (
1250+ "Copied contentlet not found in database - ID: %s, Inode: %s, Content Type: %s" ,
1251+ finalNewContent .getIdentifier (),
1252+ finalNewContent .getInode (),
1253+ finalNewContent .getContentType ().variable ()
1254+ ));
1255+ failedContents .add (new FailedContentInfo (
1256+ sourceCopy .getIdentifier (),
1257+ sourceCopy .getContentType ().variable (),
1258+ sourceCopy .getHost (),
1259+ "Copy operation result identifier not found in the database"
1260+ ));
1261+ }
1262+ } catch (final Exception e ) {
1263+ Logger .warn (this , String .format (
1264+ "Failed to verify contentlet in database - ID: %s, Inode: %s, Content Type: %s" ,
1265+ finalNewContent .getIdentifier (),
1266+ finalNewContent .getInode (),
1267+ finalNewContent .getContentType ().variable ()
1268+ ), e );
1269+ }
1270+
1271+ Logger .debug (HostAssetsJobImpl .class ,()->String .format ("---> Re-Mapping content: Identifier `%s` now points to `%s`." , sourceCopy .getIdentifier (), finalNewContent
1272+ .getIdentifier ()));
1273+ } else {
1274+ final String reason = "Copy operation returned null" ;
1275+ Logger .warn (this , String .format (
1276+ "Failed to copy contentlet - Source ID: %s, Content Type: %s, Host: %s" ,
1277+ sourceCopy .getIdentifier (),
1278+ sourceCopy .getContentType ().variable (),
1279+ sourceCopy .getHost ()
1280+ ));
1281+ failedContents .add (new FailedContentInfo (
1282+ sourceCopy .getIdentifier (),
1283+ sourceCopy .getContentType ().variable (),
1284+ sourceCopy .getHost (),
1285+ reason
1286+ ));
1287+ }
12121288
12131289 this .checkRelatedContentToCopy (sourceCopy , contentsWithRelationships , destinationSite );
12141290 } catch (final Exception e ) {
1291+ final String errorMessage = e .getMessage () != null ? e .getMessage () : e .getClass ().getSimpleName ();
12151292 Logger .error (this , String .format ("An error occurred when copying content '%s' from Site '%s' to Site '%s'." +
12161293 " The process will continue..." , sourceCopy .getIdentifier (), sourceCopy .getHost (),
12171294 destinationSite .getHostname ()), e );
1295+ failedContents .add (new FailedContentInfo (
1296+ sourceCopy .getIdentifier (),
1297+ sourceCopy .getContentType ().variable (),
1298+ sourceCopy .getHost (),
1299+ errorMessage
1300+ ));
12181301 }
12191302 return newContent ;
12201303 }
@@ -1481,6 +1564,24 @@ public RelationshipMapping(final Relationship sourceRelationship,
14811564
14821565 }
14831566
1567+ /**
1568+ * Tracks information about contentlets that failed to copy during the site copy operation.
1569+ */
1570+ private static class FailedContentInfo {
1571+ final String sourceIdentifier ;
1572+ final String contentType ;
1573+ final String host ;
1574+ final String reason ;
1575+
1576+ public FailedContentInfo (final String sourceIdentifier , final String contentType ,
1577+ final String host , final String reason ) {
1578+ this .sourceIdentifier = sourceIdentifier ;
1579+ this .contentType = contentType ;
1580+ this .host = host ;
1581+ this .reason = reason ;
1582+ }
1583+ }
1584+
14841585 /**
14851586 * Validates whether a contentlet referenced in a MultiTree entry can be used in the
14861587 * destination site. This method checks if the contentlet exists and is accessible, and
0 commit comments