killbill-memoizeit

Squashed commit of the following: commit 95c3adb49ea2a9831dbbe85d33a2b13c6781e860 Author:

1/31/2013 7:44:26 PM

Changes

.travis.yml 2(+2 -0)

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

osgi/pom.xml 93(+93 -0)

pom.xml 58(+56 -2)

Details

.travis.yml 2(+2 -0)

diff --git a/.travis.yml b/.travis.yml
index c77a8af..6a9f366 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,7 @@
 language: java
 script: mvn clean install -Ptravis
+# Remove --quiet to avoid timeouts
+install: mvn install -DskipTests=true
 
 notifications:
   email:
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccountService.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccountService.java
index d630f70..a378bc6 100644
--- a/account/src/main/java/com/ning/billing/account/api/DefaultAccountService.java
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccountService.java
@@ -25,7 +25,4 @@ public class DefaultAccountService implements AccountService {
         return ACCOUNT_SERVICE_NAME;
     }
 
-    //    @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
-    //    public void initialize() {
-    //    }
 }
diff --git a/api/src/main/java/com/ning/billing/catalog/api/CatalogUserApi.java b/api/src/main/java/com/ning/billing/catalog/api/CatalogUserApi.java
index 6225e84..2aa532c 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/CatalogUserApi.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/CatalogUserApi.java
@@ -24,10 +24,9 @@ import com.ning.billing.util.callcontext.TenantContext;
 public interface CatalogUserApi {
 
     /**
-     *
-     * @param catalogName   the name of the catalog
-     * @param context       the user context that specifies the enant information
-     * @return              the {@code Catalog}
+     * @param catalogName the name of the catalog
+     * @param context     the user context that specifies the enant information
+     * @return the {@code Catalog}
      */
     Catalog getCatalog(String catalogName, TenantContext context);
 }
