killbill-aplcache

Initial version of OSGI beatrix test relying on osgi-bundles/test

2/16/2013 12:52:23 AM

Details

.gitignore 3(+2 -1)

diff --git a/.gitignore b/.gitignore
index 6fa2692..ba3098e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,4 +16,5 @@ logs/
 *.project
 */bin/
 catalog/src/test/resources/CatalogSchema.xsd
-*/test-output/
\ No newline at end of file
+*/test-output/
+beatrix/src/test/resources/killbill-osgi-bundles-test-*-jar-with-dependencies.jar

beatrix/pom.xml 4(+4 -0)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 5ccf002..e5c159e 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -57,6 +57,10 @@
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-osgi-bundles-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
             <artifactId>killbill-tenant</artifactId>
         </dependency>
         <dependency>
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixIntegrationModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixIntegrationModule.java
index 35acacf..7399ddf 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixIntegrationModule.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixIntegrationModule.java
@@ -18,6 +18,7 @@ package com.ning.billing.beatrix.integration;
 
 import java.io.IOException;
 import java.net.URL;
+import java.util.Properties;
 import java.util.Set;
 
 import com.ning.billing.GuicyKillbillTestWithEmbeddedDBModule;
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/osgi/TestBasicOSGIWithTestBundle.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/osgi/TestBasicOSGIWithTestBundle.java
new file mode 100644
index 0000000..65e07e1
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/osgi/TestBasicOSGIWithTestBundle.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.
+ */
+
+package com.ning.billing.beatrix.integration.osgi;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.channels.FileChannel;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Query;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.osgi.api.OSGIServiceRegistration;
+import com.ning.billing.osgi.api.config.PluginConfig.PluginType;
+import com.ning.billing.osgi.api.config.PluginJavaConfig;
+import com.ning.billing.payment.plugin.api.PaymentInfoPlugin;
+import com.ning.billing.payment.plugin.api.PaymentPluginApi;
+import com.ning.billing.util.config.OSGIConfig;
+
+import com.google.common.io.Resources;
+
+import static com.jayway.awaitility.Awaitility.await;
+
+/**
+ * Basic OSGI test that relies on the 'test' bundle (com.ning.billing.osgi.bundles.test.TestActivator)
+ * <p/>
+ * The test checks that the bundle:
+ * - gets started
+ * - can make API call
+ * - can listen to KB events
+ * - can register a service (PaymentPluginApi) that this test calls
+ * - can write in the DB using the DataSource (this is how the assertion work)
+ */
+public class TestBasicOSGIWithTestBundle extends TestOSGIBase {
+
+    private final String BUNDLE_TEST_RESOURCE = "killbill-osgi-bundles-test";
+
+    @Inject
+    private OSGIServiceRegistration<PaymentPluginApi> paymentPluginApiOSGIServiceRegistration;
+
+    @BeforeClass(groups = "slow")
+    public void setup() throws Exception {
+
+        super.setup();
+
+        // This is extracted from surefire system configuration-- needs to be added explicitely in IntelliJ for correct running
+        final String killbillVersion = System.getProperty("killbill.version");
+        SetupBundleWithAssertion setupTest = new SetupBundleWithAssertion(BUNDLE_TEST_RESOURCE, config, killbillVersion);
+        setupTest.setupBundle();
+
+    }
+
+    @Test(groups = "slow")
+    public void testBundleTest() throws Exception {
+
+        // At this point test bundle should have been started already
+        final TestActivatorWithAssertion assertTor = new TestActivatorWithAssertion(getDBI());
+        assertTor.assertPluginInitialized();
+
+        // Create an account and expect test bundle listen to KB events and write the external name in its table
+        final Account account = createAccountWithPaymentMethod(getAccountData(1));
+        assertTor.assertPluginReceievdAccountCreationEvent(account.getExternalKey());
+
+        // Retrieve the PaymentPluginApi that the test bundle registered
+        final PaymentPluginApi paymentPluginApi = getTestPluginPaymentApi();
+
+        // Make a payment and expect test bundle to correcly write in its table the input values
+        final UUID paymentId = UUID.randomUUID();
+        final BigDecimal paymentAmount = new BigDecimal("14.32");
+        final PaymentInfoPlugin r = paymentPluginApi.processPayment(paymentId, account.getPaymentMethodId(), paymentAmount, callContext);
+        assertTor.assertPluginCreatedPayment(paymentId, account.getPaymentMethodId(), paymentAmount);
+    }
+
+    private static final class SetupBundleWithAssertion {
+
+        private final String bundleName;
+        private final OSGIConfig config;
+        private final String killbillVersion;
+
+        public SetupBundleWithAssertion(final String bundleName, final OSGIConfig config, final String killbillVersion) {
+            this.bundleName = bundleName;
+            this.config = config;
+            this.killbillVersion = killbillVersion;
+        }
+
+        public void setupBundle() {
+
+            try {
+                // Retrieve PluginConfig info from classpath
+                // test bundle should have been exported under Beatrix resource by the maven maven-dependency-plugin
+                final PluginJavaConfig pluginConfig = extractBundleTestResource();
+                Assert.assertNotNull(pluginConfig);
+
+                // Create OSGI install bundle directory
+                setupDirectoryStructure(pluginConfig);
+
+                // Copy the jar
+                copyFile(new File(pluginConfig.getBundleJarPath()), new File(pluginConfig.getPluginVersionRoot().getAbsolutePath(), pluginConfig.getPluginVersionnedName() + ".jar"));
+
+                // Create the config file
+                createConfigFile(pluginConfig);
+
+            } catch (IOException e) {
+                Assert.fail(e.getMessage());
+            }
+        }
+
+        private void createConfigFile(final PluginJavaConfig pluginConfig) throws IOException {
+
+            PrintStream printStream = null;
+            try {
+                final File configFile = new File(pluginConfig.getPluginVersionRoot(), config.getOSGIKillbillPropertyName());
+                configFile.createNewFile();
+                printStream = new PrintStream(new FileOutputStream(configFile));
+                printStream.print("pluginType=" + PluginType.NOTIFICATION);
+            } finally {
+                if (printStream != null) {
+                    printStream.close();
+                }
+            }
+        }
+
+        private void setupDirectoryStructure(final PluginJavaConfig pluginConfig) {
+
+            final File rootDir  = new File(config.getRootInstallationDir());
+            if (rootDir.exists()) {
+                deleteDirectory(rootDir, false);
+            }
+            pluginConfig.getPluginVersionRoot().mkdirs();
+        }
+
+        private static void deleteDirectory(final File path, final boolean deleteParent) {
+            if (path == null) {
+                return;
+            }
+
+            if (path.exists()) {
+                final File[] files = path.listFiles();
+                if (files != null) {
+                    for (final File f : files) {
+                        if (f.isDirectory()) {
+                            deleteDirectory(f, true);
+                        }
+                        f.delete();
+                    }
+                }
+
+                if (deleteParent) {
+                    path.delete();
+                }
+            }
+        }
+
+
+
+        private PluginJavaConfig extractBundleTestResource() {
+
+            final String resourceName = bundleName + "-" + killbillVersion + "-jar-with-dependencies.jar";
+            final URL resourceUrl = Resources.getResource(resourceName);
+            if (resourceUrl != null) {
+                final String[] parts = resourceUrl.getPath().split("/");
+                final String lastPart = parts[parts.length - 1];
+                if (lastPart.startsWith(bundleName)) {
+                    return createPluginConfig(resourceUrl.getPath(), lastPart);
+                }
+            }
+            return null;
+
+        }
+
+        private PluginJavaConfig createPluginConfig(final String bundleTestResourcePath, final String fileName) {
+
+            return new PluginJavaConfig() {
+                @Override
+                public String getBundleJarPath() {
+                    return bundleTestResourcePath;
+                }
+
+                @Override
+                public String getPluginName() {
+                    return bundleName;
+                }
+
+                @Override
+                public PluginType getPluginType() {
+                    return PluginType.PAYMENT;
+                }
+
+                @Override
+                public String getVersion() {
+                    return killbillVersion;
+                }
+
+                @Override
+                public String getPluginVersionnedName() {
+                    return bundleName + "-" + killbillVersion;
+                }
+
+                @Override
+                public File getPluginVersionRoot() {
+                    final StringBuilder tmp = new StringBuilder(config.getRootInstallationDir());
+                    tmp.append("/")
+                       .append(PluginLanguage.JAVA.toString().toLowerCase())
+                       .append("/")
+                       .append(bundleName)
+                       .append("/")
+                       .append(killbillVersion);
+                    final File result = new File(tmp.toString());
+                    return result;
+                }
+
+                @Override
+                public PluginLanguage getPluginLanguage() {
+                    return PluginLanguage.JAVA;
+                }
+            };
+        }
+
+        public static void copyFile(File sourceFile, File destFile) throws IOException {
+            if (!destFile.exists()) {
+                destFile.createNewFile();
+            }
+
+            FileChannel source = null;
+            FileChannel destination = null;
+
+            try {
+                source = new FileInputStream(sourceFile).getChannel();
+                destination = new FileOutputStream(destFile).getChannel();
+                destination.transferFrom(source, 0, source.size());
+            } finally {
+                if (source != null) {
+                    source.close();
+                }
+                if (destination != null) {
+                    destination.close();
+                }
+            }
+        }
+
+    }
+
+
+    private PaymentPluginApi getTestPluginPaymentApi() {
+        PaymentPluginApi result = paymentPluginApiOSGIServiceRegistration.getServiceForPluginName("test");
+        Assert.assertNotNull(result);
+        return result;
+    }
+
+    private static final class TestActivatorWithAssertion {
+
+        private final IDBI dbi;
+
+        public TestActivatorWithAssertion(final IDBI dbi) {
+            this.dbi = dbi;
+        }
+
+        public void assertPluginInitialized() {
+            assertWithCallback(new AwaitCallback() {
+                @Override
+                public boolean isSuccess() {
+                    return isPluginInitialized();
+                }
+            }, "Plugin did not complete initialization");
+        }
+
+        public void assertPluginReceievdAccountCreationEvent(final String expectedExternalKey) {
+            assertWithCallback(new AwaitCallback() {
+                @Override
+                public boolean isSuccess() {
+                    return isValidAccountExternalKey(expectedExternalKey);
+                }
+            }, "Plugin did not receive account creation event");
+        }
+
+        public void assertPluginCreatedPayment(final UUID expectedPaymentId, final UUID expectedPaymentMethodId, final BigDecimal expectedAmount) {
+            assertWithCallback(new AwaitCallback() {
+                @Override
+                public boolean isSuccess() {
+                    return isValidPayment(expectedPaymentId, expectedPaymentMethodId, expectedAmount);
+                }
+            }, "Plugin did not create the payment");
+        }
+
+
+        private void assertWithCallback(final AwaitCallback callback, final String error) {
+            try {
+                await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() throws Exception {
+                        return callback.isSuccess();
+                    }
+                });
+            } catch (Exception e) {
+                Assert.fail(error, e);
+            }
+        }
+
+        private boolean isValidPayment(final UUID expectedPaymentId, final UUID expectedPaymentMethodId, final BigDecimal expectedAmount) {
+            TestModel test = getTestModelFirstRecord();
+            return expectedPaymentId.equals(test.getPaymentId()) &&
+                   expectedPaymentMethodId.equals(test.getPaymentMethodId()) &&
+                   expectedAmount.compareTo(test.getAmount()) == 0;
+        }
+
+
+        private boolean isPluginInitialized() {
+            TestModel test = getTestModelFirstRecord();
+            return test.isStarted();
+        }
+
+        private boolean isValidAccountExternalKey(final String expectedExternalKey) {
+            TestModel test = getTestModelFirstRecord();
+            return expectedExternalKey.equals(test.getAccountExternalKey());
+        }
+
+        private TestModel getTestModelFirstRecord() {
+            TestModel test = dbi.inTransaction(new TransactionCallback<TestModel>() {
+                @Override
+                public TestModel inTransaction(final Handle conn, final TransactionStatus status) throws Exception {
+                    Query<Map<String, Object>> q = conn.createQuery("SELECT is_started, external_key, payment_id, payment_method_id, payment_amount FROM test_bundle WHERE record_id = 1;");
+                    TestModel test = q.map(new TestMapper()).first();
+                    return test;
+                }
+            });
+            return test;
+        }
+    }
+
+
+    private final static class TestModel {
+
+        private final Boolean isStarted;
+        private final String accountExternalKey;
+        private final UUID paymentId;
+        private final UUID paymentMethodId;
+        private final BigDecimal amount;
+
+        private TestModel(final Boolean started, final String accountExternalKey, final UUID paymentId, final UUID paymentMethodId, final BigDecimal amount) {
+            isStarted = started;
+            this.accountExternalKey = accountExternalKey;
+            this.paymentId = paymentId;
+            this.paymentMethodId = paymentMethodId;
+            this.amount = amount;
+        }
+
+        public Boolean isStarted() {
+            return isStarted;
+        }
+
+        public String getAccountExternalKey() {
+            return accountExternalKey;
+        }
+
+        public UUID getPaymentId() {
+            return paymentId;
+        }
+
+        public UUID getPaymentMethodId() {
+            return paymentMethodId;
+        }
+
+        public BigDecimal getAmount() {
+            return amount;
+        }
+
+    }
+
+
+    private static class TestMapper implements ResultSetMapper<TestModel> {
+
+        @Override
+        public TestModel map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+
+            final Boolean isStarted = r.getBoolean("is_started");
+            final String externalKey = r.getString("external_key");
+            final UUID paymentId = r.getString("payment_id") != null ? UUID.fromString(r.getString("payment_id")) : null;
+            final UUID paymentMethodId = r.getString("payment_method_id") != null ? UUID.fromString(r.getString("payment_method_id")) : null;
+            final BigDecimal amount = r.getBigDecimal("payment_amount");
+            return new TestModel(isStarted, externalKey, paymentId, paymentMethodId, amount);
+        }
+    }
+
+    private interface AwaitCallback {
+        boolean isSuccess();
+    }
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/DefaultOSGIService.java b/osgi/src/main/java/com/ning/billing/osgi/DefaultOSGIService.java
index 49d1289..c6f503b 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/DefaultOSGIService.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/DefaultOSGIService.java
@@ -195,7 +195,7 @@ public class DefaultOSGIService implements OSGIService {
     }
 
     private void pruneOSGICache() {
-        final String path = osgiConfig.getOSGIBundleRootDir() + "/" + osgiConfig.getOSGIBundleCacheName();
+        final String path = osgiConfig.getOSGIBundleRootDir();
         deleteUnderDirectory(new File(path));
     }
 
