Skip to content
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ There is a mandatory naming policy for Oak index definition node names which enf

Further details in <https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/operations/indexing#preparing-the-new-index-definition>.

## Enforce diff.index control nodes of type `disabled`

The control nodes `/oak:index/diff.index` and `/oak:index/diff.index.optimizer` used by Oak's simplified index management (diff indexes) are exempt from the naming, `type=lucene` and `compatVersion` rules above, but must have `type` set to `disabled`. Further details in <https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/operations/indexing#simplified-index-management-using-the-diff-index>.

# Usage with Maven

You can use this validator with the [FileVault Package Maven Plugin][3] in version 1.4.0 or higher like this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,20 @@ public class AemCloudValidator implements NodePathValidator, MetaInfPathValidato
static final String VIOLATION_MESSAGE_MUTABLE_NODES_AND_IMMUTABLE_NODES_IN_SAME_PACKAGE = "Mutable and immutable nodes must not be mixed in the same package. You must separate those into two packages and give them both a dedicated package type!";
static final String VIOLATION_MESSAGE_NON_LUCENE_TYPE_INDEX_DEFINITION = "Only oak:QueryIndexDefinitions of type='lucene' are supported in AEMaaCS but found type='%s'. Compare with https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/operations/indexing#current-limitations";
static final String VIOLATION_MESSAGE_INVALID_COMPAT_VERSION_IN_INDEX_DEFINITION = "The compatVersion property of an oak:QueryIndexDefinition must be set to the Long value '2' but found '%s'. Compare with https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/operations/indexing#current-limitations";

static final String VIOLATION_MESSAGE_INVALID_DIFF_INDEX_TYPE = "The diff-index control node used for Oak simplified index management must have type='disabled' but found type='%s'. Further details at https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/operations/indexing#simplified-index-management-using-the-diff-index";

// this path is relative to META-INF
private static final Path INSTALL_HOOK_PATH = Paths.get(Constants.VAULT_DIR, Constants.HOOKS_DIR);
/**
* The allowed patterns are defined in https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/operations/indexing#preparing-the-new-index-definition
*/
private static final Pattern INDEX_DEFINITION_NAME_PATTERN = Pattern.compile(".*-\\d++-custom-\\d++");

/**
* Paths of the diff-index control nodes used by Oak simplified index management (OAK-12010).
* They are not index definitions themselves and don't follow the naming/type/compatVersion rules of regular index definitions.
*/
private static final Collection<String> DIFF_INDEX_CONTROL_NODE_PATHS = Arrays.asList("/oak:index/diff.index", "/oak:index/diff.index.optimizer");

