You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by pa...@apache.org on 2022/01/17 15:49:45 UTC
[sling-org-apache-sling-feature-cpconverter] branch master updated: Sling 10931 (#125)
This is an automated email from the ASF dual-hosted git repository.
pauls 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 f01d77a Sling 10931 (#125)
f01d77a is described below
commit f01d77a60949595f9d266fcc06a72e243078902d
Author: Niek Raaijmakers <ni...@gmail.com>
AuthorDate: Mon Jan 17 16:49:38 2022 +0100
Sling 10931 (#125)
* SLING-10931 - refactoring the sling initial content part of CPConverter to follow the content creator pattern. Buffering XML files.
Co-authored-by: Niek Raaijmakers <ra...@adobe.com>
---
pom.xml | 68 ++++
.../ContentPackage2FeatureModelConverter.java | 21 +-
.../accesscontrol/DefaultAclManager.java | 63 +--
...ntentPackage2FeatureModelConverterLauncher.java | 5 +-
.../cpconverter/handlers/BundleEntryHandler.java | 294 +-------------
.../handlers/DefaultEntryHandlersManager.java | 8 +-
.../handlers/SlingInitialContentBundleHandler.java | 31 +-
.../slinginitialcontent/AssemblerProvider.java | 120 ++++++
.../BundleSlingInitialContentExtractContext.java | 120 ++++++
.../BundleSlingInitialContentExtractor.java | 132 +++++++
...BundleSlingInitialContentJarEntryExtractor.java | 150 +++++++
.../ContentPackageEntryPathComputer.java | 74 ++++
.../slinginitialcontent/ContentReaderProvider.java | 55 +++
.../JcrNamespaceRegistryProvider.java | 122 ++++++
.../ParentFolderRepoInitHandler.java | 120 ++++++
.../SlingInitialContentBundleEntryMetaData.java | 77 ++++
...InitialContentBundleEntryMetaDataCollector.java | 204 ++++++++++
.../VaultContentXMLContentCreator.java | 232 +++++++++++
.../XMLNodeToXMLFileWriter.java | 110 ++++++
.../slinginitialcontent/readers/XMLReader.java} | 31 +-
.../slinginitialcontent/xmlbuffer/XMLNode.java | 158 ++++++++
.../createpath/CreatePathSegmentProcessor.java | 105 +++++
.../createpath}/MixinParser.java | 4 +-
.../createpath}/PrimaryTypeParser.java | 4 +-
.../cpconverter/shared/ConverterConstants.java | 1 +
.../vltpkg/DocViewSerializerContentHandler.java | 73 +++-
.../cpconverter/vltpkg/JcrNamespaceRegistry.java | 31 +-
.../cpconverter/vltpkg/VaultPackageAssembler.java | 12 +-
.../feature/cpconverter/AdjustedFilterTest.java | 3 +-
.../ContentPackage2FeatureModelConverterTest.java | 106 +----
.../ConverterUserAndPermissionTest.java | 5 +-
.../BundleEntryHandleSlingInitialContentTest.java | 439 ++++++++++++++++++++-
.../handlers/BundleEntryHandlerTest.java | 1 +
.../handlers/GavDeclarationsInBundleTest.java | 1 +
.../vltpkg/JcrNamespaceRegistryTest.java | 16 +-
.../build_playground.ui.content-1.0-SNAPSHOT.zip | Bin 17699035 -> 0 bytes
.../handlers/bundle-entry-xmls/%3c%22&%3e.xml | 7 +
.../handlers/bundle-entry-xmls/11mumbojumbo.xml | 5 +
.../handlers/bundle-entry-xmls/homepage.xml | 3 +
.../bundle-entry-xmls/include-redirectStatus.xml | 17 +
.../handlers/bundle-entry-xmls/nodeName.xml | 7 +
.../handlers/bundle-entry-xmls/someprop.xml | 6 +
.../cpconverter/handlers/bundle-entry-xmls/xyz.xml | 5 +
.../i18n-jsonfile-xml-descriptor-test/en.json | 10 +
.../en.json.dir/.content.xml | 8 +
.../handlers/io.wcm.handler.link-1.7.02.jar | Bin 0 -> 85560 bytes
.../mysite-nodetype-and-page-json-xml-result.xml | 44 +++
...re-1.0.0-SNAPSHOT-i18n-xml-folderdescriptor.jar | Bin 0 -> 20655 bytes
.../mysite.core-1.0.0-SNAPSHOT-pagejson.jar | Bin 0 -> 20243 bytes
...ore-1.0.0-SNAPSHOT-slinginitialcontent-test.jar | Bin 0 -> 21283 bytes
....0.0-SNAPSHOT-specialchars-json-inputstream.jar | Bin 0 -> 18815 bytes
.../cpconverter/mysite.all-1.0.0-SNAPSHOT2.zip | 0
.../org/apache/sling/feature/cpconverter/seed.json | 9 +
53 files changed, 2590 insertions(+), 527 deletions(-)
diff --git a/pom.xml b/pom.xml
index ba5cfeb..3c0aa14 100644
--- a/pom.xml
+++ b/pom.xml
@@ -256,17 +256,85 @@
<version>3.4.6</version>
<scope>compile</scope>
</dependency>
+
+ <dependency>
+ <groupId>stax-utils</groupId>
+ <artifactId>stax-utils</artifactId>
+ <version>snapshot-20040917</version>
+ <scope>compile</scope>
+ </dependency>
+
+ <!-- https://mvnrepository.com/artifact/org.xmlunit/xmlunit-core -->
+ <dependency>
+ <groupId>org.xmlunit</groupId>
+ <artifactId>xmlunit-core</artifactId>
+ <version>2.6.2</version>
+ <scope>test</scope>
+ </dependency>
+ <!-- https://mvnrepository.com/artifact/org.xmlunit/xmlunit-matchers -->
+ <dependency>
+ <groupId>org.xmlunit</groupId>
+ <artifactId>xmlunit-matchers</artifactId>
+ <version>2.6.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.xmlunit</groupId>
+ <artifactId>xmlunit-assertj</artifactId>
+ <version>2.6.2</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.xml.bind</groupId>
+ <artifactId>jaxb-api</artifactId>
+ <version>2.3.1</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- https://mvnrepository.com/artifact/javax.activation/activation -->
+ <dependency>
+ <groupId>javax.activation</groupId>
+ <artifactId>activation</artifactId>
+ <version>1.1</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>2.8.6</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime -->
+ <dependency>
+ <groupId>org.glassfish.jaxb</groupId>
+ <artifactId>jaxb-runtime</artifactId>
+ <version>2.3.2</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<plugins>
<plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.0</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ </configuration>
+ </plugin>
+ <plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
<configuration>
<excludes>
<exclude>src/main/resources/META-INF/services/org.apache.sling.feature.cpconverter.handlers.EntryHandler</exclude>
<exclude>src/main/legal/NOTICE-with-deps</exclude>
+ <exclude>src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/**</exclude>
+ <exclude>src/test/resources/org/apache/sling/feature/cpconverter/handlers/i18n-jsonfile-xml-descriptor-test/**</exclude>
</excludes>
</configuration>
</plugin>
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 4ac12b7..f342e41 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -56,6 +56,7 @@ import org.apache.sling.feature.cpconverter.handlers.DefaultHandler;
import org.apache.sling.feature.cpconverter.handlers.EntryHandler;
import org.apache.sling.feature.cpconverter.handlers.EntryHandlersManager;
import org.apache.sling.feature.cpconverter.handlers.NodeTypesEntryHandler;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.BundleSlingInitialContentExtractor;
import org.apache.sling.feature.cpconverter.vltpkg.BaseVaultPackageScanner;
import org.apache.sling.feature.cpconverter.vltpkg.PackagesEventsEmitter;
import org.apache.sling.feature.cpconverter.vltpkg.RecollectorVaultPackageScanner;
@@ -104,6 +105,8 @@ public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanne
private PackagePolicy contentTypePackagePolicy = PackagePolicy.REFERENCE;
private boolean removeInstallHooks = false;
+
+ private BundleSlingInitialContentExtractor bundleSlingInitialContentExtractor = new BundleSlingInitialContentExtractor();
public enum PackagePolicy {
/**
@@ -266,6 +269,8 @@ public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanne
logger.info("content-package '{}' successfully read!", contentPackage);
aclManager.reset();
+ bundleSlingInitialContentExtractor.reset();
+
}
logger.info("Ordering input content-package(s) {}...", idPackageMapping.keySet());
@@ -302,7 +307,8 @@ public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanne
// finally serialize the Feature Model(s) file(s)
aclManager.addRepoinitExtension(assemblers, featuresManager);
-
+ bundleSlingInitialContentExtractor.addRepoInitExtension(assemblers, featuresManager);
+
logger.info("Conversion complete!");
featuresManager.serialize();
@@ -310,7 +316,9 @@ public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanne
emitters.stream().forEach(e -> e.endPackage(vaultPackage.getId(), result));
}
} finally {
+
aclManager.reset();
+ bundleSlingInitialContentExtractor.reset();
assemblers.clear();
try {
@@ -364,10 +372,11 @@ public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanne
// 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
VaultPackageAssembler handler = getMainPackageAssembler();
- assemblers.add(handler);
+
Properties parentProps = handler.getPackageProperties();
boolean isContainerPackage = PackageType.CONTAINER.equals(parentProps.get(PackageProperties.NAME_PACKAGE_TYPE));
setMainPackageAssembler(clonedPackage);
+ assemblers.add(clonedPackage);
// scan the detected package, first
traverse(vaultPackage);
@@ -494,6 +503,11 @@ public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanne
}
}
+ public ContentPackage2FeatureModelConverter setBundleSlingInitialContentExtractor(BundleSlingInitialContentExtractor bundleSlingInitialContentExtractor) {
+ this.bundleSlingInitialContentExtractor = bundleSlingInitialContentExtractor;
+ return this;
+ }
+
@Override
protected void onFile(@NotNull String entryPath, @NotNull Archive archive, @NotNull Entry entry) throws IOException, ConverterException {
try {
@@ -544,4 +558,7 @@ public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanne
cleanup();
}
+ public List<VaultPackageAssembler> getAssemblers() {
+ return new ArrayList<>(assemblers);
+ }
}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/DefaultAclManager.java b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/DefaultAclManager.java
index b69039a..067a9c7 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/DefaultAclManager.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/DefaultAclManager.java
@@ -22,11 +22,11 @@ import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
import org.apache.jackrabbit.util.Text;
import org.apache.jackrabbit.vault.fs.spi.PrivilegeDefinitions;
-import org.apache.jackrabbit.vault.util.PlatformNameFormat;
import org.apache.sling.feature.cpconverter.ConverterException;
import org.apache.sling.feature.cpconverter.features.FeaturesManager;
import org.apache.sling.feature.cpconverter.repoinit.NoOpVisitor;
import org.apache.sling.feature.cpconverter.repoinit.OperationProcessor;
+import org.apache.sling.feature.cpconverter.repoinit.createpath.CreatePathSegmentProcessor;
import org.apache.sling.feature.cpconverter.shared.ConverterConstants;
import org.apache.sling.feature.cpconverter.shared.RepoPath;
import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
@@ -48,8 +48,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.NamespaceException;
-import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
@@ -71,8 +69,6 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
-
public class DefaultAclManager implements AclManager, EnforceInfo {
private static final Logger log = LoggerFactory.getLogger(DefaultAclManager.class);
@@ -429,7 +425,7 @@ public class DefaultAclManager implements AclManager, EnforceInfo {
}
CreatePath cp = new CreatePath(null);
- boolean foundType = processSegments(path, packageAssemblers, cp);
+ boolean foundType = CreatePathSegmentProcessor.processSegments(path, packageAssemblers, cp);
if (!foundType && isBelowUserRoot(path)) {
// if no type information has been detected, don't issue a 'create path' statement for nodes below the
@@ -442,61 +438,6 @@ public class DefaultAclManager implements AclManager, EnforceInfo {
return cp;
}
}
-
- private static boolean processSegments(@NotNull RepoPath path, @NotNull List<VaultPackageAssembler> packageAssemblers, @NotNull CreatePath cp) {
- String platformPath = "";
- boolean foundType = false;
- for (String part : path.getSegments()) {
- String platformname = PlatformNameFormat.getPlatformName(part);
- platformPath += platformPath.isEmpty() ? platformname : "/" + platformname;
- boolean segmentAdded = false;
- for (VaultPackageAssembler packageAssembler : packageAssemblers) {
- File currentContent = packageAssembler.getEntry(platformPath + "/" + DOT_CONTENT_XML);
- if (currentContent.isFile()) {
- segmentAdded = addSegment(cp, part, currentContent);
- if (segmentAdded) {
- foundType = true;
- break;
- }
- }
- }
- if (!segmentAdded) {
- cp.addSegment(part, null);
- }
- }
- return foundType;
- }
-
- private static boolean addSegment(@NotNull CreatePath cp, @NotNull String part, @NotNull File currentContent) {
- try (FileInputStream input = new FileInputStream(currentContent);
- FileInputStream input2 = new FileInputStream(currentContent)) {
- String primary = new PrimaryTypeParser().parse(input);
- if (primary != null) {
- List<String> mixins = new ArrayList<>();
- String mixin = new MixinParser().parse(input2);
- if (mixin != null) {
- mixin = mixin.trim();
- if (mixin.startsWith("[")) {
- mixin = mixin.substring(1, mixin.length() - 1);
- }
- for (String m : mixin.split(",")) {
- String mixinName = m.trim();
- if (!mixinName.isEmpty()) {
- mixins.add(mixinName);
- }
- }
- }
- cp.addSegment(part, primary, mixins);
- return true;
- }
- } catch (Exception e) {
- throw new RuntimeException("A fatal error occurred while parsing the '"
- + currentContent
- + "' file, see nested exceptions: "
- + e);
- }
- return false;
- }
@NotNull
private String getRepoInitPath(@NotNull RepoPath path, @NotNull SystemUser systemUser) {
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 fcf33a1..d7ccbfa 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
@@ -37,6 +37,7 @@ import org.apache.sling.feature.cpconverter.artifacts.LocalMavenRepositoryArtifa
import org.apache.sling.feature.cpconverter.features.DefaultFeaturesManager;
import org.apache.sling.feature.cpconverter.filtering.RegexBasedResourceFilter;
import org.apache.sling.feature.cpconverter.handlers.DefaultEntryHandlersManager;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.BundleSlingInitialContentExtractor;
import org.apache.sling.feature.cpconverter.shared.ConverterConstants;
import org.apache.sling.feature.cpconverter.vltpkg.DefaultPackagesEventsEmitter;
import org.apache.sling.feature.io.json.FeatureJSONReader;
@@ -207,9 +208,11 @@ public final class ContentPackage2FeatureModelConverterLauncher implements Runna
}
try (ContentPackage2FeatureModelConverter converter = new ContentPackage2FeatureModelConverter(strictValidation, slingInitialContentPolicy)) {
+ BundleSlingInitialContentExtractor bundleSlingInitialContentExtractor = new BundleSlingInitialContentExtractor();
converter.setFeaturesManager(featuresManager)
.setBundlesDeployer(new LocalMavenRepositoryArtifactsDeployer(artifactsOutputDirectory))
- .setEntryHandlersManager(new DefaultEntryHandlersManager(entryHandlerConfigsMap, !disableInstallerPolicy, slingInitialContentPolicy, systemUserRelPath))
+ .setBundleSlingInitialContentExtractor(bundleSlingInitialContentExtractor)
+ .setEntryHandlersManager(new DefaultEntryHandlersManager(entryHandlerConfigsMap, !disableInstallerPolicy, slingInitialContentPolicy, bundleSlingInitialContentExtractor, systemUserRelPath))
.setAclManager(aclManager)
.setEmitter(DefaultPackagesEventsEmitter.open(featureModelsOutputDirectory))
.setFailOnMixedPackages(failOnMixedPackages)
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
index e6ff430..4412fea 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
@@ -19,73 +19,34 @@ package org.apache.sling.feature.cpconverter.handlers;
import static java.util.Objects.requireNonNull;
import static org.osgi.framework.Version.parseVersion;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
-import java.io.Reader;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EnumMap;
-import java.util.EnumSet;
import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
import java.util.Properties;
-import java.util.StringTokenizer;
-import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.felix.utils.manifest.Clause;
import org.apache.felix.utils.manifest.Parser;
-import org.apache.jackrabbit.commons.cnd.ParseException;
-import org.apache.jackrabbit.util.Text;
-import org.apache.jackrabbit.vault.fs.api.ImportMode;
-import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
-import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
import org.apache.jackrabbit.vault.fs.io.Archive;
import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
-import org.apache.jackrabbit.vault.packaging.PackageId;
import org.apache.jackrabbit.vault.packaging.PackageProperties;
-import org.apache.jackrabbit.vault.packaging.PackageType;
-import org.apache.jackrabbit.vault.util.PlatformNameFormat;
-import org.apache.sling.commons.osgi.ManifestHeader;
-import org.apache.sling.contentparser.api.ContentParser;
-import org.apache.sling.contentparser.api.ParserOptions;
-import org.apache.sling.contentparser.json.JSONParserFeature;
-import org.apache.sling.contentparser.json.JSONParserOptions;
-import org.apache.sling.contentparser.json.internal.JSONContentParser;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
import org.apache.sling.feature.cpconverter.ConverterException;
import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.SlingInitialContentPolicy;
import org.apache.sling.feature.cpconverter.artifacts.InputStreamArtifactWriter;
-import org.apache.sling.feature.cpconverter.vltpkg.DocViewSerializerContentHandler;
-import org.apache.sling.feature.cpconverter.vltpkg.DocViewSerializerContentHandlerException;
-import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
-import org.apache.sling.feature.cpconverter.vltpkg.SingleFileArchive;
-import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
-import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageUtils;
-import org.apache.sling.jcr.contentloader.PathEntry;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.BundleSlingInitialContentExtractor;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.BundleSlingInitialContentExtractContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.framework.Constants;
@@ -98,18 +59,15 @@ public class BundleEntryHandler extends AbstractRegexEntryHandler {
private static final String NAME_ARTIFACT_ID = "artifactId";
private static final String JAR_TYPE = "jar";
-
- public static final String NODETYPES_BUNDLE_HEADER = "Sling-Nodetypes";
-
- public static final String NAMESPACES_BUNDLE_HEADER = "Sling-Namespaces";
-
+
private static final Pattern POM_PROPERTIES_PATTERN = Pattern.compile("META-INF/maven/[^/]+/[^/]+/pom.properties");
private static final Pattern POM_XML_PATTERN = Pattern.compile("META-INF/maven/[^/]+/[^/]+/pom.xml");
private boolean enforceBundlesBelowInstallFolder;
- private SlingInitialContentPolicy slingInitialContentPolicy;
+ protected SlingInitialContentPolicy slingInitialContentPolicy;
+ private BundleSlingInitialContentExtractor bundleSlingInitialContentExtractor = new BundleSlingInitialContentExtractor();
public BundleEntryHandler() {
super("/jcr_root/(?:apps|libs)/.+/(?<foldername>install|config)(?:\\.(?<runmode>[^/]+))?/(?:(?<startlevel>[0-9]+)/)?.+\\.jar");
@@ -186,14 +144,15 @@ public class BundleEntryHandler extends AbstractRegexEntryHandler {
}
void processBundleInputStream(@NotNull String path, @NotNull Path originalBundleFile, @NotNull String bundleName, @Nullable String runMode, @Nullable Integer startLevel, @NotNull ContentPackage2FeatureModelConverter converter)
- throws ConverterException, IOException {
+ throws ConverterException, IOException {
try (JarFile jarFile = new JarFile(originalBundleFile.toFile())) {
// first extract bundle metadata from JAR input stream
Artifact artifact = extractFeatureArtifact(bundleName, jarFile);
ArtifactId id = artifact.getId();
- try (InputStream strippedBundleInput = extractSlingInitialContent(path, originalBundleFile, artifact, jarFile, converter, runMode)) {
- if (strippedBundleInput != null && slingInitialContentPolicy == SlingInitialContentPolicy.EXTRACT_AND_REMOVE) {
+ BundleSlingInitialContentExtractContext context = new BundleSlingInitialContentExtractContext(slingInitialContentPolicy, path, id, jarFile, converter, runMode);
+ try (InputStream strippedBundleInput = bundleSlingInitialContentExtractor.extract(context)) {
+ if (strippedBundleInput != null && slingInitialContentPolicy == ContentPackage2FeatureModelConverter.SlingInitialContentPolicy.EXTRACT_AND_REMOVE) {
id = id.changeVersion(id.getVersion() + "-" + ContentPackage2FeatureModelConverter.PACKAGE_CLASSIFIER);
Objects.requireNonNull(converter.getArtifactsDeployer()).deploy(new InputStreamArtifactWriter(strippedBundleInput), id);
} else {
@@ -213,232 +172,6 @@ public class BundleEntryHandler extends AbstractRegexEntryHandler {
}
}
- static Version getModifiedOsgiVersion(Version originalVersion) {
- return new Version(originalVersion.getMajor(), originalVersion.getMinor(), originalVersion.getMicro(), originalVersion.getQualifier() + "_" + ContentPackage2FeatureModelConverter.PACKAGE_CLASSIFIER);
- }
-
- @Nullable InputStream extractSlingInitialContent(@NotNull String path, @NotNull Path bundlePath, @NotNull Artifact bundleArtifact, @NotNull JarFile jarFile, @NotNull ContentPackage2FeatureModelConverter converter, @Nullable String runMode) throws IOException, ConverterException {
- if (slingInitialContentPolicy == SlingInitialContentPolicy.KEEP) {
- return null;
- }
- // parse "Sling-Initial-Content" header
- Manifest manifest = Objects.requireNonNull(jarFile.getManifest());
- Iterator<PathEntry> pathEntries = PathEntry.getContentPaths(manifest, -1);
- if (pathEntries == null) {
- return null;
- }
- logger.info("Extracting Sling-Initial-Content from '{}'", bundleArtifact.getId());
- Collection<PathEntry> pathEntryList = new ArrayList<>();
- pathEntries.forEachRemaining(pathEntryList::add);
-
- // remove header
- manifest.getMainAttributes().remove(new Attributes.Name(PathEntry.CONTENT_HEADER));
- // change version to have suffix
- Version originalVersion = new Version(Objects.requireNonNull(manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION)));
- manifest.getMainAttributes().putValue(Constants.BUNDLE_VERSION, getModifiedOsgiVersion(originalVersion).toString());
- Path newBundleFile = Files.createTempFile(converter.getTempDirectory().toPath(), "newBundle", ".jar");
-
- // create JAR file to prevent extracting it twice and for random access
- JcrNamespaceRegistry namespaceRegistry = createNamespaceRegistry(manifest, jarFile, converter.getFeaturesManager().getNamespaceUriByPrefix());
-
- Map<PackageType, VaultPackageAssembler> packageAssemblers = new EnumMap<>(PackageType.class);
- try (OutputStream fileOutput = Files.newOutputStream(newBundleFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
- JarOutputStream bundleOutput = new JarOutputStream(fileOutput, manifest)) {
-
- for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements();) {
- JarEntry jarEntry = e.nextElement();
- if (!jarEntry.isDirectory()) {
- try (InputStream input = jarFile.getInputStream(jarEntry)) {
- if (!extractSlingInitialContent(jarEntry, input, bundleArtifact, pathEntryList, packageAssemblers, namespaceRegistry, converter)) {
- // skip manifest, as already written in the constructor (as first entry)
- if (jarEntry.getName().equals(JarFile.MANIFEST_NAME)) {
- continue;
- }
- // copy entry as is to the stripped bundle
- bundleOutput.putNextEntry(jarEntry);
- IOUtils.copy(input, bundleOutput);
- bundleOutput.closeEntry();
- }
- }
- }
- }
- }
- // add additional content packages to feature model
- finalizePackageAssembly(path, packageAssemblers, converter, runMode);
-
- // return stripped bundle's inputstream which must be deleted on close
- return Files.newInputStream(newBundleFile, StandardOpenOption.READ, StandardOpenOption.DELETE_ON_CLOSE);
- }
-
- /**
- *
- * @param jarEntry
- * @param bundleFileInputStream
- * @param packageAssemblers
- * @param converter
- * @return {@code true} in case the given entry was part of the initial content otherwise {@code false}
- * @throws Exception
- */
- boolean extractSlingInitialContent(@NotNull JarEntry jarEntry, @NotNull InputStream bundleFileInputStream, @NotNull Artifact bundleArtifact, @NotNull Collection<PathEntry> pathEntries, @NotNull Map<PackageType, VaultPackageAssembler> packageAssemblers, @NotNull JcrNamespaceRegistry nsRegistry, @NotNull ContentPackage2FeatureModelConverter converter) throws IOException, ConverterException {
- final String entryName = jarEntry.getName();
- // check if current JAR entry is initial content
- Optional<PathEntry> pathEntry = pathEntries.stream().filter(p -> entryName.startsWith(p.getPath())).findFirst();
- if (!pathEntry.isPresent()) {
- return false;
- }
- Map.Entry<ContentParser, ParserOptions> contentParserAndOptions = getContentParserForEntry(jarEntry, pathEntry.get());
-
- // https://sling.apache.org/documentation/bundles/content-loading-jcr-contentloader.html#file-name-escaping
- String repositoryPath = (pathEntry.get().getTarget() != null ? pathEntry.get().getTarget() : "/") + URLDecoder.decode(entryName.substring(pathEntry.get().getPath().length()), "UTF-8");
- // all entry paths used by entry handlers start with "/"
- String contentPackageEntryPath = "/" + org.apache.jackrabbit.vault.util.Constants.ROOT_DIR + PlatformNameFormat.getPlatformPath(repositoryPath);
-
- Path tmpDocViewInputFile = null;
- try {
- if (contentParserAndOptions != null) {
- // convert to docview xml
- tmpDocViewInputFile = Files.createTempFile(converter.getTempDirectory().toPath(), "docview", ".xml");
- try (OutputStream docViewOutput = Files.newOutputStream(tmpDocViewInputFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
- DocViewSerializerContentHandler contentHandler = new DocViewSerializerContentHandler(docViewOutput, nsRegistry)) {
- contentParserAndOptions.getKey().parse(contentHandler, bundleFileInputStream, contentParserAndOptions.getValue());
- contentPackageEntryPath = FilenameUtils.removeExtension(contentPackageEntryPath) + ".xml";
- } catch (IOException e) {
- throw new IOException("Can not parse " + jarEntry, e);
- } catch (DocViewSerializerContentHandlerException e) {
- throw new IOException("Can not convert " + jarEntry + " to enhanced DocView format", e);
- }
- }
-
- // remap CND files to make sure they are picked up by NodeTypesEntryHandler;
- if (nsRegistry.getRegisteredCndSystemIds().contains(jarEntry.getName())) {
- contentPackageEntryPath = "/META-INF/vault/" + Text.getName(jarEntry.getName()) + ".cnd";
- }
- try (Archive virtualArchive = SingleFileArchive.fromPathOrInputStream(tmpDocViewInputFile, bundleFileInputStream,
- () -> Files.createTempFile(converter.getTempDirectory().toPath(), "initial-content", Text.getName(jarEntry.getName())), contentPackageEntryPath)) {
- // in which content package should this end up?
- VaultPackageAssembler packageAssembler = initPackageAssemblerForPath(bundleArtifact.getId(), repositoryPath, pathEntry.get(), packageAssemblers, converter);
- if (tmpDocViewInputFile != null) {
- packageAssembler.addEntry(contentPackageEntryPath, tmpDocViewInputFile.toFile());
- } else {
- packageAssembler.addEntry(contentPackageEntryPath, bundleFileInputStream);
- }
- }
- } finally {
- if (tmpDocViewInputFile != null) {
- Files.delete(tmpDocViewInputFile);
- }
- }
- return true;
- }
-
- JcrNamespaceRegistry createNamespaceRegistry(@NotNull Manifest manifest, @NotNull JarFile jarFile, @NotNull Map<String, String> predefinedNamespaceUriByPrefix) throws IOException {
- try {
- JcrNamespaceRegistry registry = new JcrNamespaceRegistry();
- for (Map.Entry<String, String> entry : predefinedNamespaceUriByPrefix.entrySet()) {
- registry.registerNamespace(entry.getKey(), entry.getValue());
- }
-
- // parse Sling-Namespaces header (https://github.com/apache/sling-org-apache-sling-jcr-base/blob/66be360910c265473799635fcac0e23895898913/src/main/java/org/apache/sling/jcr/base/internal/loader/Loader.java#L192)
- final String namespacesDefinitionHeader = manifest.getMainAttributes().getValue(NAMESPACES_BUNDLE_HEADER);
- if (namespacesDefinitionHeader != null) {
- final StringTokenizer st = new StringTokenizer(namespacesDefinitionHeader, ",");
-
- while ( st.hasMoreTokens() ) {
- final String token = st.nextToken().trim();
- int pos = token.indexOf('=');
- if ( pos == -1 ) {
- logger.warn("createNamespaceRegistry: Bundle {} has an invalid namespace manifest header entry: {}",
- manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME), token);
- } else {
- final String prefix = token.substring(0, pos).trim();
- final String namespace = token.substring(pos+1).trim();
- registry.registerNamespace(prefix, namespace);
- }
- }
- }
-
- // parse Sling-Nodetypes header
- final String typesHeader = manifest.getMainAttributes().getValue(NODETYPES_BUNDLE_HEADER);
- if (typesHeader != null) {
- for (ManifestHeader.Entry entry : ManifestHeader.parse(typesHeader).getEntries()) {
- JarEntry jarEntry = jarFile.getJarEntry(entry.getValue());
- if (jarEntry == null) {
- logger.warn("createNamespaceRegistry: Bundle {} has referenced a non existing node type definition: {}",
- manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME), entry.getValue());
- } else {
- try (InputStream inputStream = jarFile.getInputStream(jarEntry);
- Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
- registry.registerCnd(reader, entry.getValue());
- }
- }
- }
- }
- return registry;
- } catch ( final RepositoryException | ParseException e) {
- throw new IOException(e.getMessage(), e);
- }
- }
-
- /**
- * Lazily initializes the cache with the necessary VaultPackageAssemblers
- * @param bundleArtifactId
- * @param repositoryPath
- * @param cache
- * @param converter
- * @return the VaultPackageAssembler from the cache to use for the given repository path
- */
- public VaultPackageAssembler initPackageAssemblerForPath(@NotNull ArtifactId bundleArtifactId, @NotNull String repositoryPath, @NotNull PathEntry pathEntry, @NotNull Map<PackageType, VaultPackageAssembler> cache, @NotNull ContentPackage2FeatureModelConverter converter)
- throws ConverterException {
- PackageType packageType = VaultPackageUtils.detectPackageType(repositoryPath);
- VaultPackageAssembler assembler = cache.get(packageType);
- if (assembler == null) {
- final String packageNameSuffix;
- switch (packageType) {
- case APPLICATION:
- packageNameSuffix = "-apps";
- break;
- case CONTENT:
- packageNameSuffix = "-content";
- break;
- default:
- throw new ConverterException("Unexpected package type " + packageType + " detected for path " + repositoryPath);
- }
- final PackageId packageId = new PackageId(bundleArtifactId.getGroupId(), bundleArtifactId.getArtifactId()+packageNameSuffix, bundleArtifactId.getVersion());
- assembler = VaultPackageAssembler.create(converter.getTempDirectory(), packageId, "Generated out of Sling Initial Content from bundle " + bundleArtifactId + " by cp2fm");
- cache.put(packageType, assembler);
- logger.info("Created package {} out of Sling-Initial-Content from '{}'", packageId, bundleArtifactId);
- }
- DefaultWorkspaceFilter filter = assembler.getFilter();
- if (!filter.covers(repositoryPath)) {
- PathFilterSet pathFilterSet = new PathFilterSet(pathEntry.getTarget() != null ? pathEntry.getTarget() : "/");
- ImportMode importMode;
- if (pathEntry.isOverwrite()) {
- importMode = ImportMode.REPLACE;
- } else {
- importMode = ImportMode.MERGE;
- }
- // TODO: add handling for merge, mergeProperties and overwriteProperties (https://issues.apache.org/jira/browse/SLING-10318)
- pathFilterSet.setImportMode(importMode);
- filter.add(pathFilterSet);
- }
- return assembler;
- }
-
- void finalizePackageAssembly(@NotNull String path, @NotNull Map<PackageType, VaultPackageAssembler> packageAssemblers, @NotNull ContentPackage2FeatureModelConverter converter, @Nullable String runMode) throws IOException, ConverterException {
- for (java.util.Map.Entry<PackageType, VaultPackageAssembler> entry : packageAssemblers.entrySet()) {
- File packageFile = entry.getValue().createPackage();
- converter.processSubPackage(path + "-" + entry.getKey(), runMode, converter.open(packageFile), false);
- }
- }
-
- Map.Entry<ContentParser, ParserOptions> getContentParserForEntry(JarEntry entry, PathEntry pathEntry) {
- if (entry.getName().endsWith(".json") && !pathEntry.isIgnoredImportProvider("json")) {
- return new AbstractMap.SimpleEntry<>(new JSONContentParser(), new JSONParserOptions().withFeatures(EnumSet.of(JSONParserFeature.COMMENTS, JSONParserFeature.QUOTE_TICK)));
- } else {
- return null;
- }
- }
-
protected @NotNull Artifact extractFeatureArtifact(@NotNull String bundleName, @NotNull JarFile jarFile) throws IOException {
String artifactId = null;
String version = null;
@@ -500,7 +233,7 @@ public class BundleEntryHandler extends AbstractRegexEntryHandler {
}
}
-
+
if (groupId == null) {
// maybe the included jar is just an OSGi bundle but not a valid Maven artifact
groupId = getCheckedProperty(jarFile.getManifest(), Constants.BUNDLE_SYMBOLICNAME);
@@ -538,8 +271,11 @@ public class BundleEntryHandler extends AbstractRegexEntryHandler {
property = property.trim();
}
return requireNonNull(property, "Jar file can not be defined as a valid OSGi bundle without specifying a valid '"
- + name
- + "' property.");
+ + name
+ + "' property.");
}
+ public void setBundleSlingInitialContentExtractor(BundleSlingInitialContentExtractor bundleSlingInitialContentExtractor) {
+ this.bundleSlingInitialContentExtractor = bundleSlingInitialContentExtractor;
+ }
}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/DefaultEntryHandlersManager.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/DefaultEntryHandlersManager.java
index 4491625..ed7015b 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/DefaultEntryHandlersManager.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/DefaultEntryHandlersManager.java
@@ -17,6 +17,7 @@
package org.apache.sling.feature.cpconverter.handlers;
import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.SlingInitialContentPolicy;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.BundleSlingInitialContentExtractor;
import org.apache.sling.feature.cpconverter.shared.ConverterConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -32,11 +33,11 @@ public class DefaultEntryHandlersManager implements EntryHandlersManager {
private final List<EntryHandler> entryHandlers = new LinkedList<>();
public DefaultEntryHandlersManager() {
- this(Collections.emptyMap(), false, SlingInitialContentPolicy.KEEP, ConverterConstants.SYSTEM_USER_REL_PATH_DEFAULT);
+ this(Collections.emptyMap(), false, SlingInitialContentPolicy.KEEP,new BundleSlingInitialContentExtractor(), ConverterConstants.SYSTEM_USER_REL_PATH_DEFAULT);
}
- public DefaultEntryHandlersManager(@NotNull Map<String, String> configs, boolean enforceConfigurationsAndBundlesBelowProperFolder,
- @NotNull SlingInitialContentPolicy slingInitialContentPolicy, @NotNull String systemUserRelPath) {
+ public DefaultEntryHandlersManager(@NotNull Map<String, String> configs, boolean enforceConfigurationsAndBundlesBelowProperFolder,
+ @NotNull SlingInitialContentPolicy slingInitialContentPolicy, @NotNull BundleSlingInitialContentExtractor bundleSlingInitialContentExtractor, @NotNull String systemUserRelPath) {
ServiceLoader<EntryHandler> entryHandlersLoader = ServiceLoader.load(EntryHandler.class);
for (EntryHandler entryHandler : entryHandlersLoader) {
if (configs.containsKey(entryHandler.getClass().getName())) {
@@ -47,6 +48,7 @@ public class DefaultEntryHandlersManager implements EntryHandlersManager {
} else if (entryHandler instanceof BundleEntryHandler) {
((BundleEntryHandler) entryHandler).setEnforceBundlesBelowInstallFolder(enforceConfigurationsAndBundlesBelowProperFolder);
((BundleEntryHandler) entryHandler).setSlingInitialContentPolicy(slingInitialContentPolicy);
+ ((BundleEntryHandler) entryHandler).setBundleSlingInitialContentExtractor(bundleSlingInitialContentExtractor);
} else if (entryHandler instanceof AbstractUserEntryHandler) {
((AbstractUserEntryHandler) entryHandler).setSystemUserRelPath(systemUserRelPath);
}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/SlingInitialContentBundleHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/SlingInitialContentBundleHandler.java
index 64330d0..2e43c46 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/SlingInitialContentBundleHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/SlingInitialContentBundleHandler.java
@@ -16,11 +16,14 @@
*/
package org.apache.sling.feature.cpconverter.handlers;
+
import org.apache.jackrabbit.vault.packaging.PackageType;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.BundleSlingInitialContentExtractor;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.BundleSlingInitialContentExtractContext;
import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -29,7 +32,6 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
-import java.util.Map;
import java.util.jar.JarFile;
public class SlingInitialContentBundleHandler extends BundleEntryHandler {
@@ -39,22 +41,31 @@ public class SlingInitialContentBundleHandler extends BundleEntryHandler {
this.handler = handler;
setSlingInitialContentPolicy(slingInitialContentPolicy);
}
-
+
@Override
void processBundleInputStream(@NotNull String path, @NotNull Path originalBundleFile, @NotNull String bundleName, @Nullable String runMode, @Nullable Integer startLevel, @NotNull ContentPackage2FeatureModelConverter converter) throws IOException, ConverterException {
try (JarFile jarFile = new JarFile(originalBundleFile.toFile())) {
// first extract bundle metadata from JAR input stream
Artifact artifact = extractFeatureArtifact(bundleName, jarFile);
-
- try (InputStream ignored = extractSlingInitialContent(path, originalBundleFile, artifact, jarFile, converter, runMode)) {}
+ ArtifactId id = artifact.getId();
+
+ BundleSlingInitialContentExtractContext context = new BundleSlingInitialContentExtractContext(slingInitialContentPolicy, path, id, jarFile, converter, runMode);
+ try (InputStream ignored = new BundleSlingInitialContentExtractorOverride().extract(context)) {
+ logger.info("Ignoring inputstream {} with id {}", path, id);
+ }
}
}
-
- @Override
- void finalizePackageAssembly(@NotNull String path, @NotNull Map<PackageType, VaultPackageAssembler> packageAssemblers, @NotNull ContentPackage2FeatureModelConverter converter, @Nullable String runMode) throws IOException, ConverterException {
- for (java.util.Map.Entry<PackageType, VaultPackageAssembler> entry : packageAssemblers.entrySet()) {
- File packageFile = entry.getValue().createPackage();
- handler.processSubPackage(path + "-" + entry.getKey(), runMode, converter.open(packageFile), converter, true);
+
+ class BundleSlingInitialContentExtractorOverride extends BundleSlingInitialContentExtractor{
+ @Override
+ protected void finalizePackageAssembly(@NotNull BundleSlingInitialContentExtractContext context) throws IOException, ConverterException {
+ for (java.util.Map.Entry<PackageType, VaultPackageAssembler> entry : assemblerProvider.getPackageAssemblerEntrySet()) {
+ File packageFile = entry.getValue().createPackage();
+ ContentPackage2FeatureModelConverter converter = context.getConverter();
+ handler.processSubPackage(context.getPath() + "-" + entry.getKey(), context.getRunMode(), converter.open(packageFile), converter, true);
+ }
}
}
+
+
}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/AssemblerProvider.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/AssemblerProvider.java
new file mode 100644
index 0000000..91ae911
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/AssemblerProvider.java
@@ -0,0 +1,120 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.jackrabbit.vault.fs.api.ImportMode;
+import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
+import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.PackageType;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageUtils;
+import org.apache.sling.jcr.contentloader.PathEntry;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Encapsulates the VaultPackage assembler logic for the sling initial content extraction
+ */
+public class AssemblerProvider {
+
+ private static final Logger logger = LoggerFactory.getLogger(AssemblerProvider.class);
+
+ private final Map<PackageType, VaultPackageAssembler> packageAssemblers = new EnumMap<>(PackageType.class);
+
+ AssemblerProvider() {
+ }
+
+ /**
+ * Lazily initializes the cache with the necessary VaultPackageAssemblers
+ *
+ * @param repositoryPath repository path of the sling initial content entry
+ * @return the VaultPackageAssembler from the cache to use for the given repository path
+ */
+ @NotNull
+ public VaultPackageAssembler initPackageAssemblerForPath(@NotNull BundleSlingInitialContentExtractContext context,
+ @NotNull String repositoryPath,
+ @NotNull PathEntry pathEntry)
+ throws ConverterException {
+
+ ArtifactId bundleArtifactId = context.getBundleArtifactId();
+ PackageType packageType = VaultPackageUtils.detectPackageType(repositoryPath);
+ VaultPackageAssembler assembler = lazyConstruct(context, repositoryPath, bundleArtifactId, packageType);
+ addPathFilterSetToAssemblerFilter(pathEntry, assembler);
+ return assembler;
+ }
+
+ @NotNull
+ public Set<Map.Entry<PackageType, VaultPackageAssembler>> getPackageAssemblerEntrySet() {
+ return packageAssemblers.entrySet();
+ }
+
+ @NotNull
+ private VaultPackageAssembler lazyConstruct(@NotNull BundleSlingInitialContentExtractContext context,
+ @NotNull String repositoryPath,
+ @NotNull ArtifactId bundleArtifactId,
+ @NotNull PackageType packageType) throws ConverterException {
+ VaultPackageAssembler assembler = packageAssemblers.get(packageType);
+ if (assembler == null) {
+ final String packageNameSuffix;
+ switch (packageType) {
+ case APPLICATION:
+ packageNameSuffix = "-apps";
+ break;
+ case CONTENT:
+ packageNameSuffix = "-content";
+ break;
+ default:
+ throw new ConverterException("Unexpected package type " + packageType + " detected for path " + repositoryPath);
+ }
+ final PackageId packageId = new PackageId(bundleArtifactId.getGroupId(), bundleArtifactId.getArtifactId() + packageNameSuffix, bundleArtifactId.getVersion());
+ assembler = VaultPackageAssembler.create(context.getConverter().getTempDirectory(), packageId, "Generated out of Sling Initial Content from bundle " + bundleArtifactId + " by cp2fm");
+ packageAssemblers.put(packageType, assembler);
+ logger.info("Created package {} out of Sling-Initial-Content from '{}'", packageId, bundleArtifactId);
+ }
+ return assembler;
+ }
+
+ private void addPathFilterSetToAssemblerFilter(@NotNull PathEntry pathEntry, @NotNull VaultPackageAssembler assembler) {
+ ImportMode importMode;
+ if (pathEntry.isOverwrite()) {
+ importMode = ImportMode.UPDATE;
+ } else {
+ importMode = ImportMode.MERGE;
+ }
+
+ DefaultWorkspaceFilter filter = assembler.getFilter();
+ if (filter.getFilterSets().stream().noneMatch(set -> set.getRoot().equals(pathEntry.getTarget() != null ? pathEntry.getTarget() : "/") &&
+ set.getImportMode() == importMode)) {
+ PathFilterSet pathFilterSet = new PathFilterSet(pathEntry.getTarget() != null ? pathEntry.getTarget() : "/");
+ // TODO: add handling for merge, mergeProperties and overwriteProperties (https://issues.apache.org/jira/browse/SLING-10318)
+ pathFilterSet.setImportMode(importMode);
+ filter.add(pathFilterSet);
+ }
+ }
+
+ public void clear() {
+ this.packageAssemblers.clear();
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/BundleSlingInitialContentExtractContext.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/BundleSlingInitialContentExtractContext.java
new file mode 100644
index 0000000..390946a
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/BundleSlingInitialContentExtractContext.java
@@ -0,0 +1,120 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.jcr.contentloader.PathEntry;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+/**
+ * Holds various context variables for the BundleSlingInitialContentExtractor
+ */
+public class BundleSlingInitialContentExtractContext {
+
+ private final ContentPackage2FeatureModelConverter.SlingInitialContentPolicy slingInitialContentPolicy;
+ private final String path;
+ private final ArtifactId bundleArtifactId;
+ private final JarFile jarFile;
+ private final ContentPackage2FeatureModelConverter converter;
+ private final String runMode;
+ private final Manifest manifest;
+ private final JcrNamespaceRegistry namespaceRegistry;
+ private final List<PathEntry> pathEntryList = new ArrayList<>();
+
+ public BundleSlingInitialContentExtractContext(@NotNull ContentPackage2FeatureModelConverter.SlingInitialContentPolicy slingInitialContentPolicy,
+ @NotNull String path,
+ @NotNull ArtifactId bundleArtifactId,
+ @NotNull JarFile jarFile,
+ @NotNull ContentPackage2FeatureModelConverter converter,
+ @Nullable String runMode) throws IOException {
+ this.slingInitialContentPolicy = slingInitialContentPolicy;
+ this.path = path;
+ this.bundleArtifactId = bundleArtifactId;
+ this.jarFile = jarFile;
+ this.converter = converter;
+ this.runMode = runMode;
+
+ this.manifest = Objects.requireNonNull(jarFile.getManifest());
+ this.namespaceRegistry =
+ new JcrNamespaceRegistryProvider(manifest,
+ jarFile,
+ converter.getFeaturesManager().getNamespaceUriByPrefix()
+ ).provideRegistryFromBundle();
+
+ Iterator<PathEntry> pathEntries = PathEntry.getContentPaths(manifest, -1);
+
+ if (pathEntries != null) {
+ pathEntries.forEachRemaining(pathEntryList::add);
+ }
+ }
+
+ @NotNull
+ public ContentPackage2FeatureModelConverter.SlingInitialContentPolicy getSlingInitialContentPolicy() {
+ return slingInitialContentPolicy;
+ }
+
+ @NotNull
+ public String getPath() {
+ return path;
+ }
+
+ @NotNull
+ public ArtifactId getBundleArtifactId() {
+ return bundleArtifactId;
+ }
+
+ @NotNull
+ public ContentPackage2FeatureModelConverter getConverter() {
+ return converter;
+ }
+
+ @Nullable
+ public String getRunMode() {
+ return runMode;
+ }
+
+ @NotNull
+ public JarFile getJarFile() {
+ return jarFile;
+ }
+
+ @NotNull
+ public Manifest getManifest() {
+ return manifest;
+ }
+
+ @NotNull
+ public JcrNamespaceRegistry getNamespaceRegistry() {
+ return namespaceRegistry;
+ }
+
+ @NotNull
+ public List<PathEntry> getPathEntryList() {
+ return new ArrayList<>(pathEntryList);
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/BundleSlingInitialContentExtractor.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/BundleSlingInitialContentExtractor.java
new file mode 100644
index 0000000..941b67f
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/BundleSlingInitialContentExtractor.java
@@ -0,0 +1,132 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.jackrabbit.vault.packaging.PackageType;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.features.FeaturesManager;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.PathEntry;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+/**
+ * Extracts the sling initial content from a bundle to an java.io.InputStream.
+ */
+public class BundleSlingInitialContentExtractor {
+
+ protected final AssemblerProvider assemblerProvider = new AssemblerProvider();
+ protected final ContentReaderProvider contentReaderProvider = new ContentReaderProvider();
+ protected final ParentFolderRepoInitHandler parentFolderRepoInitHandler = new ParentFolderRepoInitHandler();
+
+ /**
+ * Extract the bundle sling initial content, assemble it into package assemblers into a different package,
+ * And strip the bundle of all the sling initial content.
+ *
+ * @param context all context variables needed to perform the extraction.
+ * @return stripped bundle inputstream
+ * @throws IOException
+ * @throws ConverterException
+ */
+ @Nullable
+ public InputStream extract(@NotNull BundleSlingInitialContentExtractContext context) throws IOException, ConverterException {
+
+ ContentPackage2FeatureModelConverter contentPackage2FeatureModelConverter = context.getConverter();
+
+ if (context.getSlingInitialContentPolicy() == ContentPackage2FeatureModelConverter.SlingInitialContentPolicy.KEEP) {
+ return null;
+ }
+ if (CollectionUtils.isEmpty(context.getPathEntryList())) {
+ return null;
+ }
+
+ // create a bundle file that will contain all non-sling initial content
+ Path strippedBundleFile = getNewBundleFile(context, contentPackage2FeatureModelConverter);
+
+ // collect the metadata into a set first, we need all the data upfront in our second loop.
+ SlingInitialContentBundleEntryMetaDataCollector collector =
+ new SlingInitialContentBundleEntryMetaDataCollector(context, contentPackage2FeatureModelConverter, strippedBundleFile);
+ Set<SlingInitialContentBundleEntryMetaData> collectedSlingInitialContentBundleEntries = collector.collectFromContextAndWriteTmpFiles();
+
+ // now that we got collectedSlingInitialContentBundleEntries ready, we loop it and perform an extract for each entry.
+ // then add it into the appropriate vault package assemblers
+ BundleSlingInitialContentJarEntryExtractor jarEntryExtractor =
+ new BundleSlingInitialContentJarEntryExtractor(assemblerProvider, contentReaderProvider, parentFolderRepoInitHandler);
+
+ for (SlingInitialContentBundleEntryMetaData slingInitialContentBundleEntryMetaData : collectedSlingInitialContentBundleEntries) {
+ jarEntryExtractor.extractAndAddToAssembler(context, slingInitialContentBundleEntryMetaData, collectedSlingInitialContentBundleEntries);
+ }
+
+ // add additional content packages to feature model
+ finalizePackageAssembly(context);
+
+ // return bundle's inputstream, stripped off sling initial content, which must be deleted on close
+ return Files.newInputStream(strippedBundleFile, StandardOpenOption.READ, StandardOpenOption.DELETE_ON_CLOSE);
+ }
+
+ static Version getModifiedOsgiVersion(@NotNull Version originalVersion) {
+ return new Version(originalVersion.getMajor(),
+ originalVersion.getMinor(),
+ originalVersion.getMicro(),
+ originalVersion.getQualifier() + "_" + ContentPackage2FeatureModelConverter.PACKAGE_CLASSIFIER);
+ }
+
+ public void reset() {
+ parentFolderRepoInitHandler.reset();
+ }
+
+ public void addRepoInitExtension(@NotNull List<VaultPackageAssembler> assemblers, @NotNull FeaturesManager featureManager) throws IOException, ConverterException {
+ parentFolderRepoInitHandler.addRepoinitExtension(assemblers, featureManager);
+ }
+
+ protected void finalizePackageAssembly(@NotNull BundleSlingInitialContentExtractContext context) throws IOException, ConverterException {
+ for (Map.Entry<PackageType, VaultPackageAssembler> entry : assemblerProvider.getPackageAssemblerEntrySet()) {
+ File packageFile = entry.getValue().createPackage();
+ ContentPackage2FeatureModelConverter converter = context.getConverter();
+ converter.processSubPackage(context.getPath() + "-" + entry.getKey(), context.getRunMode(), converter.open(packageFile), false);
+ }
+ assemblerProvider.clear();
+ }
+
+ private Path getNewBundleFile(@NotNull BundleSlingInitialContentExtractContext context, ContentPackage2FeatureModelConverter contentPackage2FeatureModelConverter) throws IOException {
+ final Manifest manifest = context.getManifest();
+ // remove header
+ manifest.getMainAttributes().remove(new Attributes.Name(PathEntry.CONTENT_HEADER));
+ // change version to have suffix
+ Version originalVersion = new Version(Objects.requireNonNull(manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION)));
+ manifest.getMainAttributes().putValue(Constants.BUNDLE_VERSION, getModifiedOsgiVersion(originalVersion).toString());
+ return Files.createTempFile(contentPackage2FeatureModelConverter.getTempDirectory().toPath(), "newBundle", ".jar");
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/BundleSlingInitialContentJarEntryExtractor.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/BundleSlingInitialContentJarEntryExtractor.java
new file mode 100644
index 0000000..25d31d2
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/BundleSlingInitialContentJarEntryExtractor.java
@@ -0,0 +1,150 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.vltpkg.DocViewSerializerContentHandlerException;
+import org.apache.sling.feature.cpconverter.vltpkg.SingleFileArchive;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentReader;
+import org.apache.sling.jcr.contentloader.PathEntry;
+import org.jetbrains.annotations.NotNull;
+
+import javax.jcr.RepositoryException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Set;
+
+import static org.apache.sling.feature.cpconverter.shared.ConverterConstants.SLASH;
+
+/**
+ * Handles the bundle sling initial content extraction on the jarEntry level.
+ */
+class BundleSlingInitialContentJarEntryExtractor {
+
+ private final AssemblerProvider assemblerProvider;
+ private final ContentReaderProvider contentReaderProvider;
+ private final ParentFolderRepoInitHandler parentFolderRepoInitHandler;
+
+ BundleSlingInitialContentJarEntryExtractor(@NotNull AssemblerProvider assemblerProvider,
+ @NotNull ContentReaderProvider contentReaderProvider,
+ @NotNull ParentFolderRepoInitHandler parentFolderRepoInitHandler) {
+ this.assemblerProvider = assemblerProvider;
+ this.contentReaderProvider = contentReaderProvider;
+ this.parentFolderRepoInitHandler = parentFolderRepoInitHandler;
+ }
+
+ /**
+ * Extract slingInitialContentBundleEntryMetaData and add it to the assembler
+ *
+ * @param context BundleSlingInitialContentExtractContext
+ * @param slingInitialContentBundleEntryMetaData SlingInitialContentBundleEntryMetaData
+ * @param collectedSlingInitialContentBundleEntries complete set of all SlingInitialContentBundleEntryMetaData for the bundle
+ * @throws IOException
+ * @throws ConverterException
+ */
+ void extractAndAddToAssembler(@NotNull BundleSlingInitialContentExtractContext context,
+ @NotNull SlingInitialContentBundleEntryMetaData slingInitialContentBundleEntryMetaData,
+ @NotNull Set<SlingInitialContentBundleEntryMetaData> collectedSlingInitialContentBundleEntries) throws IOException, ConverterException {
+
+ String repositoryPath = slingInitialContentBundleEntryMetaData.getRepositoryPath();
+ File file = slingInitialContentBundleEntryMetaData.getTargetFile();
+ PathEntry pathEntryValue = slingInitialContentBundleEntryMetaData.getPathEntry();
+ // all entry paths used by entry handlers start with "/"
+ String contentPackageEntryPath = SLASH + org.apache.jackrabbit.vault.util.Constants.ROOT_DIR + PlatformNameFormat.getPlatformPath(repositoryPath);
+
+ Path tmpDocViewInputFile = null;
+
+ try (InputStream bundleFileInputStream = new FileInputStream(file)) {
+ VaultPackageAssembler packageAssembler = assemblerProvider.initPackageAssemblerForPath(context, repositoryPath, pathEntryValue);
+
+ final ContentReader contentReader = contentReaderProvider.getContentReaderForEntry(file, pathEntryValue);
+ if (contentReader != null) {
+
+ // convert to docview xml
+ tmpDocViewInputFile = Files.createTempFile(context.getConverter().getTempDirectory().toPath(), "docview", ".xml");
+ try (OutputStream docViewOutput = Files.newOutputStream(tmpDocViewInputFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
+
+ repositoryPath = FilenameUtils.removeExtension(repositoryPath);
+ boolean isFileDescriptorEntry = isFileDescriptor(collectedSlingInitialContentBundleEntries, contentPackageEntryPath);
+ VaultContentXMLContentCreator contentCreator = new VaultContentXMLContentCreator(StringUtils.substringBeforeLast(repositoryPath, "/"), docViewOutput, context.getNamespaceRegistry(), packageAssembler, isFileDescriptorEntry);
+
+
+ if (file.getName().endsWith(".xml")) {
+ contentCreator.setIsXmlProcessed();
+ }
+
+ contentReader.parse(file.toURI().toURL(), contentCreator);
+ contentPackageEntryPath = new ContentPackageEntryPathComputer(collectedSlingInitialContentBundleEntries, contentPackageEntryPath, contentCreator).compute();
+ contentCreator.finish();
+
+ } catch (IOException e) {
+ throw new IOException("Can not parse " + file, e);
+ } catch (DocViewSerializerContentHandlerException | RepositoryException e) {
+ throw new IOException("Can not convert " + file + " to enhanced DocView format", e);
+ }
+
+ // remap CND files to make sure they are picked up by the NodeTypesEntryHandler
+ if (context.getNamespaceRegistry().getRegisteredCndSystemIds().contains(file.getName())) {
+ contentPackageEntryPath = "/META-INF/vault/" + Text.getName(file.getName()) + ".cnd";
+ }
+
+
+ }
+
+ try (Archive virtualArchive = SingleFileArchive.fromPathOrInputStream(tmpDocViewInputFile, bundleFileInputStream,
+ () -> Files.createTempFile(context.getConverter().getTempDirectory().toPath(), "initial-content", Text.getName(file.getName())), contentPackageEntryPath)) {
+ // in which content package should this end up?
+
+ if (tmpDocViewInputFile != null) {
+ packageAssembler.addEntry(contentPackageEntryPath, tmpDocViewInputFile.toFile());
+ } else {
+ packageAssembler.addEntry(contentPackageEntryPath, bundleFileInputStream);
+ }
+ parentFolderRepoInitHandler.addParentsForPath(contentPackageEntryPath);
+ }
+
+ } finally {
+ if (tmpDocViewInputFile != null) {
+ Files.delete(tmpDocViewInputFile);
+ }
+ }
+ }
+
+ @NotNull
+ private boolean isFileDescriptor(@NotNull Set<SlingInitialContentBundleEntryMetaData> bundleEntries, @NotNull final String contentPackageEntryPath) {
+
+ //sometimes we are dealing with double extensions (.json.xml)
+ String recomputedContentPackageEntryPath = FilenameUtils.removeExtension(contentPackageEntryPath);
+
+ final String checkIfRecomputedPathCandidate = StringUtils.removeStart(recomputedContentPackageEntryPath, "/jcr_root");
+ return bundleEntries.stream().anyMatch(bundleEntry -> StringUtils.equals(checkIfRecomputedPathCandidate, bundleEntry.getRepositoryPath()));
+
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/ContentPackageEntryPathComputer.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/ContentPackageEntryPathComputer.java
new file mode 100644
index 0000000..96ab646
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/ContentPackageEntryPathComputer.java
@@ -0,0 +1,74 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Set;
+
+import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
+import static org.apache.sling.feature.cpconverter.shared.ConverterConstants.SLASH;
+
+/**
+ * Performs re-computation of the ContentPackagePath of the bundle entry (Sling Initial Content)
+ */
+class ContentPackageEntryPathComputer {
+
+ private final Set<SlingInitialContentBundleEntryMetaData> bundleEntries;
+ private final String contentPackageEntryPath;
+ private final VaultContentXMLContentCreator contentCreator;
+
+ ContentPackageEntryPathComputer(@NotNull Set<SlingInitialContentBundleEntryMetaData> bundleEntries,
+ @NotNull final String contentPackageEntryPath,
+ @NotNull VaultContentXMLContentCreator contentCreator) {
+ this.bundleEntries = bundleEntries;
+ this.contentPackageEntryPath = contentPackageEntryPath;
+ this.contentCreator = contentCreator;
+ }
+
+ @NotNull
+ String compute() {
+
+ String recomputedContentPackageEntryPath = FilenameUtils.removeExtension(contentPackageEntryPath);
+
+ // this covers the case of having a primary node name defined in the xml/json descriptor itself.
+ // if this is set, we need to use it in the path.
+ if (StringUtils.isNotBlank(contentCreator.getPrimaryNodeName())) {
+ //custom node name
+ recomputedContentPackageEntryPath = StringUtils.substringBeforeLast(recomputedContentPackageEntryPath, SLASH);
+ recomputedContentPackageEntryPath = recomputedContentPackageEntryPath + SLASH + contentCreator.getPrimaryNodeName();
+ }
+
+ final String checkIfRecomputedPathCandidate = StringUtils.removeStart(recomputedContentPackageEntryPath, "/jcr_root");
+ // check if the resulting candidate matches one of the repositoryPaths in the bundle entries we have.
+ // for example /apps/testJsonFile.json.xml (descriptor entry)
+ // will match /apps/testJsonFile.json (file entry)
+ if (bundleEntries.stream().anyMatch(bundleEntry -> StringUtils.equals(checkIfRecomputedPathCandidate, bundleEntry.getRepositoryPath()))) {
+ //we are dealing with a file descriptor here
+ recomputedContentPackageEntryPath = recomputedContentPackageEntryPath + ".dir/" + DOT_CONTENT_XML;
+ } else {
+ // in this case we are dealing with a folder descriptor. for example:
+ // /apps/testJsonFolder.json
+ // we want it to end up in the following format: /apps/testJsonFolder/.content.xml in our assembler.
+ recomputedContentPackageEntryPath = recomputedContentPackageEntryPath + SLASH + DOT_CONTENT_XML;
+ }
+
+ return recomputedContentPackageEntryPath;
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/ContentReaderProvider.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/ContentReaderProvider.java
new file mode 100644
index 0000000..6dfac17
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/ContentReaderProvider.java
@@ -0,0 +1,55 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.readers.XMLReader;
+import org.apache.sling.jcr.contentloader.ContentReader;
+import org.apache.sling.jcr.contentloader.PathEntry;
+import org.apache.sling.jcr.contentloader.internal.readers.JsonReader;
+import org.apache.sling.jcr.contentloader.internal.readers.ZipReader;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+
+/**
+ * Holds The ContentReader instances and provides them for pathEntries.
+ */
+class ContentReaderProvider {
+
+ static final JsonReader jsonReader = new JsonReader();
+ static final XMLReader xmlReader = new XMLReader();
+ static final ZipReader zipReader = new ZipReader();
+
+ @Nullable
+ ContentReader getContentReaderForEntry(@NotNull File entry, @NotNull PathEntry pathEntry) {
+ String entryName = entry.getName();
+ if (entryName.endsWith(".json") && !pathEntry.isIgnoredImportProvider("json")) {
+ return jsonReader;
+ } else if (entryName.endsWith(".xml") && !pathEntry.isIgnoredImportProvider("xml")) {
+ return xmlReader;
+ } else if (
+ (entryName.endsWith(".zip") && !pathEntry.isIgnoredImportProvider("zip")) ||
+ (entryName.endsWith(".jar") && !pathEntry.isIgnoredImportProvider("jar"))
+ ) {
+ return zipReader;
+ } else {
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/JcrNamespaceRegistryProvider.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/JcrNamespaceRegistryProvider.java
new file mode 100644
index 0000000..828a87e
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/JcrNamespaceRegistryProvider.java
@@ -0,0 +1,122 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.jackrabbit.commons.cnd.ParseException;
+import org.apache.sling.commons.osgi.ManifestHeader;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.framework.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.RepositoryException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+/**
+ * Encapsulates the JcrNamespace registry provision logic for Sling Initial Content
+ */
+class JcrNamespaceRegistryProvider {
+
+ public static final String NODETYPES_BUNDLE_HEADER = "Sling-Nodetypes";
+
+ public static final String NAMESPACES_BUNDLE_HEADER = "Sling-Namespaces";
+
+ private static final Logger logger = LoggerFactory.getLogger(JcrNamespaceRegistryProvider.class);
+
+ private final Manifest manifest;
+ private final JarFile jarFile;
+ private final Map<String, String> predefinedNamespaceUriByPrefix;
+
+ JcrNamespaceRegistryProvider(@NotNull Manifest manifest,
+ @NotNull JarFile jarFile,
+ @NotNull Map<String, String> predefinedNamespaceUriByPrefix) {
+
+ this.manifest = manifest;
+ this.jarFile = jarFile;
+ this.predefinedNamespaceUriByPrefix = predefinedNamespaceUriByPrefix;
+ }
+
+ @NotNull
+ JcrNamespaceRegistry provideRegistryFromBundle() throws IOException {
+ try {
+ JcrNamespaceRegistry registry = new JcrNamespaceRegistry();
+ for (Map.Entry<String, String> entry : predefinedNamespaceUriByPrefix.entrySet()) {
+ registry.registerNamespace(entry.getKey(), entry.getValue());
+ }
+
+ // parse Sling-Namespaces header (https://github.com/apache/sling-org-apache-sling-jcr-base/blob/66be360910c265473799635fcac0e23895898913/src/main/java/org/apache/sling/jcr/base/internal/loader/Loader.java#L192)
+ String namespacesDefinitionHeader = manifest.getMainAttributes().getValue(NAMESPACES_BUNDLE_HEADER);
+ if (namespacesDefinitionHeader != null) {
+ registerNamespacesIntoRegistry(registry, namespacesDefinitionHeader);
+ }
+
+ // parse Sling-Nodetypes header
+ String typesHeader = manifest.getMainAttributes().getValue(NODETYPES_BUNDLE_HEADER);
+ if (typesHeader != null) {
+ registerCndIntoRegistry(registry, typesHeader);
+ }
+ return registry;
+ } catch (final RepositoryException | ParseException e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+ private void registerNamespacesIntoRegistry(@NotNull final JcrNamespaceRegistry registry,
+ @NotNull final String namespacesDefinitionHeader) throws RepositoryException {
+ final StringTokenizer st = new StringTokenizer(namespacesDefinitionHeader, ",");
+
+ while (st.hasMoreTokens()) {
+ final String token = st.nextToken().trim();
+ int pos = token.indexOf('=');
+ if (pos == -1) {
+ logger.warn("createNamespaceRegistry: Bundle {} has an invalid namespace manifest header entry: {}",
+ manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME), token);
+ } else {
+ String prefix = token.substring(0, pos).trim();
+ String namespace = token.substring(pos + 1).trim();
+ registry.registerNamespace(prefix, namespace);
+ }
+ }
+ }
+
+ private void registerCndIntoRegistry(@NotNull final JcrNamespaceRegistry registry,
+ @NotNull final String typesHeader) throws IOException, ParseException, RepositoryException {
+ for (ManifestHeader.Entry entry : ManifestHeader.parse(typesHeader).getEntries()) {
+ JarEntry jarEntry = jarFile.getJarEntry(entry.getValue());
+ if (jarEntry == null) {
+ logger.warn("createNamespaceRegistry: Bundle {} has referenced a non existing node type definition: {}",
+ manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME), entry.getValue());
+ } else {
+ try (InputStream inputStream = jarFile.getInputStream(jarEntry);
+ Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
+ registry.registerCnd(reader, entry.getValue());
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/ParentFolderRepoInitHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/ParentFolderRepoInitHandler.java
new file mode 100644
index 0000000..b190137
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/ParentFolderRepoInitHandler.java
@@ -0,0 +1,120 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.features.FeaturesManager;
+import org.apache.sling.feature.cpconverter.repoinit.createpath.CreatePathSegmentProcessor;
+import org.apache.sling.feature.cpconverter.shared.RepoPath;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.repoinit.parser.operations.CreatePath;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Formatter;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
+
+/**
+ * Handles creating the parent folders for sling initial content entries from the bundle
+ */
+class ParentFolderRepoInitHandler {
+
+ private static final Logger logger = LoggerFactory.getLogger(ParentFolderRepoInitHandler.class);
+
+ private final Set<RepoPath> parentFolderPaths = new HashSet<>();
+
+ /**
+ * Handles creating the parent folders for sling initial content entries from the bundle
+ *
+ * @param contentPackageEntryRepositoryPath the actual repository path of the entry
+ * eg: /jcr_root/apps/mysite/component/component-a/.content.xml
+ */
+ void addParentsForPath(@NotNull String contentPackageEntryRepositoryPath) {
+
+ String parentFolder = contentPackageEntryRepositoryPath;
+ if (StringUtils.endsWith(contentPackageEntryRepositoryPath, DOT_CONTENT_XML)) {
+ parentFolder = StringUtils.substringBeforeLast(parentFolder, "/" + DOT_CONTENT_XML);
+ }
+ parentFolder = StringUtils.substringBeforeLast(parentFolder, "/");
+ parentFolder = StringUtils.substringAfter(parentFolder, "/jcr_root");
+
+ parentFolderPaths.add(new RepoPath(parentFolder));
+ }
+
+ void reset() {
+ parentFolderPaths.clear();
+ }
+
+ void addRepoinitExtension(@NotNull List<VaultPackageAssembler> assemblers,
+ @NotNull FeaturesManager featureManager) throws IOException, ConverterException {
+
+ try (Formatter formatter = new Formatter()) {
+ parentFolderPaths.stream()
+ .filter(entry -> parentFolderPaths.stream()
+ .noneMatch(other -> !other.equals(
+ entry) &&
+ other.startsWith(entry)
+ )
+ )
+ .filter(entry ->
+ !entry.isRepositoryPath()
+ )
+ .map(entry ->
+ // we want to make sure of all our entries that are repositoryPaths,
+ // we create repoinit statements to create the parent folders with proper types.
+ // if we don't do this we will end up with constraintViolationExceptions.
+ getCreatePath(entry, assemblers)
+ )
+ .filter(Objects::nonNull)
+
+ .forEach(
+ createPath -> formatter.format("%s", createPath.asRepoInitString())
+ );
+
+ String text = formatter.toString();
+
+ if (!text.isEmpty()) {
+ featureManager.addOrAppendRepoInitExtension("content-package", text, null);
+ }
+ }
+
+ }
+
+
+ @Nullable
+ CreatePath getCreatePath(@NotNull RepoPath path, @NotNull Collection<VaultPackageAssembler> packageAssemblers) {
+ if (path.getParent() == null) {
+ logger.debug("Omit create path statement for path '{}'", path);
+ return null;
+ }
+
+ CreatePath cp = new CreatePath("sling:Folder");
+ CreatePathSegmentProcessor.processSegments(path, packageAssemblers, cp);
+ return cp;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/SlingInitialContentBundleEntryMetaData.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/SlingInitialContentBundleEntryMetaData.java
new file mode 100644
index 0000000..d8b15be
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/SlingInitialContentBundleEntryMetaData.java
@@ -0,0 +1,77 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.sling.jcr.contentloader.PathEntry;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Objects;
+
+/**
+ * Holds BundleEntry MetaData.
+ */
+class SlingInitialContentBundleEntryMetaData {
+
+ private final File targetFile;
+ private final PathEntry pathEntry;
+ private final String repositoryPath;
+
+ SlingInitialContentBundleEntryMetaData(
+ @NotNull File targetFile,
+ @NotNull PathEntry pathEntry,
+ @NotNull String repositoryPath) {
+ this.targetFile = targetFile;
+ this.pathEntry = pathEntry;
+ this.repositoryPath = repositoryPath;
+ }
+
+ @NotNull
+ File getTargetFile() {
+ return targetFile;
+ }
+
+ @NotNull
+ PathEntry getPathEntry() {
+ return pathEntry;
+ }
+
+ @NotNull
+ String getRepositoryPath() {
+ return repositoryPath;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SlingInitialContentBundleEntryMetaData that = (SlingInitialContentBundleEntryMetaData) o;
+ return repositoryPath.equals(that.repositoryPath);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(targetFile, pathEntry, repositoryPath);
+ }
+
+ @Override
+ public String toString() {
+ return "SlingInitialContentBundleEntryMetaData{" +
+ "repositoryPath='" + repositoryPath + '\'' +
+ '}';
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/SlingInitialContentBundleEntryMetaDataCollector.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/SlingInitialContentBundleEntryMetaDataCollector.java
new file mode 100644
index 0000000..9b0bb8a
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/SlingInitialContentBundleEntryMetaDataCollector.java
@@ -0,0 +1,204 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.apache.sling.jcr.contentloader.PathEntry;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import static org.apache.sling.feature.cpconverter.shared.ConverterConstants.SLASH;
+
+/**
+ * Handles collecting the metadata for each sling initial content entry, to be used for extraction in another loop
+ */
+class SlingInitialContentBundleEntryMetaDataCollector {
+
+ private static final double THRESHOLD_RATIO = 10;
+ private static final int BUFFER = 512;
+ private static final long TOOBIG = 0x6400000; // Max size of unzipped data, 100MB
+
+ private final BundleSlingInitialContentExtractContext context;
+ private final String basePath;
+ private final ContentPackage2FeatureModelConverter contentPackage2FeatureModelConverter;
+ private final Path newBundleFile;
+ private final Set<SlingInitialContentBundleEntryMetaData> collectedSlingInitialContentBundleEntries = new HashSet<>();
+ private final AtomicLong total = new AtomicLong(0);
+ private final JarFile jarFile;
+
+ SlingInitialContentBundleEntryMetaDataCollector(@NotNull BundleSlingInitialContentExtractContext context,
+ @NotNull ContentPackage2FeatureModelConverter contentPackage2FeatureModelConverter,
+ @NotNull Path newBundleFile) {
+ this.context = context;
+ this.basePath = contentPackage2FeatureModelConverter.getTempDirectory().getPath();
+ this.contentPackage2FeatureModelConverter = contentPackage2FeatureModelConverter;
+ this.newBundleFile = newBundleFile;
+ this.jarFile = context.getJarFile();
+ }
+
+ /**
+ * Collects all the MetaData from the context into a set
+ *
+ * @return
+ * @throws IOException
+ */
+ @SuppressWarnings("java:S5042") // we already addressed this
+ @NotNull
+ Set<SlingInitialContentBundleEntryMetaData> collectFromContextAndWriteTmpFiles() throws IOException {
+
+ final Manifest manifest = context.getManifest();
+
+ // create JAR file to prevent extracting it twice and for random access
+ try (OutputStream fileOutput = Files.newOutputStream(newBundleFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
+ JarOutputStream bundleOutput = new JarOutputStream(fileOutput, manifest)) {
+
+ Enumeration<? extends JarEntry> entries = jarFile.entries();
+
+ // first we collect all the entries into a set, collectedSlingInitialContentBundleEntries.
+ // we need it up front to be perform various checks in another loop later.
+ while (entries.hasMoreElements()) {
+ JarEntry jarEntry = entries.nextElement();
+
+ if (jarEntry.getName().equals(JarFile.MANIFEST_NAME)) {
+ continue;
+ }
+
+ if (!jarEntry.isDirectory()) {
+ extractFile(jarEntry, bundleOutput);
+ }
+
+ if (total.get() + BUFFER > TOOBIG) {
+ throw new IllegalStateException("File being unzipped is too big.");
+ }
+ }
+ }
+
+ return collectedSlingInitialContentBundleEntries;
+ }
+
+ private void extractFile(JarEntry jarEntry, JarOutputStream bundleOutput) throws IOException {
+
+ byte[] data = new byte[BUFFER];
+ long compressedSize = jarEntry.getCompressedSize();
+
+ try (InputStream input = new BufferedInputStream(jarFile.getInputStream(jarEntry))) {
+ if (jarEntryIsSlingInitialContent(context, jarEntry)) {
+
+ File targetFile = new File(contentPackage2FeatureModelConverter.getTempDirectory(), jarEntry.getName());
+ String canonicalDestinationPath = targetFile.getCanonicalPath();
+
+
+ if (!checkIfPathStartsWithOrIsEqual(contentPackage2FeatureModelConverter.getTempDirectory().getCanonicalPath(), canonicalDestinationPath)) {
+ throw new IOException("Entry is outside of the target directory");
+ }
+
+ targetFile.getParentFile().mkdirs();
+ if (!targetFile.exists() && !targetFile.createNewFile()) {
+ throw new IOException("Could not create placeholder file!");
+ }
+
+ FileOutputStream fos = new FileOutputStream(targetFile);
+ safelyWriteOutputStream(compressedSize, data, input, fos, true);
+
+ SlingInitialContentBundleEntryMetaData bundleEntry = createSlingInitialContentBundleEntry(context, targetFile);
+ collectedSlingInitialContentBundleEntries.add(bundleEntry);
+ } else {
+ //write 'normal' content out to the normal bundle output
+ bundleOutput.putNextEntry(jarEntry);
+ safelyWriteOutputStream(compressedSize, data, input, bundleOutput, false);
+ IOUtils.copy(input, bundleOutput);
+ bundleOutput.closeEntry();
+ }
+ }
+ }
+
+ private void safelyWriteOutputStream(@NotNull long compressedSize,
+ @NotNull byte[] data,
+ @NotNull InputStream input,
+ @NotNull OutputStream fos,
+ boolean shouldClose) throws IOException {
+ int count;
+ BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);
+ while (total.get() + BUFFER <= TOOBIG && (count = input.read(data, 0, BUFFER)) != -1) {
+ dest.write(data, 0, count);
+ total.addAndGet(count);
+
+ double compressionRatio = (double) count / compressedSize;
+ if (compressionRatio > THRESHOLD_RATIO) {
+ // ratio between compressed and uncompressed data is highly suspicious, looks like a Zip Bomb Attack
+ break;
+ }
+ }
+ dest.flush();
+
+ if (shouldClose) {
+ dest.close();
+ }
+
+ }
+
+ private boolean jarEntryIsSlingInitialContent(@NotNull BundleSlingInitialContentExtractContext context, @NotNull JarEntry jarEntry) {
+ final String entryName = jarEntry.getName();
+ return context.getPathEntryList().stream().anyMatch(
+ pathEntry -> checkIfPathStartsWithOrIsEqual(pathEntry.getPath(), entryName)
+ );
+ }
+
+ @NotNull
+ private SlingInitialContentBundleEntryMetaData createSlingInitialContentBundleEntry(@NotNull BundleSlingInitialContentExtractContext context,
+ @NotNull File targetFile) throws UnsupportedEncodingException {
+ final String entryName = StringUtils.substringAfter(targetFile.getPath(), basePath + "/");
+ final PathEntry pathEntryValue = context.getPathEntryList().stream().filter(
+ pathEntry -> checkIfPathStartsWithOrIsEqual(pathEntry.getPath(), entryName)
+ ).findFirst().orElseThrow(NullPointerException::new);
+ final String target = pathEntryValue.getTarget();
+ // https://sling.apache.org/documentation/bundles/content-loading-jcr-contentloader.html#file-name-escaping
+ String repositoryPath = (target != null ? target : "/") + URLDecoder.decode(entryName.substring(pathEntryValue.getPath().length()), "UTF-8");
+ return new SlingInitialContentBundleEntryMetaData(targetFile, pathEntryValue, repositoryPath);
+ }
+
+
+ private static boolean checkIfPathStartsWithOrIsEqual(String pathA, String pathB) {
+ String fixedPath = pathA;
+ if (!fixedPath.endsWith(SLASH)) {
+ fixedPath = pathA + SLASH;
+ }
+ return pathB.startsWith(fixedPath) || pathB.equals(pathA);
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
new file mode 100644
index 0000000..8c92dfd
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
@@ -0,0 +1,232 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+
+ private static final String ACL_NOT_SUPPORTED_MSG = "Sling Initial Content - ACL statements are not supported yet . SLING issue: https://issues.apache.org/jira/browse/SLING-11060";
+
+ private final String repositoryPath;
+ private final OutputStream targetOutputStream;
+ private final VaultPackageAssembler packageAssembler;
+ private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+ private final JcrNamespaceRegistry namespaceRegistry;
+
+ private final boolean isFileDescriptorEntry;
+
+ private boolean isFirstElement = true;
+ private boolean finished = false;
+ private boolean xmlProcessed = false;
+ private String primaryNodeName;
+ private XMLNode currentNode;
+
+ VaultContentXMLContentCreator(@NotNull String repositoryPath,
+ @NotNull OutputStream targetOutputStream,
+ @NotNull JcrNamespaceRegistry namespaceRegistry,
+ @NotNull VaultPackageAssembler packageAssembler,
+ boolean isFileDescriptorEntry) {
+ this.repositoryPath = repositoryPath;
+ this.targetOutputStream = targetOutputStream;
+ this.packageAssembler = packageAssembler;
+ this.namespaceRegistry = namespaceRegistry;
+ this.isFileDescriptorEntry = isFileDescriptorEntry;
+ }
+
+ void setIsXmlProcessed() {
+ this.xmlProcessed = true;
+ }
+
+ @Override
+ public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+
+ final String elementName;
+ final String jcrNodeName;
+ if (xmlProcessed && isFirstElement) {
+ elementName = "jcr:root";
+ primaryNodeName = name;
+ jcrNodeName = name;
+ isFirstElement = false;
+ } else if (StringUtils.isNotBlank(name)) {
+ elementName = getValidElementName(name);
+ jcrNodeName = name;
+ } else {
+ elementName = "jcr:root";
+ jcrNodeName = null;
+ }
+
+
+ final String basePath;
+ if (parentNodePathStack.isEmpty()) {
+ basePath = repositoryPath;
+ } else {
+ StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+ for (Iterator<XMLNode> xmlNodeIterator = parentNodePathStack.descendingIterator(); xmlNodeIterator.hasNext(); ) {
+ XMLNode parent = xmlNodeIterator.next();
+ String parentJcrNodeName = parent.getJcrNodeName();
+ if (StringUtils.isNotBlank(parentJcrNodeName)) {
+ basePathBuilder.append("/");
+ basePathBuilder.append(parentJcrNodeName);
+ }
+ }
+ basePath = basePathBuilder.toString();
+ }
+
+ //if we are dealing with a descriptor file, we should use nt:file as default primaryType.
+
+ String defaultNtType = isFileDescriptorEntry ? JcrConstants.NT_FILE : JcrConstants.NT_UNSTRUCTURED;
+ String toUsePrimaryNodeType = StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : defaultNtType;
+ XMLNode intermediateNode = new XMLNode(packageAssembler, basePath, elementName, jcrNodeName, toUsePrimaryNodeType, mixinNodeTypes);
+ //add the created node to the correct parent if present
+ if (currentNode != null) {
+ currentNode.addChildNode(elementName, intermediateNode);
+ }
+ //switch the current node
+ currentNode = intermediateNode;
+
+ if (ArrayUtils.isNotEmpty(mixinNodeTypes)) {
+ currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+ }
+
+ parentNodePathStack.push(currentNode);
+ }
+
+ @NotNull
+ private static String getValidElementName(@NotNull String name) {
+ if (StringUtils.isNumeric(name.substring(0, 1))) {
+ return "_" + name;
+ }
+ return name;
+ }
+
+ @Nullable
+ public String getPrimaryNodeName() {
+ return primaryNodeName;
+ }
+
+ @Override
+ public void finishNode() {
+ if (parentNodePathStack.size() > 1) {
+ this.parentNodePathStack.pop();
+ }
+ this.currentNode = this.parentNodePathStack.peek();
+ }
+
+ @Override
+ public void finish() throws RepositoryException {
+
+ if (finished) {
+ return;
+ }
+ try {
+ XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+ writer.write();
+ finished = true;
+ } catch (XMLStreamException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+
+ @Override
+ public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+ currentNode.addProperty(name, propertyType, value);
+ }
+
+
+ @Override
+ public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+ currentNode.addProperty(name, propertyType, values);
+ }
+
+
+ @Override
+ public void createProperty(String name, Object value) throws RepositoryException {
+ currentNode.addProperty(name, value);
+ }
+
+
+ @Override
+ public void createProperty(String name, Object[] values) throws RepositoryException {
+ currentNode.addProperty(name, values);
+ }
+
+ @Override
+ public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+ this.createNode(name, JcrConstants.NT_FILE, null);
+ this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+ // ensure sensible last modification date
+ if (lastModified <= 0) {
+ lastModified = System.currentTimeMillis();
+ }
+ this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+ this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+ this.createProperty(JcrConstants.JCR_DATA, data);
+ }
+
+
+ @Override
+ public boolean switchCurrentNode(String subPath, String newNodeType) {
+ throw new UnsupportedOperationException(ACL_NOT_SUPPORTED_MSG);
+ }
+
+ @Override
+ public void createUser(String name, String password, Map<String, Object> extraProperties) {
+ throw new UnsupportedOperationException(ACL_NOT_SUPPORTED_MSG);
+ }
+
+ @Override
+ public void createGroup(String name, String[] members, Map<String, Object> extraProperties) {
+ throw new UnsupportedOperationException(ACL_NOT_SUPPORTED_MSG);
+ }
+
+ @Override
+ public void createAce(String principal, String[] grantedPrivileges, String[] deniedPrivileges, String order) {
+ throw new UnsupportedOperationException(ACL_NOT_SUPPORTED_MSG);
+ }
+
+ @Override
+ public void createAce(String principalId, String[] grantedPrivilegeNames, String[] deniedPrivilegeNames,
+ String order, Map<String, Value> restrictions, Map<String, Value[]> mvRestrictions,
+ Set<String> removedRestrictionNames) {
+ throw new UnsupportedOperationException(ACL_NOT_SUPPORTED_MSG);
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/XMLNodeToXMLFileWriter.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/XMLNodeToXMLFileWriter.java
new file mode 100644
index 0000000..a218d60
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/XMLNodeToXMLFileWriter.java
@@ -0,0 +1,110 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent;
+
+
+import javanet.staxutils.IndentingXMLEventWriter;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.jetbrains.annotations.NotNull;
+
+import javax.jcr.RepositoryException;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import static org.apache.jackrabbit.vault.util.JcrConstants.JCR_MIXINTYPES;
+import static org.apache.jackrabbit.vault.util.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.vault.util.JcrConstants.NT_UNSTRUCTURED;
+
+/**
+ * Takes the buffered parent XMLNode and writes it to an actual XML file
+ */
+class XMLNodeToXMLFileWriter {
+
+ private final XMLNode parentNode;
+ private final XMLEventWriter eventWriter;
+ private final JcrNamespaceRegistry namespaceRegistry;
+ private final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
+
+ XMLNodeToXMLFileWriter(@NotNull XMLNode parentNode,
+ @NotNull OutputStream targetOutputStream,
+ @NotNull JcrNamespaceRegistry namespaceRegistry) throws XMLStreamException {
+ this.parentNode = parentNode;
+ XMLEventWriter writer = XMLOutputFactory.newInstance().createXMLEventWriter(targetOutputStream, StandardCharsets.UTF_8.name());
+ this.eventWriter = new IndentingXMLEventWriter(
+ writer
+ );
+ this.namespaceRegistry = namespaceRegistry;
+ this.eventWriter.setNamespaceContext(this.namespaceRegistry);
+
+ }
+
+ void write() throws XMLStreamException, RepositoryException {
+
+ eventWriter.add(eventFactory.createStartDocument());
+ writeNode(parentNode, true);
+ eventWriter.add(eventFactory.createEndDocument());
+
+ }
+
+ void writeNode(@NotNull XMLNode xmlNode, boolean isFirstElement) throws RepositoryException, XMLStreamException {
+
+ eventWriter.add(eventFactory.createStartElement(StringUtils.EMPTY, StringUtils.EMPTY, xmlNode.getXmlElementName()));
+
+ if (isFirstElement) {
+ for (String prefix : namespaceRegistry.getPrefixes()) {
+ eventWriter.add(eventFactory.createNamespace(prefix, namespaceRegistry.getURI(prefix)));
+ }
+ }
+
+ String primaryNodeType = xmlNode.getPrimaryNodeType();
+ String[] mixinNodeTypes = xmlNode.getMixinNodeTypes();
+
+
+ eventWriter.add(eventFactory.createAttribute(JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : NT_UNSTRUCTURED));
+
+ if (ArrayUtils.isNotEmpty(mixinNodeTypes)) {
+ eventWriter.add(eventFactory.createAttribute(JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]"));
+ }
+
+ for (Map.Entry<String, String> entry : xmlNode.getVltXmlParsedProperties().entrySet()) {
+
+ if (entry.getKey().equals(JCR_PRIMARYTYPE) || entry.getKey().equals(JCR_MIXINTYPES)) {
+ continue;
+ }
+
+ eventWriter.add(eventFactory.createAttribute(entry.getKey(), entry.getValue()));
+ }
+
+ for (XMLNode node : xmlNode.getChildren().values()) {
+ writeNode(node, false);
+ }
+
+ eventWriter.add(eventFactory.createEndElement(StringUtils.EMPTY, StringUtils.EMPTY, xmlNode.getXmlElementName()));
+
+
+ }
+
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/PrimaryTypeParser.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/readers/XMLReader.java
similarity index 52%
copy from src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/PrimaryTypeParser.java
copy to src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/readers/XMLReader.java
index 49799aa..ac34617 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/PrimaryTypeParser.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/readers/XMLReader.java
@@ -14,30 +14,15 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package org.apache.sling.feature.cpconverter.accesscontrol;
+package org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.readers;
-import org.apache.sling.feature.cpconverter.shared.AbstractJcrNodeParser;
-import org.xml.sax.Attributes;
+import org.apache.sling.jcr.contentloader.internal.readers.XmlReader;
-final class PrimaryTypeParser extends AbstractJcrNodeParser<String> {
-
- public PrimaryTypeParser() {
- super();
- }
-
- @Override
- protected void onJcrRootNode(String uri, String localName, String qName, Attributes attributes, String primaryType) {
- detectedPrimaryType = primaryType;
- }
-
- @Override
- protected void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) {
- // not needed
- }
-
- @Override
- protected String getParsingResult() {
- return detectedPrimaryType;
+/**
+ * Extends the XMLReader to perform the activate method on construction, since it's protected.
+ */
+public class XMLReader extends XmlReader {
+ public XMLReader() {
+ this.activate();
}
-
}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/xmlbuffer/XMLNode.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/xmlbuffer/XMLNode.java
new file mode 100644
index 0000000..dcad4b6
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/xmlbuffer/XMLNode.java
@@ -0,0 +1,158 @@
+/*
+ * 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.cpconverter.handlers.slinginitialcontent.xmlbuffer;
+
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Represents an XML node in the buffer to be written in a sling initial content package later
+ */
+public class XMLNode {
+
+ private static final String FORMAT_SINGLE_VALUE = "%s";
+ private static final String FORMAT_SINGLE_VALUE_TYPED = "{%s}%s";
+ private static final String FORMAT_MULTI_VALUE = "[%s]";
+ private static final String FORMAT_MULTI_VALUE_TYPED = "{%s}[%s]";
+
+ private final String basePath;
+ private final String xmlElementName;
+ private final String jcrNodeName;
+ private final String primaryNodeType;
+ private final String[] mixinNodeTypes;
+
+ private final VaultPackageAssembler packageAssembler;
+ private final Map<String, String> vltXmlParsedProperties = new HashMap<>();
+ private final Map<String, XMLNode> children = new LinkedHashMap<>();
+
+ public XMLNode(@NotNull VaultPackageAssembler packageAssembler,
+ @NotNull String basePath,
+ @NotNull String xmlElementName,
+ @Nullable String jcrNodeName,
+ @NotNull String primaryNodeType,
+ @Nullable String[] mixinNodeTypes) {
+ this.packageAssembler = packageAssembler;
+ this.basePath = basePath;
+ this.xmlElementName = xmlElementName;
+ this.jcrNodeName = jcrNodeName;
+ this.primaryNodeType = primaryNodeType;
+ this.mixinNodeTypes = mixinNodeTypes;
+ }
+
+ public void addProperty(@NotNull String name, int propertyType, @NotNull String value) {
+ String propertyTypeName = PropertyType.nameFromValue(propertyType);
+
+ if (propertyType > 0) {
+ vltXmlParsedProperties.put(name, String.format(FORMAT_SINGLE_VALUE_TYPED, propertyTypeName, value));
+ } else {
+ vltXmlParsedProperties.put(name, String.format(FORMAT_SINGLE_VALUE, value));
+ }
+
+ }
+
+ public void addProperty(@NotNull String name, int propertyType, @NotNull String[] values) {
+ String propertyTypeName = PropertyType.nameFromValue(propertyType);
+
+ if (propertyType > 0) {
+ vltXmlParsedProperties.put(name, String.format(FORMAT_MULTI_VALUE_TYPED, propertyTypeName, String.join(",", values)));
+ } else {
+ vltXmlParsedProperties.put(name, String.format(FORMAT_MULTI_VALUE, String.join(",", values)));
+ }
+
+ }
+
+ public void addChildNode(@NotNull String name, @NotNull XMLNode xmlNode) {
+ this.children.put(name, xmlNode);
+ }
+
+ public void addProperty(@NotNull String name, @Nullable Object value) throws RepositoryException {
+ if (value == null) {
+ return;
+ }
+
+ if (value instanceof Long) {
+ vltXmlParsedProperties.put(name, String.format(FORMAT_SINGLE_VALUE_TYPED, "Long", value.toString()));
+ } else if (value instanceof Date) {
+ vltXmlParsedProperties.put(name, String.format(FORMAT_SINGLE_VALUE_TYPED, "Date", ((Date) value).toGMTString()));
+ } else if (value instanceof Calendar) {
+ vltXmlParsedProperties.put(name, String.format(FORMAT_SINGLE_VALUE_TYPED, "Date", ((Calendar) value).getTime().toGMTString()));
+ } else if (value instanceof Double) {
+ vltXmlParsedProperties.put(name, String.format(FORMAT_SINGLE_VALUE_TYPED, "Double", value));
+ } else if (value instanceof Boolean) {
+ boolean theBoolValue = Boolean.parseBoolean(value.toString());
+ vltXmlParsedProperties.put(name, String.format(FORMAT_SINGLE_VALUE_TYPED, "Boolean", theBoolValue));
+ } else if (value instanceof InputStream) {
+ vltXmlParsedProperties.put(name, "{Binary}");
+ String path = "jcr_root/" + this.basePath;
+ try {
+ packageAssembler.addEntry(path, (InputStream) value);
+ } catch (IOException e) {
+ throw new RepositoryException(e);
+ }
+
+ } else {
+ vltXmlParsedProperties.put(name, String.format(FORMAT_SINGLE_VALUE, value));
+ }
+
+ }
+
+ @NotNull
+ public String getPath() {
+ return this.basePath + "/" + this.jcrNodeName;
+ }
+
+ @NotNull
+ public String getXmlElementName() {
+ return xmlElementName;
+ }
+
+ @Nullable
+ public String getJcrNodeName() {
+ return jcrNodeName;
+ }
+
+ @NotNull
+ public String getPrimaryNodeType() {
+ return primaryNodeType;
+ }
+
+ @Nullable
+ public String[] getMixinNodeTypes() {
+ return mixinNodeTypes;
+ }
+
+ @NotNull
+ public Map<String, XMLNode> getChildren() {
+ return children;
+ }
+
+ @NotNull
+ public Map<String, String> getVltXmlParsedProperties() {
+ return vltXmlParsedProperties;
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/repoinit/createpath/CreatePathSegmentProcessor.java b/src/main/java/org/apache/sling/feature/cpconverter/repoinit/createpath/CreatePathSegmentProcessor.java
new file mode 100644
index 0000000..9e9185a
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/repoinit/createpath/CreatePathSegmentProcessor.java
@@ -0,0 +1,105 @@
+/*
+ * 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.cpconverter.repoinit.createpath;
+
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
+import org.apache.sling.feature.cpconverter.shared.RepoPath;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.repoinit.parser.operations.CreatePath;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
+
+public class CreatePathSegmentProcessor {
+
+ private CreatePathSegmentProcessor() {
+ }
+
+ /**
+ * Process segments of a repopath to createpath, checking packageassemblers for existing primaryType definitions.
+ *
+ * @param path
+ * @param packageAssemblers
+ * @param cp
+ * @return
+ */
+ public static boolean processSegments(@NotNull RepoPath path, @NotNull Collection<VaultPackageAssembler> packageAssemblers, @NotNull CreatePath cp) {
+ StringBuilder platformPath = new StringBuilder();
+ boolean foundType = false;
+ for (String part : path.getSegments()) {
+ String platformName = PlatformNameFormat.getPlatformName(part);
+ platformPath.append(platformPath.toString().isEmpty() ? platformName : "/" + platformName);
+
+ boolean segmentAdded = false;
+ //loop all package assemblers and check if .content.xml is defined
+ for (VaultPackageAssembler packageAssembler : packageAssemblers) {
+ File currentContent = packageAssembler.getEntry(platformPath + "/" + DOT_CONTENT_XML);
+ if (currentContent.exists() && currentContent.isFile()) {
+ //add segment if jcr:primaryType is defined.
+ segmentAdded = addSegment(cp, part, currentContent);
+ if (segmentAdded) {
+ foundType = true;
+ break;
+ }
+ }
+ }
+ if (!segmentAdded) {
+ //use sling:Folder (defined by repo-init runtime module)
+ cp.addSegment(part, null);
+ }
+ }
+ return foundType;
+ }
+
+ private static boolean addSegment(@NotNull CreatePath cp, @NotNull String part, @NotNull File currentContent) {
+ try (FileInputStream input = new FileInputStream(currentContent);
+ FileInputStream input2 = new FileInputStream(currentContent)) {
+ String primary = new PrimaryTypeParser().parse(input);
+ if (primary != null) {
+ List<String> mixins = new ArrayList<>();
+ String mixin = new MixinParser().parse(input2);
+ if (mixin != null) {
+ mixin = mixin.trim();
+ if (mixin.startsWith("[")) {
+ mixin = mixin.substring(1, mixin.length() - 1);
+ }
+ for (String m : mixin.split(",")) {
+ String mixinName = m.trim();
+ if (!mixinName.isEmpty()) {
+ mixins.add(mixinName);
+ }
+ }
+ }
+ cp.addSegment(part, primary, mixins);
+ return true;
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("A fatal error occurred while parsing the '"
+ + currentContent
+ + "' file, see nested exceptions: "
+ + e);
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/MixinParser.java b/src/main/java/org/apache/sling/feature/cpconverter/repoinit/createpath/MixinParser.java
similarity index 91%
rename from src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/MixinParser.java
rename to src/main/java/org/apache/sling/feature/cpconverter/repoinit/createpath/MixinParser.java
index 236e401..f6a9c36 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/MixinParser.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/repoinit/createpath/MixinParser.java
@@ -14,13 +14,13 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package org.apache.sling.feature.cpconverter.accesscontrol;
+package org.apache.sling.feature.cpconverter.repoinit.createpath;
import org.apache.jackrabbit.JcrConstants;
import org.apache.sling.feature.cpconverter.shared.AbstractJcrNodeParser;
import org.xml.sax.Attributes;
-final class MixinParser extends AbstractJcrNodeParser<String> {
+public final class MixinParser extends AbstractJcrNodeParser<String> {
private String mixins;
public MixinParser() {
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/PrimaryTypeParser.java b/src/main/java/org/apache/sling/feature/cpconverter/repoinit/createpath/PrimaryTypeParser.java
similarity index 90%
rename from src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/PrimaryTypeParser.java
rename to src/main/java/org/apache/sling/feature/cpconverter/repoinit/createpath/PrimaryTypeParser.java
index 49799aa..22a2b44 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/PrimaryTypeParser.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/repoinit/createpath/PrimaryTypeParser.java
@@ -14,12 +14,12 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package org.apache.sling.feature.cpconverter.accesscontrol;
+package org.apache.sling.feature.cpconverter.repoinit.createpath;
import org.apache.sling.feature.cpconverter.shared.AbstractJcrNodeParser;
import org.xml.sax.Attributes;
-final class PrimaryTypeParser extends AbstractJcrNodeParser<String> {
+public final class PrimaryTypeParser extends AbstractJcrNodeParser<String> {
public PrimaryTypeParser() {
super();
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/shared/ConverterConstants.java b/src/main/java/org/apache/sling/feature/cpconverter/shared/ConverterConstants.java
index a76f96a..94507fc 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/shared/ConverterConstants.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/shared/ConverterConstants.java
@@ -22,4 +22,5 @@ public final class ConverterConstants {
public static final String SYSTEM_USER_REL_PATH_DEFAULT = "system";
+ public static final String SLASH = "/";
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/DocViewSerializerContentHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/DocViewSerializerContentHandler.java
index 89f4a97..672650a 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/DocViewSerializerContentHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/DocViewSerializerContentHandler.java
@@ -16,16 +16,7 @@
*/
package org.apache.sling.feature.cpconverter.vltpkg;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.AbstractMap;
-import java.util.Map;
-
-import javax.jcr.NamespaceException;
-import javax.jcr.RepositoryException;
-import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.XMLStreamWriter;
-
+import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
import org.apache.jackrabbit.spi.commons.conversion.NameParser;
@@ -35,6 +26,18 @@ import org.apache.jackrabbit.vault.fs.impl.io.DocViewSAXFormatter;
import org.apache.jackrabbit.vault.fs.io.DocViewFormat;
import org.apache.jackrabbit.vault.util.xml.serialize.FormattingXmlStreamWriter;
import org.apache.sling.contentparser.api.ContentHandler;
+import org.jetbrains.annotations.NotNull;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.RepositoryException;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.AbstractMap;
+import java.util.Map;
+
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* Similar to {@link DocViewSAXFormatter} but works outside a repository-based context on the input generated through {@link ContentHandler} callbacks.
@@ -52,7 +55,8 @@ public class DocViewSerializerContentHandler implements ContentHandler, AutoClos
try {
writer = FormattingXmlStreamWriter.create(outputStream, new DocViewFormat().getXmlOutputFormat());
writer.writeStartDocument();
-
+ writer.setNamespaceContext(nsRegistry);
+
} catch (XMLStreamException e) {
throw new DocViewSerializerContentHandlerException("Can not start document", e);
}
@@ -74,7 +78,8 @@ public class DocViewSerializerContentHandler implements ContentHandler, AutoClos
try {
// now split by prefix and local name
Map.Entry<String, Name> prefixAndQualifiedName = resolvePrefixedName(name);
- writer.writeStartElement(prefixAndQualifiedName.getKey(), prefixAndQualifiedName.getValue().getNamespaceURI(), prefixAndQualifiedName.getValue().getLocalName());
+ writeElement(prefixAndQualifiedName);
+
if (isFirstElement) {
for (String prefix : nsRegistry.getPrefixes()) {
writer.writeNamespace(prefix, nsRegistry.getURI(prefix));
@@ -84,7 +89,12 @@ public class DocViewSerializerContentHandler implements ContentHandler, AutoClos
for (Map.Entry<String, Object> property : properties.entrySet()) {
// now split by prefix and local name
prefixAndQualifiedName = resolvePrefixedName(property.getKey());
- writer.writeAttribute(prefixAndQualifiedName.getKey(), prefixAndQualifiedName.getValue().getNamespaceURI(), prefixAndQualifiedName.getValue().getLocalName(), ValueConverter.toString(property.getKey(), property.getValue()));
+ writer.writeAttribute(
+ prefixAndQualifiedName.getKey(),
+ prefixAndQualifiedName.getValue().getNamespaceURI(),
+ prefixAndQualifiedName.getValue().getLocalName(),
+ ValueConverter.toString(property.getKey(), property.getValue())
+ );
}
} catch (XMLStreamException e) {
throw new DocViewSerializerContentHandlerException("Can not start element", e);
@@ -93,6 +103,41 @@ public class DocViewSerializerContentHandler implements ContentHandler, AutoClos
}
}
+ private boolean writeElement(Map.Entry<String, Name> prefixAndQualifiedName) throws XMLStreamException {
+
+ String key = prefixAndQualifiedName.getKey();
+ String namespaceURI = prefixAndQualifiedName.getValue().getNamespaceURI();
+ String localName = getValidLocalName(prefixAndQualifiedName);
+
+ if (isNotBlank(localName)) {
+ if (isNotBlank(namespaceURI) && isNotBlank(nsRegistry.getPrefix(namespaceURI)) && isNotBlank(key)) {
+ //uri already registered in context, this method will do
+ writer.writeStartElement(namespaceURI, localName);
+ } else if (isNotBlank(namespaceURI) && isNotBlank(key)) {
+ //uri not registered in context, so writing it out completely
+ writer.writeStartElement(key, namespaceURI, localName);
+ } else {
+ writer.writeStartElement(localName);
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ @NotNull
+ private String getValidLocalName(Map.Entry<String, Name> prefixAndQualifiedName) {
+ String localName = prefixAndQualifiedName.getValue().getLocalName();
+
+ String firstCharacter = localName.substring(0, 1);
+ if (StringUtils.isNumeric(firstCharacter)) {
+ //fix localName starting out with numbers as this is illegal.
+ localName = "_x003" + firstCharacter + "_" + StringUtils.substring(localName, 1);
+ }
+ return localName;
+ }
+
public void closeParents(String stopAtParent) {
try {
while (!currentPath.equals(stopAtParent)) {
@@ -117,7 +162,7 @@ public class DocViewSerializerContentHandler implements ContentHandler, AutoClos
try {
String prefix = name.substring(0, posColon);
return new AbstractMap.SimpleEntry<>(prefix, NameParser.parse(name, nsRegistry, NameFactoryImpl.getInstance()));
- } catch (IllegalNameException|NamespaceException e) {
+ } catch (IllegalNameException | NamespaceException e) {
throw new DocViewSerializerContentHandlerException("Could not resolve namespace URI for name " + name, e);
}
}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/JcrNamespaceRegistry.java b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/JcrNamespaceRegistry.java
index d35304b..e1ea0b5 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/JcrNamespaceRegistry.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/JcrNamespaceRegistry.java
@@ -16,30 +16,37 @@
*/
package org.apache.sling.feature.cpconverter.vltpkg;
+import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.commons.SimpleValueFactory;
import org.apache.jackrabbit.commons.cnd.CndImporter;
import org.apache.jackrabbit.commons.cnd.ParseException;
import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
import org.apache.jackrabbit.vault.validation.spi.impl.nodetype.NodeTypeManagerProvider;
import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.jcr.NamespaceException;
import javax.jcr.NamespaceRegistry;
import javax.jcr.RepositoryException;
import javax.jcr.ValueFactory;
import javax.jcr.nodetype.NodeTypeManager;
+import javax.xml.namespace.NamespaceContext;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
/** Simple namespace registry backed by a map */
-public class JcrNamespaceRegistry implements NamespaceRegistry, NamespaceResolver {
+public class JcrNamespaceRegistry implements NamespaceRegistry, NamespaceResolver, NamespaceContext {
private final Collection<String> registeredCndSystemIds = new ArrayList<>();
private final NodeTypeManagerProvider ntManagerProvider = new NodeTypeManagerProvider();
private final NodeTypeManager ntManager = ntManagerProvider.getNodeTypeManager();
-
+ private final Logger logger = LoggerFactory.getLogger(JcrNamespaceRegistry.class);
+
public JcrNamespaceRegistry() throws RepositoryException, ParseException, IOException {
ntManagerProvider.registerNamespace(PREFIX_XML, NAMESPACE_XML);
ntManagerProvider.registerNamespace("sling", "http://sling.apache.org/jcr/sling/1.0");
@@ -83,14 +90,30 @@ public class JcrNamespaceRegistry implements NamespaceRegistry, NamespaceResolve
}
@Override
- public String getPrefix(String uri) throws NamespaceException {
+ public String getNamespaceURI(String prefix) {
+ try {
+ return ntManagerProvider.getURI(prefix);
+ } catch (RepositoryException e) {
+ logger.info("Could not find prefix {} in registered namespaces", prefix);
+ return StringUtils.EMPTY;
+ }
+ }
+
+ @Override
+ public String getPrefix(String uri) {
try {
return ntManagerProvider.getPrefix(uri);
} catch (RepositoryException e) {
- throw new NamespaceException(e);
+ logger.info("Could not find uri {} in registered namespaces", uri);
+ return null;
}
}
+ @Override
+ public Iterator<String> getPrefixes(String namespaceURI) {
+ return Collections.singletonList(getPrefix(namespaceURI)).iterator();
+ }
+
public @NotNull Collection<String> getRegisteredCndSystemIds() {
return registeredCndSystemIds;
}
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 e3692e2..6553df3 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
@@ -60,6 +60,7 @@ import static org.apache.jackrabbit.vault.util.Constants.META_DIR;
import static org.apache.jackrabbit.vault.util.Constants.PROPERTIES_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.shared.ConverterConstants.SLASH;
import static org.apache.sling.feature.cpconverter.vltpkg.VaultPackageUtils.getDependencies;
import static org.apache.sling.feature.cpconverter.vltpkg.VaultPackageUtils.setDependencies;
import static org.apache.sling.feature.cpconverter.vltpkg.VaultPackageUtils.toRepositoryPath;
@@ -233,8 +234,13 @@ public class VaultPackageAssembler {
}
public @NotNull File getEntry(@NotNull String path) {
- if (!path.startsWith(ROOT_DIR)) {
- path = ROOT_DIR + path;
+ if (!path.startsWith(ROOT_DIR + SLASH)) {
+ if(path.startsWith(SLASH)){
+ path = ROOT_DIR + path;
+ }else{
+ path = ROOT_DIR + SLASH + path;
+ }
+
}
return new File(storingDirectory, path);
@@ -358,7 +364,7 @@ public class VaultPackageAssembler {
}
}
}
-
+
private static final class RemoveInstallHooksPredicate implements Predicate<Map.Entry<Object, Object>> {
@Override
public boolean test(java.util.Map.Entry<Object, Object> entry) {
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/AdjustedFilterTest.java b/src/test/java/org/apache/sling/feature/cpconverter/AdjustedFilterTest.java
index b828f59..0792378 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/AdjustedFilterTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/AdjustedFilterTest.java
@@ -26,6 +26,7 @@ import org.apache.sling.feature.cpconverter.features.DefaultFeaturesManager;
import org.apache.sling.feature.cpconverter.features.FeaturesManager;
import org.apache.sling.feature.cpconverter.handlers.DefaultEntryHandlersManager;
import org.apache.sling.feature.cpconverter.handlers.EntryHandlersManager;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.BundleSlingInitialContentExtractor;
import org.apache.sling.feature.cpconverter.shared.ConverterConstants;
import org.apache.sling.feature.cpconverter.vltpkg.DefaultPackagesEventsEmitter;
import org.jetbrains.annotations.NotNull;
@@ -50,7 +51,7 @@ public class AdjustedFilterTest extends AbstractConverterTest {
private ContentPackage2FeatureModelConverter converter;
private File outputDirectory;
- private final EntryHandlersManager handlersManager = spy(new DefaultEntryHandlersManager(Collections.emptyMap(), false, ContentPackage2FeatureModelConverter.SlingInitialContentPolicy.KEEP, ConverterConstants.SYSTEM_USER_REL_PATH_DEFAULT));
+ private final EntryHandlersManager handlersManager = spy(new DefaultEntryHandlersManager(Collections.emptyMap(), false, ContentPackage2FeatureModelConverter.SlingInitialContentPolicy.KEEP,new BundleSlingInitialContentExtractor(), ConverterConstants.SYSTEM_USER_REL_PATH_DEFAULT));
@Before
public void setUp() throws Exception {
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 f5f3843..4aea947 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.sling.feature.cpconverter;
+import static org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.SlingInitialContentPolicy.EXTRACT_AND_REMOVE;
import static org.apache.sling.feature.cpconverter.Util.normalize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -34,7 +35,6 @@ import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.zip.ZipFile;
@@ -45,18 +45,14 @@ import javax.json.JsonObject;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.vault.fs.spi.PrivilegeDefinitions;
-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.PackageType;
import org.apache.jackrabbit.vault.packaging.VaultPackage;
import org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl;
-import org.apache.jackrabbit.vault.packaging.impl.ZipVaultPackage;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Artifacts;
-import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.Configuration;
import org.apache.sling.feature.Configurations;
import org.apache.sling.feature.Extension;
@@ -71,6 +67,8 @@ import org.apache.sling.feature.cpconverter.features.DefaultFeaturesManager;
import org.apache.sling.feature.cpconverter.filtering.RegexBasedResourceFilter;
import org.apache.sling.feature.cpconverter.handlers.DefaultEntryHandlersManager;
import org.apache.sling.feature.cpconverter.handlers.EntryHandlersManager;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.BundleSlingInitialContentExtractor;
+import org.apache.sling.feature.cpconverter.shared.ConverterConstants;
import org.apache.sling.feature.cpconverter.vltpkg.DefaultPackagesEventsEmitter;
import org.apache.sling.feature.io.json.FeatureJSONReader;
import org.junit.After;
@@ -93,8 +91,6 @@ public class ContentPackage2FeatureModelConverterTest extends AbstractConverterT
"test_b-1.0.zip",
"test_e-1.0.zip" };
- private static final String FORMAT = "%1$s/%2$s/%3$s/%2$s-%3$s-cp2fm-converted.zip";
-
private ContentPackage2FeatureModelConverter converter;
private EntryHandlersManager handlersManager;
@@ -103,6 +99,7 @@ public class ContentPackage2FeatureModelConverterTest extends AbstractConverterT
handlersManager = new DefaultEntryHandlersManager();
converter = new ContentPackage2FeatureModelConverter()
.setEntryHandlersManager(handlersManager)
+ .setFeaturesManager(new DefaultFeaturesManager(new File("")))
.setAclManager(new DefaultAclManager());
}
@@ -144,97 +141,6 @@ public class ContentPackage2FeatureModelConverterTest extends AbstractConverterT
}
@Test
- public void checkIfGeneratedDependenciesAreProvided() throws Exception {
- URL packageUrl = getClass().getResource("build_playground.ui.content-1.0-SNAPSHOT.zip");
- File packageFile = FileUtils.toFile(packageUrl);
-
- File outputDirectory = new File(System.getProperty("java.io.tmpdir"), getClass().getName() + '_' + System.currentTimeMillis());
- File unreferencedOutputDir = new File(outputDirectory, "mutable-content-packages");
- try {
-
- converter = new ContentPackage2FeatureModelConverter(false, ContentPackage2FeatureModelConverter.SlingInitialContentPolicy.EXTRACT_AND_REMOVE)
- .setEntryHandlersManager(handlersManager)
- .setAclManager(new DefaultAclManager());
-
- converter.setFeaturesManager(new DefaultFeaturesManager(true, 5, outputDirectory, null, null, new HashMap<>(), new DefaultAclManager()))
- .setBundlesDeployer(new LocalMavenRepositoryArtifactsDeployer(outputDirectory))
- .setEmitter(DefaultPackagesEventsEmitter.open(outputDirectory))
- .setContentTypePackagePolicy(PackagePolicy.PUT_IN_DEDICATED_FOLDER)
- .setUnreferencedArtifactsDeployer(new LocalMavenRepositoryArtifactsDeployer(unreferencedOutputDir))
- .convert(packageFile);
-
-
- File featureModel = new File(outputDirectory, "playground.ui.content.json");
- Feature feature = FeatureJSONReader.read(new FileReader(featureModel), "");
-
- List<PackageId> allPackageIds = new ArrayList<>();
- List<Dependency> contentRefsDependencies = new ArrayList<>();
-
- getContentPackagesFromFeatureModel(outputDirectory, feature,allPackageIds);
- getContentPackagesFromRefs(outputDirectory,allPackageIds,contentRefsDependencies);
-
-
- for(Dependency dependency : contentRefsDependencies){
- boolean foundDep = allPackageIds.stream().filter(Objects::nonNull).anyMatch(dependency::matches);
- assertTrue("Dependency " + dependency.getName() + " is not present in the feature model or content refs", foundDep);
- }
-
- } finally {
- deleteDirTree(outputDirectory);
- }
- }
-
- private void getContentPackagesFromFeatureModel(File outputDirectory, Feature feature, List<PackageId> packageIds) throws IOException {
-
- Artifacts artifacts = feature.getExtensions().getByName("content-packages").getArtifacts();
- for(Artifact artifact : artifacts){
- ArtifactId id = artifact.getId();
-
- String target = getPathTarget(id);
- File file = new File(outputDirectory, target);
- ZipVaultPackage vaultPackage = new ZipVaultPackage(file, true);
- //read the artifact from disk and compute the artifactId from here
- packageIds.add( vaultPackage.getProperties().getId() );
- };
-
- }
-
- private void getContentPackagesFromRefs(File outputDirectory, List<PackageId> packageIds, List<Dependency> dependencies) throws IOException {
- File contentPackagesCSV = new File(outputDirectory, "content-packages.csv");
-
- List<String> contentPackages = IOUtils.readLines(new FileInputStream(contentPackagesCSV), StandardCharsets.UTF_8);
-
- for(String contentPackageLine: contentPackages) {
-
- if (contentPackageLine.startsWith("#")) {
- continue;
- }
-
- String[] contentPackageLineSplit = contentPackageLine.split(",");
-
- String artifactIdUnparsed = contentPackageLineSplit[1];
- ArtifactId artifact = ArtifactId.fromMvnId(artifactIdUnparsed);
-
- String target = "mutable-content-packages/" + getPathTarget(artifact);
-
- File contentPackageFile = new File(outputDirectory, target);
- ZipVaultPackage vaultPackage = new ZipVaultPackage(contentPackageFile, true);
- packageIds.add(vaultPackage.getProperties().getId());
-
-
- dependencies.addAll(Arrays.asList(vaultPackage.getProperties().getDependencies()));
-
- }
- }
-
- private String getPathTarget(ArtifactId artifact) {
- String groupIdPath = StringUtils.replace(artifact.getGroupId(), ".", "/");
-
- return String.format(FORMAT, groupIdPath, artifact.getArtifactId(), artifact.getVersion());
- }
-
-
- @Test
public void convertContentPackage() throws Exception {
URL packageUrl = getClass().getResource("test-content-package.zip");
File packageFile = FileUtils.toFile(packageUrl);
@@ -382,7 +288,7 @@ public class ContentPackage2FeatureModelConverterTest extends AbstractConverterT
deleteDirTree(outputDirectory);
}
}
-
+
@Test
public void convertContentPackagePutInDedicatedFolderContentTypePackagePolicy() throws Exception {
URL packageUrl = getClass().getResource("test-content-package.zip");
@@ -1007,6 +913,4 @@ public class ContentPackage2FeatureModelConverterTest extends AbstractConverterT
return loadedResources;
}
-
-
}
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/ConverterUserAndPermissionTest.java b/src/test/java/org/apache/sling/feature/cpconverter/ConverterUserAndPermissionTest.java
index fc272e0..6ff8aea 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/ConverterUserAndPermissionTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/ConverterUserAndPermissionTest.java
@@ -34,6 +34,7 @@ import org.apache.sling.feature.cpconverter.features.DefaultFeaturesManager;
import org.apache.sling.feature.cpconverter.features.FeaturesManager;
import org.apache.sling.feature.cpconverter.handlers.DefaultEntryHandlersManager;
import org.apache.sling.feature.cpconverter.handlers.EntryHandlersManager;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.BundleSlingInitialContentExtractor;
import org.apache.sling.feature.cpconverter.shared.ConverterConstants;
import org.apache.sling.feature.cpconverter.vltpkg.DefaultPackagesEventsEmitter;
import org.apache.sling.feature.io.json.FeatureJSONReader;
@@ -135,7 +136,7 @@ public class ConverterUserAndPermissionTest extends AbstractConverterTest {
public ConverterUserAndPermissionTest(@NotNull String systemUserRelPath, @Nullable String enforcePrincipalBasedSupportedPath, @NotNull String name) throws Exception {
this.aclManager = new DefaultAclManager(enforcePrincipalBasedSupportedPath, systemUserRelPath);
this.handlersManager = new DefaultEntryHandlersManager(Collections.emptyMap(), false,
- ContentPackage2FeatureModelConverter.SlingInitialContentPolicy.KEEP, systemUserRelPath);
+ ContentPackage2FeatureModelConverter.SlingInitialContentPolicy.KEEP, new BundleSlingInitialContentExtractor(), systemUserRelPath);
this.enforcePrincipalBased = (enforcePrincipalBasedSupportedPath != null);
this.withRelPath = (enforcePrincipalBased) ? enforcePrincipalBasedSupportedPath.substring(enforcePrincipalBasedSupportedPath.indexOf(systemUserRelPath)) : systemUserRelPath;
}
@@ -250,7 +251,7 @@ public class ConverterUserAndPermissionTest extends AbstractConverterTest {
assertEquals(normalize(
"create service user eventproxy-service with path system/eventproxy\n" +
" create path /content\n" +
- " create path /var/eventproxy\n" +
+ " create path /var/eventproxy(nt:unstructured mixin rep:AccessControllable)\n" +
" set ACL for eventproxy-service\n" +
" allow jcr:read on /content\n" +
" allow jcr:read,rep:write on /var/eventproxy\n" +
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandleSlingInitialContentTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandleSlingInitialContentTest.java
index 1c72c82..6fa5143 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandleSlingInitialContentTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandleSlingInitialContentTest.java
@@ -16,19 +16,39 @@
*/
package org.apache.sling.feature.cpconverter.handlers;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.xmlunit.assertj.XmlAssert.assertThat;
import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
+import java.util.Set;
import java.util.jar.JarFile;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.vault.fs.io.Archive;
import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
import org.apache.jackrabbit.vault.packaging.PackageId;
@@ -39,8 +59,12 @@ import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Configuration;
import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter.SlingInitialContentPolicy;
+import org.apache.sling.feature.cpconverter.accesscontrol.DefaultAclManager;
import org.apache.sling.feature.cpconverter.artifacts.SimpleFolderArtifactsDeployer;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.BundleSlingInitialContentExtractor;
+import org.apache.sling.feature.cpconverter.shared.ConverterConstants;
import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -50,6 +74,14 @@ import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xmlunit.builder.Input;
+import org.xmlunit.diff.ComparisonType;
+import org.xmlunit.diff.DOMDifferenceEngine;
+import org.xmlunit.diff.DifferenceEngine;
+
+import javax.xml.transform.Source;
@RunWith(MockitoJUnitRunner.class)
public class BundleEntryHandleSlingInitialContentTest extends AbstractBundleEntryHandlerTest {
@@ -60,6 +92,9 @@ public class BundleEntryHandleSlingInitialContentTest extends AbstractBundleEntr
@Captor
ArgumentCaptor<Configuration> cfgCaptor;
+ @Captor
+ ArgumentCaptor<String> repoinitTextCaptor;
+
@Test
public void testSlingInitialContent() throws Exception {
setUpArchive("/jcr_root/apps/gav/install/io.wcm.handler.media-1.11.6.jar", "io.wcm.handler.media-1.11.6.jar");
@@ -79,10 +114,16 @@ public class BundleEntryHandleSlingInitialContentTest extends AbstractBundleEntr
props.setProperty(PackageProperties.NAME_VERSION, "1.11.6");
when(assembler.getPackageProperties()).thenReturn(props);
converter.setMainPackageAssembler(assembler);
-
+ converter.setAclManager(new DefaultAclManager());
+
+ BundleSlingInitialContentExtractor extractor = new BundleSlingInitialContentExtractor();
+
+ handler.setBundleSlingInitialContentExtractor(extractor);
handler.setSlingInitialContentPolicy(SlingInitialContentPolicy.EXTRACT_AND_REMOVE);
handler.handle("/jcr_root/apps/gav/install/io.wcm.handler.media-1.11.6.jar", archive, entry, converter);
-
+
+ extractor.addRepoInitExtension(converter.getAssemblers(), featuresManager);
+
converter.deployPackages();
// verify generated bundle
try (JarFile jarFile = new JarFile(new File(targetFolder, "io.wcm.handler.media-1.11.6-cp2fm-converted.jar"))) {
@@ -99,14 +140,38 @@ public class BundleEntryHandleSlingInitialContentTest extends AbstractBundleEntr
archive.open(true);
PackageId targetId = PackageId.fromString("io.wcm:io.wcm.handler.media-apps:1.11.6-cp2fm-converted");
assertEquals(targetId, vaultPackage.getId());
- Entry entry = archive.getEntry("jcr_root/apps/wcm-io/handler/media/components/global/include/responsiveImageSettings.xml");
+ Entry entry = archive.getEntry("jcr_root/apps/wcm-io/handler/media/components/global/include/responsiveImageSettings/.content.xml");
assertNotNull("Archive does not contain expected item", entry);
+
+
+ Set<String> expectedCreatePathStatements = new HashSet<>();
+ expectedCreatePathStatements.add("create path (sling:Folder) /apps/wcm-io/handler/media/content");
+ expectedCreatePathStatements.add("create path (sling:Folder) /apps/wcm-io/handler/media/components/granite/form/mediaformatselect(cq:Component)");
+ expectedCreatePathStatements.add("create path (sling:Folder) /apps/wcm-io/handler/media/i18n");
+ expectedCreatePathStatements.add("create path (sling:Folder) /apps/wcm-io/handler/media/components/global/include");
+ expectedCreatePathStatements.add("create path (sling:Folder) /apps/wcm-io/handler/media/components/placeholder");
+ expectedCreatePathStatements.add("create path (sling:Folder) /apps/wcm-io/handler/media/clientlibs/authoring/dialog(cq:ClientLibraryFolder)/css");
+ expectedCreatePathStatements.add("create path (sling:Folder) /apps/wcm-io/handler/media/components/granite/form/fileupload(cq:Component)");
+ expectedCreatePathStatements.add("create path (sling:Folder) /apps/wcm-io/handler/media/clientlibs/authoring/dialog(cq:ClientLibraryFolder)/js");
+ expectedCreatePathStatements.add("create path (sling:Folder) /apps/wcm-io/handler/media/components/granite/datasources/mediaformats");
+ expectedCreatePathStatements.add("create path (sling:Folder) /apps/wcm-io/handler/media/docroot/resources/img");
+ expectedCreatePathStatements.add("create path (sling:Folder) /apps/wcm-io/handler/media/components/granite/global");
+ expectedCreatePathStatements.add("create path (sling:Folder) /apps/wcm-io/handler/media/components/granite/form/pathfield(cq:Component)");
+
+
+
+ verify(featuresManager, times(1)).addOrAppendRepoInitExtension(eq("content-package"), repoinitTextCaptor.capture(), Mockito.isNull());
+
+ for(String expectedCreatePathStatement: expectedCreatePathStatements){
+ assertTrue("Repoinit text does not contain desired create path statement!",StringUtils.contains(repoinitTextCaptor.getValue(), expectedCreatePathStatement));
+ }
+
}
// verify nothing else has been deployed
assertEquals(2, targetFolder.list().length);
// verify changed id
ArgumentCaptor<Artifact> captor = ArgumentCaptor.forClass(Artifact.class);
- Mockito.verify(featuresManager).addArtifact(Mockito.isNull(), captor.capture(), Mockito.isNull());
+ verify(featuresManager).addArtifact(Mockito.isNull(), captor.capture(), Mockito.isNull());
final Artifact result = captor.getValue();
assertNotNull(result);
assertEquals(ArtifactId.fromMvnId("io.wcm:io.wcm.handler.media:1.11.6-cp2fm-converted"), result.getId());
@@ -116,10 +181,310 @@ public class BundleEntryHandleSlingInitialContentTest extends AbstractBundleEntr
@Test
+ public void testJsonI18nWithXMLFolderDescriptors() throws Exception {
+ setUpArchive("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar", "mysite.core-1.0.0-SNAPSHOT-i18n-xml-folderdescriptor.jar");
+ DefaultEntryHandlersManager handlersManager = new DefaultEntryHandlersManager(Collections.emptyMap(), false, SlingInitialContentPolicy.KEEP, new BundleSlingInitialContentExtractor(), ConverterConstants.SYSTEM_USER_REL_PATH_DEFAULT);
+ converter.setEntryHandlersManager(handlersManager);
+ Map<String, String> namespaceRegistry = new HashMap<>();
+
+ namespaceRegistry.put("cq","http://www.day.com/jcr/cq/1.0");
+ namespaceRegistry.put("granite", "http://www.adobe.com/jcr/granite/1.0");
+
+
+ when(featuresManager.getNamespaceUriByPrefix()).thenReturn(namespaceRegistry);
+
+ File targetFolder = tmpFolder.newFolder();
+ when(converter.getArtifactsDeployer()).thenReturn(new SimpleFolderArtifactsDeployer(targetFolder));
+ when(converter.isSubContentPackageIncluded("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar-APPLICATION")).thenReturn(true);
+
+ VaultPackageAssembler assembler = Mockito.mock(VaultPackageAssembler.class);
+ Properties props = new Properties();
+ props.setProperty(PackageProperties.NAME_GROUP, "com.mysite");
+ props.setProperty(PackageProperties.NAME_NAME, "mysite.core");
+ props.setProperty(PackageProperties.NAME_VERSION, "1.0.0-SNAPSHOT");
+ when(assembler.getPackageProperties()).thenReturn(props);
+ converter.setMainPackageAssembler(assembler);
+ converter.setAclManager(new DefaultAclManager());
+ BundleSlingInitialContentExtractor extractor = new BundleSlingInitialContentExtractor();
+
+ handler.setBundleSlingInitialContentExtractor(extractor);
+ handler.setSlingInitialContentPolicy(SlingInitialContentPolicy.EXTRACT_AND_REMOVE);
+ handler.handle("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar", archive, entry, converter);
+
+ converter.deployPackages();
+
+ Type typeOfHashMap = new TypeToken<Map<String, String>>() { }.getType();
+ // verify generated package
+ try (VaultPackage vaultPackage = new PackageManagerImpl().open(new File(targetFolder, "mysite.core-apps-1.0.0-SNAPSHOT-cp2fm-converted.zip"));
+ Archive archive = vaultPackage.getArchive()) {
+ archive.open(true);
+ PackageId targetId = PackageId.fromString("com.mysite:mysite.core-apps:1.0.0-SNAPSHOT-cp2fm-converted");
+ assertEquals(targetId, vaultPackage.getId());
+
+ Entry jsonFileEntry = archive.getEntry("/jcr_root/apps/myinitialcontentest/test/i18n/en.json");
+ assertNotNull(jsonFileEntry);
+
+ //compare JSON
+ Reader actualJsonFileContents = new InputStreamReader(archive.getInputSource(jsonFileEntry).getByteStream(), UTF_8);
+ Reader expectedJsonFileContents = new InputStreamReader(getClass().getResourceAsStream("i18n-jsonfile-xml-descriptor-test/en.json"), UTF_8);
+
+ Gson GSON = new GsonBuilder().create();
+
+ Map<String,String> actualJson = GSON.fromJson(actualJsonFileContents, typeOfHashMap);
+ Map<String,String> expectedJson = GSON.fromJson(expectedJsonFileContents, typeOfHashMap);
+
+ assertEquals(expectedJson, actualJson);
+ //compare XML
+
+ Entry jsonFileDescriptorEntry = archive.getEntry("/jcr_root/apps/myinitialcontentest/test/i18n/en.json.dir/.content.xml");
+ assertNotNull(jsonFileDescriptorEntry);
+
+ String expectedXML = IOUtils.toString(getClass().getResourceAsStream("i18n-jsonfile-xml-descriptor-test/en.json.dir/.content.xml"), UTF_8);
+ String actualXML = IOUtils.toString(archive.getInputSource(jsonFileDescriptorEntry).getByteStream(), UTF_8);
+
+ assertThat(actualXML).and(expectedXML).areSimilar();
+
+ }
+ }
+
+
+ @Test
+ public void testSlingInitialContentWithNodeTypeAndNoDefinedParent() throws Exception {
+ setUpArchive("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar", "mysite.core-1.0.0-SNAPSHOT-slinginitialcontent-test.jar");
+
+ DefaultEntryHandlersManager handlersManager = new DefaultEntryHandlersManager(Collections.emptyMap(), false, SlingInitialContentPolicy.KEEP, new BundleSlingInitialContentExtractor(), ConverterConstants.SYSTEM_USER_REL_PATH_DEFAULT);
+ converter.setEntryHandlersManager(handlersManager);
+ Map<String, String> namespaceRegistry = new HashMap<>();
+
+ namespaceRegistry.put("cq","http://www.day.com/jcr/cq/1.0");
+ namespaceRegistry.put("granite", "http://www.adobe.com/jcr/granite/1.0");
+
+
+ when(featuresManager.getNamespaceUriByPrefix()).thenReturn(namespaceRegistry);
+
+ File targetFolder = tmpFolder.newFolder();
+ when(converter.getArtifactsDeployer()).thenReturn(new SimpleFolderArtifactsDeployer(targetFolder));
+ when(converter.isSubContentPackageIncluded("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar-APPLICATION")).thenReturn(true);
+
+ VaultPackageAssembler assembler = Mockito.mock(VaultPackageAssembler.class);
+ Properties props = new Properties();
+ props.setProperty(PackageProperties.NAME_GROUP, "com.mysite");
+ props.setProperty(PackageProperties.NAME_NAME, "mysite.core");
+ props.setProperty(PackageProperties.NAME_VERSION, "1.0.0-SNAPSHOT");
+ when(assembler.getPackageProperties()).thenReturn(props);
+ converter.setMainPackageAssembler(assembler);
+
+ DefaultAclManager aclManager = new DefaultAclManager();
+ converter.setAclManager(aclManager);
+ BundleSlingInitialContentExtractor extractor = new BundleSlingInitialContentExtractor();
+
+ handler.setBundleSlingInitialContentExtractor(extractor);
+ handler.setSlingInitialContentPolicy(SlingInitialContentPolicy.EXTRACT_AND_REMOVE);
+ handler.handle("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar", archive, entry, converter);
+
+ converter.deployPackages();
+ extractor.addRepoInitExtension(converter.getAssemblers(), featuresManager);
+
+ try (VaultPackage vaultPackage = new PackageManagerImpl().open(new File(targetFolder, "mysite.core-apps-1.0.0-SNAPSHOT-cp2fm-converted.zip"));
+ Archive archive = vaultPackage.getArchive()) {
+ archive.open(true);
+ PackageId targetId = PackageId.fromString("com.mysite:mysite.core-apps:1.0.0-SNAPSHOT-cp2fm-converted");
+ assertEquals(targetId, vaultPackage.getId());
+
+ InputStream inputStream = archive.getInputSource(archive.getEntry("jcr_root/apps/myinitialcontentest/test/parent-with-definition/.content.xml")).getByteStream();
+ assertNotNull(inputStream);
+
+ //this needs to be defined by repoinit, not the package itself
+ Archive.Entry parentWithoutDefinitionEntry = archive.getEntry("jcr_root/apps/myinitialcontentest/test/parent-with-definition/parent-without-definition/.content.xml");
+ assertNull(parentWithoutDefinitionEntry);
+
+ InputStream someUnstructuredNode = archive.getInputSource(archive.getEntry("jcr_root/apps/myinitialcontentest/test/parent-with-definition/parent-without-definition/someUnstructuredNode/.content.xml")).getByteStream();
+ assertNotNull(someUnstructuredNode);
+
+ String repoinitText =
+ "create path (sling:Folder) /content/test/myinitialcontentest2\n" +
+ "create path (sling:Folder) /apps/myinitialcontentest/test/parent-with-definition(my:parent)/parent-without-definition\n";
+
+ verify(featuresManager, times(1)).addOrAppendRepoInitExtension(eq("content-package"), eq(repoinitText), Mockito.isNull());
+
+ }
+
+ }
+
+
+ @Test
+ public void testSlingInitialContentWithNodeTypeAndPageJson() throws Exception {
+ setUpArchive("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar", "mysite.core-1.0.0-SNAPSHOT-pagejson.jar");
+ DefaultEntryHandlersManager handlersManager = new DefaultEntryHandlersManager(Collections.emptyMap(), false, SlingInitialContentPolicy.KEEP, new BundleSlingInitialContentExtractor(), ConverterConstants.SYSTEM_USER_REL_PATH_DEFAULT);
+ converter.setEntryHandlersManager(handlersManager);
+ Map<String, String> namespaceRegistry = new HashMap<>();
+
+ namespaceRegistry.put("cq","http://www.day.com/jcr/cq/1.0");
+ namespaceRegistry.put("granite", "http://www.adobe.com/jcr/granite/1.0");
+
+
+ when(featuresManager.getNamespaceUriByPrefix()).thenReturn(namespaceRegistry);
+
+ File targetFolder = tmpFolder.newFolder();
+ when(converter.getArtifactsDeployer()).thenReturn(new SimpleFolderArtifactsDeployer(targetFolder));
+ when(converter.isSubContentPackageIncluded("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar-APPLICATION")).thenReturn(true);
+
+ VaultPackageAssembler assembler = Mockito.mock(VaultPackageAssembler.class);
+ Properties props = new Properties();
+ props.setProperty(PackageProperties.NAME_GROUP, "com.mysite");
+ props.setProperty(PackageProperties.NAME_NAME, "mysite.core");
+ props.setProperty(PackageProperties.NAME_VERSION, "1.0.0-SNAPSHOT");
+ when(assembler.getPackageProperties()).thenReturn(props);
+ converter.setMainPackageAssembler(assembler);
+ converter.setAclManager(new DefaultAclManager());
+ BundleSlingInitialContentExtractor extractor = new BundleSlingInitialContentExtractor();
+
+ handler.setBundleSlingInitialContentExtractor(extractor);
+ handler.setSlingInitialContentPolicy(SlingInitialContentPolicy.EXTRACT_AND_REMOVE);
+ handler.handle("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar", archive, entry, converter);
+
+ converter.deployPackages();
+
+ // verify generated package
+ try (VaultPackage vaultPackage = new PackageManagerImpl().open(new File(targetFolder, "mysite.core-apps-1.0.0-SNAPSHOT-cp2fm-converted.zip"));
+ Archive archive = vaultPackage.getArchive()) {
+ archive.open(true);
+ PackageId targetId = PackageId.fromString("com.mysite:mysite.core-apps:1.0.0-SNAPSHOT-cp2fm-converted");
+ assertEquals(targetId, vaultPackage.getId());
+
+ assertPageStructureFromEntry(archive, "jcr_root/apps/mysite/components/global", "homepage");
+ assertPageStructureFromEntry(archive, "jcr_root/apps/mysite/components/global", "page", "body.html");
+ assertPageStructureFromEntry(archive, "jcr_root/apps/mysite/components/global", "xfpage","body.html");
+
+ InputStream inputStream = archive.getInputSource(archive.getEntry("jcr_root/apps/mysite/components/global/homepage/.content.xml")).getByteStream();
+
+ InputSource expectedXML = new InputSource(getClass().getResource("mysite-nodetype-and-page-json-xml-result.xml").openStream());
+ InputSource actualXML = new InputSource(inputStream);
+
+
+ assertThat(expectedXML).and(actualXML).areSimilar();
+
+ }
+
+ }
+
+ @Test
+ public void testSlingInitialContentWithSpecialCharacters() throws Exception {
+ setUpArchive("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar", "mysite.core-1.0.0-SNAPSHOT-specialchars-json-inputstream.jar");
+ DefaultEntryHandlersManager handlersManager = new DefaultEntryHandlersManager(Collections.emptyMap(), false, SlingInitialContentPolicy.KEEP, new BundleSlingInitialContentExtractor(), ConverterConstants.SYSTEM_USER_REL_PATH_DEFAULT);
+ converter.setEntryHandlersManager(handlersManager);
+ Map<String, String> namespaceRegistry = new HashMap<>();
+
+ namespaceRegistry.put("cq","http://www.day.com/jcr/cq/1.0");
+ namespaceRegistry.put("granite", "http://www.adobe.com/jcr/granite/1.0");
+
+
+ when(featuresManager.getNamespaceUriByPrefix()).thenReturn(namespaceRegistry);
+
+ File targetFolder = tmpFolder.newFolder();
+ when(converter.getArtifactsDeployer()).thenReturn(new SimpleFolderArtifactsDeployer(targetFolder));
+ when(converter.isSubContentPackageIncluded("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar-APPLICATION")).thenReturn(true);
+
+ VaultPackageAssembler assembler = Mockito.mock(VaultPackageAssembler.class);
+ Properties props = new Properties();
+ props.setProperty(PackageProperties.NAME_GROUP, "com.mysite");
+ props.setProperty(PackageProperties.NAME_NAME, "mysite.core");
+ props.setProperty(PackageProperties.NAME_VERSION, "1.0.0-SNAPSHOT");
+ when(assembler.getPackageProperties()).thenReturn(props);
+ converter.setMainPackageAssembler(assembler);
+ converter.setAclManager(new DefaultAclManager());
+ BundleSlingInitialContentExtractor extractor = new BundleSlingInitialContentExtractor();
+
+ handler.setBundleSlingInitialContentExtractor(extractor);
+ handler.setSlingInitialContentPolicy(SlingInitialContentPolicy.EXTRACT_AND_REMOVE);
+ handler.handle("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar", archive, entry, converter);
+
+ converter.deployPackages();
+
+
+
+
+ // verify generated package
+ try (VaultPackage vaultPackage = new PackageManagerImpl().open(new File(targetFolder, "mysite.core-apps-1.0.0-SNAPSHOT-cp2fm-converted.zip"));
+ Archive archive = vaultPackage.getArchive()) {
+ archive.open(true);
+ PackageId targetId = PackageId.fromString("com.mysite:mysite.core-apps:1.0.0-SNAPSHOT-cp2fm-converted");
+ assertEquals(targetId, vaultPackage.getId());
+
+ //json containing a property: "fancyCharacters": "<&\"'>"
+ assertPageStructureFromEntry(archive,"jcr_root/apps/mysite/components/global", "homepage" );
+ assertResultingEntry(archive, "homepage");
+
+ //xml with custom name element: nodeName and containing a textfile
+ assertPageStructureFromEntry(archive,"jcr_root/apps/mysite/components/global", "nodeName", "testfile.txt" );
+ assertResultingEntry(archive, "nodeName");
+
+ //xml with custom "name" element "xyz"
+ assertPageStructureFromEntry(archive, "jcr_root/apps/mysite/components/global", "xyz");
+ assertResultingEntry(archive, "xyz");
+
+ //xml with custom name element: 11""><mumbojumbo
+ assertResultingEntry(archive, "11mumbojumbo");
+ assertPageStructureFromEntry(archive,"jcr_root/apps/mysite/components/global", "11mumbojumbo" );
+ }
+
+ }
+
+
+
+ @Test
+ public void testSlingInitialContentWithNumberedEntries() throws Exception {
+ setUpArchive("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar", "io.wcm.handler.link-1.7.02.jar");
+ DefaultEntryHandlersManager handlersManager = new DefaultEntryHandlersManager(Collections.emptyMap(), false, SlingInitialContentPolicy.KEEP, new BundleSlingInitialContentExtractor(), ConverterConstants.SYSTEM_USER_REL_PATH_DEFAULT);
+ converter.setEntryHandlersManager(handlersManager);
+ Map<String, String> namespaceRegistry = new HashMap<>();
+
+ namespaceRegistry.put("cq","http://www.day.com/jcr/cq/1.0");
+ namespaceRegistry.put("granite", "http://www.adobe.com/jcr/granite/1.0");
+
+
+ when(featuresManager.getNamespaceUriByPrefix()).thenReturn(namespaceRegistry);
+
+ File targetFolder = tmpFolder.newFolder();
+ when(converter.getArtifactsDeployer()).thenReturn(new SimpleFolderArtifactsDeployer(targetFolder));
+ when(converter.isSubContentPackageIncluded("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar-APPLICATION")).thenReturn(true);
+
+ VaultPackageAssembler assembler = Mockito.mock(VaultPackageAssembler.class);
+ Properties props = new Properties();
+ props.setProperty(PackageProperties.NAME_GROUP, "com.mysite");
+ props.setProperty(PackageProperties.NAME_NAME, "mysite.core");
+ props.setProperty(PackageProperties.NAME_VERSION, "1.0.0-SNAPSHOT");
+ when(assembler.getPackageProperties()).thenReturn(props);
+ converter.setMainPackageAssembler(assembler);
+ converter.setAclManager(new DefaultAclManager());
+ BundleSlingInitialContentExtractor extractor = new BundleSlingInitialContentExtractor();
+
+ handler.setBundleSlingInitialContentExtractor(extractor);
+ handler.setSlingInitialContentPolicy(SlingInitialContentPolicy.EXTRACT_AND_REMOVE);
+ handler.handle("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar", archive, entry, converter);
+
+ converter.deployPackages();
+
+ // verify generated package
+ try (VaultPackage vaultPackage = new PackageManagerImpl().open(new File(targetFolder, "io.wcm.handler.link-apps-1.7.0-cp2fm-converted.zip"));
+ Archive archive = vaultPackage.getArchive()) {
+ archive.open(true);
+
+ InputStream xmlFile = archive.getInputSource(archive.getEntry("jcr_root/apps/wcm-io/handler/link/components/global/include/redirectStatus/.content.xml")).getByteStream();
+ String expectedXML = IOUtils.toString(getClass().getResourceAsStream("bundle-entry-xmls/include-redirectStatus.xml"), UTF_8);
+ String actualXML = IOUtils.toString(xmlFile, UTF_8);
+
+ assertThat(expectedXML).and(actualXML).areSimilar();
+ }
+
+ }
+
+ @Test
public void testSlingInitialContentWithNodeType() throws Exception {
setUpArchive("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar", "mysite-slinginitialcontent-nodetype-def.jar");
DefaultEntryHandlersManager handlersManager = new DefaultEntryHandlersManager();
converter.setEntryHandlersManager(handlersManager);
+ converter.setAclManager(new DefaultAclManager());
Map<String, String> namespaceRegistry = Collections.singletonMap("granite", "http://www.adobe.com/jcr/granite/1.0");
when(featuresManager.getNamespaceUriByPrefix()).thenReturn(namespaceRegistry);
@@ -134,7 +499,9 @@ public class BundleEntryHandleSlingInitialContentTest extends AbstractBundleEntr
props.setProperty(PackageProperties.NAME_VERSION, "1.0.0-SNAPSHOT");
when(assembler.getPackageProperties()).thenReturn(props);
converter.setMainPackageAssembler(assembler);
+ BundleSlingInitialContentExtractor extractor = new BundleSlingInitialContentExtractor();
+ handler.setBundleSlingInitialContentExtractor(extractor);
handler.setSlingInitialContentPolicy(SlingInitialContentPolicy.EXTRACT_AND_REMOVE);
handler.handle("/jcr_root/apps/mysite/install/mysite-slinginitialcontent-nodetype-def.jar", archive, entry, converter);
@@ -154,14 +521,14 @@ public class BundleEntryHandleSlingInitialContentTest extends AbstractBundleEntr
archive.open(true);
PackageId targetId = PackageId.fromString("com.mysite:mysite.core-apps:1.0.0-SNAPSHOT-cp2fm-converted");
assertEquals(targetId, vaultPackage.getId());
- Entry entry = archive.getEntry("jcr_root/apps/myinitialcontentest/my-first-node.xml");
+ Entry entry = archive.getEntry("jcr_root/apps/myinitialcontentest/my-first-node/.content.xml");
assertNotNull("Archive does not contain expected item", entry);
}
// verify nothing else has been deployed
assertEquals(2, targetFolder.list().length);
// verify changed id
ArgumentCaptor<Artifact> captor = ArgumentCaptor.forClass(Artifact.class);
- Mockito.verify(featuresManager).addArtifact(Mockito.isNull(), captor.capture(), Mockito.isNull());
+ verify(featuresManager).addArtifact(Mockito.isNull(), captor.capture(), Mockito.isNull());
final Artifact result = captor.getValue();
assertNotNull(result);
assertEquals(ArtifactId.fromMvnId("com.mysite:mysite.core:1.0.0-SNAPSHOT-cp2fm-converted"), result.getId());
@@ -182,11 +549,15 @@ public class BundleEntryHandleSlingInitialContentTest extends AbstractBundleEntr
props.setProperty(PackageProperties.NAME_VERSION, "1.11.6");
when(assembler.getPackageProperties()).thenReturn(props);
converter.setMainPackageAssembler(assembler);
+ converter.setAclManager(new DefaultAclManager());
+ BundleSlingInitialContentExtractor extractor = new BundleSlingInitialContentExtractor();
+
+ handler.setBundleSlingInitialContentExtractor(extractor);
handler.setSlingInitialContentPolicy(SlingInitialContentPolicy.EXTRACT_AND_REMOVE);
handler.handle("/jcr_root/apps/gav/install/composum-nodes-config-2.5.3.jar", archive, entry, converter);
// modified bundle
ArgumentCaptor<Artifact> captor = ArgumentCaptor.forClass(Artifact.class);
- Mockito.verify(featuresManager).addArtifact(Mockito.isNull(), captor.capture(), Mockito.isNull());
+ verify(featuresManager).addArtifact(Mockito.isNull(), captor.capture(), Mockito.isNull());
final Artifact result = captor.getValue();
assertNotNull(result);
assertEquals(ArtifactId.fromMvnId("com.composum.nodes:composum-nodes-config:2.5.3-cp2fm-converted"), result.getId());
@@ -194,7 +565,7 @@ public class BundleEntryHandleSlingInitialContentTest extends AbstractBundleEntr
assertEquals("2.5.3", result.getMetadata().get(Constants.BUNDLE_VERSION));
// need to use ArgumentCaptur to properly compare string arrays
- Mockito.verify(featuresManager).addConfiguration(ArgumentMatchers.isNull(), cfgCaptor.capture(), ArgumentMatchers.eq("/jcr_root/libs/composum/nodes/install/org.apache.sling.jcr.base.internal.LoginAdminWhitelist.fragment-composum_core_v2.config"), dictionaryCaptor.capture());
+ verify(featuresManager).addConfiguration(ArgumentMatchers.isNull(), cfgCaptor.capture(), eq("/jcr_root/libs/composum/nodes/install/org.apache.sling.jcr.base.internal.LoginAdminWhitelist.fragment-composum_core_v2.config"), dictionaryCaptor.capture());
assertEquals("composum_core", dictionaryCaptor.getValue().get("whitelist.name"));
assertEquals("org.apache.sling.jcr.base.internal.LoginAdminWhitelist.fragment~composum_core_v2", cfgCaptor.getValue().getPid());
assertArrayEquals(new String[] {
@@ -216,20 +587,24 @@ public class BundleEntryHandleSlingInitialContentTest extends AbstractBundleEntr
props.setProperty(PackageProperties.NAME_VERSION, "1.11.6");
when(assembler.getPackageProperties()).thenReturn(props);
converter.setMainPackageAssembler(assembler);
+ converter.setAclManager(new DefaultAclManager());
+ BundleSlingInitialContentExtractor extractor = new BundleSlingInitialContentExtractor();
+
+ handler.setBundleSlingInitialContentExtractor(extractor);
handler.setSlingInitialContentPolicy(SlingInitialContentPolicy.EXTRACT_AND_KEEP);
handler.handle("/jcr_root/apps/gav/install/composum-nodes-config-2.5.3.jar", archive, entry, converter);
// original bundle
ArgumentCaptor<Artifact> captor = ArgumentCaptor.forClass(Artifact.class);
- Mockito.verify(featuresManager).addArtifact(Mockito.isNull(), captor.capture(), Mockito.isNull());
+ verify(featuresManager).addArtifact(Mockito.isNull(), captor.capture(), Mockito.isNull());
final Artifact result = captor.getValue();
assertNotNull(result);
assertEquals(ArtifactId.fromMvnId("com.composum.nodes:composum-nodes-config:2.5.3"), result.getId());
assertEquals("com.composum.nodes.config", result.getMetadata().get(Constants.BUNDLE_SYMBOLICNAME));
assertEquals("2.5.3", result.getMetadata().get(Constants.BUNDLE_VERSION));
// need to use ArgumentCaptur to properly compare string arrays
- Mockito.verify(featuresManager).addConfiguration(ArgumentMatchers.isNull(),
- cfgCaptor.capture(),
- ArgumentMatchers.eq("/jcr_root/libs/composum/nodes/install/org.apache.sling.jcr.base.internal.LoginAdminWhitelist.fragment-composum_core_v2.config"), dictionaryCaptor.capture());
+ verify(featuresManager).addConfiguration(ArgumentMatchers.isNull(),
+ cfgCaptor.capture(),
+ eq("/jcr_root/libs/composum/nodes/install/org.apache.sling.jcr.base.internal.LoginAdminWhitelist.fragment-composum_core_v2.config"), dictionaryCaptor.capture());
assertEquals("composum_core", dictionaryCaptor.getValue().get("whitelist.name"));
assertEquals("org.apache.sling.jcr.base.internal.LoginAdminWhitelist.fragment~composum_core_v2", cfgCaptor.getValue().getPid());
assertArrayEquals(new String[] {
@@ -237,4 +612,44 @@ public class BundleEntryHandleSlingInitialContentTest extends AbstractBundleEntr
"com.composum.nodes.pckgmgr",
"com.composum.nodes.pckginstall" }, (String[])dictionaryCaptor.getValue().get("whitelist.bundles"));
}
+
+ private void assertResultingEntry(Archive archive, String entryKey) throws IOException, SAXException {
+ InputStream xmlFile = archive.getInputSource(archive.getEntry("jcr_root/apps/mysite/components/global/" + entryKey +"/.content.xml")).getByteStream();
+ InputStream expectedXmlFileStream = getClass().getResourceAsStream("bundle-entry-xmls/" + entryKey + ".xml");
+ String expectedXML = IOUtils.toString(expectedXmlFileStream, UTF_8);
+ String actualXML = IOUtils.toString(xmlFile, UTF_8);
+
+
+ Source control = Input.fromString(expectedXML).build();
+ Source test = Input.fromString(actualXML).build();
+
+ DifferenceEngine diff = new DOMDifferenceEngine();
+ diff.addDifferenceListener((comparison, outcome) -> {
+
+ if(comparison.getType() == ComparisonType.CHILD_NODELIST_LENGTH){
+ //this comparison is buggy so we can't use it.
+ return;
+ }
+
+ String actualString = comparison.getTestDetails().getValue().toString();
+ String expectedString = comparison.getControlDetails().getValue().toString();
+ if(!actualString.trim().equals(expectedString.trim())){
+ Assert.fail("difference found in XML: " + actualString + " vs " + expectedString);
+ }
+ });
+ diff.compare(control, test);
+ }
+
+ private void assertPageStructureFromEntry(Archive archive, String basePath, String pageName, String... files) throws IOException {
+ Entry contentXml = archive.getEntry( basePath + "/" + pageName + "/.content.xml");
+ assertNotNull(contentXml);
+ Entry pageXml = archive.getEntry( basePath + "/" + pageName + ".xml");
+ assertNull(pageXml);
+
+ for(String file: files){
+ Entry expectedEntry = archive.getEntry( basePath + "/" + pageName + "/" + file);
+ assertNotNull(expectedEntry);
+ }
+ }
+
}
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
index 6c254de..fbfc6b5 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
@@ -43,6 +43,7 @@ import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter
import org.apache.sling.feature.cpconverter.artifacts.LocalMavenRepositoryArtifactsDeployer;
import org.apache.sling.feature.cpconverter.features.DefaultFeaturesManager;
import org.apache.sling.feature.cpconverter.features.FeaturesManager;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.BundleSlingInitialContentExtractor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/GavDeclarationsInBundleTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/GavDeclarationsInBundleTest.java
index ebd9afc..1ad5975 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/handlers/GavDeclarationsInBundleTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/GavDeclarationsInBundleTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals;
import java.io.File;
import java.util.jar.JarFile;
+import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.junit.Test;
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/JcrNamespaceRegistryTest.java b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/JcrNamespaceRegistryTest.java
index 751f1eb..ff04530 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/JcrNamespaceRegistryTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/vltpkg/JcrNamespaceRegistryTest.java
@@ -30,7 +30,7 @@ import static javax.jcr.NamespaceRegistry.PREFIX_XML;
public class JcrNamespaceRegistryTest{
- @Test(expected = NamespaceException.class)
+ @Test
public void test_unregister() throws RepositoryException, ParseException, IOException {
JcrNamespaceRegistry jcrNamespaceRegistry = new JcrNamespaceRegistry();
@@ -39,21 +39,23 @@ public class JcrNamespaceRegistryTest{
Assert.assertEquals(8, jcrNamespaceRegistry.getURIs().length);
//throws javax.jcr.NamespaceException: No URI for prefix 'xml' declared.
- jcrNamespaceRegistry.getURI(PREFIX_XML);
+ Assert.assertThrows(NamespaceException.class, () -> jcrNamespaceRegistry.getURI(PREFIX_XML));
+
}
-
- @Test(expected = NamespaceException.class)
+
+ @Test
public void test_get_prefix_exception() throws RepositoryException, ParseException, IOException {
JcrNamespaceRegistry jcrNamespaceRegistry = new JcrNamespaceRegistry();
- jcrNamespaceRegistry.getPrefix("_Asd");
+ Assert.assertNull(jcrNamespaceRegistry.getPrefix("_Asd"));
}
- @Test(expected = NamespaceException.class)
+ @Test
public void test_get_uri_exception() throws RepositoryException, ParseException, IOException {
JcrNamespaceRegistry jcrNamespaceRegistry = new JcrNamespaceRegistry();
- jcrNamespaceRegistry.getURI("_Asd");
+ Assert.assertThrows(NamespaceException.class, () -> jcrNamespaceRegistry.getURI("_Asd"));
+
}
}
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/build_playground.ui.content-1.0-SNAPSHOT.zip b/src/test/resources/org/apache/sling/feature/cpconverter/build_playground.ui.content-1.0-SNAPSHOT.zip
deleted file mode 100644
index 9c07c08..0000000
Binary files a/src/test/resources/org/apache/sling/feature/cpconverter/build_playground.ui.content-1.0-SNAPSHOT.zip and /dev/null differ
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/%3c%22&%3e.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/%3c%22&%3e.xml
new file mode 100644
index 0000000..db7494e
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/%3c%22&%3e.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<jcr:root xmlns="" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:xml="http://www.w3.org/XML/1998/namespace"
+ xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:oak="http://jackrabbit.apache.org/oak/ns/1.0"
+ xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" xmlns:mix="http://www.jcp.org/jcr/mix/1.0"
+ xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0"
+ xmlns:vlt="http://www.day.com/jcr/vault/1.0" jcr:primaryType="cq:Component"
+ sling:resourceSuperType="mysite-aem/core/components/global/page" fancyCharacters="<&"'>"></jcr:root>
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/11mumbojumbo.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/11mumbojumbo.xml
new file mode 100644
index 0000000..de1f197
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/11mumbojumbo.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<jcr:root xmlns="" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:oak="http://jackrabbit.apache.org/oak/ns/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:vlt="http://www.day.com/jcr/vault/1.0" jcr:primaryType="cq:Component" prop=" [...]
+ <someOtherNode jcr:primaryType="nt:unstructured" prop="{String}property value as string">
+ </someOtherNode>
+</jcr:root>
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/homepage.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/homepage.xml
new file mode 100644
index 0000000..7bdf717
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/homepage.xml
@@ -0,0 +1,3 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<jcr:root xmlns="" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:oak="http://jackrabbit.apache.org/oak/ns/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:vlt="http://www.day.com/jcr/vault/1.0" jcr:primaryType="cq:Component" sling: [...]
+</jcr:root>
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/include-redirectStatus.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/include-redirectStatus.xml
new file mode 100755
index 0000000..a441d1c
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/include-redirectStatus.xml
@@ -0,0 +1,17 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<jcr:root xmlns="" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:oak="http://jackrabbit.apache.org/oak/ns/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:vlt="http://www.day.com/jcr/vault/1.0" jcr:primaryType="nt:unstructured" mar [...]
+ <items jcr:primaryType="nt:unstructured">
+ <column jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/container">
+ <items jcr:primaryType="nt:unstructured">
+ <redirectStatus jcr:primaryType="nt:unstructured" defaultValue="301" fieldLabel="io.wcm.handler.link.components.global.include.linkRefContainer.redirectStatus.redirectStatus.fieldLabel" name="./redirectStatus" sling:resourceType="granite/ui/components/coral/foundation/form/select" fieldDescription="io.wcm.handler.link.components.global.include.linkRefContainer.redirectStatus.redirectStatus.fieldDescription">
+ <items jcr:primaryType="nt:unstructured">
+ <_301 jcr:primaryType="nt:unstructured" text="io.wcm.handler.link.components.global.include.linkRefContainer.redirectStatus.redirectStatus.301" value="301">
+ </_301>
+ <_302 jcr:primaryType="nt:unstructured" text="io.wcm.handler.link.components.global.include.linkRefContainer.redirectStatus.redirectStatus.302" value="302">
+ </_302>
+ </items>
+ </redirectStatus>
+ </items>
+ </column>
+ </items>
+</jcr:root>
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/nodeName.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/nodeName.xml
new file mode 100644
index 0000000..c11e395
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/nodeName.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<jcr:root xmlns="" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:oak="http://jackrabbit.apache.org/oak/ns/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:vlt="http://www.day.com/jcr/vault/1.0" jcr:primaryType="nt:unstructured" pro [...]
+ <testfile.txt jcr:primaryType="nt:file">
+ <jcr:content jcr:primaryType="nt:resource" jcr:mimeType="application/test" jcr:data="{Binary}" jcr:lastModified="{Long}233992800000">
+ </jcr:content>
+ </testfile.txt>
+</jcr:root>
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/someprop.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/someprop.xml
new file mode 100644
index 0000000..c442151
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/someprop.xml
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<jcr:root xmlns="" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:xml="http://www.w3.org/XML/1998/namespace"
+ xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:oak="http://jackrabbit.apache.org/oak/ns/1.0"
+ xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" xmlns:mix="http://www.jcp.org/jcr/mix/1.0"
+ xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0"
+ xmlns:vlt="http://www.day.com/jcr/vault/1.0" jcr:primaryType="cq:Component" prop="{String}aa"></jcr:root>
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/xyz.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/xyz.xml
new file mode 100644
index 0000000..78dfbe0
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/bundle-entry-xmls/xyz.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<jcr:root xmlns="" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:oak="http://jackrabbit.apache.org/oak/ns/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:vlt="http://www.day.com/jcr/vault/1.0" jcr:primaryType="nt:unstructured" jcr [...]
+ <someOtherNode jcr:primaryType="nt:unstructured" prop="{String}property value as string">
+ </someOtherNode>
+</jcr:root>
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/i18n-jsonfile-xml-descriptor-test/en.json b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/i18n-jsonfile-xml-descriptor-test/en.json
new file mode 100755
index 0000000..ee1613a
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/i18n-jsonfile-xml-descriptor-test/en.json
@@ -0,0 +1,10 @@
+{
+ "com.mysite.components.templating.navigation.title": "Navigation",
+ "com.mysite.components.templating.navigation.overview": "Overview",
+ "com.mysite.components.templating.navigation.open": "Open navigation",
+ "com.mysite.components.templating.navigation.close": "Close navigation",
+ "com.mysite.components.templating.navigation.go-back": "Go back",
+ "com.mysite.components.search": "Search",
+ "com.mysite.components.search.results": "results found",
+ "com.mysite.components.search.showmore": "Show more"
+}
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/i18n-jsonfile-xml-descriptor-test/en.json.dir/.content.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/i18n-jsonfile-xml-descriptor-test/en.json.dir/.content.xml
new file mode 100755
index 0000000..79aadfd
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/i18n-jsonfile-xml-descriptor-test/en.json.dir/.content.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
+ xmlns:oak="http://jackrabbit.apache.org/oak/ns/1.0" xmlns:mix="http://www.jcp.org/jcr/mix/1.0"
+ xmlns:my="http://namespace.com/my"
+ jcr:language="{String}en"
+ jcr:mixinTypes="[mix:language]"
+ jcr:primaryType="nt:file">
+</jcr:root>
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/io.wcm.handler.link-1.7.02.jar b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/io.wcm.handler.link-1.7.02.jar
new file mode 100755
index 0000000..372a19b
Binary files /dev/null and b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/io.wcm.handler.link-1.7.02.jar differ
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite-nodetype-and-page-json-xml-result.xml b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite-nodetype-and-page-json-xml-result.xml
new file mode 100644
index 0000000..6c4ca35
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite-nodetype-and-page-json-xml-result.xml
@@ -0,0 +1,44 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ 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.
+-->
+<jcr:root xmlns="" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:oak="http://jackrabbit.apache.org/oak/ns/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:vlt="http://www.day.com/jcr/vault/1.0" jcr:primaryType="cq:Component" sling: [...]
+ <cq:dialog jcr:primaryType="nt:unstructured">
+ <content jcr:primaryType="nt:unstructured">
+ <items jcr:primaryType="nt:unstructured">
+ <tabs jcr:primaryType="nt:unstructured">
+ <items jcr:primaryType="nt:unstructured">
+ <basic jcr:primaryType="nt:unstructured">
+ <items jcr:primaryType="nt:unstructured">
+ <column jcr:primaryType="nt:unstructured">
+ <items jcr:primaryType="nt:unstructured">
+ <errorhandling jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form/fieldset" jcr:title="Error Handling">
+ <items jcr:primaryType="nt:unstructured">
+ <errorpages jcr:primaryType="nt:unstructured" fieldLabel="Error Pages" name="./errorPages" sling:resourceType="wcm-io/wcm/ui/granite/components/form/pathfield" fieldDescription="Error pages for this content tree" rootPath="/content">
+ <granite:data jcr:primaryType="nt:unstructured" cq-msm-lockable="errorPages"/>
+ </errorpages>
+ </items>
+ </errorhandling>
+ </items>
+ </column>
+ </items>
+ </basic>
+ </items>
+ </tabs>
+ </items>
+ </content>
+ </cq:dialog>
+</jcr:root>
\ No newline at end of file
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite.core-1.0.0-SNAPSHOT-i18n-xml-folderdescriptor.jar b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite.core-1.0.0-SNAPSHOT-i18n-xml-folderdescriptor.jar
new file mode 100644
index 0000000..06be679
Binary files /dev/null and b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite.core-1.0.0-SNAPSHOT-i18n-xml-folderdescriptor.jar differ
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite.core-1.0.0-SNAPSHOT-pagejson.jar b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite.core-1.0.0-SNAPSHOT-pagejson.jar
new file mode 100644
index 0000000..4773cbe
Binary files /dev/null and b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite.core-1.0.0-SNAPSHOT-pagejson.jar differ
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite.core-1.0.0-SNAPSHOT-slinginitialcontent-test.jar b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite.core-1.0.0-SNAPSHOT-slinginitialcontent-test.jar
new file mode 100644
index 0000000..07428f7
Binary files /dev/null and b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite.core-1.0.0-SNAPSHOT-slinginitialcontent-test.jar differ
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite.core-1.0.0-SNAPSHOT-specialchars-json-inputstream.jar b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite.core-1.0.0-SNAPSHOT-specialchars-json-inputstream.jar
new file mode 100644
index 0000000..74ba2ef
Binary files /dev/null and b/src/test/resources/org/apache/sling/feature/cpconverter/handlers/mysite.core-1.0.0-SNAPSHOT-specialchars-json-inputstream.jar differ
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/mysite.all-1.0.0-SNAPSHOT2.zip b/src/test/resources/org/apache/sling/feature/cpconverter/mysite.all-1.0.0-SNAPSHOT2.zip
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/seed.json b/src/test/resources/org/apache/sling/feature/cpconverter/seed.json
new file mode 100644
index 0000000..be10416
--- /dev/null
+++ b/src/test/resources/org/apache/sling/feature/cpconverter/seed.json
@@ -0,0 +1,9 @@
+{
+ "id":"foo:bar:slingosgifeature:1.0.0",
+ "repoinit:TEXT|true":[
+ "register namespace (granite) http://www.adobe.com/jcr/granite/1.0",
+ "register namespace (wcmio) http://wcm.io/ns",
+ "register namespace (crx) http://www.day.com/crx/1.0",
+ "register namespace (cq) http://www.day.com/jcr/cq/1.0"
+ ]
+}
\ No newline at end of file