Skip to content

Commit a02f63a

Browse files
authored
Update index definition naming validation (#34)
Adobe updated the policy in https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/operations/indexing#preparing-the-new-index-definition Add unit tests This closes #33
1 parent 7ea6c1e commit a02f63a

File tree

4 files changed

+90
-22
lines changed

4 files changed

+90
-22
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@ Currently only Oak index definitions of type `lucene` are supported in AEMaaCS.
7373

7474
## Follow naming policy for Oak index definition node names
7575

76-
There is a mandatory naming policy for Oak index definition node names which enforces them to end with `-custom-<version-as-integer>`. The format is used in [`IndexName`](https://github.com/apache/jackrabbit-oak/blob/08c7b20e0676739d9c445b5249c3f71004b6b894/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexName.java#L36) and allows for upgrades of existing index definitions in blue/green deployments.
76+
There is a mandatory naming policy for Oak index definition node names which enforces them to either comply with pattern `<indexName>-<productVersion>-custom-<customVersion>` (customized OOTB index) or `<prefix>.<indexName>-<productVersion>-custom-<customVersion>` (fully customized index). The format is used in [`IndexName`](https://github.com/apache/jackrabbit-oak/blob/08c7b20e0676739d9c445b5249c3f71004b6b894/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexName.java#L36) and allows for upgrades of existing index definitions in blue/green deployments.
7777

78-
Further details in <https://experienceleague.adobe.com/docs/experience-manager-cloud-service/operations/indexing.html?lang=en#changes-in-aem-as-a-cloud-service>.
78+
Further details in <https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/operations/indexing#preparing-the-new-index-definition>.
7979

8080
# Usage with Maven
8181

82-
You can use this validator with the [FileVault Package Maven Plugin][3] in version 1.1.0 or higher like this
82+
You can use this validator with the [FileVault Package Maven Plugin][3] in version 1.4.0 or higher like this
8383

8484
```
8585
<plugin>

pom.xml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151

5252
<properties>
5353
<maven.version>3.5.4</maven.version>
54+
<jackrabbit.version>2.20.8</jackrabbit.version><!-- minimum Jackrabbit API version supported by the referenced filevault artifact -->
5455
<java.target.version>8</java.target.version> <!-- used for compiler plugin, javadoc plugin and animal-sniffer, for compatibility reasons with Java 9 only the values 6,7,8 and 9 is allowed -->
5556
<maven.compiler.release>${java.target.version}</maven.compiler.release>
5657
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -61,7 +62,7 @@
6162
<dependency>
6263
<groupId>org.apache.jackrabbit.vault</groupId>
6364
<artifactId>vault-validation</artifactId>
64-
<version>3.4.2</version>
65+
<version>3.8.2</version>
6566
</dependency>
6667
<dependency>
6768
<groupId>org.slf4j</groupId>
@@ -101,18 +102,22 @@
101102
<scope>test</scope>
102103
</dependency>
103104
<!-- only transitive dependencies of 'vault-validation' but must be declared due to https://issues.apache.org/jira/browse/JCRVLT-394 -->
105+
<dependency>
106+
<groupId>org.apache.jackrabbit</groupId>
107+
<artifactId>jackrabbit-spi-commons</artifactId>
108+
<version>${jackrabbit.version}</version>
109+
</dependency>
110+
<dependency>
111+
<groupId>org.apache.jackrabbit</groupId>
112+
<artifactId>jackrabbit-jcr-commons</artifactId>
113+
<version>${jackrabbit.version}</version>
114+
</dependency>
104115
<dependency>
105116
<groupId>javax.jcr</groupId>
106117
<artifactId>jcr</artifactId>
107118
<version>2.0</version>
108119
<scope>test</scope>
109120
</dependency>
110-
<dependency>
111-
<groupId>org.apache.jackrabbit</groupId>
112-
<artifactId>jackrabbit-jcr-commons</artifactId>
113-
<version>2.20.0</version>
114-
<scope>test</scope>
115-
</dependency>
116121
<dependency>
117122
<groupId>org.apache.jackrabbit</groupId>
118123
<artifactId>oak-jackrabbit-api</artifactId>

src/main/java/biz/netcentric/filevault/validator/aem/cloud/AemCloudValidator.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@
2222
import java.util.Collections;
2323
import java.util.regex.Pattern;
2424

25+
import org.apache.jackrabbit.spi.Name;
26+
import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
27+
import org.apache.jackrabbit.util.Text;
2528
import org.apache.jackrabbit.vault.packaging.PackageProperties;
2629
import org.apache.jackrabbit.vault.packaging.PackageType;
2730
import org.apache.jackrabbit.vault.util.Constants;
28-
import org.apache.jackrabbit.vault.util.DocViewNode;
31+
import org.apache.jackrabbit.vault.util.DocViewNode2;
2932
import org.apache.jackrabbit.vault.validation.spi.DocumentViewXmlValidator;
3033
import org.apache.jackrabbit.vault.validation.spi.MetaInfPathValidator;
34+
import org.apache.jackrabbit.vault.validation.spi.NodeContext;
3135
import org.apache.jackrabbit.vault.validation.spi.NodePathValidator;
3236
import org.apache.jackrabbit.vault.validation.spi.PropertiesValidator;
3337
import org.apache.jackrabbit.vault.validation.spi.ValidationContext;
@@ -40,15 +44,19 @@ public class AemCloudValidator implements NodePathValidator, MetaInfPathValidato
4044

4145
static final String VIOLATION_MESSAGE_READONLY_MUTABLE_PATH = "Using mutable nodes in this repository location is only allowed in author-specific packages as it is not writable by the underlying service user on a publish instance. Consider to use repoinit scripts instead or move that content to another location. Further details at https://experienceleague.adobe.com/docs/experience-manager-learn/cloud-service/debugging/debugging-aem-as-a-cloud-service/build-and-deployment.html?lang=en#including-%2Fvar-in-content-package";
4246
static final String VIOLATION_MESSAGE_INSTALL_HOOK_IN_MUTABLE_PACKAGE = "Using install hooks in mutable content packages leads to deployment failures as the underlying service user on the publish does not have the right to execute those.";
43-
static final String VIOLATION_MESSAGE_INVALID_INDEX_DEFINITION_NODE_NAME = "All Oak index definition node names must end with '-custom-<integer>' but found name '%s'. Further details at https://experienceleague.adobe.com/docs/experience-manager-cloud-service/operations/indexing.html?lang=en#how-to-use";
47+
static final String VIOLATION_MESSAGE_INVALID_INDEX_DEFINITION_NODE_NAME = "All Oak index definition node names must follow scheme '<indexName>-<productVersion>-custom-<customVersion>' (for a customized OOTB index) or '<prefix>.<indexName>-<productVersion>-custom-<customVersion>' (for a fully customized index) but found name '%s'. Further details at https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/operations/indexing#preparing-the-new-index-definition";
4448
static final String VIOLATION_MESSAGE_LIBS_NODES = "Nodes below '/libs' may be overwritten by future product upgrades. Rather use '/apps'. Further details at https://experienceleague.adobe.com/docs/experience-manager-cloud-service/implementing/developing/full-stack/overlays.html?lang=en#developing";
4549
static final String VIOLATION_MESSAGE_MUTABLE_NODES_IN_MIXED_PACKAGE = "Mutable nodes in mixed package types are not installed!";
4650
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!";
4751
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/docs/experience-manager-cloud-service/operations/indexing.html?lang=en#changes-in-aem-as-a-cloud-service";
4852

4953
// this path is relative to META-INF
5054
private static final Path INSTALL_HOOK_PATH = Paths.get(Constants.VAULT_DIR, Constants.HOOKS_DIR);
51-
private static final Pattern INDEX_DEFINITION_NAME_PATTERN = Pattern.compile(".*-custom-\\d++");
55+
/**
56+
* The allowed patterns are defined in https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/operations/indexing#preparing-the-new-index-definition
57+
*/
58+
private static final Pattern INDEX_DEFINITION_NAME_PATTERN = Pattern.compile(".*-\\d++-custom-\\d++");
59+
5260
private static final Collection<String> IMMUTABLE_PATH_PREFIXES = Arrays.asList("/apps", "/libs", "/oak:index");
5361
private static final Collection<String> WRITABLE_PATHS_BY_DISTRIBUTION_IMPORTER = Arrays.asList(
5462
"/content", // access provided by system user content-writer-service and sling-distribution-importer
@@ -69,6 +77,7 @@ public class AemCloudValidator implements NodePathValidator, MetaInfPathValidato
6977
private boolean hasImmutableNodes;
7078

7179
private static final int MAX_NUM_VIOLATIONS_PER_TYPE = 5;
80+
private static final Name PN_TYPE = NameFactoryImpl.getInstance().create(Name.NS_DEFAULT_URI, "type");
7281
private int numVarNodeViolations = 0;
7382
private int numLibNodeViolations = 0;
7483
private int numMutableNodeViolations = 0;
@@ -196,19 +205,19 @@ static boolean isPackagePathInstalledConditionally(String runMode, Path packageR
196205
}
197206

198207
@Override
199-
public @Nullable Collection<ValidationMessage> validate(@NotNull DocViewNode node, @NotNull String nodePath,
200-
@NotNull Path filePath, boolean isRoot) {
201-
if ("oak:QueryIndexDefinition".equals(node.primary)) {
208+
public @Nullable Collection<ValidationMessage> validate(@NotNull DocViewNode2 node, @NotNull NodeContext nodeContext, boolean isRoot) {
209+
if ("oak:QueryIndexDefinition".equals(node.getPrimaryType().orElse(""))) {
202210
Collection<ValidationMessage> messages = new ArrayList<>();
203-
String indexType = node.getValue("{}type");
211+
String indexType = node.getPropertyValue(PN_TYPE).orElse("");
204212
if (!"lucene".equals(indexType)) {
205213
messages.add(new ValidationMessage(defaultSeverity,
206214
String.format(VIOLATION_MESSAGE_NON_LUCENE_TYPE_INDEX_DEFINITION, indexType)));
207215
}
208-
// check node name
209-
if (!INDEX_DEFINITION_NAME_PATTERN.matcher(node.name).matches()) {
216+
// check node name (jcr qualified name as contained in the path)
217+
String qualifiedName = Text.getName(nodeContext.getNodePath());
218+
if (!INDEX_DEFINITION_NAME_PATTERN.matcher(qualifiedName).matches()) {
210219
messages.add(new ValidationMessage(defaultSeverity,
211-
String.format(VIOLATION_MESSAGE_INVALID_INDEX_DEFINITION_NODE_NAME, node.name)));
220+
String.format(VIOLATION_MESSAGE_INVALID_INDEX_DEFINITION_NODE_NAME, qualifiedName)));
212221
}
213222
return messages;
214223
}

src/test/java/biz/netcentric/filevault/validator/aem/cloud/AemCloudValidatorTest.java

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,25 @@
1515

1616
import java.nio.file.Paths;
1717
import java.util.ArrayList;
18+
import java.util.Arrays;
1819
import java.util.Collection;
20+
import java.util.List;
1921
import java.util.Optional;
2022

23+
import org.apache.jackrabbit.spi.Name;
24+
import org.apache.jackrabbit.spi.commons.name.NameConstants;
25+
import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
2126
import org.apache.jackrabbit.vault.packaging.PackageType;
27+
import org.apache.jackrabbit.vault.util.DocViewNode2;
28+
import org.apache.jackrabbit.vault.util.DocViewProperty2;
29+
import org.apache.jackrabbit.vault.validation.spi.NodeContext;
2230
import org.apache.jackrabbit.vault.validation.spi.ValidationMessage;
2331
import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
32+
import org.apache.jackrabbit.vault.validation.spi.util.NodeContextImpl;
2433
import org.junit.jupiter.api.Assertions;
2534
import org.junit.jupiter.api.Test;
2635

27-
import static biz.netcentric.filevault.validator.aem.cloud.AemCloudValidator.VIOLATION_MESSAGE_INSTALL_HOOK_IN_MUTABLE_PACKAGE;
36+
import static org.junit.jupiter.api.Assertions.assertEquals;
2837

2938
class AemCloudValidatorTest {
3039

@@ -75,7 +84,7 @@ void testAllowHooksInMutableContent() {
7584
Collection<ValidationMessage> messages = new ArrayList<>();
7685
Optional.ofNullable(validator.validateMetaInfPath(Paths.get("vault/hooks/install-hook.jar"))).ifPresent(messages::addAll);
7786
Optional.ofNullable(validator.done()).ifPresent(messages::addAll);
78-
Assertions.assertTrue(messages.stream().anyMatch(message -> message.getMessage().equals(VIOLATION_MESSAGE_INSTALL_HOOK_IN_MUTABLE_PACKAGE)));
87+
Assertions.assertTrue(messages.stream().anyMatch(message -> message.getMessage().equals(AemCloudValidator.VIOLATION_MESSAGE_INSTALL_HOOK_IN_MUTABLE_PACKAGE)));
7988

8089
validator = new AemCloudValidator(true, false, true, PackageType.CONTENT, null, ValidationMessageSeverity.ERROR);
8190
messages = new ArrayList<>();
@@ -84,4 +93,49 @@ void testAllowHooksInMutableContent() {
8493
Assertions.assertTrue(messages.isEmpty());
8594
}
8695

96+
@Test
97+
void testValidIndexDefinitions() {
98+
AemCloudValidator validator = new AemCloudValidator(true, false, false, PackageType.CONTENT, null, ValidationMessageSeverity.ERROR);
99+
Collection<ValidationMessage> messages = new ArrayList<>();
100+
List<DocViewProperty2> properties = Arrays.asList(
101+
new DocViewProperty2(NameConstants.JCR_PRIMARYTYPE, "oak:QueryIndexDefinition"),
102+
new DocViewProperty2(NameFactoryImpl.getInstance().create(Name.NS_DEFAULT_URI, "type"), "lucene"));
103+
// valid lucene index definition
104+
NodeContext context = new NodeContextImpl("/oak:index/prefix.myindex-1-custom-1", Paths.get("_oak_index/test"),Paths.get("./jcr_root"));
105+
DocViewNode2 node = new DocViewNode2(NameConstants.JCR_ROOT, properties);
106+
Optional.ofNullable(validator.validate(node, context, true)).ifPresent(messages::addAll);
107+
Assertions.assertTrue(messages.isEmpty());
108+
context = new NodeContextImpl("/oak:index/productindex-1-custom-1", Paths.get("_oak_index/test"),Paths.get("./jcr_root"));
109+
Optional.ofNullable(validator.validate(node, context, true)).ifPresent(messages::addAll);
110+
Assertions.assertTrue(messages.isEmpty());
111+
}
112+
113+
@Test
114+
void testInvalidLuceneIndexDefinitions() {
115+
AemCloudValidator validator = new AemCloudValidator(true, false, false, PackageType.CONTENT, null, ValidationMessageSeverity.ERROR);
116+
Collection<ValidationMessage> messages = new ArrayList<>();
117+
List<DocViewProperty2> properties = Arrays.asList(
118+
new DocViewProperty2(NameConstants.JCR_PRIMARYTYPE, "oak:QueryIndexDefinition"),
119+
new DocViewProperty2(NameFactoryImpl.getInstance().create(Name.NS_DEFAULT_URI, "type"), "lucene"));
120+
NodeContext context = new NodeContextImpl("/oak:index/myindex", Paths.get("_oak_index/test"),Paths.get("./jcr_root"));
121+
DocViewNode2 node = new DocViewNode2(NameConstants.JCR_ROOT, properties);
122+
Optional.ofNullable(validator.validate(node, context, true)).ifPresent(messages::addAll);
123+
assertEquals(1, messages.size());
124+
assertEquals(String.format(AemCloudValidator.VIOLATION_MESSAGE_INVALID_INDEX_DEFINITION_NODE_NAME, "myindex"), messages.iterator().next().getMessage());
125+
}
126+
127+
@Test
128+
void testInvalidPropertyIndexDefinition() {
129+
AemCloudValidator validator = new AemCloudValidator(true, false, false, PackageType.CONTENT, null, ValidationMessageSeverity.ERROR);
130+
Collection<ValidationMessage> messages = new ArrayList<>();
131+
List<DocViewProperty2> properties = Arrays.asList(
132+
new DocViewProperty2(NameConstants.JCR_PRIMARYTYPE, "oak:QueryIndexDefinition"),
133+
new DocViewProperty2(NameFactoryImpl.getInstance().create(Name.NS_DEFAULT_URI, "type"), "property"));
134+
// invalid property index definition
135+
DocViewNode2 node = new DocViewNode2(NameConstants.JCR_ROOT, properties);
136+
NodeContext context = new NodeContextImpl("/oak:index/prefix.myindex-1-custom-1", Paths.get("_oak_index/test"),Paths.get("./jcr_root"));
137+
Optional.ofNullable(validator.validate(node, context, true)).ifPresent(messages::addAll);
138+
assertEquals(1, messages.size());
139+
assertEquals(String.format(AemCloudValidator.VIOLATION_MESSAGE_NON_LUCENE_TYPE_INDEX_DEFINITION, "property"), messages.iterator().next().getMessage());
140+
}
87141
}

0 commit comments

Comments
 (0)