Skip to content

Commit d984a19

Browse files
committed
Add validator for embedded bundles
This closes #5
1 parent a9966f9 commit d984a19

File tree

4 files changed

+181
-0
lines changed

4 files changed

+181
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ There are several validators included in this artifact, all relate to namespacin
1919
1. [OSGi Configuration][osgi-installer-configurations]
2020
1. [Sling Resource Type and Resource Super Type][sling-resource-type] (`sling:resourceType` and `sling:resourceSuperType` properties)
2121
1. [AEM Client Library][aem-clientlibrary] (`categories` property)
22+
1. [Embedded Bundles][embedded] (the `Bundle-SymbolicName` of embedded bundles)
2223

2324
Namespacing has been explicitly mentioned in [Achim Koch's Blog: Hosting Multiple Tenants on AEM](https://blog.developer.adobe.com/hosting-multiple-tenants-on-aem-815c8ed0c9f9) but obviously namespacing is just one of multiple aspects to consider for multi-tenant AEM environments.
2425

@@ -48,6 +49,7 @@ Validator ID | Option | Description
4849
`netcentric-resourcetype-namespace` | `allowedTypePatterns` | Comma-separated list of regular expression patterns. Each `sling:resourceType` property of arbitrary JCR nodes must match at least one of the given patterns.
4950
`netcentric-resourcetype-namespace` | `allowedSuperTypePatterns` | Comma-separated list of regular expression patterns. Each `sling:resourceSuperType` property of arbitrary JCR nodes must match at least one of the given patterns.
5051
`netcentric-clientlibrary-namespace` | `allowedCategoryPatterns` | Comma-separated list of regular expression patterns. Each [client library's `categories` value][aem-clientlibrary] must match at least one of the given patterns.
52+
`netcentric-embedded-namespace` | `allowedBundleSymbolicNamePatterns` | Comma-separated list of regular expression patterns. Each embedded bundle in the package must have a `Bundle-SymbolicName` in its manifest which matches at least one of the given patterns.
5153

5254
*Due to the use of comma-separated strings it is not possible to use a comma within the regular expressions. However, as those are matched against names/paths (which don't allow a comma anyhow) using the comma inside the regular expressions shouldn't be necessary anyhow.*
5355

@@ -131,4 +133,5 @@ Adobe, and AEM are either registered trademarks or trademarks of Adobe in the Un
131133
[filevault-package-id]: https://jackrabbit.apache.org/filevault/properties.html
132134
[sling-resource-type]: https://sling.apache.org/documentation/the-sling-engine/resources.html#resource-types
133135
[oak-authorizables]: https://jackrabbit.apache.org/oak/docs/security/user/default.html#representation-in-the-repository
136+
[embedded]: https://jackrabbit.apache.org/filevault-package-maven-plugin/osgi.html#bundles-and-configurations
134137

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*-
2+
* #%L
3+
* AEM FileVault Content Package Namespace Validators
4+
* %%
5+
* Copyright (C) 2024 Cognizant Netcentric
6+
* %%
7+
* All rights reserved. This program and the accompanying materials are made available under the terms of the
8+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
9+
* https://www.eclipse.org/legal/epl-v20.html
10+
* SPDX-License-Identifier: EPL-2.0
11+
* #L%
12+
*/
13+
package biz.netcentric.filevault.validator.aem.namespace;
14+
15+
import java.io.IOException;
16+
import java.io.InputStream;
17+
import java.nio.file.Path;
18+
import java.util.Collection;
19+
import java.util.Collections;
20+
import java.util.Map;
21+
import java.util.Set;
22+
import java.util.Spliterators;
23+
import java.util.jar.JarInputStream;
24+
import java.util.jar.Manifest;
25+
import java.util.regex.Pattern;
26+
import java.util.stream.Collectors;
27+
import java.util.stream.StreamSupport;
28+
29+
import org.apache.jackrabbit.vault.validation.spi.GenericJcrDataValidator;
30+
import org.apache.jackrabbit.vault.validation.spi.ValidationMessage;
31+
import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
32+
import org.jetbrains.annotations.NotNull;
33+
import org.jetbrains.annotations.Nullable;
34+
35+
public class EmbeddedNamespaceValidator implements GenericJcrDataValidator {
36+
37+
private final ValidationMessageSeverity severity;
38+
private final Set<Pattern> allowedBundleSymbolicNamePatterns;
39+
40+
public EmbeddedNamespaceValidator(
41+
ValidationMessageSeverity severity, Set<Pattern> allowedBundleSymbolicNamePatterns) {
42+
super();
43+
this.severity = severity;
44+
this.allowedBundleSymbolicNamePatterns = allowedBundleSymbolicNamePatterns;
45+
}
46+
47+
@Override
48+
public @Nullable Collection<ValidationMessage> validateJcrData(
49+
@NotNull InputStream input,
50+
@NotNull Path filePath,
51+
@NotNull Path basePath,
52+
@NotNull Map<String, Integer> nodePathsAndLineNumbers)
53+
throws IOException {
54+
try (JarInputStream jarInputStream = new JarInputStream(input)) {
55+
String bundleSymbolicName = getBundleSymbolicName(jarInputStream.getManifest());
56+
if (allowedBundleSymbolicNamePatterns.stream()
57+
.noneMatch(pattern -> pattern.matcher(bundleSymbolicName).matches())) {
58+
return Collections.singleton(new ValidationMessage(
59+
severity,
60+
String.format(
61+
"Bundle-SymbolicName '%s' does not match any of the allowed patterns [%s]",
62+
bundleSymbolicName,
63+
allowedBundleSymbolicNamePatterns.stream()
64+
.map(Pattern::pattern)
65+
.collect(Collectors.joining(",")))));
66+
}
67+
}
68+
69+
return GenericJcrDataValidator.super.validateJcrData(input, filePath, basePath, nodePathsAndLineNumbers);
70+
}
71+
72+
String getBundleSymbolicName(Manifest manifest) {
73+
return manifest.getMainAttributes().getValue("Bundle-SymbolicName");
74+
}
75+
76+
@Override
77+
public boolean shouldValidateJcrData(@NotNull Path filePath, @NotNull Path basePath) {
78+
return isEmbeddedBundle(filePath);
79+
}
80+
81+
static boolean isEmbeddedBundle(@NotNull Path filePath) {
82+
if (!filePath.getName(0).toString().equals("apps")) {
83+
return false;
84+
}
85+
if (!filePath.getFileName().toString().endsWith(".jar")) {
86+
return false;
87+
}
88+
return StreamSupport.stream(Spliterators.spliterator(filePath.iterator(), filePath.getNameCount(), 0), false)
89+
.limit(5) // max depth
90+
.map(Path::getFileName)
91+
.map(Path::toString)
92+
.anyMatch(name -> name.startsWith("install.") || name.equals("install"));
93+
}
94+
95+
@Override
96+
public @Nullable Collection<ValidationMessage> done() {
97+
return null;
98+
}
99+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*-
2+
* #%L
3+
* AEM FileVault Content Package Namespace Validators
4+
* %%
5+
* Copyright (C) 2024 Cognizant Netcentric
6+
* %%
7+
* All rights reserved. This program and the accompanying materials are made available under the terms of the
8+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
9+
* https://www.eclipse.org/legal/epl-v20.html
10+
* SPDX-License-Identifier: EPL-2.0
11+
* #L%
12+
*/
13+
package biz.netcentric.filevault.validator.aem.namespace;
14+
15+
import java.util.Set;
16+
import java.util.regex.Pattern;
17+
18+
import org.apache.jackrabbit.vault.validation.spi.ValidationContext;
19+
import org.apache.jackrabbit.vault.validation.spi.Validator;
20+
import org.apache.jackrabbit.vault.validation.spi.ValidatorFactory;
21+
import org.apache.jackrabbit.vault.validation.spi.ValidatorSettings;
22+
import org.jetbrains.annotations.NotNull;
23+
import org.kohsuke.MetaInfServices;
24+
25+
@MetaInfServices(ValidatorFactory.class)
26+
public class EmbeddedNamespaceValidatorFactory extends AbstractPatternSettingsValidatorFactory {
27+
28+
public EmbeddedNamespaceValidatorFactory() {
29+
super("netcentric-embedded-namespace", "allowedBundleSymbolicNamePatterns", false);
30+
}
31+
32+
@Override
33+
protected Validator createValidator(
34+
@NotNull Set<Pattern> patterns, @NotNull ValidationContext context, @NotNull ValidatorSettings settings) {
35+
return new EmbeddedNamespaceValidator(settings.getDefaultSeverity(), patterns);
36+
}
37+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*-
2+
* #%L
3+
* AEM FileVault Content Package Namespace Validators
4+
* %%
5+
* Copyright (C) 2024 Cognizant Netcentric
6+
* %%
7+
* All rights reserved. This program and the accompanying materials are made available under the terms of the
8+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
9+
* https://www.eclipse.org/legal/epl-v20.html
10+
* SPDX-License-Identifier: EPL-2.0
11+
* #L%
12+
*/
13+
package biz.netcentric.filevault.validator.aem.namespace;
14+
15+
import java.nio.file.Paths;
16+
17+
import org.junit.jupiter.api.Test;
18+
19+
import static org.junit.jupiter.api.Assertions.*;
20+
21+
class EmbeddedNamespaceValidatorTest {
22+
23+
@Test
24+
void testIsEmbeddedBundle() {
25+
assertTrue(EmbeddedNamespaceValidator.isEmbeddedBundle(Paths.get("apps", "myapp", "install", "mybundle.jar")));
26+
// with run modes
27+
assertTrue(EmbeddedNamespaceValidator.isEmbeddedBundle(
28+
Paths.get("apps", "myapp", "install.author", "mybundle.jar")));
29+
assertTrue(EmbeddedNamespaceValidator.isEmbeddedBundle(
30+
Paths.get("apps", "myapp", "install.author.test", "mybundle.jar")));
31+
// outside /apps
32+
assertFalse(EmbeddedNamespaceValidator.isEmbeddedBundle(Paths.get("conf", "myapp", "install", "mybundle.jar")));
33+
// at max depth
34+
assertTrue(EmbeddedNamespaceValidator.isEmbeddedBundle(
35+
Paths.get("apps", "myapp", "child", "child", "install", "mybundle.jar")));
36+
// below max depth
37+
assertFalse(EmbeddedNamespaceValidator.isEmbeddedBundle(
38+
Paths.get("apps", "myapp", "child", "child", "child", "child", "install", "mybundle.jar")));
39+
// no jar
40+
assertFalse(EmbeddedNamespaceValidator.isEmbeddedBundle(Paths.get("apps", "myapp", "install", "mybundle.zip")));
41+
}
42+
}

0 commit comments

Comments
 (0)