/** * Copyright (c) 2016, All Contributors (see CONTRIBUTORS file) * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.eventsourcing; import com.google.common.base.Joiner; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.ServiceRegistration; import org.osgi.framework.wiring.BundleWiring; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.util.tracker.BundleTracker; import org.osgi.util.tracker.BundleTrackerCustomizer; import java.lang.reflect.Modifier; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; /** * Provides an event and command sets from OSGi bundles */ @Component(immediate = true) public class OSGiEntitiesProvider { private BundleTracker<Collection<ServiceRegistration>> tracker; @Activate protected void activate(ComponentContext context) { tracker = new BundleTracker<>(context.getBundleContext(), Bundle.ACTIVE, new ScannerBundleTracker(context.getBundleContext())); tracker.open(); } @Deactivate protected void deactivate() { tracker.close(); } private static class BundleCommandEventSetProvider implements CommandSetProvider, EventSetProvider { private final List<Class<? extends Command>> commands; private final List<Class<? extends Event>> events; public BundleCommandEventSetProvider(Bundle bundle) { commands = findSubTypesOf(bundle, Collections.singleton(Command.class)); events = findSubTypesOf(bundle, Collections.singleton(Event.class)); } private <T> List<Class<? extends T>> findSubTypesOf(Bundle bundle, Collection<Class<T>> superclasses) { BundleWiring wiring = bundle.adapt(BundleWiring.class); Collection<String> names = wiring .listResources("/", "*", BundleWiring.LISTRESOURCES_RECURSE); return names.stream().map(new Function<String, Class<?>>() { @Override @SneakyThrows public Class<?> apply(String name) { String n = name.replaceAll("\\.class$", "").replace('/', '.'); try { return bundle.loadClass(n); } catch (ClassNotFoundException | NoClassDefFoundError e) { return null; } } }).filter(c -> c != null).filter(c -> superclasses.stream().anyMatch(sc -> sc.isAssignableFrom(c))) .filter(c -> !c.isInterface() && !Modifier.isAbstract(c.getModifiers())) .map((Function<Class<?>, Class<? extends T>>) aClass -> (Class<? extends T>) aClass) .collect(Collectors.toList()); } @Override public Set<Class<? extends Event>> getEvents() { return new HashSet<>(events); } @Override public Set<Class<? extends Command>> getCommands() { return new HashSet<>(commands); } } @Slf4j private static class ScannerBundleTracker implements BundleTrackerCustomizer<Collection<ServiceRegistration>> { private final BundleContext bundleContext; public ScannerBundleTracker(BundleContext bundleContext) { this.bundleContext = bundleContext; } @Override public Collection<ServiceRegistration> addingBundle(Bundle bundle, BundleEvent event) { BundleCommandEventSetProvider bundleCommandEventSetProvider = new BundleCommandEventSetProvider(bundle); ArrayList<ServiceRegistration> registrations = new ArrayList<>(); Hashtable<String, Object> properties = new Hashtable<>(); properties.put("bundle", bundle.getSymbolicName()); if (!bundleCommandEventSetProvider.getCommands().isEmpty()) { properties.put("commands", Joiner.on(", ").join( bundleCommandEventSetProvider.getCommands().stream().map(Class::getName).collect(Collectors.toList()))); log.info("Registering a service for providing commands in {}", bundle.getSymbolicName()); registrations.add(bundleContext.registerService(CommandSetProvider.class, bundleCommandEventSetProvider, properties)); } if (!bundleCommandEventSetProvider.getEvents().isEmpty()) { properties.put("events", Joiner.on(", ").join( bundleCommandEventSetProvider.getEvents().stream().map(Class::getName).collect(Collectors.toList()))); log.info("Registering a service for providing events in {}", bundle.getSymbolicName()); registrations.add(bundleContext.registerService(EventSetProvider.class, bundleCommandEventSetProvider, properties)); } return registrations; } @Override public void modifiedBundle(Bundle bundle, BundleEvent event, Collection<ServiceRegistration> registrations) { } @Override public void removedBundle(Bundle bundle, BundleEvent event, Collection<ServiceRegistration> registrations) { if (!registrations.isEmpty()) { log.info("Deregistering services for providing commands and events in {}", bundle.getSymbolicName()); registrations.forEach(ServiceRegistration::unregister); } } } }