Merge pull request #16 from apache/SLING-8251_merged_after_releases
SLING-8251 - Support checking dependencies for content packages
diff --git a/pom.xml b/pom.xml
index 775f419..3fdeb9a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -143,7 +143,33 @@
<version>1.11.0</version>
<scope>provided</scope>
</dependency>
-
+
+ <!-- Content-Package check -->
+ <dependency>
+ <groupId>org.apache.jackrabbit.vault</groupId>
+ <artifactId>org.apache.jackrabbit.vault</artifactId>
+ <version>3.2.6</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-spi-commons</artifactId>
+ <version>2.19.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.jcr</groupId>
+ <artifactId>jcr</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.6</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- END Content-Package check -->
+
<!-- Testing -->
<dependency>
<groupId>junit</groupId>
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesDependencies.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesDependencies.java
new file mode 100644
index 0000000..b3ee952
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesDependencies.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.analyser.task.impl;
+
+import java.io.File;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
+import java.util.Set;
+
+import org.apache.jackrabbit.vault.packaging.Dependency;
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.PackageManager;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl;
+import org.apache.sling.feature.analyser.task.AnalyserTask;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.apache.sling.feature.io.IOUtils;
+import org.apache.sling.feature.scanner.ArtifactDescriptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CheckContentPackagesDependencies implements AnalyserTask {
+
+ private final PackageManager packageManager = new PackageManagerImpl();
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ @Override
+ public String getId() {
+ return "content-packages-dependencies";
+ }
+
+ @Override
+ public String getName() {
+ return "Content-packages dependencies checker";
+ }
+
+ @Override
+ public void execute(AnalyserTaskContext ctx) throws Exception {
+ Set<ArtifactDescriptor> descriptors = ctx.getFeatureDescriptor().getArtifactDescriptors();
+ if (descriptors == null || descriptors.isEmpty()) {
+ return;
+ }
+
+ Map<PackageId, Dependency[]> dependenciesMap = new HashMap<>();
+
+ for (ArtifactDescriptor descriptor : descriptors) {
+ onDescriptor(ctx, descriptor, dependenciesMap);
+ }
+
+ for (PackageId contentPackageId : dependenciesMap.keySet()) {
+ verifyDependenciesTree(ctx, contentPackageId, dependenciesMap);
+ }
+ }
+
+ private void onDescriptor(AnalyserTaskContext ctx, ArtifactDescriptor descriptor, Map<PackageId, Dependency[]> dependenciesMap) throws Exception {
+ URL resourceUrl = descriptor.getArtifactFile();
+ File artifactFile = IOUtils.getFileFromURL(resourceUrl, true, null);
+ if (!artifactFile.exists() || !artifactFile.isFile()) {
+ ctx.reportError("Artifact file " + artifactFile + " does not exist or it is not a file");
+ return;
+ }
+
+ try (VaultPackage vaultPackage = packageManager.open(artifactFile, true)) {
+ PackageId packageId = vaultPackage.getId();
+
+ logger.debug("Collecting " + packageId + " dependencies...");
+
+ dependenciesMap.put(packageId, vaultPackage.getDependencies());
+
+ logger.debug(packageId + " dependencies collected.");
+ }
+ }
+
+ private void verifyDependenciesTree(AnalyserTaskContext ctx, PackageId root, Map<PackageId, Dependency[]> dependenciesMap) {
+ logger.debug("Verifying " + root + " transitive dependencies...");
+
+ Queue<Dependency> toBeVisited = new LinkedList<>();
+ enqueue(toBeVisited, dependenciesMap.get(root));
+
+ Set<Dependency> alreadyVisited = new HashSet<>();
+
+ while (!toBeVisited.isEmpty()) {
+ Dependency current = toBeVisited.poll();
+
+ if (alreadyVisited.add(current)) {
+ boolean found = false;
+
+ for (Entry<PackageId, Dependency[]> entry : dependenciesMap.entrySet()) {
+ if (current.matches(entry.getKey())) {
+ enqueue(toBeVisited, entry.getValue());
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ ctx.reportError("Missing " + current + " dependency for " + root);
+ }
+ }
+ }
+ }
+
+ private static void enqueue(Queue<Dependency> target, Dependency...dependencies) {
+ if (dependencies == null) {
+ return;
+ }
+
+ for (Dependency dependency : dependencies) {
+ target.offer(dependency);
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java
index b2f0be1..09f7781 100644
--- a/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java
@@ -208,27 +208,19 @@
}
}
} finally {
- deleteRecursive(tempDir);
+ deleteOnExitRecursive(tempDir);
}
}
- private boolean deleteRecursive(File file) {
+ private void deleteOnExitRecursive(File file) {
+ file.deleteOnExit();
if (file.isDirectory()) {
File[] childs = file.listFiles();
if (childs != null) {
for (File child : childs) {
- if (!deleteRecursive(child)) {
- return false;
- }
+ deleteOnExitRecursive(child);
}
- return file.delete();
}
- else {
- return false;
- }
- }
- else {
- return file.delete();
}
}
diff --git a/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask b/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask
index 8c8f112..42c29dd 100644
--- a/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask
+++ b/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask
@@ -7,3 +7,4 @@
org.apache.sling.feature.analyser.task.impl.CheckApiRegionsDependencies
org.apache.sling.feature.analyser.task.impl.CheckApiRegionsDuplicates
org.apache.sling.feature.analyser.task.impl.CheckApiRegionsOrder
+org.apache.sling.feature.analyser.task.impl.CheckContentPackagesDependencies
diff --git a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesDependenciesTest.java b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesDependenciesTest.java
new file mode 100644
index 0000000..a2d961f
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesDependenciesTest.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.analyser.task.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.analyser.task.AnalyserTask;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.apache.sling.feature.scanner.ArtifactDescriptor;
+import org.apache.sling.feature.scanner.FeatureDescriptor;
+import org.apache.sling.feature.scanner.impl.FeatureDescriptorImpl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CheckContentPackagesDependenciesTest {
+
+ private AnalyserTask task;
+
+ @Before
+ public void setUp() {
+ task = new CheckContentPackagesDependencies();
+ }
+
+ @After
+ public void tearDown() {
+ task = null;
+ }
+
+ @Test
+ public void dependenciesNotResolvedThrowsError() throws Exception {
+ List<String> errors = execute("test_a-1.0.zip");
+
+ assertFalse(errors.isEmpty());
+
+ Iterator<String> errorsIterator = errors.iterator();
+ assertEquals("Missing my_packages:test_b dependency for my_packages:test_a:1.0", errorsIterator.next());
+ assertEquals("Missing my_packages:test_c:[1.0,2.0) dependency for my_packages:test_a:1.0", errorsIterator.next());
+ }
+
+ @Test
+ public void dependenciesResolved() throws Exception {
+ List<String> errors = execute("test_a-1.0.zip",
+ "test_b-1.0.zip",
+ "test_c-1.0.zip",
+ "test_d-1.0.zip",
+ "test_e-1.0.zip");
+ assertTrue(errors.isEmpty());
+ }
+
+ private List<String> execute(String...resources) throws Exception {
+ Feature feature = mock(Feature.class);
+ when(feature.getId()).thenReturn(new ArtifactId("org.apache.sling.testing",
+ "org.apache.sling.testing.contentpackages",
+ "1.0.0",
+ null,
+ null));
+ FeatureDescriptor featureDescriptor = new FeatureDescriptorImpl(feature);
+
+ for (String resource : resources) {
+ ArtifactId id = mock(ArtifactId.class);
+ Artifact artifact = mock(Artifact.class);
+ when(artifact.getId()).thenReturn(id);
+
+ ArtifactDescriptor descriptor = mock(ArtifactDescriptor.class);
+ when(descriptor.getArtifact()).thenReturn(artifact);
+ when(descriptor.getArtifactFile()).thenReturn(getClass().getClassLoader().getResource(resource));
+
+ featureDescriptor.getArtifactDescriptors().add(descriptor);
+ }
+
+ AnalyserTaskContext ctx = mock(AnalyserTaskContext.class);
+
+ when(ctx.getFeatureDescriptor()).thenReturn(featureDescriptor);
+
+ List<String> errors = new LinkedList<>();
+
+ doAnswer(invocation -> {
+ String error = invocation.getArgument(0);
+ errors.add(error);
+ return null;
+ }).when(ctx).reportError(anyString());
+
+ task.execute(ctx);
+
+ return errors;
+ }
+
+}
diff --git a/src/test/resources/test_a-1.0.zip b/src/test/resources/test_a-1.0.zip
new file mode 100644
index 0000000..08df03a
--- /dev/null
+++ b/src/test/resources/test_a-1.0.zip
Binary files differ
diff --git a/src/test/resources/test_b-1.0.zip b/src/test/resources/test_b-1.0.zip
new file mode 100644
index 0000000..85fac13
--- /dev/null
+++ b/src/test/resources/test_b-1.0.zip
Binary files differ
diff --git a/src/test/resources/test_c-1.0.zip b/src/test/resources/test_c-1.0.zip
new file mode 100644
index 0000000..245291a
--- /dev/null
+++ b/src/test/resources/test_c-1.0.zip
Binary files differ
diff --git a/src/test/resources/test_d-1.0.zip b/src/test/resources/test_d-1.0.zip
new file mode 100644
index 0000000..7acf3d1
--- /dev/null
+++ b/src/test/resources/test_d-1.0.zip
Binary files differ
diff --git a/src/test/resources/test_e-1.0.zip b/src/test/resources/test_e-1.0.zip
new file mode 100644
index 0000000..372bbb9
--- /dev/null
+++ b/src/test/resources/test_e-1.0.zip
Binary files differ