Skip to content

Commit 4a9f978

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

File tree

10 files changed

+248
-3
lines changed

10 files changed

+248
-3
lines changed

README.md

Lines changed: 5 additions & 2 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

@@ -33,8 +34,8 @@ The following options are supported apart from the default settings mentioned in
3334
Leaving the validators with the default options will not emit validation issues at all, i.e. none of the options are mandatory.
3435

3536

36-
Validator ID | Option | Description
37-
--- | --- | ---
37+
Validator ID | Option | Description | Since
38+
--- | --- | --- | ---
3839
`netcentric-filter-namespace` | `allowedPathPatterns` | Comma-separated list of regular expression patterns. Each package filter `root` must match at least one of the given patterns.
3940
`netcentric-packageid-namespace` | `allowedGroupPatterns` | Comma-separated list of regular expression patterns. The package's group must match at least one of the given patterns.
4041
`netcentric-packageid-namespace` | `allowedNamePatterns` | Comma-separated list of regular expression patterns. The package's name must match at least one of the given patterns.
@@ -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. | 1.1.0
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

src/it/inside-namespace/container-package/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,23 @@
1616
<artifactId>filevault-package-maven-plugin</artifactId>
1717
<configuration>
1818
<packageType>container</packageType>
19+
<embeddeds>
20+
<embedded>
21+
<artifactId>commons-lang3</artifactId>
22+
<filter>true</filter>
23+
</embedded>
24+
</embeddeds>
25+
<embeddedTarget>/apps/mytenant/install</embeddedTarget>
1926
</configuration>
2027
</plugin>
2128
</plugins>
2229
</build>
30+
31+
<dependencies>
32+
<dependency>
33+
<groupId>org.apache.commons</groupId>
34+
<artifactId>commons-lang3</artifactId>
35+
<version>3.17.0</version>
36+
</dependency>
37+
</dependencies>
2338
</project>

src/it/inside-namespace/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@
9494
<allowedTypePatterns>/apps/mytenant2/components/.*</allowedTypePatterns>
9595
</options>
9696
</netcentric-resourcetype-namespace>
97+
<netcentric-embedded-namespace>
98+
<options>
99+
<allowedBundleSymbolicNamePatterns>org.apache.commons.lang3</allowedBundleSymbolicNamePatterns>
100+
</options>
101+
</netcentric-embedded-namespace>
97102
</validatorsSettings>
98103
</configuration>
99104
<dependencies>

src/it/no-config/container-package/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,23 @@
1616
<artifactId>filevault-package-maven-plugin</artifactId>
1717
<configuration>
1818
<packageType>container</packageType>
19+
<embeddeds>
20+
<embedded>
21+
<artifactId>commons-lang3</artifactId>
22+
<filter>true</filter>
23+
</embedded>
24+
</embeddeds>
25+
<embeddedTarget>/apps/mytenant/install</embeddedTarget>
1926
</configuration>
2027
</plugin>
2128
</plugins>
2229
</build>
30+
31+
<dependencies>
32+
<dependency>
33+
<groupId>org.apache.commons</groupId>
34+
<artifactId>commons-lang3</artifactId>
35+
<version>3.17.0</version>
36+
</dependency>
37+
</dependencies>
2338
</project>

src/it/outside-namespace/container-package/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,23 @@
1616
<artifactId>filevault-package-maven-plugin</artifactId>
1717
<configuration>
1818
<packageType>container</packageType>
19+
<embeddeds>
20+
<embedded>
21+
<artifactId>commons-lang3</artifactId>
22+
<filter>true</filter>
23+
</embedded>
24+
</embeddeds>
25+
<embeddedTarget>/apps/mytenant/install</embeddedTarget>
1926
</configuration>
2027
</plugin>
2128
</plugins>
2229
</build>
30+
31+
<dependencies>
32+
<dependency>
33+
<groupId>org.apache.commons</groupId>
34+
<artifactId>commons-lang3</artifactId>
35+
<version>3.17.0</version>
36+
</dependency>
37+
</dependencies>
2338
</project>

src/it/outside-namespace/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@
100100
<allowedTypePatterns>/apps/mytenant2/components/.*</allowedTypePatterns>
101101
</options>
102102
</netcentric-resourcetype-namespace>
103+
<netcentric-embedded-namespace>
104+
<options>
105+
<allowedBundleSymbolicNamePatterns>some-unused-prefix</allowedBundleSymbolicNamePatterns>
106+
</options>
107+
</netcentric-embedded-namespace>
103108
</validatorsSettings>
104109
</configuration>
105110
<dependencies>

