killbill-memoizeit
Changes
.travis.yml 2(+2 -0)
beatrix/pom.xml 16(+16 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java 7(+0 -7)
entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestDefaultEntitlementTransferApi.java 7(+1 -6)
entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java 5(+0 -5)
osgi/pom.xml 93(+93 -0)
osgi-bundles/hello/pom.xml 107(+107 -0)
osgi-bundles/hello/src/main/java/com/ning/billing/osgi/bundles/hello/HelloActivator.java 181(+181 -0)
osgi-bundles/jruby/pom.xml 111(+111 -0)
osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyNotificationPlugin.java 64(+64 -0)
osgi-bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPaymentPlugin.java 187(+187 -0)
osgi-bundles/pom.xml 33(+33 -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 {
+
+}
osgi-bundles/hello/pom.xml 107(+107 -0)
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());
+ }
+}
osgi-bundles/jruby/pom.xml 111(+111 -0)
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);
+ }
+ }
+}
osgi-bundles/pom.xml 33(+33 -0)
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 {