Skip to content

Commit

Permalink
CF models must evaluate only cq:lastModified and cq:created
Browse files Browse the repository at this point in the history
This closes #24
  • Loading branch information
kwin committed Jul 12, 2024
1 parent aa75a75 commit 7cb0636
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 46 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<jcr:content
cq:lastReplicationAction="Activate"
cq:lastReplicated="{Date}2022-01-01T00:00:00.000+01:00"
cq:created="{Date}2023-01-01T00:00:00.000+01:00"
cq:scaffolding="/conf/hcp-plus/settings/dam/cfm/models/speaker/jcr:content/model"
cq:templateType="/libs/settings/dam/cfm/model-types/fragment"
jcr:primaryType="cq:PageContent"
Expand Down
3 changes: 1 addition & 2 deletions src/it/project1/verify.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ assert buildLog.contains("[ERROR] ValidationViolation: \"netcentric-aem-replicat
assert buildLog.contains("[ERROR] ValidationViolation: \"netcentric-aem-replication-metadata: No replication date set for agent publish: Replication properties {http://www.day.com/jcr/cq/1.0}lastReplicated or {http://www.day.com/jcr/cq/1.0}lastPublished not found\", filePath=jcr_root${File.separator}conf${File.separator}example${File.separator}my-segment${File.separator}.content.xml, nodePath=/conf/example/my-segment/jcr:content, line=19, column=19") : 'violation for segment page not found'

// Content fragment models
assert buildLog.contains("[ERROR] ValidationViolation: \"netcentric-aem-replication-metadata: The replication date 2021-12-31T23:00:00Z for agent publish is older than the comparison date ") : 'violation for content fragment model not found'
assert buildLog.contains("(auto created jcr:created)\", filePath=jcr_root${File.separator}conf${File.separator}example${File.separator}settings${File.separator}dam${File.separator}cfm${File.separator}models${File.separator}speaker${File.separator}.content.xml, nodePath=/conf/example/settings/dam/cfm/models/speaker/jcr:content, line=105, column=19") : 'violation for content fragment model not found'
assert buildLog.contains("[ERROR] ValidationViolation: \"netcentric-aem-replication-metadata: The replication date 2021-12-31T23:00:00Z for agent publish is older than the comparison date 2022-12-31T23:00:00Z ({http://www.day.com/jcr/cq/1.0}created)\", filePath=jcr_root${File.separator}conf${File.separator}example${File.separator}settings${File.separator}dam${File.separator}cfm${File.separator}models${File.separator}speaker${File.separator}.content.xml, nodePath=/conf/example/settings/dam/cfm/models/speaker/jcr:content, line=106, column=19") : 'violation for content fragment model not found'


assert buildLog.contains('[ERROR] Failed to execute goal org.apache.jackrabbit:filevault-package-maven-plugin:1.3.0:validate-package (default-validate-package) on project content-package: Found 8 violation(s) (with severity=ERROR). Check above errors for details -> [Help 1]') : 'Incorrect number of validation issues found'
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private static Collection<TypeSettings> 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));
Expand Down
91 changes: 51 additions & 40 deletions src/main/java/biz/netcentric/filevault/validator/DateProperty.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
* <ol>
* <li>property {@code cq:lastModified}</li> or
* <li>property {@code cq:lastModified} or</li>
* <li>property {@code jcr:lastModified}</li>
* </ol>
*/
MODIFIED(false, false),
MODIFIED(Arrays.asList(PropertyName.PROPERTY_CQ_LAST_MODIFIED, PropertyName.PROPERTY_JCR_LAST_MODIFIED), false),
/**
* Extracts the date from
* <ol>
* <li>property {@code cq:lastModified}</li>
* <li>property {@code jcr:lastModified}</li>
* <li>property {@code jcr:created}</li>
* <li>property {@code jcr:created} or</li>
* <li>the current date</li>
* </ol>
*/
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
* <ol>
* <li>property {@code cq:lastModified}</li>
* <li>property {@code cq:created} or</li>
* <li>the current date</li>
* </ol>
*/
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<PropertyName> propertyNames;

DateProperty(boolean useCreatedProperty, boolean useCurrentDateAsLastResort) {
this.useCreatedProperty = useCreatedProperty;
DateProperty(Collection<PropertyName> propertyNames, boolean useCurrentDateAsLastResort) {
this.useCurrentDateAsLastResort = useCurrentDateAsLastResort;
this.propertyNames = propertyNames;
}

/**
Expand All @@ -67,38 +81,35 @@ enum DateProperty {
* @throws RepositoryException
*/
Optional<Map.Entry<Calendar, String>> extractDate(@NotNull DocViewNode2 node) throws RepositoryException {
Map.Entry<Calendar, String> dateAndLabel = null;
Optional<DocViewProperty2> 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<Calendar, String> dateAndLabel = extractDate(node, propertyNames).orElse(
useCurrentDateAsLastResort ? new SimpleEntry<>(Calendar.getInstance(), "current date") : null);
return Optional.ofNullable(dateAndLabel);
}

Optional<Map.Entry<Calendar, String>> extractDate(@NotNull DocViewNode2 node, Collection<PropertyName> propertyNames) throws RepositoryException {
for (PropertyName propertyName : propertyNames) {
Optional<Map.Entry<Calendar, String>> dateAndLabel = extractDate(node, propertyName);
if (dateAndLabel.isPresent()) {
return dateAndLabel;
}
}

return Optional.empty();
}

Optional<Map.Entry<Calendar, String>> extractDate(@NotNull DocViewNode2 node, PropertyName propertyName) throws RepositoryException {
Map.Entry<Calendar, String> dateAndLabel = null;
Optional<DocViewProperty2> 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<String> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
51 changes: 51 additions & 0 deletions src/main/java/biz/netcentric/filevault/validator/PropertyName.java
Original file line number Diff line number Diff line change
@@ -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<String> autoCreatedTypes; // either mixin or primary types

private PropertyName(Name name) {
this(name, Collections.emptySet());
}

private PropertyName(Name name, Collection<String> autoGenerateTypes) {
super();
this.name = name;
this.autoCreatedTypes = autoGenerateTypes;
}

public Name getName() {
return name;
}

public Collection<String> getAutoCreatedTypes() {
return autoCreatedTypes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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());
}
}

0 comments on commit 7cb0636

Please sign in to comment.