/*************************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 java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.Random; import java.util.zip.ZipInputStream; import com.thoughtworks.go.util.ReflectionUtil; import com.thoughtworks.go.util.SystemEnvironment; import com.thoughtworks.go.util.ZipUtil; import com.thoughtworks.go.plugin.api.info.PluginDescriptor; import com.thoughtworks.go.plugin.api.info.PluginDescriptorAware; import com.thoughtworks.go.plugin.infra.plugininfo.DefaultPluginRegistry; import com.thoughtworks.go.plugin.infra.plugininfo.GoPluginDescriptor; import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; import static com.thoughtworks.go.util.FileUtil.recreateDirectory; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; public class FelixGoPluginOSGiFrameworkIntegrationTest { private static final Random RANDOM = new Random(); private FelixGoPluginOSGiFramework pluginOSGiFramework; private File TMP_DIR; private File descriptorBundleDir; private File errorGeneratingDescriptorBundleDir; private File exceptionThrowingAtLoadDescriptorBundleDir; private DefaultPluginRegistry registry; @Before public void setUp() throws Exception { TMP_DIR = new File("./tmp" + RANDOM.nextFloat()); recreateDirectory(TMP_DIR); registry = new DefaultPluginRegistry(); pluginOSGiFramework = new FelixGoPluginOSGiFramework(registry, new SystemEnvironment()); pluginOSGiFramework.start(); ZipInputStream zippedOSGiBundleFile = new ZipInputStream(FileUtils.openInputStream(pathOfFileInDefaultFiles("descriptor-aware-test-plugin.osgi.jar"))); descriptorBundleDir = explodeBundleIntoDirectory(zippedOSGiBundleFile, "descriptor-plugin-bundle-dir"); zippedOSGiBundleFile = new ZipInputStream(FileUtils.openInputStream(pathOfFileInDefaultFiles("error-generating-descriptor-aware-test-plugin.osgi.jar"))); errorGeneratingDescriptorBundleDir = explodeBundleIntoDirectory(zippedOSGiBundleFile, "error-generating-descriptor-plugin-bundle-dir"); zippedOSGiBundleFile = new ZipInputStream(FileUtils.openInputStream(pathOfFileInDefaultFiles("exception-throwing-at-load-plugin.osgi.jar"))); exceptionThrowingAtLoadDescriptorBundleDir = explodeBundleIntoDirectory(zippedOSGiBundleFile, "exception-throwing-at-load-plugin-bundle-dir"); } @After public void tearDown() throws Exception { FileUtils.deleteQuietly(TMP_DIR); } @Test public void shouldLoadAValidGoPluginOSGiBundle() throws Exception { Bundle bundle = pluginOSGiFramework.loadPlugin(new GoPluginDescriptor(null, null, null, null, descriptorBundleDir, true)); assertThat(bundle.getState(), is(Bundle.ACTIVE)); BundleContext context = bundle.getBundleContext(); ServiceReference<?>[] allServiceReferences = context.getServiceReferences(PluginDescriptorAware.class.getCanonicalName(), null); assertThat(allServiceReferences.length, is(1)); try { PluginDescriptorAware service = (PluginDescriptorAware) context.getService(allServiceReferences[0]); service.setPluginDescriptor(getDescriptor()); assertThat("@Load should have been called", getIntField(service, "loadCalled"), is(1)); } catch (Exception e) { fail(String.format("setPluginDescriptor should have been called. Exception: %s", e.getMessage())); } } @Test public void shouldNotifyListenersWhenPluginLoaded() throws Exception { PluginChangeListener pluginChangeListener = mock(PluginChangeListener.class); pluginOSGiFramework.addPluginChangeListener(pluginChangeListener); GoPluginDescriptor pluginDescriptor = new GoPluginDescriptor(null, null, null, null, descriptorBundleDir, true); pluginOSGiFramework.loadPlugin(pluginDescriptor); verify(pluginChangeListener).pluginLoaded(pluginDescriptor); } @Test public void shouldNotifyListenersWhenPluginUnLoaded() throws Exception { PluginChangeListener pluginChangeListener = mock(PluginChangeListener.class); pluginOSGiFramework.addPluginChangeListener(pluginChangeListener); GoPluginDescriptor pluginDescriptor = new GoPluginDescriptor(null, null, null, null, descriptorBundleDir, true); Bundle bundle = pluginOSGiFramework.loadPlugin(pluginDescriptor); pluginDescriptor.setBundle(bundle); pluginOSGiFramework.unloadPlugin(pluginDescriptor); verify(pluginChangeListener).pluginUnLoaded(pluginDescriptor); } private PluginDescriptor getDescriptor() { return new PluginDescriptor() { @Override public String id() { return null; } @Override public String version() { return null; } @Override public About about() { return null; } }; } private int getIntField(PluginDescriptorAware service, String fieldName) { return Integer.parseInt(ReflectionUtil.getField(service, fieldName) + ""); } @Test public void shouldLoadAValidGoPluginOSGiBundleAndShouldBeDiscoverableThroughSymbolicNameFilter() throws Exception { Bundle bundle = pluginOSGiFramework.loadPlugin(new GoPluginDescriptor(null, null, null, null, descriptorBundleDir, true)); assertThat(bundle.getState(), is(Bundle.ACTIVE)); String filterBySymbolicName = String.format("(%s=%s)", Constants.BUNDLE_SYMBOLICNAME, "testplugin.descriptorValidator"); BundleContext context = bundle.getBundleContext(); ServiceReference<?>[] allServiceReferences = context.getServiceReferences(PluginDescriptorAware.class.getCanonicalName(), filterBySymbolicName); assertThat(allServiceReferences.length, is(1)); try { PluginDescriptorAware service = (PluginDescriptorAware) context.getService(allServiceReferences[0]); service.setPluginDescriptor(getDescriptor()); } catch (Exception e) { fail(String.format("setPluginDescriptor should have been called. Exception: %s", e.getMessage())); } } @Test public void shouldHandleErrorGeneratedByAValidGoPluginOSGiBundleAtUsageTime() throws Exception { Bundle bundle = pluginOSGiFramework.loadPlugin(new GoPluginDescriptor(null, null, null, null, errorGeneratingDescriptorBundleDir, true)); assertThat(bundle.getState(), is(Bundle.ACTIVE)); ActionWithReturn<PluginDescriptorAware, Object> action = new ActionWithReturn<PluginDescriptorAware, Object>() { @Override public Object execute(PluginDescriptorAware descriptorAware, GoPluginDescriptor goPluginDescriptor) { descriptorAware.setPluginDescriptor(null); return null; } }; try { pluginOSGiFramework.doOn(PluginDescriptorAware.class, "testplugin.descriptorValidator", action); fail("Should Throw An Exception"); } catch (Exception ex) { assertThat(ex.getCause() instanceof AbstractMethodError, is(true)); } } @Test public void shouldPassInCorrectDescriptorToAction() throws Exception { final GoPluginDescriptor descriptor = new GoPluginDescriptor("testplugin.descriptorValidator", null, null, null, descriptorBundleDir, true); Bundle bundle = pluginOSGiFramework.loadPlugin(descriptor); registry.loadPlugin(descriptor); assertThat(bundle.getState(), is(Bundle.ACTIVE)); ActionWithReturn<PluginDescriptorAware, Object> action = new ActionWithReturn<PluginDescriptorAware, Object>() { @Override public Object execute(PluginDescriptorAware descriptorAware, GoPluginDescriptor pluginDescriptor) { assertThat(pluginDescriptor, is(descriptor)); descriptorAware.setPluginDescriptor(pluginDescriptor); return null; } }; pluginOSGiFramework.doOn(PluginDescriptorAware.class, "testplugin.descriptorValidator", action); } @Test public void shouldUnloadALoadedPlugin() throws Exception { GoPluginDescriptor pluginDescriptor = new GoPluginDescriptor(null, null, null, null, descriptorBundleDir, true); Bundle bundle = pluginOSGiFramework.loadPlugin(pluginDescriptor); BundleContext context = bundle.getBundleContext(); ServiceReference<?>[] allServiceReferences = context.getServiceReferences(PluginDescriptorAware.class.getCanonicalName(), null); assertThat(allServiceReferences.length, is(1)); PluginDescriptorAware service = (PluginDescriptorAware) context.getService(allServiceReferences[0]); assertThat("@Load should have been called", getIntField(service, "loadCalled"), is(1)); pluginDescriptor.setBundle(bundle); pluginOSGiFramework.unloadPlugin(pluginDescriptor); assertThat(bundle.getState(), is(Bundle.UNINSTALLED)); assertThat("@UnLoad should have been called", getIntField(service, "unloadCalled"), is(1)); } @Test public void shouldMarkAPluginInvalidAnUnloadPluginIfAtLoadOfAnyExtensionPointInItFails() throws Exception { String id = "com.tw.go.exception.throwing.at.loadplugin"; GoPluginDescriptor pluginDescriptor = new GoPluginDescriptor(id, null, null, null, exceptionThrowingAtLoadDescriptorBundleDir, true); registry.loadPlugin(pluginDescriptor); assertThat(pluginDescriptor.isInvalid(), is(false)); Bundle bundle = pluginOSGiFramework.loadPlugin(pluginDescriptor); assertThat(pluginDescriptor.isInvalid(),is(true)); assertThat(bundle.getState(),is(Bundle.UNINSTALLED)); } private File pathOfFileInDefaultFiles(String filePath) { return new File(getClass().getClassLoader().getResource("defaultFiles/" + filePath).getFile()); } private File explodeBundleIntoDirectory(ZipInputStream src, String destinationDir) throws IOException, URISyntaxException { File destinationPluginBundleLocation = new File(TMP_DIR, destinationDir); destinationPluginBundleLocation.mkdirs(); new ZipUtil().unzip(src, destinationPluginBundleLocation); return destinationPluginBundleLocation; } }