src/it/outside-namespace/verify.groovy

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ assert buildLog.contains("""[ERROR] ValidationViolation: Filter root '/home/user
2323

2424
// container-package
2525
assert buildLog.contains("""[ERROR] ValidationViolation: Filter root '/apps/mytenant/config' is not allowed (does not match any of the allowed patterns [/apps/mytenant2(/.*)?,/conf/mytenant2(/.*)?,/home/users/mytenant2(/.*)?,/oak:index/mytenant2-(.*)]) @ META-INF${File.separator}vault${File.separator}filter.xml, validator: netcentric-filter-namespace
26+
[ERROR] ValidationViolation: Filter root '/apps/mytenant/install/commons-lang3-3.17.0.jar' is not allowed (does not match any of the allowed patterns [/apps/mytenant2(/.*)?,/conf/mytenant2(/.*)?,/home/users/mytenant2(/.*)?,/oak:index/mytenant2-(.*)]) @ META-INF${File.separator}vault${File.separator}filter.xml, validator: netcentric-filter-namespace
2627
[ERROR] ValidationViolation: Package group 'biz.netcentric.filevault.validator.aem.namespace.it' is not allowed (does not match any of the group patterns [invalid-group]) @ META-INF${File.separator}vault${File.separator}properties.xml, validator: netcentric-packageid-namespace
2728
[ERROR] ValidationViolation: Package name 'container-package' is not allowed (does not match any of the name patterns [invalid-name]) @ META-INF${File.separator}vault${File.separator}properties.xml, validator: netcentric-packageid-namespace
2829
[ERROR] ValidationViolation: OSGi configuration PID 'com.example.mytenant.MyComponent2' is not allowed to be configured (does not match any of the allowed patterns [com\\.example\\.mytenant2\\..*]) @ jcr_root${File.separator}apps${File.separator}mytenant${File.separator}config${File.separator}com.example.mytenant.MyComponent2.cfg.json, validator: jackrabbit-osgiconfigparser
2930
[ERROR] ValidationViolation: OSGi configuration PID 'com.example.mytenant.MyComponent' is not allowed to be configured (does not match any of the allowed patterns [com\\.example\\.mytenant2\\..*]) @ jcr_root${File.separator}apps${File.separator}mytenant${File.separator}config${File.separator}com.example.mytenant.MyComponent~name.cfg.json, validator: jackrabbit-osgiconfigparser
30-
[ERROR] ValidationViolation: OSGi factory configuration PID 'com.example.mytenant.MyComponent' is not allowed with the given subname 'name' (does not match any of the allowed patterns [othername.*]) @ jcr_root${File.separator}apps${File.separator}mytenant${File.separator}config${File.separator}com.example.mytenant.MyComponent~name.cfg.json, validator: jackrabbit-osgiconfigparser""") : 'container-package'
31+
[ERROR] ValidationViolation: OSGi factory configuration PID 'com.example.mytenant.MyComponent' is not allowed with the given subname 'name' (does not match any of the allowed patterns [othername.*]) @ jcr_root${File.separator}apps${File.separator}mytenant${File.separator}config${File.separator}com.example.mytenant.MyComponent~name.cfg.json, validator: jackrabbit-osgiconfigparser
32+
[ERROR] ValidationViolation: Bundle-SymbolicName 'org.apache.commons.lang3' does not match any of the allowed patterns [some-unused-prefix] @ jcr_root${File.separator}apps${File.separator}mytenant${File.separator}install${File.separator}commons-lang3-3.17.0.jar, validator: netcentric-embedded-namespace""") : 'container-package'
3133

3234
return true
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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 (bundleSymbolicName == null) {
57+
return Collections.singleton(new ValidationMessage(
58+
ValidationMessageSeverity.WARN,
59+
"Either no manifest or no Bundle-SymbolicName header found in manifest. Skip evaluation!"));
60+
}
61+
if (allowedBundleSymbolicNamePatterns.stream()
62+
.noneMatch(pattern -> pattern.matcher(bundleSymbolicName).matches())) {
63+
return Collections.singleton(new ValidationMessage(
64+
severity,
65+
String.format(
66+
"Bundle-SymbolicName '%s' does not match any of the allowed patterns [%s]",
67+
bundleSymbolicName,
68+
allowedBundleSymbolicNamePatterns.stream()
69+
.map(Pattern::pattern)
70+
.collect(Collectors.joining(",")))));
71+
}
72+
}
73+
return null;
74+
}
75+
76+
String getBundleSymbolicName(Manifest manifest) {
77+
if (manifest == null) {
78+
return null;
79+
}
80+
return manifest.getMainAttributes().getValue("Bundle-SymbolicName");
81+
}
82+
83+
@Override
84+
public boolean shouldValidateJcrData(@NotNull Path filePath, @NotNull Path basePath) {
85+
return isEmbeddedBundle(filePath);
86+
}
87+
88+
static boolean isEmbeddedBundle(@NotNull Path filePath) {
89+
if (!filePath.getName(0).toString().equals("apps")) {
90+
return false;
91+
}
92+
if (!filePath.getFileName().toString().endsWith(".jar")) {
93+
return false;
94+
}
95+
return StreamSupport.stream(Spliterators.spliterator(filePath.iterator(), filePath.getNameCount(), 0), false)
96+
.limit(5) // max depth
97+
.map(Path::getFileName)
98+
.map(Path::toString)
99+
.anyMatch(name -> name.startsWith("install.") || name.equals("install"));
100+
}
101+
102+
@Override
103+
public @Nullable Collection<ValidationMessage> done() {
104+
return null;
105+
}
106+
}
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)