private static final Collection<String> IMMUTABLE_PATH_PREFIXES = Arrays.asList("/apps", "/libs", "/oak:index");
private static final Collection<String> WRITABLE_PATHS_BY_DISTRIBUTION_IMPORTER = Arrays.asList(
"/content", // access provided by system user content-writer-service and sling-distribution-importer
Expand Down Expand Up @@ -213,6 +219,14 @@ static boolean isPackagePathInstalledConditionally(String runMode, Path packageR
@Override
public @Nullable Collection<ValidationMessage> validate(@NotNull DocViewNode2 node, @NotNull NodeContext nodeContext, boolean isRoot) {
if ("oak:QueryIndexDefinition".equals(node.getPrimaryType().orElse(""))) {
if (DIFF_INDEX_CONTROL_NODE_PATHS.contains(nodeContext.getNodePath())) {
String diffIndexType = node.getPropertyValue(PN_TYPE).orElse("");
if (!"disabled".equals(diffIndexType)) {
return Collections.singleton(new ValidationMessage(defaultSeverity,
String.format(VIOLATION_MESSAGE_INVALID_DIFF_INDEX_TYPE, diffIndexType)));
}
return null;
}
Collection<ValidationMessage> messages = new ArrayList<>();
String indexType = node.getPropertyValue(PN_TYPE).orElse("");
if (!"lucene".equals(indexType)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,76 @@ void testInvalidLuceneIndexDefinitions() {
));
}

@Test
void testValidDiffIndexDefinitions() {
AemCloudValidator validator = new AemCloudValidator(true, false, false, PackageType.CONTENT, null, ValidationMessageSeverity.ERROR);
List<DocViewProperty2> properties = Arrays.asList(
new DocViewProperty2(NameConstants.JCR_PRIMARYTYPE, "oak:QueryIndexDefinition"),
new DocViewProperty2(NameFactoryImpl.getInstance().create(Name.NS_DEFAULT_URI, "type"), "disabled"));
DocViewNode2 node = new DocViewNode2(NameConstants.JCR_ROOT, properties);
// diff.index control node, no version-suffix name, no compatVersion
NodeContext context = new NodeContextImpl("/oak:index/diff.index", Paths.get("_oak_index/test"),Paths.get("./jcr_root"));
Collection<ValidationMessage> messages = new ArrayList<>();
Optional.ofNullable(validator.validate(node, context, true)).ifPresent(messages::addAll);
MatcherAssert.assertThat(messages, Matchers.empty());
// diff.index.optimizer control node (Index Optimizer)
context = new NodeContextImpl("/oak:index/diff.index.optimizer", Paths.get("_oak_index/test"),Paths.get("./jcr_root"));
messages = new ArrayList<>();
Optional.ofNullable(validator.validate(node, context, true)).ifPresent(messages::addAll);
MatcherAssert.assertThat(messages, Matchers.empty());
}

@Test
void testInvalidDiffIndexType() {
AemCloudValidator validator = new AemCloudValidator(true, false, false, PackageType.CONTENT, null, ValidationMessageSeverity.ERROR);
List<DocViewProperty2> properties = Arrays.asList(
new DocViewProperty2(NameConstants.JCR_PRIMARYTYPE, "oak:QueryIndexDefinition"),
new DocViewProperty2(NameFactoryImpl.getInstance().create(Name.NS_DEFAULT_URI, "type"), "lucene"));
DocViewNode2 node = new DocViewNode2(NameConstants.JCR_ROOT, properties);
NodeContext context = new NodeContextImpl("/oak:index/diff.index", Paths.get("_oak_index/test"),Paths.get("./jcr_root"));
Collection<ValidationMessage> messages = new ArrayList<>();
Optional.ofNullable(validator.validate(node, context, true)).ifPresent(messages::addAll);
MatcherAssert.assertThat(messages, Matchers.contains(
new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(AemCloudValidator.VIOLATION_MESSAGE_INVALID_DIFF_INDEX_TYPE, "lucene"))
));
// same for the Index Optimizer control node
context = new NodeContextImpl("/oak:index/diff.index.optimizer", Paths.get("_oak_index/test"),Paths.get("./jcr_root"));
messages = new ArrayList<>();
Optional.ofNullable(validator.validate(node, context, true)).ifPresent(messages::addAll);
MatcherAssert.assertThat(messages, Matchers.contains(
new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(AemCloudValidator.VIOLATION_MESSAGE_INVALID_DIFF_INDEX_TYPE, "lucene"))
));
}

@Test
void testDiffIndexLookalikeNamesNotExempted() {
AemCloudValidator validator = new AemCloudValidator(true, false, false, PackageType.CONTENT, null, ValidationMessageSeverity.ERROR);
List<DocViewProperty2> properties = Arrays.asList(
new DocViewProperty2(NameConstants.JCR_PRIMARYTYPE, "oak:QueryIndexDefinition"),
new DocViewProperty2(NameFactoryImpl.getInstance().create(Name.NS_DEFAULT_URI, "type"), "lucene"),
new DocViewProperty2(NameFactoryImpl.getInstance().create(Name.NS_DEFAULT_URI, "compatVersion"), "2", PropertyType.LONG));
DocViewNode2 node = new DocViewNode2(NameConstants.JCR_ROOT, properties);
// a real, correctly named custom index whose name happens to start with "diff." must not be exempted
NodeContext context = new NodeContextImpl("/oak:index/diff.indexer-1-custom-1", Paths.get("_oak_index/test"),Paths.get("./jcr_root"));
Collection<ValidationMessage> messages = new ArrayList<>();
Optional.ofNullable(validator.validate(node, context, true)).ifPresent(messages::addAll);
MatcherAssert.assertThat(messages, Matchers.empty());
// a lucene index misnamed to look like an Index Optimizer diff must still go through the normal naming/type/compatVersion checks
context = new NodeContextImpl("/oak:index/diff.index.custom-index", Paths.get("_oak_index/test"),Paths.get("./jcr_root"));
messages = new ArrayList<>();
Optional.ofNullable(validator.validate(node, context, true)).ifPresent(messages::addAll);
MatcherAssert.assertThat(messages, Matchers.contains(
new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(AemCloudValidator.VIOLATION_MESSAGE_INVALID_INDEX_DEFINITION_NODE_NAME, "diff.index.custom-index"))
));
// the control node name below a different parent path must not be exempted either
context = new NodeContextImpl("/apps/foo/diff.index", Paths.get("_oak_index/test"),Paths.get("./jcr_root"));
messages = new ArrayList<>();
Optional.ofNullable(validator.validate(node, context, true)).ifPresent(messages::addAll);
MatcherAssert.assertThat(messages, Matchers.contains(
new ValidationMessage(ValidationMessageSeverity.ERROR, String.format(AemCloudValidator.VIOLATION_MESSAGE_INVALID_INDEX_DEFINITION_NODE_NAME, "diff.index"))
));
}

@Test
void testInvalidPropertyIndexDefinition() {
AemCloudValidator validator = new AemCloudValidator(true, false, false, PackageType.CONTENT, null, ValidationMessageSeverity.ERROR);
Expand Down
Loading