blob: 8bd4c0608be5f5e20c200df6a1544879297d9844 [file] [log] [blame]
/*
* 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.scanner.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import org.apache.felix.utils.manifest.Clause;
import org.apache.felix.utils.manifest.Parser;
import org.apache.felix.utils.resource.ResourceBuilder;
import org.apache.felix.utils.resource.ResourceImpl;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.builder.ArtifactProvider;
import org.apache.sling.feature.io.IOUtils;
import org.apache.sling.feature.scanner.BundleDescriptor;
import org.apache.sling.feature.scanner.PackageInfo;
import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Information about a bundle
*/
public class BundleDescriptorImpl
extends BundleDescriptor {
private static final Logger logger = LoggerFactory.getLogger(BundleDescriptorImpl.class);
/** The provider to use if now file is given up-front **/
private final ArtifactProvider artifactProvider;
/** The bundle symbolic name. */
private String symbolicName;
/** The bundle version. */
private String bundleVersion;
/** Manifest */
private final Manifest manifest;
/** The physical file for analyzing. */
private URL artifactFile;
/** The corresponding artifact from the feature. */
private final Artifact artifact;
static Manifest getManifest(URL file) throws IOException {
Manifest manifest = null;
try {
if ("file".equals(file.getProtocol()) && new File(file.toURI()).isDirectory()) {
// it's a directory
Path manifestPath = Paths.get(file.toURI()).resolve("META-INF/MANIFEST.MF");
if (Files.exists(manifestPath)) {
try (FileInputStream is = new FileInputStream(manifestPath.toFile())) {
manifest = new Manifest(is);
}
}
} else {
try (JarFile jarFile = IOUtils.getJarFileFromURL(file, true, null)) {
manifest = jarFile.getManifest();
} catch ( final IOException ioe) {
// rethrow with more info
throw new IOException(file + " : " + ioe.getMessage(), ioe);
}
}
} catch (URISyntaxException e) {
throw new IOException("Failed to determine if the file is a directory", e);
}
return manifest;
}
/**
* Constructor for a new descriptor
* @param artifact The artifact
* @param url The URL
* @throws IOException If the manifest can't be get
* @throws NullPointerException If artifact is {@code null}
*/
public BundleDescriptorImpl(final Artifact artifact,
final URL url) throws IOException {
this(artifact, url, null, getManifest(url));
}
/**
* Constructor for a new descriptor
* @param artifact The artifact
* @param provider The artifact provider
* @param manifest The manifest
* @throws IOException If the manifest can't be get
* @throws NullPointerException If artifact is {@code null}
*/
public BundleDescriptorImpl(final Artifact artifact,
final ArtifactProvider provider,
final Manifest manifest) throws IOException {
this(artifact, null, provider, manifest);
}
/**
* Constructor for a new descriptor
* @param artifact The artifact
* @param url The URL
* @param provider The artifact provider
* @param manifest The manifest
* @throws IOException If the manifest can't be get
* @throws NullPointerException If artifact is {@code null}
*/
public BundleDescriptorImpl(final Artifact artifact,
final URL url,
final ArtifactProvider provider,
final Manifest manifest) throws IOException {
super(artifact.getId().toMvnId());
this.artifact = artifact;
this.artifactFile = url;
this.artifactProvider = provider;
if ( manifest == null ) {
throw new IOException("File has no manifest");
}
this.manifest = new Manifest(manifest);
this.analyze();
this.lock();
}
/**
* Get the bundle symbolic name.
* @return The bundle symbolic name
*/
@Override
public String getBundleSymbolicName() {
return symbolicName;
}
/**
* Get the bundle version
* @return The bundle version
*/
@Override
public String getBundleVersion() {
return bundleVersion;
}
@Override
public URL getArtifactFile() {
if (artifactFile == null && artifactProvider != null) {
try {
artifactFile = artifactProvider.provide(artifact.getId());
} catch (Exception ex) {
// Ignore, we assume this is a best effort and callers can handle a null.
logger.debug("Unable to get artifact file for: " + artifact.getId(), ex);
}
}
return artifactFile;
}
@Override
public Artifact getArtifact() {
return artifact;
}
@Override
public Manifest getManifest() {
return this.manifest;
}
private void analyze() throws IOException {
final String name = this.manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
if ( name != null ) {
final String version = this.manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
if ( version == null ) {
throw new IOException("Unable to get bundle version from artifact " + getArtifact().getId().toMvnId());
}
this.symbolicName = name;
this.bundleVersion = version;
final String newBundleName = this.getArtifact().getMetadata().get("bundle:rename-bsn");
if (newBundleName != null) {
this.symbolicName = newBundleName;
}
this.getExportedPackages().addAll(extractExportedPackages(this.manifest));
this.getImportedPackages().addAll(extractImportedPackages(this.manifest));
this.getDynamicImportedPackages().addAll(extractDynamicImportedPackages(this.manifest));
try {
ResourceImpl resource = ResourceBuilder.build(this.artifact.getId().toMvnUrl(), this.manifest.getMainAttributes().entrySet().stream()
.collect(Collectors.toMap(entry -> entry.getKey().toString(), entry -> entry.getValue().toString())));
this.getCapabilities().addAll(resource.getCapabilities(null));
this.getRequirements().addAll(resource.getRequirements(null).stream()
.map(entry -> new MatchingRequirementImpl(entry)).collect(Collectors.toList()));
} catch (Exception ex) {
throw new IOException(ex);
}
} else {
throw new IOException("Unable to get bundle symbolic name from artifact " + getArtifact().getId().toMvnId());
}
}
private static List<PackageInfo> extractPackages(final Manifest m,
final String headerName,
final String defaultVersion,
final boolean checkOptional) {
final String pckInfo = m.getMainAttributes().getValue(headerName);
if (pckInfo != null) {
final Clause[] clauses = Parser.parseHeader(pckInfo);
final List<PackageInfo> pcks = new ArrayList<>();
for(final Clause entry : clauses) {
Object versionObj = entry.getAttribute("version");
final String version;
if ( versionObj == null ) {
version = defaultVersion;
} else {
version = versionObj.toString();
}
boolean optional = false;
if ( checkOptional ) {
final String resolution = entry.getDirective("resolution");
optional = "optional".equalsIgnoreCase(resolution);
}
Set<String> uses = new HashSet<>();
String usesAttribute = entry.getDirective("uses");
if (usesAttribute != null && !usesAttribute.isEmpty()) {
StringTokenizer tokenizer = new StringTokenizer(usesAttribute, ",");
while (tokenizer.hasMoreTokens()) {
String usePackage = tokenizer.nextToken();
uses.add(usePackage);
}
}
final PackageInfo pck = new PackageInfo(entry.getName(),
version,
optional,
uses);
pcks.add(pck);
}
return pcks;
}
return Collections.emptyList();
}
private static List<PackageInfo> extractExportedPackages(final Manifest m) {
return extractPackages(m, Constants.EXPORT_PACKAGE, "0.0.0", false);
}
private static List<PackageInfo> extractImportedPackages(final Manifest m) {
return extractPackages(m, Constants.IMPORT_PACKAGE, null, true);
}
private static List<PackageInfo> extractDynamicImportedPackages(final Manifest m) {
return extractPackages(m, Constants.DYNAMICIMPORT_PACKAGE, null, false);
}
}