From 7cb0636a3e2385680e14dac4071d0d3b30c69dc5 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Fri, 12 Jul 2024 14:49:24 +0200 Subject: [PATCH] CF models must evaluate only cq:lastModified and cq:created This closes #24 --- README.md | 2 +- .../dam/cfm/models/speaker/.content.xml | 1 + src/it/project1/verify.groovy | 3 +- ...emReplicationMetadataValidatorFactory.java | 2 +- .../filevault/validator/DateProperty.java | 91 +++++++++++-------- .../filevault/validator/NameConstants.java | 1 + .../filevault/validator/PropertyName.java | 51 +++++++++++ .../filevault/validator/DatePropertyTest.java | 4 +- 8 files changed, 109 insertions(+), 46 deletions(-) create mode 100644 src/main/java/biz/netcentric/filevault/validator/PropertyName.java diff --git a/README.md b/README.md index 337ba3e..e9a399f 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The following repository locations are considered by default which must contain 1. [Editable templates][page-templates]' policy nodes (as found by `com.day.cq.wcm.core.impl.reference.ContentPolicyReferenceProvider`), this includes both *policy mappings* (with resource type=`wcm/core/components/policies/mappings`) as well as *actual policies* (with resource type=`wcm/core/components/policy/policy`). The latter are also found outside actual editable templates. 1. Generic [Sling Context-Aware configurations][ca-configs] (as found by [`com.adobe.cq.wcm.core.components.internal.services.CaConfigReferenceProvider`](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)) 1. [Segment Pages][segment-pages] (as found by `com.day.cq.personalization.impl.TargetedComponentReferenceProvider`) -1. [Content Fragment Models][content-fragment-models] (as found by `com.adobe.cq.dam.cfm.impl.search.ContentFragmentReferencePublishProvider`) +1. [Content Fragment Models][content-fragment-models] (as found by `com.adobe.cq.dam.cfm.impl.search.FragmentReferencePublishProvider`) Those locations are given through the default value for `includedNodePathPatternsAndTypes`. This default set can be overridden through the settings outlined below to check for other nodes. diff --git a/src/it/project1/content-package/src/main/jcr_root/conf/example/settings/dam/cfm/models/speaker/.content.xml b/src/it/project1/content-package/src/main/jcr_root/conf/example/settings/dam/cfm/models/speaker/.content.xml index c44cbec..c023c34 100644 --- a/src/it/project1/content-package/src/main/jcr_root/conf/example/settings/dam/cfm/models/speaker/.content.xml +++ b/src/it/project1/content-package/src/main/jcr_root/conf/example/settings/dam/cfm/models/speaker/.content.xml @@ -6,6 +6,7 @@ [Help 1]') : 'Incorrect number of validation issues found' diff --git a/src/main/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidatorFactory.java b/src/main/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidatorFactory.java index c20a27d..c438e10 100644 --- a/src/main/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidatorFactory.java +++ b/src/main/java/biz/netcentric/filevault/validator/AemReplicationMetadataValidatorFactory.java @@ -57,7 +57,7 @@ private static Collection createDefaultIncludedTypesSettings() { 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) TypeSettings typeSettings = new TypeSettings(".*/settings/dam/cfm/models/.*", RESOURCE_TYPE_CONTENT_FRAGMENT_MODEL_PAGE); - typeSettings.setComparisonDatePropery(DateProperty.MODIFIED_CREATED_OR_CURRENT); + typeSettings.setComparisonDatePropery(DateProperty.CQ_MODIFIED_CREATED_OR_CURRENT); typesSettings.add(typeSettings); // 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) typesSettings.add(new TypeSettings("/(apps|conf)/.*/(sling:configs|settings/cloudconfigs)/.*", NameConstants.NT_PAGE)); diff --git a/src/main/java/biz/netcentric/filevault/validator/DateProperty.java b/src/main/java/biz/netcentric/filevault/validator/DateProperty.java index 2ccfbf1..668df5d 100644 --- a/src/main/java/biz/netcentric/filevault/validator/DateProperty.java +++ b/src/main/java/biz/netcentric/filevault/validator/DateProperty.java @@ -12,52 +12,66 @@ */ package biz.netcentric.filevault.validator; +import java.util.AbstractMap.SimpleEntry; +import java.util.Arrays; import java.util.Calendar; +import java.util.Collection; +import java.util.HashSet; import java.util.Map; import java.util.Optional; -import java.util.AbstractMap.SimpleEntry; + import javax.jcr.Node; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; -import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants; import org.apache.jackrabbit.vault.util.DocViewNode2; import org.apache.jackrabbit.vault.util.DocViewProperty2; import org.jetbrains.annotations.NotNull; -import com.day.cq.commons.jcr.JcrConstants; - /** * Represents a date property which can be extracted from a given {@link Node}. * It may have multiple fallbacks (i.e. tries to extract from multiple properties until one is found) */ enum DateProperty { + + /** * Extracts the date from *
    - *
  1. property {@code cq:lastModified}
  2. or + *
  3. property {@code cq:lastModified} or
  4. *
  5. property {@code jcr:lastModified}
  6. *
