basic winegrower osgi framework impl - still some enhancements but sh… (#12)
* basic winegrower osgi framework impl - still some enhancements but should enable to use it with karaf main/wrapper
* adding framework factory
Co-authored-by: Jean-Baptiste Onofré <jbonofre@apache.org>
diff --git a/winegrower-core/pom.xml b/winegrower-core/pom.xml
index 88f5124..4ca60c1 100644
--- a/winegrower-core/pom.xml
+++ b/winegrower-core/pom.xml
@@ -15,7 +15,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -69,4 +69,50 @@
<scope>test</scope>
</dependency>
</dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>3.2.4</version>
+ <executions>
+ <execution>
+ <id>fatjar</id>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <shadedArtifactAttached>true</shadedArtifactAttached>
+ <shadedClassifierName>fatjar</shadedClassifierName>
+ <createDependencyReducedPom>false</createDependencyReducedPom>
+ <dependencyReducedPomLocation>${project.build.directory}/shade.pom</dependencyReducedPomLocation>
+ <artifactSet>
+ <includes>
+ <include>org.apache.xbean:*</include>
+ </includes>
+ </artifactSet>
+ <filters>
+ <filter>
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+ </excludes>
+ </filter>
+ <filter>
+ <artifact>org.apache.xbean:*</artifact>
+ <excludes>
+ <exclude>META-INF/MANIFEST.MF</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
</project>
diff --git a/winegrower-core/src/main/asciidoc/index.adoc b/winegrower-core/src/main/asciidoc/index.adoc
index 156747c..7ce457b 100644
--- a/winegrower-core/src/main/asciidoc/index.adoc
+++ b/winegrower-core/src/main/asciidoc/index.adoc
@@ -29,3 +29,18 @@
TIP: you can also pass `winegrower.service.*` properties in this file.
+=== Use as Karaf framework
+
+In some cloud oriented application it can makes sense to deploy Winegrower as a Karaf framework.
+To do so:
+
+1. Customize your `config.properties` setting/adding these properties:
++
+[source,properties]
+----
+karaf.framework=winegrower
+karaf.framework.factory=org.apache.winegrower.framework.WinegrowerFramework$Factory
+karaf.framework.winegrower=mvn\:org.apache.winegrower/winegrower-core/<version>/jar/fatjar
+----
+2. Ensure `winegrower-core` fatjar is in `system/` directly of Karaf
+3. Ensure `winegrower-core` OSGi API dependencies are in `karaf/lib` (typically config admin is required or `osgi.cmpn`)
diff --git a/winegrower-core/src/main/java/org/apache/winegrower/Ripener.java b/winegrower-core/src/main/java/org/apache/winegrower/Ripener.java
index fdbeb38..69b34b2 100644
--- a/winegrower-core/src/main/java/org/apache/winegrower/Ripener.java
+++ b/winegrower-core/src/main/java/org/apache/winegrower/Ripener.java
@@ -115,6 +115,7 @@
private File workDir = new File(System.getProperty("java.io.tmpdir"), "karaf-boot_" + UUID.randomUUID().toString());
private Predicate<String> jarFilter = it -> DEFAULT_EXCLUSIONS.stream().anyMatch(it::startsWith);
+ private boolean lazyInstall;
private Collection<String> scanningIncludes;
private Collection<String> scanningExcludes;
private Collection<String> ignoredBundles = emptyList();
@@ -138,6 +139,14 @@
"pax-web-runtime",
"org.apache.aries.cdi");
+ public boolean isLazyInstall() {
+ return lazyInstall;
+ }
+
+ public void setLazyInstall(boolean lazyInstall) {
+ this.lazyInstall = lazyInstall;
+ }
+
public Collection<String> getIgnoredBundles() {
return ignoredBundles;
}
@@ -193,8 +202,65 @@
public Predicate<String> getJarFilter() {
return jarFilter;
}
- }
+ public void fromProperties(final Properties properties) {
+ ofNullable(properties.getProperty("winegrower.ripener.configuration.workdir"))
+ .map(String::valueOf)
+ .map(File::new)
+ .ifPresent(this::setWorkDir);
+ ofNullable(properties.getProperty("winegrower.ripener.configuration.lazyInstall"))
+ .map(String::valueOf)
+ .map(Boolean::parseBoolean)
+ .ifPresent(this::setLazyInstall);
+ ofNullable(properties.getProperty("winegrower.ripener.configuration.prioritizedBundles"))
+ .map(String::valueOf)
+ .filter(it -> !it.isEmpty())
+ .map(it -> asList(it.split(",")))
+ .ifPresent(this::setPrioritizedBundles);
+ ofNullable(properties.getProperty("winegrower.ripener.configuration.ignoredBundles"))
+ .map(String::valueOf)
+ .filter(it -> !it.isEmpty())
+ .map(it -> asList(it.split(",")))
+ .ifPresent(this::setIgnoredBundles);
+ ofNullable(properties.getProperty("winegrower.ripener.configuration.scanningIncludes"))
+ .map(String::valueOf)
+ .filter(it -> !it.isEmpty())
+ .map(it -> asList(it.split(",")))
+ .ifPresent(this::setScanningIncludes);
+ ofNullable(properties.getProperty("winegrower.ripener.configuration.scanningExcludes"))
+ .map(String::valueOf)
+ .filter(it -> !it.isEmpty())
+ .map(it -> asList(it.split(",")))
+ .ifPresent(this::setScanningExcludes);
+ ofNullable(properties.getProperty("winegrower.ripener.configuration.manifestContributors"))
+ .map(String::valueOf)
+ .filter(it -> !it.isEmpty())
+ .map(it -> asList(it.split(",")))
+ .ifPresent(contributors -> setManifestContributors(contributors.stream().map(clazz -> {
+ try {
+ return Thread.currentThread().getContextClassLoader().loadClass(clazz).getConstructor().newInstance();
+ } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException
+ | ClassNotFoundException e) {
+ throw new IllegalArgumentException(e);
+ } catch (final InvocationTargetException e) {
+ throw new IllegalArgumentException(e.getTargetException());
+ }
+ }).map(ManifestContributor.class::cast).collect(toList())));
+ ofNullable(properties.getProperty("winegrower.ripener.configuration.jarFilter"))
+ .map(String::valueOf)
+ .filter(it -> !it.isEmpty())
+ .ifPresent(filter -> {
+ try {
+ setJarFilter((Predicate<String>) Thread.currentThread().getContextClassLoader().loadClass(filter)
+ .getConstructor().newInstance());
+ } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
+ throw new IllegalArgumentException(e);
+ } catch (final InvocationTargetException e) {
+ throw new IllegalArgumentException(e.getTargetException());
+ }
+ });
+ }
+ }
class Impl implements Ripener {
private static final Logger LOGGER = LoggerFactory.getLogger(Ripener.class);
@@ -207,6 +273,7 @@
private final Configuration configuration;
private long startTime = -1;
+ private StandaloneScanner scanner;
public Impl(final Configuration configuration) {
this.configuration = configuration;
@@ -222,7 +289,8 @@
registerBuiltInService(EventAdmin.class, this.eventAdmin, new Hashtable<>());
registerBuiltInService(org.osgi.service.log.LoggerFactory.class, loadLoggerFactory(), new Hashtable<>());
- try (final InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("winegrower.properties")) {
+ try (final InputStream stream = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream("winegrower.properties")) {
loadConfiguration(stream);
} catch (final IOException e) {
LOGGER.warn(e.getMessage());
@@ -367,7 +435,10 @@
startTime = System.currentTimeMillis();
LOGGER.info("Starting Apache Winegrower application on {}",
LocalDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneId.systemDefault()));
- final StandaloneScanner scanner = new StandaloneScanner(configuration, registry.getFramework());
+ if (configuration.isLazyInstall()) {
+ return this;
+ }
+ final StandaloneScanner scanner = getScanner();
final AtomicLong bundleIdGenerator = new AtomicLong(1);
Stream.concat(Stream.concat(
scanner.findOSGiBundles().stream(),
@@ -382,9 +453,14 @@
.peek(OSGiBundleLifecycle::start)
.peek(it -> registry.getBundles().put(it.getBundle().getBundleId(), it))
.forEach(bundle -> LOGGER.debug("Bundle {}", bundle));
+ this.scanner = null; // we don't need it anymore since we don't support runtime install so make it gc friendly
return this;
}
+ public synchronized StandaloneScanner getScanner() {
+ return scanner == null ? scanner = new StandaloneScanner(configuration, registry.getFramework()) : scanner;
+ }
+
@Override
public synchronized void stop() {
LOGGER.info("Stopping Apache Winegrower application on {}", LocalDateTime.now());
@@ -474,6 +550,7 @@
.map(it -> configuration.getPrioritizedBundles().indexOf(it))
.orElse(-1);
}
+
}
static Ripener create(final Configuration configuration) {
@@ -484,59 +561,7 @@
static void main(final String[] args) {
final CountDownLatch latch = new CountDownLatch(1);
final Configuration configuration = new Configuration();
- ofNullable(System.getProperty("winegrower.ripener.configuration.workdir"))
- .map(String::valueOf)
- .map(File::new)
- .ifPresent(configuration::setWorkDir);
- ofNullable(System.getProperty("winegrower.ripener.configuration.prioritizedBundles"))
- .map(String::valueOf)
- .filter(it -> !it.isEmpty())
- .map(it -> asList(it.split(",")))
- .ifPresent(configuration::setPrioritizedBundles);
- ofNullable(System.getProperty("winegrower.ripener.configuration.ignoredBundles"))
- .map(String::valueOf)
- .filter(it -> !it.isEmpty())
- .map(it -> asList(it.split(",")))
- .ifPresent(configuration::setIgnoredBundles);
- ofNullable(System.getProperty("winegrower.ripener.configuration.scanningIncludes"))
- .map(String::valueOf)
- .filter(it -> !it.isEmpty())
- .map(it -> asList(it.split(",")))
- .ifPresent(configuration::setScanningIncludes);
- ofNullable(System.getProperty("winegrower.ripener.configuration.scanningExcludes"))
- .map(String::valueOf)
- .filter(it -> !it.isEmpty())
- .map(it -> asList(it.split(",")))
- .ifPresent(configuration::setScanningExcludes);
- ofNullable(System.getProperty("winegrower.ripener.configuration.manifestContributors"))
- .map(String::valueOf)
- .filter(it -> !it.isEmpty())
- .map(it -> asList(it.split(",")))
- .ifPresent(contributors -> {
- configuration.setManifestContributors(contributors.stream().map(clazz -> {
- try {
- return Thread.currentThread().getContextClassLoader().loadClass(clazz).getConstructor().newInstance();
- } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException
- | ClassNotFoundException e) {
- throw new IllegalArgumentException(e);
- } catch (final InvocationTargetException e) {
- throw new IllegalArgumentException(e.getTargetException());
- }
- }).map(ManifestContributor.class::cast).collect(toList()));
- });
- ofNullable(System.getProperty("winegrower.ripener.configuration.jarFilter"))
- .map(String::valueOf)
- .filter(it -> !it.isEmpty())
- .ifPresent(filter -> {
- try {
- configuration.setJarFilter((Predicate<String>) Thread.currentThread().getContextClassLoader().loadClass(filter)
- .getConstructor().newInstance());
- } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
- throw new IllegalArgumentException(e);
- } catch (final InvocationTargetException e) {
- throw new IllegalArgumentException(e.getTargetException());
- }
- });
+ configuration.fromProperties(System.getProperties());
final Ripener main = new Impl(configuration).start();
Runtime.getRuntime().addShutdownHook(new Thread() {
diff --git a/winegrower-core/src/main/java/org/apache/winegrower/deployer/BundleContextImpl.java b/winegrower-core/src/main/java/org/apache/winegrower/deployer/BundleContextImpl.java
index e1f3133..bfeb121 100644
--- a/winegrower-core/src/main/java/org/apache/winegrower/deployer/BundleContextImpl.java
+++ b/winegrower-core/src/main/java/org/apache/winegrower/deployer/BundleContextImpl.java
@@ -26,6 +26,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Function;
import java.util.function.Supplier;
import java.util.jar.Manifest;
import java.util.stream.Stream;
@@ -63,6 +64,8 @@
private final Collection<FrameworkListener> frameworkListeners = new CopyOnWriteArrayList<>();
private final Map<ServiceReference<?>, Object> serviceInstances = new ConcurrentHashMap<>();
+ private Function<String, Bundle> installer;
+
BundleContextImpl(final Manifest manifest, final OSGiServices services, final Supplier<Bundle> bundleSupplier,
final BundleRegistry registry) {
this.manifest = manifest;
@@ -71,6 +74,10 @@
this.registry = registry;
}
+ public void setInstaller(final Function<String, Bundle> installer) {
+ this.installer = installer;
+ }
+
public BundleRegistry getRegistry() {
return registry;
}
@@ -103,12 +110,15 @@
@Override
public Bundle installBundle(final String location, final InputStream input) throws BundleException {
+ if (installer != null) {
+ return installer.apply(location);
+ }
throw new BundleException("Unsupported operation");
}
@Override
public Bundle installBundle(final String location) throws BundleException {
- throw new BundleException("Unsupported operation");
+ return installBundle(location, null);
}
@Override
diff --git a/winegrower-core/src/main/java/org/apache/winegrower/framework/WinegrowerFramework.java b/winegrower-core/src/main/java/org/apache/winegrower/framework/WinegrowerFramework.java
new file mode 100644
index 0000000..32a2279
--- /dev/null
+++ b/winegrower-core/src/main/java/org/apache/winegrower/framework/WinegrowerFramework.java
@@ -0,0 +1,356 @@
+/**
+ * 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.framework;
+
+import org.apache.winegrower.Ripener;
+import org.apache.winegrower.deployer.BundleContextImpl;
+import org.apache.winegrower.deployer.BundleImpl;
+import org.apache.winegrower.deployer.OSGiBundleLifecycle;
+import org.apache.winegrower.scanner.StandaloneScanner;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.Version;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.launch.FrameworkFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.cert.X509Certificate;
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collector;
+import java.util.stream.Stream;
+
+import static java.util.Optional.ofNullable;
+
+public class WinegrowerFramework implements Framework {
+ private volatile int state = INSTALLED;
+
+ private final AtomicLong bundleIdGenerator = new AtomicLong(1);
+
+ private Ripener ripener;
+ private Ripener.Configuration configuration = new Ripener.Configuration();
+ private FrameworkListener[] listeners;
+ private BundleImpl frameworkBundle;
+
+ public WinegrowerFramework() {
+ configuration.setLazyInstall(true);
+ }
+
+ public void setConfiguration(final Ripener.Configuration configuration) {
+ this.configuration = configuration;
+ this.configuration.fromProperties(System.getProperties());
+ }
+
+ public void setConfigurationProperties(final Properties configuration) {
+ this.configuration.fromProperties(configuration);
+ }
+
+ @Override
+ public void init() {
+ init(new FrameworkListener[0]);
+ }
+
+ @Override
+ public void init(final FrameworkListener... listeners) {
+ ripener = Ripener.create(configuration);
+ frameworkBundle = ripener.getRegistry().getBundles().get(0L).getBundle();
+ BundleContextImpl.class.cast(getBundleContext()).setInstaller(this::installBundle);
+ this.listeners = listeners;
+ state = INSTALLED;
+ fireFrameworkEvent(null);
+ }
+
+ @Override
+ public FrameworkEvent waitForStop(final long timeout) throws InterruptedException {
+ final Clock clock = Clock.systemUTC();
+ final Instant end = clock.instant().plusMillis(timeout);
+ while (clock.instant().isBefore(end)) {
+ switch (state) {
+ case ACTIVE:
+ case RESOLVED:
+ case INSTALLED:
+ Thread.sleep(250);
+ default:
+ break;
+ }
+ }
+ return new FrameworkEvent(state, getFrameworkBundle(), null);
+ }
+
+ @Override
+ public void start() throws BundleException {
+ start(ACTIVE);
+ }
+
+ @Override
+ public int getState() {
+ return state;
+ }
+
+ @Override
+ public void start(final int options) throws BundleException {
+ state = STARTING;
+ fireFrameworkEvent(null);
+ try {
+ ripener.start();
+ state = ACTIVE;
+ fireFrameworkEvent(null);
+ } catch (final RuntimeException re) {
+ state = UNINSTALLED;
+ fireFrameworkEvent(re);
+ throw re;
+ } catch (final Exception e) {
+ state = UNINSTALLED;
+ fireFrameworkEvent(e);
+ throw new BundleException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public void stop() throws BundleException {
+ stop(STOP_TRANSIENT);
+ }
+
+ @Override
+ public void stop(final int options) throws BundleException {
+ state = STOPPING;
+ fireFrameworkEvent(null);
+ try {
+ ripener.stop();
+ state = STOP_TRANSIENT;
+ fireFrameworkEvent(null);
+ } catch (final RuntimeException re) {
+ state = UNINSTALLED;
+ fireFrameworkEvent(re);
+ throw re;
+ } catch (final Exception e) {
+ state = UNINSTALLED;
+ fireFrameworkEvent(e);
+ throw new BundleException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public void uninstall() {
+ state = Framework.UNINSTALLED;
+ final Map<Long, OSGiBundleLifecycle> bundles = ripener.getRegistry().getBundles();
+ bundles.entrySet().stream()
+ .filter(it -> it.getKey() > 0)
+ .forEach(e -> e.getValue().stop());
+ final OSGiBundleLifecycle fwk = bundles.remove(0L);
+ bundles.clear();
+ bundles.put(0L, fwk);
+ fireFrameworkEvent(null);
+ }
+
+ @Override
+ public Dictionary<String, String> getHeaders() {
+ return getHeaders(null);
+ }
+
+ @Override
+ public void update() {
+ // no-op
+ }
+
+ @Override
+ public void update(final InputStream in) {
+ // no-op
+ }
+
+ @Override
+ public long getBundleId() {
+ return 0;
+ }
+
+ @Override
+ public String getLocation() {
+ return "system";
+ }
+
+ @Override
+ public ServiceReference<?>[] getRegisteredServices() {
+ return ripener.getServices().getServices().stream()
+ .map(ServiceRegistration::getReference)
+ .toArray(ServiceReference[]::new);
+ }
+
+ @Override
+ public ServiceReference<?>[] getServicesInUse() {
+ return getRegisteredServices();
+ }
+
+ @Override
+ public boolean hasPermission(final Object permission) {
+ return true;
+ }
+
+ @Override
+ public URL getResource(final String name) {
+ return getFrameworkBundle().getResource(name);
+ }
+
+ @Override
+ public Dictionary<String, String> getHeaders(final String locale) {
+ return getFrameworkBundle().getHeaders(locale);
+ }
+
+ @Override
+ public String getSymbolicName() {
+ return getFrameworkBundle().getSymbolicName();
+ }
+
+ @Override
+ public Class<?> loadClass(final String name) throws ClassNotFoundException {
+ return getFrameworkBundle().loadClass(name);
+ }
+
+ @Override
+ public Enumeration<URL> getResources(final String name) throws IOException {
+ return getFrameworkBundle().getResources(name);
+ }
+
+ @Override
+ public Enumeration<String> getEntryPaths(final String path) {
+ return getFrameworkBundle().getEntryPaths(path);
+ }
+
+ @Override
+ public URL getEntry(final String path) {
+ return getFrameworkBundle().getEntry(path);
+ }
+
+ @Override
+ public long getLastModified() {
+ return getFrameworkBundle().getLastModified();
+ }
+
+ @Override
+ public Enumeration<URL> findEntries(final String path, final String filePattern, final boolean recurse) {
+ return getFrameworkBundle().findEntries(path, filePattern, recurse);
+ }
+
+ @Override
+ public BundleContext getBundleContext() {
+ return getFrameworkBundle().getBundleContext();
+ }
+
+ @Override
+ public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(final int signersType) {
+ return getFrameworkBundle().getSignerCertificates(signersType);
+ }
+
+ @Override
+ public Version getVersion() {
+ return getFrameworkBundle().getVersion();
+ }
+
+ @Override
+ public <A> A adapt(final Class<A> type) {
+ return getFrameworkBundle().adapt(type);
+ }
+
+ @Override
+ public File getDataFile(final String filename) {
+ return getFrameworkBundle().getDataFile(filename);
+ }
+
+ @Override
+ public int compareTo(final Bundle o) {
+ return getFrameworkBundle().compareTo(o);
+ }
+
+ private Bundle installBundle(final String location) {
+ final StandaloneScanner scanner = Ripener.Impl.class.cast(ripener).getScanner();
+ final StandaloneScanner.BundleDefinition bundleDefinition = Stream.concat(
+ scanner.findOSGiBundles().stream(),
+ scanner.findPotentialOSGiBundles().stream())
+ .filter(bundle -> doesLocationMatches(bundle, location))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("No bundle found for " + location + ", available:\n\n" +
+ scanner.findOSGiBundles() + "\n" +
+ scanner.findPotentialOSGiBundles()));
+ final OSGiBundleLifecycle lifecycle = new OSGiBundleLifecycle(
+ bundleDefinition.getManifest(), bundleDefinition.getJar(),
+ ripener.getServices(), ripener.getRegistry(), configuration,
+ bundleIdGenerator.getAndIncrement(),
+ bundleDefinition.getFiles());
+ lifecycle.start();
+ ripener.getRegistry().getBundles().put(lifecycle.getBundle().getBundleId(), lifecycle);
+ return lifecycle.getBundle();
+ }
+
+ // todo: enhance with mvn:, file:// support
+ private boolean doesLocationMatches(final StandaloneScanner.BundleDefinition bundle, final String location) {
+ if (bundle.getJar() != null) {
+ final boolean direct = location.contains(bundle.getJar().getAbsolutePath());
+ if (direct) {
+ return true;
+ }
+
+ final String normalizedName = location.replace(File.separatorChar, '/');
+ if (!normalizedName.contains("/")) {
+ return bundle.getJar().getName().equals(normalizedName);
+ }
+
+ try {
+ return bundle.getJar().toURI().toURL().toExternalForm().equals(location);
+ } catch (final MalformedURLException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private BundleImpl getFrameworkBundle() {
+ return frameworkBundle;
+ }
+
+ private void fireFrameworkEvent(final Throwable error) { // todo: shouldn't really be state
+ final FrameworkEvent event = new FrameworkEvent(error != null ? FrameworkEvent.ERROR : state, getFrameworkBundle(), error);
+ ofNullable(listeners).map(Stream::of).orElseGet(Stream::empty).forEach(l -> l.frameworkEvent(event));
+ }
+
+ public static class Factory implements FrameworkFactory {
+ @Override
+ public Framework newFramework(final Map<String, String> configuration) {
+ final WinegrowerFramework framework = new WinegrowerFramework();
+ ofNullable(configuration)
+ .map(c -> c.entrySet().stream().collect(Collector.of(
+ Properties::new,
+ (p, i) -> p.setProperty(i.getKey(), i.getValue()),
+ (p1, p2) -> {
+ p1.putAll(p2);
+ return p1;
+ })))
+ .ifPresent(framework.configuration::fromProperties);
+ return framework;
+ }
+ }
+}
diff --git a/winegrower-core/src/main/java/org/apache/winegrower/scanner/StandaloneScanner.java b/winegrower-core/src/main/java/org/apache/winegrower/scanner/StandaloneScanner.java
index 9b3cfb2..eee1829 100644
--- a/winegrower-core/src/main/java/org/apache/winegrower/scanner/StandaloneScanner.java
+++ b/winegrower-core/src/main/java/org/apache/winegrower/scanner/StandaloneScanner.java
@@ -62,6 +62,9 @@
private final Map<String, Manifest> providedManifests;
private final Map<String, List<String>> providedIndex;
+ private List<BundleDefinition> potentialBundles;
+ private List<BundleDefinition> bundles;
+
public StandaloneScanner(final Ripener.Configuration configuration, final File frameworkJar) {
this.configuration = configuration;
this.frameworkJar = frameworkJar;
@@ -152,8 +155,11 @@
}
public Collection<BundleDefinition> findPotentialOSGiBundles() {
+ if (potentialBundles != null) {
+ return potentialBundles;
+ }
final KnownJarsFilter filter = new KnownJarsFilter(configuration);
- return urls.stream()
+ return potentialBundles = urls.stream()
.map(it -> new FileAndUrl(Files.toFile(it), it))
.filter(it -> !it.file.getAbsoluteFile().equals(frameworkJar))
.filter(it -> filter.test(it.file.getName()))
@@ -179,7 +185,10 @@
}
public Collection<BundleDefinition> findOSGiBundles() {
- return Stream.concat(
+ if (bundles != null) {
+ return bundles;
+ }
+ return bundles = Stream.concat(
urls.stream()
.map(Files::toFile)
.filter(this::isIncluded)
@@ -265,6 +274,13 @@
public File getJar() {
return jar;
}
+
+ @Override
+ public String toString() {
+ return "BundleDefinition{" +
+ "jar=" + jar +
+ '}';
+ }
}
private static class FileAndUrl {
diff --git a/winegrower-core/src/test/java/org/apache/winegrower/framework/WinegrowerFrameworkTest.java b/winegrower-core/src/test/java/org/apache/winegrower/framework/WinegrowerFrameworkTest.java
new file mode 100644
index 0000000..b302d2d
--- /dev/null
+++ b/winegrower-core/src/test/java/org/apache/winegrower/framework/WinegrowerFrameworkTest.java
@@ -0,0 +1,46 @@
+/**
+ * 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.framework;
+
+import org.junit.jupiter.api.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class WinegrowerFrameworkTest {
+ @Test
+ public void run() throws BundleException {
+ final Framework framework = new WinegrowerFramework();
+ { // let this specific API not be exposed in the other parts of the test
+ WinegrowerFramework.class.cast(framework).setConfigurationProperties(new Properties() {{
+ setProperty("winegrower.ripener.configuration.scanningExcludes", "test-classes");
+ }});
+ }
+ assertEquals(Framework.INSTALLED, framework.getState());
+ framework.init();
+ assertEquals(Framework.INSTALLED, framework.getState());
+ framework.start();
+ assertEquals(Framework.ACTIVE, framework.getState());
+ final Bundle[] bundles = framework.getBundleContext().getBundles();
+ assertEquals(1, bundles.length);
+ framework.getBundleContext().installBundle("org.apache.aries.cdi.extra-1.1.0.jar");
+ assertEquals(2, framework.getBundleContext().getBundles().length);
+ framework.stop();
+ assertEquals(Framework.STOP_TRANSIENT, framework.getState());
+ }
+}