killbill-memoizeit

This fix is for #98 Previous implementation had 2 issues: -

10/15/2013 9:23:06 PM

Changes

Details

diff --git a/api/src/main/java/com/ning/billing/events/BlockingTransitionInternalEvent.java b/api/src/main/java/com/ning/billing/events/BlockingTransitionInternalEvent.java
new file mode 100644
index 0000000..bb6cb7a
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/events/BlockingTransitionInternalEvent.java
@@ -0,0 +1,36 @@
+/*
+ * 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.events;
+
+import java.util.UUID;
+
+import com.ning.billing.entitlement.api.BlockingStateType;
+
+public interface BlockingTransitionInternalEvent extends BusInternalEvent  {
+
+    public UUID getBlockableId();
+
+    public BlockingStateType getBlockingType();
+
+    public Boolean isTransitionedToBlockedBilling();
+
+    public Boolean isTransitionedToUnblockedBilling();
+
+    public Boolean isTransitionedToBlockedEntitlement();
+
+    public Boolean isTransitionedToUnblockedEntitlement();
+}
diff --git a/api/src/main/java/com/ning/billing/events/BusInternalEvent.java b/api/src/main/java/com/ning/billing/events/BusInternalEvent.java
index 81fbf5f..0a798ad 100644
--- a/api/src/main/java/com/ning/billing/events/BusInternalEvent.java
+++ b/api/src/main/java/com/ning/billing/events/BusInternalEvent.java
@@ -22,27 +22,28 @@ import com.ning.billing.bus.api.BusEvent;
 public interface BusInternalEvent extends BusEvent {
 
     public enum BusInternalEventType {
-        ACCOUNT_CREATE,
         ACCOUNT_CHANGE,
-        SUBSCRIPTION_TRANSITION,
-        ENTITLEMENT_TRANSITION,
+        ACCOUNT_CREATE,
+        BLOCKING_STATE,
         BUNDLE_REPAIR,
-        INVOICE_EMPTY,
-        INVOICE_CREATION,
-        INVOICE_ADJUSTMENT,
-        PAYMENT_INFO,
-        PAYMENT_ERROR,
-        CONTROL_TAG_CREATION,
-        CONTROL_TAG_DELETION,
-        USER_TAG_CREATION,
-        USER_TAG_DELETION,
         CONTROL_TAGDEFINITION_CREATION,
         CONTROL_TAGDEFINITION_DELETION,
-        USER_TAGDEFINITION_CREATION,
-        USER_TAGDEFINITION_DELETION,
-        OVERDUE_CHANGE,
+        CONTROL_TAG_CREATION,
+        CONTROL_TAG_DELETION,
         CUSTOM_FIELD_CREATION,
         CUSTOM_FIELD_DELETION,
+        ENTITLEMENT_TRANSITION,
+        INVOICE_ADJUSTMENT,
+        INVOICE_CREATION,
+        INVOICE_EMPTY,
+        OVERDUE_CHANGE,
+        PAYMENT_ERROR,
+        PAYMENT_INFO,
+        SUBSCRIPTION_TRANSITION,
+        USER_TAGDEFINITION_CREATION,
+        USER_TAGDEFINITION_DELETION,
+        USER_TAG_CREATION,
+        USER_TAG_DELETION
     }
 
     public BusInternalEventType getBusEventType();
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceInternalApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceInternalApi.java
index 6f672c7..c982029 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceInternalApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceInternalApi.java
@@ -71,15 +71,4 @@ public interface InvoiceInternalApi {
      */
     public void consumeExistingCBAOnAccountWithUnpaidInvoices(final UUID accountId, final InternalCallContext context) throws InvoiceApiException;
 
-
-    /**
-     * Insert a new notification with a notificationDate of today to trigger a new invoice on the account.
-     *
-     * @param accountId        account id
-     * @param accountTimeZone  timezone of the account
-     * @param context          the context
-     *
-     * @throws InvoiceApiException
-     */
-    public void scheduleInvoiceForAccount(UUID accountId, DateTimeZone accountTimeZone, InternalCallContext context) throws InvoiceApiException;
 }
diff --git a/beatrix/src/test/resources/beatrix.properties b/beatrix/src/test/resources/beatrix.properties
index ad6d9fb..86d8626 100644
--- a/beatrix/src/test/resources/beatrix.properties
+++ b/beatrix/src/test/resources/beatrix.properties
@@ -26,3 +26,5 @@ killbill.payment.retry.days=8,8,8,8,8,8,8,8
 killbill.osgi.bundle.install.dir=/var/tmp/beatrix-bundles
 org.slf4j.simpleLogger.showDateTime=true
 
