Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CF models must evaluate only cq:lastModified and cq:created #25

Merged
merged 1 commit into from
Jul 12, 2024
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
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());
}
}
Loading