diff --git a/src/main/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidator.java b/src/main/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidator.java index 9b4f017..b15af05 100644 --- a/src/main/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidator.java +++ b/src/main/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidator.java @@ -16,64 +16,45 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Queue; import java.util.Set; -import java.util.regex.Pattern; import javax.jcr.RepositoryException; -import javax.jcr.ValueFactory; import org.apache.jackrabbit.spi.Name; -import org.apache.jackrabbit.spi.NameFactory; -import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.apache.jackrabbit.util.Text; -import org.apache.jackrabbit.value.ValueFactoryImpl; import org.apache.jackrabbit.vault.util.DocViewNode2; import org.apache.jackrabbit.vault.validation.spi.DocumentViewXmlValidator; import org.apache.jackrabbit.vault.validation.spi.NodeContext; import org.apache.jackrabbit.vault.validation.spi.ValidationMessage; import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity; -import org.apache.sling.api.SlingConstants; -import org.apache.sling.jcr.resource.api.JcrResourceConstants; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.day.cq.commons.jcr.JcrConstants; import com.day.cq.wcm.api.NameConstants; public class AemReplicationMetadataValidator implements DocumentViewXmlValidator { - static final NameFactory NAME_FACTORY = NameFactoryImpl.getInstance(); - static final ValueFactory VALUE_FACTORY = ValueFactoryImpl.getInstance(); - static final String CQ_NAMESPACE_URI = "http://www.day.com/jcr/cq/1.0"; // no constant defined in https://developer.adobe.com/experience-manager/reference-materials/6-5/javadoc/constant-values.html - - private static final String NT_CQ_PAGE_CONTENT = "cq:PageContent"; private static final Name NAME_JCR_CONTENT = org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CONTENT; - private static final Name NAME_SLING_RESOURCETYPE = NAME_FACTORY.create(JcrResourceConstants.SLING_NAMESPACE_URI, SlingConstants.PROPERTY_RESOURCE_TYPE); - private static final Logger LOGGER = LoggerFactory.getLogger(AemReplicationMetadataValidator.class); private final @NotNull ValidationMessageSeverity validationMessageSeverity; - private final @NotNull Map includedNodePathsPatternsAndTypes; - private final @NotNull Map excludedNodePathsPatternsAndTypes; + private final @NotNull Collection includedTypesSettings; + private final @NotNull Collection excludedTypesSettings; private final boolean strictLastModificationCheck; private final @NotNull Set<@NotNull String> agentNames; private Queue relevantNodeMetadata = Collections.asLifoQueue(new ArrayDeque<>()); - public AemReplicationMetadataValidator(@NotNull ValidationMessageSeverity validationMessageSeverity, @NotNull Map includedNodePathsPatternsAndTypes, - @NotNull Map excludedNodePathsPatternsAndTypes, boolean strictLastModificationDateCheck, @NotNull Set<@NotNull String> agentNames) { + public AemReplicationMetadataValidator(@NotNull ValidationMessageSeverity validationMessageSeverity, @NotNull Collection includedTypesSettings, + @NotNull Collection excludedTypesSettings, boolean strictLastModificationDateCheck, @NotNull Set<@NotNull String> agentNames) { this.validationMessageSeverity = validationMessageSeverity; - this.includedNodePathsPatternsAndTypes = includedNodePathsPatternsAndTypes; - this.excludedNodePathsPatternsAndTypes = excludedNodePathsPatternsAndTypes; + this.includedTypesSettings = includedTypesSettings; + this.excludedTypesSettings = excludedTypesSettings; this.strictLastModificationCheck = strictLastModificationDateCheck; this.agentNames = agentNames; - - LOGGER.error("Map: {}", includedNodePathsPatternsAndTypes); } @Nullable @@ -96,13 +77,13 @@ private Optional getNodeMetadata(@NotNull String nodePath, @NotNul } // first check includes, then excludes, first match returning relevant metadata wins boolean isExclude = false; - Optional newMetadata = includedNodePathsPatternsAndTypes.entrySet().stream() + Optional newMetadata = includedTypesSettings.stream() .map(e -> getNodeMetadata(false, nodePath, node, e)) .filter(Optional::isPresent) // only interested in first result returning new metadata .map(Optional::get) .findFirst(); if (!newMetadata.isPresent()) { - newMetadata = excludedNodePathsPatternsAndTypes.entrySet().stream() + newMetadata = excludedTypesSettings.stream() .map(e -> getNodeMetadata(true, nodePath, node, e)) .filter(Optional::isPresent) // only interested in first result returning new metadata .map(Optional::get) @@ -116,16 +97,13 @@ private Optional getNodeMetadata(@NotNull String nodePath, @NotNul } - private Optional getNodeMetadata(boolean isExclude, @NotNull String nodePath, @NotNull DocViewNode2 node, Entry patternAndType) { - if (!patternAndType.getKey().matcher(nodePath).matches()) { - return Optional.empty(); - } - NodeMetadata currentMetadata; - boolean isNodeRelevant = isRelevantNodeType(node, patternAndType.getValue()); - if (!isNodeRelevant) { + private Optional getNodeMetadata(boolean isExclude, @NotNull String nodePath, @NotNull DocViewNode2 node, @NotNull TypeSettings typeSettings) { + if (!typeSettings.matches(nodePath, node)) { return Optional.empty(); } else { - if (NameConstants.NT_PAGE.equals(patternAndType.getValue()) || NameConstants.NT_TEMPLATE.equals(patternAndType.getValue())) { + String actualPrimaryType = node.getPrimaryType().orElse(""); + NodeMetadata currentMetadata; + if (NameConstants.NT_PAGE.equals(actualPrimaryType) || NameConstants.NT_TEMPLATE.equals(actualPrimaryType)) { LOGGER.debug("Waiting for jcr:content below {}", nodePath); currentMetadata = new NodeMetadata(isExclude, nodePath + "/" + NameConstants.NN_CONTENT, true); relevantNodeMetadata.add(currentMetadata); @@ -138,15 +116,6 @@ private Optional getNodeMetadata(boolean isExclude, @NotNull Strin } } - private static boolean isRelevantNodeType(DocViewNode2 node, String type) { - boolean isNodeRelevant = node.getPrimaryType().equals(Optional.of(type)); - // if node type == nt:unstructured, evaluate sling:resourceType instead - if (!isNodeRelevant && (node.getPrimaryType().equals(Optional.of(JcrConstants.NT_UNSTRUCTURED)) || node.getPrimaryType().equals(Optional.of(NT_CQ_PAGE_CONTENT)))) { - isNodeRelevant = node.getPropertyValue(NAME_SLING_RESOURCETYPE).equals(Optional.of(type)); - } - return isNodeRelevant; - } - @Override @Nullable public Collection validate(@NotNull DocViewNode2 node, @NotNull NodeContext nodeContext, boolean isRoot) { diff --git a/src/main/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidatorFactory.java b/src/main/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidatorFactory.java index 040cd53..34a6090 100644 --- a/src/main/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidatorFactory.java +++ b/src/main/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidatorFactory.java @@ -12,11 +12,10 @@ */ package biz.netcentric.filevault.validator; -import java.util.AbstractMap; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -42,49 +41,49 @@ public class AemReplicationMetadataValidatorFactory implements ValidatorFactory private static final String OPTION_STRICT_LAST_MODIFICATION_CHECK = "strictLastModificationDateCheck"; private static final String OPTION_AGENT_NAMES = "agentNames"; private static final @NotNull Set<@NotNull String> DEFAULT_AGENT_NAMES = Collections.singleton(ReplicationMetadata.DEFAULT_AGENT_NAME); - private static final @NotNull Map DEFAULT_INCLUDED_NODE_PATH_PATTERNS_AND_TYPES = createDefaultIncludeMap(); + private static final @NotNull Collection DEFAULT_INCLUDED_TYPES_SETTINGS = createDefaultIncludedTypesSettings(); private static final String RESOURCE_TYPE_SEGMENT_PAGE = "cq/contexthub/components/segment-page"; private static final String RESOURCE_TYPE_CONTENT_FRAGMENT_MODEL_PAGE = "dam/cfm/models/console/components/data/entity/default"; - private static Map createDefaultIncludeMap() { - Map map = new HashMap<>(); + private static Collection createDefaultIncludedTypesSettings() { + Collection typesSettings = new ArrayList<>(); // by default: editable templates and their structure child (as found by com.day.cq.wcm.core.impl.reference.PageTemplateReferenceProvider) - map.put(Pattern.compile(".*/settings/wcm/templates/[^/]*"), NameConstants.NT_TEMPLATE); - map.put(Pattern.compile(".*/settings/wcm/templates/[^/]*/structure"), NameConstants.NT_PAGE); + typesSettings.add(new TypeSettings(".*/settings/wcm/templates/[^/]*", NameConstants.NT_TEMPLATE)); + typesSettings.add(new TypeSettings(".*/settings/wcm/templates/[^/]*/structure", NameConstants.NT_PAGE)); // content policies mappings (as found by com.day.cq.wcm.core.impl.reference.ContentPolicyReferenceProvider) - map.put(Pattern.compile(".*/settings/wcm/templates/[^/]*/policies"), NameConstants.NT_PAGE); + typesSettings.add(new TypeSettings(".*/settings/wcm/templates/[^/]*/policies", NameConstants.NT_PAGE)); // mapped content policies (as found by com.day.cq.wcm.core.impl.reference.ContentPolicyReferenceProvider) - map.put(Pattern.compile(".*/settings/wcm/policies/.*"), RESOURCE_TYPE_CONTENT_POLICY); + typesSettings.add(new TypeSettings(".*/settings/wcm/policies/.*", RESOURCE_TYPE_CONTENT_POLICY)); // content fragment models (as found by com.adobe.cq.dam.cfm.impl.search.ContentFragmentReferencePublishProvider) - map.put(Pattern.compile(".*/settings/dam/cfm/models/.*"), RESOURCE_TYPE_CONTENT_FRAGMENT_MODEL_PAGE); + typesSettings.add(new TypeSettings(".*/settings/dam/cfm/models/.*", RESOURCE_TYPE_CONTENT_FRAGMENT_MODEL_PAGE)); // regular context-aware configuration (as found by https://github.com/adobe/aem-core-wcm-components/blob/main/bundles/core/src/main/java/com/adobe/cq/wcm/core/components/internal/services/CaConfigReferenceProvider.java) - map.put(Pattern.compile("/(apps|conf)/.*/(sling:configs|settings/cloudconfigs)/.*"), NameConstants.NT_PAGE); + typesSettings.add(new TypeSettings("/(apps|conf)/.*/(sling:configs|settings/cloudconfigs)/.*", NameConstants.NT_PAGE)); // segment pages (as found by com.day.cq.personalization.impl.TargetedComponentReferenceProvider) - map.put(Pattern.compile("/(apps|conf)/.*/jcr:content"), RESOURCE_TYPE_SEGMENT_PAGE); - return Collections.unmodifiableMap(map); + typesSettings.add(new TypeSettings("/(apps|conf)/.*/jcr:content", RESOURCE_TYPE_SEGMENT_PAGE)); + return Collections.unmodifiableCollection(typesSettings); } - private static final @NotNull Map DEFAULT_EXCLUDED_NODE_PATH_PATTERNS_AND_TYPES = createDefaultExcludeMap(); + private static final @NotNull Collection DEFAULT_EXCLUDED_TYPES_SETTINGS = createDefaultExcludedTypesSettings(); - private static Map createDefaultExcludeMap() { - Map map = new HashMap<>(); - map.put(Pattern.compile(".*/settings/wcm/templates/[^/]*/initial"), NameConstants.NT_PAGE); - return Collections.unmodifiableMap(map); + private static Collection createDefaultExcludedTypesSettings() { + Collection typesSettings = new ArrayList<>(); + typesSettings.add(new TypeSettings(".*/settings/wcm/templates/[^/]*/initial", NameConstants.NT_PAGE)); + return Collections.unmodifiableCollection(typesSettings); } @Nullable public Validator createValidator(@NotNull ValidationContext context, @NotNull ValidatorSettings settings) { - final @NotNull Map includedNodePathsPatternsAndTypes; + final @NotNull Collection includedTypesSettings; if (settings.getOptions().containsKey(OPTION_INCLUDED_NODE_PATH_PATTERNS_AND_TYPES)) { - includedNodePathsPatternsAndTypes = parseNodePathPatternsAndTypes(settings.getOptions().get(OPTION_INCLUDED_NODE_PATH_PATTERNS_AND_TYPES)); + includedTypesSettings = parseNodePathPatternsAndTypes(settings.getOptions().get(OPTION_INCLUDED_NODE_PATH_PATTERNS_AND_TYPES)); } else { - includedNodePathsPatternsAndTypes = DEFAULT_INCLUDED_NODE_PATH_PATTERNS_AND_TYPES; + includedTypesSettings = DEFAULT_INCLUDED_TYPES_SETTINGS; } - final @NotNull Map excludedNodePathsPatternsAndTypes; + final @NotNull Collection excludedTypesSettings; if (settings.getOptions().containsKey(OPTION_EXCLUDED_NODE_PATH_PATTERNS_AND_TYPES)) { - excludedNodePathsPatternsAndTypes = parseNodePathPatternsAndTypes(settings.getOptions().get(OPTION_EXCLUDED_NODE_PATH_PATTERNS_AND_TYPES)); + excludedTypesSettings = parseNodePathPatternsAndTypes(settings.getOptions().get(OPTION_EXCLUDED_NODE_PATH_PATTERNS_AND_TYPES)); } else { - excludedNodePathsPatternsAndTypes = DEFAULT_EXCLUDED_NODE_PATH_PATTERNS_AND_TYPES; + excludedTypesSettings = DEFAULT_EXCLUDED_TYPES_SETTINGS; } boolean strictLastModificationDateCheck = Boolean.parseBoolean(settings.getOptions().get(OPTION_STRICT_LAST_MODIFICATION_CHECK)); final @NotNull Set<@NotNull String> agentNames; @@ -93,28 +92,29 @@ public Validator createValidator(@NotNull ValidationContext context, @NotNull Va } else { agentNames = DEFAULT_AGENT_NAMES; } - return new AemReplicationMetadataValidator(settings.getDefaultSeverity(), includedNodePathsPatternsAndTypes, excludedNodePathsPatternsAndTypes, strictLastModificationDateCheck, agentNames); + return new AemReplicationMetadataValidator(settings.getDefaultSeverity(), includedTypesSettings, excludedTypesSettings, strictLastModificationDateCheck, agentNames); } - static @NotNull Map parseNodePathPatternsAndTypes(String option) { + static @NotNull Collection parseNodePathPatternsAndTypes(String option) { return Pattern.compile(",").splitAsStream(option) - .map(entry -> { - int startType = entry.lastIndexOf('['); - if (startType == -1) { - throw new IllegalArgumentException("Each entry must end with a type enclosed by \"[\" and \"]\", but found entry " + entry); - } - if (entry.charAt(entry.length() - 1) != ']') { - throw new IllegalArgumentException("Each entry must end with \"]\", but found entry " + entry); - } - Pattern pattern = Pattern.compile(entry.substring(0, startType)); - String type = entry.substring(startType + 1, entry.length()-1); - return new AbstractMap.SimpleEntry<>(pattern, type); - }) - .collect(Collectors.toMap( - Map.Entry::getKey, - Map.Entry::getValue)); + .map(AemReplicationMetadataValidatorFactory::parseTypeSettings) + .collect(Collectors.toList()); } - + + static @NotNull TypeSettings parseTypeSettings(String entry) { + int startType = entry.lastIndexOf('['); + if (startType == -1) { + throw new IllegalArgumentException("Each entry must end with a type enclosed by \"[\" and \"]\", but found entry " + entry); + } + if (entry.charAt(entry.length() - 1) != ']') { + throw new IllegalArgumentException("Each entry must end with \"]\", but found entry " + entry); + } + + String pattern = entry.substring(0, startType); + String type = entry.substring(startType + 1, entry.length()-1); + return new TypeSettings(pattern, type); + } + public boolean shouldValidateSubpackages() { return true; } diff --git a/src/main/java/biz/netcentric/filevault/validator/NodeMetadata.java b/src/main/java/biz/netcentric/filevault/validator/NodeMetadata.java index 3cf7e38..209a7f9 100644 --- a/src/main/java/biz/netcentric/filevault/validator/NodeMetadata.java +++ b/src/main/java/biz/netcentric/filevault/validator/NodeMetadata.java @@ -23,8 +23,12 @@ import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; +import javax.jcr.ValueFactory; import org.apache.jackrabbit.spi.Name; +import org.apache.jackrabbit.spi.NameFactory; +import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; +import org.apache.jackrabbit.value.ValueFactoryImpl; import org.apache.jackrabbit.vault.util.DocViewNode2; import org.apache.jackrabbit.vault.util.DocViewProperty2; import org.apache.jackrabbit.vault.validation.spi.ValidationMessage; @@ -41,8 +45,12 @@ */ public class NodeMetadata { - static final Name NAME_CQ_LAST_MODIFIED = AemReplicationMetadataValidator.NAME_FACTORY.create(AemReplicationMetadataValidator.CQ_NAMESPACE_URI, "lastModified"); - private static final Name NAME_JCR_LAST_MODIFIED = AemReplicationMetadataValidator.NAME_FACTORY.create(Property.JCR_LAST_MODIFIED); + static final NameFactory NAME_FACTORY = NameFactoryImpl.getInstance(); + static final ValueFactory VALUE_FACTORY = ValueFactoryImpl.getInstance(); + static final String CQ_NAMESPACE_URI = "http://www.day.com/jcr/cq/1.0"; // no constant defined in https://developer.adobe.com/experience-manager/reference-materials/6-5/javadoc/constant-values.html + + static final Name NAME_CQ_LAST_MODIFIED = NAME_FACTORY.create(CQ_NAMESPACE_URI, "lastModified"); + private static final Name NAME_JCR_LAST_MODIFIED = NAME_FACTORY.create(Property.JCR_LAST_MODIFIED); /** * If {@code true} the node is supposed to contain replication metadata which indicates it is active and not modified, @@ -90,7 +98,7 @@ public void captureLastModificationDate(@NotNull DocViewNode2 node) throws Illeg .orElseGet(() -> node.getProperty(NAME_JCR_LAST_MODIFIED) .orElse(null))); if (property.isPresent()) { - Value lastModifiedValue = AemReplicationMetadataValidator.VALUE_FACTORY.createValue(property.get().getStringValue().orElseThrow(() -> new IllegalStateException("No value found in " + property.get().getName())), PropertyType.DATE); + Value lastModifiedValue = VALUE_FACTORY.createValue(property.get().getStringValue().orElseThrow(() -> new IllegalStateException("No value found in " + property.get().getName())), PropertyType.DATE); lastModificationDate = Optional.of(lastModifiedValue.getDate()); } else { // assume current date as last modified date (only for binaries and nodes with autocreated jcr:lastModified through mixin mix:lastModified) diff --git a/src/main/java/biz/netcentric/filevault/validator/ReplicationMetadata.java b/src/main/java/biz/netcentric/filevault/validator/ReplicationMetadata.java index f439c71..fb51e34 100644 --- a/src/main/java/biz/netcentric/filevault/validator/ReplicationMetadata.java +++ b/src/main/java/biz/netcentric/filevault/validator/ReplicationMetadata.java @@ -53,7 +53,7 @@ static DocViewProperty2 getProperty(@NotNull DocViewNode2 node, @NotNull String String metadataPropertySuffix = agentName.equals(DEFAULT_AGENT_NAME) ? "" : ("_" + agentName); List suffixedPropertyNames = Arrays.stream(propertyNames).map(s -> s + metadataPropertySuffix).collect(Collectors.toList()); for (String propertyName : suffixedPropertyNames) { - Optional property = node.getProperty(AemReplicationMetadataValidator.NAME_FACTORY.create(namespaceUri, propertyName)); + Optional property = node.getProperty(NodeMetadata.NAME_FACTORY.create(namespaceUri, propertyName)); if (property.isPresent()) { return property.get(); } @@ -69,10 +69,10 @@ static DocViewProperty2 getProperty(@NotNull DocViewNode2 node, @NotNull String public Calendar getLastReplicationDate(boolean allowNullReturnValue) { // this logic is derived from com.day.cq.replication.impl.ReplicationStatusImpl.readAgentStatus(...) // and com.day.cq.wcm.core.impl.reference.ReferenceReplicationStatusProvider.initReplicationStatusMap(...) - DocViewProperty2 property = getProperty(node, agentName, allowNullReturnValue, AemReplicationMetadataValidator.CQ_NAMESPACE_URI, CQ_LAST_REPLICATED, CQ_LAST_PUBLISHED); + DocViewProperty2 property = getProperty(node, agentName, allowNullReturnValue, NodeMetadata.CQ_NAMESPACE_URI, CQ_LAST_REPLICATED, CQ_LAST_PUBLISHED); Optional lastReplicationDate = Optional.ofNullable(property) .flatMap(DocViewProperty2::getStringValue) - .map(AemReplicationMetadataValidator.VALUE_FACTORY::createValue) + .map(NodeMetadata.VALUE_FACTORY::createValue) .map(t -> { try { return t.getDate(); @@ -89,7 +89,7 @@ public Calendar getLastReplicationDate(boolean allowNullReturnValue) { public ReplicationActionType getLastReplicationAction(boolean allowNullReturnValue) { // this logic is derived from com.day.cq.replication.impl.ReplicationStatusImpl.readAgentStatus(...) // and com.day.cq.wcm.core.impl.reference.ReferenceReplicationStatusProvider.initReplicationStatusMap(...) - DocViewProperty2 property = getProperty(node, agentName, allowNullReturnValue, AemReplicationMetadataValidator.CQ_NAMESPACE_URI, CQ_LAST_REPLICATION_ACTION); + DocViewProperty2 property = getProperty(node, agentName, allowNullReturnValue, NodeMetadata.CQ_NAMESPACE_URI, CQ_LAST_REPLICATION_ACTION); Optional replicationActionType = Optional.ofNullable(property) .flatMap(DocViewProperty2::getStringValue) .map(ReplicationActionType::fromName); diff --git a/src/main/java/biz/netcentric/filevault/validator/TypeSettings.java b/src/main/java/biz/netcentric/filevault/validator/TypeSettings.java new file mode 100644 index 0000000..9645f79 --- /dev/null +++ b/src/main/java/biz/netcentric/filevault/validator/TypeSettings.java @@ -0,0 +1,86 @@ +/*- + * #%L + * AEM Replication Metadata Validator + * %% + * Copyright (C) 2024 Cognizant Netcentric + * %% + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * #L% + */ +package biz.netcentric.filevault.validator; + +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; + +import org.apache.jackrabbit.spi.Name; +import org.apache.jackrabbit.vault.util.DocViewNode2; +import org.apache.sling.api.SlingConstants; +import org.apache.sling.jcr.resource.api.JcrResourceConstants; +import org.jetbrains.annotations.NotNull; + +import com.day.cq.commons.jcr.JcrConstants; + +/** + * Settings for a particular node type which requires replicaton metadata + */ +public class TypeSettings { + + static final @NotNull Name NAME_SLING_RESOURCETYPE = NodeMetadata.NAME_FACTORY.create(JcrResourceConstants.SLING_NAMESPACE_URI, SlingConstants.PROPERTY_RESOURCE_TYPE); + static final @NotNull String NT_CQ_PAGE_CONTENT = "cq:PageContent"; + + enum ComparisonDate { + JCR_CQ_LASTMODIFIED, + JCR_CQ_LASTMODIFIED_JCR_CREATED; + } + private final @NotNull Pattern pathPattern; + private final @NotNull String type; // either primary type or resource type + private @NotNull ComparisonDate comparisonDate; + + public TypeSettings(@NotNull String pathPattern, @NotNull String type) { + this.pathPattern = Pattern.compile(pathPattern); + this.type = type; + this.comparisonDate = ComparisonDate.JCR_CQ_LASTMODIFIED; + } + + public void setReferenceDate(@NotNull ComparisonDate comparisonDate) { + this.comparisonDate = comparisonDate; + } + + public boolean matches(@NotNull String actualNodePath, @NotNull DocViewNode2 node) { + if (!pathPattern.matcher(actualNodePath).matches()) { + return false; + } + String actualPrimaryType = node.getPrimaryType().orElse(""); + boolean isNodeRelevant = actualPrimaryType.equals(type); + // if node type == nt:unstructured or cq:PageContent, evaluate sling:resourceType in addition + if (!isNodeRelevant && (actualPrimaryType.equals(JcrConstants.NT_UNSTRUCTURED) || actualPrimaryType.equals(NT_CQ_PAGE_CONTENT))) { + isNodeRelevant = node.getPropertyValue(NAME_SLING_RESOURCETYPE).equals(Optional.of(type)); + } + return isNodeRelevant; + } + + @Override + public int hashCode() { + return Objects.hash(comparisonDate, pathPattern, type); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof TypeSettings)) + return false; + TypeSettings other = (TypeSettings) obj; + return comparisonDate == other.comparisonDate && Objects.equals(pathPattern.pattern(), other.pathPattern.pattern()) + && Objects.equals(type, other.type); + } + + @Override + public String toString() { + return "TypeSettings [resourcePathPattern=" + pathPattern + ", type=" + type + ", comparisonDate=" + comparisonDate + "]"; + } +} diff --git a/src/test/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidatorFactoryTest.java b/src/test/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidatorFactoryTest.java index 530954c..48b6234 100644 --- a/src/test/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidatorFactoryTest.java +++ b/src/test/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidatorFactoryTest.java @@ -12,21 +12,15 @@ */ package biz.netcentric.filevault.validator; -import java.util.regex.Pattern; - import org.assertj.core.api.Assertions; -import org.assertj.core.api.Condition; import org.junit.jupiter.api.Test; class AemReplicationMetadataValidatorFactoryTest { @Test void test() { - // due to https://bugs.openjdk.org/browse/JDK-7163589 need custom condition for the pattern Assertions.assertThat(AemReplicationMetadataValidatorFactory.parseNodePathPatternsAndTypes("test[cq:Page]")) - .hasEntrySatisfying(new Condition(p -> p.pattern().equals("test"), "is pattern 'test'" ), - new Condition(s -> s.equals("cq:Page"), "is a page")) - .hasSize(1); + .contains(new TypeSettings("test", "cq:Page")); } }