*/ - MODIFIED(false, false), + MODIFIED(Arrays.asList(PropertyName.PROPERTY_CQ_LAST_MODIFIED, PropertyName.PROPERTY_JCR_LAST_MODIFIED), false), /** * Extracts the date from *
    *
  1. property {@code cq:lastModified}
  2. *
  3. property {@code jcr:lastModified}
  4. - *
  5. property {@code jcr:created}
  6. + *
  7. property {@code jcr:created} or
  8. *
  9. the current date
  10. *
*/ - MODIFIED_CREATED_OR_CURRENT(true, true); + MODIFIED_CREATED_OR_CURRENT(Arrays.asList(PropertyName.PROPERTY_CQ_LAST_MODIFIED, PropertyName.PROPERTY_JCR_LAST_MODIFIED, PropertyName.PROPERTY_JCR_CREATED), true), + + + /** + * Extracts the date from + *
    + *
  1. property {@code cq:lastModified}
  2. + *
  3. property {@code cq:created} or
  4. + *
  5. the current date
  6. + *
+ */ + CQ_MODIFIED_CREATED_OR_CURRENT(Arrays.asList(PropertyName.PROPERTY_CQ_LAST_MODIFIED, PropertyName.PROPERTY_CQ_CREATED), true); - private final boolean useCreatedProperty; - boolean useCurrentDateAsLastResort; + private final boolean useCurrentDateAsLastResort; + private final Collection propertyNames; - DateProperty(boolean useCreatedProperty, boolean useCurrentDateAsLastResort) { - this.useCreatedProperty = useCreatedProperty; + DateProperty(Collection propertyNames, boolean useCurrentDateAsLastResort) { this.useCurrentDateAsLastResort = useCurrentDateAsLastResort; + this.propertyNames = propertyNames; } /** @@ -67,38 +81,35 @@ enum DateProperty { * @throws RepositoryException */ Optional> extractDate(@NotNull DocViewNode2 node) throws RepositoryException { - Map.Entry dateAndLabel = null; - Optional property = Optional.ofNullable(node.getProperty(NameConstants.CQ_LAST_MODIFIED) - .orElseGet(() -> - node.getProperty(NameConstants.JCR_LASTMODIFIED) - .orElse(null))); - if (!property.isPresent()) { - // auto-created property? - if (node.getPrimaryType().orElse("").equals(JcrConstants.NT_RESOURCE) - || node.getPrimaryType().orElse("").equals(NodeTypeConstants.NT_OAK_RESOURCE) - || node.getMixinTypes().contains(JcrConstants.MIX_LAST_MODIFIED)) { - dateAndLabel = new SimpleEntry<>(Calendar.getInstance(), "auto created jcr:lastModified"); - } - } - if (useCreatedProperty && !property.isPresent()) { - property = node.getProperty(NameConstants.JCR_CREATED); - // implicitly created? - if (!property.isPresent()) { - // auto-created property? - if (node.getPrimaryType().orElse("").equals(NameConstants.NT_CQ_PAGE_CONTENT) - || node.getMixinTypes().contains(JcrConstants.MIX_CREATED)) { - dateAndLabel = new SimpleEntry<>(Calendar.getInstance(), "auto created jcr:created"); - } + Map.Entry dateAndLabel = extractDate(node, propertyNames).orElse( + useCurrentDateAsLastResort ? new SimpleEntry<>(Calendar.getInstance(), "current date") : null); + return Optional.ofNullable(dateAndLabel); + } + + Optional> extractDate(@NotNull DocViewNode2 node, Collection propertyNames) throws RepositoryException { + for (PropertyName propertyName : propertyNames) { + Optional> dateAndLabel = extractDate(node, propertyName); + if (dateAndLabel.isPresent()) { + return dateAndLabel; } } - + return Optional.empty(); + } + + Optional> extractDate(@NotNull DocViewNode2 node, PropertyName propertyName) throws RepositoryException { + Map.Entry dateAndLabel = null; + Optional property = node.getProperty(propertyName.getName()); if (property.isPresent()) { - String propertyName = property.get().getName().toString(); - Value propertyValue = NameConstants.VALUE_FACTORY.createValue(property.get().getStringValue().orElseThrow(() -> new IllegalStateException("No value found in " + propertyName)), PropertyType.DATE); + Value propertyValue = NameConstants.VALUE_FACTORY.createValue(property.get().getStringValue().orElseThrow(() -> new IllegalStateException("No value found in " + propertyName.getName())), PropertyType.DATE); dateAndLabel = new SimpleEntry<>(propertyValue.getDate(), property.get().getName().toString()); - } - if (dateAndLabel == null && useCurrentDateAsLastResort) { - dateAndLabel = new SimpleEntry<>(Calendar.getInstance(), "current date"); + } else { + // check for auto-created property + Collection types = new HashSet<>(); + node.getPrimaryType().map(t -> types.add(t)); + types.addAll(node.getMixinTypes()); + if (propertyName.getAutoCreatedTypes().stream().anyMatch(types::contains)) { + dateAndLabel = new SimpleEntry<>(Calendar.getInstance(), "auto created " + propertyName.getName().toString()); + } } return Optional.ofNullable(dateAndLabel); } diff --git a/src/main/java/biz/netcentric/filevault/validator/NameConstants.java b/src/main/java/biz/netcentric/filevault/validator/NameConstants.java index 292ced0..93658c3 100644 --- a/src/main/java/biz/netcentric/filevault/validator/NameConstants.java +++ b/src/main/java/biz/netcentric/filevault/validator/NameConstants.java @@ -37,6 +37,7 @@ private NameConstants() { public static final @NotNull String NT_CQ_PAGE_CONTENT = "cq:PageContent"; public static final @NotNull Name CQ_LAST_MODIFIED = NAME_FACTORY.create(CQ_NAMESPACE_URI, "lastModified"); + public static final @NotNull Name CQ_CREATED = NAME_FACTORY.create(CQ_NAMESPACE_URI, "created"); public static final @NotNull Name JCR_LASTMODIFIED = org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_LASTMODIFIED; public static final @NotNull Name JCR_CREATED = org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_CREATED; diff --git a/src/main/java/biz/netcentric/filevault/validator/PropertyName.java b/src/main/java/biz/netcentric/filevault/validator/PropertyName.java new file mode 100644 index 0000000..0b3a5bb --- /dev/null +++ b/src/main/java/biz/netcentric/filevault/validator/PropertyName.java @@ -0,0 +1,51 @@ +/*- + * #%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.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants; +import org.apache.jackrabbit.spi.Name; + +import com.day.cq.commons.jcr.JcrConstants; + +final class PropertyName { + + static final PropertyName PROPERTY_CQ_CREATED = new PropertyName(NameConstants.CQ_CREATED); + static final PropertyName PROPERTY_CQ_LAST_MODIFIED = new PropertyName(NameConstants.CQ_LAST_MODIFIED); + static final PropertyName PROPERTY_JCR_LAST_MODIFIED = new PropertyName(NameConstants.JCR_LASTMODIFIED, Arrays.asList(JcrConstants.NT_RESOURCE, NodeTypeConstants.NT_OAK_RESOURCE, JcrConstants.MIX_LAST_MODIFIED)); + static final PropertyName PROPERTY_JCR_CREATED = new PropertyName(NameConstants.JCR_CREATED, Arrays.asList(NameConstants.NT_CQ_PAGE_CONTENT, JcrConstants.MIX_CREATED)); + + private final Name name; + private final Collection autoCreatedTypes; // either mixin or primary types + + private PropertyName(Name name) { + this(name, Collections.emptySet()); + } + + private PropertyName(Name name, Collection autoGenerateTypes) { + super(); + this.name = name; + this.autoCreatedTypes = autoGenerateTypes; + } + + public Name getName() { + return name; + } + + public Collection getAutoCreatedTypes() { + return autoCreatedTypes; + } +} \ No newline at end of file diff --git a/src/test/java/biz/netcentric/filevault/validator/DatePropertyTest.java b/src/test/java/biz/netcentric/filevault/validator/DatePropertyTest.java index d2efa55..3e8a587 100644 --- a/src/test/java/biz/netcentric/filevault/validator/DatePropertyTest.java +++ b/src/test/java/biz/netcentric/filevault/validator/DatePropertyTest.java @@ -64,7 +64,7 @@ void testExtractDateModified() throws IllegalStateException, RepositoryException assertTrue(result.isPresent()); long delta = Duration.between(Instant.now(), result.get().getKey().toInstant()).toMillis(); assertTrue(delta < 100); - assertEquals("auto created jcr:lastModified", result.get().getValue()); + assertEquals("auto created {http://www.jcp.org/jcr/1.0}lastModified", result.get().getValue()); } @Test @@ -105,6 +105,6 @@ void testExtractDateModifiedCreatedOrCurrent() throws IllegalStateException, Rep assertTrue(result.isPresent()); delta = Duration.between(Instant.now(), result.get().getKey().toInstant()).toMillis(); assertTrue(delta < 100); - assertEquals("auto created jcr:created", result.get().getValue()); + assertEquals("auto created {http://www.jcp.org/jcr/1.0}created", result.get().getValue()); } }