blob: 9b3cfb2d2d3ef6ac5f4c168a42d02ecf2494c1c5 [file] [log] [blame]
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.winegrower.scanner;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.list;
import static java.util.Collections.singletonList;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static org.apache.xbean.finder.archive.ClasspathArchive.archive;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Stream;
import org.apache.winegrower.Ripener;
import org.apache.winegrower.scanner.manifest.ManifestContributor;
import org.apache.winegrower.scanner.manifest.ManifestCreator;
import org.apache.xbean.finder.AnnotationFinder;
import org.apache.xbean.finder.ClassLoaders;
import org.apache.xbean.finder.UrlSet;
import org.apache.xbean.finder.archive.Archive;
import org.apache.xbean.finder.archive.ClassesArchive;
import org.apache.xbean.finder.util.Files;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StandaloneScanner {
private final static Logger LOGGER = LoggerFactory.getLogger(StandaloneScanner.class);
private static final Attributes.Name OSGI_MANIFEST_MARKER = new Attributes.Name("Bundle-Version");
private final List<URL> urls;
private final Ripener.Configuration configuration;
private final ClassLoader loader;
private final File frameworkJar;
private final Map<String, Manifest> providedManifests;
private final Map<String, List<String>> providedIndex;
public StandaloneScanner(final Ripener.Configuration configuration, final File frameworkJar) {
this.configuration = configuration;
this.frameworkJar = frameworkJar;
this.loader = Thread.currentThread().getContextClassLoader();
this.urls = findUrls();
try { // fatjar plugin
providedManifests = list(this.loader.getResources("WINEGROWER-INF/manifests.properties")).stream()
.flatMap(url -> {
final Properties properties = new Properties();
try (final InputStream stream = url.openStream()) {
properties.load(stream);
} catch (final IOException e) {
throw new IllegalArgumentException(e);
}
return properties.stringPropertyNames().stream()
.collect(toMap(identity(), key -> {
final String property = properties.getProperty(key);
final Manifest manifest = new Manifest();
try (final ByteArrayInputStream mfStream = new ByteArrayInputStream(property.getBytes(StandardCharsets.UTF_8))) {
manifest.read(mfStream);
} catch (final IOException e) {
throw new IllegalArgumentException(e);
}
return manifest;
})).entrySet().stream();
})
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
providedIndex = list(this.loader.getResources("WINEGROWER-INF/index.properties")).stream()
.flatMap(url -> {
final Properties properties = new Properties();
try (final InputStream stream = url.openStream()) {
properties.load(stream);
} catch (final IOException e) {
throw new IllegalArgumentException(e);
}
return properties.stringPropertyNames().stream()
.collect(toMap(identity(), key -> {
final String property = properties.getProperty(key);
return asList(property.split(","));
})).entrySet().stream();
})
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
} catch (final IOException e) {
throw new IllegalArgumentException(e);
}
}
private List<URL> findUrls() {
if (loader == null || System.getProperty("java.home") == null /*graalvm*/ ||
Boolean.getBoolean("winegrower.scanner.standalone.skipUrlsScanning")) {
return emptyList();
}
try {
return new UrlSet(ClassLoaders.findUrls(loader)).excludeJvm().getUrls();
} catch (final IOException e) {
throw new IllegalStateException(e);
}
}
public Collection<BundleDefinition> findEmbeddedClasses() {
// potentially other scripting languages
return scanGroovy();
}
private Collection<BundleDefinition> scanGroovy() {
if (loader == null || !loader.getClass().getName().equals("groovy.lang.GroovyClassLoader")) {
return emptyList();
}
try { // groovy - note that the classloader returns classes but not .class as resource so we fail to activate our contributors
final Class<?>[] loadedClasses = Class[].class.cast(
loader.getClass().getMethod("getLoadedClasses").invoke(loader));
try {
final Archive archive = new ClassesArchive(loadedClasses);
final Manifest groovyClassesManifest = tryLoadManifest(archive, "EmbeddedGroovyClasses");
if (groovyClassesManifest == null) {
return emptyList();
}
LOGGER.debug("EmbeddedGroovyClasses was scanned and is converted to a bundle");
return singletonList(new BundleDefinition(groovyClassesManifest, null, emptyList()));
} catch (final LinkageError e) {
LOGGER.debug("EmbeddedGroovyClasses is not scannable, maybe exclude it in framework configuration");
return emptyList();
}
} catch (final Exception e) {
return emptyList();
}
}
public Collection<BundleDefinition> findPotentialOSGiBundles() {
final KnownJarsFilter filter = new KnownJarsFilter(configuration);
return urls.stream()
.map(it -> new FileAndUrl(Files.toFile(it), it))
.filter(it -> !it.file.getAbsoluteFile().equals(frameworkJar))
.filter(it -> filter.test(it.file.getName()))
.filter(it -> toDefinition(it.file) == null)
.map(it -> {
final Archive jarArchive = archive(loader, it.url);
// we scan per archive to be able to create bundle after
try {
final String name = it.file.getName();
final Manifest manifest = tryLoadManifest(jarArchive, name);
if (manifest == null) {
return null;
}
LOGGER.debug("{} was scanned and is converted to a bundle", it.file);
return new BundleDefinition(manifest, it.file, null);
} catch (final LinkageError e) {
LOGGER.debug("{} is not scannable, maybe exclude it in framework configuration", it.file);
return null;
}
})
.filter(Objects::nonNull)
.collect(toList());
}
public Collection<BundleDefinition> findOSGiBundles() {
return Stream.concat(
urls.stream()
.map(Files::toFile)
.filter(this::isIncluded)
.filter(file -> !providedManifests.containsKey(file.getName())) // don't duplicate it
.filter(it -> this.configuration.getIgnoredBundles().stream().noneMatch(ex -> it.getName().startsWith(ex)))
.map(this::toDefinition)
.filter(Objects::nonNull),
providedManifests.entrySet().stream()
.map(it -> new BundleDefinition(it.getValue(), null, providedIndex.get(it.getKey()))))
.collect(toList());
}
private Manifest tryLoadManifest(final Archive archive, final String name) {
final AnnotationFinder archiveFinder = new ManifestContributor.WinegrowerAnnotationFinder(archive, false);
final ManifestCreator manifestCreator = new ManifestCreator(name);
configuration.getManifestContributors()
.forEach(c -> c.contribute(archiveFinder, manifestCreator));
final Manifest manifest = manifestCreator.getManifest();
if (manifest == null) {
LOGGER.debug("{} was scanned for nothing, maybe adjust scanning exclusions", name);
return null;
}
return manifest;
}
private boolean isIncluded(final File file) {
return !configuration.getJarFilter().test(file.getName());
}
private BundleDefinition toDefinition(final File file) {
if (file.isDirectory()) {
final File manifest = new File(file, "META-INF/MANIFEST.MF");
if (manifest.exists()) {
try (final InputStream stream = new FileInputStream(manifest)) {
final Manifest mf = new Manifest(stream);
if (isOSGi(mf)) {
return new BundleDefinition(mf, file, null);
}
return null;
} catch (final IOException e) {
throw new IllegalStateException(e);
}
}
return null;
}
try (final JarFile jar = new JarFile(file)) {
final Manifest manifest = jar.getManifest();
if (manifest == null) {
return null;
}
if (isOSGi(manifest)) {
return new BundleDefinition(manifest, file, null);
}
return null;
} catch (final Exception e) {
return null;
}
}
private boolean isOSGi(final Manifest mf) {
return mf.getMainAttributes().containsKey(OSGI_MANIFEST_MARKER);
}
public static class BundleDefinition {
private final Manifest manifest;
private final File jar;
private final Collection<String> files;
private BundleDefinition(final Manifest manifest, final File jar, final Collection<String> files) {
this.manifest = manifest;
this.jar = jar;
this.files = files;
}
public Collection<String> getFiles() {
return files;
}
public Manifest getManifest() {
return manifest;
}
public File getJar() {
return jar;
}
}
private static class FileAndUrl {
private final File file;
private final URL url;
private FileAndUrl(final File file, final URL url) {
this.file = file;
this.url = url;
}
}
}