Skip to content

Commit

Permalink
Refactoring: TypeSettings in dedicated class instead of Map
Browse files Browse the repository at this point in the history
  • Loading branch information
kwin committed Jul 6, 2024
1 parent feb9a9e commit 9fa9163
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pattern, String> includedNodePathsPatternsAndTypes;
private final @NotNull Map<Pattern, String> excludedNodePathsPatternsAndTypes;
private final @NotNull Collection<TypeSettings> includedTypesSettings;
private final @NotNull Collection<TypeSettings> excludedTypesSettings;
private final boolean strictLastModificationCheck;
private final @NotNull Set<@NotNull String> agentNames;
private Queue<NodeMetadata> relevantNodeMetadata = Collections.asLifoQueue(new ArrayDeque<>());

public AemReplicationMetadataValidator(@NotNull ValidationMessageSeverity validationMessageSeverity, @NotNull Map<Pattern, String> includedNodePathsPatternsAndTypes,
@NotNull Map<Pattern, String> excludedNodePathsPatternsAndTypes, boolean strictLastModificationDateCheck, @NotNull Set<@NotNull String> agentNames) {
public AemReplicationMetadataValidator(@NotNull ValidationMessageSeverity validationMessageSeverity, @NotNull Collection<TypeSettings> includedTypesSettings,
@NotNull Collection<TypeSettings> 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
Expand All @@ -96,13 +77,13 @@ private Optional<NodeMetadata> getNodeMetadata(@NotNull String nodePath, @NotNul
}
// first check includes, then excludes, first match returning relevant metadata wins
boolean isExclude = false;
Optional<NodeMetadata> newMetadata = includedNodePathsPatternsAndTypes.entrySet().stream()
Optional<NodeMetadata> 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)
Expand All @@ -116,16 +97,13 @@ private Optional<NodeMetadata> getNodeMetadata(@NotNull String nodePath, @NotNul

}

private Optional<NodeMetadata> getNodeMetadata(boolean isExclude, @NotNull String nodePath, @NotNull DocViewNode2 node, Entry<Pattern, String> patternAndType) {
if (!patternAndType.getKey().matcher(nodePath).matches()) {
return Optional.empty();
}
NodeMetadata currentMetadata;
boolean isNodeRelevant = isRelevantNodeType(node, patternAndType.getValue());
if (!isNodeRelevant) {
private Optional<NodeMetadata> 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);
Expand All @@ -138,15 +116,6 @@ private Optional<NodeMetadata> 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<ValidationMessage> validate(@NotNull DocViewNode2 node, @NotNull NodeContext nodeContext, boolean isRoot) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Pattern, String> DEFAULT_INCLUDED_NODE_PATH_PATTERNS_AND_TYPES = createDefaultIncludeMap();
private static final @NotNull Collection<TypeSettings> 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<Pattern, String> createDefaultIncludeMap() {
Map<Pattern, String> map = new HashMap<>();
private static Collection<TypeSettings> createDefaultIncludedTypesSettings() {
Collection<TypeSettings> 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<Pattern, String> DEFAULT_EXCLUDED_NODE_PATH_PATTERNS_AND_TYPES = createDefaultExcludeMap();
private static final @NotNull Collection<TypeSettings> DEFAULT_EXCLUDED_TYPES_SETTINGS = createDefaultExcludedTypesSettings();

private static Map<Pattern, String> createDefaultExcludeMap() {
Map<Pattern, String> map = new HashMap<>();
map.put(Pattern.compile(".*/settings/wcm/templates/[^/]*/initial"), NameConstants.NT_PAGE);
return Collections.unmodifiableMap(map);
private static Collection<TypeSettings> createDefaultExcludedTypesSettings() {
Collection<TypeSettings> 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<Pattern, String> includedNodePathsPatternsAndTypes;
final @NotNull Collection<TypeSettings> 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<Pattern, String> excludedNodePathsPatternsAndTypes;
final @NotNull Collection<TypeSettings> 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;
Expand All @@ -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<Pattern, String> parseNodePathPatternsAndTypes(String option) {
static @NotNull Collection<TypeSettings> 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;
}
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/biz/netcentric/filevault/validator/NodeMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 9fa9163

Please sign in to comment.