/*************************GO-LICENSE-START********************************* * Copyright 2014 ThoughtWorks, Inc. * * 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 * * 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. *************************GO-LICENSE-END***********************************/ package com.thoughtworks.go.plugin.infra; import com.thoughtworks.go.plugin.api.GoPluginApiMarker; import com.thoughtworks.go.plugin.infra.plugininfo.GoPluginDescriptor; import com.thoughtworks.go.plugin.infra.plugininfo.PluginRegistry; import com.thoughtworks.go.plugin.infra.service.DefaultPluginHealthService; import com.thoughtworks.go.plugin.infra.service.DefaultPluginLoggingService; import com.thoughtworks.go.plugin.internal.api.LoggingService; import com.thoughtworks.go.plugin.internal.api.PluginHealthService; import com.thoughtworks.go.util.SystemEnvironment; import org.apache.commons.collections.Closure; import org.apache.commons.collections.IteratorUtils; import org.apache.felix.framework.cache.BundleCache; import org.apache.felix.framework.util.FelixConstants; import org.apache.log4j.Logger; import org.osgi.framework.*; import org.osgi.framework.launch.Framework; import org.osgi.framework.launch.FrameworkFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.File; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.ServiceLoader; import java.util.concurrent.ConcurrentLinkedQueue; import static java.util.Arrays.asList; import static org.apache.commons.collections.CollectionUtils.forAllDo; @Component public class FelixGoPluginOSGiFramework implements GoPluginOSGiFramework { private static Logger LOGGER = Logger.getLogger(FelixGoPluginOSGiFramework.class); private final PluginRegistry registry; private Framework framework; private SystemEnvironment systemEnvironment; private Collection<PluginChangeListener> pluginChangeListeners = new ConcurrentLinkedQueue<>(); @Autowired public FelixGoPluginOSGiFramework(PluginRegistry registry, SystemEnvironment systemEnvironment) { this.registry = registry; this.systemEnvironment = systemEnvironment; } @Override public void start() { List<FrameworkFactory> frameworkFactories = IteratorUtils.toList(ServiceLoader.load(FrameworkFactory.class).iterator()); if (frameworkFactories.size() != 1) { throw new RuntimeException("One OSGi framework expected. Got " + frameworkFactories.size() + ": " + frameworkFactories); } try { framework = getFelixFramework(frameworkFactories); framework.start(); registerInternalServices(framework.getBundleContext()); } catch (BundleException e) { throw new RuntimeException("Failed to initialize OSGi framework", e); } } @Override public void stop() { if (framework == null) { return; } try { framework.stop(); } catch (BundleException e) { throw new RuntimeException(e); } registry.clear(); } @Override public Bundle loadPlugin(GoPluginDescriptor pluginDescriptor) { File bundleLocation = pluginDescriptor.bundleLocation(); return getBundle(pluginDescriptor, bundleLocation); } private Bundle getBundle(GoPluginDescriptor pluginDescriptor, File bundleLocation) { Bundle bundle =null; try { bundle = framework.getBundleContext().installBundle("reference:" + bundleLocation.toURI()); pluginDescriptor.setBundle(bundle); bundle.start(); if(pluginDescriptor.isInvalid()){ handlePluginInvalidation(pluginDescriptor, bundleLocation, bundle); return bundle; } forAllDo(pluginChangeListeners, notifyPluginLoadedEvent(pluginDescriptor)); return bundle; } catch (Exception e) { pluginDescriptor.markAsInvalid(asList(e.getMessage()), e); LOGGER.error("Failed to load plugin: " + bundleLocation,e); stopAndUninstallBundle(bundle, bundleLocation); throw new RuntimeException("Failed to load plugin: " + bundleLocation, e); } } private void handlePluginInvalidation(GoPluginDescriptor pluginDescriptor, File bundleLocation, Bundle bundle) { String failureMsg = String.format("Failed to load plugin: %s. Plugin is invalid. Reasons %s", bundleLocation, pluginDescriptor.getStatus().getMessages()); LOGGER.error(failureMsg); stopAndUninstallBundle(bundle, bundleLocation); } private void stopAndUninstallBundle(Bundle bundle, File bundleLocation) { if(bundle!=null){ try { bundle.stop(); bundle.uninstall(); } catch (BundleException e) { String stopFailMsg = "Failed to stop/uninstall bundle: " + bundleLocation; LOGGER.error(stopFailMsg,e); throw new RuntimeException(stopFailMsg, e); } } } @Override public void unloadPlugin(GoPluginDescriptor pluginDescriptor) { Bundle bundle = pluginDescriptor.bundle(); if (bundle == null) { return; } try { bundle.stop(); bundle.uninstall(); forAllDo(pluginChangeListeners, notifyPluginUnLoadedEvent(pluginDescriptor)); } catch (BundleException e) { throw new RuntimeException("Failed to unload plugin: " + bundle, e); } } @Override public void addPluginChangeListener(PluginChangeListener pluginChangeListener) { pluginChangeListeners.add(pluginChangeListener); } private void registerInternalServices(BundleContext bundleContext) { bundleContext.registerService(PluginHealthService.class, new DefaultPluginHealthService(registry), null); bundleContext.registerService(LoggingService.class, new DefaultPluginLoggingService(systemEnvironment), null); } Framework getFelixFramework(List<FrameworkFactory> frameworkFactories) { return frameworkFactories.get(0).newFramework(generateOSGiFrameworkConfig()); } private HashMap<String, String> generateOSGiFrameworkConfig() { String osgiFrameworkPackage = Bundle.class.getPackage().getName(); String goPluginApiPackage = GoPluginApiMarker.class.getPackage().getName(); String subPackagesOfGoPluginApiPackage = goPluginApiPackage + ".*"; String internalServicesPackage = PluginHealthService.class.getPackage().getName(); String javaxPackages = "javax.*"; String orgXmlSaxPackages = "org.xml.sax, org.xml.sax.*"; String orgW3cDomPackages = "org.w3c.dom, org.w3c.dom.*"; HashMap<String, String> config = new HashMap<>(); config.put(Constants.FRAMEWORK_BUNDLE_PARENT, Constants.FRAMEWORK_BUNDLE_PARENT_FRAMEWORK); config.put(Constants.FRAMEWORK_BOOTDELEGATION, osgiFrameworkPackage + ", " + goPluginApiPackage + ", " + subPackagesOfGoPluginApiPackage + ", " + internalServicesPackage + ", " + javaxPackages + ", " + orgXmlSaxPackages + ", " + orgW3cDomPackages); config.put(Constants.FRAMEWORK_STORAGE_CLEAN, "onFirstInit"); config.put(BundleCache.CACHE_LOCKING_PROP, "false"); config.put(FelixConstants.SERVICE_URLHANDLERS_PROP, "false"); return config; } @Override public <T> void doOnAll(Class<T> serviceReferenceClass, Action<T> actionToDoOnEachRegisteredServiceWhichMatches) { doOnAllWithExceptionHandling(serviceReferenceClass, actionToDoOnEachRegisteredServiceWhichMatches, new ExceptionHandler<T>() { @Override public void handleException(T obj, Throwable t) { throw new RuntimeException(t.getMessage(), t); } }); } @Override public <T> void doOnAllWithExceptionHandling(Class<T> serviceReferenceClass, Action<T> actionToDoOnEachRegisteredServiceWhichMatches, ExceptionHandler<T> handler) { if (framework == null) { LOGGER.warn("[Plugin Framework] Plugins are not enabled, so cannot do an action on all implementations of " + serviceReferenceClass); return; } BundleContext bundleContext = framework.getBundleContext(); Collection<ServiceReference<T>> matchingServiceReferences; try { matchingServiceReferences = bundleContext.getServiceReferences(serviceReferenceClass, null); } catch (InvalidSyntaxException e) { throw new RuntimeException(e); } for (ServiceReference<T> currentServiceReference : matchingServiceReferences) { T service = bundleContext.getService(currentServiceReference); GoPluginDescriptor descriptor = getDescriptorFor(currentServiceReference); try { actionToDoOnEachRegisteredServiceWhichMatches.execute(service, descriptor); } catch (Throwable t) { handler.handleException(service, t); } } } private <T> GoPluginDescriptor getDescriptorFor(ServiceReference<T> serviceReference) { String symbolicName = serviceReference.getBundle().getSymbolicName(); return registry.getPlugin(symbolicName); } @Override public <T, R> R doOn(Class<T> serviceReferenceClass, String pluginId, ActionWithReturn<T, R> action) { if (framework == null) { LOGGER.warn("[Plugin Framework] Plugins are not enabled, so cannot do an action on all implementations of " + serviceReferenceClass); return null; } BundleContext bundleContext = framework.getBundleContext(); Collection<ServiceReference<T>> matchingServiceReferences = findServiceReferenceWithPluginId(serviceReferenceClass, pluginId, bundleContext); ServiceReference<T> serviceReference = validateAndGetTheOnlyReferenceWithGivenSymbolicName(matchingServiceReferences, serviceReferenceClass, pluginId); T service = bundleContext.getService(serviceReference); return executeActionOnTheService(action, service, getDescriptorFor(serviceReference)); } @Override public <T> void doOn(Class<T> serviceReferenceClass, String pluginId, Action<T> action) { doOnWithExceptionHandling(serviceReferenceClass, pluginId, action, null); } @Override public <T> void doOnWithExceptionHandling(Class<T> serviceReferenceClass, String pluginId, Action<T> action, ExceptionHandler<T> handler) { if (framework == null) { LOGGER.warn("[Plugin Framework] Plugins are not enabled, so cannot do an action on all implementations of " + serviceReferenceClass); return; } BundleContext bundleContext = framework.getBundleContext(); Collection<ServiceReference<T>> matchingServiceReferences = findServiceReferenceWithPluginId(serviceReferenceClass, pluginId, bundleContext); ServiceReference<T> serviceReference = validateAndGetTheOnlyReferenceWithGivenSymbolicName(matchingServiceReferences, serviceReferenceClass, pluginId); T service = bundleContext.getService(serviceReference); executeActionOnTheService(action, service, getDescriptorFor(serviceReference), handler); } @Override public <T> void doOnAllForPlugin(Class<T> serviceReferenceClass, String pluginId, Action<T> action) { doOnAllWithExceptionHandlingForPlugin(serviceReferenceClass, pluginId, action, null); } @Override public <T> void doOnAllWithExceptionHandlingForPlugin(Class<T> serviceReferenceClass, String pluginId, Action<T> action, ExceptionHandler<T> handler) { if (framework == null) { LOGGER.warn("[Plugin Framework] Plugins are not enabled, so cannot do an action on all implementations of " + serviceReferenceClass); return; } BundleContext bundleContext = framework.getBundleContext(); Collection<ServiceReference<T>> matchingServiceReferences = findServiceReferenceWithPluginId(serviceReferenceClass, pluginId, bundleContext); for (ServiceReference<T> serviceReference : matchingServiceReferences) { T service = bundleContext.getService(serviceReference); executeActionOnTheService(action, service, getDescriptorFor(serviceReference), handler); } } @Override public <T> boolean hasReferenceFor(Class<T> serviceReferenceClass, String pluginId) { if (framework == null) { LOGGER.warn("[Plugin Framework] Plugins are not enabled, so cannot do an action on all implementations of " + serviceReferenceClass); return false; } BundleContext bundleContext = framework.getBundleContext(); Collection<ServiceReference<T>> matchingServiceReferences = findServiceReferenceWithPluginId(serviceReferenceClass, pluginId, bundleContext); return !matchingServiceReferences.isEmpty(); } private <T> void executeActionOnTheService(Action<T> action, T service, GoPluginDescriptor goPluginDescriptor, ExceptionHandler<T> handler) { try { action.execute(service, goPluginDescriptor); } catch (Throwable t) { if (handler != null) { handler.handleException(service, t); } else { throw new RuntimeException(t.getMessage(), t); } } } private <T, R> R executeActionOnTheService(ActionWithReturn<T, R> action, T service, GoPluginDescriptor goPluginDescriptor) { try { return action.execute(service, goPluginDescriptor); } catch (Throwable t) { throw new RuntimeException(t.getMessage(), t); } } private <T> Collection<ServiceReference<T>> findServiceReferenceWithPluginId(Class<T> serviceReferenceClass, String pluginId, BundleContext bundleContext) { String filterBySymbolicName = String.format("(%s=%s)", Constants.BUNDLE_SYMBOLICNAME, pluginId); Collection<ServiceReference<T>> matchingServiceReferences; try { matchingServiceReferences = bundleContext.getServiceReferences(serviceReferenceClass, filterBySymbolicName); } catch (InvalidSyntaxException e) { String message = String.format("Failed To find reference for Service Reference %s and Filter %s", serviceReferenceClass, filterBySymbolicName); throw new GoPluginFrameworkException(message, e); } return matchingServiceReferences; } private <T> ServiceReference<T> validateAndGetTheOnlyReferenceWithGivenSymbolicName(Collection<ServiceReference<T>> matchingServiceReferences, Class<T> serviceReference, String pluginId) { if (matchingServiceReferences.isEmpty()) { throw new GoPluginFrameworkException(String.format("No reference found for the given Service Reference: %s and Plugin Id %s. It is likely that the plugin is missing.", serviceReference.getCanonicalName(), pluginId)); } if (matchingServiceReferences.size() > 1) { throw new GoPluginFrameworkException(String.format("More than one reference found for the given " + "Service Reference: %s and Plugin Id %s; References: %s", serviceReference.getCanonicalName(), pluginId, matchingServiceReferences)); } return matchingServiceReferences.iterator().next(); } private Closure notifyPluginLoadedEvent(final GoPluginDescriptor pluginDescriptor) { return new Closure() { @Override public void execute(Object o) { PluginChangeListener pluginChangeListener = (PluginChangeListener) o; pluginChangeListener.pluginLoaded(pluginDescriptor); } }; } private Closure notifyPluginUnLoadedEvent(final GoPluginDescriptor pluginDescriptor) { return new Closure() { @Override public void execute(Object o) { PluginChangeListener pluginChangeListener = (PluginChangeListener) o; pluginChangeListener.pluginUnLoaded(pluginDescriptor); } }; } }