blob: d6d2a0ef9daa0cb7af3e31244a2867bc72c6b01c [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;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Bundles;
import org.apache.sling.feature.Extension;
import org.apache.sling.feature.Feature;
import org.apache.sling.feature.builder.ArtifactProvider;
import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
import org.apache.sling.feature.scanner.impl.FeatureDescriptorImpl;
import org.apache.sling.feature.scanner.spi.ExtensionScanner;
import org.apache.sling.feature.scanner.spi.FrameworkScanner;
import javax.json.JsonObject;
import javax.json.JsonValue;
/**
* The scanner is a service that scans items and provides descriptions for
* these. The following items can be scanned individually
* <ul>
* <li>A bundle artifact
* <li>A feature (requires {@link ExtensionScanner}s)
* <li>A framework (requires {@link FrameworkScanner}s)
* </ul>
*
* The scanner uses an internal cache for the scanned results, subsequent scan
* calls with the same input will be directly served from the cache. The cache
* is an in memory cache and its lifetime is bound to the lifetime of the used
* scanner instance.
*
*/
public class Scanner {
private final ArtifactProvider artifactProvider;
private final List<ExtensionScanner> extensionScanners;
private final List<FrameworkScanner> frameworkScanners;
/** The in memory cache for the scanned descriptors. */
private final Map<String, Object> cache = new ConcurrentHashMap<>();
/**
* Create a new scanner
*
* @param artifactProvider The artifact provider
* @param extensionScanners A list of extension scanners
* @param frameworkScanners A list of framework scanners
* @throws IOException If something goes wrong
*/
public Scanner(final ArtifactProvider artifactProvider,
final List<ExtensionScanner> extensionScanners,
final List<FrameworkScanner> frameworkScanners)
throws IOException {
this.artifactProvider = artifactProvider;
this.extensionScanners = extensionScanners == null ? getServices(ExtensionScanner.class) : extensionScanners;
this.frameworkScanners = frameworkScanners == null ? getServices(FrameworkScanner.class) : frameworkScanners;
}
/**
* Create a new scanner and use the service loader to find the scanners
*
* @param artifactProvider The artifact provider
* @throws IOException If something goes wrong
*/
public Scanner(final ArtifactProvider artifactProvider)
throws IOException {
this(artifactProvider, null, null);
}
/**
* Get services from the service loader
*
* @param clazz The service class
* @return The list of services might be empty.
*/
private static <T> List<T> getServices(final Class<T> clazz) {
final ServiceLoader<T> loader = ServiceLoader.load(clazz);
final List<T> list = new ArrayList<>();
for(final T task : loader) {
list.add(task);
}
return list;
}
/**
* Scan a bundle
*
* @param bundle The bundle artifact
* @return The bundle descriptor
* @throws IOException If something goes wrong or the provided artifact is not a
* bundle.
*/
public BundleDescriptor scanBundle(final Artifact bundle) throws IOException {
return this.doScan(bundle, bundle.getStartOrder());
}
/**
* Scan a bundle
*
* @param bundle The bundle artifact
* @param startLevel The start level of the bundle
* @return The bundle descriptor
* @throws IOException If something goes wrong or the provided artifact is not a
* bundle.
* @deprecated Use {@link #scanBundle(Artifact)}
*/
@Deprecated
public BundleDescriptor scan(final Artifact bundle, final int startLevel) throws IOException {
return this.doScan(bundle, startLevel);
}
private BundleDescriptor doScan(final Artifact bundle, final int startLevel) throws IOException {
final String key = bundle.getId().toMvnId().concat(":")
.concat(String.valueOf(startLevel)).concat(":")
.concat(Stream.of(bundle.getFeatureOrigins()).map(ArtifactId::toMvnId).collect(Collectors.joining(",")));
BundleDescriptor desc = (BundleDescriptor) this.cache.get(key);
if (desc == null) {
final URL file = artifactProvider.provide(bundle.getId());
if (file == null) {
throw new IOException("Unable to find file for " + bundle.getId());
}
desc = new BundleDescriptorImpl(bundle, file, startLevel);
this.cache.put(key, desc);
}
return desc;
}
/**
* Get all bundle descriptors
*
* @param bundles The bundles
* @param desc The container descriptor
* @throws IOException If something goes wrong or no suitable scanner is found.
*/
private void getBundleInfos(final Bundles bundles, final ContainerDescriptor desc)
throws IOException {
for (final Artifact bundle : bundles) {
final BundleDescriptor bundleDesc = scanBundle(bundle);
desc.getBundleDescriptors().add(bundleDesc);
}
}
/**
* Scan all extensions of a feature
*
* @param f The feature
* @param desc The container descriptor
* @throws IOException If something goes wrong or no suitable scanner is found.
*/
private void scanExtensions(final Feature f, final ContainerDescriptor desc)
throws IOException {
for (final Extension ext : f.getExtensions()) {
if (ext.getName().equals("analyser-metadata")) {
continue;
}
ContainerDescriptor extDesc = null;
for(final ExtensionScanner scanner : this.extensionScanners) {
extDesc = scanner.scan(f, ext, this.artifactProvider);
if ( extDesc != null ) {
break;
}
}
if ( extDesc == null ) {
throw new IOException("No extension scanner found for extension named " + ext.getName() + " of type " + ext.getType().name());
}
desc.getRequirements().addAll(extDesc.getRequirements());
desc.getCapabilities().addAll(extDesc.getCapabilities());
desc.getExportedPackages().addAll(extDesc.getExportedPackages());
desc.getImportedPackages().addAll(extDesc.getImportedPackages());
desc.getDynamicImportedPackages().addAll(extDesc.getDynamicImportedPackages());
desc.getArtifactDescriptors().addAll(extDesc.getArtifactDescriptors());
desc.getBundleDescriptors().addAll(extDesc.getBundleDescriptors());
}
}
/**
* Compact the container description
*
* @param desc The contaier description
*/
private void compact(final ContainerDescriptor desc) {
// TBD remove all import packages / dynamic import packages which are resolved by this bundle set
// same with requirements
}
/**
* Scan a feature
*
* @param feature The feature
* @return The feature descriptor
* @throws IOException If something goes wrong or a scanner is missing
*/
public FeatureDescriptor scan(final Feature feature) throws IOException {
final String key = feature.getId().toMvnId();
FeatureDescriptorImpl desc = (FeatureDescriptorImpl) this.cache.get(key);
if (desc == null) {
desc = new FeatureDescriptorImpl(feature);
populateCache(feature);
getBundleInfos(feature.getBundles(), desc);
scanExtensions(feature, desc);
compact(desc);
desc.lock();
this.cache.put(key, desc);
}
return desc;
}
private void populateCache(Feature feature) throws IOException {
Extension extension = feature.getExtensions().getByName("analyser-metadata");
if (extension != null) {
JsonObject json = extension.getJSONStructure().asJsonObject();
for (Map.Entry<String, JsonValue> entry : json.entrySet()) {
ArtifactId id = ArtifactId.fromMvnId(entry.getKey());
if (feature.getBundles().containsExact(id)) {
Artifact bundle = feature.getBundles().getExact(id);
final String key = id.toMvnId().concat(":")
.concat(String.valueOf(bundle.getStartOrder())).concat(":")
.concat(Stream.of(bundle.getFeatureOrigins()).map(ArtifactId::toMvnId).collect(Collectors.joining(",")));
if (this.cache.get(key) == null) {
JsonObject headers = entry.getValue().asJsonObject();
if (headers.containsKey("manifest")) {
final URL file = artifactProvider.provide(id);
Manifest manifest = new Manifest();
JsonObject manifestHeaders = headers.getJsonObject("manifest");
for (String name : manifestHeaders.keySet()) {
manifest.getMainAttributes().putValue(name, manifestHeaders.getString(name));
}
BundleDescriptor desc = new BundleDescriptorImpl(bundle, file, manifest, bundle.getStartOrder());
this.cache.put(key, desc);
}
}
}
}
}
}
/**
* Scan a framework
*
* @param framework The framework
* @param props framework properties to launch the framework
* @return The framework descriptor
* @throws IOException If something goes wrong or a scanner is missing
*/
public BundleDescriptor scan(final ArtifactId framework, final Map<String,String> props) throws IOException {
final StringBuilder sb = new StringBuilder();
sb.append(framework.toMvnId());
if (props != null) {
final Map<String, String> sortedMap = new TreeMap<String, String>(props);
for (final Map.Entry<String, String> entry : sortedMap.entrySet()) {
sb.append(":").append(entry.getKey()).append("=").append(entry.getValue());
}
}
final String key = sb.toString();
BundleDescriptor desc = (BundleDescriptor) this.cache.get(key);
if (desc == null) {
for (final FrameworkScanner scanner : this.frameworkScanners) {
desc = scanner.scan(framework, props, artifactProvider);
if (desc != null) {
break;
}
}
if (desc == null) {
throw new IOException("No scanner found for framework " + framework.toMvnId());
}
this.cache.put(key, desc);
}
return desc;
}
}