killbill-aplcache
Changes
entitlement/pom.xml 8(+8 -0)
entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java 403(+274 -129)
entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java 208(+208 -0)
entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementModule.java 9(+9 -0)
entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementProviderPluginRegistryProvider.java 39(+39 -0)
entitlement/src/main/java/org/killbill/billing/entitlement/provider/DefaultEntitlementProviderPluginRegistry.java 71(+71 -0)
pom.xml 2(+1 -1)
Details
entitlement/pom.xml 8(+8 -0)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 6b4146b..6d0260b 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -106,6 +106,10 @@
</dependency>
<dependency>
<groupId>org.kill-bill.billing</groupId>
+ <artifactId>killbill-platform-osgi-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-platform-test</artifactId>
<scope>test</scope>
</dependency>
@@ -125,6 +129,10 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.kill-bill.billing.plugin</groupId>
+ <artifactId>killbill-plugin-api-entitlement</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.kill-bill.commons</groupId>
<artifactId>killbill-clock</artifactId>
</dependency>
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
index 3874218..6736e28 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
@@ -24,39 +24,36 @@ import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
-import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.bus.api.PersistentBus.EventBusException;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-import org.killbill.clock.Clock;
import org.killbill.billing.entitlement.AccountEventsStreams;
import org.killbill.billing.entitlement.DefaultEntitlementService;
import org.killbill.billing.entitlement.EntitlementService;
import org.killbill.billing.entitlement.EntitlementTransitionType;
import org.killbill.billing.entitlement.EventsStream;
-import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.block.BlockingChecker;
import org.killbill.billing.entitlement.dao.BlockingStateDao;
import org.killbill.billing.entitlement.engine.core.EntitlementNotificationKey;
import org.killbill.billing.entitlement.engine.core.EntitlementNotificationKeyAction;
import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
import org.killbill.billing.entitlement.engine.core.EventsStreamBuilder;
+import org.killbill.billing.entitlement.plugin.api.EntitlementContext;
+import org.killbill.billing.entitlement.plugin.api.EntitlementPluginApi;
+import org.killbill.billing.entitlement.plugin.api.EntitlementPluginApiException;
+import org.killbill.billing.entitlement.plugin.api.OnFailureEntitlementResult;
+import org.killbill.billing.entitlement.plugin.api.OnSuccessEntitlementResult;
+import org.killbill.billing.entitlement.plugin.api.OperationType;
+import org.killbill.billing.entitlement.plugin.api.PriorEntitlementResult;
import org.killbill.billing.junction.DefaultBlockingState;
-import org.killbill.notificationq.api.NotificationEvent;
-import org.killbill.notificationq.api.NotificationQueue;
-import org.killbill.notificationq.api.NotificationQueueService;
-import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
import org.killbill.billing.subscription.api.transfer.SubscriptionBaseTransferApi;
@@ -66,6 +63,15 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.killbill.clock.Clock;
+import org.killbill.notificationq.api.NotificationEvent;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
@@ -93,13 +99,15 @@ public class DefaultEntitlementApi implements EntitlementApi {
private final EventsStreamBuilder eventsStreamBuilder;
private final EntitlementUtils entitlementUtils;
private final NotificationQueueService notificationQueueService;
+ private final OSGIServiceRegistration<EntitlementPluginApi> pluginRegistry;
@Inject
public DefaultEntitlementApi(final PersistentBus eventBus, final InternalCallContextFactory internalCallContextFactory,
final SubscriptionBaseTransferApi subscriptionTransferApi, final SubscriptionBaseInternalApi subscriptionInternalApi,
final AccountInternalApi accountApi, final BlockingStateDao blockingStateDao, final Clock clock,
final BlockingChecker checker, final NotificationQueueService notificationQueueService,
- final EventsStreamBuilder eventsStreamBuilder, final EntitlementUtils entitlementUtils) {
+ final EventsStreamBuilder eventsStreamBuilder, final EntitlementUtils entitlementUtils,
+ final OSGIServiceRegistration<EntitlementPluginApi> pluginRegistry) {
this.eventBus = eventBus;
this.internalCallContextFactory = internalCallContextFactory;
this.subscriptionBaseInternalApi = subscriptionInternalApi;
@@ -111,58 +119,119 @@ public class DefaultEntitlementApi implements EntitlementApi {
this.notificationQueueService = notificationQueueService;
this.eventsStreamBuilder = eventsStreamBuilder;
this.entitlementUtils = entitlementUtils;
+ this.pluginRegistry = pluginRegistry;
this.dateHelper = new EntitlementDateHelper(accountApi, clock);
}
- @Override
- public Entitlement createBaseEntitlement(final UUID accountId, final PlanPhaseSpecifier planPhaseSpecifier, final String externalKey, final List<PlanPhasePriceOverride> overrides, final LocalDate effectiveDate, final CallContext callContext) throws EntitlementApiException {
- final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
- try {
-
- if (entitlementUtils.getFirstActiveSubscriptionIdForKeyOrNull(externalKey, contextWithValidAccountRecordId) != null) {
- throw new EntitlementApiException(new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, externalKey));
- }
-
- final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.createBundleForAccount(accountId, externalKey, contextWithValidAccountRecordId);
+ public interface WithEntitlementPlugin<T> {
+ T doCall(final EntitlementApi entitlementApi) throws EntitlementApiException;
+ }
- final DateTime referenceTime = clock.getUTCNow();
- final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(effectiveDate, referenceTime, contextWithValidAccountRecordId);
- final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundle.getId(), planPhaseSpecifier, overrides, requestedDate, contextWithValidAccountRecordId);
+ private <T> T executeWithPlugin(final WithEntitlementPlugin<T> callback, final EntitlementApi entitlementApi, final EntitlementContext pluginContext) throws EntitlementApiException {
- return new DefaultEntitlement(subscription.getId(), eventsStreamBuilder, this,
- blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
- entitlementUtils, dateHelper, clock, internalCallContextFactory, callContext);
- } catch (SubscriptionBaseApiException e) {
- throw new EntitlementApiException(e);
+ try {
+ final PriorEntitlementResult priorEntitlementResult = executePluginPriorCalls(pluginContext);
+ if (priorEntitlementResult != null && priorEntitlementResult.isAborted()) {
+ throw new EntitlementApiException(ErrorCode.ENT_PLUGIN_API_ABORTED);
+ }
+ final EntitlementContext updatedPluginContext = new DefaultEntitlementContext(pluginContext, priorEntitlementResult);
+ try {
+ T result = callback.doCall(entitlementApi);
+ executePluginOnSuccessCalls(updatedPluginContext);
+ return result;
+ } catch (final EntitlementApiException e) {
+ executePluginOnFailureCalls(updatedPluginContext);
+ throw e;
+ }
+ } catch (final EntitlementPluginApiException e) {
+ throw new EntitlementApiException(ErrorCode.ENT_PLUGIN_API_ABORTED, e.getMessage());
}
}
@Override
- public Entitlement addEntitlement(final UUID bundleId, final PlanPhaseSpecifier planPhaseSpecifier, final List<PlanPhasePriceOverride> overrides, final LocalDate effectiveDate, final CallContext callContext) throws EntitlementApiException {
- final EventsStream eventsStreamForBaseSubscription = eventsStreamBuilder.buildForBaseSubscription(bundleId, callContext);
-
- // Check the base entitlement state is active
- if (!eventsStreamForBaseSubscription.isEntitlementActive()) {
- throw new EntitlementApiException(ErrorCode.SUB_GET_NO_SUCH_BASE_SUBSCRIPTION, bundleId);
- }
-
- // Check the base entitlement state is not blocked
- if (eventsStreamForBaseSubscription.isBlockChange()) {
- throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getEntitlementId().toString()));
- }
+ public Entitlement createBaseEntitlement(final UUID accountId, final PlanPhaseSpecifier planPhaseSpecifier, final String externalKey, final List<PlanPhasePriceOverride> overrides, final LocalDate effectiveDate, final CallContext callContext) throws EntitlementApiException {
- final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(effectiveDate, eventsStreamForBaseSubscription.getSubscriptionBase().getStartDate(), eventsStreamForBaseSubscription.getInternalTenantContext());
+ final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SUBSCRIPTION,
+ accountId,
+ null,
+ planPhaseSpecifier,
+ externalKey,
+ overrides,
+ effectiveDate,
+ null,
+ callContext);
+
+ final WithEntitlementPlugin<Entitlement> createBaseEntitlementWithPlugin = new WithEntitlementPlugin<Entitlement>() {
+ @Override
+ public Entitlement doCall(final EntitlementApi entitlementApi) throws EntitlementApiException {
+ final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
+ try {
+
+ if (entitlementUtils.getFirstActiveSubscriptionIdForKeyOrNull(externalKey, contextWithValidAccountRecordId) != null) {
+ throw new EntitlementApiException(new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, externalKey));
+ }
+
+
+ final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.createBundleForAccount(accountId, externalKey, contextWithValidAccountRecordId);
+
+ final DateTime referenceTime = clock.getUTCNow();
+ final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(effectiveDate, referenceTime, contextWithValidAccountRecordId);
+ final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundle.getId(), planPhaseSpecifier, overrides, requestedDate, contextWithValidAccountRecordId);
+
+ return new DefaultEntitlement(subscription.getId(), eventsStreamBuilder, entitlementApi,
+ blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
+ entitlementUtils, dateHelper, clock, internalCallContextFactory, callContext);
+ } catch (SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
+ }
+ };
+ return executeWithPlugin(createBaseEntitlementWithPlugin, this, pluginContext);
+ }
- try {
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(callContext);
- final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundleId, planPhaseSpecifier, overrides, requestedDate, context);
+ @Override
+ public Entitlement addEntitlement(final UUID bundleId, final PlanPhaseSpecifier planPhaseSpecifier, final List<PlanPhasePriceOverride> overrides, final LocalDate effectiveDate, final CallContext callContext) throws EntitlementApiException {
- return new DefaultEntitlement(subscription.getId(), eventsStreamBuilder, this,
- blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
- entitlementUtils, dateHelper, clock, internalCallContextFactory, callContext);
- } catch (SubscriptionBaseApiException e) {
- throw new EntitlementApiException(e);
- }
+ final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SUBSCRIPTION,
+ null,
+ bundleId,
+ planPhaseSpecifier,
+ null,
+ overrides,
+ effectiveDate,
+ null,
+ callContext);
+
+ final WithEntitlementPlugin<Entitlement> addEntitlementWithPlugin = new WithEntitlementPlugin<Entitlement>() {
+ @Override
+ public Entitlement doCall(final EntitlementApi entitlementApi) throws EntitlementApiException {
+ final EventsStream eventsStreamForBaseSubscription = eventsStreamBuilder.buildForBaseSubscription(bundleId, callContext);
+
+ // Check the base entitlement state is active
+ if (!eventsStreamForBaseSubscription.isEntitlementActive()) {
+ throw new EntitlementApiException(ErrorCode.SUB_GET_NO_SUCH_BASE_SUBSCRIPTION, bundleId);
+ }
+
+ // Check the base entitlement state is not blocked
+ if (eventsStreamForBaseSubscription.isBlockChange()) {
+ throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getEntitlementId().toString()));
+ }
+
+ final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(effectiveDate, eventsStreamForBaseSubscription.getSubscriptionBase().getStartDate(), eventsStreamForBaseSubscription.getInternalTenantContext());
+
+ try {
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(callContext);
+ final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundleId, planPhaseSpecifier, overrides, requestedDate, context);
+
+ return new DefaultEntitlement(subscription.getId(), eventsStreamBuilder, entitlementApi,
+ blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
+ entitlementUtils, dateHelper, clock, internalCallContextFactory, callContext);
+ } catch (SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
+ }
+ };
+ return executeWithPlugin(addEntitlementWithPlugin, this, pluginContext);
}
@Override
@@ -239,89 +308,124 @@ public class DefaultEntitlementApi implements EntitlementApi {
@Override
public void pause(final UUID bundleId, final LocalDate localEffectiveDate, final CallContext context) throws EntitlementApiException {
- try {
- final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, context);
- final BlockingState currentState = blockingStateDao.getBlockingStateForService(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, EntitlementService.ENTITLEMENT_SERVICE_NAME, contextWithValidAccountRecordId);
- if (currentState != null && currentState.getStateName().equals(ENT_STATE_BLOCKED)) {
- throw new EntitlementApiException(ErrorCode.ENT_ALREADY_BLOCKED, bundleId);
- }
-
- final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getBundleFromId(bundleId, contextWithValidAccountRecordId);
- final Account account = accountApi.getAccountById(bundle.getAccountId(), contextWithValidAccountRecordId);
- final SubscriptionBase baseSubscription = subscriptionBaseInternalApi.getBaseSubscription(bundleId, contextWithValidAccountRecordId);
- final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(localEffectiveDate, baseSubscription.getStartDate(), contextWithValidAccountRecordId);
- if (!dateHelper.isBeforeOrEqualsToday(effectiveDate, account.getTimeZone())) {
- recordPauseResumeNotificationEntry(baseSubscription.getId(), bundleId, effectiveDate, true, contextWithValidAccountRecordId);
- return;
+ final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.PAUSE_SUBSCRIPTION,
+ null,
+ bundleId,
+ null,
+ null,
+ null,
+ localEffectiveDate,
+ null,
+ context);
+
+ final WithEntitlementPlugin<Void> pauseWithPlugin = new WithEntitlementPlugin<Void>() {
+ @Override
+ public Void doCall(final EntitlementApi entitlementApi) throws EntitlementApiException {
+ try {
+ final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, context);
+ final BlockingState currentState = blockingStateDao.getBlockingStateForService(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, EntitlementService.ENTITLEMENT_SERVICE_NAME, contextWithValidAccountRecordId);
+ if (currentState != null && currentState.getStateName().equals(ENT_STATE_BLOCKED)) {
+ throw new EntitlementApiException(ErrorCode.ENT_ALREADY_BLOCKED, bundleId);
+ }
+
+ final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getBundleFromId(bundleId, contextWithValidAccountRecordId);
+ final Account account = accountApi.getAccountById(bundle.getAccountId(), contextWithValidAccountRecordId);
+ final SubscriptionBase baseSubscription = subscriptionBaseInternalApi.getBaseSubscription(bundleId, contextWithValidAccountRecordId);
+ final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(localEffectiveDate, baseSubscription.getStartDate(), contextWithValidAccountRecordId);
+
+ if (!dateHelper.isBeforeOrEqualsToday(effectiveDate, account.getTimeZone())) {
+ recordPauseResumeNotificationEntry(baseSubscription.getId(), bundleId, effectiveDate, true, contextWithValidAccountRecordId);
+ return null;
+ }
+
+ final BlockingState state = new DefaultBlockingState(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, ENT_STATE_BLOCKED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, true, effectiveDate);
+ entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(state, contextWithValidAccountRecordId);
+
+ // Should we send one event per entitlement in the bundle?
+ // Code below only sends one event for the bundle and use the base entitlementId
+ final DefaultEffectiveEntitlementEvent event = new DefaultEffectiveEntitlementEvent(state.getId(), baseSubscription.getId(), bundleId, bundle.getAccountId(), EntitlementTransitionType.BLOCK_BUNDLE,
+ effectiveDate, clock.getUTCNow(),
+ contextWithValidAccountRecordId.getAccountRecordId(), contextWithValidAccountRecordId.getTenantRecordId(),
+ contextWithValidAccountRecordId.getUserToken());
+
+ try {
+ eventBus.post(event);
+ } catch (EventBusException e) {
+ log.warn("Failed to post bus event for pause operation on bundle " + bundleId);
+ }
+
+ } catch (SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ } catch (AccountApiException e) {
+ throw new EntitlementApiException(e);
+ }
+ return null;
}
-
- final BlockingState state = new DefaultBlockingState(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, ENT_STATE_BLOCKED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, true, effectiveDate);
- entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(state, contextWithValidAccountRecordId);
-
- // Should we send one event per entitlement in the bundle?
- // Code below only sends one event for the bundle and use the base entitlementId
- final DefaultEffectiveEntitlementEvent event = new DefaultEffectiveEntitlementEvent(state.getId(), baseSubscription.getId(), bundleId, bundle.getAccountId(), EntitlementTransitionType.BLOCK_BUNDLE,
- effectiveDate, clock.getUTCNow(),
- contextWithValidAccountRecordId.getAccountRecordId(), contextWithValidAccountRecordId.getTenantRecordId(),
- contextWithValidAccountRecordId.getUserToken());
-
- try {
- eventBus.post(event);
- } catch (EventBusException e) {
- log.warn("Failed to post bus event for pause operation on bundle " + bundleId);
- }
-
- } catch (SubscriptionBaseApiException e) {
- throw new EntitlementApiException(e);
- } catch (AccountApiException e) {
- throw new EntitlementApiException(e);
- }
+ };
+ executeWithPlugin(pauseWithPlugin, this, pluginContext);
}
@Override
public void resume(final UUID bundleId, final LocalDate localEffectiveDate, final CallContext context) throws EntitlementApiException {
- try {
- final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, context);
- final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getBundleFromId(bundleId, contextWithValidAccountRecordId);
- final Account account = accountApi.getAccountById(bundle.getAccountId(), contextWithValidAccountRecordId);
- final SubscriptionBase baseSubscription = subscriptionBaseInternalApi.getBaseSubscription(bundleId, contextWithValidAccountRecordId);
-
- final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(localEffectiveDate, baseSubscription.getStartDate(), contextWithValidAccountRecordId);
- if (!dateHelper.isBeforeOrEqualsToday(effectiveDate, account.getTimeZone())) {
- recordPauseResumeNotificationEntry(baseSubscription.getId(), bundleId, effectiveDate, false, contextWithValidAccountRecordId);
- return;
+ final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.RESUME_SUBSCRIPTION,
+ null,
+ bundleId,
+ null,
+ null,
+ null,
+ localEffectiveDate,
+ null,
+ context);
+ final WithEntitlementPlugin<Void> resumeWithPlugin = new WithEntitlementPlugin<Void>() {
+ @Override
+ public Void doCall(final EntitlementApi entitlementApi) throws EntitlementApiException {
+ try {
+ final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, context);
+ final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getBundleFromId(bundleId, contextWithValidAccountRecordId);
+ final Account account = accountApi.getAccountById(bundle.getAccountId(), contextWithValidAccountRecordId);
+ final SubscriptionBase baseSubscription = subscriptionBaseInternalApi.getBaseSubscription(bundleId, contextWithValidAccountRecordId);
+
+ final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(localEffectiveDate, baseSubscription.getStartDate(), contextWithValidAccountRecordId);
+
+ if (!dateHelper.isBeforeOrEqualsToday(effectiveDate, account.getTimeZone())) {
+ recordPauseResumeNotificationEntry(baseSubscription.getId(), bundleId, effectiveDate, false, contextWithValidAccountRecordId);
+ return null;
+ }
+
+ final BlockingState currentState = blockingStateDao.getBlockingStateForService(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, EntitlementService.ENTITLEMENT_SERVICE_NAME, contextWithValidAccountRecordId);
+ if (currentState == null || currentState.getStateName().equals(ENT_STATE_CLEAR)) {
+ // Nothing to do.
+ log.warn("Current state is {}, nothing to resume", currentState);
+ return null;
+ }
+
+ final BlockingState state = new DefaultBlockingState(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, ENT_STATE_CLEAR, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, effectiveDate);
+ entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(state, contextWithValidAccountRecordId);
+
+ // Should we send one event per entitlement in the bundle?
+ // Code below only sends one event for the bundle and use the base entitlementId
+ final DefaultEffectiveEntitlementEvent event = new DefaultEffectiveEntitlementEvent(state.getId(), baseSubscription.getId(), bundleId, bundle.getAccountId(), EntitlementTransitionType.UNBLOCK_BUNDLE,
+ effectiveDate, clock.getUTCNow(),
+ contextWithValidAccountRecordId.getAccountRecordId(), contextWithValidAccountRecordId.getTenantRecordId(),
+ contextWithValidAccountRecordId.getUserToken());
+
+ try {
+ eventBus.post(event);
+ } catch (EventBusException e) {
+ log.warn("Failed to post bus event for resume operation on bundle " + bundleId);
+ }
+
+ } catch (SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ } catch (AccountApiException e) {
+ throw new EntitlementApiException(e);
+ }
+ return null;
}
-
- final BlockingState currentState = blockingStateDao.getBlockingStateForService(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, EntitlementService.ENTITLEMENT_SERVICE_NAME, contextWithValidAccountRecordId);
- if (currentState == null || currentState.getStateName().equals(ENT_STATE_CLEAR)) {
- // Nothing to do.
- log.warn("Current state is {}, nothing to resume", currentState);
- return;
- }
-
- final BlockingState state = new DefaultBlockingState(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, ENT_STATE_CLEAR, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, effectiveDate);
- entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(state, contextWithValidAccountRecordId);
-
- // Should we send one event per entitlement in the bundle?
- // Code below only sends one event for the bundle and use the base entitlementId
- final DefaultEffectiveEntitlementEvent event = new DefaultEffectiveEntitlementEvent(state.getId(), baseSubscription.getId(), bundleId, bundle.getAccountId(), EntitlementTransitionType.UNBLOCK_BUNDLE,
- effectiveDate, clock.getUTCNow(),
- contextWithValidAccountRecordId.getAccountRecordId(), contextWithValidAccountRecordId.getTenantRecordId(),
- contextWithValidAccountRecordId.getUserToken());
-
- try {
- eventBus.post(event);
- } catch (EventBusException e) {
- log.warn("Failed to post bus event for resume operation on bundle " + bundleId);
- }
-
- } catch (SubscriptionBaseApiException e) {
- throw new EntitlementApiException(e);
- } catch (AccountApiException e) {
- throw new EntitlementApiException(e);
- }
+ };
+ executeWithPlugin(resumeWithPlugin, this, pluginContext);
}
@Override
@@ -352,7 +456,7 @@ public class DefaultEntitlementApi implements EntitlementApi {
final SubscriptionBaseBundle baseBundle = baseSubscription != null ?
subscriptionBaseInternalApi.getBundleFromId(baseSubscription.getBundleId(), contextWithValidAccountRecordId) : null;
- if (baseBundle == null || ! baseBundle.getAccountId().equals(sourceAccountId)) {
+ if (baseBundle == null || !baseBundle.getAccountId().equals(sourceAccountId)) {
throw new EntitlementApiException(new SubscriptionBaseApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_KEY, externalKey));
}
@@ -391,4 +495,45 @@ public class DefaultEntitlementApi implements EntitlementApi {
}
}
+ private PriorEntitlementResult executePluginPriorCalls(final EntitlementContext entitlementContextArg) throws EntitlementPluginApiException {
+
+ // Return as soon as the first plugin aborts, or the last result for the last plugin
+ PriorEntitlementResult prevResult = null;
+
+ EntitlementContext currentContext = entitlementContextArg;
+ for (final String pluginName : pluginRegistry.getAllServices()) {
+ final EntitlementPluginApi plugin = pluginRegistry.getServiceForName(pluginName);
+ if (plugin == null) {
+ // First call to plugin, we log warn, if plugin is not registered
+ log.warn("Skipping unknown entitlement control plugin {} when fetching results", pluginName);
+ continue;
+ }
+ prevResult = plugin.priorCall(currentContext, currentContext.getPluginProperties());
+ if (prevResult.isAborted()) {
+ break;
+ }
+ currentContext = new DefaultEntitlementContext(currentContext, prevResult);
+ }
+ return prevResult;
+ }
+
+ private OnSuccessEntitlementResult executePluginOnSuccessCalls(final EntitlementContext context) throws EntitlementPluginApiException {
+ for (final String pluginName : pluginRegistry.getAllServices()) {
+ final EntitlementPluginApi plugin = pluginRegistry.getServiceForName(pluginName);
+ if (plugin != null) {
+ plugin.onSuccessCall(context, context.getPluginProperties());
+ }
+ }
+ return null;
+ }
+
+ private OnFailureEntitlementResult executePluginOnFailureCalls(final EntitlementContext context) throws EntitlementPluginApiException {
+ for (final String pluginName : pluginRegistry.getAllServices()) {
+ final EntitlementPluginApi plugin = pluginRegistry.getServiceForName(pluginName);
+ if (plugin != null) {
+ plugin.onFailureCall(context, context.getPluginProperties());
+ }
+ }
+ return null;
+ }
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java
new file mode 100644
index 0000000..eb7c293
--- /dev/null
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.entitlement.api;
+
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.entitlement.plugin.api.EntitlementContext;
+import org.killbill.billing.entitlement.plugin.api.OperationType;
+import org.killbill.billing.entitlement.plugin.api.PriorEntitlementResult;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.CallOrigin;
+import org.killbill.billing.util.callcontext.UserType;
+
+import com.google.common.base.MoreObjects;
+
+public class DefaultEntitlementContext implements EntitlementContext {
+
+ private final OperationType operationType;
+ private final UUID accountId;
+ private final UUID bundleId;
+ private final PlanPhaseSpecifier spec;
+ private final String externalKey;
+ private final List<PlanPhasePriceOverride> planPhasePriceOverrides;
+ private final LocalDate effectiveDate;
+ private final Iterable<PluginProperty> pluginProperties;
+ private final UUID userToken;
+ private final String userName;
+ private final CallOrigin callOrigin;
+ private final UserType userType;
+ private final String reasonCode;
+ private final String comments;
+ private final DateTime createdDate;
+ private final DateTime updatedDate;
+ private final UUID tenantId;
+
+
+ public DefaultEntitlementContext(final EntitlementContext prev,
+ @Nullable final PriorEntitlementResult pluginResult) {
+ this(prev.getOperationType(),
+ prev.getAccountId(),
+ prev.getBundleId(),
+ pluginResult != null && pluginResult.getAdjustedPlanPhaseSpecifier() != null ? pluginResult.getAdjustedPlanPhaseSpecifier() : prev.getPlanPhaseSpecifier(),
+ prev.getExternalKey(),
+ pluginResult != null && pluginResult.getAdjustedPlanPhasePriceOverride() != null ? pluginResult.getAdjustedPlanPhasePriceOverride() : prev.getPlanPhasePriceOverride(),
+ pluginResult != null && pluginResult.getAdjustedEffectiveDate() != null ? pluginResult.getAdjustedEffectiveDate() : prev.getEffectiveDate(),
+ pluginResult != null && pluginResult.getAdjustedPluginProperties() != null ? pluginResult.getAdjustedPluginProperties() : prev.getPluginProperties(),
+ prev);
+ }
+
+ public DefaultEntitlementContext(final OperationType operationType,
+ final UUID accountId,
+ final UUID bundleId,
+ final PlanPhaseSpecifier spec,
+ final String externalKey,
+ final List<PlanPhasePriceOverride> planPhasePriceOverrides,
+ final LocalDate effectiveDate,
+ final Iterable<PluginProperty> pluginProperties,
+ final CallContext callContext) {
+ this(operationType, accountId, bundleId, spec, externalKey, planPhasePriceOverrides, effectiveDate, pluginProperties,
+ callContext.getUserToken(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getReasonCode(),
+ callContext.getComments(), callContext.getCreatedDate(), callContext.getUpdatedDate(), callContext.getTenantId());
+ }
+
+
+ public DefaultEntitlementContext(final OperationType operationType,
+ final UUID accountId,
+ final UUID bundleId,
+ final PlanPhaseSpecifier spec,
+ final String externalKey,
+ final List<PlanPhasePriceOverride> planPhasePriceOverrides,
+ final LocalDate effectiveDate,
+ final Iterable<PluginProperty> pluginProperties,
+ final UUID userToken,
+ final String userName,
+ final CallOrigin callOrigin,
+ final UserType userType,
+ final String reasonCode,
+ final String comments,
+ final DateTime createdDate,
+ final DateTime updatedDate,
+ final UUID tenantId) {
+ this.operationType = operationType;
+ this.accountId = accountId;
+ this.bundleId = bundleId;
+ this.spec = spec;
+ this.externalKey = externalKey;
+ this.planPhasePriceOverrides = planPhasePriceOverrides;
+ this.effectiveDate = effectiveDate;
+ this.pluginProperties = pluginProperties;
+ this.userToken = userToken;
+ this.userName = userName;
+ this.callOrigin = callOrigin;
+ this.userType = userType;
+ this.reasonCode = reasonCode;
+ this.comments = comments;
+ this.createdDate = createdDate;
+ this.updatedDate = updatedDate;
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public OperationType getOperationType() {
+ return operationType;
+ }
+
+ @Override
+ public UUID getAccountId() {
+ return accountId;
+ }
+
+ @Override
+ public UUID getBundleId() {
+ return bundleId;
+ }
+
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return spec;
+ }
+
+ @Override
+ public String getExternalKey() {
+ return externalKey;
+ }
+
+ @Override
+ public List<PlanPhasePriceOverride> getPlanPhasePriceOverride() {
+ return planPhasePriceOverrides;
+ }
+
+ @Override
+ public LocalDate getEffectiveDate() {
+ return effectiveDate;
+ }
+
+ @Override
+ public Iterable<PluginProperty> getPluginProperties() {
+ return pluginProperties;
+ }
+
+ @Override
+ public UUID getUserToken() {
+ return userToken;
+ }
+
+ @Override
+ public String getUserName() {
+ return userName;
+ }
+
+ @Override
+ public CallOrigin getCallOrigin() {
+ return callOrigin;
+ }
+
+ @Override
+ public UserType getUserType() {
+ return userType;
+ }
+
+ @Override
+ public String getReasonCode() {
+ return reasonCode;
+ }
+
+ @Override
+ public String getComments() {
+ return comments;
+ }
+
+ @Override
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ @Override
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+
+ @Override
+ public UUID getTenantId() {
+ return tenantId;
+ }
+}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementModule.java b/entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementModule.java
index 420073f..852dbd6 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementModule.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementModule.java
@@ -33,17 +33,25 @@ import org.killbill.billing.entitlement.dao.BlockingStateDao;
import org.killbill.billing.entitlement.dao.ProxyBlockingStateDao;
import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
import org.killbill.billing.entitlement.engine.core.EventsStreamBuilder;
+import org.killbill.billing.entitlement.plugin.api.EntitlementPluginApi;
import org.killbill.billing.glue.EntitlementModule;
import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.platform.api.KillbillConfigSource;
import org.killbill.billing.util.glue.KillBillModule;
+import com.google.inject.TypeLiteral;
+
public class DefaultEntitlementModule extends KillBillModule implements EntitlementModule {
public DefaultEntitlementModule(final KillbillConfigSource configSource) {
super(configSource);
}
+ protected void installEntitlementPluginApi() {
+ bind(new TypeLiteral<OSGIServiceRegistration<EntitlementPluginApi>>() {}).toProvider(DefaultEntitlementProviderPluginRegistryProvider.class).asEagerSingleton();
+ }
+
@Override
protected void configure() {
installBlockingStateDao();
@@ -55,6 +63,7 @@ public class DefaultEntitlementModule extends KillBillModule implements Entitlem
bind(EntitlementService.class).to(DefaultEntitlementService.class).asEagerSingleton();
bind(EntitlementUtils.class).asEagerSingleton();
bind(EventsStreamBuilder.class).asEagerSingleton();
+ installEntitlementPluginApi();
}
@Override
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementProviderPluginRegistryProvider.java b/entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementProviderPluginRegistryProvider.java
new file mode 100644
index 0000000..19a7e39
--- /dev/null
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementProviderPluginRegistryProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.entitlement.glue;
+
+import javax.inject.Inject;
+
+import org.killbill.billing.entitlement.plugin.api.EntitlementPluginApi;
+import org.killbill.billing.entitlement.provider.DefaultEntitlementProviderPluginRegistry;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+
+import com.google.inject.Provider;
+
+public class DefaultEntitlementProviderPluginRegistryProvider implements Provider<OSGIServiceRegistration<EntitlementPluginApi>> {
+
+ @Inject
+ public DefaultEntitlementProviderPluginRegistryProvider() {
+ }
+
+ @Override
+ public OSGIServiceRegistration<EntitlementPluginApi> get() {
+ final DefaultEntitlementProviderPluginRegistry pluginRegistry = new DefaultEntitlementProviderPluginRegistry();
+ return pluginRegistry;
+ }
+}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/provider/DefaultEntitlementProviderPluginRegistry.java b/entitlement/src/main/java/org/killbill/billing/entitlement/provider/DefaultEntitlementProviderPluginRegistry.java
new file mode 100644
index 0000000..54b1f19
--- /dev/null
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/provider/DefaultEntitlementProviderPluginRegistry.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.entitlement.provider;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.inject.Inject;
+
+import org.killbill.billing.entitlement.plugin.api.EntitlementPluginApi;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultEntitlementProviderPluginRegistry implements OSGIServiceRegistration<EntitlementPluginApi> {
+
+ private final static Logger log = LoggerFactory.getLogger(DefaultEntitlementProviderPluginRegistry.class);
+
+ private final Map<String, EntitlementPluginApi> pluginsByName = new ConcurrentHashMap<String, EntitlementPluginApi>();
+
+ @Inject
+ public DefaultEntitlementProviderPluginRegistry() {
+ }
+
+ @Override
+ public void registerService(final OSGIServiceDescriptor desc, final EntitlementPluginApi service) {
+ log.info("DefaultEntitlementProviderPluginRegistry registering service " + desc.getRegistrationName());
+ pluginsByName.put(desc.getRegistrationName(), service);
+ }
+
+ @Override
+ public void unregisterService(final String serviceName) {
+ log.info("DefaultEntitlementProviderPluginRegistry unregistering service " + serviceName);
+ pluginsByName.remove(serviceName);
+ }
+
+ @Override
+ public EntitlementPluginApi getServiceForName(final String serviceName) {
+ if (serviceName == null) {
+ throw new IllegalArgumentException("Null entitlement plugin API name");
+ }
+ final EntitlementPluginApi plugin = pluginsByName.get(serviceName);
+ return plugin;
+ }
+
+ @Override
+ public Set<String> getAllServices() {
+ return pluginsByName.keySet(); }
+
+ @Override
+ public Class<EntitlementPluginApi> getServiceType() {
+ return EntitlementPluginApi.class;
+ }
+}
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 3d22ed7..9d12dd9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.23</version>
+ <version>0.24-SNAPSHOT</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.15.1-SNAPSHOT</version>