diff --git a/api/src/main/java/com/ning/billing/osgi/api/config/PluginConfig.java b/api/src/main/java/com/ning/billing/osgi/api/config/PluginConfig.java
new file mode 100644
index 0000000..ced4e47
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/osgi/api/config/PluginConfig.java
@@ -0,0 +1,41 @@
+/*
+ * 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.api.config;
+
+public interface PluginConfig {
+
+    public enum PluginType {
+        PAYMENT,
+        NOTIFICATION,
+        __UNKNOWN__
+    }
+
+    public enum PluginLanguage {
+        JAVA,
+        RUBY
+    }
+
+    public String getPluginName();
+
+    public PluginType getPluginType();
+
+    public String getVersion();
+
+    public String getPluginVersionnedName();
+
+    public PluginLanguage getPluginLanguage();
+}
diff --git a/api/src/main/java/com/ning/billing/osgi/api/config/PluginConfigServiceApi.java b/api/src/main/java/com/ning/billing/osgi/api/config/PluginConfigServiceApi.java
new file mode 100644
index 0000000..a84441a
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/osgi/api/config/PluginConfigServiceApi.java
@@ -0,0 +1,24 @@
+/*
+ * 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.api.config;
+
+public interface PluginConfigServiceApi {
+
+    public PluginJavaConfig getPluginJavaConfig(long bundleId);
+
+    public PluginRubyConfig getPluginRubyConfig(long bundleId);
+}
diff --git a/api/src/main/java/com/ning/billing/osgi/api/config/PluginJavaConfig.java b/api/src/main/java/com/ning/billing/osgi/api/config/PluginJavaConfig.java
new file mode 100644
index 0000000..bdd4465
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/osgi/api/config/PluginJavaConfig.java
@@ -0,0 +1,22 @@
+/*
+ * 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.api.config;
+
+public interface PluginJavaConfig extends PluginConfig {
+
+    public String getBundleJarPath();
+}
diff --git a/api/src/main/java/com/ning/billing/osgi/api/config/PluginRubyConfig.java b/api/src/main/java/com/ning/billing/osgi/api/config/PluginRubyConfig.java
new file mode 100644
index 0000000..0e9ca2a
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/osgi/api/config/PluginRubyConfig.java
@@ -0,0 +1,25 @@
+/*
+ * 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.api.config;
+
+public interface PluginRubyConfig extends PluginConfig {
+
+    public String getRubyMainClass();
+
+    public String getRubyLoadDir();
+    
+}
diff --git a/api/src/main/java/com/ning/billing/osgi/api/OSGIService.java b/api/src/main/java/com/ning/billing/osgi/api/OSGIService.java
new file mode 100644
index 0000000..fcb7456
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/osgi/api/OSGIService.java
@@ -0,0 +1,22 @@
+/*
+ * 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.api;
+
+import com.ning.billing.lifecycle.KillbillService;
+
+public interface OSGIService extends KillbillService {
+}

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

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 506054f..071d650 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -45,6 +45,22 @@
             <artifactId>killbill-junction</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-meter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-osgi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-tenant</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-usage</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
index 1c2d9b6..6b01719 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
@@ -89,10 +89,21 @@ public class ServiceFinder {
         for (int h = 0; h < classPaths.length; h++) {
             Enumeration<?> files = null;
             JarFile module = null;
-            final File classPath = new File((URL.class).isInstance(classPaths[h]) ?
-                                              ((URL) classPaths[h]).getFile() : classPaths[h].toString());
-            if (classPath.isDirectory()) {
 
+            final String protocol;
+            final File classPath;
+            if ((URL.class).isInstance(classPaths[h])) {
+                final URL urlClassPath = (URL) classPaths[h];
+                classPath = new File(urlClassPath.getFile());
+                protocol = urlClassPath.getProtocol();
+            } else {
+                classPath = new File(classPaths[h].toString());
+                protocol = "file";
+            }
+
+            // Check if the protocol is "file". For example, if classPath is http://felix.extensions:9/,
+            // the file will be "/" and we don't want to scan the full filesystem
+            if ("file".equals(protocol) && classPath.isDirectory()) {
                 log.debug("DIR : " + classPath);
 
                 final List<String> dirListing = new ArrayList<String>();
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 10b04fe..956b28e 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
@@ -50,10 +50,15 @@ import com.ning.billing.invoice.api.InvoiceService;
 import com.ning.billing.invoice.glue.DefaultInvoiceModule;
 import com.ning.billing.junction.glue.DefaultJunctionModule;
 import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.meter.glue.MeterModule;
+import com.ning.billing.osgi.DefaultOSGIService;
+import com.ning.billing.osgi.glue.DefaultOSGIModule;
 import com.ning.billing.overdue.OverdueService;
 import com.ning.billing.payment.api.PaymentService;
 import com.ning.billing.payment.glue.PaymentModule;
 import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.tenant.glue.TenantModule;
+import com.ning.billing.usage.glue.UsageModule;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.config.PaymentConfig;
@@ -65,6 +70,7 @@ import com.ning.billing.util.glue.BusModule;
 import com.ning.billing.util.glue.CacheModule;
 import com.ning.billing.util.glue.CallContextModule;
 import com.ning.billing.util.glue.CustomFieldModule;
+import com.ning.billing.util.glue.ExportModule;
 import com.ning.billing.util.glue.NonEntityDaoModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
 import com.ning.billing.util.glue.TagStoreModule;
@@ -119,6 +125,11 @@ public class BeatrixIntegrationModule extends AbstractModule {
         install(new DefaultJunctionModule());
         install(new IntegrationTestOverdueModule());
         install(new AuditModule());
+        install(new MeterModule());
+        install(new UsageModule());
+        install(new TenantModule());
+        install(new ExportModule());
+        install(new DefaultOSGIModule());
         install(new NonEntityDaoModule());
 
         bind(AccountChecker.class).asEagerSingleton();
@@ -172,6 +183,7 @@ public class BeatrixIntegrationModule extends AbstractModule {
                     .add(injector.getInstance(PaymentService.class))
                     .add(injector.getInstance(OverdueService.class))
                     .add(injector.getInstance(DefaultBeatrixService.class))
+                    .add(injector.getInstance(DefaultOSGIService.class))
                     .build();
             return services;
         }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/osgi/TestOSGIBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/osgi/TestOSGIBase.java
new file mode 100644
index 0000000..437ebc0
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/osgi/TestOSGIBase.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.ning.billing.beatrix.integration.TestIntegrationBase;
+import com.ning.billing.util.config.OSGIConfig;
+
+import com.google.inject.Inject;
+
+public class TestOSGIBase extends TestIntegrationBase {
+
+    @Inject
+    protected OSGIConfig config;
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/osgi/TestOSGIIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/osgi/TestOSGIIntegration.java
new file mode 100644
index 0000000..98fced0
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/osgi/TestOSGIIntegration.java
@@ -0,0 +1,31 @@
+/*
+ * 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 org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.ning.billing.beatrix.integration.BeatrixIntegrationModule;
+
+@Guice(modules = {BeatrixIntegrationModule.class})
+public class TestOSGIIntegration extends TestOSGIBase {
+
+    @Test(groups = "slow")
+    public void testJRubyIntegration() throws Exception {
+        createAccountWithPaymentMethod(getAccountData(1));
+    }
+}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/api/user/DefaultCatalogUserApi.java b/catalog/src/main/java/com/ning/billing/catalog/api/user/DefaultCatalogUserApi.java
new file mode 100644
index 0000000..6b60bbd
--- /dev/null
+++ b/catalog/src/main/java/com/ning/billing/catalog/api/user/DefaultCatalogUserApi.java
@@ -0,0 +1,29 @@
+/*
+ * 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.catalog.api.user;
+
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogUserApi;
+import com.ning.billing.util.callcontext.TenantContext;
+
+public class DefaultCatalogUserApi implements CatalogUserApi {
+
+    @Override
+    public Catalog getCatalog(final String catalogName, final TenantContext context) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/glue/CatalogModule.java b/catalog/src/main/java/com/ning/billing/catalog/glue/CatalogModule.java
index 3091f04..8781188 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/glue/CatalogModule.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/glue/CatalogModule.java
@@ -22,14 +22,18 @@ import org.skife.config.ConfigSource;
 import org.skife.config.ConfigurationObjectFactory;
 import org.skife.config.SimplePropertyConfigSource;
 
-import com.google.inject.AbstractModule;
 import com.ning.billing.catalog.DefaultCatalogService;
 import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.CatalogUserApi;
+import com.ning.billing.catalog.api.user.DefaultCatalogUserApi;
 import com.ning.billing.catalog.io.ICatalogLoader;
 import com.ning.billing.catalog.io.VersionedCatalogLoader;
 import com.ning.billing.util.config.CatalogConfig;
 
+import com.google.inject.AbstractModule;
+
 public class CatalogModule extends AbstractModule {
+
     final ConfigSource configSource;
 
     public CatalogModule() {
@@ -54,9 +58,14 @@ public class CatalogModule extends AbstractModule {
         bind(ICatalogLoader.class).to(VersionedCatalogLoader.class).asEagerSingleton();
     }
 
+    protected void installCatalogUserApi() {
+        bind(CatalogUserApi.class).to(DefaultCatalogUserApi.class).asEagerSingleton();
+    }
+
     @Override
     protected void configure() {
         installConfig();
         installCatalog();
+        installCatalogUserApi();
     }
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java
index 890e555..a1de54c 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java
@@ -23,7 +23,6 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.Interval;
-import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -43,11 +42,6 @@ import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.api.user.TestUtil.TestWithException;
 import com.ning.billing.entitlement.api.user.TestUtil.TestWithExceptionCallback;
-import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
-
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
@@ -58,7 +52,6 @@ public class TestRepairWithError extends EntitlementTestSuiteNoDB {
     private TestWithException test;
     private Subscription baseSubscription;
 
-
     @BeforeMethod(alwaysRun = true)
     public void setupTest() throws Exception {
         super.setupTest();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestDefaultEntitlementTransferApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestDefaultEntitlementTransferApi.java
index 29b3d81..a5a3519 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestDefaultEntitlementTransferApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestDefaultEntitlementTransferApi.java
@@ -25,8 +25,6 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.ning.billing.GuicyKillbillTestSuiteNoDB;
-import com.ning.billing.api.TestListenerStatus;
 import com.ning.billing.catalog.MockCatalog;
 import com.ning.billing.catalog.MockCatalogService;
 import com.ning.billing.catalog.api.BillingPeriod;
@@ -36,7 +34,6 @@ import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.EntitlementTestSuiteNoDB;
-import com.ning.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
 import com.ning.billing.entitlement.api.SubscriptionApiService;
 import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
@@ -50,8 +47,6 @@ import com.ning.billing.entitlement.events.user.ApiEventTransfer;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.util.cache.CacheControllerDispatcher;
 import com.ning.billing.util.callcontext.InternalCallContextFactory;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.dao.NonEntityDao;
 
 import com.google.common.collect.ImmutableList;
@@ -66,7 +61,7 @@ public class TestDefaultEntitlementTransferApi extends EntitlementTestSuiteNoDB 
         final NonEntityDao nonEntityDao = Mockito.mock(NonEntityDao.class);
         final EntitlementDao dao = Mockito.mock(EntitlementDao.class);
         final CatalogService catalogService = new MockCatalogService(new MockCatalog());
-        final SubscriptionApiService apiService =  Mockito.mock(SubscriptionApiService.class);
+        final SubscriptionApiService apiService = Mockito.mock(SubscriptionApiService.class);
         final EntitlementTimelineApi timelineApi = Mockito.mock(EntitlementTimelineApi.class);
         final InternalCallContextFactory internalCallContextFactory = new InternalCallContextFactory(clock, nonEntityDao, new CacheControllerDispatcher());
         transferApi = new DefaultEntitlementTransferApi(clock, dao, timelineApi, catalogService, apiService, internalCallContextFactory);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
index c4c3088..0c04375 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
@@ -16,10 +16,6 @@
 
 package com.ning.billing.entitlement.api.user;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
-
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -42,6 +38,10 @@ import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.util.callcontext.TenantContext;
 import com.ning.billing.util.clock.DefaultClock;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
 public class TestUserApiError extends EntitlementTestSuiteNoDB {
 
     private final TenantContext tenantContext = Mockito.mock(TenantContext.class);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteNoDB.java b/entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteNoDB.java
index faf33af..775b8b7 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteNoDB.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteNoDB.java
@@ -16,60 +16,32 @@
 
 package com.ning.billing.entitlement;
 
-import java.io.IOException;
-import java.net.URL;
-import java.util.List;
-import java.util.UUID;
-
-import javax.annotation.Nullable;
 import javax.inject.Inject;
 
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.mockito.Mockito;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 
 import com.ning.billing.GuicyKillbillTestSuiteNoDB;
 import com.ning.billing.account.api.AccountData;
-import com.ning.billing.account.api.BillCycleDay;
 import com.ning.billing.api.TestApiListener;
 import com.ning.billing.api.TestListenerStatus;
-import com.ning.billing.catalog.DefaultCatalogService;
-import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Catalog;
 import com.ning.billing.catalog.api.CatalogService;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.catalog.api.Duration;
-import com.ning.billing.catalog.api.PhaseType;
-import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.entitlement.api.EntitlementService;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
-import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementAccountMigration;
 import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
 import com.ning.billing.entitlement.api.transfer.EntitlementTransferApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
-import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.api.user.TestUtil;
-import com.ning.billing.entitlement.api.user.TestUtil.EntitlementSubscriptionMigrationCaseWithCTD;
-import com.ning.billing.entitlement.engine.core.Engine;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.engine.dao.MockEntitlementDaoMemory;
-import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
-import com.ning.billing.entitlement.glue.MockEngineModuleSql;
-import com.ning.billing.mock.MockAccountBuilder;
-import com.ning.billing.util.bus.DefaultBusService;
 import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.config.EntitlementConfig;
-import com.ning.billing.util.events.EffectiveSubscriptionInternalEvent;
 import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
 import com.ning.billing.util.svcsapi.bus.BusService;
 
@@ -77,9 +49,7 @@ import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 
-import static org.testng.Assert.assertNotNull;
-
-public class EntitlementTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB  {
+public class EntitlementTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     protected static final Logger log = LoggerFactory.getLogger(EntitlementTestSuiteNoDB.class);
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java b/entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
index 087f7ef..f547904 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
@@ -20,7 +20,6 @@ import javax.inject.Inject;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
@@ -51,7 +50,6 @@ import com.google.inject.Stage;
 
 public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWithEmbeddedDB {
 
-
     protected static final Logger log = LoggerFactory.getLogger(EntitlementTestSuiteWithEmbeddedDB.class);
 
     @Inject
@@ -93,7 +91,6 @@ public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWi
     protected AccountData accountData;
     protected SubscriptionBundle bundle;
 
-
     @BeforeClass(groups = "slow")
     public void setup() throws Exception {
         DefaultEntitlementTestInitializer.loadSystemPropertiesFromClasspath("/entitlement.properties");
@@ -101,7 +98,6 @@ public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWi
         g.injectMembers(this);
     }
 
-
     @BeforeMethod(groups = "slow")
     public void setupTest() throws Exception {
         entitlementTestInitializer.startTestFamework(testListener, testListenerStatus, clock, busService, entitlementService);
@@ -111,7 +107,6 @@ public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWi
         this.bundle = entitlementTestInitializer.initBundle(entitlementApi, callContext);
     }
 
-
     @AfterMethod(groups = "slow")
     public void cleanupTest() throws Exception {
         entitlementTestInitializer.stopTestFramework(testListener, busService, entitlementService);
diff --git a/meter/src/main/java/com/ning/billing/meter/DefaultMeterService.java b/meter/src/main/java/com/ning/billing/meter/DefaultMeterService.java
index 725da04..a7b08b9 100644
--- a/meter/src/main/java/com/ning/billing/meter/DefaultMeterService.java
+++ b/meter/src/main/java/com/ning/billing/meter/DefaultMeterService.java
@@ -57,8 +57,10 @@ public class DefaultMeterService implements MeterService {
     public void start() {
         // Replay any log files that might not have been committed in the db-- should only occur if we crashed previously
         timelineEventHandler.replay(config.getSpoolDir(), internalCallContext);
-        // Start the aggregation thread
-        timelineAggregator.runAggregationThread();
+        // Start the aggregation thread, if enabled
+        if (config.getTimelineAggregationEnabled()) {
+            timelineAggregator.runAggregationThread();
+        }
         // Start the backgroundDBChunkWriter thread
         backgroundDBChunkWriter.runBackgroundWriteThread();
         // Start the purger thread to delete old log files

osgi/pom.xml 93(+93 -0)

diff --git a/osgi/pom.xml b/osgi/pom.xml
new file mode 100644
index 0000000..a35a289
--- /dev/null
+++ b/osgi/pom.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ Copyright 2010-2011 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</artifactId>
+        <version>0.1.52-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>killbill-osgi</artifactId>
+    <name>Killbill billing platform: OSGI framework</name>
+    <packaging>jar</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.framework</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.skife.config</groupId>
+            <artifactId>config-magic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <!-- Default Bundles -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>osgi-over-slf4j</artifactId>
+        </dependency>
+        <!-- TEST SCOPE -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.jayway.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/osgi/src/main/java/com/ning/billing/osgi/DefaultOSGIService.java b/osgi/src/main/java/com/ning/billing/osgi/DefaultOSGIService.java
new file mode 100644
index 0000000..8091eff
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/DefaultOSGIService.java
@@ -0,0 +1,214 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.apache.felix.framework.Felix;
+import org.apache.felix.framework.util.FelixConstants;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.osgi.logservice.impl.Activator;
+
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.osgi.api.OSGIService;
+import com.ning.billing.osgi.api.config.PluginConfigServiceApi;
+import com.ning.billing.osgi.api.config.PluginJavaConfig;
+import com.ning.billing.osgi.api.config.PluginRubyConfig;
+import com.ning.billing.osgi.pluginconf.DefaultPluginConfigServiceApi;
+import com.ning.billing.osgi.pluginconf.PluginConfigException;
+import com.ning.billing.osgi.pluginconf.PluginFinder;
+import com.ning.billing.util.config.OSGIConfig;
+
+import com.google.common.collect.ImmutableList;
+
+public class DefaultOSGIService implements OSGIService {
+
+    public static final String OSGI_SERVICE_NAME = "osgi-service";
+
+    private static final Logger logger = LoggerFactory.getLogger(DefaultOSGIService.class);
+
+    private Framework framework;
+
+    private final OSGIConfig osgiConfig;
+    private final PluginFinder pluginFinder;
+    private final PluginConfigServiceApi pluginConfigServiceApi;
+    private final KillbillActivator killbillActivator;
+
+    @Inject
+    public DefaultOSGIService(final OSGIConfig osgiConfig, final PluginFinder pluginFinder,
+                              final PluginConfigServiceApi pluginConfigServiceApi, final KillbillActivator killbillActivator) {
+        this.osgiConfig = osgiConfig;
+        this.pluginFinder = pluginFinder;
+        this.pluginConfigServiceApi = pluginConfigServiceApi;
+        this.killbillActivator = killbillActivator;
+        this.framework = null;
+    }
+
+    @Override
+    public String getName() {
+        return OSGI_SERVICE_NAME;
+    }
+
+    @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
+    public void initialize() {
+        try {
+            // We start by deleting existing osi cache; we might optimize later keeping the cache
+            pruneOSGICache();
+
+            // Create the system bundle for killbill and start the framework
+            this.framework = createAndInitFramework();
+            framework.start();
+
+            // This will call the start() method for the bundles
+            installAndStartBundles(framework);
+        } catch (BundleException e) {
+            logger.error("Failed to initialize Killbill OSGIService", e);
+        }
+    }
+
+    @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
+    public void startFramework() {
+    }
+
+    @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.REGISTER_EVENTS)
+    public void registerForExternalEvents() {
+    }
+
+    @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.UNREGISTER_EVENTS)
+    public void unregisterForExternalEvents() {
+    }
+
+    @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
+    public void stop() {
+        try {
+            framework.stop();
+            framework.waitForStop(0);
+        } catch (BundleException e) {
+            logger.error("Failed to Stop Killbill OSGIService " + e.getMessage());
+        } catch (InterruptedException e) {
+            logger.error("Failed to Stop Killbill OSGIService " + e.getMessage());
+        }
+    }
+
+    private void installAndStartBundles(final Framework framework) throws BundleException {
+        try {
+            final BundleContext context = framework.getBundleContext();
+
+            // Install all bundles and create service mapping
+            final List<Bundle> installedBundles = new LinkedList<Bundle>();
+            installAllJavaBundles(context, installedBundles);
+            installAllJRubyBundles(context, installedBundles);
+
+            // Start all the bundles
+            for (final Bundle bundle : installedBundles) {
+                logger.info("Starting bundle {}", bundle.getLocation());
+                bundle.start();
+            }
+        } catch (PluginConfigException e) {
+            logger.error("Error while parsing plugin configurations", e);
+        }
+    }
+
+    private void installAllJavaBundles(final BundleContext context, final List<Bundle> installedBundles) throws PluginConfigException, BundleException {
+        final List<PluginJavaConfig> pluginJavaConfigs = pluginFinder.getLatestJavaPlugins();
+        for (final PluginJavaConfig cur : pluginJavaConfigs) {
+            logger.info("Installing Java bundle for plugin {} in {}", cur.getPluginName(), cur.getBundleJarPath());
+            final Bundle bundle = context.installBundle("file:" + cur.getBundleJarPath());
+            ((DefaultPluginConfigServiceApi) pluginConfigServiceApi).registerBundle(bundle.getBundleId(), cur);
+            installedBundles.add(bundle);
+        }
+    }
+
+    private void installAllJRubyBundles(final BundleContext context, final List<Bundle> installedBundles) throws PluginConfigException, BundleException {
+        final List<PluginRubyConfig> pluginRubyConfigs = pluginFinder.getLatestRubyPlugins();
+        for (final PluginRubyConfig cur : pluginRubyConfigs) {
+            logger.info("Installing JRuby bundle for plugin {} in {}", cur.getPluginName(), cur.getRubyLoadDir());
+            final Bundle bundle = context.installBundle(osgiConfig.getJrubyBundlePath());
+            ((DefaultPluginConfigServiceApi) pluginConfigServiceApi).registerBundle(bundle.getBundleId(), cur);
+            installedBundles.add(bundle);
+        }
+    }
+
+    private Framework createAndInitFramework() throws BundleException {
+        final Map<String, String> config = new HashMap<String, String>();
+        config.put("org.osgi.framework.system.packages.extra", osgiConfig.getSystemBundleExportPackages());
+        config.put("felix.cache.rootdir", osgiConfig.getOSGIBundleRootDir());
+        config.put("org.osgi.framework.storage", osgiConfig.getOSGIBundleCacheName());
+        return createAndInitFelixFrameworkWithSystemBundle(config);
+    }
+
+    private Framework createAndInitFelixFrameworkWithSystemBundle(final Map<String, String> config) throws BundleException {
+        // From standard properties add Felix specific property to add a System bundle activator
+        final Map<Object, Object> felixConfig = new HashMap<Object, Object>();
+        felixConfig.putAll(config);
+
+        // Install default bundles: killbill and slf4j ones
+        felixConfig.put(FelixConstants.SYSTEMBUNDLE_ACTIVATORS_PROP, ImmutableList.<BundleActivator>of(killbillActivator, new Activator()));
+
+        final Framework felix = new Felix(felixConfig);
+        felix.init();
+        return felix;
+    }
+
+    private void pruneOSGICache() {
+        final String path = osgiConfig.getOSGIBundleRootDir() + "/" + osgiConfig.getOSGIBundleCacheName();
+        deleteUnderDirectory(new File(path));
+    }
+
+    private static void deleteUnderDirectory(final File path) {
+        deleteDirectory(path, false);
+    }
+
+    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);
+                    }
+                    if (!f.delete()) {
+                        logger.warn("Unable to delete {}", f.getAbsolutePath());
+                    }
+                }
+            }
+
+            if (deleteParent) {
+                if (!path.delete()) {
+                    logger.warn("Unable to delete {}", path.getAbsolutePath());
+                }
+            }
+        }
+    }
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/glue/DefaultOSGIModule.java b/osgi/src/main/java/com/ning/billing/osgi/glue/DefaultOSGIModule.java
new file mode 100644
index 0000000..b505635
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/glue/DefaultOSGIModule.java
@@ -0,0 +1,43 @@
+/*
+ * 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.glue;
+
+import org.skife.config.ConfigurationObjectFactory;
+
+import com.ning.billing.osgi.KillbillActivator;
+import com.ning.billing.osgi.api.config.PluginConfigServiceApi;
+import com.ning.billing.osgi.pluginconf.DefaultPluginConfigServiceApi;
+import com.ning.billing.osgi.pluginconf.PluginFinder;
+import com.ning.billing.util.config.OSGIConfig;
+
+import com.google.inject.AbstractModule;
+
+public class DefaultOSGIModule extends AbstractModule {
+
+    protected void installConfig() {
+        final OSGIConfig config = new ConfigurationObjectFactory(System.getProperties()).build(OSGIConfig.class);
+        bind(OSGIConfig.class).toInstance(config);
+    }
+
+    @Override
+    protected void configure() {
+        installConfig();
+        bind(KillbillActivator.class).asEagerSingleton();
+        bind(PluginFinder.class).asEagerSingleton();
+        bind(PluginConfigServiceApi.class).to(DefaultPluginConfigServiceApi.class).asEagerSingleton();
+    }
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/KillbillActivator.java b/osgi/src/main/java/com/ning/billing/osgi/KillbillActivator.java
new file mode 100644
index 0000000..9030032
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/KillbillActivator.java
@@ -0,0 +1,282 @@
+/*
+ * 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;
+
+import javax.inject.Inject;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.analytics.api.sanity.AnalyticsSanityApi;
+import com.ning.billing.analytics.api.user.AnalyticsUserApi;
+import com.ning.billing.beatrix.bus.api.ExternalBus;
+import com.ning.billing.catalog.api.CatalogUserApi;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.transfer.EntitlementTransferApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.invoice.api.InvoiceMigrationApi;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.meter.api.MeterUserApi;
+import com.ning.billing.osgi.api.config.PluginConfigServiceApi;
+import com.ning.billing.overdue.OverdueUserApi;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.tenant.api.TenantUserApi;
+import com.ning.billing.usage.api.UsageUserApi;
+import com.ning.billing.util.api.AuditUserApi;
+import com.ning.billing.util.api.CustomFieldUserApi;
+import com.ning.billing.util.api.ExportUserApi;
+import com.ning.billing.util.api.TagUserApi;
+
+public class KillbillActivator implements BundleActivator {
+
+    private final AccountUserApi accountUserApi;
+    private final AnalyticsSanityApi analyticsSanityApi;
+    private final AnalyticsUserApi analyticsUserApi;
+    private final CatalogUserApi catalogUserApi;
+    private final EntitlementMigrationApi entitlementMigrationApi;
+    private final EntitlementTimelineApi entitlementTimelineApi;
+    private final EntitlementTransferApi entitlementTransferApi;
+    private final EntitlementUserApi entitlementUserApi;
+    private final InvoiceMigrationApi invoiceMigrationApi;
+    private final InvoicePaymentApi invoicePaymentApi;
+    private final InvoiceUserApi invoiceUserApi;
+    private final MeterUserApi meterUserApi;
+    private final OverdueUserApi overdueUserApi;
+    private final PaymentApi paymentApi;
+    private final TenantUserApi tenantUserApi;
+    private final UsageUserApi usageUserApi;
+    private final AuditUserApi auditUserApi;
+    private final CustomFieldUserApi customFieldUserApi;
+    private final ExportUserApi exportUserApi;
+    private final TagUserApi tagUserApi;
+
+    private final ExternalBus externalBus;
+    private final PluginConfigServiceApi configServiceApi;
+
+    private volatile ServiceRegistration accountUserApiRegistration = null;
+    private volatile ServiceRegistration analyticsSanityApiRegistration = null;
+    private volatile ServiceRegistration analyticsUserApiRegistration = null;
+    private volatile ServiceRegistration catalogUserApiRegistration = null;
+    private volatile ServiceRegistration entitlementMigrationApiRegistration = null;
+    private volatile ServiceRegistration entitlementTimelineApiRegistration = null;
+    private volatile ServiceRegistration entitlementTransferApiRegistration = null;
+    private volatile ServiceRegistration entitlementUserApiRegistration = null;
+    private volatile ServiceRegistration invoiceMigrationApiRegistration = null;
+    private volatile ServiceRegistration invoicePaymentApiRegistration = null;
+    private volatile ServiceRegistration invoiceUserApiRegistration = null;
+    private volatile ServiceRegistration meterUserApiRegistration = null;
+    private volatile ServiceRegistration overdueUserApiRegistration = null;
+    private volatile ServiceRegistration paymentApiRegistration = null;
+    private volatile ServiceRegistration tenantUserApiRegistration = null;
+    private volatile ServiceRegistration usageUserApiRegistration = null;
+    private volatile ServiceRegistration auditUserApiRegistration = null;
+    private volatile ServiceRegistration customFieldUserApiRegistration = null;
+    private volatile ServiceRegistration exportUserApiRegistration = null;
+    private volatile ServiceRegistration tagUserApiRegistration = null;
+
+    private volatile ServiceRegistration externalBusRegistration = null;
+    private volatile ServiceRegistration configServiceApiRegistration = null;
+
+    @Inject
+    public KillbillActivator(final AccountUserApi accountUserApi,
+                             final AnalyticsSanityApi analyticsSanityApi,
+                             final AnalyticsUserApi analyticsUserApi,
+                             final CatalogUserApi catalogUserApi,
+                             final EntitlementMigrationApi entitlementMigrationApi,
+                             final EntitlementTimelineApi entitlementTimelineApi,
+                             final EntitlementTransferApi entitlementTransferApi,
+                             final EntitlementUserApi entitlementUserApi,
+                             final InvoiceMigrationApi invoiceMigrationApi,
+                             final InvoicePaymentApi invoicePaymentApi,
+                             final InvoiceUserApi invoiceUserApi,
+                             final MeterUserApi meterUserApi,
+                             final OverdueUserApi overdueUserApi,
+                             final PaymentApi paymentApi,
+                             final TenantUserApi tenantUserApi,
+                             final UsageUserApi usageUserApi,
+                             final AuditUserApi auditUserApi,
+                             final CustomFieldUserApi customFieldUserApi,
+                             final ExportUserApi exportUserApi,
+                             final TagUserApi tagUserApi,
+                             final ExternalBus externalBus,
+                             final PluginConfigServiceApi configServiceApi) {
+        this.accountUserApi = accountUserApi;
+        this.analyticsSanityApi = analyticsSanityApi;
+        this.analyticsUserApi = analyticsUserApi;
+        this.catalogUserApi = catalogUserApi;
+        this.entitlementMigrationApi = entitlementMigrationApi;
+        this.entitlementTimelineApi = entitlementTimelineApi;
+        this.entitlementTransferApi = entitlementTransferApi;
+        this.entitlementUserApi = entitlementUserApi;
+        this.invoiceMigrationApi = invoiceMigrationApi;
+        this.invoicePaymentApi = invoicePaymentApi;
+        this.invoiceUserApi = invoiceUserApi;
+        this.meterUserApi = meterUserApi;
+        this.overdueUserApi = overdueUserApi;
+        this.paymentApi = paymentApi;
+        this.tenantUserApi = tenantUserApi;
+        this.usageUserApi = usageUserApi;
+        this.auditUserApi = auditUserApi;
+        this.customFieldUserApi = customFieldUserApi;
+        this.exportUserApi = exportUserApi;
+        this.tagUserApi = tagUserApi;
+        this.externalBus = externalBus;
+        this.configServiceApi = configServiceApi;
+    }
+
+    @Override
+    public void start(final BundleContext context) throws Exception {
+        registerServices(context);
+    }
+
+    private void registerServices(final BundleContext context) {
+        accountUserApiRegistration = context.registerService(AccountUserApi.class.getName(), accountUserApi, null);
+        analyticsSanityApiRegistration = context.registerService(AnalyticsSanityApi.class.getName(), analyticsSanityApi, null);
+        analyticsUserApiRegistration = context.registerService(AnalyticsUserApi.class.getName(), analyticsUserApi, null);
+        catalogUserApiRegistration = context.registerService(CatalogUserApi.class.getName(), catalogUserApi, null);
+        entitlementMigrationApiRegistration = context.registerService(EntitlementMigrationApi.class.getName(), entitlementMigrationApi, null);
+        entitlementTimelineApiRegistration = context.registerService(EntitlementTimelineApi.class.getName(), entitlementTimelineApi, null);
+        entitlementTransferApiRegistration = context.registerService(EntitlementTransferApi.class.getName(), entitlementTransferApi, null);
+        entitlementUserApiRegistration = context.registerService(EntitlementUserApi.class.getName(), entitlementUserApi, null);
+        invoiceMigrationApiRegistration = context.registerService(InvoiceMigrationApi.class.getName(), invoiceMigrationApi, null);
+        invoicePaymentApiRegistration = context.registerService(InvoicePaymentApi.class.getName(), invoicePaymentApi, null);
+        invoiceUserApiRegistration = context.registerService(InvoiceUserApi.class.getName(), invoiceUserApi, null);
+        meterUserApiRegistration = context.registerService(MeterUserApi.class.getName(), meterUserApi, null);
+        overdueUserApiRegistration = context.registerService(OverdueUserApi.class.getName(), overdueUserApi, null);
+        paymentApiRegistration = context.registerService(PaymentApi.class.getName(), paymentApi, null);
+        tenantUserApiRegistration = context.registerService(TenantUserApi.class.getName(), tenantUserApi, null);
+        usageUserApiRegistration = context.registerService(UsageUserApi.class.getName(), usageUserApi, null);
+        auditUserApiRegistration = context.registerService(AuditUserApi.class.getName(), auditUserApi, null);
+        customFieldUserApiRegistration = context.registerService(CustomFieldUserApi.class.getName(), customFieldUserApi, null);
+        exportUserApiRegistration = context.registerService(ExportUserApi.class.getName(), exportUserApi, null);
+        tagUserApiRegistration = context.registerService(TagUserApi.class.getName(), tagUserApi, null);
+
+        externalBusRegistration = context.registerService(ExternalBus.class.getName(), externalBus, null);
+        configServiceApiRegistration = context.registerService(PluginConfigServiceApi.class.getName(), configServiceApi, null);
+    }
+
+    @Override
+    public void stop(final BundleContext context) throws Exception {
+        if (accountUserApiRegistration != null) {
+            accountUserApiRegistration.unregister();
+            accountUserApiRegistration = null;
+        }
+        if (analyticsSanityApiRegistration != null) {
+            analyticsSanityApiRegistration.unregister();
+            analyticsSanityApiRegistration = null;
+        }
+        if (analyticsUserApiRegistration != null) {
+            analyticsUserApiRegistration.unregister();
+            analyticsUserApiRegistration = null;
+        }
+        if (catalogUserApiRegistration != null) {
+            catalogUserApiRegistration.unregister();
+            catalogUserApiRegistration = null;
+        }
+        if (entitlementMigrationApiRegistration != null) {
+            entitlementMigrationApiRegistration.unregister();
+            entitlementMigrationApiRegistration = null;
+        }
+        if (entitlementTimelineApiRegistration != null) {
+            entitlementTimelineApiRegistration.unregister();
+            entitlementTimelineApiRegistration = null;
+        }
+        if (entitlementTransferApiRegistration != null) {
+            entitlementTransferApiRegistration.unregister();
+            entitlementTransferApiRegistration = null;
+        }
+        if (entitlementUserApiRegistration != null) {
+            entitlementUserApiRegistration.unregister();
+            entitlementUserApiRegistration = null;
+        }
+        if (invoiceMigrationApiRegistration != null) {
+            invoiceMigrationApiRegistration.unregister();
+            invoiceMigrationApiRegistration = null;
+        }
+        if (invoicePaymentApiRegistration != null) {
+            invoicePaymentApiRegistration.unregister();
+            invoicePaymentApiRegistration = null;
+        }
+        if (invoiceUserApiRegistration != null) {
+            invoiceUserApiRegistration.unregister();
+            invoiceUserApiRegistration = null;
+        }
+        if (meterUserApiRegistration != null) {
+            meterUserApiRegistration.unregister();
+            meterUserApiRegistration = null;
+        }
+        if (overdueUserApiRegistration != null) {
+            overdueUserApiRegistration.unregister();
+            overdueUserApiRegistration = null;
+        }
+        if (paymentApiRegistration != null) {
+            paymentApiRegistration.unregister();
+            paymentApiRegistration = null;
+        }
+        if (tenantUserApiRegistration != null) {
+            tenantUserApiRegistration.unregister();
+            tenantUserApiRegistration = null;
+        }
+        if (usageUserApiRegistration != null) {
+            usageUserApiRegistration.unregister();
+            usageUserApiRegistration = null;
+        }
+        if (auditUserApiRegistration != null) {
+            auditUserApiRegistration.unregister();
+            auditUserApiRegistration = null;
+        }
+        if (customFieldUserApiRegistration != null) {
+            customFieldUserApiRegistration.unregister();
+            customFieldUserApiRegistration = null;
+        }
+        if (exportUserApiRegistration != null) {
+            exportUserApiRegistration.unregister();
+            exportUserApiRegistration = null;
+        }
+        if (tagUserApiRegistration != null) {
+            tagUserApiRegistration.unregister();
+            tagUserApiRegistration = null;
+        }
+        if (externalBusRegistration != null) {
+            externalBusRegistration.unregister();
+            externalBusRegistration = null;
+        }
+
+        if (configServiceApiRegistration != null) {
+            configServiceApiRegistration.unregister();
+            configServiceApiRegistration = null;
+        }
+    }
+
+    //    public PaymentPluginApi getPaymentPluginApiForPlugin(final String pluginName) {
+    //        try {
+    //            final ServiceReference<PaymentPluginApi>[] paymentApiReferences = (ServiceReference<PaymentPluginApi>[]) context.getServiceReferences(PaymentPluginApi.class.getName(), "(name=hello)");
+    //            final PaymentPluginApi pluginApi = context.getService(paymentApiReferences[0]);
+    //            return pluginApi;
+    //        } catch (InvalidSyntaxException e) {
+    //            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
+    //        } finally {
+    //            //context.ungetService(paymentApiReferences[0]);
+    //            // STEPH TODO leak reference here
+    //        }
+    //        return null;
+    //    }
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/pluginconf/DefaultPluginConfig.java b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/DefaultPluginConfig.java
new file mode 100644
index 0000000..0d428b7
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/DefaultPluginConfig.java
@@ -0,0 +1,105 @@
+/*
+ * 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.pluginconf;
+
+import java.util.Properties;
+
+import com.ning.billing.osgi.api.config.PluginConfig;
+
+public abstract class DefaultPluginConfig implements PluginConfig {
+
+    private static final String PROP_PLUGIN_TYPE_NAME = "pluginType";
+
+    private final String pluginName;
+    private final PluginType pluginType;
+    private final String version;
+
+    public DefaultPluginConfig(final String pluginName, final String version, final Properties props) {
+        this.pluginName = pluginName;
+        this.version = version;
+        this.pluginType = PluginType.valueOf(props.getProperty(PROP_PLUGIN_TYPE_NAME, PluginType.__UNKNOWN__.toString()));
+    }
+
+    @Override
+    public String getPluginName() {
+        return pluginName;
+    }
+
+    @Override
+    public PluginType getPluginType() {
+        return pluginType;
+    }
+
+    @Override
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public String getPluginVersionnedName() {
+        return pluginName + "-" + version;
+    }
+
+    @Override
+    public abstract PluginLanguage getPluginLanguage();
+
+    protected abstract void validate() throws PluginConfigException;
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultPluginConfig that = (DefaultPluginConfig) o;
+
+        if (!pluginName.equals(that.pluginName)) {
+            return false;
+        }
+        if (pluginType != that.pluginType) {
+            return false;
+        }
+        if (!version.equals(that.version)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = pluginName.hashCode();
+        result = 31 * result + pluginType.hashCode();
+        result = 31 * result + version.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultPluginConfig");
+        sb.append("{pluginName='").append(pluginName).append('\'');
+        sb.append(", pluginType=").append(pluginType);
+        sb.append(", version='").append(version).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/pluginconf/DefaultPluginConfigServiceApi.java b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/DefaultPluginConfigServiceApi.java
new file mode 100644
index 0000000..fd37212
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/DefaultPluginConfigServiceApi.java
@@ -0,0 +1,56 @@
+/*
+ * 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.pluginconf;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.ning.billing.osgi.api.config.PluginConfigServiceApi;
+import com.ning.billing.osgi.api.config.PluginJavaConfig;
+import com.ning.billing.osgi.api.config.PluginRubyConfig;
+
+public class DefaultPluginConfigServiceApi implements PluginConfigServiceApi {
+
+    private final Map<Long, PluginJavaConfig> javaConfigMappings = new HashMap<Long, PluginJavaConfig>();
+    private final Map<Long, PluginRubyConfig> rubyConfigMappings = new HashMap<Long, PluginRubyConfig>();
+
+    @Override
+    public PluginJavaConfig getPluginJavaConfig(final long bundleId) {
+        synchronized (javaConfigMappings) {
+            return javaConfigMappings.get(bundleId);
+        }
+    }
+
+    @Override
+    public PluginRubyConfig getPluginRubyConfig(final long bundleId) {
+        synchronized (rubyConfigMappings) {
+            return rubyConfigMappings.get(bundleId);
+        }
+    }
+
+    public void registerBundle(final Long bundleId, final PluginJavaConfig javaConfig) {
+        synchronized (javaConfigMappings) {
+            javaConfigMappings.put(bundleId, javaConfig);
+        }
+    }
+
+    public void registerBundle(final Long bundleId, final PluginRubyConfig rubyConfig) {
+        synchronized (rubyConfigMappings) {
+            rubyConfigMappings.put(bundleId, rubyConfig);
+        }
+    }
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/pluginconf/DefaultPluginJavaConfig.java b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/DefaultPluginJavaConfig.java
new file mode 100644
index 0000000..92415e6
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/DefaultPluginJavaConfig.java
@@ -0,0 +1,64 @@
+/*
+ * 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.pluginconf;
+
+import java.io.File;
+import java.util.Properties;
+
+import com.ning.billing.osgi.api.config.PluginJavaConfig;
+
+public class DefaultPluginJavaConfig extends DefaultPluginConfig implements PluginJavaConfig {
+
+    private final String bundleJarPath;
+
+    public DefaultPluginJavaConfig(final String pluginName, final String version, final File pluginVersionRoot, final Properties props) throws PluginConfigException {
+        super(pluginName, version, props);
+        this.bundleJarPath = extractJarPath(pluginVersionRoot);
+        validate();
+    }
+
+    private String extractJarPath(final File pluginVersionRoot) {
+        final File[] files = pluginVersionRoot.listFiles();
+        if (files == null) {
+            return null;
+        }
+
+        for (final File f : files) {
+            if (f.isFile() && f.getName().endsWith(".jar")) {
+                return f.getAbsolutePath();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String getBundleJarPath() {
+        return bundleJarPath;
+    }
+
+    @Override
+    public PluginLanguage getPluginLanguage() {
+        return PluginLanguage.JAVA;
+    }
+
+    @Override
+    protected void validate() throws PluginConfigException {
+        if (bundleJarPath == null) {
+            throw new PluginConfigException("Invalid plugin " + getPluginVersionnedName() + ": cannot find jar file");
+        }
+    }
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/pluginconf/DefaultPluginRubyConfig.java b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/DefaultPluginRubyConfig.java
new file mode 100644
index 0000000..f39f66e
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/DefaultPluginRubyConfig.java
@@ -0,0 +1,64 @@
+/*
+ * 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.pluginconf;
+
+import java.io.File;
+import java.util.Properties;
+
+import com.ning.billing.osgi.api.config.PluginRubyConfig;
+
+public class DefaultPluginRubyConfig extends DefaultPluginConfig implements PluginRubyConfig {
+
+    private static final String INSTALLATION_GEM_NAME = "gems";
+
+    private static final String PROP_RUBY_MAIN_CLASS_NAME = "mainClass";
+
+    private final String rubyMainClass;
+    private final File rubyLoadDir;
+
+    public DefaultPluginRubyConfig(final String pluginName, final String version, final File pluginVersionRoot, final Properties props) throws PluginConfigException {
+        super(pluginName, version, props);
+        this.rubyMainClass = props.getProperty(PROP_RUBY_MAIN_CLASS_NAME);
+        this.rubyLoadDir = new File(pluginVersionRoot.getAbsolutePath() + "/" + INSTALLATION_GEM_NAME);
+        validate();
+    }
+
+    @Override
+    protected void validate() throws PluginConfigException {
+        if (rubyMainClass == null) {
+            throw new PluginConfigException("Missing property " + PROP_RUBY_MAIN_CLASS_NAME + " for plugin " + getPluginVersionnedName());
+        }
+        if (rubyLoadDir == null || !rubyLoadDir.exists() || !rubyLoadDir.isDirectory()) {
+            throw new PluginConfigException("Missing gem installation directory " + rubyLoadDir.getAbsolutePath() + " for plugin " + getPluginVersionnedName());
+        }
+    }
+
+    @Override
+    public String getRubyMainClass() {
+        return rubyMainClass;
+    }
+
+    @Override
+    public String getRubyLoadDir() {
+        return rubyLoadDir.getAbsolutePath();
+    }
+
+    @Override
+    public PluginLanguage getPluginLanguage() {
+        return PluginLanguage.RUBY;
+    }
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/pluginconf/PluginConfigException.java b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/PluginConfigException.java
new file mode 100644
index 0000000..aa8ebd1
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/PluginConfigException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.pluginconf;
+
+import java.io.IOException;
+
+public class PluginConfigException extends Exception {
+
+    public PluginConfigException(final String msg) {
+        super(msg);
+    }
+
+    public PluginConfigException(final String msg, final IOException ioe) {
+        super(msg, ioe);
+    }
+}
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
new file mode 100644
index 0000000..acdba18
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/PluginFinder.java
@@ -0,0 +1,212 @@
+/*
+ * 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.pluginconf;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.osgi.api.config.PluginConfig;
+import com.ning.billing.osgi.api.config.PluginConfig.PluginLanguage;
+import com.ning.billing.osgi.api.config.PluginJavaConfig;
+import com.ning.billing.osgi.api.config.PluginRubyConfig;
+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);
+
+    private final OSGIConfig osgiConfig;
+    private final Map<String, List<? extends PluginConfig>> allPlugins;
+
+    @Inject
+    public PluginFinder(final OSGIConfig osgiConfig) {
+        this.osgiConfig = osgiConfig;
+        this.allPlugins = new HashMap<String, List<? extends PluginConfig>>();
+    }
+
+    public List<PluginJavaConfig> getLatestJavaPlugins() throws PluginConfigException {
+        return getLatestPluginForLanguage(PluginLanguage.JAVA);
+    }
+
+    public List<PluginRubyConfig> getLatestRubyPlugins() throws PluginConfigException {
+        return getLatestPluginForLanguage(PluginLanguage.RUBY);
+    }
+
+    public <T extends PluginConfig> List<T> getVersionsForPlugin(final String lookupName) throws PluginConfigException {
+        loadPluginsIfRequired();
+
+        final List<T> result = new LinkedList<T>();
+        for (final String pluginName : allPlugins.keySet()) {
+            if (pluginName.equals(lookupName)) {
+                for (final PluginConfig cur : allPlugins.get(pluginName)) {
+                    result.add((T) cur);
+                }
+            }
+        }
+        return result;
+    }
+
+    private <T extends PluginConfig> List<T> getLatestPluginForLanguage(final PluginLanguage pluginLanguage) throws PluginConfigException {
+        loadPluginsIfRequired();
+
+        final List<T> result = new LinkedList<T>();
+        for (final String pluginName : allPlugins.keySet()) {
+            final T plugin = (T) allPlugins.get(pluginName).get(0);
+            if (pluginLanguage != plugin.getPluginLanguage()) {
+                continue;
+            }
+            result.add(plugin);
+        }
+
+        return result;
+    }
+
+    private void loadPluginsIfRequired() throws PluginConfigException {
+        synchronized (allPlugins) {
+
+            if (allPlugins.size() > 0) {
+                return;
+            }
+
+            loadPluginsForLanguage(PluginLanguage.RUBY);
+            loadPluginsForLanguage(PluginLanguage.JAVA);
+
+            // Order for each plugin by versions starting from highest version
+            for (final String pluginName : allPlugins.keySet()) {
+                final List<? extends PluginConfig> value = allPlugins.get(pluginName);
+                Collections.sort(value, new Comparator<PluginConfig>() {
+                    @Override
+                    public int compare(final PluginConfig o1, final PluginConfig o2) {
+                        return -(o1.getVersion().compareTo(o2.getVersion()));
+                    }
+                });
+            }
+        }
+    }
+
+    private <T extends PluginConfig> void loadPluginsForLanguage(final PluginLanguage pluginLanguage) throws PluginConfigException {
+        final String rootDirPath = osgiConfig.getRootInstallationDir() + "/" + pluginLanguage.toString().toLowerCase();
+        final File rootDir = new File(rootDirPath);
+        if (!rootDir.exists() || !rootDir.isDirectory()) {
+            logger.warn("Configuration root dir {} is not a valid directory", rootDirPath);
+            return;
+        }
+
+        final File[] files = rootDir.listFiles();
+        if (files == null) {
+            return;
+        }
+        for (final File curPlugin : files) {
+            // Skip any non directory entry
+            if (!curPlugin.isDirectory()) {
+                logger.warn("Skipping entry {} in directory {}", curPlugin.getName(), rootDir.getAbsolutePath());
+                continue;
+            }
+            final String pluginName = curPlugin.getName();
+
+            final File[] filesInDir = curPlugin.listFiles();
+            if (filesInDir == null) {
+                continue;
+            }
+            for (final File curVersion : filesInDir) {
+                // Skip any non directory entry
+                if (!curVersion.isDirectory()) {
+                    logger.warn("Skipping entry {} in directory {}", curPlugin.getName(), rootDir.getAbsolutePath());
+                    continue;
+                }
+                final String version = curVersion.getName();
+
+                final T plugin = extractPluginConfig(pluginLanguage, pluginName, version, curVersion);
+                List<T> curPluginVersionlist = (List<T>) allPlugins.get(plugin.getPluginName());
+                if (curPluginVersionlist == null) {
+                    curPluginVersionlist = new LinkedList<T>();
+                    allPlugins.put(plugin.getPluginName(), curPluginVersionlist);
+                }
+                curPluginVersionlist.add(plugin);
+                logger.info("Adding plugin {} ", plugin.getPluginVersionnedName());
+            }
+        }
+    }
+
+    private <T extends PluginConfig> T extractPluginConfig(final PluginLanguage pluginLanguage, final String pluginName, final String pluginVersion, final File pluginVersionDir) throws PluginConfigException {
+        T result;
+        Properties props = null;
+        try {
+            final File[] files = pluginVersionDir.listFiles();
+            if (files == null) {
+                throw new PluginConfigException("Unable to list files in " + pluginVersionDir.getAbsolutePath());
+            }
+
+            for (final File cur : files) {
+                if (cur.isFile() && cur.getName().equals(INSTALATION_PROPERTIES)) {
+                    props = readPluginConfigurationFile(cur);
+                }
+                if (props != null) {
+                    break;
+                }
+            }
+
+            if (pluginLanguage == PluginLanguage.RUBY && props == null) {
+                throw new PluginConfigException("Invalid plugin configuration file for " + pluginName + "-" + pluginVersion);
+            }
+
+        } catch (IOException e) {
+            throw new PluginConfigException("Failed to read property file for " + pluginName + "-" + pluginVersion, e);
+        }
+        switch (pluginLanguage) {
+            case RUBY:
+                result = (T) new DefaultPluginRubyConfig(pluginName, pluginVersion, pluginVersionDir, props);
+                break;
+            case JAVA:
+                result = (T) new DefaultPluginJavaConfig(pluginName, pluginVersion, pluginVersionDir, (props == null) ? new Properties() : props);
+                break;
+            default:
+                throw new RuntimeException("Unknown plugin language " + pluginLanguage);
+        }
+        return result;
+    }
+
+    private Properties readPluginConfigurationFile(final File config) throws IOException {
+        final Properties props = new Properties();
+        final BufferedReader br = new BufferedReader(new FileReader(config));
+        String line;
+        while ((line = br.readLine()) != null) {
+            final String[] parts = line.split("\\s*=\\s*");
+            final String key = parts[0];
+            final String value = parts[1];
+            props.put(key, value);
+        }
+        br.close();
+        return props;
+    }
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/pluginconf/PuginConfServiceApi.java b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/PuginConfServiceApi.java
new file mode 100644
index 0000000..e46ca4b
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/pluginconf/PuginConfServiceApi.java
@@ -0,0 +1,21 @@
+/*
+ * 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.pluginconf;
+
+public class PuginConfServiceApi {
+
+}
diff --git a/osgi-bundles/hello/pom.xml b/osgi-bundles/hello/pom.xml
new file mode 100644
index 0000000..5785a73
--- /dev/null
+++ b/osgi-bundles/hello/pom.xml
@@ -0,0 +1,107 @@
+<?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.52-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>killbill-osgi-bundles-hello</artifactId>
+    <name>Killbill billing platform: OSGI Hello World 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.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.hello.HelloActivator</Bundle-Activator>
+                        <Import-Package>
+                            *;resolution:=optional
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>process-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-hello</id>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <phase>package</phase>
+                        <configuration>
+                            <createSourcesJar>true</createSourcesJar>
+                            <shadedArtifactAttached>true</shadedArtifactAttached>
+                            <shadedClassifierName>jar-with-dependencies</shadedClassifierName>
+                            <filters>
+                                <filter>
+                                    <artifact>${project.groupId}:${project.artifactId}</artifact>
+                                </filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/osgi-bundles/hello/src/main/java/com/ning/billing/osgi/bundles/hello/HelloActivator.java b/osgi-bundles/hello/src/main/java/com/ning/billing/osgi/bundles/hello/HelloActivator.java
new file mode 100644
index 0000000..3b83860
--- /dev/null
+++ b/osgi-bundles/hello/src/main/java/com/ning/billing/osgi/bundles/hello/HelloActivator.java
@@ -0,0 +1,181 @@
+/*
+ * 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.hello;
+
+import java.math.BigDecimal;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+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 com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.beatrix.bus.api.ExtBusEvent;
+import com.ning.billing.beatrix.bus.api.ExternalBus;
+import com.ning.billing.payment.api.PaymentMethodPlugin;
+import com.ning.billing.payment.plugin.api.PaymentInfoPlugin;
+import com.ning.billing.payment.plugin.api.PaymentPluginApi;
+import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.TenantContext;
+
+import com.google.common.eventbus.Subscribe;
+
+public class HelloActivator implements BundleActivator {
+
+    private volatile boolean isRunning;
+    private volatile ServiceRegistration paymentInfoPluginRegistration;
+
+    @Override
+    public void start(final BundleContext context) {
+        isRunning = true;
+        System.out.println("Hello world from HelloActivator!");
+
+        doSomeWorkWithKillbillApis(context);
+        registerForKillbillEvents(context);
+        registerPaymentApi(context);
+    }
+
+    @Override
+    public void stop(final BundleContext context) {
+        isRunning = false;
+        System.out.println("Good bye world from HelloActivator!");
+    }
+
+    private void doSomeWorkWithKillbillApis(final BundleContext context) {
+        final TenantContext tenantContext = new TenantContext() {
+            @Override
+            public UUID getTenantId() {
+                return new UUID(12, 42);
+            }
+        };
+
+        final Thread th = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                while (isRunning) {
+                    @SuppressWarnings("unchecked")
+                    final ServiceReference<AccountUserApi> accountUserApiReference = (ServiceReference<AccountUserApi>) context.getServiceReference(AccountUserApi.class.getName());
+                    final AccountUserApi accountUserApi = context.getService(accountUserApiReference);
+
+                    try {
+                        final List<Account> accounts = accountUserApi.getAccounts(tenantContext);
+                        System.out.println("Found " + accounts.size() + " accounts");
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                        System.err.println("Interrupted in HelloActivator");
+                    } catch (Exception e) {
+                        System.err.println("Error in HelloActivator: " + e.getLocalizedMessage());
+                    } finally {
+                        if (accountUserApiReference != null) {
+                            context.ungetService(accountUserApiReference);
+                        }
+                    }
+                }
+            }
+        });
+        th.start();
+    }
+
+    private void registerForKillbillEvents(final BundleContext context) {
+        @SuppressWarnings("unchecked")
+        final ServiceReference<ExternalBus> externalBusReference = (ServiceReference<ExternalBus>) context.getServiceReference(ExternalBus.class.getName());
+        try {
+            final ExternalBus externalBus = context.getService(externalBusReference);
+            externalBus.register(this);
+        } catch (Exception e) {
+            System.err.println("Error in HelloActivator: " + e.getLocalizedMessage());
+        } finally {
+            if (externalBusReference != null) {
+                context.ungetService(externalBusReference);
+            }
+        }
+    }
+
+    private void registerPaymentApi(final BundleContext context) {
+        final Dictionary props = new Hashtable();
+        props.put("name", "hello");
+
+        this.paymentInfoPluginRegistration = context.registerService(PaymentPluginApi.class.getName(), new PaymentPluginApi() {
+            @Override
+            public String getName() {
+                return "helloName";
+            }
+
+            @Override
+            public PaymentInfoPlugin processPayment(final String externalAccountKey, final UUID paymentId, final BigDecimal amount, final CallContext context) throws PaymentPluginApiException {
+                return null;
+            }
+
+            @Override
+            public PaymentInfoPlugin getPaymentInfo(final UUID paymentId, final TenantContext context) throws PaymentPluginApiException {
+                return null;
+            }
+
+            @Override
+            public void processRefund(final Account account, final UUID paymentId, final BigDecimal refundAmount, final CallContext context) throws PaymentPluginApiException {
+            }
+
+            @Override
+            public int getNbRefundForPaymentAmount(final Account account, final UUID paymentId, final BigDecimal refundAmount, final TenantContext context) throws PaymentPluginApiException {
+                return 0;
+            }
+
+            @Override
+            public String createPaymentProviderAccount(final Account account, final CallContext context) throws PaymentPluginApiException {
+                return null;
+            }
+
+            @Override
+            public List<PaymentMethodPlugin> getPaymentMethodDetails(final String accountKey, final TenantContext context) throws PaymentPluginApiException {
+                return null;
+            }
+
+            @Override
+            public PaymentMethodPlugin getPaymentMethodDetail(final String accountKey, final String externalPaymentMethodId, final TenantContext context) throws PaymentPluginApiException {
+                return null;
+            }
+
+            @Override
+            public String addPaymentMethod(final String accountKey, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final CallContext context) throws PaymentPluginApiException {
+                return null;
+            }
+
+            @Override
+            public void updatePaymentMethod(final String accountKey, final PaymentMethodPlugin paymentMethodProps, final CallContext context) throws PaymentPluginApiException {
+            }
+
+            @Override
+            public void deletePaymentMethod(final String accountKey, final String externalPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+            }
+
+            @Override
+            public void setDefaultPaymentMethod(final String accountKey, final String externalPaymentId, final CallContext context) throws PaymentPluginApiException {
+            }
+        }, props);
+    }
+
+    @Subscribe
+    public void handleKillbillEvent(final ExtBusEvent killbillEvent) {
+        System.out.println("Received external event " + killbillEvent.toString());
+    }
+}
diff --git a/osgi-bundles/jruby/pom.xml b/osgi-bundles/jruby/pom.xml
new file mode 100644
index 0000000..d493e70
--- /dev/null
+++ b/osgi-bundles/jruby/pom.xml
@@ -0,0 +1,111 @@
+<?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.52-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>killbill-osgi-bundles-jruby</artifactId>
+    <name>Killbill billing platform: OSGI JRuby 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.jruby</groupId>
+            <artifactId>jruby-complete</artifactId>
+            <version>1.7.1</version>
+        </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.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.jruby.Activator</Bundle-Activator>
+                        <Import-Package>
+                            *;resolution:=optional,org.osgi.service.log;resolution:=optional,org.jruby;resolution:=optional;version="[1.7,2)",javax.management,javax.crypto,javax.net.ssl,javax.security.auth.x500;resolution:=optional
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>process-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-jruby</id>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <phase>package</phase>
+                        <configuration>
+                            <createSourcesJar>true</createSourcesJar>
+                            <shadedArtifactAttached>true</shadedArtifactAttached>
+                            <shadedClassifierName>jar-with-dependencies</shadedClassifierName>
+                            <filters>
+                                <filter>
+                                    <artifact>${project.groupId}:${project.artifactId}</artifact>
+                                </filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/Activator.java b/osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/Activator.java
new file mode 100644
index 0000000..a8e99eb
--- /dev/null
+++ b/osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/Activator.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2010-2012 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.jruby;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.jruby.embed.ScriptingContainer;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.analytics.api.sanity.AnalyticsSanityApi;
+import com.ning.billing.analytics.api.user.AnalyticsUserApi;
+import com.ning.billing.catalog.api.CatalogUserApi;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.transfer.EntitlementTransferApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.invoice.api.InvoiceMigrationApi;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.meter.api.MeterUserApi;
+import com.ning.billing.osgi.api.config.PluginConfig.PluginType;
+import com.ning.billing.osgi.api.config.PluginConfigServiceApi;
+import com.ning.billing.osgi.api.config.PluginRubyConfig;
+import com.ning.billing.overdue.OverdueUserApi;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.tenant.api.TenantUserApi;
+import com.ning.billing.usage.api.UsageUserApi;
+import com.ning.billing.util.api.AuditUserApi;
+import com.ning.billing.util.api.CustomFieldUserApi;
+import com.ning.billing.util.api.ExportUserApi;
+import com.ning.billing.util.api.TagUserApi;
+
+public class Activator implements BundleActivator {
+
+    private final List<ServiceReference<?>> serviceReferences = new ArrayList<ServiceReference<?>>();
+
+    private LogService logger = null;
+    private JRubyPlugin plugin = null;
+
+    public void start(final BundleContext context) throws Exception {
+        logger = retrieveApi(context, LogService.class);
+        log(LogService.LOG_INFO, "JRuby bundle activated");
+
+        doMagicToMakeJRubyAndFelixHappy();
+
+        // Retrieve the plugin config
+        final PluginRubyConfig rubyConfig = retrievePluginRubyConfig(context);
+
+        // Setup JRuby
+        final ScriptingContainer scriptingContainer = setupScriptingContainer(rubyConfig);
+        if (PluginType.NOTIFICATION.equals(rubyConfig.getPluginType())) {
+            plugin = new JRubyNotificationPlugin(rubyConfig, scriptingContainer, logger);
+        } else if (PluginType.PAYMENT.equals(rubyConfig.getPluginType())) {
+            plugin = new JRubyPaymentPlugin(rubyConfig, scriptingContainer, logger);
+        }
+
+        // Validate and instantiate the plugin
+        final Map<String, Object> killbillApis = retrieveKillbillApis(context);
+        plugin.instantiatePlugin(killbillApis);
+
+        log(LogService.LOG_INFO, "Starting JRuby plugin " + plugin.getPluginMainClass());
+        plugin.startPlugin(context);
+    }
+
+    private PluginRubyConfig retrievePluginRubyConfig(final BundleContext context) {
+        @SuppressWarnings("unchecked")
+        final ServiceReference<PluginConfigServiceApi> pluginConfigServiceApiServiceReference = (ServiceReference<PluginConfigServiceApi>) context.getServiceReference(PluginConfigServiceApi.class.getName());
+        final PluginConfigServiceApi pluginConfigServiceApi = context.getService(pluginConfigServiceApiServiceReference);
+        return pluginConfigServiceApi.getPluginRubyConfig(context.getBundle().getBundleId());
+    }
+
+    // JRuby/Felix specifics, it works out of the box on Equinox.
+    // Other OSGI frameworks are untested.
+    private void doMagicToMakeJRubyAndFelixHappy() {
+        // Tell JRuby to use the correct class loader
+        Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
+    }
+
+    private ScriptingContainer setupScriptingContainer(final PluginRubyConfig rubyConfig) {
+        final ScriptingContainer scriptingContainer = new ScriptingContainer();
+
+        // Set the load paths instead of adding, to avoid looking at the filesystem
+        scriptingContainer.setLoadPaths(Collections.<String>singletonList(rubyConfig.getRubyLoadDir()));
+
+        return scriptingContainer;
+    }
+
+    public void stop(final BundleContext context) throws Exception {
+        log(LogService.LOG_INFO, "Stopping JRuby plugin " + plugin.getPluginMainClass());
+        plugin.stopPlugin(context);
+
+        for (final ServiceReference apiReference : serviceReferences) {
+            context.ungetService(apiReference);
+        }
+    }
+
+    private Map<String, Object> retrieveKillbillApis(final BundleContext context) {
+        final Map<String, Object> killbillUserApis = new HashMap<String, Object>();
+
+        // See killbill/plugin.rb for the naming convention magic
+        killbillUserApis.put("account_user_api", retrieveApi(context, AccountUserApi.class));
+        killbillUserApis.put("analytics_sanity_api", retrieveApi(context, AnalyticsSanityApi.class));
+        killbillUserApis.put("analytics_user_api", retrieveApi(context, AnalyticsUserApi.class));
+        killbillUserApis.put("catalog_user_api", retrieveApi(context, CatalogUserApi.class));
+        killbillUserApis.put("entitlement_migration_api", retrieveApi(context, EntitlementMigrationApi.class));
+        killbillUserApis.put("entitlement_timeline_api", retrieveApi(context, EntitlementTimelineApi.class));
+        killbillUserApis.put("entitlement_transfer_api", retrieveApi(context, EntitlementTransferApi.class));
+        killbillUserApis.put("entitlement_user_api", retrieveApi(context, EntitlementUserApi.class));
+        killbillUserApis.put("invoice_migration_api", retrieveApi(context, InvoiceMigrationApi.class));
+        killbillUserApis.put("invoice_payment_api", retrieveApi(context, InvoicePaymentApi.class));
+        killbillUserApis.put("invoice_user_api", retrieveApi(context, InvoiceUserApi.class));
+        killbillUserApis.put("meter_user_api", retrieveApi(context, MeterUserApi.class));
+        killbillUserApis.put("overdue_user_api", retrieveApi(context, OverdueUserApi.class));
+        killbillUserApis.put("payment_api", retrieveApi(context, PaymentApi.class));
+        killbillUserApis.put("tenant_user_api", retrieveApi(context, TenantUserApi.class));
+        killbillUserApis.put("usage_user_api", retrieveApi(context, UsageUserApi.class));
+        killbillUserApis.put("audit_user_api", retrieveApi(context, AuditUserApi.class));
+        killbillUserApis.put("custom_field_user_api", retrieveApi(context, CustomFieldUserApi.class));
+        killbillUserApis.put("export_user_api", retrieveApi(context, ExportUserApi.class));
+        killbillUserApis.put("tag_user_api", retrieveApi(context, TagUserApi.class));
+
+        return killbillUserApis;
+    }
+
+    /**
+     * Retrieve a service class (e.g. Killbill API)
+     *
+     * @param context OSGI Bundle context
+     * @param clazz   service class to retrieve
+     * @param <T>     class type to retrieve
+     * @return instance of the service class
+     */
+    private <T> T retrieveApi(final BundleContext context, final Class<T> clazz) {
+        final ServiceReference<T> apiReference = context.getServiceReference(clazz);
+        if (apiReference != null) {
+            // Keep references to stop the bundle properly
+            serviceReferences.add(apiReference);
+
+            return context.getService(apiReference);
+        } else {
+            return null;
+        }
+    }
+
+    private void log(final int level, final String message) {
+        if (logger != null) {
+            logger.log(level, message);
+        }
+    }
+}
diff --git a/osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyNotificationPlugin.java b/osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyNotificationPlugin.java
new file mode 100644
index 0000000..8c1d7bb
--- /dev/null
+++ b/osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyNotificationPlugin.java
@@ -0,0 +1,64 @@
+/*
+ * 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.jruby;
+
+import javax.annotation.Nullable;
+
+import org.jruby.embed.ScriptingContainer;
+import org.jruby.javasupport.JavaEmbedUtils;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+
+import com.ning.billing.beatrix.bus.api.ExtBusEvent;
+import com.ning.billing.beatrix.bus.api.ExternalBus;
+import com.ning.billing.osgi.api.config.PluginRubyConfig;
+
+import com.google.common.eventbus.Subscribe;
+
+public class JRubyNotificationPlugin extends JRubyPlugin {
+
+    public JRubyNotificationPlugin(final PluginRubyConfig config, final ScriptingContainer container, @Nullable final LogService logger) {
+        super(config, container, logger);
+    }
+
+    @Override
+    public void startPlugin(final BundleContext context) {
+        super.startPlugin(context);
+
+        @SuppressWarnings("unchecked")
+        final ServiceReference<ExternalBus> externalBusReference = (ServiceReference<ExternalBus>) context.getServiceReference(ExternalBus.class.getName());
+        try {
+            final ExternalBus externalBus = context.getService(externalBusReference);
+            externalBus.register(this);
+        } catch (Exception e) {
+            log(LogService.LOG_WARNING, "Error registering notification plugin service", e);
+        } finally {
+            if (externalBusReference != null) {
+                context.ungetService(externalBusReference);
+            }
+        }
+    }
+
+    @Subscribe
+    public void onEvent(final ExtBusEvent event) {
+        checkValidNotificationPlugin();
+        checkPluginIsRunning();
+
+        pluginInstance.callMethod("on_event", JavaEmbedUtils.javaToRuby(getRuntime(), event));
+    }
+}
diff --git a/osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPaymentPlugin.java b/osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPaymentPlugin.java
new file mode 100644
index 0000000..9296655
--- /dev/null
+++ b/osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPaymentPlugin.java
@@ -0,0 +1,187 @@
+/*
+ * 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.jruby;
+
+import java.math.BigDecimal;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.jruby.Ruby;
+import org.jruby.embed.ScriptingContainer;
+import org.jruby.javasupport.JavaEmbedUtils;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogService;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.osgi.api.config.PluginRubyConfig;
+import com.ning.billing.payment.api.PaymentMethodPlugin;
+import com.ning.billing.payment.plugin.api.PaymentInfoPlugin;
+import com.ning.billing.payment.plugin.api.PaymentPluginApi;
+import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.TenantContext;
+
+public class JRubyPaymentPlugin extends JRubyPlugin implements PaymentPluginApi {
+
+    private volatile ServiceRegistration<PaymentPluginApi> paymentInfoPluginRegistration;
+
+    public JRubyPaymentPlugin(final PluginRubyConfig config, final ScriptingContainer container, @Nullable final LogService logger) {
+        super(config, container, logger);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void startPlugin(final BundleContext context) {
+        super.startPlugin(context);
+
+        final Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put("name", pluginMainClass);
+
+        paymentInfoPluginRegistration = (ServiceRegistration<PaymentPluginApi>) context.registerService(PaymentPluginApi.class.getName(), this, props);
+    }
+
+    @Override
+    public void stopPlugin(final BundleContext context) {
+        paymentInfoPluginRegistration.unregister();
+
+        super.stopPlugin(context);
+    }
+
+    @Override
+    public String getName() {
+        return pluginMainClass;
+    }
+
+    @Override
+    public PaymentInfoPlugin processPayment(final String externalAccountKey, final UUID paymentId, final BigDecimal amount, final CallContext context) throws PaymentPluginApiException {
+        checkValidPaymentPlugin();
+        checkPluginIsRunning();
+
+        final Ruby runtime = getRuntime();
+        pluginInstance.callMethod("charge",
+                                  JavaEmbedUtils.javaToRuby(runtime, externalAccountKey),
+                                  JavaEmbedUtils.javaToRuby(runtime, paymentId.toString()),
+                                  JavaEmbedUtils.javaToRuby(runtime, amount.longValue() * 100));
+
+        // TODO
+        return null;
+    }
+
+    @Override
+    public PaymentInfoPlugin getPaymentInfo(final UUID paymentId, final TenantContext context) throws PaymentPluginApiException {
+        checkValidPaymentPlugin();
+        checkPluginIsRunning();
+
+        pluginInstance.callMethod("get_payment_info", JavaEmbedUtils.javaToRuby(getRuntime(), paymentId.toString()));
+
+        // TODO
+        return null;
+    }
+
+    @Override
+    public void processRefund(final Account account, final UUID paymentId, final BigDecimal refundAmount, final CallContext context) throws PaymentPluginApiException {
+        checkValidPaymentPlugin();
+        checkPluginIsRunning();
+
+        final Ruby runtime = getRuntime();
+        pluginInstance.callMethod("refund",
+                                  JavaEmbedUtils.javaToRuby(runtime, account.getExternalKey()),
+                                  JavaEmbedUtils.javaToRuby(runtime, paymentId.toString()),
+                                  JavaEmbedUtils.javaToRuby(runtime, refundAmount.longValue() * 100));
+    }
+
+    @Override
+    public int getNbRefundForPaymentAmount(final Account account, final UUID paymentId, final BigDecimal refundAmount, final TenantContext context) throws PaymentPluginApiException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String createPaymentProviderAccount(final Account account, final CallContext context) throws PaymentPluginApiException {
+        checkValidPaymentPlugin();
+        checkPluginIsRunning();
+
+        pluginInstance.callMethod("create_account", JavaEmbedUtils.javaToRuby(getRuntime(), account));
+
+        // TODO
+        return null;
+    }
+
+    @Override
+    public List<PaymentMethodPlugin> getPaymentMethodDetails(final String accountKey, final TenantContext context) throws PaymentPluginApiException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PaymentMethodPlugin getPaymentMethodDetail(final String accountKey, final String externalPaymentMethodId, final TenantContext context) throws PaymentPluginApiException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String addPaymentMethod(final String accountKey, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final CallContext context) throws PaymentPluginApiException {
+        checkValidPaymentPlugin();
+        checkPluginIsRunning();
+
+        final Ruby runtime = getRuntime();
+        pluginInstance.callMethod("add_payment_method",
+                                  JavaEmbedUtils.javaToRuby(runtime, accountKey),
+                                  JavaEmbedUtils.javaToRuby(runtime, paymentMethodProps));
+        if (setDefault) {
+            setDefaultPaymentMethod(accountKey, paymentMethodProps.getExternalPaymentMethodId(), context);
+        }
+
+        // TODO
+        return null;
+    }
+
+    @Override
+    public void updatePaymentMethod(final String accountKey, final PaymentMethodPlugin paymentMethodProps, final CallContext context) throws PaymentPluginApiException {
+        checkValidPaymentPlugin();
+        checkPluginIsRunning();
+
+        final Ruby runtime = getRuntime();
+        pluginInstance.callMethod("update_payment_method",
+                                  JavaEmbedUtils.javaToRuby(runtime, accountKey),
+                                  JavaEmbedUtils.javaToRuby(runtime, paymentMethodProps));
+    }
+
+    @Override
+    public void deletePaymentMethod(final String accountKey, final String externalPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+        checkValidPaymentPlugin();
+        checkPluginIsRunning();
+
+        final Ruby runtime = getRuntime();
+        pluginInstance.callMethod("delete_payment_method",
+                                  JavaEmbedUtils.javaToRuby(runtime, accountKey),
+                                  JavaEmbedUtils.javaToRuby(runtime, externalPaymentMethodId));
+    }
+
+    @Override
+    public void setDefaultPaymentMethod(final String accountKey, final String externalPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+        checkValidPaymentPlugin();
+        checkPluginIsRunning();
+
+        final Ruby runtime = getRuntime();
+        pluginInstance.callMethod("set_default_payment_method",
+                                  JavaEmbedUtils.javaToRuby(runtime, accountKey),
+                                  JavaEmbedUtils.javaToRuby(runtime, externalPaymentMethodId));
+    }
+}
diff --git a/osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPlugin.java b/osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPlugin.java
new file mode 100644
index 0000000..1a9423c
--- /dev/null
+++ b/osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPlugin.java
@@ -0,0 +1,189 @@
+/*
+ * 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.jruby;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import org.jruby.Ruby;
+import org.jruby.RubyObject;
+import org.jruby.embed.EvalFailedException;
+import org.jruby.embed.ScriptingContainer;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+
+import com.ning.billing.osgi.api.config.PluginRubyConfig;
+
+// Bridge between the OSGI bundle and the ruby plugin
+public abstract class JRubyPlugin {
+
+    // Killbill gem base classes
+    private static final String KILLBILL_PLUGIN_BASE = "Killbill::Plugin::PluginBase";
+    private static final String KILLBILL_PLUGIN_NOTIFICATION = "Killbill::Plugin::Notification";
+    private static final String KILLBILL_PLUGIN_PAYMENT = "Killbill::Plugin::Payment";
+
+    // Magic ruby variables
+    private static final String JAVA_APIS = "java_apis";
+    private static final String ACTIVE = "@active";
+
+    protected final LogService logger;
+    protected final String pluginGemName;
+    protected final String pluginMainClass;
+    protected final ScriptingContainer container;
+    protected final String pluginLibdir;
+
+    protected RubyObject pluginInstance;
+
+    private String cachedRequireLine = null;
+
+    public JRubyPlugin(final PluginRubyConfig config, final ScriptingContainer container, @Nullable final LogService logger) {
+        this.logger = logger;
+        this.pluginGemName = config.getPluginName();
+        this.pluginMainClass = config.getRubyMainClass();
+        this.container = container;
+        this.pluginLibdir = config.getRubyLoadDir();
+
+        // Path to the gem
+        if (pluginLibdir != null) {
+            container.setLoadPaths(Arrays.asList(pluginLibdir));
+        }
+    }
+
+    public String getPluginMainClass() {
+        return pluginMainClass;
+    }
+
+    public String getPluginLibdir() {
+        return pluginLibdir;
+    }
+
+    public void instantiatePlugin(final Map<String, Object> killbillApis) {
+        checkValidPlugin();
+
+        // Register all killbill APIs
+        container.put(JAVA_APIS, killbillApis);
+
+        // Note that the JAVA_APIS variable will be available once only!
+        // Don't put any code here!
+
+        // Start the plugin
+        pluginInstance = (RubyObject) container.runScriptlet(pluginMainClass + ".new(" + JAVA_APIS + ")");
+    }
+
+    public void startPlugin(final BundleContext context) {
+        checkPluginIsStopped();
+        pluginInstance.callMethod("start_plugin");
+        checkPluginIsRunning();
+    }
+
+    public void stopPlugin(final BundleContext context) {
+        checkPluginIsRunning();
+        pluginInstance.callMethod("stop_plugin");
+        checkPluginIsStopped();
+    }
+
+    protected void checkPluginIsRunning() {
+        if (pluginInstance == null || !pluginInstance.getInstanceVariable(ACTIVE).isTrue()) {
+            throw new IllegalStateException(String.format("Plugin %s didn't start properly", pluginMainClass));
+        }
+    }
+
+    protected void checkPluginIsStopped() {
+        if (pluginInstance == null || pluginInstance.getInstanceVariable(ACTIVE).isTrue()) {
+            throw new IllegalStateException(String.format("Plugin %s didn't stop properly", pluginMainClass));
+        }
+    }
+
+    protected void checkValidPlugin() {
+        try {
+            container.runScriptlet(checkInstanceOfPlugin(KILLBILL_PLUGIN_BASE));
+        } catch (EvalFailedException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    protected void checkValidNotificationPlugin() throws IllegalArgumentException {
+        try {
+            container.runScriptlet(checkInstanceOfPlugin(KILLBILL_PLUGIN_NOTIFICATION));
+        } catch (EvalFailedException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    protected void checkValidPaymentPlugin() throws IllegalArgumentException {
+        try {
+            container.runScriptlet(checkInstanceOfPlugin(KILLBILL_PLUGIN_PAYMENT));
+        } catch (EvalFailedException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    protected String checkInstanceOfPlugin(final String baseClass) {
+        final StringBuilder builder = new StringBuilder(getRequireLine());
+        builder.append("raise ArgumentError.new('Invalid plugin: ")
+               .append(pluginMainClass)
+               .append(", is not a ")
+               .append(baseClass)
+               .append("') unless ")
+               .append(pluginMainClass)
+               .append(" <= ")
+               .append(baseClass);
+        return builder.toString();
+    }
+
+    private String getRequireLine() {
+        if (cachedRequireLine == null) {
+            final StringBuilder builder = new StringBuilder();
+            builder.append("ENV[\"GEM_HOME\"] = \"").append(pluginLibdir).append("\"").append("\n");
+            builder.append("ENV[\"GEM_PATH\"] = ENV[\"GEM_HOME\"]\n");
+            // Always require the Killbill gem
+            builder.append("gem 'killbill'\n");
+            builder.append("require 'killbill'\n");
+            // Assume the plugin is shipped as a Gem
+            builder.append("begin\n")
+                   .append("gem '").append(pluginGemName).append("'\n")
+                   .append("require '").append(pluginGemName).append("' rescue warn \"WARN: unable to load ").append(pluginGemName).append("\"\n")
+                   .append("rescue Gem::LoadError\n")
+                   .append("warn \"WARN: unable to load gem ").append(pluginGemName).append("\"\n")
+                   .append("end\n");
+            // Require any file directly in the pluginLibdir directory (e.g. /var/tmp/bundles/ruby/foo/1.0/gems/*.rb).
+            // Although it is likely that any Killbill plugin will be distributed as a gem, it is still useful to
+            // be able to load individual scripts for prototyping/testing/...
+            builder.append("Dir.glob(ENV[\"GEM_HOME\"] + \"/*.rb\").each {|x| require x rescue warn \"WARN: unable to load #{x}\"}\n");
+            cachedRequireLine = builder.toString();
+        }
+        return cachedRequireLine;
+    }
+
+    protected Ruby getRuntime() {
+        return pluginInstance.getMetaClass().getRuntime();
+    }
+
+    protected void log(final int level, final String message) {
+        if (logger != null) {
+            logger.log(level, message);
+        }
+    }
+
+    protected void log(final int level, final String message, final Throwable throwable) {
+        if (logger != null) {
+            logger.log(level, message, throwable);
+        }
+    }
+}
diff --git a/osgi-bundles/pom.xml b/osgi-bundles/pom.xml
new file mode 100644
index 0000000..113b569
--- /dev/null
+++ b/osgi-bundles/pom.xml
@@ -0,0 +1,33 @@
+<?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</artifactId>
+        <version>0.1.52-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>killbill-osgi-bundles</artifactId>
+    <name>Killbill billing platform: OSGI bundles</name>
+    <packaging>pom</packaging>
+    <modules>
+        <module>hello</module>
+        <module>jruby</module>
+    </modules>
+</project>

pom.xml 58(+56 -2)

diff --git a/pom.xml b/pom.xml
index 020e177..3fa3815 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,8 @@
 	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">
+<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">
     <parent>
         <groupId>org.sonatype.oss</groupId>
         <artifactId>oss-parent</artifactId>
@@ -35,7 +36,7 @@
     </scm>
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-        <slf4j.version>1.6.5</slf4j.version>
+        <slf4j.version>1.7.2</slf4j.version>
         <ehcache.version>2.6.2</ehcache.version>
     </properties>
     <modules>
@@ -55,10 +56,22 @@
         <module>jaxrs</module>
         <module>server</module>
         <module>tenant</module>
+        <module>osgi</module>
+        <module>osgi-bundles</module>
     </modules>
     <dependencyManagement>
         <dependencies>
             <dependency>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>org.apache.felix.framework</artifactId>
+                <version>4.0.3</version>
+            </dependency>
+            <dependency>
+                <groupId>org.osgi</groupId>
+                <artifactId>org.osgi.core</artifactId>
+                <version>4.3.1</version>
+            </dependency>
+            <dependency>
                 <groupId>net.sf.ehcache</groupId>
                 <artifactId>ehcache-core</artifactId>
                 <version>${ehcache.version}</version>
@@ -225,6 +238,26 @@
                 <scope>test</scope>
             </dependency>
             <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-osgi</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-osgi-bundles</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-osgi-bundles-hello</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-osgi-bundles-jruby</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
                 <groupId>com.jolbox</groupId>
                 <artifactId>bonecp</artifactId>
                 <version>0.7.1.RELEASE</version>
@@ -359,6 +392,11 @@
             </dependency>
             <dependency>
                 <groupId>org.slf4j</groupId>
+                <artifactId>osgi-over-slf4j</artifactId>
+                <version>${slf4j.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.slf4j</groupId>
                 <artifactId>slf4j-simple</artifactId>
                 <version>${slf4j.version}</version>
             </dependency>
@@ -408,6 +446,20 @@
         </dependencies>
     </dependencyManagement>
     <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.felix</groupId>
+                    <artifactId>maven-bundle-plugin</artifactId>
+                    <version>2.3.7</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-shade-plugin</artifactId>
+                    <version>1.4</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
         <plugins>
             <plugin>
                 <groupId>com.ning.maven.plugins</groupId>
@@ -534,6 +586,8 @@
                                 <!-- exclude mustache template files -->
                                 <exclude>**/*.mustache</exclude>
                                 <exclude>examples/**</exclude>
+                                <exclude>Gemfile.lock</exclude>
+                                <exclude>src/main/ruby/kbadmin/**</exclude>
                             </excludes>
                         </configuration>
                     </execution>
diff --git a/usage/src/main/java/com/ning/billing/usage/dao/DefaultRolledUpUsageDao.java b/usage/src/main/java/com/ning/billing/usage/dao/DefaultRolledUpUsageDao.java
index 1bc4b78..b9dcf2b 100644
--- a/usage/src/main/java/com/ning/billing/usage/dao/DefaultRolledUpUsageDao.java
+++ b/usage/src/main/java/com/ning/billing/usage/dao/DefaultRolledUpUsageDao.java
@@ -22,6 +22,7 @@ import java.util.UUID;
 import javax.inject.Inject;
 
 import org.joda.time.DateTime;
+import org.skife.jdbi.v2.IDBI;
 
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
@@ -31,8 +32,8 @@ public class DefaultRolledUpUsageDao implements RolledUpUsageDao {
     private final RolledUpUsageSqlDao rolledUpUsageSqlDao;
 
     @Inject
-    public DefaultRolledUpUsageDao(final RolledUpUsageSqlDao rolledUpUsageSqlDao) {
-        this.rolledUpUsageSqlDao = rolledUpUsageSqlDao;
+    public DefaultRolledUpUsageDao(final IDBI dbi) {
+        this.rolledUpUsageSqlDao = dbi.onDemand(RolledUpUsageSqlDao.class);
     }
 
     @Override
diff --git a/usage/src/main/java/com/ning/billing/usage/glue/UsageModule.java b/usage/src/main/java/com/ning/billing/usage/glue/UsageModule.java
index 45fd6a2..3f2a8d2 100644
--- a/usage/src/main/java/com/ning/billing/usage/glue/UsageModule.java
+++ b/usage/src/main/java/com/ning/billing/usage/glue/UsageModule.java
@@ -16,11 +16,26 @@
 
 package com.ning.billing.usage.glue;
 
+import com.ning.billing.usage.api.UsageUserApi;
+import com.ning.billing.usage.api.user.DefaultUsageUserApi;
+import com.ning.billing.usage.dao.DefaultRolledUpUsageDao;
+import com.ning.billing.usage.dao.RolledUpUsageDao;
+
 import com.google.inject.AbstractModule;
 
 public class UsageModule extends AbstractModule {
 
+    protected void installRolledUpUsageDao() {
+        bind(RolledUpUsageDao.class).to(DefaultRolledUpUsageDao.class).asEagerSingleton();
+    }
+
+    protected void installUsageUserApi() {
+        bind(UsageUserApi.class).to(DefaultUsageUserApi.class).asEagerSingleton();
+    }
+
     @Override
     protected void configure() {
+        installRolledUpUsageDao();
+        installUsageUserApi();
     }
 }
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
new file mode 100644
index 0000000..6e929cf
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/config/OSGIConfig.java
@@ -0,0 +1,46 @@
+/*
+ * 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.util.config;
+
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+public interface OSGIConfig extends KillbillConfig {
+
+    @Config("killbill.osgi.root.dir")
+    @Default("/var/tmp/felix")
+    public String getOSGIBundleRootDir();
+
+    @Config("killbill.osgi.bundle.cache.name")
+    @Default("osgi-cache")
+    public String getOSGIBundleCacheName();
+
+
+    @Config("killbill.osgi.bundle.install.dir")
+    @Default("/var/tmp/bundles")
+    public String getRootInstallationDir();
+
+    @Config("killbill.osgi.system.bundle.export.packages")
+    @Default("com.ning.billing.account.api,com.ning.billing.beatrix.bus.api,com.ning.billing.payment.plugin.api,com.ning.billing.osgi.api.config,com.ning.billing.util.callcontext,com.google.common.eventbus")
+    public String getSystemBundleExportPackages();
+
+    // TODO FIXME OSGI
+    @Config("killbill.osgi.jruby.bundle.path")
+    @Default("file:/var/tmp/killbill-osgi-jruby-bundle-0.1.51-SNAPSHOT-jar-with-dependencies.jar")
+    public String getJrubyBundlePath();
+
+}
diff --git a/util/src/test/java/com/ning/billing/GuicyKillbillTestSuite.java b/util/src/test/java/com/ning/billing/GuicyKillbillTestSuite.java
index 2cf9a7f..d299670 100644
--- a/util/src/test/java/com/ning/billing/GuicyKillbillTestSuite.java
+++ b/util/src/test/java/com/ning/billing/GuicyKillbillTestSuite.java
@@ -17,7 +17,6 @@
 package com.ning.billing;
 
 import java.lang.reflect.Method;
-import java.util.UUID;
 
 import javax.inject.Inject;
 
@@ -28,11 +27,7 @@ import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 
 import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
 import com.ning.billing.util.callcontext.InternalCallContext;
-import com.ning.billing.util.callcontext.InternalCallContextFactory;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
 
 public class GuicyKillbillTestSuite {