This is an automated email from the ASF dual-hosted git repository.
kwin pushed a commit to branch master
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-cpconverter.git
The following commit(s) were added to refs/heads/master by this push:
new 1b7f2e9 SLING-10205 keep custom properties in converted packages (#62)
1b7f2e9 is described below
commit 1b7f2e900f1474809b66b57c0564e1727ff8c8de
Author: Konrad Windszus <[email protected]>
AuthorDate: Tue Mar 16 15:31:33 2021 +0100
SLING-10205 keep custom properties in converted packages (#62)
---
.../ContentPackage2FeatureModelConverter.java | 11 ++-
...ntentPackage2FeatureModelConverterLauncher.java | 3 +
.../cpconverter/vltpkg/VaultPackageAssembler.java | 84 +++++++++-------
.../ContentPackage2FeatureModelConverterTest.java | 109 +++++++++++++++++++--
.../vltpkg/VaultPackageAssemblerTest.java | 4 +-
.../cpconverter/test-with-install-hooks.zip | Bin 0 -> 17655 bytes
6 files changed, 165 insertions(+), 46 deletions(-)
diff --git
a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 090865e..ebd0c33 100644
---
a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++
b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -94,6 +94,8 @@ public class ContentPackage2FeatureModelConverter extends
BaseVaultPackageScanne
private boolean dropContent = false;
+ private boolean removeInstallHooks = false;
+
private final File tmpDirectory;
public ContentPackage2FeatureModelConverter() {
@@ -166,6 +168,11 @@ public class ContentPackage2FeatureModelConverter extends
BaseVaultPackageScanne
return this;
}
+ public @NotNull ContentPackage2FeatureModelConverter
setRemoveInstallHooks(boolean removeInstallHook) {
+ this.removeInstallHooks = removeInstallHook;
+ return this;
+ }
+
public File getTempDirectory() {
return this.tmpDirectory;
}
@@ -227,7 +234,7 @@ public class ContentPackage2FeatureModelConverter extends
BaseVaultPackageScanne
for (VaultPackage vaultPackage : orderedContentPackages) {
try {
emitter.startPackage(vaultPackage);
- mainPackageAssembler =
VaultPackageAssembler.create(this.getTempDirectory(), vaultPackage);
+ mainPackageAssembler =
VaultPackageAssembler.create(this.getTempDirectory(), vaultPackage,
removeInstallHooks);
assemblers.add(mainPackageAssembler);
ArtifactId mvnPackageId = toArtifactId(vaultPackage);
@@ -309,7 +316,7 @@ public class ContentPackage2FeatureModelConverter extends
BaseVaultPackageScanne
PackageId originalPackageId = vaultPackage.getId();
ArtifactId mvnPackageId = toArtifactId(vaultPackage);
- VaultPackageAssembler clonedPackage =
VaultPackageAssembler.create(this.getTempDirectory(), vaultPackage);
+ VaultPackageAssembler clonedPackage =
VaultPackageAssembler.create(this.getTempDirectory(), vaultPackage,
removeInstallHooks);
// Please note: THIS IS A HACK to meet the new requirement without
drastically change the original design
// temporary swap the main handler to collect stuff
diff --git
a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
index 61dd3c5..eb45068 100644
---
a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
+++
b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
@@ -104,6 +104,9 @@ public final class
ContentPackage2FeatureModelConverterLauncher implements Runna
@Option(names = { "--entry-handler-config" }, description = "Config for
entry handlers that support it (classname:<config-string>", required = false)
private List<String> entryHandlerConfigs = null;
+ @Option(names = { "--remove-install-hooks" }, description = "Removes both
internal and external hooks from processed packages", required = false)
+ private boolean removeInstallHooks = false;
+
@Override
public void run() {
if (quiet) {
diff --git
a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
index 4991088..81da36c 100644
---
a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
+++
b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
@@ -16,13 +16,13 @@
*/
package org.apache.sling.feature.cpconverter.vltpkg;
-import static org.apache.jackrabbit.vault.util.Constants.CONFIG_XML;
import static org.apache.jackrabbit.vault.util.Constants.FILTER_XML;
import static org.apache.jackrabbit.vault.util.Constants.META_DIR;
import static
org.apache.jackrabbit.vault.util.Constants.PACKAGE_DEFINITION_XML;
+import static org.apache.jackrabbit.vault.util.Constants.CONFIG_XML;
import static org.apache.jackrabbit.vault.util.Constants.PROPERTIES_XML;
-import static org.apache.jackrabbit.vault.util.Constants.ROOT_DIR;
import static org.apache.jackrabbit.vault.util.Constants.SETTINGS_XML;
+import static org.apache.jackrabbit.vault.util.Constants.ROOT_DIR;
import static
org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.PACKAGE_CLASSIFIER;
import static
org.apache.sling.feature.cpconverter.vltpkg.VaultPackageUtils.getDependencies;
import static
org.apache.sling.feature.cpconverter.vltpkg.VaultPackageUtils.setDependencies;
@@ -34,8 +34,14 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.function.Predicate;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
@@ -48,6 +54,7 @@ import org.apache.jackrabbit.vault.packaging.Dependency;
import org.apache.jackrabbit.vault.packaging.PackageId;
import org.apache.jackrabbit.vault.packaging.PackageProperties;
import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.jackrabbit.vault.util.Constants;
import
org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
import org.apache.sling.feature.cpconverter.handlers.EntryHandler;
import org.codehaus.plexus.archiver.Archiver;
@@ -55,22 +62,30 @@ import org.codehaus.plexus.archiver.util.DefaultFileSet;
import org.codehaus.plexus.archiver.zip.ZipArchiver;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class VaultPackageAssembler implements EntryHandler, FileFilter {
- private static final String NAME_PATH = "path";
-
- private static final String JCR_ROOT_DIR = "jcr_root";
-
private static final String[] INCLUDE_RESOURCES = {
PACKAGE_DEFINITION_XML, CONFIG_XML, SETTINGS_XML };
private static final Pattern OSGI_BUNDLE_PATTERN =
Pattern.compile("(jcr_root)?/apps/[^/]+/install(\\.([^/]+))?/.+\\.jar");
-
- public static @NotNull VaultPackageAssembler create(@NotNull File tempDir,
@NotNull VaultPackage vaultPackage) {
- return create(tempDir, vaultPackage,
Objects.requireNonNull(vaultPackage.getMetaInf().getFilter()));
+
+ private static final Logger log =
LoggerFactory.getLogger(VaultPackageAssembler.class);
+
+ private final static class RemoveInstallHooksPredicate implements
Predicate<Map.Entry<Object, Object>> {
+ @Override
+ public boolean test(java.util.Map.Entry<Object, Object> entry) {
+ String key = (String)entry.getKey();
+ return !key.startsWith(PackageProperties.PREFIX_INSTALL_HOOK);
+ }
}
- private static @NotNull VaultPackageAssembler create(@NotNull File
baseTempDir, @NotNull VaultPackage vaultPackage, @NotNull WorkspaceFilter
filter) {
+ public static @NotNull VaultPackageAssembler create(@NotNull File tempDir,
@NotNull VaultPackage vaultPackage, boolean removeInstallHooks) {
+ return create(tempDir, vaultPackage,
Objects.requireNonNull(vaultPackage.getMetaInf().getFilter()),
removeInstallHooks);
+ }
+
+ private static @NotNull VaultPackageAssembler create(@NotNull File
baseTempDir, @NotNull VaultPackage vaultPackage, @NotNull WorkspaceFilter
filter, boolean removeInstallHooks) {
final File tempDir = new File(baseTempDir,
"synthetic-content-packages_" + System.currentTimeMillis());
PackageId packageId = vaultPackage.getId();
String fileName = packageId.toString().replaceAll("/",
"-").replaceAll(":", "-") + "-" + vaultPackage.getFile().getName();
@@ -88,33 +103,25 @@ public class VaultPackageAssembler implements
EntryHandler, FileFilter {
throw new IllegalStateException("Unable to create jcr root dir: "
+ jcrRootDirectory);
}
- PackageProperties packageProperties = vaultPackage.getProperties();
-
Properties properties = new Properties();
+ Map<Object, Object> originalPackageProperties =
vaultPackage.getMetaInf().getProperties();
+ if (originalPackageProperties == null) {
+ throw new IllegalArgumentException("No package properties found in
" + vaultPackage.getId());
+ }
+ if (removeInstallHooks) {
+ // filter install hook properties
+ log.info("Removing install hooks from original package");
+ originalPackageProperties =
originalPackageProperties.entrySet().stream().filter(new
RemoveInstallHooksPredicate()).collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue));
+ }
+ properties.putAll(originalPackageProperties);
properties.setProperty(PackageProperties.NAME_VERSION,
-
packageProperties.getProperty(PackageProperties.NAME_VERSION)
+ vaultPackage.getId().getVersion().toString()
+ '-'
+
PACKAGE_CLASSIFIER);
- for (String key : new String[] {
- PackageProperties.NAME_GROUP,
- PackageProperties.NAME_NAME,
- PackageProperties.NAME_CREATED_BY,
- PackageProperties.NAME_CREATED,
- PackageProperties.NAME_REQUIRES_ROOT,
- PackageProperties.NAME_PACKAGE_TYPE,
- PackageProperties.NAME_AC_HANDLING,
- NAME_PATH
- }) {
- String value = packageProperties.getProperty(key);
- if (value != null && !value.isEmpty()) {
- properties.setProperty(key, value);
- }
- }
-
Set<Dependency> dependencies = getDependencies(vaultPackage);
- VaultPackageAssembler assembler = new VaultPackageAssembler(tempDir,
storingDirectory, properties, dependencies);
+ VaultPackageAssembler assembler = new VaultPackageAssembler(tempDir,
storingDirectory, properties, dependencies, removeInstallHooks);
assembler.mergeFilters(filter);
return assembler;
}
@@ -129,6 +136,8 @@ public class VaultPackageAssembler implements EntryHandler,
FileFilter {
private final File tmpDir;
+ private final boolean removeInstallHooks;
+
File getTempDir() {
return this.tmpDir;
}
@@ -141,19 +150,24 @@ public class VaultPackageAssembler implements
EntryHandler, FileFilter {
@Override
public void handle(@NotNull String path, @NotNull Archive archive,
@NotNull Entry entry, @NotNull ContentPackage2FeatureModelConverter converter)
throws Exception {
- addEntry(path, archive, entry);
+ if (removeInstallHooks && path.startsWith("/" + Constants.META_DIR +
"/" + Constants.HOOKS_DIR)) {
+ log.info("Skipping install hook {} from original package", path);
+ } else {
+ addEntry(path, archive, entry);
+ }
}
/**
* This class can not be instantiated from outside
*/
- private VaultPackageAssembler(@NotNull File tempDir, @NotNull File
storingDirectory, @NotNull Properties properties, @NotNull Set<Dependency>
dependencies) {
+ private VaultPackageAssembler(@NotNull File tempDir, @NotNull File
storingDirectory, @NotNull Properties properties, @NotNull Set<Dependency>
dependencies, boolean removeInstallHooks) {
this.storingDirectory = storingDirectory;
this.properties = properties;
this.dependencies = dependencies;
this.tmpDir = tempDir;
+ this.removeInstallHooks = removeInstallHooks;
}
-
+
public @NotNull Properties getPackageProperties() {
return this.properties;
}
@@ -272,7 +286,7 @@ public class VaultPackageAssembler implements EntryHandler,
FileFilter {
}
private void computeFilters(@NotNull File outputDirectory) {
- File jcrRootDir = new File(outputDirectory, JCR_ROOT_DIR);
+ File jcrRootDir = new File(outputDirectory, ROOT_DIR);
if (jcrRootDir.exists() && jcrRootDir.isDirectory()) {
for (File child : jcrRootDir.listFiles(this)) {
diff --git
a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
index 4fa6911..801d5b0 100644
---
a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
+++
b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
@@ -27,6 +27,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
+import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
@@ -36,11 +37,14 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Enumeration;
import java.util.Hashtable;
+import java.util.InvalidPropertiesFormatException;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
+import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.json.Json;
@@ -53,6 +57,7 @@ import org.apache.jackrabbit.vault.packaging.PackageId;
import org.apache.jackrabbit.vault.packaging.PackageProperties;
import org.apache.jackrabbit.vault.packaging.VaultPackage;
import org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl;
+import org.apache.jackrabbit.vault.util.Constants;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Artifacts;
import org.apache.sling.feature.Configuration;
@@ -281,19 +286,13 @@ public class ContentPackage2FeatureModelConverterTest {
"META-INF/vault/properties.xml",
"META-INF/vault/config.xml",
"META-INF/vault/filter.xml",
- "jcr_root/settings.xml",
- "jcr_root/config.xml",
- "jcr_root/definition/.content.xml",
"jcr_root/apps/.content.xml");
verifyContentPackage(new File(outputDirectory,
"asd/sample/asd.retail.apps/0.0.1/asd.retail.apps-0.0.1-cp2fm-converted.zip"),
"META-INF/vault/settings.xml",
"META-INF/vault/properties.xml",
"META-INF/vault/config.xml",
"META-INF/vault/filter.xml",
- "META-INF/vault/filter-plugin-generated.xml",
- "jcr_root/settings.xml",
- "jcr_root/config.xml",
- "jcr_root/definition/.content.xml");
+ "META-INF/vault/filter-plugin-generated.xml");
// in contrast to previous test when dropping content packages the
cases below would be filtered out and files wouldn'T be in cache
assertFalse(new File(outputDirectory,
"asd/sample/Asd.Retail.ui.content/0.0.1/Asd.Retail.ui.content-0.0.1-cp2fm-converted.zip").exists());
assertFalse(new File(outputDirectory,
"asd/sample/asd.retail.all/0.0.1/asd.retail.all-0.0.1-cp2fm-converted.zip").exists());
@@ -304,6 +303,68 @@ public class ContentPackage2FeatureModelConverterTest {
}
@Test
+ public void convertContentPackageRemoveInstallHooks() throws Exception {
+ URL packageUrl = getClass().getResource("test-with-install-hooks.zip");
+ File packageFile = FileUtils.toFile(packageUrl);
+
+ File outputDirectory = new File(System.getProperty("java.io.tmpdir"),
getClass().getName() + '_' + System.currentTimeMillis());
+
+ try {
+ converter.setFeaturesManager(new DefaultFeaturesManager(true, 5,
outputDirectory, null, null, null))
+ .setBundlesDeployer(new
DefaultArtifactsDeployer(outputDirectory))
+
.setEmitter(DefaultPackagesEventsEmitter.open(outputDirectory))
+ .setRemoveInstallHooks(true)
+ .convert(packageFile);
+
+ File expectedPackage = new File(outputDirectory,
"my_packages/tmp/0.0.0/tmp-0.0.0-cp2fm-converted.zip");
+ verifyContentPackageEntryNames(expectedPackage,
+ "META-INF/vault/config.xml",
+ "META-INF/vault/definition/.content.xml",
+ "META-INF/vault/filter.xml",
+ "META-INF/vault/properties.xml",
+ "jcr_root/.content.xml",
+ "jcr_root/testroot/.content.xml",
+ "jcr_root/settings.xml",
+ "jcr_root/config.xml",
+ "jcr_root/definition/.content.xml");
+ verifyPropertiesXmlEntry(expectedPackage,
"!installhook.test1.class", "!installhook.test2.class");
+ } finally {
+ deleteDirTree(outputDirectory);
+ }
+ }
+
+ @Test
+ public void convertContentPackageKeepInstallHooks() throws Exception {
+ URL packageUrl = getClass().getResource("test-with-install-hooks.zip");
+ File packageFile = FileUtils.toFile(packageUrl);
+
+ File outputDirectory = new File(System.getProperty("java.io.tmpdir"),
getClass().getName() + '_' + System.currentTimeMillis());
+
+ try {
+ converter.setFeaturesManager(new DefaultFeaturesManager(true, 5,
outputDirectory, null, null, null))
+ .setBundlesDeployer(new
DefaultArtifactsDeployer(outputDirectory))
+
.setEmitter(DefaultPackagesEventsEmitter.open(outputDirectory))
+ .convert(packageFile);
+
+ File expectedPackage = new File(outputDirectory,
"my_packages/tmp/0.0.0/tmp-0.0.0-cp2fm-converted.zip");
+ verifyContentPackageEntryNames(expectedPackage,
+ "META-INF/vault/config.xml",
+ "META-INF/vault/definition/.content.xml",
+ "META-INF/vault/filter.xml",
+ "META-INF/vault/hooks/vault-hook-example-3.0.0.jar",
+ "META-INF/vault/properties.xml",
+ "jcr_root/.content.xml",
+ "jcr_root/testroot/.content.xml",
+ "jcr_root/settings.xml",
+ "jcr_root/config.xml",
+ "jcr_root/definition/.content.xml");
+ verifyPropertiesXmlEntry(expectedPackage,
"installhook.test1.class", "installhook.test2.class");
+ } finally {
+ deleteDirTree(outputDirectory);
+ }
+ }
+
+ @Test
public void testConvertContentPackageWithAPIRegion() throws Exception {
URL cp = getClass().getResource("test_c-1.0.zip");
File cpFile = new File(cp.getFile());
@@ -419,6 +480,21 @@ public class ContentPackage2FeatureModelConverterTest {
assertTrue("POM file " + pomFile + " does not exist",
pomFile.exists());
}
+ private void verifyContentPackageEntryNames(File contentPackage,
String...expectedEntryNames) throws Exception {
+ try (ZipFile zipFile = new ZipFile(contentPackage)) {
+
+ List<String> expectedEntryNamesList =
Arrays.asList(expectedEntryNames);
+ for (Enumeration<? extends ZipEntry> zipEntries =
zipFile.entries(); zipEntries.hasMoreElements();) {
+ ZipEntry zipEntry = zipEntries.nextElement();
+ String entryName = zipEntry.getName();
+ if (!entryName.endsWith("/")) {
+ assertTrue("ZipEntry with name " + entryName + " not
expected", expectedEntryNamesList.contains(entryName));
+ }
+ }
+
+ }
+ }
+
private void verifyContentPackage(File contentPackage,
String...expectedEntries) throws Exception {
try (ZipFile zipFile = new ZipFile(contentPackage)) {
for (String expectedEntry : expectedEntries) {
@@ -428,6 +504,25 @@ public class ContentPackage2FeatureModelConverterTest {
}
}
+ private void verifyPropertiesXmlEntry(File contentPackage, String...
expectedPropertyKeys) throws InvalidPropertiesFormatException, IOException {
+ try (ZipFile zipFile = new ZipFile(contentPackage)) {
+ ZipEntry zipEntry = zipFile.getEntry(Constants.META_DIR + "/" +
Constants.PROPERTIES_XML);
+ assertNotNull("Package didn't contain properties.xml", zipEntry);
+ try (InputStream inputStream = zipFile.getInputStream(zipEntry)) {
+ Properties properties = new Properties();
+ properties.loadFromXML(inputStream);
+ for (String expectedPropertyKey : expectedPropertyKeys) {
+ if (expectedPropertyKey.startsWith("!")) {
+ String key = expectedPropertyKey.substring(1);
+ assertFalse("Properties.xml was not supposed to
contain key " + key + " but it does", properties.containsKey(key));
+ } else {
+ assertTrue("Properties.xml was supposed to contain key
" + expectedPropertyKey + " but it does not",
properties.containsKey(expectedPropertyKey));
+ }
+ }
+ }
+ }
+ }
+
@Test(expected = IllegalArgumentException.class)
public void verifyFilteringOutUndesiredPackages() throws Exception {
RegexBasedResourceFilter resourceFilter = new
RegexBasedResourceFilter();
diff --git
a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
index a757177..c1726cf 100644
---
a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
+++
b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssemblerTest.java
@@ -59,7 +59,7 @@ public class VaultPackageAssemblerTest {
VaultPackage vaultPackage = new PackageManagerImpl().open(file);
this.testDirectory = new File(System.getProperty("java.io.tmpdir"),
getClass().getName() + '_' + System.currentTimeMillis());
- this.assembler = VaultPackageAssembler.create(testDirectory,
vaultPackage);
+ this.assembler = VaultPackageAssembler.create(testDirectory,
vaultPackage, false);
}
@After
@@ -98,7 +98,7 @@ public class VaultPackageAssemblerTest {
File file = FileUtils.toFile(resource);
VaultPackage vaultPackage = new PackageManagerImpl().open(file);
- VaultPackageAssembler assembler =
VaultPackageAssembler.create(testDirectory, vaultPackage);
+ VaultPackageAssembler assembler =
VaultPackageAssembler.create(testDirectory, vaultPackage, false);
PackageId packageId = vaultPackage.getId();
String fileName = packageId.toString().replaceAll("/",
"-").replaceAll(":", "-") + "-" + vaultPackage.getFile().getName();
File storingDirectory = new File(assembler.getTempDir(), fileName +
"-deflated");
diff --git
a/src/test/resources/org/apache/sling/feature/cpconverter/test-with-install-hooks.zip
b/src/test/resources/org/apache/sling/feature/cpconverter/test-with-install-hooks.zip
new file mode 100644
index 0000000..af3a859
Binary files /dev/null and
b/src/test/resources/org/apache/sling/feature/cpconverter/test-with-install-hooks.zip
differ