kwin commented on a change in pull request #74:
URL:
https://github.com/apache/sling-org-apache-sling-feature-cpconverter/pull/74#discussion_r615767600
##########
File path:
src/main/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandler.java
##########
@@ -108,111 +144,302 @@ public void handle(@NotNull String path, @NotNull
Archive archive, @NotNull Entr
logger.debug("Start level {} was extracted from path {}",
startLevel, path);
}
- try (JarInputStream jarInput = new
JarInputStream(Objects.requireNonNull(archive.openInputStream(entry)))) {
- Properties properties = readGav(entry.getName(), jarInput);
- manifest = jarInput.getManifest();
-
- if (!properties.isEmpty()) {
- groupId = getCheckedProperty(properties, NAME_GROUP_ID);
- artifactId = getCheckedProperty(properties, NAME_ARTIFACT_ID);
- version = getCheckedProperty(properties, NAME_VERSION);
- classifier = properties.getProperty(NAME_CLASSIFIER);
- } else { // maybe the included jar is just an OSGi bundle but not
a valid Maven artifact
- groupId = getCheckedProperty(manifest, BUNDLE_SYMBOLIC_NAME);
- // Make sure there are not spaces in the name to adhere to the
Maven Group Id specification
- groupId = groupId.replace(' ', '_').replace(':',
'_').replace('/', '_').replace('\\', '_');
- if (groupId.indexOf('.') != -1) {
- artifactId = groupId.substring(groupId.lastIndexOf('.') +
1);
- groupId = groupId.substring(0, groupId.lastIndexOf('.'));
- }
- if (artifactId == null || artifactId.isEmpty()) {
- artifactId = groupId;
+ String bundleName = entry.getName();
+ // Remove the leading path
+ int idx = bundleName.lastIndexOf('/');
+ if (idx >= 0) {
+ bundleName = bundleName.substring(idx + 1);
+ }
+ // Remove the extension
+ int edx = bundleName.lastIndexOf('.');
+ if (edx > 0) {
+ bundleName = bundleName.substring(0, edx);
+ }
+
+ // create a temporary JAR file (extracted from archive)
+ Path tmpBundleJar =
Files.createTempFile(converter.getTempDirectory().toPath(), "extracted",
bundleName + ".jar");
+ try (OutputStream output = Files.newOutputStream(tmpBundleJar);
+ InputStream input =
Objects.requireNonNull(archive.openInputStream(entry))) {
+ IOUtils.copy(input, output);
+ }
+
+ try (JarFile jarFile = new JarFile(tmpBundleJar.toFile())) {
+ // first extract bundle metadata from JAR input stream
+ ArtifactId id = extractArtifactId(bundleName, jarFile);
+
+ try (InputStream strippedBundleInput = extractInitialContent(id,
jarFile, converter, runMode)) {
+
Objects.requireNonNull(converter.getArtifactsDeployer()).deploy(new
InputStreamArtifactWriter(strippedBundleInput), id);
+
Objects.requireNonNull(converter.getFeaturesManager()).addArtifact(runMode, id,
startLevel);
+
+ String exportHeader =
Objects.requireNonNull(jarFile.getManifest()).getMainAttributes().getValue(Constants.EXPORT_PACKAGE);
+ if (exportHeader != null) {
+ for (Clause clause : Parser.parseHeader(exportHeader)) {
+
converter.getFeaturesManager().addAPIRegionExport(runMode, clause.getName());
+ }
}
- Version osgiVersion =
Version.parseVersion(getCheckedProperty(manifest, BUNDLE_VERSION));
- version = osgiVersion.getMajor() + "." +
osgiVersion.getMinor() + "." + osgiVersion.getMicro() +
(osgiVersion.getQualifier().isEmpty() ? "" : "-" + osgiVersion.getQualifier());
}
+ } finally {
+ Files.delete(tmpBundleJar);
}
+ }
- try (InputStream input = archive.openInputStream(entry)) {
- if (input != null) {
- ArtifactId id = new ArtifactId(groupId, artifactId, version,
classifier, JAR_TYPE);
+ @NotNull InputStream extractInitialContent(@NotNull ArtifactId
bundleArtifactId, @NotNull JarFile jarFile, @NotNull
ContentPackage2FeatureModelConverter converter, @Nullable String runMode)
throws Exception {
+ // parse "Sling-Initial-Content" header
+ Manifest manifest = Objects.requireNonNull(jarFile.getManifest());
+ Iterator<PathEntry> pathEntries = PathEntry.getContentPaths(manifest,
-1);
+ if (pathEntries == null) {
+ return new FileInputStream(jarFile.getName());
+ }
+ logger.info("Extracting Sling-Initial-Content from '{}'",
bundleArtifactId);
+ Collection<PathEntry> pathEntryList = new ArrayList<>();
+ pathEntries.forEachRemaining(pathEntryList::add);
+
+ // remove header
+ manifest.getMainAttributes().remove(new
Attributes.Name(PathEntry.CONTENT_HEADER));
+ 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 (!extractInitialContent(jarEntry, input,
bundleArtifactId, 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
+ ZipEntry ze = new ZipEntry(jarEntry.getName());
+ bundleOutput.putNextEntry(ze);
+ IOUtils.copy(input, bundleOutput);
+ bundleOutput.closeEntry();
+ }
+ }
+ }
+ }
+ }
+ // add additional content packages to feature model
+ finalizePackageAssembly(packageAssemblers, converter, runMode);
+
+ // return stripped bundle's inputstream which must be deleted on close
+ return Files.newInputStream(newBundleFile, StandardOpenOption.READ,
StandardOpenOption.DELETE_ON_CLOSE);
+ }
-
Objects.requireNonNull(converter.getArtifactsDeployer()).deploy(new
InputStreamArtifactWriter(input), id);
+ /**
+ *
+ * @param jarEntry
+ * @param bundleFileInputStream
+ * @param pathEntriesStream
+ * @param packageAssemblers
+ * @param converter
+ * @return {@code true} in case the given entry was part of the initial
content otherwise {@code false}
+ * @throws Exception
+ */
+ boolean extractInitialContent(@NotNull JarEntry jarEntry, @NotNull
InputStream bundleFileInputStream, @NotNull ArtifactId bundleArtifactId,
@NotNull Collection<PathEntry> pathEntries, @NotNull Map<PackageType,
VaultPackageAssembler> packageAssemblers, @NotNull JcrNamespaceRegistry
nsRegistry, @NotNull ContentPackage2FeatureModelConverter converter) throws
Exception {
+ 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;
+ }
+ ContentParser contentParser = 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");
+ String contentPackagePath =
org.apache.jackrabbit.vault.util.Constants.ROOT_DIR +
PlatformNameFormat.getPlatformPath(repositoryPath);
+
+ // in which content package should this end up?
+ VaultPackageAssembler packageAssembler =
initPackageAssemblerForPath(bundleArtifactId, repositoryPath, pathEntry.get(),
packageAssemblers, converter);
+ Path tmpInputFile = null;
+ if (contentParser != null) {
+ // convert to docview xml
+ tmpInputFile =
Files.createTempFile(converter.getTempDirectory().toPath(), "docview", ".xml");
+ try (OutputStream docViewOutput =
Files.newOutputStream(tmpInputFile, StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING);
+ DocViewSerializerContentHandler contentHandler = new
DocViewSerializerContentHandler(docViewOutput, nsRegistry)) {
+ contentParser.parse(contentHandler, bundleFileInputStream, new
ParserOptions());
+ contentPackagePath =
FilenameUtils.removeExtension(contentPackagePath) + ".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);
+ }
+ }
-
Objects.requireNonNull(converter.getFeaturesManager()).addArtifact(runMode, id,
startLevel);
+ // does entry in initial content need to be extracted into feature
model (e.g. for OSGi configurations)
+ EntryHandler entryHandler =
converter.getHandlersManager().getEntryHandlerByEntryPath(contentPackagePath);
+ if (entryHandler != null) {
+ if (tmpInputFile == null) {
+ tmpInputFile =
Files.createTempFile(converter.getTempDirectory().toPath(), "initial-content",
Text.getName(jarEntry.getName()));
+ try (OutputStream tmpBundleOutput =
Files.newOutputStream(tmpInputFile, StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)) {
+ IOUtils.copy(bundleFileInputStream, tmpBundleOutput);
+ }
+ }
+ // TODO: map path to imitate content-package structure
+ SingleFileArchive archive = new
SingleFileArchive(tmpInputFile.toFile(), contentPackagePath);
+ entryHandler.handle(repositoryPath, archive, archive.getRoot(),
converter);
+ Files.delete(tmpInputFile);
+ } else {
+ // ... otherwise add it to the content package
+ if (tmpInputFile != null) {
+ packageAssembler.addEntry(contentPackagePath,
tmpInputFile.toFile());
+ Files.delete(tmpInputFile);
+ } else {
+ packageAssembler.addEntry(contentPackagePath,
bundleFileInputStream);
+ }
+ }
+ return true;
+ }
- String epHeader =
manifest.getMainAttributes().getValue(Constants.EXPORT_PACKAGE);
- if (epHeader != null) {
- for (Clause clause : Parser.parseHeader(epHeader)) {
-
converter.getFeaturesManager().addAPIRegionExport(runMode, clause.getName());
+ JcrNamespaceRegistry createNamespaceRegistry(@NotNull Manifest manifest,
@NotNull JarFile jarFile, @NotNull Map<String, String>
predefinedNamespaceUriByPrefix) throws RepositoryException, IOException,
ParseException {
+ 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 (StringUtils.isNotBlank(namespacesDefinitionHeader)) {
+ for (ManifestHeader.Entry entry :
ManifestHeader.parse(namespacesDefinitionHeader).getEntries()) {
+ final String token = entry.getValue();
+ 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 (StringUtils.isNotBlank(typesHeader) ) {
+ 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;
}
- // method visibility set to 'protected' fot testing purposes
- protected @NotNull Properties readGav(@NotNull String entryName, @NotNull
JarInputStream jarInput) throws IOException {
- Properties pomProperties = new Properties();
- Properties pomXmlProperties = new Properties();
+ /**
+ * 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) {
+ 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 IllegalStateException("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);
+ }
+
+ DefaultWorkspaceFilter filter = assembler.getFilter();
Review comment:
@stefanseifert I need some support here. Unsure which `ImportMode`
matches the different flags of the JCR Contentloader best (compare also with
[SLING-10318](https://issues.apache.org/jira/browse/SLING-10318))
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]