killbill-uncached

entitlement: optimize DefaultSubscriptionApi Go through

11/25/2013 12:49:57 PM

Changes

Details

diff --git a/api/src/main/java/com/ning/billing/entitlement/AccountEntitlements.java b/api/src/main/java/com/ning/billing/entitlement/AccountEntitlements.java
new file mode 100644
index 0000000..3a192e9
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/AccountEntitlements.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.api.Entitlement;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+
+// Wrapper object to save on DAO calls
+public interface AccountEntitlements {
+
+    public Account getAccount();
+
+    // Map bundle id -> bundle
+    public Map<UUID, SubscriptionBaseBundle> getBundles();
+
+    // Map bundle id -> entitlements
+    public Map<UUID, Collection<Entitlement>> getEntitlements();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/AccountEventsStreams.java b/api/src/main/java/com/ning/billing/entitlement/AccountEventsStreams.java
new file mode 100644
index 0000000..94e7817
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/AccountEventsStreams.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.entitlement;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+
+// Wrapper object to save on DAO calls
+public interface AccountEventsStreams {
+
+    public Account getAccount();
+
+    // Map bundle id -> bundle
+    public Map<UUID, SubscriptionBaseBundle> getBundles();
+
+    // Map bundle id -> events streams
+    public Map<UUID, Collection<EventsStream>> getEventsStreams();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/EntitlementInternalApi.java b/api/src/main/java/com/ning/billing/entitlement/EntitlementInternalApi.java
new file mode 100644
index 0000000..44953ea
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/EntitlementInternalApi.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+import java.util.UUID;
+
+import com.ning.billing.entitlement.api.EntitlementApiException;
+import com.ning.billing.util.callcontext.TenantContext;
+
+public interface EntitlementInternalApi {
+
+    public AccountEntitlements getAllEntitlementsForAccountId(UUID accountId, TenantContext context) throws EntitlementApiException;
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/EventsStream.java b/api/src/main/java/com/ning/billing/entitlement/EventsStream.java
new file mode 100644
index 0000000..fc57b74
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/EventsStream.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+
+import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.entitlement.api.BlockingState;
+import com.ning.billing.entitlement.api.Entitlement.EntitlementState;
+import com.ning.billing.subscription.api.SubscriptionBase;
+
+public interface EventsStream {
+
+    UUID getAccountId();
+
+    DateTimeZone getAccountTimeZone();
+
+    UUID getBundleId();
+
+    String getBundleExternalKey();
+
+    UUID getEntitlementId();
+
+    EntitlementState getEntitlementState();
+
+    LocalDate getEntitlementEffectiveEndDate();
+
+    SubscriptionBase getSubscription();
+
+    SubscriptionBase getBaseSubscription();
+
+    boolean isEntitlementActive();
+
+    boolean isBlockChange();
+
+    boolean isEntitlementCancelled();
+
+    boolean isSubscriptionCancelled();
+
+    Collection<BlockingState> getCurrentSubscriptionEntitlementBlockingStatesForServices();
+
+    Collection<BlockingState> getPendingEntitlementCancellationEvents();
+
+    Collection<BlockingState> getSubscriptionEntitlementStates();
+
+    Collection<BlockingState> getBundleEntitlementStates();
+
+    Collection<BlockingState> getAccountEntitlementStates();
+
+    Collection<BlockingState> computeAddonsBlockingStatesForNextSubscriptionBaseEvent(DateTime effectiveDate);
+
+    Collection<BlockingState> computeAddonsBlockingStatesForFutureSubscriptionBaseEvents();
+
+    InternalTenantContext getInternalTenantContext();
+}
diff --git a/api/src/main/java/com/ning/billing/glue/EntitlementModule.java b/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
index 456fb17..bd21d81 100644
--- a/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
+++ b/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
@@ -17,12 +17,15 @@
 package com.ning.billing.glue;
 
 public interface EntitlementModule {
+
     public void installBlockingStateDao();
 
     public void installBlockingApi();
 
     public void installEntitlementApi();
 
+    public void installEntitlementInternalApi();
+
     public void installSubscriptionApi();
 
     public void installBlockingChecker();
diff --git a/api/src/main/java/com/ning/billing/glue/SubscriptionModule.java b/api/src/main/java/com/ning/billing/glue/SubscriptionModule.java
index a400b68..65185eb 100644
--- a/api/src/main/java/com/ning/billing/glue/SubscriptionModule.java
+++ b/api/src/main/java/com/ning/billing/glue/SubscriptionModule.java
@@ -27,5 +27,4 @@ public interface SubscriptionModule {
     public void installSubscriptionInternalApi();
 
     public void installSubscriptionTimelineApi();
-
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
index 983381d..ac479f7 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
@@ -21,10 +21,10 @@ import java.util.Collection;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 
 import com.ning.billing.ErrorCode;
-import com.ning.billing.account.api.Account;
 import com.ning.billing.callcontext.InternalCallContext;
 import com.ning.billing.catalog.api.BillingActionPolicy;
 import com.ning.billing.catalog.api.BillingPeriod;
@@ -36,12 +36,12 @@ import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.clock.Clock;
 import com.ning.billing.entitlement.DefaultEntitlementService;
 import com.ning.billing.entitlement.EntitlementService;
+import com.ning.billing.entitlement.EventsStream;
 import com.ning.billing.entitlement.block.BlockingChecker;
 import com.ning.billing.entitlement.dao.BlockingStateDao;
 import com.ning.billing.entitlement.engine.core.EntitlementNotificationKey;
 import com.ning.billing.entitlement.engine.core.EntitlementNotificationKeyAction;
 import com.ning.billing.entitlement.engine.core.EntitlementUtils;
-import com.ning.billing.entitlement.engine.core.EventsStream;
 import com.ning.billing.entitlement.engine.core.EventsStreamBuilder;
 import com.ning.billing.entity.EntityBase;
 import com.ning.billing.junction.DefaultBlockingState;
@@ -88,7 +88,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
                               final SubscriptionBaseInternalApi subscriptionInternalApi, final BlockingChecker checker,
                               final NotificationQueueService notificationQueueService, final EntitlementUtils entitlementUtils,
                               final EntitlementDateHelper dateHelper, final Clock clock, final InternalCallContextFactory internalCallContextFactory) {
-        super(eventsStream.getSubscription().getId(), eventsStream.getSubscription().getCreatedDate(), eventsStream.getSubscription().getUpdatedDate());
+        super(eventsStream.getEntitlementId(), eventsStream.getSubscription().getCreatedDate(), eventsStream.getSubscription().getUpdatedDate());
         this.eventsStreamBuilder = eventsStreamBuilder;
         this.eventsStream = eventsStream;
         this.dateHelper = dateHelper;
@@ -116,18 +116,22 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
              in.getInternalCallContextFactory());
     }
 
-    public Account getAccount() {
-        return eventsStream.getAccount();
-    }
-
     public EventsStream getEventsStream() {
         return eventsStream;
     }
 
+    public DateTimeZone getAccountTimeZone() {
+        return eventsStream.getAccountTimeZone();
+    }
+
     public SubscriptionBase getSubscriptionBase() {
         return eventsStream.getSubscription();
     }
 
+    public SubscriptionBase getBaseSubscription() {
+        return eventsStream.getBaseSubscription();
+    }
+
     public EventsStreamBuilder getEventsStreamBuilder() {
         return eventsStreamBuilder;
     }
@@ -170,22 +174,22 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
 
     @Override
     public UUID getBaseEntitlementId() {
-        return eventsStream.getSubscription().getId();
+        return eventsStream.getEntitlementId();
     }
 
     @Override
     public UUID getBundleId() {
-        return eventsStream.getSubscription().getBundleId();
+        return eventsStream.getBundleId();
     }
 
     @Override
     public UUID getAccountId() {
-        return eventsStream.getAccount().getId();
+        return eventsStream.getAccountId();
     }
 
     @Override
     public String getExternalKey() {
-        return eventsStream.getBundle().getExternalKey();
+        return eventsStream.getBundleExternalKey();
     }
 
     @Override
@@ -200,7 +204,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
 
     @Override
     public LocalDate getEffectiveStartDate() {
-        return new LocalDate(getSubscriptionBase().getStartDate(), eventsStream.getAccount().getTimeZone());
+        return new LocalDate(getSubscriptionBase().getStartDate(), eventsStream.getAccountTimeZone());
     }
 
     @Override
@@ -319,7 +323,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         // (we don't want an entitlement cancel date one second or so after the subscription cancel date or add-ons cancellations
         // computations won't work).
         final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
-        final LocalDate effectiveLocalDate = new LocalDate(localCancelDate, eventsStream.getAccount().getTimeZone());
+        final LocalDate effectiveLocalDate = new LocalDate(localCancelDate, eventsStream.getAccountTimeZone());
         final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(effectiveLocalDate, getSubscriptionBase().getStartDate(), contextWithValidAccountRecordId);
 
         try {
@@ -341,10 +345,10 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         final LocalDate cancellationDate;
         switch (entitlementPolicy) {
             case IMMEDIATE:
-                cancellationDate = new LocalDate(clock.getUTCNow(), eventsStream.getAccount().getTimeZone());
+                cancellationDate = new LocalDate(clock.getUTCNow(), eventsStream.getAccountTimeZone());
                 break;
             case END_OF_TERM:
-                cancellationDate = getSubscriptionBase().getChargedThroughDate() != null ? new LocalDate(getSubscriptionBase().getChargedThroughDate(), eventsStream.getAccount().getTimeZone()) : new LocalDate(clock.getUTCNow(), eventsStream.getAccount().getTimeZone());
+                cancellationDate = getSubscriptionBase().getChargedThroughDate() != null ? new LocalDate(getSubscriptionBase().getChargedThroughDate(), eventsStream.getAccountTimeZone()) : new LocalDate(clock.getUTCNow(), eventsStream.getAccountTimeZone());
                 break;
             default:
                 throw new RuntimeException("Unsupported policy " + entitlementPolicy);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java
index 5be1c0e..dfd9cd6 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java
@@ -17,9 +17,7 @@
 package com.ning.billing.entitlement.api;
 
 import java.io.IOException;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 import java.util.UUID;
 
 import javax.inject.Inject;
@@ -41,15 +39,16 @@ import com.ning.billing.callcontext.InternalTenantContext;
 import com.ning.billing.catalog.api.BillingActionPolicy;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.AccountEventsStreams;
 import com.ning.billing.entitlement.DefaultEntitlementService;
 import com.ning.billing.entitlement.EntitlementService;
 import com.ning.billing.entitlement.EntitlementTransitionType;
+import com.ning.billing.entitlement.EventsStream;
 import com.ning.billing.entitlement.block.BlockingChecker;
 import com.ning.billing.entitlement.dao.BlockingStateDao;
 import com.ning.billing.entitlement.engine.core.EntitlementNotificationKey;
 import com.ning.billing.entitlement.engine.core.EntitlementNotificationKeyAction;
 import com.ning.billing.entitlement.engine.core.EntitlementUtils;
-import com.ning.billing.entitlement.engine.core.EventsStream;
 import com.ning.billing.entitlement.engine.core.EventsStreamBuilder;
 import com.ning.billing.junction.DefaultBlockingState;
 import com.ning.billing.notificationq.api.NotificationEvent;
@@ -141,8 +140,8 @@ public class DefaultEntitlementApi implements EntitlementApi {
         }
 
         // Check the base entitlement state is not blocked
-        if (eventsStreamForBaseSubscription.getCurrentBlockingAggregator().isBlockChange()) {
-            throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getSubscription().getId().toString()));
+        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.getSubscription().getStartDate(), eventsStreamForBaseSubscription.getInternalTenantContext());
@@ -184,8 +183,21 @@ public class DefaultEntitlementApi implements EntitlementApi {
 
     @Override
     public List<Entitlement> getAllEntitlementsForBundle(final UUID bundleId, final TenantContext tenantContext) throws EntitlementApiException {
-        final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(tenantContext);
-        return getAllEntitlementsForBundle(subscriptionInternalApi.getSubscriptionsForBundle(bundleId, context), tenantContext);
+        final InternalTenantContext internalContext = internalCallContextFactory.createInternalTenantContext(tenantContext);
+        final UUID accountId;
+        try {
+            accountId = subscriptionInternalApi.getBundleFromId(bundleId, internalContext).getAccountId();
+        } catch (SubscriptionBaseApiException e) {
+            throw new EntitlementApiException(e);
+        }
+
+        return ImmutableList.<Entitlement>copyOf(Iterables.<Entitlement>filter(getAllEntitlementsForAccountId(accountId, tenantContext),
+                                                                               new Predicate<Entitlement>() {
+                                                                                   @Override
+                                                                                   public boolean apply(final Entitlement input) {
+                                                                                       return bundleId.equals(input.getBundleId());
+                                                                                   }
+                                                                               }));
     }
 
     @Override
@@ -202,29 +214,20 @@ public class DefaultEntitlementApi implements EntitlementApi {
 
     @Override
     public List<Entitlement> getAllEntitlementsForAccountId(final UUID accountId, final TenantContext tenantContext) throws EntitlementApiException {
+        final EntitlementApi entitlementApi = this;
         final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(accountId, tenantContext);
-        final Map<UUID, List<SubscriptionBase>> subscriptionsPerBundle = subscriptionInternalApi.getSubscriptionsForAccount(context);
-
-        final List<Entitlement> result = new LinkedList<Entitlement>();
-        for (final UUID bundleId : subscriptionsPerBundle.keySet()) {
-            final List<Entitlement> entitlements = getAllEntitlementsForBundle(subscriptionsPerBundle.get(bundleId), tenantContext);
-            result.addAll(entitlements);
-        }
-        return result;
-    }
 
-    private List<Entitlement> getAllEntitlementsForBundle(final List<SubscriptionBase> subscriptions, final TenantContext context) {
-        return Lists.transform(subscriptions,
-                               new Function<SubscriptionBase, Entitlement>() {
-                                   @Override
-                                   public Entitlement apply(final SubscriptionBase input) {
-                                       try {
-                                           return getEntitlementForId(input.getId(), context);
-                                       } catch (EntitlementApiException e) {
-                                           throw new RuntimeException("Failed to extract blocking state for subscription " + input.getId().toString());
-                                       }
-                                   }
-                               });
+        final AccountEventsStreams accountEventsStreams = eventsStreamBuilder.buildForAccount(context);
+        final List<EventsStream> eventsStreams = ImmutableList.<EventsStream>copyOf(Iterables.<EventsStream>concat(accountEventsStreams.getEventsStreams().values()));
+        return Lists.<EventsStream, Entitlement>transform(eventsStreams,
+                                                          new Function<EventsStream, Entitlement>() {
+                                                              @Override
+                                                              public Entitlement apply(final EventsStream eventsStream) {
+                                                                  return new DefaultEntitlement(eventsStream, eventsStreamBuilder, entitlementApi,
+                                                                                                blockingStateDao, subscriptionInternalApi, checker, notificationQueueService,
+                                                                                                entitlementUtils, dateHelper, clock, internalCallContextFactory);
+                                                              }
+                                                          });
     }
 
     @Override
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscription.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscription.java
index 25842b7..3938659 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscription.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscription.java
@@ -16,29 +16,32 @@
 
 package com.ning.billing.entitlement.api;
 
-import java.util.List;
+import java.util.Collection;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
 public class DefaultSubscription extends DefaultEntitlement implements Subscription {
 
-    private final List<BlockingState> blockingStates;
+    private final Collection<BlockingState> currentSubscriptionBlockingStatesForServices;
 
-    DefaultSubscription(final DefaultEntitlement entitlement, final List<BlockingState> blockingStates) {
+    DefaultSubscription(final DefaultEntitlement entitlement) {
         super(entitlement);
-        this.blockingStates = blockingStates;
+        this.currentSubscriptionBlockingStatesForServices = eventsStream.getCurrentSubscriptionEntitlementBlockingStatesForServices();
     }
 
     @Override
     public LocalDate getBillingStartDate() {
-        return new LocalDate(getSubscriptionBase().getStartDate(), getAccount().getTimeZone());
+        return new LocalDate(getSubscriptionBase().getStartDate(), getAccountTimeZone());
     }
 
     @Override
     public LocalDate getBillingEndDate() {
         final DateTime futureOrCurrentEndDateForSubscription = getSubscriptionBase().getEndDate() != null ? getSubscriptionBase().getEndDate() : getSubscriptionBase().getFutureEndDate();
-        final DateTime futureOrCurrentEndDateForBaseSubscription = getEventsStream().getBaseSubscription().getEndDate() != null ? getEventsStream().getBaseSubscription().getEndDate() : getEventsStream().getBaseSubscription().getFutureEndDate();
+        final DateTime futureOrCurrentEndDateForBaseSubscription = getBaseSubscription().getEndDate() != null ? getBaseSubscription().getEndDate() : getBaseSubscription().getFutureEndDate();
 
         final DateTime futureOrCurrentEndDate;
         if (futureOrCurrentEndDateForBaseSubscription != null && futureOrCurrentEndDateForBaseSubscription.isBefore(futureOrCurrentEndDateForSubscription)) {
@@ -47,25 +50,27 @@ public class DefaultSubscription extends DefaultEntitlement implements Subscript
             futureOrCurrentEndDate = futureOrCurrentEndDateForSubscription;
         }
 
-        return futureOrCurrentEndDate != null ? new LocalDate(futureOrCurrentEndDate, getAccount().getTimeZone()) : null;
+        return futureOrCurrentEndDate != null ? new LocalDate(futureOrCurrentEndDate, getAccountTimeZone()) : null;
     }
 
     @Override
     public LocalDate getChargedThroughDate() {
-        return getSubscriptionBase().getChargedThroughDate() != null ? new LocalDate(getSubscriptionBase().getChargedThroughDate(), getAccount().getTimeZone()) : null;
+        return getSubscriptionBase().getChargedThroughDate() != null ? new LocalDate(getSubscriptionBase().getChargedThroughDate(), getAccountTimeZone()) : null;
     }
 
     @Override
     public String getCurrentStateForService(final String serviceName) {
-
-        if (blockingStates == null) {
+        if (currentSubscriptionBlockingStatesForServices == null) {
             return null;
+        } else {
+            final BlockingState blockingState = Iterables.<BlockingState>tryFind(currentSubscriptionBlockingStatesForServices,
+                                                                                 new Predicate<BlockingState>() {
+                                                                                     @Override
+                                                                                     public boolean apply(final BlockingState input) {
+                                                                                         return serviceName.equals(input.getService());
+                                                                                     }
+                                                                                 }).orNull();
+            return blockingState == null ? null : blockingState.getService();
         }
-        for (BlockingState cur : blockingStates) {
-            if (cur.getService().equals(serviceName)) {
-                return cur.getStateName();
-            }
-        }
-        return null;
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java
index 850317f..d3b480e 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java
@@ -17,11 +17,10 @@
 package com.ning.billing.entitlement.api;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 import java.util.UUID;
 
 import javax.inject.Inject;
@@ -29,217 +28,184 @@ import javax.inject.Inject;
 import org.joda.time.DateTimeZone;
 
 import com.ning.billing.ErrorCode;
-import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountApiException;
-import com.ning.billing.account.api.AccountInternalApi;
+import com.ning.billing.ObjectType;
 import com.ning.billing.callcontext.InternalTenantContext;
-import com.ning.billing.clock.Clock;
-import com.ning.billing.entitlement.block.BlockingChecker;
-import com.ning.billing.entitlement.dao.BlockingStateDao;
+import com.ning.billing.entitlement.AccountEntitlements;
+import com.ning.billing.entitlement.EntitlementInternalApi;
 import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
 import com.ning.billing.subscription.api.user.SubscriptionBaseApiException;
 import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+import com.ning.billing.util.cache.Cachable.CacheType;
+import com.ning.billing.util.cache.CacheControllerDispatcher;
 import com.ning.billing.util.callcontext.InternalCallContextFactory;
 import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.customfield.ShouldntHappenException;
+import com.ning.billing.util.dao.NonEntityDao;
 
-import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.ListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 
 public class DefaultSubscriptionApi implements SubscriptionApi {
 
+    private final EntitlementInternalApi entitlementInternalApi;
     private final SubscriptionBaseInternalApi subscriptionInternalApi;
-    private final EntitlementApi entitlementApi;
-    private final BlockingChecker checker;
-    private final BlockingStateDao blockingStateDao;
-    private final EntitlementDateHelper dateHelper;
-    private final Clock clock;
     private final InternalCallContextFactory internalCallContextFactory;
-    private final AccountInternalApi accountApi;
+    private final NonEntityDao nonEntityDao;
+    private final CacheControllerDispatcher cacheControllerDispatcher;
 
     @Inject
-    public DefaultSubscriptionApi(final SubscriptionBaseInternalApi subscriptionInternalApi, final EntitlementApi entitlementApi, final BlockingChecker checker, final BlockingStateDao blockingStateDao, final AccountInternalApi accountApi, final Clock clock, final InternalCallContextFactory internalCallContextFactory) {
+    public DefaultSubscriptionApi(final EntitlementInternalApi entitlementInternalApi, final SubscriptionBaseInternalApi subscriptionInternalApi,
+                                  final InternalCallContextFactory internalCallContextFactory, final NonEntityDao nonEntityDao, final CacheControllerDispatcher cacheControllerDispatcher) {
+        this.entitlementInternalApi = entitlementInternalApi;
         this.subscriptionInternalApi = subscriptionInternalApi;
-        this.entitlementApi = entitlementApi;
-        this.accountApi = accountApi;
-        this.checker = checker;
-        this.blockingStateDao = blockingStateDao;
-        this.dateHelper = new EntitlementDateHelper(accountApi, clock);
-        ;
-        this.clock = clock;
         this.internalCallContextFactory = internalCallContextFactory;
+        this.nonEntityDao = nonEntityDao;
+        this.cacheControllerDispatcher = cacheControllerDispatcher;
     }
 
     @Override
     public Subscription getSubscriptionForEntitlementId(final UUID entitlementId, final TenantContext context) throws SubscriptionApiException {
+        final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(entitlementId, ObjectType.SUBSCRIPTION, cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID));
+        final UUID accountId = nonEntityDao.retrieveIdFromObject(accountRecordId, ObjectType.ACCOUNT);
+
+        // Retrieve entitlements
+        final AccountEntitlements accountEntitlements;
         try {
-            final Entitlement entitlement = entitlementApi.getEntitlementForId(entitlementId, context);
-            final InternalTenantContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalTenantContext(entitlement.getAccountId(), context);
-            return fromEntitlement(entitlement, contextWithValidAccountRecordId);
+            accountEntitlements = entitlementInternalApi.getAllEntitlementsForAccountId(accountId, context);
         } catch (EntitlementApiException e) {
             throw new SubscriptionApiException(e);
         }
+
+        // Build subscriptions
+        final Iterable<Subscription> accountSubscriptions = Iterables.<Subscription>concat(buildSubscriptionsFromEntitlements(accountEntitlements).values());
+
+        return Iterables.<Subscription>find(accountSubscriptions,
+                                            new Predicate<Subscription>() {
+                                                @Override
+                                                public boolean apply(final Subscription subscription) {
+                                                    return subscription.getId().equals(entitlementId);
+                                                }
+                                            });
     }
 
     @Override
     public SubscriptionBundle getSubscriptionBundle(final UUID bundleId, final TenantContext context) throws SubscriptionApiException {
-
-        try {
-
-            final List<Entitlement> entitlements = entitlementApi.getAllEntitlementsForBundle(bundleId, context);
-            if (entitlements.isEmpty()) {
-                throw new SubscriptionApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_ID, bundleId);
-            }
-            return getSubscriptionBundleFromEntitlements(bundleId, entitlements, context);
-        } catch (EntitlementApiException e) {
-            throw new SubscriptionApiException(e);
-        } catch (SubscriptionBaseApiException e) {
-            throw new SubscriptionApiException(e);
-        } catch (AccountApiException e) {
-            throw new SubscriptionApiException(e);
+        final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(bundleId, ObjectType.BUNDLE, cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID));
+        final UUID accountId = nonEntityDao.retrieveIdFromObject(accountRecordId, ObjectType.ACCOUNT);
+
+        final Optional<SubscriptionBundle> bundleOptional = Iterables.<SubscriptionBundle>tryFind(getSubscriptionBundlesForAccount(accountId, context),
+                                                                                                  new Predicate<SubscriptionBundle>() {
+                                                                                                      @Override
+                                                                                                      public boolean apply(final SubscriptionBundle bundle) {
+                                                                                                          return bundle.getId().equals(bundleId);
+                                                                                                      }
+                                                                                                  });
+        if (!bundleOptional.isPresent()) {
+            throw new SubscriptionApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_ID, bundleId);
+        } else {
+            return bundleOptional.get();
         }
     }
 
-
     @Override
     public List<SubscriptionBundle> getSubscriptionBundlesForAccountIdAndExternalKey(final UUID accountId, final String externalKey, final TenantContext context) throws SubscriptionApiException {
-
-        try {
-
-            final List<Entitlement> entitlements = entitlementApi.getAllEntitlementsForAccountIdAndExternalKey(accountId, externalKey, context);
-            if (entitlements.isEmpty()) {
-                throw new SubscriptionApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_KEY, externalKey);
-            }
-            return getSubscriptionBundles(entitlements, context);
-        } catch (EntitlementApiException e) {
-            throw new SubscriptionApiException(e);
-        } catch (SubscriptionBaseApiException e) {
-            throw new SubscriptionApiException(e);
-        } catch (AccountApiException e) {
-            throw new SubscriptionApiException(e);
-        }
+        return ImmutableList.<SubscriptionBundle>copyOf(Iterables.<SubscriptionBundle>filter(getSubscriptionBundlesForAccount(accountId, context),
+                                                                                             new Predicate<SubscriptionBundle>() {
+                                                                                                 @Override
+                                                                                                 public boolean apply(final SubscriptionBundle bundle) {
+                                                                                                     return bundle.getExternalKey().equals(externalKey);
+                                                                                                 }
+                                                                                             }));
     }
 
     @Override
-    public SubscriptionBundle getActiveSubscriptionBundleForExternalKey(final String externalKey,
-                                                                        final TenantContext context) throws SubscriptionApiException {
-
+    public SubscriptionBundle getActiveSubscriptionBundleForExternalKey(final String externalKey, final TenantContext context) throws SubscriptionApiException {
         final InternalTenantContext internalContext = internalCallContextFactory.createInternalTenantContext(context);
         try {
             final SubscriptionBaseBundle baseBundle = subscriptionInternalApi.getActiveBundleForKey(externalKey, internalContext);
-            final List<Entitlement> allEntitlementsForBundle = entitlementApi.getAllEntitlementsForBundle(baseBundle.getId(), context);
-
-            return getSubscriptionBundleFromEntitlements(baseBundle.getId(), allEntitlementsForBundle, context);
+            return getSubscriptionBundle(baseBundle.getId(), context);
         } catch (SubscriptionBaseApiException e) {
             throw new SubscriptionApiException(e);
-        } catch (EntitlementApiException e) {
-            throw new SubscriptionApiException(e);
-        } catch (AccountApiException e) {
-            throw new SubscriptionApiException(e);
         }
     }
 
     @Override
     public List<SubscriptionBundle> getSubscriptionBundlesForExternalKey(final String externalKey, final TenantContext context) throws SubscriptionApiException {
-
         final InternalTenantContext internalContext = internalCallContextFactory.createInternalTenantContext(context);
-        try {
-            final List<SubscriptionBaseBundle> baseBundles = subscriptionInternalApi.getBundlesForKey(externalKey, internalContext);
-            final List<SubscriptionBundle> result = new ArrayList<SubscriptionBundle>(baseBundles.size());
-            for (SubscriptionBaseBundle cur : baseBundles) {
-                final List<Entitlement> allEntitlementsForBundle = entitlementApi.getAllEntitlementsForBundle(cur.getId(), context);
-                final SubscriptionBundle bundle = getSubscriptionBundleFromEntitlements(cur.getId(), allEntitlementsForBundle, context);
-                result.add(bundle);
-            }
-            return result;
-        } catch (SubscriptionBaseApiException e) {
-            throw new SubscriptionApiException(e);
-        } catch (EntitlementApiException e) {
-            throw new SubscriptionApiException(e);
-        } catch (AccountApiException e) {
-            throw new SubscriptionApiException(e);
+        final List<SubscriptionBaseBundle> baseBundles = subscriptionInternalApi.getBundlesForKey(externalKey, internalContext);
+
+        final List<SubscriptionBundle> result = new ArrayList<SubscriptionBundle>(baseBundles.size());
+        for (final SubscriptionBaseBundle cur : baseBundles) {
+            final SubscriptionBundle bundle = getSubscriptionBundle(cur.getId(), context);
+            result.add(bundle);
         }
+
+        return result;
     }
 
     @Override
-    public List<SubscriptionBundle> getSubscriptionBundlesForAccountId(final UUID accountId,
-                                                                       final TenantContext context) throws SubscriptionApiException {
-        try {
+    public List<SubscriptionBundle> getSubscriptionBundlesForAccountId(final UUID accountId, final TenantContext context) throws SubscriptionApiException {
+        return getSubscriptionBundlesForAccount(accountId, context);
+    }
 
-            final List<Entitlement> entitlements = entitlementApi.getAllEntitlementsForAccountId(accountId, context);
-            if (entitlements.isEmpty()) {
-                return Collections.emptyList();
-            }
-            return getSubscriptionBundles(entitlements, context);
+    private List<SubscriptionBundle> getSubscriptionBundlesForAccount(final UUID accountId, final TenantContext tenantContext) throws SubscriptionApiException {
+        // Retrieve entitlements
+        final AccountEntitlements accountEntitlements;
+        try {
+            accountEntitlements = entitlementInternalApi.getAllEntitlementsForAccountId(accountId, tenantContext);
         } catch (EntitlementApiException e) {
             throw new SubscriptionApiException(e);
-        } catch (SubscriptionBaseApiException e) {
-            throw new SubscriptionApiException(e);
-        } catch (AccountApiException e) {
-            throw new SubscriptionApiException(e);
         }
-    }
 
-    private List<SubscriptionBundle> getSubscriptionBundles(final List<Entitlement> entitlements, final TenantContext context) throws SubscriptionBaseApiException, AccountApiException {
-        final ListMultimap<UUID, Entitlement> perBundleEntitlements = LinkedListMultimap.create();
-        for (Entitlement cur : entitlements) {
-            perBundleEntitlements.put(cur.getBundleId(), cur);
+        // Build subscriptions
+        final Map<UUID, List<Subscription>> subscriptionsPerBundle = buildSubscriptionsFromEntitlements(accountEntitlements);
+
+        final DateTimeZone accountTimeZone = accountEntitlements.getAccount().getTimeZone();
+
+        // Build subscription bundles
+        final List<SubscriptionBundle> bundles = new LinkedList<SubscriptionBundle>();
+        for (final UUID bundleId : subscriptionsPerBundle.keySet()) {
+            final List<Subscription> subscriptionsForBundle = subscriptionsPerBundle.get(bundleId);
+            final String externalKey = subscriptionsForBundle.get(0).getExternalKey();
+
+            final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone,
+                                                                                              accountId,
+                                                                                              bundleId,
+                                                                                              externalKey,
+                                                                                              accountEntitlements.getEntitlements().get(bundleId));
+
+            final SubscriptionBaseBundle baseBundle = accountEntitlements.getBundles().get(bundleId);
+            final SubscriptionBundle subscriptionBundle = new DefaultSubscriptionBundle(bundleId,
+                                                                                        accountId,
+                                                                                        externalKey,
+                                                                                        subscriptionsForBundle,
+                                                                                        timeline,
+                                                                                        baseBundle.getOriginalCreatedDate(),
+                                                                                        baseBundle.getCreatedDate(),
+                                                                                        baseBundle.getUpdatedDate());
+            bundles.add(subscriptionBundle);
         }
 
-        final List<SubscriptionBundle> result = new ArrayList<SubscriptionBundle>(perBundleEntitlements.keySet().size());
-        for (UUID bundleId : perBundleEntitlements.keySet()) {
-            final List<Entitlement> allEntitlementsForBundle = perBundleEntitlements.get(bundleId);
-            final SubscriptionBundle bundle = getSubscriptionBundleFromEntitlements(bundleId, allEntitlementsForBundle, context);
-            result.add(bundle);
-        }
-        return result;
+        return bundles;
     }
 
-    private Subscription fromEntitlement(final Entitlement entitlement, final InternalTenantContext internalTenantContext) {
-
-        final List<BlockingState> states = blockingStateDao.getBlockingState(entitlement.getId(), BlockingStateType.SUBSCRIPTION, internalTenantContext);
-        final Subscription result = new DefaultSubscription((DefaultEntitlement) entitlement, states);
-        return result;
-    }
-
-    private SubscriptionBundle getSubscriptionBundleFromEntitlements(final UUID bundleId, final List<Entitlement> entitlements, final TenantContext context) throws SubscriptionBaseApiException, AccountApiException {
-        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
-        final SubscriptionBaseBundle baseBundle = subscriptionInternalApi.getBundleFromId(bundleId, internalTenantContext);
-        final List<Subscription> subscriptions = new ArrayList<Subscription>(entitlements.size());
-        subscriptions.addAll(Collections2.transform(entitlements, new Function<Entitlement, Subscription>() {
-            @Override
-            public Subscription apply(final Entitlement input) {
-                return fromEntitlement(input, internalTenantContext);
+    private Map<UUID, List<Subscription>> buildSubscriptionsFromEntitlements(final AccountEntitlements accountEntitlements) {
+        final Map<UUID, List<Subscription>> subscriptionsPerBundle = new HashMap<UUID, List<Subscription>>();
+        for (final UUID bundleId : accountEntitlements.getEntitlements().keySet()) {
+            if (subscriptionsPerBundle.get(bundleId) == null) {
+                subscriptionsPerBundle.put(bundleId, new LinkedList<Subscription>());
             }
-        }));
-
-        final Account account = accountApi.getAccountById(baseBundle.getAccountId(), internalTenantContext);
-
-        final InternalTenantContext internalTenantContextWithAccountRecordId = internalCallContextFactory.createInternalTenantContext(account.getId(), context);
-
-        final DateTimeZone accountTimeZone = account.getTimeZone();
 
-        final List<BlockingState> allBlockingStatesPerAccountRecordId = blockingStateDao.getBlockingAllForAccountRecordId(internalTenantContextWithAccountRecordId);
-        final Set<UUID> allEntitlementIds = new HashSet<UUID>(Collections2.<Entitlement, UUID>transform(entitlements, new Function<Entitlement, UUID>() {
-            @Override
-            public UUID apply(final Entitlement input) {
-                return input.getId();
+            for (final Entitlement entitlement : accountEntitlements.getEntitlements().get(bundleId)) {
+                if (entitlement instanceof DefaultEntitlement) {
+                    subscriptionsPerBundle.get(bundleId).add(new DefaultSubscription((DefaultEntitlement) entitlement));
+                } else {
+                    throw new ShouldntHappenException("Entitlement should be a DefaultEntitlement instance");
+                }
             }
-        }));
-
-        final List<BlockingState> filteredBlockingStates = new LinkedList<BlockingState>(Collections2.filter(allBlockingStatesPerAccountRecordId, new Predicate<BlockingState>() {
-            @Override
-            public boolean apply(final BlockingState input) {
-                return input.getType() == BlockingStateType.ACCOUNT ||
-                       (input.getType() == BlockingStateType.SUBSCRIPTION_BUNDLE && input.getBlockedId().equals(bundleId)) ||
-                       (input.getType() == BlockingStateType.SUBSCRIPTION && allEntitlementIds.contains(input.getBlockedId()));
-            }
-        }));
-
-        final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, account.getId(), bundleId, baseBundle.getExternalKey(), entitlements, filteredBlockingStates);
-        final DefaultSubscriptionBundle bundle = new DefaultSubscriptionBundle(bundleId, baseBundle.getAccountId(), baseBundle.getExternalKey(), subscriptions, timeline, baseBundle.getOriginalCreatedDate() , baseBundle.getCreatedDate(), baseBundle.getUpdatedDate());
-        return bundle;
+        }
+        return subscriptionsPerBundle;
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
index 796eb06..608e6b6 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
@@ -21,6 +21,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -56,13 +57,11 @@ import com.google.common.collect.ImmutableList;
 
 public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTimeline {
 
-
     private final Logger logger = LoggerFactory.getLogger(DefaultSubscriptionBundleTimeline.class);
 
     // STEPH This is added to give us confidence the timeline we generate behaves as expected. Could be removed at some point
     private final static String TIMELINE_WARN_LOG = "Sanity Timeline: ";
 
-
     public static final String BILLING_SERVICE_NAME = "billing-service";
     public static final String ENT_BILLING_SERVICE_NAME = "entitlement+billing-service";
 
@@ -71,6 +70,18 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
     private final UUID bundleId;
     private final String externalKey;
 
+    public DefaultSubscriptionBundleTimeline(final DateTimeZone accountTimeZone, final UUID accountId, final UUID bundleId, final String externalKey, final Collection<Entitlement> entitlements) {
+        final Collection<BlockingState> blockingStates = new HashSet<BlockingState>();
+        for (final Entitlement entitlement : entitlements) {
+            blockingStates.addAll(((DefaultEntitlement) entitlement).getEventsStream().getSubscriptionEntitlementStates());
+            blockingStates.addAll(((DefaultEntitlement) entitlement).getEventsStream().getBundleEntitlementStates());
+            blockingStates.addAll(((DefaultEntitlement) entitlement).getEventsStream().getAccountEntitlementStates());
+        }
+        this.accountId = accountId;
+        this.bundleId = bundleId;
+        this.externalKey = externalKey;
+        this.events = computeEvents(entitlements, new LinkedList<BlockingState>(blockingStates), accountTimeZone);
+    }
 
     public DefaultSubscriptionBundleTimeline(final DateTimeZone accountTimeZone, final UUID accountId, final UUID bundleId, final String externalKey, final List<Entitlement> entitlements, List<BlockingState> allBlockingStates) {
         this.accountId = accountId;
@@ -85,7 +96,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
     // - base subscription events are already ordered for each Entitlement and so when we reorder at the bundle level we try not to break that initial ordering
     // - blocking state events occur at various level (account, bundle and subscription) so for higher level, we need to dispatch that on each subscription.
     //
-    private List<SubscriptionEvent> computeEvents(final List<Entitlement> entitlements, List<BlockingState> allBlockingStates, final DateTimeZone accountTimeZone) {
+    private List<SubscriptionEvent> computeEvents(final Collection<Entitlement> entitlements, List<BlockingState> allBlockingStates, final DateTimeZone accountTimeZone) {
 
         // Extract ids for all entitlement in the list
         final Set<UUID> allEntitlementUUIDs = new TreeSet(Collections2.transform(entitlements, new Function<Entitlement, UUID>() {
@@ -171,7 +182,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
 
             int currentIndex = i;
             if (shouldSwap) {
-                Collections.swap(events, i, i+1);
+                Collections.swap(events, i, i + 1);
             }
             if (shouldReverseSort) {
                 while (currentIndex >= 1) {
@@ -189,7 +200,6 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
         }
     }
 
-
     private boolean shouldSwap(DefaultSubscriptionEvent cur, DefaultSubscriptionEvent other, boolean isAscending) {
 
         // For a given date, order by subscriptionId, and within subscription by event type
@@ -198,12 +208,11 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
                 ((isAscending &&
                   ((idComp > 0) ||
                    (idComp == 0 && cur.getSubscriptionEventType().ordinal() > other.getSubscriptionEventType().ordinal()))) ||
-                (!isAscending &&
-                   ((idComp < 0) ||
-                    (idComp == 0 && cur.getSubscriptionEventType().ordinal() < other.getSubscriptionEventType().ordinal())))));
+                 (!isAscending &&
+                  ((idComp < 0) ||
+                   (idComp == 0 && cur.getSubscriptionEventType().ordinal() < other.getSubscriptionEventType().ordinal())))));
     }
 
-
     private void insertAfterIndex(final LinkedList<SubscriptionEvent> original, final List<SubscriptionEvent> newEvents, int index) {
 
         final boolean firstPosition = (index == -1);
@@ -227,7 +236,6 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
     //
     private int insertFromBlockingEvent(final DateTimeZone accountTimeZone, final Set<UUID> allEntitlementUUIDs, final List<SubscriptionEvent> result, final BlockingState bs, final DateTime bsEffectiveDate, final List<SubscriptionEvent> newEvents) {
 
-
         // Keep the current state per entitlement
         final Map<UUID, TargetState> targetStates = new HashMap<UUID, TargetState>();
         for (UUID cur : allEntitlementUUIDs) {
@@ -325,7 +333,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
     }
 
     // Compute the initial stream of events based on the subscription base events
-    private LinkedList<SubscriptionEvent> computeSubscriptionBaseEvents(final List<Entitlement> entitlements, final DateTimeZone accountTimeZone) {
+    private LinkedList<SubscriptionEvent> computeSubscriptionBaseEvents(final Collection<Entitlement> entitlements, final DateTimeZone accountTimeZone) {
         final LinkedList<SubscriptionEvent> result = new LinkedList<SubscriptionEvent>();
         for (final Entitlement cur : entitlements) {
             final SubscriptionBase base = ((DefaultEntitlement) cur).getSubscriptionBase();
@@ -404,7 +412,6 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
         result.add(index, event);
     }
 
-
     private SubscriptionEvent toSubscriptionEvent(final SubscriptionEvent prev, final SubscriptionEvent next, final UUID entitlementId, final BlockingState in, final SubscriptionEventType eventType, final DateTimeZone accountTimeZone) {
         return new DefaultSubscriptionEvent(in.getId(),
                                             entitlementId,
@@ -430,7 +437,6 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
                                             accountTimeZone);
     }
 
-
     private SubscriptionEvent toSubscriptionEvent(final SubscriptionBaseTransition in, final SubscriptionEventType eventType, final DateTimeZone accountTimeZone) {
         return new DefaultSubscriptionEvent(in.getId(),
                                             in.getSubscriptionId(),
@@ -526,415 +532,413 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
         return events;
     }
 
-//
-// Internal class to keep the state associated with each subscription
-//
-private final static class TargetState {
-
-    private boolean isEntitlementStarted;
-    private boolean isEntitlementStopped;
-    private boolean isBillingStarted;
-    private boolean isBillingStopped;
-    private Map<String, BlockingState> perServiceBlockingState;
-
-    public TargetState() {
-        this.isEntitlementStarted = false;
-        this.isEntitlementStopped = false;
-        this.isBillingStarted = false;
-        this.isBillingStopped = false;
-        this.perServiceBlockingState = new HashMap<String, BlockingState>();
-    }
+    //
+    // Internal class to keep the state associated with each subscription
+    //
+    private final static class TargetState {
 
-    public void setEntitlementStarted() {
-        isEntitlementStarted = true;
-    }
+        private boolean isEntitlementStarted;
+        private boolean isEntitlementStopped;
+        private boolean isBillingStarted;
+        private boolean isBillingStopped;
+        private Map<String, BlockingState> perServiceBlockingState;
 
-    public void setEntitlementStopped() {
-        isEntitlementStopped = true;
-    }
+        public TargetState() {
+            this.isEntitlementStarted = false;
+            this.isEntitlementStopped = false;
+            this.isBillingStarted = false;
+            this.isBillingStopped = false;
+            this.perServiceBlockingState = new HashMap<String, BlockingState>();
+        }
 
-    public void setBillingStarted() {
-        isBillingStarted = true;
-    }
+        public void setEntitlementStarted() {
+            isEntitlementStarted = true;
+        }
 
-    public void setBillingStopped() {
-        isBillingStopped = true;
-    }
+        public void setEntitlementStopped() {
+            isEntitlementStopped = true;
+        }
 
-    public void addEntitlementEvent(final SubscriptionEvent e) {
-        final BlockingState converted = new DefaultBlockingState(e.getEntitlementId(), BlockingStateType.SUBSCRIPTION,
-                                                                 e.getServiceStateName(), e.getServiceName(), false, e.isBlockedEntitlement(), e.isBlockedBilling(),
-                                                                 ((DefaultSubscriptionEvent) e).getEffectiveDateTime());
-        perServiceBlockingState.put(converted.getService(), converted);
+        public void setBillingStarted() {
+            isBillingStarted = true;
+        }
 
-    }
+        public void setBillingStopped() {
+            isBillingStopped = true;
+        }
+
+        public void addEntitlementEvent(final SubscriptionEvent e) {
+            final BlockingState converted = new DefaultBlockingState(e.getEntitlementId(), BlockingStateType.SUBSCRIPTION,
+                                                                     e.getServiceStateName(), e.getServiceName(), false, e.isBlockedEntitlement(), e.isBlockedBilling(),
+                                                                     ((DefaultSubscriptionEvent) e).getEffectiveDateTime());
+            perServiceBlockingState.put(converted.getService(), converted);
 
-    //
-    // From the current state of that subscription, compute the effect of the new state based on the incoming blockingState event
-    //
-    private List<SubscriptionEventType> addStateAndReturnEventTypes(final BlockingState bs) {
-
-        // Turn off isBlockedEntitlement and isBlockedBilling if there was not start event
-        final BlockingState fixedBlockingState = new DefaultBlockingState(bs.getBlockedId(),
-                                                                          bs.getType(),
-                                                                          bs.getStateName(),
-                                                                          bs.getService(),
-                                                                          bs.isBlockChange(),
-                                                                          (bs.isBlockEntitlement() && isEntitlementStarted && !isEntitlementStopped),
-                                                                          (bs.isBlockBilling() && isBillingStarted && !isBillingStopped),
-                                                                          bs.getEffectiveDate());
-
-        final List<SubscriptionEventType> result = new ArrayList<SubscriptionEventType>(4);
-        if (fixedBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CANCELLED)) {
-            isEntitlementStopped = true;
-            result.add(SubscriptionEventType.STOP_ENTITLEMENT);
-            return result;
         }
 
         //
-        // We look at the effect of the incoming event for the specific service, and then recompute the state after so we can compare if anything has changed
-        // across all services
+        // From the current state of that subscription, compute the effect of the new state based on the incoming blockingState event
         //
-        final BlockingAggregator stateBefore = getState();
-        perServiceBlockingState.put(fixedBlockingState.getService(), fixedBlockingState);
-        final BlockingAggregator stateAfter = getState();
+        private List<SubscriptionEventType> addStateAndReturnEventTypes(final BlockingState bs) {
+
+            // Turn off isBlockedEntitlement and isBlockedBilling if there was not start event
+            final BlockingState fixedBlockingState = new DefaultBlockingState(bs.getBlockedId(),
+                                                                              bs.getType(),
+                                                                              bs.getStateName(),
+                                                                              bs.getService(),
+                                                                              bs.isBlockChange(),
+                                                                              (bs.isBlockEntitlement() && isEntitlementStarted && !isEntitlementStopped),
+                                                                              (bs.isBlockBilling() && isBillingStarted && !isBillingStopped),
+                                                                              bs.getEffectiveDate());
+
+            final List<SubscriptionEventType> result = new ArrayList<SubscriptionEventType>(4);
+            if (fixedBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CANCELLED)) {
+                isEntitlementStopped = true;
+                result.add(SubscriptionEventType.STOP_ENTITLEMENT);
+                return result;
+            }
 
-        final boolean shouldResumeEntitlement = isEntitlementStarted && !isEntitlementStopped && stateBefore.isBlockEntitlement() && !stateAfter.isBlockEntitlement();
-        if (shouldResumeEntitlement) {
-            result.add(SubscriptionEventType.RESUME_ENTITLEMENT);
-        }
-        final boolean shouldResumeBilling = isBillingStarted && !isBillingStopped && stateBefore.isBlockBilling() && !stateAfter.isBlockBilling();
-        if (shouldResumeBilling) {
-            result.add(SubscriptionEventType.RESUME_BILLING);
-        }
+            //
+            // We look at the effect of the incoming event for the specific service, and then recompute the state after so we can compare if anything has changed
+            // across all services
+            //
+            final BlockingAggregator stateBefore = getState();
+            perServiceBlockingState.put(fixedBlockingState.getService(), fixedBlockingState);
+            final BlockingAggregator stateAfter = getState();
 
-        final boolean shouldBlockEntitlement = isEntitlementStarted && !isEntitlementStopped && !stateBefore.isBlockEntitlement() && stateAfter.isBlockEntitlement();
-        if (shouldBlockEntitlement) {
-            result.add(SubscriptionEventType.PAUSE_ENTITLEMENT);
-        }
-        final boolean shouldBlockBilling = isBillingStarted && !isBillingStopped && !stateBefore.isBlockBilling() && stateAfter.isBlockBilling();
-        if (shouldBlockBilling) {
-            result.add(SubscriptionEventType.PAUSE_BILLING);
-        }
+            final boolean shouldResumeEntitlement = isEntitlementStarted && !isEntitlementStopped && stateBefore.isBlockEntitlement() && !stateAfter.isBlockEntitlement();
+            if (shouldResumeEntitlement) {
+                result.add(SubscriptionEventType.RESUME_ENTITLEMENT);
+            }
+            final boolean shouldResumeBilling = isBillingStarted && !isBillingStopped && stateBefore.isBlockBilling() && !stateAfter.isBlockBilling();
+            if (shouldResumeBilling) {
+                result.add(SubscriptionEventType.RESUME_BILLING);
+            }
+
+            final boolean shouldBlockEntitlement = isEntitlementStarted && !isEntitlementStopped && !stateBefore.isBlockEntitlement() && stateAfter.isBlockEntitlement();
+            if (shouldBlockEntitlement) {
+                result.add(SubscriptionEventType.PAUSE_ENTITLEMENT);
+            }
+            final boolean shouldBlockBilling = isBillingStarted && !isBillingStopped && !stateBefore.isBlockBilling() && stateAfter.isBlockBilling();
+            if (shouldBlockBilling) {
+                result.add(SubscriptionEventType.PAUSE_BILLING);
+            }
 
-        if (!shouldResumeEntitlement && !shouldBlockEntitlement && !shouldBlockEntitlement && !shouldBlockBilling && !fixedBlockingState.getService().equals(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME)) {
-            result.add(SubscriptionEventType.SERVICE_STATE_CHANGE);
+            if (!shouldResumeEntitlement && !shouldBlockEntitlement && !shouldBlockEntitlement && !shouldBlockBilling && !fixedBlockingState.getService().equals(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME)) {
+                result.add(SubscriptionEventType.SERVICE_STATE_CHANGE);
+            }
+            return result;
         }
-        return result;
-    }
 
-    private BlockingAggregator getState() {
-        final DefaultBlockingAggregator aggrBefore = new DefaultBlockingAggregator();
-        for (BlockingState cur : perServiceBlockingState.values()) {
-            aggrBefore.or(cur);
+        private BlockingAggregator getState() {
+            final DefaultBlockingAggregator aggrBefore = new DefaultBlockingAggregator();
+            for (BlockingState cur : perServiceBlockingState.values()) {
+                aggrBefore.or(cur);
+            }
+            return aggrBefore;
+        }
+    }
+
+    protected static final class DefaultSubscriptionEvent implements SubscriptionEvent {
+
+        private final UUID id;
+        private final UUID entitlementId;
+        private final DateTime effectiveDate;
+        private final DateTime requestedDate;
+        private final SubscriptionEventType eventType;
+        private final boolean isBlockingEntitlement;
+        private final boolean isBlockingBilling;
+        private final String serviceName;
+        private final String serviceStateName;
+        private final Product prevProduct;
+        private final Plan prevPlan;
+        private final PlanPhase prevPlanPhase;
+        private final PriceList prevPriceList;
+        private final BillingPeriod prevBillingPeriod;
+        private final Product nextProduct;
+        private final Plan nextPlan;
+        private final PlanPhase nextPlanPhase;
+        private final PriceList nextPriceList;
+        private final BillingPeriod nextBillingPeriod;
+        private final DateTime createdDate;
+        private final DateTimeZone accountTimeZone;
+
+        public DefaultSubscriptionEvent(final UUID id,
+                                        final UUID entitlementId,
+                                        final DateTime effectiveDate,
+                                        final DateTime requestedDate,
+                                        final SubscriptionEventType eventType,
+                                        final boolean blockingEntitlement,
+                                        final boolean blockingBilling,
+                                        final String serviceName,
+                                        final String serviceStateName,
+                                        final Product prevProduct,
+                                        final Plan prevPlan,
+                                        final PlanPhase prevPlanPhase,
+                                        final PriceList prevPriceList,
+                                        final BillingPeriod prevBillingPeriod,
+                                        final Product nextProduct,
+                                        final Plan nextPlan,
+                                        final PlanPhase nextPlanPhase,
+                                        final PriceList nextPriceList,
+                                        final BillingPeriod nextBillingPeriod,
+                                        final DateTime createDate,
+                                        final DateTimeZone accountTimeZone) {
+            this.id = id;
+            this.entitlementId = entitlementId;
+            this.effectiveDate = effectiveDate;
+            this.requestedDate = requestedDate;
+            this.eventType = eventType;
+            this.isBlockingEntitlement = blockingEntitlement;
+            this.isBlockingBilling = blockingBilling;
+            this.serviceName = serviceName;
+            this.serviceStateName = serviceStateName;
+            this.prevProduct = prevProduct;
+            this.prevPlan = prevPlan;
+            this.prevPlanPhase = prevPlanPhase;
+            this.prevPriceList = prevPriceList;
+            this.prevBillingPeriod = prevBillingPeriod;
+            this.nextProduct = nextProduct;
+            this.nextPlan = nextPlan;
+            this.nextPlanPhase = nextPlanPhase;
+            this.nextPriceList = nextPriceList;
+            this.nextBillingPeriod = nextBillingPeriod;
+            this.createdDate = createDate;
+            this.accountTimeZone = accountTimeZone;
+        }
+
+        private DefaultSubscriptionEvent(DefaultSubscriptionEvent copy, SubscriptionEventType newEventType) {
+            this(copy.getId(),
+                 copy.getEntitlementId(),
+                 copy.getEffectiveDateTime(),
+                 copy.getRequestedDateTime(),
+                 newEventType,
+                 copy.isBlockedEntitlement(),
+                 copy.isBlockedBilling(),
+                 copy.getServiceName(),
+                 copy.getServiceStateName(),
+                 copy.getPrevProduct(),
+                 copy.getPrevPlan(),
+                 copy.getPrevPhase(),
+                 copy.getPrevPriceList(),
+                 copy.getPrevBillingPeriod(),
+                 copy.getNextProduct(),
+                 copy.getNextPlan(),
+                 copy.getNextPhase(),
+                 copy.getNextPriceList(),
+                 copy.getNextBillingPeriod(),
+                 copy.getCreatedDate(),
+                 copy.getAccountTimeZone());
         }
-        return aggrBefore;
-    }
-}
 
+        public DateTimeZone getAccountTimeZone() {
+            return accountTimeZone;
+        }
 
-protected static final class DefaultSubscriptionEvent implements SubscriptionEvent {
-
-    private final UUID id;
-    private final UUID entitlementId;
-    private final DateTime effectiveDate;
-    private final DateTime requestedDate;
-    private final SubscriptionEventType eventType;
-    private final boolean isBlockingEntitlement;
-    private final boolean isBlockingBilling;
-    private final String serviceName;
-    private final String serviceStateName;
-    private final Product prevProduct;
-    private final Plan prevPlan;
-    private final PlanPhase prevPlanPhase;
-    private final PriceList prevPriceList;
-    private final BillingPeriod prevBillingPeriod;
-    private final Product nextProduct;
-    private final Plan nextPlan;
-    private final PlanPhase nextPlanPhase;
-    private final PriceList nextPriceList;
-    private final BillingPeriod nextBillingPeriod;
-    private final DateTime createdDate;
-    private final DateTimeZone accountTimeZone;
-
-
-    public DefaultSubscriptionEvent(final UUID id,
-                                     final UUID entitlementId,
-                                     final DateTime effectiveDate,
-                                     final DateTime requestedDate,
-                                     final SubscriptionEventType eventType,
-                                     final boolean blockingEntitlement,
-                                     final boolean blockingBilling,
-                                     final String serviceName,
-                                     final String serviceStateName,
-                                     final Product prevProduct,
-                                     final Plan prevPlan,
-                                     final PlanPhase prevPlanPhase,
-                                     final PriceList prevPriceList,
-                                     final BillingPeriod prevBillingPeriod,
-                                     final Product nextProduct,
-                                     final Plan nextPlan,
-                                     final PlanPhase nextPlanPhase,
-                                     final PriceList nextPriceList,
-                                     final BillingPeriod nextBillingPeriod,
-                                     final DateTime createDate,
-                                     final DateTimeZone accountTimeZone) {
-        this.id = id;
-        this.entitlementId = entitlementId;
-        this.effectiveDate = effectiveDate;
-        this.requestedDate = requestedDate;
-        this.eventType = eventType;
-        this.isBlockingEntitlement = blockingEntitlement;
-        this.isBlockingBilling = blockingBilling;
-        this.serviceName = serviceName;
-        this.serviceStateName = serviceStateName;
-        this.prevProduct = prevProduct;
-        this.prevPlan = prevPlan;
-        this.prevPlanPhase = prevPlanPhase;
-        this.prevPriceList = prevPriceList;
-        this.prevBillingPeriod = prevBillingPeriod;
-        this.nextProduct = nextProduct;
-        this.nextPlan = nextPlan;
-        this.nextPlanPhase = nextPlanPhase;
-        this.nextPriceList = nextPriceList;
-        this.nextBillingPeriod = nextBillingPeriod;
-        this.createdDate = createDate;
-        this.accountTimeZone = accountTimeZone;
-    }
+        public DateTime getEffectiveDateTime() {
+            return effectiveDate;
+        }
 
-    private DefaultSubscriptionEvent(DefaultSubscriptionEvent copy, SubscriptionEventType newEventType) {
-        this(copy.getId(),
-             copy.getEntitlementId(),
-             copy.getEffectiveDateTime(),
-             copy.getRequestedDateTime(),
-             newEventType,
-             copy.isBlockedEntitlement(),
-             copy.isBlockedBilling(),
-             copy.getServiceName(),
-             copy.getServiceStateName(),
-             copy.getPrevProduct(),
-             copy.getPrevPlan(),
-             copy.getPrevPhase(),
-             copy.getPrevPriceList(),
-             copy.getPrevBillingPeriod(),
-             copy.getNextProduct(),
-             copy.getNextPlan(),
-             copy.getNextPhase(),
-             copy.getNextPriceList(),
-             copy.getNextBillingPeriod(),
-             copy.getCreatedDate(),
-             copy.getAccountTimeZone());
-    }
+        public DateTime getRequestedDateTime() {
+            return requestedDate;
+        }
 
-    public DateTimeZone getAccountTimeZone() {
-        return accountTimeZone;
-    }
+        @Override
+        public UUID getId() {
+            return id;
+        }
 
-    public DateTime getEffectiveDateTime() {
-        return effectiveDate;
-    }
+        @Override
+        public UUID getEntitlementId() {
+            return entitlementId;
+        }
 
-    public DateTime getRequestedDateTime() {
-        return requestedDate;
-    }
+        @Override
+        public LocalDate getEffectiveDate() {
+            return effectiveDate != null ? new LocalDate(effectiveDate, accountTimeZone) : null;
+        }
 
-    @Override
-    public UUID getId() {
-        return id;
-    }
+        @Override
+        public LocalDate getRequestedDate() {
+            return requestedDate != null ? new LocalDate(requestedDate, accountTimeZone) : null;
+        }
 
-    @Override
-    public UUID getEntitlementId() {
-        return entitlementId;
-    }
+        @Override
+        public SubscriptionEventType getSubscriptionEventType() {
+            return eventType;
+        }
 
-    @Override
-    public LocalDate getEffectiveDate() {
-        return effectiveDate != null ? new LocalDate(effectiveDate, accountTimeZone) : null;
-    }
+        @Override
+        public boolean isBlockedBilling() {
+            return isBlockingBilling;
+        }
 
-    @Override
-    public LocalDate getRequestedDate() {
-        return requestedDate != null ? new LocalDate(requestedDate, accountTimeZone) : null;
-    }
+        @Override
+        public boolean isBlockedEntitlement() {
+            return isBlockingEntitlement;
+        }
 
-    @Override
-    public SubscriptionEventType getSubscriptionEventType() {
-        return eventType;
-    }
+        @Override
+        public String getServiceName() {
+            return serviceName;
+        }
 
-    @Override
-    public boolean isBlockedBilling() {
-        return isBlockingBilling;
-    }
+        @Override
+        public String getServiceStateName() {
+            return serviceStateName;
+        }
 
-    @Override
-    public boolean isBlockedEntitlement() {
-        return isBlockingEntitlement;
-    }
+        @Override
+        public Product getPrevProduct() {
+            return prevProduct;
+        }
 
-    @Override
-    public String getServiceName() {
-        return serviceName;
-    }
+        @Override
+        public Plan getPrevPlan() {
+            return prevPlan;
+        }
 
-    @Override
-    public String getServiceStateName() {
-        return serviceStateName;
-    }
+        @Override
+        public PlanPhase getPrevPhase() {
+            return prevPlanPhase;
+        }
 
-    @Override
-    public Product getPrevProduct() {
-        return prevProduct;
-    }
+        @Override
+        public PriceList getPrevPriceList() {
+            return prevPriceList;
+        }
 
-    @Override
-    public Plan getPrevPlan() {
-        return prevPlan;
-    }
+        @Override
+        public BillingPeriod getPrevBillingPeriod() {
+            return prevBillingPeriod;
+        }
 
-    @Override
-    public PlanPhase getPrevPhase() {
-        return prevPlanPhase;
-    }
+        @Override
+        public Product getNextProduct() {
+            return nextProduct;
+        }
 
-    @Override
-    public PriceList getPrevPriceList() {
-        return prevPriceList;
-    }
+        @Override
+        public Plan getNextPlan() {
+            return nextPlan;
+        }
 
-    @Override
-    public BillingPeriod getPrevBillingPeriod() {
-        return prevBillingPeriod;
-    }
+        @Override
+        public PlanPhase getNextPhase() {
+            return nextPlanPhase;
+        }
 
-    @Override
-    public Product getNextProduct() {
-        return nextProduct;
-    }
+        @Override
+        public PriceList getNextPriceList() {
+            return nextPriceList;
+        }
 
-    @Override
-    public Plan getNextPlan() {
-        return nextPlan;
-    }
+        @Override
+        public BillingPeriod getNextBillingPeriod() {
+            return nextBillingPeriod;
+        }
 
-    @Override
-    public PlanPhase getNextPhase() {
-        return nextPlanPhase;
-    }
+        public DateTime getCreatedDate() {
+            return createdDate;
+        }
 
-    @Override
-    public PriceList getNextPriceList() {
-        return nextPriceList;
-    }
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
 
-    @Override
-    public BillingPeriod getNextBillingPeriod() {
-        return nextBillingPeriod;
-    }
+            final DefaultSubscriptionEvent that = (DefaultSubscriptionEvent) o;
 
-    public DateTime getCreatedDate() {
-        return createdDate;
-    }
+            if (isBlockingBilling != that.isBlockingBilling) {
+                return false;
+            }
+            if (isBlockingEntitlement != that.isBlockingEntitlement) {
+                return false;
+            }
+            if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
+                return false;
+            }
+            if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
+                return false;
+            }
+            if (entitlementId != null ? !entitlementId.equals(that.entitlementId) : that.entitlementId != null) {
+                return false;
+            }
+            if (eventType != that.eventType) {
+                return false;
+            }
+            if (id != null ? !id.equals(that.id) : that.id != null) {
+                return false;
+            }
+            if (nextBillingPeriod != that.nextBillingPeriod) {
+                return false;
+            }
+            if (nextPlan != null ? !nextPlan.equals(that.nextPlan) : that.nextPlan != null) {
+                return false;
+            }
+            if (nextPlanPhase != null ? !nextPlanPhase.equals(that.nextPlanPhase) : that.nextPlanPhase != null) {
+                return false;
+            }
+            if (nextPriceList != null ? !nextPriceList.equals(that.nextPriceList) : that.nextPriceList != null) {
+                return false;
+            }
+            if (nextProduct != null ? !nextProduct.equals(that.nextProduct) : that.nextProduct != null) {
+                return false;
+            }
+            if (prevBillingPeriod != that.prevBillingPeriod) {
+                return false;
+            }
+            if (prevPlan != null ? !prevPlan.equals(that.prevPlan) : that.prevPlan != null) {
+                return false;
+            }
+            if (prevPlanPhase != null ? !prevPlanPhase.equals(that.prevPlanPhase) : that.prevPlanPhase != null) {
+                return false;
+            }
+            if (prevPriceList != null ? !prevPriceList.equals(that.prevPriceList) : that.prevPriceList != null) {
+                return false;
+            }
+            if (prevProduct != null ? !prevProduct.equals(that.prevProduct) : that.prevProduct != null) {
+                return false;
+            }
+            if (requestedDate != null ? !requestedDate.equals(that.requestedDate) : that.requestedDate != null) {
+                return false;
+            }
+            if (serviceName != null ? !serviceName.equals(that.serviceName) : that.serviceName != null) {
+                return false;
+            }
+            if (serviceStateName != null ? !serviceStateName.equals(that.serviceStateName) : that.serviceStateName != null) {
+                return false;
+            }
 
-    @Override
-    public boolean equals(final Object o) {
-        if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        final DefaultSubscriptionEvent that = (DefaultSubscriptionEvent) o;
 
-        if (isBlockingBilling != that.isBlockingBilling) {
-            return false;
-        }
-        if (isBlockingEntitlement != that.isBlockingEntitlement) {
-            return false;
-        }
-        if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
-            return false;
-        }
-        if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
-            return false;
-        }
-        if (entitlementId != null ? !entitlementId.equals(that.entitlementId) : that.entitlementId != null) {
-            return false;
-        }
-        if (eventType != that.eventType) {
-            return false;
-        }
-        if (id != null ? !id.equals(that.id) : that.id != null) {
-            return false;
-        }
-        if (nextBillingPeriod != that.nextBillingPeriod) {
-            return false;
-        }
-        if (nextPlan != null ? !nextPlan.equals(that.nextPlan) : that.nextPlan != null) {
-            return false;
-        }
-        if (nextPlanPhase != null ? !nextPlanPhase.equals(that.nextPlanPhase) : that.nextPlanPhase != null) {
-            return false;
-        }
-        if (nextPriceList != null ? !nextPriceList.equals(that.nextPriceList) : that.nextPriceList != null) {
-            return false;
-        }
-        if (nextProduct != null ? !nextProduct.equals(that.nextProduct) : that.nextProduct != null) {
-            return false;
-        }
-        if (prevBillingPeriod != that.prevBillingPeriod) {
-            return false;
-        }
-        if (prevPlan != null ? !prevPlan.equals(that.prevPlan) : that.prevPlan != null) {
-            return false;
-        }
-        if (prevPlanPhase != null ? !prevPlanPhase.equals(that.prevPlanPhase) : that.prevPlanPhase != null) {
-            return false;
-        }
-        if (prevPriceList != null ? !prevPriceList.equals(that.prevPriceList) : that.prevPriceList != null) {
-            return false;
-        }
-        if (prevProduct != null ? !prevProduct.equals(that.prevProduct) : that.prevProduct != null) {
-            return false;
-        }
-        if (requestedDate != null ? !requestedDate.equals(that.requestedDate) : that.requestedDate != null) {
-            return false;
-        }
-        if (serviceName != null ? !serviceName.equals(that.serviceName) : that.serviceName != null) {
-            return false;
-        }
-        if (serviceStateName != null ? !serviceStateName.equals(that.serviceStateName) : that.serviceStateName != null) {
-            return false;
+        @Override
+        public int hashCode() {
+            int result = id != null ? id.hashCode() : 0;
+            result = 31 * result + (entitlementId != null ? entitlementId.hashCode() : 0);
+            result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
+            result = 31 * result + (requestedDate != null ? requestedDate.hashCode() : 0);
+            result = 31 * result + (eventType != null ? eventType.hashCode() : 0);
+            result = 31 * result + (isBlockingEntitlement ? 1 : 0);
+            result = 31 * result + (isBlockingBilling ? 1 : 0);
+            result = 31 * result + (serviceName != null ? serviceName.hashCode() : 0);
+            result = 31 * result + (serviceStateName != null ? serviceStateName.hashCode() : 0);
+            result = 31 * result + (prevProduct != null ? prevProduct.hashCode() : 0);
+            result = 31 * result + (prevPlan != null ? prevPlan.hashCode() : 0);
+            result = 31 * result + (prevPlanPhase != null ? prevPlanPhase.hashCode() : 0);
+            result = 31 * result + (prevPriceList != null ? prevPriceList.hashCode() : 0);
+            result = 31 * result + (prevBillingPeriod != null ? prevBillingPeriod.hashCode() : 0);
+            result = 31 * result + (nextProduct != null ? nextProduct.hashCode() : 0);
+            result = 31 * result + (nextPlan != null ? nextPlan.hashCode() : 0);
+            result = 31 * result + (nextPlanPhase != null ? nextPlanPhase.hashCode() : 0);
+            result = 31 * result + (nextPriceList != null ? nextPriceList.hashCode() : 0);
+            result = 31 * result + (nextBillingPeriod != null ? nextBillingPeriod.hashCode() : 0);
+            result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
+            return result;
         }
-
-        return true;
     }
-
-    @Override
-    public int hashCode() {
-        int result = id != null ? id.hashCode() : 0;
-        result = 31 * result + (entitlementId != null ? entitlementId.hashCode() : 0);
-        result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
-        result = 31 * result + (requestedDate != null ? requestedDate.hashCode() : 0);
-        result = 31 * result + (eventType != null ? eventType.hashCode() : 0);
-        result = 31 * result + (isBlockingEntitlement ? 1 : 0);
-        result = 31 * result + (isBlockingBilling ? 1 : 0);
-        result = 31 * result + (serviceName != null ? serviceName.hashCode() : 0);
-        result = 31 * result + (serviceStateName != null ? serviceStateName.hashCode() : 0);
-        result = 31 * result + (prevProduct != null ? prevProduct.hashCode() : 0);
-        result = 31 * result + (prevPlan != null ? prevPlan.hashCode() : 0);
-        result = 31 * result + (prevPlanPhase != null ? prevPlanPhase.hashCode() : 0);
-        result = 31 * result + (prevPriceList != null ? prevPriceList.hashCode() : 0);
-        result = 31 * result + (prevBillingPeriod != null ? prevBillingPeriod.hashCode() : 0);
-        result = 31 * result + (nextProduct != null ? nextProduct.hashCode() : 0);
-        result = 31 * result + (nextPlan != null ? nextPlan.hashCode() : 0);
-        result = 31 * result + (nextPlanPhase != null ? nextPlanPhase.hashCode() : 0);
-        result = 31 * result + (nextPriceList != null ? nextPriceList.hashCode() : 0);
-        result = 31 * result + (nextBillingPeriod != null ? nextBillingPeriod.hashCode() : 0);
-        result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
-        return result;
-    }
-}
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEntitlements.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEntitlements.java
new file mode 100644
index 0000000..72898be
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEntitlements.java
@@ -0,0 +1,90 @@
+/*
+ * 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.svcs;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.AccountEntitlements;
+import com.ning.billing.entitlement.AccountEventsStreams;
+import com.ning.billing.entitlement.api.Entitlement;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+
+public class DefaultAccountEntitlements implements AccountEntitlements {
+
+    private final AccountEventsStreams accountEventsStreams;
+    private final Map<UUID, Collection<Entitlement>> entitlements;
+
+    public DefaultAccountEntitlements(final AccountEventsStreams accountEventsStreams, final Map<UUID, Collection<Entitlement>> entitlements) {
+        this.accountEventsStreams = accountEventsStreams;
+        this.entitlements = entitlements;
+    }
+
+    @Override
+    public Account getAccount() {
+        return accountEventsStreams.getAccount();
+    }
+
+    @Override
+    public Map<UUID, SubscriptionBaseBundle> getBundles() {
+        return accountEventsStreams.getBundles();
+    }
+
+    @Override
+    public Map<UUID, Collection<Entitlement>> getEntitlements() {
+        return entitlements;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("DefaultAccountEntitlements{");
+        sb.append("accountEventsStreams=").append(accountEventsStreams);
+        sb.append(", entitlements=").append(entitlements);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultAccountEntitlements that = (DefaultAccountEntitlements) o;
+
+        if (accountEventsStreams != null ? !accountEventsStreams.equals(that.accountEventsStreams) : that.accountEventsStreams != null) {
+            return false;
+        }
+        if (entitlements != null ? !entitlements.equals(that.entitlements) : that.entitlements != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountEventsStreams != null ? accountEventsStreams.hashCode() : 0;
+        result = 31 * result + (entitlements != null ? entitlements.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEventsStreams.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEventsStreams.java
new file mode 100644
index 0000000..d05df80
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEventsStreams.java
@@ -0,0 +1,108 @@
+/*
+ * 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.svcs;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.AccountEventsStreams;
+import com.ning.billing.entitlement.EventsStream;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class DefaultAccountEventsStreams implements AccountEventsStreams {
+
+    private final Account account;
+    private final Map<UUID, Collection<EventsStream>> eventsStreams;
+    private final Map<UUID, SubscriptionBaseBundle> bundles = new HashMap<UUID, SubscriptionBaseBundle>();
+
+    public DefaultAccountEventsStreams(final Account account,
+                                       final Iterable<SubscriptionBaseBundle> bundles,
+                                       final Map<UUID, Collection<EventsStream>> eventsStreams) {
+        this.account = account;
+        this.eventsStreams = eventsStreams;
+        for (final SubscriptionBaseBundle baseBundle : bundles) {
+            this.bundles.put(baseBundle.getId(), baseBundle);
+        }
+    }
+
+    public DefaultAccountEventsStreams(final Account account) {
+        this(account, ImmutableList.<SubscriptionBaseBundle>of(), ImmutableMap.<UUID, Collection<EventsStream>>of());
+    }
+
+    @Override
+    public Account getAccount() {
+        return account;
+    }
+
+    @Override
+    public Map<UUID, SubscriptionBaseBundle> getBundles() {
+        return bundles;
+    }
+
+    @Override
+    public Map<UUID, Collection<EventsStream>> getEventsStreams() {
+        return eventsStreams;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("DefaultAccountEventsStreams{");
+        sb.append("account=").append(account);
+        sb.append(", eventsStreams=").append(eventsStreams);
+        sb.append(", bundles=").append(bundles);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultAccountEventsStreams that = (DefaultAccountEventsStreams) o;
+
+        if (account != null ? !account.equals(that.account) : that.account != null) {
+            return false;
+        }
+        if (bundles != null ? !bundles.equals(that.bundles) : that.bundles != null) {
+            return false;
+        }
+        if (eventsStreams != null ? !eventsStreams.equals(that.eventsStreams) : that.eventsStreams != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = account != null ? account.hashCode() : 0;
+        result = 31 * result + (eventsStreams != null ? eventsStreams.hashCode() : 0);
+        result = 31 * result + (bundles != null ? bundles.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
new file mode 100644
index 0000000..f941e9b
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
@@ -0,0 +1,101 @@
+/*
+ * 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.svcs;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import com.ning.billing.account.api.AccountInternalApi;
+import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.AccountEntitlements;
+import com.ning.billing.entitlement.AccountEventsStreams;
+import com.ning.billing.entitlement.EntitlementInternalApi;
+import com.ning.billing.entitlement.EventsStream;
+import com.ning.billing.entitlement.api.DefaultEntitlement;
+import com.ning.billing.entitlement.api.Entitlement;
+import com.ning.billing.entitlement.api.EntitlementApi;
+import com.ning.billing.entitlement.api.EntitlementApiException;
+import com.ning.billing.entitlement.api.EntitlementDateHelper;
+import com.ning.billing.entitlement.block.BlockingChecker;
+import com.ning.billing.entitlement.dao.BlockingStateDao;
+import com.ning.billing.entitlement.engine.core.EntitlementUtils;
+import com.ning.billing.entitlement.engine.core.EventsStreamBuilder;
+import com.ning.billing.notificationq.api.NotificationQueueService;
+import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
+import com.ning.billing.util.callcontext.InternalCallContextFactory;
+import com.ning.billing.util.callcontext.TenantContext;
+
+public class DefaultEntitlementInternalApi implements EntitlementInternalApi {
+
+    private final EntitlementApi entitlementApi;
+    private final SubscriptionBaseInternalApi subscriptionInternalApi;
+    private final Clock clock;
+    private final InternalCallContextFactory internalCallContextFactory;
+    private final BlockingChecker checker;
+    private final BlockingStateDao blockingStateDao;
+    private final EntitlementDateHelper dateHelper;
+    private final EventsStreamBuilder eventsStreamBuilder;
+    private final EntitlementUtils entitlementUtils;
+    private final NotificationQueueService notificationQueueService;
+
+    @Inject
+    public DefaultEntitlementInternalApi(final EntitlementApi entitlementApi, final InternalCallContextFactory internalCallContextFactory,
+                                         final SubscriptionBaseInternalApi subscriptionInternalApi,
+                                         final AccountInternalApi accountApi, final BlockingStateDao blockingStateDao, final Clock clock,
+                                         final BlockingChecker checker, final NotificationQueueService notificationQueueService,
+                                         final EventsStreamBuilder eventsStreamBuilder, final EntitlementUtils entitlementUtils) {
+        this.entitlementApi = entitlementApi;
+        this.internalCallContextFactory = internalCallContextFactory;
+        this.subscriptionInternalApi = subscriptionInternalApi;
+        this.clock = clock;
+        this.checker = checker;
+        this.blockingStateDao = blockingStateDao;
+        this.notificationQueueService = notificationQueueService;
+        this.eventsStreamBuilder = eventsStreamBuilder;
+        this.entitlementUtils = entitlementUtils;
+        this.dateHelper = new EntitlementDateHelper(accountApi, clock);
+    }
+
+    @Override
+    public AccountEntitlements getAllEntitlementsForAccountId(final UUID accountId, final TenantContext tenantContext) throws EntitlementApiException {
+        final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(accountId, tenantContext);
+
+        final AccountEventsStreams accountEventsStreams = eventsStreamBuilder.buildForAccount(context);
+
+        final Map<UUID, Collection<Entitlement>> entitlementsPerBundle = new HashMap<UUID, Collection<Entitlement>>();
+        for (final UUID bundleId : accountEventsStreams.getEventsStreams().keySet()) {
+            if (entitlementsPerBundle.get(bundleId) == null) {
+                entitlementsPerBundle.put(bundleId, new LinkedList<Entitlement>());
+            }
+
+            for (final EventsStream eventsStream : accountEventsStreams.getEventsStreams().get(bundleId)) {
+                final Entitlement entitlement = new DefaultEntitlement(eventsStream, eventsStreamBuilder, entitlementApi,
+                                                                       blockingStateDao, subscriptionInternalApi, checker, notificationQueueService,
+                                                                       entitlementUtils, dateHelper, clock, internalCallContextFactory);
+                entitlementsPerBundle.get(bundleId).add(entitlement);
+            }
+        }
+
+        return new DefaultAccountEntitlements(accountEventsStreams, entitlementsPerBundle);
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java
new file mode 100644
index 0000000..604b488
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java
@@ -0,0 +1,75 @@
+/*
+ * 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.dao;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.skife.jdbi.v2.IDBI;
+
+import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.EventsStream;
+import com.ning.billing.entitlement.api.BlockingState;
+import com.ning.billing.entitlement.api.BlockingStateType;
+import com.ning.billing.entitlement.api.EntitlementApiException;
+import com.ning.billing.entitlement.engine.core.EventsStreamBuilder;
+import com.ning.billing.subscription.api.SubscriptionBase;
+import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+import com.ning.billing.util.cache.CacheControllerDispatcher;
+import com.ning.billing.util.dao.NonEntityDao;
+
+import com.google.common.collect.ImmutableList;
+
+public class OptimizedProxyBlockingStateDao extends ProxyBlockingStateDao {
+
+    public OptimizedProxyBlockingStateDao(final EventsStreamBuilder eventsStreamBuilder, final SubscriptionBaseInternalApi subscriptionBaseInternalApi,
+                                          final IDBI dbi, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher,
+                                          final NonEntityDao nonEntityDao) {
+        super(eventsStreamBuilder, subscriptionBaseInternalApi, dbi, clock, cacheControllerDispatcher, nonEntityDao);
+    }
+
+    // Special signature for EventsStreamBuilder to save some DAO calls
+    public List<BlockingState> getBlockingHistoryForService(final List<BlockingState> blockingStatesOnDisk,
+                                                            final SubscriptionBaseBundle bundle,
+                                                            @Nullable final SubscriptionBase baseSubscription,
+                                                            final SubscriptionBase subscription,
+                                                            final List<SubscriptionBase> allSubscriptionsForBundle,
+                                                            final InternalTenantContext context) throws EntitlementApiException {
+        // blockable id points to a subscription, but make sure it's an add-on
+        if (!ProductCategory.ADD_ON.equals(subscription.getCategory())) {
+            // blockable id points to a base or standalone subscription, there is nothing to do
+            return blockingStatesOnDisk;
+        }
+
+        // Find all base entitlements that we care about (for which we want to find future cancelled add-ons)
+        final Iterable<EventsStream> eventsStreams = ImmutableList.<EventsStream>of(eventsStreamBuilder.buildForEntitlement(bundle,
+                                                                                                                            baseSubscription,
+                                                                                                                            allSubscriptionsForBundle,
+                                                                                                                            context));
+
+        return addBlockingStatesNotOnDisk(subscription.getId(),
+                                          BlockingStateType.SUBSCRIPTION,
+                                          new LinkedList<BlockingState>(blockingStatesOnDisk),
+                                          ImmutableList.<SubscriptionBase>of(baseSubscription),
+                                          eventsStreams);
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java
index 13f3cd5..850dd0f 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java
@@ -20,6 +20,7 @@ import java.util.Collection;
 import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -36,12 +37,12 @@ import com.ning.billing.callcontext.InternalTenantContext;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.clock.Clock;
 import com.ning.billing.entitlement.EntitlementService;
+import com.ning.billing.entitlement.EventsStream;
 import com.ning.billing.entitlement.api.BlockingState;
 import com.ning.billing.entitlement.api.BlockingStateType;
 import com.ning.billing.entitlement.api.DefaultEntitlementApi;
 import com.ning.billing.entitlement.api.Entitlement.EntitlementState;
 import com.ning.billing.entitlement.api.EntitlementApiException;
-import com.ning.billing.entitlement.engine.core.EventsStream;
 import com.ning.billing.entitlement.engine.core.EventsStreamBuilder;
 import com.ning.billing.subscription.api.SubscriptionBase;
 import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
@@ -79,10 +80,11 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
         }
     });
 
-    private final EventsStreamBuilder eventsStreamBuilder;
     private final SubscriptionBaseInternalApi subscriptionInternalApi;
     private final Clock clock;
-    private final DefaultBlockingStateDao delegate;
+
+    protected final EventsStreamBuilder eventsStreamBuilder;
+    protected final DefaultBlockingStateDao delegate;
 
     @Inject
     public ProxyBlockingStateDao(final EventsStreamBuilder eventsStreamBuilder, final SubscriptionBaseInternalApi subscriptionBaseInternalApi,
@@ -182,11 +184,12 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
 
         // Find all base entitlements that we care about (for which we want to find future cancelled add-ons)
         final Iterable<SubscriptionBase> baseSubscriptionsToConsider;
+        final Iterable<EventsStream> eventsStreams;
         try {
             if (blockingStateType == null) {
                 // We're coming from getBlockingAllForAccountRecordId
-                final Iterable<SubscriptionBase> subscriptions = Iterables.<SubscriptionBase>concat(subscriptionInternalApi.getSubscriptionsForAccount(context).values());
-                baseSubscriptionsToConsider = Iterables.<SubscriptionBase>filter(subscriptions,
+                final Map<UUID, List<SubscriptionBase>> subscriptions = subscriptionInternalApi.getSubscriptionsForAccount(context);
+                baseSubscriptionsToConsider = Iterables.<SubscriptionBase>filter(Iterables.<SubscriptionBase>concat(subscriptions.values()),
                                                                                  new Predicate<SubscriptionBase>() {
                                                                                      @Override
                                                                                      public boolean apply(final SubscriptionBase input) {
@@ -194,6 +197,7 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
                                                                                                 !EntitlementState.CANCELLED.equals(input.getState());
                                                                                      }
                                                                                  });
+                eventsStreams = Iterables.<EventsStream>concat(eventsStreamBuilder.buildForAccount(subscriptions, context).getEventsStreams().values());
             } else if (BlockingStateType.SUBSCRIPTION.equals(blockingStateType)) {
                 // We're coming from getBlockingHistoryForService / getBlockingAll
                 final SubscriptionBase subscription = subscriptionInternalApi.getSubscriptionFromId(blockableId, context);
@@ -202,6 +206,7 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
                 if (ProductCategory.ADD_ON.equals(subscription.getCategory())) {
                     final SubscriptionBase baseSubscription = subscriptionInternalApi.getBaseSubscription(subscription.getBundleId(), context);
                     baseSubscriptionsToConsider = ImmutableList.<SubscriptionBase>of(baseSubscription);
+                    eventsStreams = ImmutableList.<EventsStream>of(eventsStreamBuilder.buildForEntitlement(baseSubscription, context));
                 } else {
                     // blockable id points to a base or standalone subscription, there is nothing to do
                     return blockingStatesOnDisk;
@@ -213,25 +218,22 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
         } catch (SubscriptionBaseApiException e) {
             log.error("Error retrieving subscriptions for account record id " + context.getAccountRecordId(), e);
             throw new RuntimeException(e);
-        }
-
-        // Retrieve the cancellation blocking state on disk, if it exists (will be used later)
-        final BlockingState cancellationBlockingStateOnDisk = findEntitlementCancellationBlockingState(blockableId, blockingStatesOnDiskCopy);
-
-        final List<EventsStream> eventsStreams;
-        try {
-            if (blockingStateType == null) {
-                // We're coming from getBlockingAllForAccountRecordId
-                eventsStreams = eventsStreamBuilder.buildForAccount(context);
-            } else {
-                // We're coming from getBlockingHistoryForService / getBlockingAll
-                eventsStreams = ImmutableList.<EventsStream>of(eventsStreamBuilder.buildForEntitlement(baseSubscriptionsToConsider.iterator().next().getId(), context));
-            }
         } catch (EntitlementApiException e) {
             log.error("Error computing blocking states for addons for account record id " + context.getAccountRecordId(), e);
             throw new RuntimeException(e);
         }
 
+        return addBlockingStatesNotOnDisk(blockableId, blockingStateType, blockingStatesOnDiskCopy, baseSubscriptionsToConsider, eventsStreams);
+    }
+
+    protected List<BlockingState> addBlockingStatesNotOnDisk(@Nullable final UUID blockableId,
+                                                             @Nullable final BlockingStateType blockingStateType,
+                                                             final Collection<BlockingState> blockingStatesOnDiskCopy,
+                                                             final Iterable<SubscriptionBase> baseSubscriptionsToConsider,
+                                                             final Iterable<EventsStream> eventsStreams) {
+        // Retrieve the cancellation blocking state on disk, if it exists (will be used later)
+        final BlockingState cancellationBlockingStateOnDisk = findEntitlementCancellationBlockingState(blockableId, blockingStatesOnDiskCopy);
+
         // Compute the blocking states not on disk for all base subscriptions
         final DateTime now = clock.getUTCNow();
         for (final SubscriptionBase baseSubscription : baseSubscriptionsToConsider) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStreamBuilder.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStreamBuilder.java
index d5f713e..f9f9cfb 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStreamBuilder.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStreamBuilder.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.entitlement.engine.core;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -26,7 +27,6 @@ import javax.annotation.Nullable;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
-import org.joda.time.DateTime;
 import org.skife.jdbi.v2.IDBI;
 
 import com.ning.billing.ObjectType;
@@ -36,14 +36,16 @@ import com.ning.billing.account.api.AccountInternalApi;
 import com.ning.billing.callcontext.InternalTenantContext;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.AccountEventsStreams;
 import com.ning.billing.entitlement.EntitlementService;
+import com.ning.billing.entitlement.EventsStream;
 import com.ning.billing.entitlement.api.BlockingState;
 import com.ning.billing.entitlement.api.BlockingStateType;
 import com.ning.billing.entitlement.api.EntitlementApiException;
+import com.ning.billing.entitlement.api.svcs.DefaultAccountEventsStreams;
 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.entitlement.dao.DefaultBlockingStateDao;
+import com.ning.billing.entitlement.dao.OptimizedProxyBlockingStateDao;
 import com.ning.billing.subscription.api.SubscriptionBase;
 import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
 import com.ning.billing.subscription.api.user.SubscriptionBaseApiException;
@@ -53,6 +55,7 @@ import com.ning.billing.util.callcontext.InternalCallContextFactory;
 import com.ning.billing.util.callcontext.TenantContext;
 import com.ning.billing.util.dao.NonEntityDao;
 
+import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -65,31 +68,29 @@ public class EventsStreamBuilder {
     private final AccountInternalApi accountInternalApi;
     private final SubscriptionBaseInternalApi subscriptionInternalApi;
     private final BlockingChecker checker;
-    private final BlockingStateDao blockingStateDao;
+    private final OptimizedProxyBlockingStateDao blockingStateDao;
     private final DefaultBlockingStateDao defaultBlockingStateDao;
     private final Clock clock;
-    private final NonEntityDao nonEntityDao;
     private final InternalCallContextFactory internalCallContextFactory;
 
     @Inject
     public EventsStreamBuilder(final AccountInternalApi accountInternalApi, final SubscriptionBaseInternalApi subscriptionInternalApi,
                                final BlockingChecker checker, final IDBI dbi, final Clock clock,
                                final CacheControllerDispatcher cacheControllerDispatcher,
-                               final BlockingStateDao blockingStateDao, final NonEntityDao nonEntityDao,
+                               final NonEntityDao nonEntityDao,
                                final InternalCallContextFactory internalCallContextFactory) {
         this.accountInternalApi = accountInternalApi;
         this.subscriptionInternalApi = subscriptionInternalApi;
         this.checker = checker;
         this.clock = clock;
-        this.blockingStateDao = blockingStateDao;
-        this.nonEntityDao = nonEntityDao;
         this.internalCallContextFactory = internalCallContextFactory;
 
         this.defaultBlockingStateDao = new DefaultBlockingStateDao(dbi, clock, cacheControllerDispatcher, nonEntityDao);
+        this.blockingStateDao = new OptimizedProxyBlockingStateDao(this, subscriptionInternalApi, dbi, clock, cacheControllerDispatcher, nonEntityDao);
     }
 
     public EventsStream refresh(final EventsStream eventsStream, final TenantContext tenantContext) throws EntitlementApiException {
-        return buildForEntitlement(eventsStream.getSubscription().getId(), tenantContext);
+        return buildForEntitlement(eventsStream.getEntitlementId(), tenantContext);
     }
 
     public EventsStream buildForBaseSubscription(final UUID bundleId, final TenantContext tenantContext) throws EntitlementApiException {
@@ -109,25 +110,33 @@ public class EventsStreamBuilder {
         return buildForEntitlement(entitlementId, internalTenantContext);
     }
 
-    public List<EventsStream> buildForAccount(final InternalTenantContext internalTenantContext) throws EntitlementApiException {
+    public AccountEventsStreams buildForAccount(final InternalTenantContext internalTenantContext) throws EntitlementApiException {
         // Retrieve the subscriptions (map bundle id -> subscriptions)
         final Map<UUID, List<SubscriptionBase>> subscriptions = subscriptionInternalApi.getSubscriptionsForAccount(internalTenantContext);
-        if (subscriptions.isEmpty()) {
-            // Bail early
-            return ImmutableList.<EventsStream>of();
-        }
+        return buildForAccount(subscriptions, internalTenantContext);
+    }
 
+    // Special signature for ProxyBlockingStateDao to save a DAO call
+    public AccountEventsStreams buildForAccount(final Map<UUID, List<SubscriptionBase>> subscriptions, final InternalTenantContext internalTenantContext) throws EntitlementApiException {
         // Retrieve the account
-        final UUID accountId = nonEntityDao.retrieveIdFromObject(internalTenantContext.getAccountRecordId(), ObjectType.ACCOUNT);
         final Account account;
         try {
-            account = accountInternalApi.getAccountById(accountId, internalTenantContext);
+            account = accountInternalApi.getAccountByRecordId(internalTenantContext.getAccountRecordId(), internalTenantContext);
         } catch (AccountApiException e) {
             throw new EntitlementApiException(e);
         }
 
+        return buildForAccount(account, subscriptions, internalTenantContext);
+    }
+
+    private AccountEventsStreams buildForAccount(final Account account, final Map<UUID, List<SubscriptionBase>> subscriptions, final InternalTenantContext internalTenantContext) throws EntitlementApiException {
+        if (subscriptions.isEmpty()) {
+            // Bail early
+            return new DefaultAccountEventsStreams(account);
+        }
+
         // Retrieve the bundles
-        final List<SubscriptionBaseBundle> bundles = subscriptionInternalApi.getBundlesForAccount(accountId, internalTenantContext);
+        final List<SubscriptionBaseBundle> bundles = subscriptionInternalApi.getBundlesForAccount(account.getId(), internalTenantContext);
         // Map bundle id -> bundles
         final Map<UUID, SubscriptionBaseBundle> bundlesPerId = new HashMap<UUID, SubscriptionBaseBundle>();
         for (final SubscriptionBaseBundle bundle : bundles) {
@@ -136,19 +145,32 @@ public class EventsStreamBuilder {
 
         // Retrieve the blocking states
         final List<BlockingState> blockingStatesForAccount = BLOCKING_STATE_ORDERING.immutableSortedCopy(defaultBlockingStateDao.getBlockingAllForAccountRecordId(internalTenantContext));
-        // Copy fully the list (avoid lazy loading)
-        final List<BlockingState> accountEntitlementStates = ImmutableList.<BlockingState>copyOf(Iterables.<BlockingState>filter(blockingStatesForAccount,
-                                                                                                                                 new Predicate<BlockingState>() {
-                                                                                                                                     @Override
-                                                                                                                                     public boolean apply(final BlockingState input) {
-                                                                                                                                         return BlockingStateType.ACCOUNT.equals(input.getType()) &&
-                                                                                                                                                EntitlementService.ENTITLEMENT_SERVICE_NAME.equals(input.getService()) &&
-                                                                                                                                                accountId.equals(input.getBlockedId());
-                                                                                                                                     }
-                                                                                                                                 }));
+
+        // Optimization: build lookup tables for entitlement states
+        final List<BlockingState> accountEntitlementStates = new LinkedList<BlockingState>();
+        final Map<UUID, List<BlockingState>> entitlementStatesPerSubscription = new HashMap<UUID, List<BlockingState>>();
+        final Map<UUID, List<BlockingState>> entitlementStatesPerBundle = new HashMap<UUID, List<BlockingState>>();
+        for (final BlockingState blockingState : blockingStatesForAccount) {
+            if (!EntitlementService.ENTITLEMENT_SERVICE_NAME.equals(blockingState.getService())) {
+                continue;
+            } else if (BlockingStateType.SUBSCRIPTION.equals(blockingState.getType())) {
+                if (entitlementStatesPerSubscription.get(blockingState.getBlockedId()) == null) {
+                    entitlementStatesPerSubscription.put(blockingState.getBlockedId(), new LinkedList<BlockingState>());
+                }
+                entitlementStatesPerSubscription.get(blockingState.getBlockedId()).add(blockingState);
+            } else if (BlockingStateType.SUBSCRIPTION_BUNDLE.equals(blockingState.getType())) {
+                if (entitlementStatesPerBundle.get(blockingState.getBlockedId()) == null) {
+                    entitlementStatesPerBundle.put(blockingState.getBlockedId(), new LinkedList<BlockingState>());
+                }
+                entitlementStatesPerBundle.get(blockingState.getBlockedId()).add(blockingState);
+            } else if (BlockingStateType.ACCOUNT.equals(blockingState.getType()) &&
+                       account.getId().equals(blockingState.getBlockedId())) {
+                accountEntitlementStates.add(blockingState);
+            }
+        }
 
         // Build the EventsStream objects
-        final List<EventsStream> results = new LinkedList<EventsStream>();
+        final Map<UUID, Collection<EventsStream>> entitlementsPerBundle = new HashMap<UUID, Collection<EventsStream>>();
         for (final UUID bundleId : subscriptions.keySet()) {
             final SubscriptionBaseBundle bundle = bundlesPerId.get(bundleId);
             final List<SubscriptionBase> allSubscriptionsForBundle = subscriptions.get(bundleId);
@@ -159,40 +181,34 @@ public class EventsStreamBuilder {
                                                                                                   return ProductCategory.BASE.equals(input.getLastActiveProduct().getCategory());
                                                                                               }
                                                                                           }).orNull();
-            // Copy fully the list (avoid lazy loading)
-            final List<BlockingState> bundleEntitlementStates = ImmutableList.<BlockingState>copyOf(Iterables.<BlockingState>filter(blockingStatesForAccount,
-                                                                                                                                    new Predicate<BlockingState>() {
-                                                                                                                                        @Override
-                                                                                                                                        public boolean apply(final BlockingState input) {
-                                                                                                                                            return BlockingStateType.SUBSCRIPTION_BUNDLE.equals(input.getType()) &&
-                                                                                                                                                   EntitlementService.ENTITLEMENT_SERVICE_NAME.equals(input.getService()) &&
-                                                                                                                                                   bundle.getId().equals(input.getBlockedId());
-                                                                                                                                        }
-                                                                                                                                    }));
+            final List<BlockingState> bundleEntitlementStates = Objects.firstNonNull(entitlementStatesPerBundle.get(bundleId), ImmutableList.<BlockingState>of());
+
+            if (entitlementsPerBundle.get(bundleId) == null) {
+                entitlementsPerBundle.put(bundleId, new LinkedList<EventsStream>());
+            }
 
             for (final SubscriptionBase subscription : allSubscriptionsForBundle) {
-                // Copy fully the list (avoid lazy loading)
+                final List<BlockingState> subscriptionBlockingStatesOnDisk = Objects.firstNonNull(entitlementStatesPerSubscription.get(subscription.getId()), ImmutableList.<BlockingState>of());
+
                 // We cannot use blockingStatesForAccount here: we need subscriptionEntitlementStates to contain the events not on disk when building an EventsStream
                 // for an add-on - which means going through the magic of ProxyBlockingStateDao, which will recursively
                 // create EventsStream objects. To avoid an infinite recursion, bypass ProxyBlockingStateDao when it's not
                 // needed, i.e. if this EventStream is for a standalone or a base subscription
                 final List<BlockingState> subscriptionEntitlementStates = (baseSubscription == null || subscription.getId().equals(baseSubscription.getId())) ?
-                                                                          ImmutableList.<BlockingState>copyOf(Iterables.<BlockingState>filter(blockingStatesForAccount,
-                                                                                                                                              new Predicate<BlockingState>() {
-                                                                                                                                                  @Override
-                                                                                                                                                  public boolean apply(final BlockingState input) {
-                                                                                                                                                      return BlockingStateType.SUBSCRIPTION.equals(input.getType()) &&
-                                                                                                                                                             EntitlementService.ENTITLEMENT_SERVICE_NAME.equals(input.getService()) &&
-                                                                                                                                                             subscription.getId().equals(input.getBlockedId());
-                                                                                                                                                  }
-                                                                                                                                              })) :
-                                                                          BLOCKING_STATE_ORDERING.immutableSortedCopy(blockingStateDao.getBlockingHistoryForService(subscription.getId(), BlockingStateType.SUBSCRIPTION, EntitlementService.ENTITLEMENT_SERVICE_NAME, internalTenantContext));
-
-                results.add(buildForEntitlement(account, bundle, baseSubscription, subscription, allSubscriptionsForBundle, subscriptionEntitlementStates, bundleEntitlementStates, accountEntitlementStates, internalTenantContext));
+                                                                          subscriptionBlockingStatesOnDisk :
+                                                                          blockingStateDao.getBlockingHistoryForService(subscriptionBlockingStatesOnDisk,
+                                                                                                                        bundle,
+                                                                                                                        baseSubscription,
+                                                                                                                        subscription,
+                                                                                                                        allSubscriptionsForBundle,
+                                                                                                                        internalTenantContext);
+
+                final EventsStream eventStream = buildForEntitlement(account, bundle, baseSubscription, subscription, allSubscriptionsForBundle, subscriptionEntitlementStates, bundleEntitlementStates, accountEntitlementStates, internalTenantContext);
+                entitlementsPerBundle.get(bundleId).add(eventStream);
             }
         }
 
-        return results;
+        return new DefaultAccountEventsStreams(account, bundles, entitlementsPerBundle);
     }
 
     public EventsStream buildForEntitlement(final UUID entitlementId, final InternalTenantContext internalTenantContext) throws EntitlementApiException {
@@ -218,6 +234,27 @@ public class EventsStreamBuilder {
         return buildForEntitlement(bundle, baseSubscription, subscription, allSubscriptionsForBundle, internalTenantContext);
     }
 
+    // Special signature for ProxyBlockingStateDao to save some DAO calls
+    public EventsStream buildForEntitlement(final SubscriptionBase subscription, final InternalTenantContext internalTenantContext) throws EntitlementApiException {
+        final SubscriptionBaseBundle bundle;
+        try {
+            bundle = subscriptionInternalApi.getBundleFromId(subscription.getBundleId(), internalTenantContext);
+        } catch (SubscriptionBaseApiException e) {
+            throw new EntitlementApiException(e);
+        }
+
+        final List<SubscriptionBase> allSubscriptionsForBundle = subscriptionInternalApi.getSubscriptionsForBundle(subscription.getBundleId(), internalTenantContext);
+        return buildForEntitlement(bundle, subscription, subscription, allSubscriptionsForBundle, internalTenantContext);
+    }
+
+    // Special signature for OptimizedProxyBlockingStateDao to save some DAO calls
+    public EventsStream buildForEntitlement(final SubscriptionBaseBundle bundle,
+                                            final SubscriptionBase subscription,
+                                            final List<SubscriptionBase> allSubscriptionsForBundle,
+                                            final InternalTenantContext internalTenantContext) throws EntitlementApiException {
+        return buildForEntitlement(bundle, subscription, subscription, allSubscriptionsForBundle, internalTenantContext);
+    }
+
     private EventsStream buildForEntitlement(final SubscriptionBaseBundle bundle,
                                              @Nullable final SubscriptionBase baseSubscription,
                                              final SubscriptionBase subscription,
@@ -232,14 +269,20 @@ public class EventsStreamBuilder {
 
         final List<BlockingState> bundleEntitlementStates = BLOCKING_STATE_ORDERING.immutableSortedCopy(defaultBlockingStateDao.getBlockingHistoryForService(bundle.getId(), BlockingStateType.SUBSCRIPTION_BUNDLE, EntitlementService.ENTITLEMENT_SERVICE_NAME, internalTenantContext));
         final List<BlockingState> accountEntitlementStates = BLOCKING_STATE_ORDERING.immutableSortedCopy(defaultBlockingStateDao.getBlockingHistoryForService(account.getId(), BlockingStateType.ACCOUNT, EntitlementService.ENTITLEMENT_SERVICE_NAME, internalTenantContext));
+        final ImmutableList<BlockingState> subscriptionEntitlementStatesOnDisk = BLOCKING_STATE_ORDERING.immutableSortedCopy(defaultBlockingStateDao.getBlockingHistoryForService(subscription.getId(), BlockingStateType.SUBSCRIPTION, EntitlementService.ENTITLEMENT_SERVICE_NAME, internalTenantContext));
 
         // We need subscriptionEntitlementStates to contain the events not on disk when building an EventsStream
         // for an add-on - which means going through the magic of ProxyBlockingStateDao, which will recursively
         // create EventsStream objects. To avoid an infinite recursion, bypass ProxyBlockingStateDao when it's not
         // needed, i.e. if this EventStream is for a standalone or a base subscription
         final List<BlockingState> subscriptionEntitlementStates = (baseSubscription == null || subscription.getId().equals(baseSubscription.getId())) ?
-                                                                  BLOCKING_STATE_ORDERING.immutableSortedCopy(defaultBlockingStateDao.getBlockingHistoryForService(subscription.getId(), BlockingStateType.SUBSCRIPTION, EntitlementService.ENTITLEMENT_SERVICE_NAME, internalTenantContext)) :
-                                                                  BLOCKING_STATE_ORDERING.immutableSortedCopy(blockingStateDao.getBlockingHistoryForService(subscription.getId(), BlockingStateType.SUBSCRIPTION, EntitlementService.ENTITLEMENT_SERVICE_NAME, internalTenantContext));
+                                                                  subscriptionEntitlementStatesOnDisk :
+                                                                  blockingStateDao.getBlockingHistoryForService(subscriptionEntitlementStatesOnDisk,
+                                                                                                                bundle,
+                                                                                                                baseSubscription,
+                                                                                                                subscription,
+                                                                                                                allSubscriptionsForBundle,
+                                                                                                                internalTenantContext);
 
         return buildForEntitlement(account, bundle, baseSubscription, subscription, allSubscriptionsForBundle, subscriptionEntitlementStates, bundleEntitlementStates, accountEntitlementStates, internalTenantContext);
     }
@@ -253,39 +296,16 @@ public class EventsStreamBuilder {
                                              final List<BlockingState> bundleEntitlementStates,
                                              final List<BlockingState> accountEntitlementStates,
                                              final InternalTenantContext internalTenantContext) throws EntitlementApiException {
-        final BlockingAggregator blockingAggregator = checker.getBlockedStatus(filterCurrentBlockableStatePerService(accountEntitlementStates),
-                                                                               filterCurrentBlockableStatePerService(bundleEntitlementStates),
-                                                                               filterCurrentBlockableStatePerService(subscriptionEntitlementStates),
-                                                                               internalTenantContext);
-
-        return new EventsStream(account,
-                                bundle,
-                                subscriptionEntitlementStates,
-                                bundleEntitlementStates,
-                                accountEntitlementStates,
-                                blockingAggregator,
-                                baseSubscription,
-                                subscription,
-                                allSubscriptionsForBundle,
-                                internalTenantContext,
-                                clock.getUTCNow());
-    }
-
-    private List<BlockingState> filterCurrentBlockableStatePerService(final Iterable<BlockingState> allBlockingStates) {
-        final DateTime now = clock.getUTCNow();
-
-        final Map<String, BlockingState> currentBlockingStatePerService = new HashMap<String, BlockingState>();
-        for (final BlockingState blockingState : allBlockingStates) {
-            if (blockingState.getEffectiveDate().isAfter(now)) {
-                continue;
-            }
-
-            if (currentBlockingStatePerService.get(blockingState.getService()) == null ||
-                currentBlockingStatePerService.get(blockingState.getService()).getEffectiveDate().isBefore(blockingState.getEffectiveDate())) {
-                currentBlockingStatePerService.put(blockingState.getService(), blockingState);
-            }
-        }
-
-        return ImmutableList.<BlockingState>copyOf(currentBlockingStatePerService.values());
+        return new DefaultEventsStream(account,
+                                       bundle,
+                                       subscriptionEntitlementStates,
+                                       bundleEntitlementStates,
+                                       accountEntitlementStates,
+                                       checker,
+                                       baseSubscription,
+                                       subscription,
+                                       allSubscriptionsForBundle,
+                                       internalTenantContext,
+                                       clock.getUTCNow());
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java b/entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java
index d95248b..975419d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java
@@ -19,11 +19,13 @@ package com.ning.billing.entitlement.glue;
 import org.skife.config.ConfigSource;
 
 import com.ning.billing.entitlement.DefaultEntitlementService;
+import com.ning.billing.entitlement.EntitlementInternalApi;
 import com.ning.billing.entitlement.EntitlementService;
 import com.ning.billing.entitlement.api.DefaultEntitlementApi;
 import com.ning.billing.entitlement.api.DefaultSubscriptionApi;
 import com.ning.billing.entitlement.api.EntitlementApi;
 import com.ning.billing.entitlement.api.SubscriptionApi;
+import com.ning.billing.entitlement.api.svcs.DefaultEntitlementInternalApi;
 import com.ning.billing.entitlement.api.svcs.DefaultInternalBlockingApi;
 import com.ning.billing.entitlement.block.BlockingChecker;
 import com.ning.billing.entitlement.block.DefaultBlockingChecker;
@@ -38,7 +40,6 @@ import com.google.inject.AbstractModule;
 
 public class DefaultEntitlementModule extends AbstractModule implements EntitlementModule {
 
-
     public DefaultEntitlementModule(final ConfigSource configSource) {
     }
 
@@ -47,6 +48,7 @@ public class DefaultEntitlementModule extends AbstractModule implements Entitlem
         installBlockingStateDao();
         installBlockingApi();
         installEntitlementApi();
+        installEntitlementInternalApi();
         installSubscriptionApi();
         installBlockingChecker();
         bind(EntitlementService.class).to(DefaultEntitlementService.class).asEagerSingleton();
@@ -70,6 +72,11 @@ public class DefaultEntitlementModule extends AbstractModule implements Entitlem
     }
 
     @Override
+    public void installEntitlementInternalApi() {
+        bind(EntitlementInternalApi.class).to(DefaultEntitlementInternalApi.class).asEagerSingleton();
+    }
+
+    @Override
     public void installSubscriptionApi() {
         bind(SubscriptionApi.class).to(DefaultSubscriptionApi.class).asEagerSingleton();
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java
index e250102..78ba189 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java
@@ -29,7 +29,6 @@ import org.testng.annotations.Test;
 
 import com.ning.billing.account.api.Account;
 import com.ning.billing.api.TestApiListener.NextEvent;
-import com.ning.billing.callcontext.InternalCallContext;
 import com.ning.billing.callcontext.InternalTenantContext;
 import com.ning.billing.catalog.api.BillingActionPolicy;
 import com.ning.billing.catalog.api.BillingPeriod;
@@ -38,6 +37,7 @@ import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.EntitlementService;
 import com.ning.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
+import com.ning.billing.entitlement.EventsStream;
 import com.ning.billing.entitlement.api.BlockingState;
 import com.ning.billing.entitlement.api.BlockingStateType;
 import com.ning.billing.entitlement.api.DefaultEntitlement;
@@ -440,7 +440,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
 
     private Collection<BlockingState> computeFutureBlockingStatesForAssociatedAddonsViaAccount(final DefaultEntitlement baseEntitlement) throws EntitlementApiException {
         final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(baseEntitlement.getAccountId(), callContext);
-        final EventsStream eventsStream = Iterables.<EventsStream>find(eventsStreamBuilder.buildForAccount(context),
+        final EventsStream eventsStream = Iterables.<EventsStream>find(Iterables.<EventsStream>concat(eventsStreamBuilder.buildForAccount(context).getEventsStreams().values()),
                                                                        new Predicate<EventsStream>() {
                                                                            @Override
                                                                            public boolean apply(final EventsStream input) {
@@ -457,7 +457,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
 
     private Collection<BlockingState> computeBlockingStatesForAssociatedAddonsViaAccount(final DefaultEntitlement baseEntitlement, final DateTime effectiveDate) throws EntitlementApiException {
         final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(baseEntitlement.getAccountId(), callContext);
-        final EventsStream eventsStream = Iterables.<EventsStream>find(eventsStreamBuilder.buildForAccount(context),
+        final EventsStream eventsStream = Iterables.<EventsStream>find(Iterables.<EventsStream>concat(eventsStreamBuilder.buildForAccount(context).getEventsStreams().values()),
                                                                        new Predicate<EventsStream>() {
                                                                            @Override
                                                                            public boolean apply(final EventsStream input) {
diff --git a/subscription/src/main/java/com/ning/billing/subscription/glue/DefaultSubscriptionModule.java b/subscription/src/main/java/com/ning/billing/subscription/glue/DefaultSubscriptionModule.java
index 0c35d1c..953a2ea 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/glue/DefaultSubscriptionModule.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/glue/DefaultSubscriptionModule.java
@@ -23,6 +23,7 @@ import com.ning.billing.glue.SubscriptionModule;
 import com.ning.billing.subscription.alignment.MigrationPlanAligner;
 import com.ning.billing.subscription.alignment.PlanAligner;
 import com.ning.billing.subscription.api.SubscriptionBaseApiService;
+import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
 import com.ning.billing.subscription.api.SubscriptionBaseService;
 import com.ning.billing.subscription.api.migration.DefaultSubscriptionBaseMigrationApi;
 import com.ning.billing.subscription.api.migration.SubscriptionBaseMigrationApi;
@@ -40,7 +41,6 @@ import com.ning.billing.subscription.engine.dao.DefaultSubscriptionDao;
 import com.ning.billing.subscription.engine.dao.RepairSubscriptionDao;
 import com.ning.billing.subscription.engine.dao.SubscriptionDao;
 import com.ning.billing.util.config.SubscriptionConfig;
-import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.name.Names;
@@ -68,7 +68,6 @@ public class DefaultSubscriptionModule extends AbstractModule implements Subscri
     }
 
     protected void installSubscriptionCore() {
-
         bind(SubscriptionBaseApiService.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairSubscriptionApiService.class).asEagerSingleton();
         bind(SubscriptionBaseApiService.class).to(DefaultSubscriptionBaseApiService.class).asEagerSingleton();
 
@@ -106,7 +105,6 @@ public class DefaultSubscriptionModule extends AbstractModule implements Subscri
         bind(SubscriptionBaseMigrationApi.class).to(DefaultSubscriptionBaseMigrationApi.class).asEagerSingleton();
     }
 
-
     @Override
     public void installSubscriptionInternalApi() {
         bind(SubscriptionBaseInternalApi.class).to(DefaultSubscriptionInternalApi.class).asEagerSingleton();
diff --git a/util/src/main/java/com/ning/billing/util/customfield/ShouldntHappenException.java b/util/src/main/java/com/ning/billing/util/customfield/ShouldntHappenException.java
new file mode 100644
index 0000000..5d4cc2d
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/customfield/ShouldntHappenException.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.util.customfield;
+
+public class ShouldntHappenException extends RuntimeException {
+
+    public ShouldntHappenException(final String message) {
+        super(message);
+    }
+}
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 407e98f..60f9fe8 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
@@ -18,6 +18,7 @@ package com.ning.billing.mock.glue;
 
 import org.mockito.Mockito;
 
+import com.ning.billing.entitlement.EntitlementInternalApi;
 import com.ning.billing.entitlement.api.EntitlementApi;
 import com.ning.billing.entitlement.api.SubscriptionApi;
 import com.ning.billing.glue.EntitlementModule;
@@ -29,6 +30,7 @@ public class MockEntitlementModule extends AbstractModule implements Entitlement
 
     private final BlockingInternalApi blockingApi = Mockito.mock(BlockingInternalApi.class);
     private final EntitlementApi entitlementApi = Mockito.mock(EntitlementApi.class);
+    private final EntitlementInternalApi entitlementInternalApi = Mockito.mock(EntitlementInternalApi.class);
     private final SubscriptionApi subscriptionApi = Mockito.mock(SubscriptionApi.class);
 
     @Override
@@ -54,6 +56,11 @@ public class MockEntitlementModule extends AbstractModule implements Entitlement
     }
 
     @Override
+    public void installEntitlementInternalApi() {
+        bind(EntitlementInternalApi.class).toInstance(entitlementInternalApi);
+    }
+
+    @Override
     public void installSubscriptionApi() {
         bind(SubscriptionApi.class).toInstance(subscriptionApi);
     }
@@ -61,5 +68,4 @@ public class MockEntitlementModule extends AbstractModule implements Entitlement
     @Override
     public void installBlockingChecker() {
     }
-
 }