OsgiBundleFile.java
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2021 wcm.io
* %%
* Licensed 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.
* #L%
*/
package io.wcm.maven.plugins.slinginitialcontenttransform;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.commons.osgi.ManifestHeader;
final class OsgiBundleFile implements Closeable {
static final String HEADER_INITIAL_CONTENT = "Sling-Initial-Content";
static final String HEADER_NAMESPACES = "Sling-Namespaces";
private final JarFile jarFile;
private final List<ContentMapping> contentMappings;
private final Map<String, String> namespaces;
OsgiBundleFile(File file) throws IOException {
this.jarFile = new JarFile(file);
this.contentMappings = buildContentMappings(jarFile);
this.namespaces = buildNamespaces(jarFile);
}
/**
* Reads mappings from bundle paths to content paths defined in Sling-Initial-Content manifest entry.
* @param jarFile JAR file
* @return Content mappings
* @throws IOException I/O exception
*/
private static List<ContentMapping> buildContentMappings(JarFile jarFile) throws IOException {
List<ContentMapping> result = new ArrayList<>();
String manifestAttribute = getManifestAttribute(jarFile, HEADER_INITIAL_CONTENT);
ManifestHeader header = null;
if (manifestAttribute != null) {
header = ManifestHeader.parse(manifestAttribute);
}
if (header != null) {
for (ManifestHeader.Entry entry : header.getEntries()) {
String bundlePath = entry.getValue();
String contentPath = entry.getDirectiveValue("path");
String ignoreImportProviders = entry.getDirectiveValue("ignoreImportProviders");
if (StringUtils.isNoneBlank(bundlePath, contentPath)) {
result.add(new ContentMapping(bundlePath, contentPath, ignoreImportProviders));
}
}
}
return result;
}
/**
* Builds map with Sling namespace definitions.
* @param jarFile JAR file
* @return Namespaces
* @throws IOException I/O exception
*/
private static Map<String, String> buildNamespaces(JarFile jarFile) throws IOException {
Map<String, String> result = new HashMap<>();
String manifestAttribute = getManifestAttribute(jarFile, HEADER_NAMESPACES);
if (manifestAttribute != null) {
String[] entries = StringUtils.split(manifestAttribute, ",");
for (String mapping : entries) {
String key = StringUtils.substringBefore(mapping, "=");
String value = StringUtils.substringAfter(mapping, "=");
if (StringUtils.isNoneBlank(key, value)) {
result.put(key, value);
}
}
}
return result;
}
private static String getManifestAttribute(JarFile jarFile, String headerName) throws IOException {
Manifest manifest = jarFile.getManifest();
if (manifest != null) {
return manifest.getMainAttributes().getValue(headerName);
}
return null;
}
/**
* @return true if bundle has any Sling-Initial-Content
*/
public boolean hasContent() {
return !contentMappings.isEmpty();
}
/**
* @return Returns all configured Sling-Initial-Content mappings.
*/
public List<ContentMapping> getContentMappings() {
return contentMappings;
}
/**
* @return Returns sling namespaces.
*/
public Map<String, String> getNamespaces() {
return this.namespaces;
}
/**
* Get all Sling-Initial-Content entries matching for the given mapping.
* @param mapping Content mapping
* @return Content entries.
*/
@SuppressWarnings("null")
public Stream<BundleEntry> getContentEntries(ContentMapping mapping) {
Pattern bundlePathPattern = Pattern.compile("^" + Pattern.quote(mapping.getBundlePath()) + "/.*$");
return jarFile.stream()
.filter(entry -> bundlePathPattern.matcher(entry.getName()).matches())
.map(entry -> {
String path = mapping.getContentPath() + StringUtils.substringAfter(entry.getName(), mapping.getBundlePath());
return new BundleEntry(path, jarFile, entry);
});
}
/**
* Get all JAR entries not matching any Sling-Initial-Content mapped path.
* @return JAR entries
*/
@SuppressWarnings("null")
public Stream<BundleEntry> getNonContentEntries() {
Pattern allBundlePathsPattern = Pattern.compile("^(" + contentMappings.stream()
.map(ContentMapping::getBundlePath)
.map(Pattern::quote)
.collect(Collectors.joining("|")) + ")(/.*)?$");
return jarFile.stream()
.filter(entry -> !allBundlePathsPattern.matcher(entry.getName()).matches())
.map(entry -> new BundleEntry(entry.getName(), jarFile, entry));
}
@Override
public void close() throws IOException {
jarFile.close();
}
}