Skip to content

Commit fec8286

Browse files
committed
#33661 : Added config property to log message for binary field validation
1 parent b9dc071 commit fec8286

File tree

2 files changed

+133
-13
lines changed

2 files changed

+133
-13
lines changed

dotCMS/src/enterprise/java/com/dotcms/enterprise/priv/HostAssetsJobImpl.java

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,17 @@ public class ESContentletAPIImpl implements ContentletAPI {
289289

290290
private final static Lazy<Boolean> SET_DEFAULT_VALUES = Lazy.of(()-> Config.getBooleanProperty("CONTENT_API_SET_DEFAULT_VALUES", true));
291291

292+
/**
293+
* Configuration property to control whether to skip throwing FileAssetValidationException
294+
* when a file asset is missing its binary field. When set to true, a debug log message
295+
* will be written instead of throwing an exception. This can be useful during migration
296+
* or bulk operations where some file assets may have missing binaries that need to be
297+
* handled gracefully. Default: false (throw exception)
298+
*/
299+
public static final String SKIP_FILE_ASSET_BINARY_VALIDATION = "SKIP_FILE_ASSET_BINARY_VALIDATION";
300+
301+
private static final Lazy<Boolean> SKIP_FILE_ASSET_BINARY_VALIDATION_FLAG = Lazy.of(() ->
302+
Config.getBooleanProperty(SKIP_FILE_ASSET_BINARY_VALIDATION, false));
292303

293304
private final Lazy<UniqueFieldValidationStrategyResolver> uniqueFieldValidationStrategyResolver;
294305

@@ -6024,10 +6035,18 @@ private boolean addOrUpdateContentletIdentifier(final Contentlet contentlet,
60246035
final String binaryNode =
60256036
contentletRaw.getInode() != null ? contentletRaw.getInode()
60266037
: BLANK;
6027-
throw new FileAssetValidationException(
6028-
"Unable to validate field: " + FileAssetAPI.BINARY_FIELD
6029-
+ " identifier: " + binaryIdentifier
6030-
+ " inode: " + binaryNode);
6038+
6039+
if (SKIP_FILE_ASSET_BINARY_VALIDATION_FLAG.get()) {
6040+
Logger.debug(this,
6041+
"Missing binary field " + FileAssetAPI.BINARY_FIELD
6042+
+ " for identifier: " + binaryIdentifier
6043+
+ ", inode: " + binaryNode);
6044+
} else {
6045+
throw new FileAssetValidationException(
6046+
"Unable to validate field: " + FileAssetAPI.BINARY_FIELD
6047+
+ " identifier: " + binaryIdentifier
6048+
+ " inode: " + binaryNode);
6049+
}
60316050
} else {
60326051
//We no longer use the old BinaryField to recover the file name.
60336052
//From now on we'll recover such value from the field "fileName" presented on the screen.

0 commit comments

Comments
 (0)