blob: 63c9015961a3aead112d2ef22c19c8dcfdbd1bc0 [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.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(!Boolean.getBoolean("winegrower.framework.lazyInstall.skip"));
}
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;
}
}
}