Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@
*
* @since 8.1.0
*/
public record IncrementalWriteConfig(String hashKeyName, String timestampKeyName, boolean canonicalizeJson,
Consumer<DocumentWriteOperation[]> skippedDocumentsConsumer,
String[] jsonExclusions, String[] xmlExclusions, Map<String, String> xmlNamespaces,
String schemaName, String viewName) {
public class IncrementalWriteConfig {

private final String hashKeyName;
private final String timestampKeyName;
private final boolean canonicalizeJson;
private final Consumer<DocumentWriteOperation[]> skippedDocumentsConsumer;
private final String[] jsonExclusions;
private final String[] xmlExclusions;
private final Map<String, String> xmlNamespaces;
private final String schemaName;
private final String viewName;

public IncrementalWriteConfig(String hashKeyName, String timestampKeyName, boolean canonicalizeJson,
Consumer<DocumentWriteOperation[]> skippedDocumentsConsumer,
Expand All @@ -34,11 +41,39 @@ public IncrementalWriteConfig(String hashKeyName, String timestampKeyName, boole
this.viewName = viewName;
}

public String getHashKeyName() {
return hashKeyName;
}

public String getTimestampKeyName() {
return timestampKeyName;
}

public boolean isCanonicalizeJson() {
return canonicalizeJson;
}

public Consumer<DocumentWriteOperation[]> getSkippedDocumentsConsumer() {
return skippedDocumentsConsumer;
}

public String[] getJsonExclusions() {
return jsonExclusions;
}
Comment on lines +60 to +62
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The array fields jsonExclusions and xmlExclusions should be defensively copied in their getters to prevent external modification. Unlike Java records which automatically provide defensive copies for arrays, the current implementation allows callers to modify the internal array state. Consider returning Arrays.copyOf(jsonExclusions, jsonExclusions.length) or jsonExclusions != null ? jsonExclusions.clone() : null instead of returning the array directly.

Copilot uses AI. Check for mistakes.

@Override
public Map<String, String> xmlNamespaces() {
public String[] getXmlExclusions() {
return xmlExclusions;
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The array field xmlExclusions should be defensively copied in its getter to prevent external modification. Unlike Java records which automatically provide defensive copies for arrays, the current implementation allows callers to modify the internal array state. Consider returning Arrays.copyOf(xmlExclusions, xmlExclusions.length) or xmlExclusions != null ? xmlExclusions.clone() : null instead of returning the array directly.

Suggested change
return xmlExclusions;
return xmlExclusions != null ? xmlExclusions.clone() : null;

Copilot uses AI. Check for mistakes.
}

public Map<String, String> getXmlNamespaces() {
return xmlNamespaces != null ? xmlNamespaces : Collections.emptyMap();
}

public String getSchemaName() {
return schemaName;
}

public String getViewName() {
return viewName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -242,19 +242,19 @@ protected final DocumentWriteSet filterDocuments(Context context, Function<Strin

if (existingHash != null) {
if (!existingHash.equals(contentHash)) {
newWriteSet.add(addHashToMetadata(doc, config.hashKeyName(), contentHash, config.timestampKeyName(), timestamp));
} else if (config.skippedDocumentsConsumer() != null) {
newWriteSet.add(addHashToMetadata(doc, config.getHashKeyName(), contentHash, config.getTimestampKeyName(), timestamp));
} else if (config.getSkippedDocumentsConsumer() != null) {
skippedDocuments.add(doc);
} else {
// No consumer, so skip the document silently.
}
} else {
newWriteSet.add(addHashToMetadata(doc, config.hashKeyName(), contentHash, config.timestampKeyName(), timestamp));
newWriteSet.add(addHashToMetadata(doc, config.getHashKeyName(), contentHash, config.getTimestampKeyName(), timestamp));
}
}

if (!skippedDocuments.isEmpty() && config.skippedDocumentsConsumer() != null) {
config.skippedDocumentsConsumer().accept(skippedDocuments.toArray(new DocumentWriteOperation[0]));
if (!skippedDocuments.isEmpty() && config.getSkippedDocumentsConsumer() != null) {
config.getSkippedDocumentsConsumer().accept(skippedDocuments.toArray(new DocumentWriteOperation[0]));
}

return newWriteSet;
Expand All @@ -271,11 +271,11 @@ private String serializeContent(DocumentWriteOperation doc) {
format = baseHandle.getFormat();
}

if (config.canonicalizeJson() && (Format.JSON.equals(format) || isPossiblyJsonContent(content))) {
if (config.isCanonicalizeJson() && (Format.JSON.equals(format) || isPossiblyJsonContent(content))) {
JsonCanonicalizer jc;
try {
if (config.jsonExclusions() != null && config.jsonExclusions().length > 0) {
content = ContentExclusionUtil.applyJsonExclusions(doc.getUri(), content, config.jsonExclusions());
if (config.getJsonExclusions() != null && config.getJsonExclusions().length > 0) {
content = ContentExclusionUtil.applyJsonExclusions(doc.getUri(), content, config.getJsonExclusions());
}
jc = new JsonCanonicalizer(content);
return jc.getEncodedString();
Expand All @@ -286,9 +286,9 @@ private String serializeContent(DocumentWriteOperation doc) {
logger.warn("Unable to canonicalize JSON content for URI {}, using original content for hashing; cause: {}",
doc.getUri(), e.getMessage());
}
} else if (config.xmlExclusions() != null && config.xmlExclusions().length > 0) {
} else if (config.getXmlExclusions() != null && config.getXmlExclusions().length > 0) {
try {
content = ContentExclusionUtil.applyXmlExclusions(doc.getUri(), content, config.xmlNamespaces(), config.xmlExclusions());
content = ContentExclusionUtil.applyXmlExclusions(doc.getUri(), content, config.getXmlNamespaces(), config.getXmlExclusions());
} catch (Exception e) {
logger.warn("Unable to apply XML exclusions for URI {}, using original content for hashing; cause: {}",
doc.getUri(), e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public DocumentWriteSet apply(Context context) {
Map<String, Long> existingHashes = new RowTemplate(context.getDatabaseClient()).query(op ->
op.fromLexicons(Map.of(
"uri", op.cts.uriReference(),
"hash", op.cts.fieldReference(getConfig().hashKeyName())
"hash", op.cts.fieldReference(getConfig().getHashKeyName())
)).where(
op.cts.documentQuery(op.xs.stringSeq(uris))
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ public DocumentWriteSet apply(Context context) {

try {
Map<String, Long> existingHashes = new RowTemplate(context.getDatabaseClient()).query(op ->
op.fromView(getConfig().schemaName(), getConfig().viewName(), "")
op.fromView(getConfig().getSchemaName(), getConfig().getViewName(), "")
.where(op.cts.documentQuery(op.xs.stringSeq(uris)))
,
rows -> {
Map<String, Long> map = new HashMap<>();
rows.forEach(row -> {
String uri = row.getString("uri");
String hashString = row.getString(getConfig().hashKeyName());
String hashString = row.getString(getConfig().getHashKeyName());
if (hashString != null && !hashString.isEmpty()) {
long existingHash = Long.parseUnsignedLong(hashString);
map.put(uri, existingHash);
Expand All @@ -51,7 +51,8 @@ public DocumentWriteSet apply(Context context) {

return filterDocuments(context, uri -> existingHashes.get(uri));
} catch (FailedRequestException e) {
String message = "Unable to query for existing incremental write hashes from view " + getConfig().schemaName() + "." + getConfig().viewName() + "; cause: " + e.getMessage();
String message = "Unable to query for existing incremental write hashes from view "
+ getConfig().getSchemaName() + "." + getConfig().getViewName() + "; cause: " + e.getMessage();
throw new FailedRequestException(message, e.getFailedRequest());
}
}
Expand Down