diff --git a/osgi/src/main/java/com/ning/billing/osgi/pluginconf/PluginFinder.java b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/PluginFinder.java
index acdba18..fd4963a 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/pluginconf/PluginFinder.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/PluginFinder.java
@@ -41,7 +41,6 @@ import com.ning.billing.util.config.OSGIConfig;
 
 public class PluginFinder {
 
-    private static final String INSTALATION_PROPERTIES = "killbill.properties";
 
     private final Logger logger = LoggerFactory.getLogger(PluginFinder.class);
 
@@ -168,7 +167,7 @@ public class PluginFinder {
             }
 
             for (final File cur : files) {
-                if (cur.isFile() && cur.getName().equals(INSTALATION_PROPERTIES)) {
+                if (cur.isFile() && cur.getName().equals(osgiConfig.getOSGIKillbillPropertyName())) {
                     props = readPluginConfigurationFile(cur);
                 }
                 if (props != null) {
diff --git a/osgi-bundles/pom.xml b/osgi-bundles/pom.xml
index 3e1cb4e..797ba11 100644
--- a/osgi-bundles/pom.xml
+++ b/osgi-bundles/pom.xml
@@ -30,5 +30,6 @@
         <module>hello</module>
         <module>jruby</module>
         <module>meter</module>
+        <module>test</module>
     </modules>
 </project>
diff --git a/osgi-bundles/test/pom.xml b/osgi-bundles/test/pom.xml
new file mode 100644
index 0000000..0f6f665
--- /dev/null
+++ b/osgi-bundles/test/pom.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2010-2013 Ning, Inc.
+  ~
+  ~ Ning licenses this file to you 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.
+  -->
+
+<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>
+    <parent>
+        <groupId>com.ning.billing</groupId>
+        <artifactId>killbill-osgi-bundles</artifactId>
+        <version>0.1.54-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>killbill-osgi-bundles-test</artifactId>
+    <name>Killbill billing platform: OSGI Test bundle</name>
+    <packaging>bundle</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>osgi-over-slf4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jdbi</groupId>
+            <artifactId>jdbi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>com.ning.billing.osgi.bundles.test.TestActivator</Bundle-Activator>
+                        <Import-Package>
+                            *;resolution:=optional
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>process-test-classes</phase>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>assemble-killbill-osgi-bundles-test</id>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <phase>package</phase>
+                        <configuration>
+                            <shadedArtifactAttached>true</shadedArtifactAttached>
+                            <shadedClassifierName>jar-with-dependencies</shadedClassifierName>
+                            <filters>
+                                <filter>
+                                    <artifact>${project.groupId}:${project.artifactId}</artifact>
+                                </filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <version>1.4</version>
+                <executions>
+                    <execution>
+                        <id>copy</id>
+                        <phase>package</phase>
+                        <configuration>
+                            <tasks>
+                                <copy file="${basedir}/target/killbill-osgi-bundles-test-${project.version}-jar-with-dependencies.jar" tofile="${basedir}/../../beatrix/src/test/resources/killbill-osgi-bundles-test-${project.version}-jar-with-dependencies.jar"/>
+                            </tasks>
+                        </configuration>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+        </plugins>
+    </build>
+</project>
diff --git a/osgi-bundles/test/src/main/java/com/ning/billing/osgi/bundles/test/dao/TestDao.java b/osgi-bundles/test/src/main/java/com/ning/billing/osgi/bundles/test/dao/TestDao.java
new file mode 100644
index 0000000..fe63c4c
--- /dev/null
+++ b/osgi-bundles/test/src/main/java/com/ning/billing/osgi/bundles/test/dao/TestDao.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.
+ */
+
+package com.ning.billing.osgi.bundles.test.dao;
+
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+
+public class TestDao {
+
+    private final IDBI dbi;
+
+    public TestDao(final IDBI dbi) {
+        this.dbi = dbi;
+    }
+
+    public void createTable() {
+
+        dbi.inTransaction(new TransactionCallback<Object>() {
+            @Override
+            public Object inTransaction(final Handle conn, final TransactionStatus status) throws Exception {
+                conn.execute("DROP TABLE IF EXISTS test_bundle;");
+                conn.execute("CREATE TABLE test_bundle (" +
+                "record_id int(11) unsigned NOT NULL AUTO_INCREMENT, " +
+                "is_started bool DEFAULT false, " +
+                "is_logged bool DEFAULT false, " +
+                "external_key varchar(128) NULL, " +
+                "payment_id char(36) NULL," +
+                "payment_method_id char(36) NULL," +
+                "payment_amount decimal(10,4) NULL," +
+                "PRIMARY KEY(record_id)" +
+                ");");
+                return null;
+            }
+        });
+    }
+
+    public void insertStarted() {
+        dbi.inTransaction(new TransactionCallback<Object>() {
+            @Override
+            public Object inTransaction(final Handle conn, final TransactionStatus status) throws Exception {
+                conn.execute("INSERT INTO test_bundle (is_started) VALUES (1);");
+                return null;
+            }
+        });
+    }
+
+    public void insertAccountExternalKey(final String externalKey) {
+        dbi.inTransaction(new TransactionCallback<Object>() {
+            @Override
+            public Object inTransaction(final Handle conn, final TransactionStatus status) throws Exception {
+                conn.execute("UPDATE test_bundle SET external_key = '" + externalKey + "' WHERE record_id = 1;");
+                return null;
+            }
+        });
+    }
+
+    public void insertProcessedPayment(final UUID paymentId, final UUID paymentMethodId, final BigDecimal paymentAmount) {
+        dbi.inTransaction(new TransactionCallback<Object>() {
+            @Override
+            public Object inTransaction(final Handle conn, final TransactionStatus status) throws Exception {
+                conn.execute("UPDATE test_bundle SET payment_id = '" + paymentId.toString() +
+                             "', payment_method_id = '" + paymentMethodId.toString() +
+                             "', payment_amount = " + paymentAmount +
+                             " WHERE record_id = 1;");
+                return null;
+            }
+        });
+    }
+}
diff --git a/osgi-bundles/test/src/main/java/com/ning/billing/osgi/bundles/test/TestActivator.java b/osgi-bundles/test/src/main/java/com/ning/billing/osgi/bundles/test/TestActivator.java
new file mode 100644
index 0000000..83576ca
--- /dev/null
+++ b/osgi-bundles/test/src/main/java/com/ning/billing/osgi/bundles/test/TestActivator.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.
+ */
+
+package com.ning.billing.osgi.bundles.test;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.UUID;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogService;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.beatrix.bus.api.ExtBusEvent;
+import com.ning.billing.beatrix.bus.api.ExtBusEventType;
+import com.ning.billing.beatrix.bus.api.ExternalBus;
+import com.ning.billing.osgi.api.OSGIKillbill;
+import com.ning.billing.osgi.api.OSGIPluginProperties;
+import com.ning.billing.osgi.bundles.test.dao.TestDao;
+import com.ning.billing.payment.plugin.api.PaymentPluginApi;
+import com.ning.billing.util.callcontext.TenantContext;
+
+import com.google.common.eventbus.Subscribe;
+
+/**
+ * Test class used by Beatrix OSGI test to verify that:
+ *  - "test" bundle is started
+ *  - test bundle is able to make API call
+ *  - test bundle is able to register a fake PaymentApi service
+ *  - test bundle can use the DataSource from Killbill and write on disk
+ */
+public class TestActivator implements BundleActivator {
+
+    private OSGIKillbill osgiKillbill;
+    private volatile ServiceReference<OSGIKillbill> osgiKillbillReference;
+
+    private LogService logService;
+    private volatile ServiceReference<LogService> logServiceReference;
+
+
+    private volatile boolean isRunning;
+    private volatile ServiceRegistration paymentInfoPluginRegistration;
+
+    private TestDao testDao;
+
+    @Override
+    public void start(final BundleContext context) {
+
+        final String bundleName = context.getBundle().getSymbolicName();
+        System.out.println("TestActivator starting bundle = " + bundleName);
+
+        fetchOSGIKIllbill(context);
+        fetchLogService(context);
+
+        final IDBI dbi = new DBI(osgiKillbill.getDataSource());
+        testDao = new TestDao(dbi);
+        registerPaymentApi(context, testDao);
+
+        registerForKillbillEvents(context);
+
+        testDao.createTable();
+
+        testDao.insertStarted();
+
+        this.isRunning = true;
+    }
+
+    @Override
+    public void stop(final BundleContext context) {
+        this.isRunning = false;
+        releaseOSGIKIllbill(context);
+        this.osgiKillbill = null;
+        releaseLogService(context);
+        this.logService = null;
+        unregisterPlaymentPluginApi(context);
+        System.out.println("Good bye world from TestActivator!");
+    }
+
+    @Subscribe
+    public void handleKillbillEvent(final ExtBusEvent killbillEvent) {
+
+        logService.log(LogService.LOG_INFO, "Received external event " + killbillEvent.toString());
+
+        // Only looking at account creation
+        if (killbillEvent.getEventType() != ExtBusEventType.ACCOUNT_CREATION) {
+            return;
+        }
+
+        final TenantContext tenantContext = new TenantContext() {
+            @Override
+            public UUID getTenantId() {
+                return null;
+            }
+        };
+
+
+        try {
+            Account account = osgiKillbill.getAccountUserApi().getAccountById(killbillEvent.getAccountId(), tenantContext);
+            testDao.insertAccountExternalKey(account.getExternalKey());
+
+        } catch (AccountApiException e) {
+            logService.log(LogService.LOG_ERROR, e.getMessage());
+        }
+    }
+
+
+    private void registerForKillbillEvents(final BundleContext context) {
+        try {
+            final ExternalBus externalBus = osgiKillbill.getExternalBus();
+            externalBus.register(this);
+        } catch (Exception e) {
+            System.err.println("Error in TestActivator: " + e.getLocalizedMessage());
+        } finally {
+        }
+    }
+
+    private void fetchOSGIKIllbill(final BundleContext context) {
+        this.osgiKillbillReference = (ServiceReference<OSGIKillbill>) context.getServiceReference(OSGIKillbill.class.getName());
+        try {
+            this.osgiKillbill = context.getService(osgiKillbillReference);
+        } catch (Exception e) {
+            System.err.println("Error in TestActivator: " + e.getLocalizedMessage());
+        }
+    }
+
+    private void releaseOSGIKIllbill(final BundleContext context) {
+        if (osgiKillbillReference != null) {
+            context.ungetService(osgiKillbillReference);
+        }
+    }
+
+
+    private void fetchLogService(final BundleContext context) {
+        this.logServiceReference = (ServiceReference<LogService>) context.getServiceReference(LogService.class.getName());
+        try {
+            this.logService = context.getService(logServiceReference);
+        } catch (Exception e) {
+            System.err.println("Error in TestActivator: " + e.getLocalizedMessage());
+        }
+    }
+
+    private void releaseLogService(final BundleContext context) {
+        if (logServiceReference != null) {
+            context.ungetService(logServiceReference);
+        }
+    }
+
+    private void registerPaymentApi(final BundleContext context, final TestDao dao) {
+
+        final Dictionary props = new Hashtable();
+        props.put(OSGIPluginProperties.PLUGIN_NAME_PROP, "test");
+
+        this.paymentInfoPluginRegistration = context.registerService(PaymentPluginApi.class.getName(),
+                                                                     new TestPaymentPluginApi("test", dao), props);
+    }
+
+    private void unregisterPlaymentPluginApi(final BundleContext context) {
+        if (paymentInfoPluginRegistration != null) {
+            paymentInfoPluginRegistration.unregister();
+            paymentInfoPluginRegistration = null;
+        }
+    }
+}
diff --git a/osgi-bundles/test/src/main/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java b/osgi-bundles/test/src/main/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java
new file mode 100644
index 0000000..712c75e
--- /dev/null
+++ b/osgi-bundles/test/src/main/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.
+ */
+
+package com.ning.billing.osgi.bundles.test;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.osgi.bundles.test.dao.TestDao;
+import com.ning.billing.payment.api.PaymentMethodPlugin;
+import com.ning.billing.payment.plugin.api.PaymentInfoPlugin;
+import com.ning.billing.payment.plugin.api.PaymentMethodInfoPlugin;
+import com.ning.billing.payment.plugin.api.PaymentPluginApi;
+import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
+import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.TenantContext;
+
+public class TestPaymentPluginApi implements PaymentPluginApi {
+
+    private final TestDao testDao;
+    private final String name;
+
+    public TestPaymentPluginApi(final String name, final TestDao testDao) {
+        this.testDao = testDao;
+        this.name = name;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public PaymentInfoPlugin processPayment(final UUID kbPaymentId, final UUID kbPaymentMethodId, final BigDecimal amount, final CallContext context) throws PaymentPluginApiException {
+        testDao.insertProcessedPayment(kbPaymentId, kbPaymentMethodId, amount);
+        return new PaymentInfoPlugin() {
+            @Override
+            public BigDecimal getAmount() {
+                return amount;
+            }
+            @Override
+            public DateTime getCreatedDate() {
+                return new DateTime();
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return new DateTime();
+            }
+            @Override
+            public PaymentPluginStatus getStatus() {
+                return PaymentPluginStatus.PROCESSED;
+            }
+            @Override
+            public String getGatewayError() {
+                return null;
+            }
+            @Override
+            public String getGatewayErrorCode() {
+                return null;
+            }
+        };
+    }
+
+
+    @Override
+    public PaymentInfoPlugin getPaymentInfo(final UUID kbPaymentId, final TenantContext context) throws PaymentPluginApiException {
+        return null;
+    }
+
+    @Override
+    public RefundInfoPlugin processRefund(final UUID kbPaymentId, final BigDecimal refundAmount, final CallContext context) throws PaymentPluginApiException {
+        return null;
+    }
+
+    @Override
+    public void addPaymentMethod(final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final CallContext context) throws PaymentPluginApiException {
+    }
+
+    @Override
+    public void deletePaymentMethod(final UUID kbPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+    }
+
+    @Override
+    public void setDefaultPaymentMethod(final UUID kbPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+    }
+
+    @Override
+    public List<PaymentMethodInfoPlugin> getPaymentMethods(final UUID kbAccountId, final boolean refreshFromGateway, final CallContext context) throws PaymentPluginApiException {
+        return null;
+    }
+
+    @Override
+    public void resetPaymentMethods(final List<PaymentMethodInfoPlugin> paymentMethods) throws PaymentPluginApiException {
+    }
+}
diff --git a/osgi-bundles/test/src/test/resources/ddl_test.sql b/osgi-bundles/test/src/test/resources/ddl_test.sql
new file mode 100644
index 0000000..e4bfa5d
--- /dev/null
+++ b/osgi-bundles/test/src/test/resources/ddl_test.sql
@@ -0,0 +1,10 @@
+/*! SET storage_engine=INNODB */;
+
+DROP TABLE IF EXISTS test_bundle;
+CREATE TABLE test_bundle (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    is_started bool DEFAULT false,
+    is_logged bool DEFAULT false,
+    external_key varchar(128) NULL
+    PRIMARY KEY(record_id)
+);

pom.xml 8(+8 -0)

diff --git a/pom.xml b/pom.xml
index 6ec9b79..ce46a7d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -266,6 +266,11 @@
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-osgi-bundles-test</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
                 <artifactId>killbill-osgi-bundles-meter</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -645,7 +650,9 @@
                     <useManifestOnlyJar>false</useManifestOnlyJar>
                     <groups>fast,slow,mysql</groups>
                     <systemPropertyVariables>
+                        <propertyName>propertyValue</propertyName>
                         <log4j.configuration>file:${project.basedir}/src/test/resources/log4j.xml</log4j.configuration>
+                        <killbill.version>${project.version}</killbill.version>
                     </systemPropertyVariables>
                 </configuration>
             </plugin>
@@ -715,6 +722,7 @@
                                 </com.ning.billing.dbi.jdbc.url>
                                 <file.encoding>UTF-8</file.encoding>
                                 <user.timezone>GMT</user.timezone>
+                                <killbill.version>${project.version}</killbill.version>
                             </systemPropertyVariables>
                         </configuration>
                     </plugin>
diff --git a/util/src/main/java/com/ning/billing/util/config/OSGIConfig.java b/util/src/main/java/com/ning/billing/util/config/OSGIConfig.java
index fc184fb..c135656 100644
--- a/util/src/main/java/com/ning/billing/util/config/OSGIConfig.java
+++ b/util/src/main/java/com/ning/billing/util/config/OSGIConfig.java
@@ -21,6 +21,10 @@ import org.skife.config.Default;
 
 public interface OSGIConfig extends KillbillConfig {
 
+    @Config("killbill.osgi.bundle.property.name")
+    @Default("killbill.properties")
+    public String getOSGIKillbillPropertyName();
+
     @Config("killbill.osgi.root.dir")
     @Default("/var/tmp/felix")
     public String getOSGIBundleRootDir();
@@ -48,6 +52,7 @@ public interface OSGIConfig extends KillbillConfig {
              "com.ning.billing.osgi.api.config," +
              "com.ning.billing.overdue," +
              "com.ning.billing.payment.api," +
+             "com.ning.billing.payment.plugin.api," +
              "com.ning.billing.tenant.api," +
              "com.ning.billing.usage.api," +
              "com.ning.billing.util.api," +