+killbill.invoice.triggerInvoiceOnBlockingEvent=true
+
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultBlockingTransitionInternalEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultBlockingTransitionInternalEvent.java
new file mode 100644
index 0000000..6d4efb4
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultBlockingTransitionInternalEvent.java
@@ -0,0 +1,153 @@
+/*
+ * 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.entitlement.api;
+
+import java.util.UUID;
+
+import com.ning.billing.events.BlockingTransitionInternalEvent;
+import com.ning.billing.events.BusEventBase;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class DefaultBlockingTransitionInternalEvent extends BusEventBase implements BlockingTransitionInternalEvent {
+
+
+    private final UUID blockableId;
+    private final BlockingStateType blockingType;
+    private final Boolean isTransitionedToBlockedBilling;
+    private final Boolean isTransitionedToUnblockedBilling;
+    private final Boolean isTransitionedToBlockedEntitlement;
+    private final Boolean isTransitionedToUnblockedEntitlement;
+
+    @JsonCreator
+    public DefaultBlockingTransitionInternalEvent(@JsonProperty("blockableId") final UUID blockableId,
+                                                  @JsonProperty("blockingType") final BlockingStateType blockingType,
+                                                  @JsonProperty("isTransitionedToBlockedBilling") final Boolean transitionedToBlockedBilling,
+                                                  @JsonProperty("isTransitionedToUnblockedBilling") final Boolean transitionedToUnblockedBilling,
+                                                  @JsonProperty("isTransitionedToBlockedEntitlement") final Boolean transitionedToBlockedEntitlement,
+                                                  @JsonProperty("isTransitionedToUnblockedEntitlement") final Boolean transitionedToUnblockedEntitlement,
+                                                  @JsonProperty("searchKey1") final Long searchKey1,
+                                                  @JsonProperty("searchKey2") final Long searchKey2,
+                                                  @JsonProperty("userToken") final UUID userToken) {
+        super(searchKey1, searchKey2, userToken);
+        this.blockableId = blockableId;
+        this.blockingType = blockingType;
+        isTransitionedToBlockedBilling = transitionedToBlockedBilling;
+        isTransitionedToUnblockedBilling = transitionedToUnblockedBilling;
+        isTransitionedToBlockedEntitlement = transitionedToBlockedEntitlement;
+        isTransitionedToUnblockedEntitlement = transitionedToUnblockedEntitlement;
+    }
+
+    @Override
+    public UUID getBlockableId() {
+        return blockableId;
+    }
+
+    @Override
+    public BlockingStateType getBlockingType() {
+        return blockingType;
+    }
+
+    @JsonProperty("isTransitionedToBlockedBilling")
+    @Override
+    public Boolean isTransitionedToBlockedBilling() {
+        return isTransitionedToBlockedBilling;
+    }
+
+    @JsonProperty("isTransitionedToUnblockedBilling")
+    @Override
+    public Boolean isTransitionedToUnblockedBilling() {
+        return isTransitionedToUnblockedBilling;
+    }
+
+    @JsonProperty("isTransitionedToBlockedEntitlement")
+    @Override
+    public Boolean isTransitionedToBlockedEntitlement() {
+        return isTransitionedToBlockedEntitlement;
+    }
+
+    @JsonProperty("isTransitionedToUnblockedEntitlement")
+    @Override
+    public Boolean isTransitionedToUnblockedEntitlement() {
+        return isTransitionedToUnblockedEntitlement;
+    }
+
+    @JsonIgnore
+    @Override
+    public BusInternalEventType getBusEventType() {
+        return BusInternalEventType.BLOCKING_STATE;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultBlockingTransitionInternalEvent)) {
+            return false;
+        }
+
+        final DefaultBlockingTransitionInternalEvent that = (DefaultBlockingTransitionInternalEvent) o;
+
+        if (blockableId != null ? !blockableId.equals(that.blockableId) : that.blockableId != null) {
+            return false;
+        }
+        if (blockingType != that.blockingType) {
+            return false;
+        }
+        if (isTransitionedToBlockedBilling != null ? !isTransitionedToBlockedBilling.equals(that.isTransitionedToBlockedBilling) : that.isTransitionedToBlockedBilling != null) {
+            return false;
+        }
+        if (isTransitionedToBlockedEntitlement != null ? !isTransitionedToBlockedEntitlement.equals(that.isTransitionedToBlockedEntitlement) : that.isTransitionedToBlockedEntitlement != null) {
+            return false;
+        }
+        if (isTransitionedToUnblockedBilling != null ? !isTransitionedToUnblockedBilling.equals(that.isTransitionedToUnblockedBilling) : that.isTransitionedToUnblockedBilling != null) {
+            return false;
+        }
+        if (isTransitionedToUnblockedEntitlement != null ? !isTransitionedToUnblockedEntitlement.equals(that.isTransitionedToUnblockedEntitlement) : that.isTransitionedToUnblockedEntitlement != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = blockableId != null ? blockableId.hashCode() : 0;
+        result = 31 * result + (blockingType != null ? blockingType.hashCode() : 0);
+        result = 31 * result + (isTransitionedToBlockedBilling != null ? isTransitionedToBlockedBilling.hashCode() : 0);
+        result = 31 * result + (isTransitionedToUnblockedBilling != null ? isTransitionedToUnblockedBilling.hashCode() : 0);
+        result = 31 * result + (isTransitionedToBlockedEntitlement != null ? isTransitionedToBlockedEntitlement.hashCode() : 0);
+        result = 31 * result + (isTransitionedToUnblockedEntitlement != null ? isTransitionedToUnblockedEntitlement.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("DefaultBlockingTransitionInternalEvent{");
+        sb.append("blockableId=").append(blockableId);
+        sb.append(", blockingType=").append(blockingType);
+        sb.append(", isTransitionedToBlockedBilling=").append(isTransitionedToBlockedBilling);
+        sb.append(", isTransitionedToUnblockedBilling=").append(isTransitionedToUnblockedBilling);
+        sb.append(", isTransitionedToBlockedEntitlement=").append(isTransitionedToBlockedEntitlement);
+        sb.append(", isTransitionedToUnblockedEntitlement=").append(isTransitionedToUnblockedEntitlement);
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java
index c8e4f7e..1e5d183 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java
@@ -16,31 +16,50 @@
 
 package com.ning.billing.entitlement.api.svcs;
 
-import com.google.inject.Inject;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.ning.billing.account.api.Account;
+import com.ning.billing.bus.api.PersistentBus;
+import com.ning.billing.bus.api.PersistentBus.EventBusException;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
 import com.ning.billing.clock.Clock;
-import com.ning.billing.entitlement.api.BlockingStateType;
-import com.ning.billing.entitlement.dao.BlockingStateDao;
 import com.ning.billing.entitlement.api.Blockable;
+import com.ning.billing.entitlement.api.BlockingApiException;
 import com.ning.billing.entitlement.api.BlockingState;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.entitlement.api.BlockingStateType;
+import com.ning.billing.entitlement.api.DefaultBlockingTransitionInternalEvent;
+import com.ning.billing.entitlement.block.BlockingChecker;
+import com.ning.billing.entitlement.block.BlockingChecker.BlockingAggregator;
+import com.ning.billing.entitlement.dao.BlockingStateDao;
+import com.ning.billing.events.BlockingTransitionInternalEvent;
 import com.ning.billing.junction.BlockingInternalApi;
 import com.ning.billing.junction.DefaultBlockingState;
 
-import java.util.List;
-import java.util.UUID;
+import com.google.inject.Inject;
 
 public class DefaultInternalBlockingApi implements BlockingInternalApi {
 
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultInternalBlockingApi.class);
+
     private final BlockingStateDao dao;
+    private final BlockingChecker blockingChecker;
     private final Clock clock;
+    private final PersistentBus eventBus;
 
     @Inject
-    public DefaultInternalBlockingApi(final BlockingStateDao dao, final Clock clock) {
+    public DefaultInternalBlockingApi(final BlockingStateDao dao, final BlockingChecker blockingChecker, final PersistentBus eventBus, final Clock clock) {
         this.dao = dao;
         this.clock = clock;
+        this.blockingChecker = blockingChecker;
+        this.eventBus = eventBus;
     }
 
     @Override
@@ -89,14 +108,58 @@ public class DefaultInternalBlockingApi implements BlockingInternalApi {
 
     @Override
     public void setBlockingState(final BlockingState state, final InternalCallContext context) {
+
+        final BlockingAggregator previousState = getBlockingStateFor(state.getBlockedId(), state.getType(), context);
+
         dao.setBlockingState(state, clock, context);
+
+        final BlockingAggregator currentState = getBlockingStateFor(state.getBlockedId(), state.getType(), context);
+        if (previousState != null && currentState != null) {
+            postBlockingTransitionEvent(state.getBlockedId(), state.getType(), previousState, currentState, context);
+        }
     }
 
+    private BlockingAggregator getBlockingStateFor(final UUID blockableId, final BlockingStateType type, final InternalCallContext context) {
+        try {
+            return blockingChecker.getBlockedStatus(blockableId, type, context);
+        } catch (BlockingApiException e) {
+            log.warn("Failed to retrieve blocking state for {} {}", blockableId, type);
+            return null;
+        }
+    }
+
+    private void postBlockingTransitionEvent(final UUID blockableId, final BlockingStateType type,
+            final BlockingAggregator previousState, final BlockingAggregator currentState, final InternalCallContext context) {
+
+        try {
+            final boolean isTransitionToBlockedBilling = !previousState.isBlockBilling() && currentState.isBlockBilling();
+            final boolean isTransitionToUnblockedBilling = previousState.isBlockBilling() && !currentState.isBlockBilling();
+
+            final boolean isTransitionToBlockedEntitlement = !previousState.isBlockEntitlement() && currentState.isBlockEntitlement();
+            final boolean isTransitionToUnblockedEntitlement = previousState.isBlockEntitlement() && !currentState.isBlockEntitlement();
+
+            final BlockingTransitionInternalEvent event = new DefaultBlockingTransitionInternalEvent(blockableId, type,
+                                                                                               isTransitionToBlockedBilling, isTransitionToUnblockedBilling,
+                                                                                               isTransitionToBlockedEntitlement, isTransitionToUnblockedEntitlement,
+
+                                                                                               context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
+
+            // TODO
+            // STEPH Ideally we would like to post from transaction when we inserted the new blocking state, but new state would have to be recalculated from transaction which is
+            // difficult without the help of BlockingChecker -- which itself relies on dao. Other alternative is duplicating the logic, or refactoring the DAO to export higher level api.
+            eventBus.post(event);
+        } catch (EventBusException e) {
+            log.warn("Failed to post event {}", e);
+        }
+
+    }
+
+
     BlockingStateType getBlockingStateType(final Blockable overdueable) {
         if (overdueable instanceof Account) {
             return BlockingStateType.ACCOUNT;
         }
-        // STEPH this is here to ve rify there are no service trying to block on something different than ACCOUNT level
+        // STEPH this is here to verify there are no service trying to block on something different than ACCOUNT level
         // All the other entities
         throw new RuntimeException("Unexpected blockable type");
     }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/block/BlockingChecker.java b/entitlement/src/main/java/com/ning/billing/entitlement/block/BlockingChecker.java
index 64b83c5..5095042 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/block/BlockingChecker.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/block/BlockingChecker.java
@@ -16,9 +16,12 @@
 
 package com.ning.billing.entitlement.block;
 
+import java.util.UUID;
+
 import com.ning.billing.entitlement.api.Blockable;
 import com.ning.billing.entitlement.api.BlockingApiException;
 import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.entitlement.api.BlockingStateType;
 
 public interface BlockingChecker {
 
@@ -40,6 +43,8 @@ public interface BlockingChecker {
     // Only throws if we can't find the blockable enties
     public BlockingAggregator getBlockedStatus(Blockable blockable, InternalTenantContext context) throws BlockingApiException;
 
+    public BlockingAggregator getBlockedStatus(final UUID blockableId, final BlockingStateType type, final InternalTenantContext context) throws BlockingApiException;
+
     public void checkBlockedChange(Blockable blockable, InternalTenantContext context) throws BlockingApiException;
 
     public void checkBlockedEntitlement(Blockable blockable, InternalTenantContext context) throws BlockingApiException;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/block/DefaultBlockingChecker.java b/entitlement/src/main/java/com/ning/billing/entitlement/block/DefaultBlockingChecker.java
index 2dc87c8..a1ca6d8 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/block/DefaultBlockingChecker.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/block/DefaultBlockingChecker.java
@@ -24,6 +24,7 @@ import com.ning.billing.account.api.Account;
 import com.ning.billing.entitlement.api.Blockable;
 import com.ning.billing.entitlement.api.BlockingApiException;
 import com.ning.billing.entitlement.api.BlockingState;
+import com.ning.billing.entitlement.api.BlockingStateType;
 import com.ning.billing.entitlement.dao.BlockingStateDao;
 import com.ning.billing.subscription.api.SubscriptionBase;
 import com.ning.billing.subscription.api.user.SubscriptionBaseApiException;
@@ -155,17 +156,28 @@ public class DefaultBlockingChecker implements BlockingChecker {
     }
 
     @Override
-    public BlockingAggregator getBlockedStatus(final Blockable blockable, final InternalTenantContext context) throws BlockingApiException {
-        if (blockable instanceof SubscriptionBase) {
-            return getBlockedStateSubscription((SubscriptionBase) blockable, context);
-        } else if (blockable instanceof SubscriptionBaseBundle) {
-            return getBlockedStateBundle((SubscriptionBaseBundle) blockable, context);
-        } else { //(blockable instanceof Account) {
-            return getBlockedStateAccount((Account) blockable, context);
+    public BlockingAggregator getBlockedStatus(final UUID blockableId, final BlockingStateType type, final InternalTenantContext context) throws BlockingApiException {
+        if (type == BlockingStateType.SUBSCRIPTION) {
+            return getBlockedStateSubscriptionId(blockableId, context);
+        } else if (type == BlockingStateType.SUBSCRIPTION_BUNDLE) {
+            return getBlockedStateBundleId(blockableId, context);
+        } else { // BlockingStateType.ACCOUNT {
+            return getBlockedStateAccountId(blockableId, context);
         }
     }
 
     @Override
+    public BlockingAggregator getBlockedStatus(final Blockable blockable, final InternalTenantContext context) throws BlockingApiException {
+            if (blockable instanceof SubscriptionBase) {
+                return getBlockedStateSubscription((SubscriptionBase) blockable, context);
+            } else if (blockable instanceof SubscriptionBaseBundle) {
+                return getBlockedStateBundle((SubscriptionBaseBundle) blockable, context);
+            } else { //(blockable instanceof Account) {
+                return getBlockedStateAccount((Account) blockable, context);
+            }
+    }
+
+    @Override
     public void checkBlockedChange(final Blockable blockable, final InternalTenantContext context) throws BlockingApiException {
         if (blockable instanceof SubscriptionBase && getBlockedStateSubscription((SubscriptionBase) blockable, context).isBlockChange()) {
             throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, ACTION_CHANGE, TYPE_SUBSCRIPTION, blockable.getId().toString());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestEventJson.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestEventJson.java
new file mode 100644
index 0000000..718dd7b
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestEventJson.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.entitlement.api;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.entitlement.EntitlementTestSuiteNoDB;
+import com.ning.billing.events.BlockingTransitionInternalEvent;
+import com.ning.billing.util.jackson.ObjectMapper;
+
+public class TestEventJson extends EntitlementTestSuiteNoDB {
+
+    private final ObjectMapper mapper = new ObjectMapper();
+
+    @Test(groups = "fast", description = "Test Blocking event deserialization")
+    public void testDefaultBlockingTransitionInternalEvent() throws Exception {
+        final BlockingTransitionInternalEvent e = new DefaultBlockingTransitionInternalEvent(UUID.randomUUID(), BlockingStateType.ACCOUNT, true, false, false, true, 1L, 2L, null);
+
+        final String json = mapper.writeValueAsString(e);
+
+        final Class<?> claz = Class.forName("com.ning.billing.entitlement.api.DefaultBlockingTransitionInternalEvent");
+        final Object obj = mapper.readValue(json, claz);
+        Assert.assertTrue(obj.equals(e));
+    }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/block/MockBlockingChecker.java b/entitlement/src/test/java/com/ning/billing/entitlement/block/MockBlockingChecker.java
new file mode 100644
index 0000000..1a97df2
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/block/MockBlockingChecker.java
@@ -0,0 +1,49 @@
+/*
+ * 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.entitlement.block;
+
+import java.util.UUID;
+
+import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.entitlement.api.Blockable;
+import com.ning.billing.entitlement.api.BlockingApiException;
+import com.ning.billing.entitlement.api.BlockingStateType;
+
+public class MockBlockingChecker implements BlockingChecker {
+
+    @Override
+    public BlockingAggregator getBlockedStatus(final Blockable blockable, final InternalTenantContext context) throws BlockingApiException {
+        return null;
+    }
+
+    @Override
+    public BlockingAggregator getBlockedStatus(final UUID blockableId, final BlockingStateType type, final InternalTenantContext context) throws BlockingApiException {
+        return null;
+    }
+
+    @Override
+    public void checkBlockedChange(final Blockable blockable, final InternalTenantContext context) throws BlockingApiException {
+    }
+
+    @Override
+    public void checkBlockedEntitlement(final Blockable blockable, final InternalTenantContext context) throws BlockingApiException {
+    }
+
+    @Override
+    public void checkBlockedBilling(final Blockable blockable, final InternalTenantContext context) throws BlockingApiException {
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
index 1046548..8eea774 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
@@ -145,31 +145,4 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
         dao.consumeExstingCBAOnAccountWithUnpaidInvoices(accountId, context);
     }
 
-    @Override
-    public void scheduleInvoiceForAccount(final UUID accountId, final DateTimeZone accountTimeZone, final InternalCallContext context) throws InvoiceApiException {
-        final Map<UUID, List<SubscriptionBase>> subscriptions = subscriptionBaseApi.getSubscriptionsForAccount(context);
-        SubscriptionBase targetSubscription = null;
-        for (UUID key : subscriptions.keySet()) {
-            for (SubscriptionBase cur : subscriptions.get(key)) {
-                if (cur.getCategory() == ProductCategory.ADD_ON) {
-                    continue;
-                }
-                if (cur.getState() != EntitlementState.ACTIVE) {
-                    continue;
-                }
-                if (cur.getCurrentPhase() != null && cur.getCurrentPhase().getBillingPeriod() != BillingPeriod.NO_BILLING_PERIOD) {
-                    targetSubscription = cur;
-                    break;
-                }
-            }
-        }
-        if (targetSubscription == null) {
-            log.info("scheduleInvoiceForAccount : no active subscriptions for account {}", accountId);
-            return;
-        }
-
-        final DateAndTimeZoneContext timeZoneContext = new DateAndTimeZoneContext(targetSubscription.getStartDate(), accountTimeZone, clock);
-        final DateTime futureNotificationTime = timeZoneContext.computeUTCDateTimeFromNow();
-        nextBillingDatePoster.insertNextBillingNotification(accountId, targetSubscription.getId(), futureNotificationTime, context.getUserToken());
-    }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
index 709da03..6efe1e7 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -22,6 +22,10 @@ import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountInternalApi;
+import com.ning.billing.clock.Clock;
+import com.ning.billing.events.BlockingTransitionInternalEvent;
 import com.ning.billing.subscription.api.SubscriptionBaseTransitionType;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.util.callcontext.CallOrigin;
@@ -31,6 +35,7 @@ import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.events.EffectiveEntitlementInternalEvent;
 import com.ning.billing.events.EffectiveSubscriptionInternalEvent;
 import com.ning.billing.events.RepairSubscriptionInternalEvent;
+import com.ning.billing.util.config.InvoiceConfig;
 
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
@@ -38,13 +43,21 @@ import com.google.inject.Inject;
 public class InvoiceListener {
 
     private static final Logger log = LoggerFactory.getLogger(InvoiceListener.class);
+
     private final InvoiceDispatcher dispatcher;
     private final InternalCallContextFactory internalCallContextFactory;
+    private final AccountInternalApi accountApi;
+    private final InvoiceConfig invoiceConfig;
+    private final Clock clock;
 
     @Inject
-    public InvoiceListener(final InternalCallContextFactory internalCallContextFactory, final InvoiceDispatcher dispatcher) {
+    public InvoiceListener(final AccountInternalApi accountApi, final Clock clock, final InternalCallContextFactory internalCallContextFactory,
+                           final InvoiceConfig invoiceConfig, final InvoiceDispatcher dispatcher) {
+        this.accountApi = accountApi;
         this.dispatcher = dispatcher;
+        this.invoiceConfig = invoiceConfig;
         this.internalCallContextFactory = internalCallContextFactory;
+        this.clock = clock;
     }
 
     @Subscribe
@@ -87,6 +100,26 @@ public class InvoiceListener {
         }
     }
 
+    @Subscribe
+    public void handleBlockingStateTransition(final BlockingTransitionInternalEvent event) {
+
+        // We are only interested in unblockBilling transitions or blockBilling transitions when those are configured.
+        if (!event.isTransitionedToUnblockedBilling() &&
+            !(event.isTransitionedToBlockedBilling() && invoiceConfig.isTriggerInvoiceOnBlockingEvent())) {
+            return;
+        }
+
+        try {
+            final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "SubscriptionBaseTransition", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
+            final UUID accountId = accountApi.getByRecordId(event.getSearchKey1(), context);
+            dispatcher.processAccount(accountId, clock.getUTCNow(), false, context);
+        } catch (InvoiceApiException e) {
+            log.error(e.getMessage());
+        } catch (AccountApiException e) {
+            log.error(e.getMessage());
+        }
+    }
+
     public void handleNextBillingDateEvent(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
         try {
             final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index 9673242..d8db31e 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -102,6 +102,11 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
             public boolean isEmailNotificationsEnabled() {
                 return false;
             }
+
+            @Override
+            public boolean isTriggerInvoiceOnBlockingEvent() {
+                return false;
+            }
         };
         this.generator = new DefaultInvoiceGenerator(clock, invoiceConfig);
     }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceNotificationQListener.java b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceNotificationQListener.java
index d426de5..074e5e6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceNotificationQListener.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceNotificationQListener.java
@@ -22,6 +22,8 @@ import javax.inject.Inject;
 
 import org.joda.time.DateTime;
 
+import com.ning.billing.account.api.AccountInternalApi;
+import com.ning.billing.clock.Clock;
 import com.ning.billing.util.callcontext.InternalCallContextFactory;
 
 public class TestInvoiceNotificationQListener extends InvoiceListener {
@@ -30,8 +32,8 @@ public class TestInvoiceNotificationQListener extends InvoiceListener {
     UUID latestSubscriptionId = null;
 
     @Inject
-    public TestInvoiceNotificationQListener(final InternalCallContextFactory internalCallContextFactory, final InvoiceDispatcher dispatcher) {
-        super(internalCallContextFactory, dispatcher);
+    public TestInvoiceNotificationQListener(final AccountInternalApi accountApi, final Clock clock, final InternalCallContextFactory internalCallContextFactory, final InvoiceDispatcher dispatcher) {
+        super(accountApi, clock, internalCallContextFactory, null, dispatcher);
     }
 
     @Override
diff --git a/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModule.java b/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModule.java
index 197341e..0d4307d 100644
--- a/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModule.java
+++ b/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModule.java
@@ -20,6 +20,8 @@ import org.skife.config.ConfigSource;
 
 import com.ning.billing.catalog.MockCatalogModule;
 import com.ning.billing.entitlement.api.svcs.DefaultInternalBlockingApi;
+import com.ning.billing.entitlement.block.BlockingChecker;
+import com.ning.billing.entitlement.block.MockBlockingChecker;
 import com.ning.billing.entitlement.dao.BlockingStateDao;
 import com.ning.billing.entitlement.dao.MockBlockingStateDao;
 import com.ning.billing.mock.glue.MockAccountModule;
@@ -60,5 +62,11 @@ public class TestJunctionModule extends DefaultJunctionModule {
         public void installBlockingStateDao() {
             bind(BlockingStateDao.class).to(MockBlockingStateDao.class).asEagerSingleton();
         }
+
+        @Override
+        public void installBlockingChecker() {
+            bind(BlockingChecker.class).to(MockBlockingChecker.class).asEagerSingleton();
+        }
+
     }
 }
diff --git a/overdue/src/main/java/com/ning/billing/overdue/applicator/DefaultOverdueChangeEvent.java b/overdue/src/main/java/com/ning/billing/overdue/applicator/DefaultOverdueChangeEvent.java
index 1c9288c..c6b4397 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/applicator/DefaultOverdueChangeEvent.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/applicator/DefaultOverdueChangeEvent.java
@@ -83,4 +83,56 @@ public class DefaultOverdueChangeEvent extends BusEventBase implements OverdueCh
     public Boolean isUnblockedBilling() {
         return isUnblockedBilling;
     }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("DefaultOverdueChangeEvent{");
+        sb.append("overdueObjectId=").append(overdueObjectId);
+        sb.append(", previousOverdueStateName='").append(previousOverdueStateName).append('\'');
+        sb.append(", nextOverdueStateName='").append(nextOverdueStateName).append('\'');
+        sb.append(", isBlockedBilling=").append(isBlockedBilling);
+        sb.append(", isUnblockedBilling=").append(isUnblockedBilling);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultOverdueChangeEvent)) {
+            return false;
+        }
+
+        final DefaultOverdueChangeEvent that = (DefaultOverdueChangeEvent) o;
+
+        if (isBlockedBilling != null ? !isBlockedBilling.equals(that.isBlockedBilling) : that.isBlockedBilling != null) {
+            return false;
+        }
+        if (isUnblockedBilling != null ? !isUnblockedBilling.equals(that.isUnblockedBilling) : that.isUnblockedBilling != null) {
+            return false;
+        }
+        if (nextOverdueStateName != null ? !nextOverdueStateName.equals(that.nextOverdueStateName) : that.nextOverdueStateName != null) {
+            return false;
+        }
+        if (overdueObjectId != null ? !overdueObjectId.equals(that.overdueObjectId) : that.overdueObjectId != null) {
+            return false;
+        }
+        if (previousOverdueStateName != null ? !previousOverdueStateName.equals(that.previousOverdueStateName) : that.previousOverdueStateName != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = overdueObjectId != null ? overdueObjectId.hashCode() : 0;
+        result = 31 * result + (previousOverdueStateName != null ? previousOverdueStateName.hashCode() : 0);
+        result = 31 * result + (nextOverdueStateName != null ? nextOverdueStateName.hashCode() : 0);
+        result = 31 * result + (isBlockedBilling != null ? isBlockedBilling.hashCode() : 0);
+        result = 31 * result + (isUnblockedBilling != null ? isUnblockedBilling.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
index 477ad68..1567ce5 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
@@ -49,9 +49,6 @@ import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceInternalApi;
 import com.ning.billing.junction.BlockingInternalApi;
 import com.ning.billing.junction.DefaultBlockingState;
-import com.ning.billing.overdue.notification.OverdueCheckNotificationKey;
-import com.ning.billing.overdue.notification.OverdueCheckNotifier;
-import com.ning.billing.overdue.notification.OverduePoster;
 import com.ning.billing.overdue.OverdueApiException;
 import com.ning.billing.overdue.OverdueCancellationPolicy;
 import com.ning.billing.overdue.OverdueService;
@@ -60,6 +57,9 @@ import com.ning.billing.overdue.config.api.BillingState;
 import com.ning.billing.overdue.config.api.OverdueException;
 import com.ning.billing.overdue.config.api.OverdueStateSet;
 import com.ning.billing.overdue.glue.DefaultOverdueModule;
+import com.ning.billing.overdue.notification.OverdueCheckNotificationKey;
+import com.ning.billing.overdue.notification.OverdueCheckNotifier;
+import com.ning.billing.overdue.notification.OverduePoster;
 import com.ning.billing.tag.TagInternalApi;
 import com.ning.billing.util.dao.NonEntityDao;
 import com.ning.billing.util.email.DefaultEmailSender;
@@ -83,7 +83,6 @@ public class OverdueStateApplicator {
     private final PersistentBus bus;
     private final AccountInternalApi accountApi;
     private final EntitlementApi entitlementApi;
-    private final InvoiceInternalApi invoiceInternalApi;
     private final OverdueEmailGenerator overdueEmailGenerator;
     private final TagInternalApi tagApi;
     private final EmailSender emailSender;
@@ -93,9 +92,8 @@ public class OverdueStateApplicator {
     public OverdueStateApplicator(final BlockingInternalApi accessApi,
                                   final AccountInternalApi accountApi,
                                   final EntitlementApi entitlementApi,
-                                  final InvoiceInternalApi invoiceInternalApi,
                                   final Clock clock,
-                                  @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_CHECK_NAMED)final OverduePoster checkPoster,
+                                  @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_CHECK_NAMED) final OverduePoster checkPoster,
                                   final OverdueEmailGenerator overdueEmailGenerator,
                                   final EmailConfig config,
                                   final PersistentBus bus,
@@ -105,7 +103,6 @@ public class OverdueStateApplicator {
         this.blockingApi = accessApi;
         this.accountApi = accountApi;
         this.entitlementApi = entitlementApi;
-        this.invoiceInternalApi = invoiceInternalApi;
         this.clock = clock;
         this.checkPoster = checkPoster;
         this.overdueEmailGenerator = overdueEmailGenerator;
@@ -137,7 +134,7 @@ public class OverdueStateApplicator {
                 final Period reevaluationInterval = nextOverdueState.isClearState() ? overdueStateSet.getInitialReevaluationInterval() : nextOverdueState.getReevaluationInterval();
                 // If there is no configuration in the config, we assume this is because the overdue conditions are not time based and so there is nothing to retry
                 if (reevaluationInterval == null) {
-                    log.debug("OverdueStateApplicator <notificationQ> : Missing InitialReevaluationInterval from config, NOT inserting notification for account " +  account.getId());
+                    log.debug("OverdueStateApplicator <notificationQ> : Missing InitialReevaluationInterval from config, NOT inserting notification for account " + account.getId());
 
                 } else {
                     log.debug("OverdueStateApplicator <notificationQ> : inserting notification for account " + account.getId() + ", time = " + clock.getUTCNow().plus(reevaluationInterval));
@@ -156,18 +153,13 @@ public class OverdueStateApplicator {
 
             cancelSubscriptionsIfRequired(account, nextOverdueState, context);
 
-            scheduleInvoiceIfNeeded(account, previousOverdueState, nextOverdueState, context);
-
             sendEmailIfRequired(billingState, account, nextOverdueState, context);
 
         } catch (OverdueApiException e) {
             if (e.getCode() != ErrorCode.OVERDUE_NO_REEVALUATION_INTERVAL.getCode()) {
                 throw new OverdueException(e);
             }
-        } catch (InvoiceApiException e) {
-            throw new OverdueException(e);
         }
-
         try {
             bus.post(createOverdueEvent(account, previousOverdueState.getName(), nextOverdueState.getName(), isBlockBillingTransition(previousOverdueState, nextOverdueState),
                                         isUnblockBillingTransition(previousOverdueState, nextOverdueState), context));
@@ -176,19 +168,6 @@ public class OverdueStateApplicator {
         }
     }
 
-    private void scheduleInvoiceIfNeeded(final Account account, final OverdueState previousOverdueState, final OverdueState nextOverdueState, final InternalCallContext context) throws InvoiceApiException {
-        //
-        // Invoice will re-enter a notification to schedule a new invoice with a notificationDate equivalent to today. For a given active subscription on this account:
-        // - If that notificationDate is less or equals than the chargeThroughDate of the given subscription, it means the invoice was previously invoiced and so blocking/unblocking will have an effect,
-        //   a new invoice will be generated
-        // - If that notificationDate is greater than the chargeThroughDate, then that subscription will be invoiced for the next period.
-        //
-        // So in both case a new invoice will be generated.
-        //
-        if (isBlockBillingTransition(previousOverdueState, nextOverdueState) || isUnblockBillingTransition(previousOverdueState, nextOverdueState)) {
-            invoiceInternalApi.scheduleInvoiceForAccount(account.getId(), account.getTimeZone(), context);
-        }
-    }
 
     public void clear(final Account overdueable, final OverdueState previousOverdueState, final OverdueState clearState, final InternalCallContext context) throws OverdueException {
 
diff --git a/util/src/main/java/com/ning/billing/util/config/InvoiceConfig.java b/util/src/main/java/com/ning/billing/util/config/InvoiceConfig.java
index 0a16915..e4c978d 100644
--- a/util/src/main/java/com/ning/billing/util/config/InvoiceConfig.java
+++ b/util/src/main/java/com/ning/billing/util/config/InvoiceConfig.java
@@ -31,4 +31,10 @@ public interface InvoiceConfig extends KillbillConfig {
     @Default("false")
     @Description("Whether to send email notifications on invoice creation (for configured accounts)")
     public boolean isEmailNotificationsEnabled();
+
+
+    @Config("killbill.invoice.triggerInvoiceOnBlockingEvent")
+    @Default("false")
+    @Description("Whether the invoice code regenerate a new invoice when a blocking event is received")
+    public boolean isTriggerInvoiceOnBlockingEvent();
 }
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java
index 54091cf..407e98f 100644
--- a/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java
@@ -36,6 +36,7 @@ public class MockEntitlementModule extends AbstractModule implements Entitlement
         installBlockingStateDao();
         installBlockingApi();
         installEntitlementApi();
+        installBlockingChecker();
     }
 
     @Override