killbill-uncached

Merge branch 'migration' of git@github.com:ning/killbill

12/21/2011 10:16:52 PM

Changes

Details

diff --git a/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java b/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
index b325d6b..53854d1 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
@@ -17,13 +17,13 @@
 package com.ning.billing.entitlement.api;
 
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
 import com.ning.billing.entitlement.api.test.EntitlementTestApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.lifecycle.KillbillService;
 
 public interface EntitlementService extends KillbillService {
 
-
     @Override
     public String getName();
 
@@ -33,5 +33,5 @@ public interface EntitlementService extends KillbillService {
 
     public EntitlementTestApi getTestApi();
 
-
+    public EntitlementMigrationApi getMigrationApi();
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/migration/EntitlementMigrationApi.java b/api/src/main/java/com/ning/billing/entitlement/api/migration/EntitlementMigrationApi.java
index 7d41f08..b4f13a8 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/migration/EntitlementMigrationApi.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/migration/EntitlementMigrationApi.java
@@ -60,13 +60,15 @@ public interface EntitlementMigrationApi {
 
      *
      */
-    public void migrate(EntitlementAccountMigration toBeMigrated);
+    public void migrate(EntitlementAccountMigration toBeMigrated)
+    throws EntitlementMigrationApiException;
 
     /**
      * Remove all the data pertaining to that acount
      *
      * @param accountKey
      */
-    public void undoMigration(UUID accountKey);
+    public void undoMigration(UUID accountKey)
+        throws EntitlementMigrationApiException;
 
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/migration/EntitlementMigrationApiException.java b/api/src/main/java/com/ning/billing/entitlement/api/migration/EntitlementMigrationApiException.java
new file mode 100644
index 0000000..55835fd
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/migration/EntitlementMigrationApiException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.entitlement.api.migration;
+
+public class EntitlementMigrationApiException extends Exception {
+
+    private static final long serialVersionUID = 7623133L;
+
+    public EntitlementMigrationApiException() {
+        super();
+    }
+
+    public EntitlementMigrationApiException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public EntitlementMigrationApiException(String message) {
+        super(message);
+    }
+
+    public EntitlementMigrationApiException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
index 00e478f..770bdb8 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
@@ -28,6 +28,7 @@ import com.ning.billing.util.eventbus.EventBusNotification;
 public interface SubscriptionTransition extends EventBusNotification {
 
     public enum SubscriptionTransitionType {
+        MIGRATE_ENTITLEMENT,
         CREATE,
         CHANGE,
         PAUSE,
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/MigrationPlanAligner.java b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/MigrationPlanAligner.java
new file mode 100644
index 0000000..da1eb4d
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/MigrationPlanAligner.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.entitlement.alignment;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
+import com.ning.billing.entitlement.events.user.ApiEventType;
+
+public class MigrationPlanAligner {
+
+    private final CatalogService catalogService;
+
+    @Inject
+    public MigrationPlanAligner(CatalogService catalogService) {
+        this.catalogService = catalogService;
+    }
+
+
+    public TimedMigration [] getEventsOnRegularMigration(SubscriptionData subscription,
+            Plan plan, PlanPhase initialPhase, String priceList, DateTime effectiveDate) {
+        TimedMigration [] result = new TimedMigration[1];
+        result[0] = new TimedMigration(effectiveDate, EventType.API_USER, ApiEventType.MIGRATE_ENTITLEMENT, plan, initialPhase, priceList);
+        return result;
+    }
+
+    public TimedMigration [] getEventsOnFuturePhaseChangeMigration(SubscriptionData subscription,
+            Plan plan, PlanPhase initialPhase, String priceList, DateTime effectiveDate, DateTime effectiveDateForNextPhase)
+        throws EntitlementMigrationApiException {
+
+        TimedMigration [] result = new TimedMigration[2];
+        result[0] = new TimedMigration(effectiveDate, EventType.API_USER, ApiEventType.MIGRATE_ENTITLEMENT, plan, initialPhase, priceList);
+        boolean foundCurrent = false;
+        PlanPhase nextPhase = null;
+        for (PlanPhase cur : plan.getAllPhases()) {
+            if (cur == initialPhase) {
+                foundCurrent = true;
+                continue;
+            }
+            if (foundCurrent) {
+                nextPhase = cur;
+            }
+        }
+        if (nextPhase == null) {
+            throw new EntitlementMigrationApiException(String.format("Cannot find next phase for Plan %s and current Phase %s",
+                        plan.getName(), initialPhase.getName()));
+        }
+        result[1] = new TimedMigration(effectiveDateForNextPhase, EventType.PHASE, null, plan, nextPhase, priceList);
+        return result;
+    }
+
+    public TimedMigration [] getEventsOnFuturePlanChangeMigration(SubscriptionData subscription,
+            Plan currentPlan, PlanPhase currentPhase, Plan newPlan, PlanPhase newPhase, String priceList, DateTime effectiveDate, DateTime effectiveDateForChangePlan) {
+        TimedMigration [] result = new TimedMigration[2];
+        result[0] = new TimedMigration(effectiveDate, EventType.API_USER, ApiEventType.MIGRATE_ENTITLEMENT, currentPlan, currentPhase, priceList);
+        result[1] = new TimedMigration(effectiveDateForChangePlan, EventType.API_USER, ApiEventType.CHANGE, newPlan, newPhase, priceList);
+        return result;
+    }
+
+    public TimedMigration [] getEventsOnFuturePlanCancelMigration(SubscriptionData subscription,
+            Plan plan, PlanPhase initialPhase, String priceList, DateTime effectiveDate, DateTime effectiveDateForCancellation) {
+        TimedMigration [] result = new TimedMigration[2];
+        result[0] = new TimedMigration(effectiveDate, EventType.API_USER, ApiEventType.MIGRATE_ENTITLEMENT, plan, initialPhase, priceList);
+        result[1] = new TimedMigration(effectiveDateForCancellation, EventType.API_USER, ApiEventType.CANCEL, null, null, null);
+        return result;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/TimedMigration.java b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/TimedMigration.java
new file mode 100644
index 0000000..e1ad564
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/TimedMigration.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.entitlement.alignment;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
+import com.ning.billing.entitlement.events.user.ApiEventType;
+
+public class TimedMigration {
+
+    private final DateTime eventTime;
+    private final EventType eventType;
+    private final ApiEventType apiEventType;
+
+    private final Plan plan;
+    private final PlanPhase phase;
+    private final String priceList;
+
+
+    public TimedMigration(DateTime eventTime, EventType eventType,
+            ApiEventType apiEventType, Plan plan, PlanPhase phase, String priceList) {
+        super();
+        this.eventTime = eventTime;
+        this.eventType = eventType;
+        this.apiEventType = apiEventType;
+        this.plan = plan;
+        this.phase = phase;
+        this.priceList = priceList;
+    }
+
+    public DateTime getEventTime() {
+        return eventTime;
+    }
+
+    public EventType getEventType() {
+        return eventType;
+    }
+
+
+    public ApiEventType getApiEventType() {
+        return apiEventType;
+    }
+
+    public Plan getPlan() {
+        return plan;
+    }
+
+    public PlanPhase getPhase() {
+        return phase;
+    }
+
+    public String getPriceList() {
+        return priceList;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java
new file mode 100644
index 0000000..7396136
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.entitlement.api.migration;
+
+import java.util.List;
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+
+public class AccountMigrationData {
+
+    private final List<BundleMigrationData> data;
+
+    public AccountMigrationData(List<BundleMigrationData> data) {
+        super();
+        this.data = data;
+    }
+
+    public List<BundleMigrationData> getData() {
+        return data;
+    }
+
+    public static class BundleMigrationData {
+
+        private final SubscriptionBundleData data;
+        private final List<SubscriptionMigrationData> subscriptions;
+
+        public BundleMigrationData(SubscriptionBundleData data,
+                List<SubscriptionMigrationData> subscriptions) {
+            super();
+            this.data = data;
+            this.subscriptions = subscriptions;
+        }
+
+        public SubscriptionBundleData getData() {
+            return data;
+        }
+
+        public List<SubscriptionMigrationData> getSubscriptions() {
+            return subscriptions;
+        }
+    }
+
+    public static class SubscriptionMigrationData {
+
+        private final SubscriptionData data;
+        private final List<EntitlementEvent> initialEvents;
+
+        public SubscriptionMigrationData(SubscriptionData data,
+
+                List<EntitlementEvent> initialEvents) {
+            super();
+            this.data = data;
+            this.initialEvents = initialEvents;
+        }
+
+        public SubscriptionData getData() {
+            return data;
+        }
+
+        public List<EntitlementEvent> getInitialEvents() {
+            return initialEvents;
+        }
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
index a5595e6..b847ebb 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
@@ -16,15 +16,266 @@
 
 package com.ning.billing.entitlement.api.migration;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.alignment.MigrationPlanAligner;
+import com.ning.billing.entitlement.alignment.TimedMigration;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionFactory;
+import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
+import com.ning.billing.entitlement.events.phase.PhaseEvent;
+import com.ning.billing.entitlement.events.phase.PhaseEventData;
+import com.ning.billing.entitlement.events.user.ApiEventBuilder;
+import com.ning.billing.entitlement.events.user.ApiEventCancel;
+import com.ning.billing.entitlement.events.user.ApiEventChange;
+import com.ning.billing.entitlement.events.user.ApiEventMigrate;
+import com.ning.billing.entitlement.exceptions.EntitlementError;
+import com.ning.billing.util.clock.Clock;
+
 public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
 
+
+    private final EntitlementDao dao;
+    private final MigrationPlanAligner migrationAligner;
+    private final SubscriptionFactory factory;
+    private final CatalogService catalogService;
+    private final Clock clock;
+
+    @Inject
+    public DefaultEntitlementMigrationApi(MigrationPlanAligner migrationAligner,
+            SubscriptionFactory factory,
+            CatalogService catalogService,
+            EntitlementDao dao,
+            Clock clock) {
+        this.dao = dao;
+        this.migrationAligner = migrationAligner;
+        this.factory = factory;
+        this.catalogService = catalogService;
+        this.clock = clock;
+    }
+
     @Override
-    public void migrate(EntitlementAccountMigration toBeMigrated) {
+    public void migrate(EntitlementAccountMigration toBeMigrated)
+    throws EntitlementMigrationApiException {
+        AccountMigrationData accountMigrationData = createAccountMigrationData(toBeMigrated);
+        dao.migrate(accountMigrationData);
     }
 
     @Override
-    public void undoMigration(UUID accountKey) {
+    public void undoMigration(UUID accountId) {
+        dao.undoMigration(accountId);
+    }
+
+    private AccountMigrationData createAccountMigrationData(EntitlementAccountMigration toBeMigrated)
+    throws EntitlementMigrationApiException  {
+
+        final UUID accountId = toBeMigrated.getAccountKey();
+        final DateTime now = clock.getUTCNow();
+
+        List<BundleMigrationData> accountBundleData = new LinkedList<BundleMigrationData>();
+
+        for (final EntitlementBundleMigration curBundle : toBeMigrated.getBundles()) {
+
+            SubscriptionBundleData bundleData = new SubscriptionBundleData(curBundle.getBundleKey(), accountId);
+            List<SubscriptionMigrationData> bundleSubscriptionData = new LinkedList<AccountMigrationData.SubscriptionMigrationData>();
+
+            for (EntitlementSubscriptionMigration curSub : curBundle.getSubscriptions()) {
+                SubscriptionMigrationData data = null;
+                switch (curSub.getCategory()) {
+                case BASE:
+                    data = createBaseSubscriptionMigrationData(bundleData.getId(), curSub.getCategory(), curSub.getSubscriptionCases(), now);
+                    break;
+                case ADD_ON:
+                    // Not implemented yet
+                    break;
+                case STANDALONE:
+                    // Not implemented yet
+                    break;
+                default:
+                    throw new EntitlementMigrationApiException(String.format("Unkown product type ", curSub.getCategory()));
+                }
+                if (data != null) {
+                    bundleSubscriptionData.add(data);
+                }
+            }
+            BundleMigrationData bundleMigrationData = new BundleMigrationData(bundleData, bundleSubscriptionData);
+            accountBundleData.add(bundleMigrationData);
+        }
+        AccountMigrationData accountMigrationData = new AccountMigrationData(accountBundleData);
+        return accountMigrationData;
+    }
+
+    private SubscriptionMigrationData createBaseSubscriptionMigrationData(UUID bundleId, ProductCategory productCategory,
+            EntitlementSubscriptionMigrationCase [] input, DateTime now)
+        throws EntitlementMigrationApiException {
+
+        try {
+            final DateTime bundleStartDate = now;
+
+            List<EntitlementEvent> emptyEvents =  Collections.emptyList();
+
+            SubscriptionData subscriptionData = factory.createSubscription(new SubscriptionBuilder()
+            .setId(UUID.randomUUID())
+            .setBundleId(bundleId)
+            .setCategory(productCategory)
+            .setBundleStartDate(bundleStartDate)
+            .setStartDate(now),
+            emptyEvents);
+
+            TimedMigration [] events = null;
+            Plan plan0 = catalogService.getCatalog().findPlan(input[0].getPlanPhaseSpecifer().getProductName(),
+                    input[0].getPlanPhaseSpecifer().getBillingPeriod(), input[0].getPlanPhaseSpecifer().getPriceListName());
+
+            Plan plan1 = (input.length > 1) ? catalogService.getCatalog().findPlan(input[1].getPlanPhaseSpecifer().getProductName(),
+                    input[1].getPlanPhaseSpecifer().getBillingPeriod(), input[1].getPlanPhaseSpecifer().getPriceListName()) :
+                        null;
+
+            if (isRegularMigratedSubscription(input)) {
+
+                events = migrationAligner.getEventsOnRegularMigration(subscriptionData,
+                        plan0,
+                        getPlanPhase(plan0, input[0].getPlanPhaseSpecifer().getPhaseType()),
+                        input[0].getPlanPhaseSpecifer().getPriceListName(),
+                        now);
+
+            } else if (isRegularFutureCancelledMigratedSubscription(input)) {
+
+                events = migrationAligner.getEventsOnFuturePlanCancelMigration(subscriptionData,
+                        plan0,
+                        getPlanPhase(plan0, input[0].getPlanPhaseSpecifer().getPhaseType()),
+                        input[0].getPlanPhaseSpecifer().getPriceListName(),
+                        now,
+                        input[0].getCancelledDate());
+
+            } else if (isPhaseChangeMigratedSubscription(input)) {
+
+                events = migrationAligner.getEventsOnFuturePhaseChangeMigration(subscriptionData,
+                        plan0,
+                        getPlanPhase(plan0, input[0].getPlanPhaseSpecifer().getPhaseType()),
+                        input[0].getPlanPhaseSpecifer().getPriceListName(),
+                        now,
+                        input[1].getEffectiveDate());
+
+            } else if (isPlanChangeMigratedSubscription(input)) {
+
+                events = migrationAligner.getEventsOnFuturePlanChangeMigration(subscriptionData,
+                        plan0,
+                        getPlanPhase(plan0, input[0].getPlanPhaseSpecifer().getPhaseType()),
+                        plan1,
+                        getPlanPhase(plan1, input[1].getPlanPhaseSpecifer().getPhaseType()),
+                        input[0].getPlanPhaseSpecifer().getPriceListName(),
+                        now,
+                        input[1].getEffectiveDate());
+
+            } else {
+                throw new EntitlementMigrationApiException("Unknown migration type");
+            }
+            return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, events));
+        } catch (CatalogApiException e) {
+            throw new EntitlementMigrationApiException(e);
+        }
+    }
+
+    // STEPH should be in catalog
+    private PlanPhase getPlanPhase(Plan plan, PhaseType phaseType) throws EntitlementMigrationApiException {
+        for (PlanPhase cur: plan.getAllPhases()) {
+            if (cur.getPhaseType() == phaseType) {
+                return cur;
+            }
+        }
+        throw new EntitlementMigrationApiException(String.format("Cannot find PlanPhase from Plan %s and type %s", plan.getName(), phaseType));
+    }
+
+    private List<EntitlementEvent> toEvents(SubscriptionData subscriptionData, DateTime now, TimedMigration [] migrationEvents) {
+
+
+        List<EntitlementEvent> events = new ArrayList<EntitlementEvent>(migrationEvents.length);
+        for (TimedMigration cur : migrationEvents) {
+
+            if (cur.getEventType() == EventType.PHASE) {
+                PhaseEvent nextPhaseEvent = PhaseEventData.getNextPhaseEvent(cur.getPhase().getName(), subscriptionData, now, cur.getEventTime());
+                events.add(nextPhaseEvent);
+
+            } else if (cur.getEventType() == EventType.API_USER) {
+
+                ApiEventBuilder builder = new ApiEventBuilder()
+                .setSubscriptionId(subscriptionData.getId())
+                .setEventPlan((cur.getPlan() != null) ? cur.getPlan().getName() : null)
+                .setEventPlanPhase((cur.getPhase() != null) ? cur.getPhase().getName() : null)
+                .setEventPriceList(cur.getPriceList())
+                .setActiveVersion(subscriptionData.getActiveVersion())
+                .setEffectiveDate(cur.getEventTime())
+                .setProcessedDate(now)
+                .setRequestedDate(now);
+
+                switch(cur.getApiEventType()) {
+                case MIGRATE_ENTITLEMENT:
+                    events.add(new ApiEventMigrate(builder));
+                    break;
+
+                case CHANGE:
+                    events.add(new ApiEventChange(builder));
+                    break;
+                case CANCEL:
+                    events.add(new ApiEventCancel(builder));
+                    break;
+                default:
+                    throw new EntitlementError(String.format("Unexpected type of api migration event %s", cur.getApiEventType()));
+                }
+            } else {
+                throw new EntitlementError(String.format("Unexpected type of migration event %s", cur.getEventType()));
+            }
+        }
+        return events;
+    }
+
+    private boolean isRegularMigratedSubscription(EntitlementSubscriptionMigrationCase [] input) {
+        return (input.length == 1 && input[0].getCancelledDate() == null);
+    }
+
+    private boolean isRegularFutureCancelledMigratedSubscription(EntitlementSubscriptionMigrationCase [] input) {
+        return (input.length == 1 && input[0].getCancelledDate() != null);
+    }
+
+    private boolean isPhaseChangeMigratedSubscription(EntitlementSubscriptionMigrationCase [] input) {
+        if (input.length != 2) {
+            return false;
+        }
+        return isSamePlan(input[0].getPlanPhaseSpecifer(), input[1].getPlanPhaseSpecifer());
+    }
+
+    private boolean isPlanChangeMigratedSubscription(EntitlementSubscriptionMigrationCase [] input) {
+        if (input.length != 2) {
+            return false;
+        }
+        return ! isSamePlan(input[0].getPlanPhaseSpecifer(), input[1].getPlanPhaseSpecifer());
+    }
+
+    private boolean isSamePlan(PlanPhaseSpecifier plan0, PlanPhaseSpecifier plan1) {
+        if (plan0.getPriceListName().equals(plan1.getPriceListName()) &&
+                plan0.getProductName().equals(plan1.getProductName())) {
+            return true;
+        }
+        return false;
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
index 1561aec..cc390d9 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
@@ -88,7 +88,10 @@ public class SubscriptionApiService {
             .setEffectiveDate(effectiveDate)
             .setRequestedDate(requestedDate));
 
-            PhaseEvent nextPhaseEvent = PhaseEventData.getNextPhaseEvent(curAndNextPhases[1], subscription, processedDate);
+            TimedPhase nextTimedPhase = curAndNextPhases[1];
+            PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
+                    PhaseEventData.getNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, processedDate, nextTimedPhase.getStartPhase()) :
+                        null;
             List<EntitlementEvent> events = new ArrayList<EntitlementEvent>();
             events.add(creationEvent);
             if (nextPhaseEvent != null) {
@@ -163,7 +166,9 @@ public class SubscriptionApiService {
 
         DateTime planStartDate = subscription.getCurrentPlanStart();
         TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription.getCurrentPlan(), subscription.getInitialPhaseOnCurrentPlan().getPhaseType(), now, planStartDate);
-        PhaseEvent nextPhaseEvent = PhaseEventData.getNextPhaseEvent(nextTimedPhase, subscription, now);
+        PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
+                PhaseEventData.getNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, now, nextTimedPhase.getStartPhase()) :
+                    null;
         if (nextPhaseEvent != null) {
             uncancelEvents.add(nextPhaseEvent);
         }
@@ -227,7 +232,9 @@ public class SubscriptionApiService {
         .setRequestedDate(now));
 
         TimedPhase nextTimedPhase = planAligner.getNextTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), effectiveDate);
-        PhaseEvent nextPhaseEvent = PhaseEventData.getNextPhaseEvent(nextTimedPhase, subscription, now);
+        PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
+                PhaseEventData.getNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, now, nextTimedPhase.getStartPhase()) :
+                    null;
         List<EntitlementEvent> changeEvents = new ArrayList<EntitlementEvent>();
         // Only add the PHASE if it does not coincide with the CHANGE, if not this is 'just' a CHANGE.
         if (nextPhaseEvent != null && ! nextPhaseEvent.getEffectiveDate().equals(changeEvent.getEffectiveDate())) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index 12333a2..8ae9cec 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -355,6 +355,7 @@ public class SubscriptionData implements Subscription {
                 ApiEvent userEV = (ApiEvent) cur;
                 apiEventType = userEV.getEventType();
                 switch(apiEventType) {
+                case MIGRATE_ENTITLEMENT:
                 case CREATE:
                     nextState = SubscriptionState.ACTIVE;
                     nextPlanName = userEV.getEventPlan();
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionFactory.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionFactory.java
index 48008bf..0f5987d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionFactory.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionFactory.java
@@ -44,12 +44,13 @@ public class SubscriptionFactory {
 
     public SubscriptionData createSubscription(SubscriptionBuilder builder, List<EntitlementEvent> events) {
         SubscriptionData subscription = new SubscriptionData(builder, apiService, clock);
-        subscription.rebuildTransitions(events, catalogService.getCatalog());
+        if (events.size() > 0) {
+            subscription.rebuildTransitions(events, catalogService.getCatalog());
+        }
         return subscription;
     }
 
 
-
     public static class SubscriptionBuilder {
 
         private  UUID id;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
index 89b4f2b..721c476 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
@@ -24,11 +24,14 @@ import com.google.inject.Inject;
 import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.config.EntitlementConfig;
 
+import com.ning.billing.entitlement.alignment.MigrationPlanAligner;
 import com.ning.billing.entitlement.alignment.PlanAligner;
 import com.ning.billing.entitlement.alignment.TimedPhase;
 import com.ning.billing.entitlement.api.EntitlementService;
 import com.ning.billing.entitlement.api.billing.DefaultEntitlementBillingApi;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.entitlement.api.migration.DefaultEntitlementMigrationApi;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
 import com.ning.billing.entitlement.api.test.DefaultEntitlementTestApi;
 import com.ning.billing.entitlement.api.test.EntitlementTestApi;
 import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
@@ -64,6 +67,7 @@ public class Engine implements EventListener, EntitlementService {
     private final EntitlementUserApi userApi;
     private final EntitlementBillingApi billingApi;
     private final EntitlementTestApi testApi;
+    private final EntitlementMigrationApi migrationApi;
     private final EventBus eventBus;
 
     private boolean startedNotificationThread;
@@ -71,7 +75,8 @@ public class Engine implements EventListener, EntitlementService {
     @Inject
     public Engine(Clock clock, EntitlementDao dao, EventNotifier apiEventProcessor,
             PlanAligner planAligner, EntitlementConfig config, DefaultEntitlementUserApi userApi,
-            DefaultEntitlementBillingApi billingApi, DefaultEntitlementTestApi testApi, EventBus eventBus) {
+            DefaultEntitlementBillingApi billingApi, DefaultEntitlementTestApi testApi,
+            DefaultEntitlementMigrationApi migrationApi, EventBus eventBus) {
         super();
         this.clock = clock;
         this.dao = dao;
@@ -80,6 +85,7 @@ public class Engine implements EventListener, EntitlementService {
         this.userApi = userApi;
         this.testApi = testApi;
         this.billingApi = billingApi;
+        this.migrationApi = migrationApi;
         this.eventBus = eventBus;
 
         this.startedNotificationThread = false;
@@ -124,6 +130,12 @@ public class Engine implements EventListener, EntitlementService {
     }
 
     @Override
+    public EntitlementMigrationApi getMigrationApi() {
+        return migrationApi;
+    }
+
+
+    @Override
     public void processEventReady(EntitlementEvent event) {
         SubscriptionData subscription = (SubscriptionData) dao.getSubscriptionFromId(event.getSubscriptionId());
         if (subscription == null) {
@@ -180,7 +192,9 @@ public class Engine implements EventListener, EntitlementService {
         try {
             DateTime now = clock.getUTCNow();
             TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription.getCurrentPlan(), subscription.getInitialPhaseOnCurrentPlan().getPhaseType(), now, subscription.getCurrentPlanStart());
-            PhaseEvent nextPhaseEvent = PhaseEventData.getNextPhaseEvent(nextTimedPhase, subscription, now);
+            PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
+                    PhaseEventData.getNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, now, nextTimedPhase.getStartPhase()) :
+                        null;
             if (nextPhaseEvent != null) {
                 dao.createNextPhaseEvent(subscription.getId(), nextPhaseEvent);
             }
@@ -188,4 +202,5 @@ public class Engine implements EventListener, EntitlementService {
             log.error(String.format("Failed to insert next phase for subscription %s", subscription.getId()), e);
         }
     }
+
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
index 10e0348..0bafa09 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
@@ -47,6 +47,9 @@ public interface BundleSqlDao extends Transactional<EventSqlDao>, CloseMe, Trans
     @SqlUpdate
     public void insertBundle(@Bind(binder = SubscriptionBundleBinder.class) SubscriptionBundleData bundle);
 
+    @SqlUpdate
+    public void removeBundle(@Bind("id") String id);
+
     @SqlQuery
     @Mapper(ISubscriptionBundleSqlMapper.class)
     public SubscriptionBundle getBundleFromId(@Bind("id") String id);
@@ -59,7 +62,6 @@ public interface BundleSqlDao extends Transactional<EventSqlDao>, CloseMe, Trans
     @Mapper(ISubscriptionBundleSqlMapper.class)
     public List<SubscriptionBundle> getBundleFromAccount(@Bind("account_id") String accountId);
 
-
     public static class SubscriptionBundleBinder implements Binder<Bind, SubscriptionBundleData> {
 
         private Date getDate(DateTime dateTime) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
index 835da51..dbcc46d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
@@ -20,6 +20,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.entitlement.api.migration.AccountMigrationData;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
@@ -68,4 +69,8 @@ public interface EntitlementDao {
     public void uncancelSubscription(UUID subscriptionId, List<EntitlementEvent> uncancelEvents);
 
     public void changePlan(UUID subscriptionId, List<EntitlementEvent> changeEvents);
+
+    public void migrate(AccountMigrationData data);
+
+    public void undoMigration(UUID accountId);
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
index 15f1e8c..f16eb61 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
@@ -32,6 +32,9 @@ import org.slf4j.LoggerFactory;
 import com.google.inject.Inject;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.config.EntitlementConfig;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
@@ -339,4 +342,58 @@ public class EntitlementSqlDao implements EntitlementDao {
         }
         return result;
     }
+
+    @Override
+    public void migrate(final AccountMigrationData accountData) {
+
+        eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
+
+            @Override
+            public Void inTransaction(EventSqlDao transEventDao,
+                    TransactionStatus status) throws Exception {
+
+                SubscriptionSqlDao transSubDao = transEventDao.become(SubscriptionSqlDao.class);
+                BundleSqlDao transBundleDao = transEventDao.become(BundleSqlDao.class);
+
+                for (BundleMigrationData curBundle : accountData.getData()) {
+                    SubscriptionBundleData bundleData = curBundle.getData();
+                    for (SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
+                        SubscriptionData subData = curSubscription.getData();
+                        for (EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
+                            transEventDao.insertEvent(curEvent);
+                        }
+                        transSubDao.insertSubscription(subData);
+                    }
+                    transBundleDao.insertBundle(bundleData);
+                }
+                return null;
+            }
+        });
+    }
+
+    @Override
+    public void undoMigration(final UUID accountId) {
+
+        eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
+
+            @Override
+            public Void inTransaction(EventSqlDao transEventDao,
+                    TransactionStatus status) throws Exception {
+
+                SubscriptionSqlDao transSubDao = transEventDao.become(SubscriptionSqlDao.class);
+                BundleSqlDao transBundleDao = transEventDao.become(BundleSqlDao.class);
+
+                final List<SubscriptionBundle> bundles = transBundleDao.getBundleFromAccount(accountId.toString());
+                for (SubscriptionBundle curBundle : bundles) {
+                    List<Subscription> subscriptions = transSubDao.getSubscriptionsFromBundleId(curBundle.getId().toString());
+                    for (Subscription cur : subscriptions) {
+                        eventsDao.removeEvents(cur.getId().toString());
+                        transSubDao.removeSubscription(cur.getId().toString());
+                    }
+                    transBundleDao.removeBundle(curBundle.getId().toString());
+                }
+                return null;
+            }
+        });
+    }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
index a1e2670..27e06a0 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
@@ -49,6 +49,7 @@ import com.ning.billing.entitlement.events.user.ApiEventBuilder;
 import com.ning.billing.entitlement.events.user.ApiEventCancel;
 import com.ning.billing.entitlement.events.user.ApiEventChange;
 import com.ning.billing.entitlement.events.user.ApiEventCreate;
+import com.ning.billing.entitlement.events.user.ApiEventMigrate;
 import com.ning.billing.entitlement.events.user.ApiEventPause;
 import com.ning.billing.entitlement.events.user.ApiEventResume;
 import com.ning.billing.entitlement.events.user.ApiEventType;
@@ -70,6 +71,9 @@ public interface EventSqlDao extends Transactional<EventSqlDao>, CloseMe, Transm
     public int claimEvent(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("event_id") String eventId, @Bind("now") Date now);
 
     @SqlUpdate
+    public void removeEvents(@Bind("subscription_id") String subscriptionId);
+
+    @SqlUpdate
     public void clearEvent(@Bind("event_id") String eventId, @Bind("owner") String owner);
 
     @SqlUpdate
@@ -176,6 +180,8 @@ public interface EventSqlDao extends Transactional<EventSqlDao>, CloseMe, Transm
 
                 if (userType == ApiEventType.CREATE) {
                     result = new ApiEventCreate(builder);
+                } else if (userType == ApiEventType.MIGRATE_ENTITLEMENT) {
+                    result = new ApiEventMigrate(builder);
                 } else if (userType == ApiEventType.CHANGE) {
                     result = new ApiEventChange(builder);
                 } else if (userType == ApiEventType.CANCEL) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
index 4a5312c..2741a10 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
@@ -50,6 +50,9 @@ public interface SubscriptionSqlDao extends Transactional<SubscriptionSqlDao>, C
     @SqlUpdate
     public void insertSubscription(@Bind(binder = ISubscriptionDaoBinder.class) SubscriptionData sub);
 
+    @SqlUpdate
+    public void removeSubscription(@Bind("id") String id);
+
     @SqlQuery
     @Mapper(ISubscriptionDaoSqlMapper.class)
     public Subscription getSubscriptionFromId(@Bind("id") String id);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEventData.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEventData.java
index 873fb6d..074dc75 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEventData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEventData.java
@@ -57,15 +57,15 @@ public class PhaseEventData extends EventBase implements PhaseEvent {
                 + ", isActive()=" + isActive() + "]\n";
     }
 
-    public static final PhaseEvent getNextPhaseEvent(TimedPhase nextTimedPhase, SubscriptionData subscription, DateTime now) {
-        return (nextTimedPhase == null) ?
+    public static final PhaseEvent getNextPhaseEvent(String phaseName, SubscriptionData subscription, DateTime now, DateTime effectiveDate) {
+        return (phaseName == null) ?
                 null :
                     new PhaseEventData(new PhaseEventBuilder()
                         .setSubscriptionId(subscription.getId())
                         .setRequestedDate(now)
-                        .setEffectiveDate(nextTimedPhase.getStartPhase())
+                        .setEffectiveDate(effectiveDate)
                         .setProcessedDate(now)
                         .setActiveVersion(subscription.getActiveVersion())
-                        .setPhaseName(nextTimedPhase.getPhase().getName()));
+                        .setPhaseName(phaseName));
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventMigrate.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventMigrate.java
new file mode 100644
index 0000000..636a940
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventMigrate.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.entitlement.events.user;
+
+public class ApiEventMigrate extends ApiEventBase {
+
+    public ApiEventMigrate(ApiEventBuilder builder) {
+        super(builder.setEventType(ApiEventType.MIGRATE_ENTITLEMENT));
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java
index c28941e..b994899 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java
@@ -20,6 +20,10 @@ import com.ning.billing.entitlement.api.user.SubscriptionTransition.Subscription
 
 
 public enum ApiEventType {
+    MIGRATE_ENTITLEMENT {
+        @Override
+        public SubscriptionTransitionType getSubscriptionTransitionType() { return SubscriptionTransitionType.MIGRATE_ENTITLEMENT; }
+    },
     CREATE {
         @Override
         public SubscriptionTransitionType getSubscriptionTransitionType() { return SubscriptionTransitionType.CREATE; }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
index fd2290f..dc3c102 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
@@ -20,6 +20,7 @@ import org.skife.config.ConfigurationObjectFactory;
 
 import com.google.inject.AbstractModule;
 import com.ning.billing.config.EntitlementConfig;
+import com.ning.billing.entitlement.alignment.MigrationPlanAligner;
 import com.ning.billing.entitlement.alignment.PlanAligner;
 import com.ning.billing.entitlement.api.EntitlementService;
 import com.ning.billing.entitlement.api.billing.DefaultEntitlementBillingApi;
@@ -66,6 +67,7 @@ public class EntitlementModule extends AbstractModule {
         bind(EntitlementService.class).to(Engine.class).asEagerSingleton();
         bind(Engine.class).asEagerSingleton();
         bind(PlanAligner.class).asEagerSingleton();
+        bind(MigrationPlanAligner.class).asEagerSingleton();
         bind(EntitlementTestApi.class).to(DefaultEntitlementTestApi.class).asEagerSingleton();
         bind(EntitlementUserApi.class).to(DefaultEntitlementUserApi.class).asEagerSingleton();
         bind(EntitlementBillingApi.class).to(DefaultEntitlementBillingApi.class).asEagerSingleton();
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
index f79666e..4540ad8 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
@@ -3,7 +3,7 @@ CREATE TABLE events (
     id int(11) unsigned NOT NULL AUTO_INCREMENT,
     event_id char(36) NOT NULL,
     event_type varchar(9) NOT NULL,
-    user_type varchar(10) DEFAULT NULL,
+    user_type varchar(25) DEFAULT NULL,
     created_dt datetime NOT NULL,
     updated_dt datetime NOT NULL,
     requested_dt datetime NOT NULL,
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg
index cb15e9b..d9f26c2 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg
@@ -14,6 +14,12 @@ insertBundle() ::= <<
     );
 >>
 
+removeBundle(id) ::= <<
+    delete from bundles
+    where
+      id = :id
+    ;
+>>
 
 getBundleFromId(id) ::= <<
     select
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
index a3b677a..31e9eaf 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
@@ -96,6 +96,13 @@ insertEvent() ::= <<
     );   
 >>
 
+removeEvents(subscription_id) ::= <<
+    delete from events
+      where
+    subscription_id = :subscription_id
+    ;
+>>
+
 insertClaimedHistory(sequence_id, owner_id, hostname, claimed_dt, event_id) ::= <<
     insert into claimed_events (
         sequence_id
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
index 094bdf1..aee490c 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
@@ -22,6 +22,13 @@ insertSubscription() ::= <<
     );
 >>
 
+removeSubscription(id) ::= <<
+    delete from subscriptions
+      where
+   where id = :id
+    ;    
+>>
+
 getSubscriptionFromId(id) ::= <<
     select
         id
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
index b14bf38..8f90bc7 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
@@ -40,6 +40,7 @@ public class ApiTestListener {
     private volatile boolean completed;
 
     public enum NextEvent {
+        MIGRATE_ENTITLEMENT,
         CREATE,
         CHANGE,
         CANCEL,
@@ -56,6 +57,9 @@ public class ApiTestListener {
     @Subscribe
     public void handleEntitlementEvent(SubscriptionTransition event) {
         switch (event.getTransitionType()) {
+        case MIGRATE_ENTITLEMENT:
+            subscriptionMigrated(event);
+            break;
         case CREATE:
             subscriptionCreated(event);
             break;
@@ -139,6 +143,12 @@ public class ApiTestListener {
     }
 
 
+    public void subscriptionMigrated(SubscriptionTransition migrated) {
+        log.debug("-> Got event MIGRATED");
+        assertEqualsNicely(NextEvent.MIGRATE_ENTITLEMENT);
+        notifyIfStackEmpty();
+    }
+
     public void subscriptionCreated(SubscriptionTransition created) {
         log.debug("-> Got event CREATED");
         assertEqualsNicely(NextEvent.CREATE);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
new file mode 100644
index 0000000..ef01b80
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.entitlement.api.migration;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApiException;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementAccountMigration;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementBundleMigration;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementSubscriptionMigration;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementSubscriptionMigrationCase;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+
+public abstract class TestMigration extends TestApiBase {
+
+
+    public void testSingleBasePlanReal() {
+
+        try {
+            DateTime beforeMigration = clock.getUTCNow();
+            EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlan();
+            DateTime afterMigration = clock.getUTCNow();
+
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            migrationApi.migrate(toBeMigrated);
+            assertTrue(testListener.isCompleted(5000));
+
+            List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
+            assertEquals(bundles.size(), 1);
+            SubscriptionBundle bundle = bundles.get(0);
+
+            List<Subscription> subscriptions = entitlementApi.getSubscriptionsForBundle(bundle.getId());
+            assertEquals(subscriptions.size(), 1);
+            Subscription subscription = subscriptions.get(0);
+            assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
+            assertEquals(subscription.getEndDate(), null);
+            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
+            assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
+            assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-annual");
+        } catch (EntitlementMigrationApiException e) {
+            Assert.fail("", e);
+        }
+    }
+
+
+    public void testSingleBasePlanFutureCancelledReal() {
+
+        try {
+
+            DateTime beforeMigration = clock.getUTCNow();
+            EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlanFutreCancelled();
+            DateTime afterMigration = clock.getUTCNow();
+
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            migrationApi.migrate(toBeMigrated);
+            assertTrue(testListener.isCompleted(5000));
+
+            List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
+            assertEquals(bundles.size(), 1);
+            SubscriptionBundle bundle = bundles.get(0);
+            //assertEquals(bundle.getStartDate(), effectiveDate);
+
+            List<Subscription> subscriptions = entitlementApi.getSubscriptionsForBundle(bundle.getId());
+            assertEquals(subscriptions.size(), 1);
+            Subscription subscription = subscriptions.get(0);
+            assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
+            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
+            assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
+            assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-annual");
+
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            Duration oneYear = getDurationYear(1);
+            clock.setDeltaFromReality(oneYear, 0);
+            assertTrue(testListener.isCompleted(5000));
+
+            assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
+            assertNotNull(subscription.getEndDate());
+            assertTrue(subscription.getEndDate().isAfterNow());
+            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPhase(), null);
+            assertEquals(subscription.getState(), SubscriptionState.CANCELLED);
+            assertNull(subscription.getCurrentPlan());
+
+        } catch (EntitlementMigrationApiException e) {
+            Assert.fail("", e);
+        }
+    }
+
+    public void testSingleBasePlanWithPendingPhaseReal() {
+
+        try {
+            DateTime beforeMigration = clock.getUTCNow();
+            EntitlementAccountMigration toBeMigrated = createAccountFuturePendingPhase();
+            DateTime afterMigration = clock.getUTCNow();
+
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            migrationApi.migrate(toBeMigrated);
+            assertTrue(testListener.isCompleted(5000));
+
+            List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
+            assertEquals(bundles.size(), 1);
+            SubscriptionBundle bundle = bundles.get(0);
+
+            List<Subscription> subscriptions = entitlementApi.getSubscriptionsForBundle(bundle.getId());
+            assertEquals(subscriptions.size(), 1);
+            Subscription subscription = subscriptions.get(0);
+            assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
+            assertEquals(subscription.getEndDate(), null);
+            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.TRIAL);
+            assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
+            assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
+
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            Duration thirtyDays = getDurationDay(30);
+            clock.setDeltaFromReality(thirtyDays, 0);
+            assertTrue(testListener.isCompleted(5000));
+
+            assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
+            assertEquals(subscription.getEndDate(), null);
+            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
+            assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
+            assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
+            assertEquals(subscription.getCurrentPhase().getName(), "assault-rifle-monthly-evergreen");
+
+        } catch (EntitlementMigrationApiException e) {
+            Assert.fail("", e);
+        }
+    }
+
+
+    public void testSingleBasePlanWithPendingChangeReal() {
+
+        try {
+            DateTime beforeMigration = clock.getUTCNow();
+            EntitlementAccountMigration toBeMigrated = createAccountFuturePendingChange();
+            DateTime afterMigration = clock.getUTCNow();
+
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            migrationApi.migrate(toBeMigrated);
+            assertTrue(testListener.isCompleted(5000));
+
+            List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
+            assertEquals(bundles.size(), 1);
+            SubscriptionBundle bundle = bundles.get(0);
+
+            List<Subscription> subscriptions = entitlementApi.getSubscriptionsForBundle(bundle.getId());
+            assertEquals(subscriptions.size(), 1);
+            Subscription subscription = subscriptions.get(0);
+            assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
+            assertEquals(subscription.getEndDate(), null);
+            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
+            assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
+            assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
+
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            Duration oneMonth = getDurationMonth(1);
+            clock.setDeltaFromReality(oneMonth, 0);
+            assertTrue(testListener.isCompleted(5000));
+
+            assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
+            assertEquals(subscription.getEndDate(), null);
+            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+
+            assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
+            assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
+            assertEquals(subscription.getCurrentPlan().getName(), "shotgun-annual");
+
+        } catch (EntitlementMigrationApiException e) {
+            Assert.fail("", e);
+        }
+    }
+
+
+    private EntitlementAccountMigration createAccountWithSingleBasePlan(final List<EntitlementSubscriptionMigrationCase> cases) {
+
+        return new EntitlementAccountMigration() {
+
+            private final UUID accountId = UUID.randomUUID();
+
+            @Override
+            public EntitlementBundleMigration[] getBundles() {
+                List<EntitlementBundleMigration> bundles = new ArrayList<EntitlementBundleMigration>();
+                EntitlementBundleMigration bundle0 = new EntitlementBundleMigration() {
+
+                    @Override
+                    public EntitlementSubscriptionMigration[] getSubscriptions() {
+                        EntitlementSubscriptionMigration subscription = new EntitlementSubscriptionMigration() {
+                            @Override
+                            public EntitlementSubscriptionMigrationCase[] getSubscriptionCases() {
+                                return cases.toArray(new EntitlementSubscriptionMigrationCase[cases.size()]);
+                            }
+                            @Override
+                            public ProductCategory getCategory() {
+                                return ProductCategory.BASE;
+                            }
+                        };
+                        EntitlementSubscriptionMigration[] result = new EntitlementSubscriptionMigration[1];
+                        result[0] = subscription;
+                        return result;
+                    }
+                    @Override
+                    public String getBundleKey() {
+                        return "12345";
+                    }
+                };
+                bundles.add(bundle0);
+                return bundles.toArray(new EntitlementBundleMigration[bundles.size()]);
+            }
+
+            @Override
+            public UUID getAccountKey() {
+                return accountId;
+            }
+        };
+    }
+
+    private EntitlementAccountMigration createAccountWithRegularBasePlan() {
+        List<EntitlementSubscriptionMigrationCase> cases = new LinkedList<EntitlementSubscriptionMigrationCase>();
+        cases.add(new EntitlementSubscriptionMigrationCase() {
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifer() {
+                return new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return clock.getUTCNow().minusMonths(3);
+            }
+            @Override
+            public DateTime getCancelledDate() {
+                return null;
+            }
+        });
+        return createAccountWithSingleBasePlan(cases);
+    }
+
+    private EntitlementAccountMigration createAccountWithRegularBasePlanFutreCancelled() {
+        List<EntitlementSubscriptionMigrationCase> cases = new LinkedList<EntitlementSubscriptionMigrationCase>();
+        final DateTime effectiveDate = clock.getUTCNow().minusMonths(3);
+        cases.add(new EntitlementSubscriptionMigrationCase() {
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifer() {
+                return new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return effectiveDate;
+            }
+            @Override
+            public DateTime getCancelledDate() {
+                return effectiveDate.plusYears(1);
+            }
+        });
+        return createAccountWithSingleBasePlan(cases);
+    }
+
+
+    private EntitlementAccountMigration createAccountFuturePendingPhase() {
+        List<EntitlementSubscriptionMigrationCase> cases = new LinkedList<EntitlementSubscriptionMigrationCase>();
+        final DateTime trialDate = clock.getUTCNow().minusDays(10);
+        cases.add(new EntitlementSubscriptionMigrationCase() {
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifer() {
+                return new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return trialDate;
+            }
+            @Override
+            public DateTime getCancelledDate() {
+                return trialDate.plusDays(30);
+            }
+        });
+        cases.add(new EntitlementSubscriptionMigrationCase() {
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifer() {
+                return new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return trialDate.plusDays(31);
+            }
+            @Override
+            public DateTime getCancelledDate() {
+                return null;
+            }
+        });
+        return createAccountWithSingleBasePlan(cases);
+    }
+
+    private EntitlementAccountMigration createAccountFuturePendingChange() {
+        List<EntitlementSubscriptionMigrationCase> cases = new LinkedList<EntitlementSubscriptionMigrationCase>();
+        final DateTime effectiveDate = clock.getUTCNow().minusDays(10);
+        cases.add(new EntitlementSubscriptionMigrationCase() {
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifer() {
+                return new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return effectiveDate;
+            }
+            @Override
+            public DateTime getCancelledDate() {
+                return effectiveDate.plusMonths(1);
+            }
+        });
+        cases.add(new EntitlementSubscriptionMigrationCase() {
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifer() {
+                return new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return effectiveDate.plusMonths(1).plusDays(1);
+            }
+            @Override
+            public DateTime getCancelledDate() {
+                return null;
+            }
+        });
+        return createAccountWithSingleBasePlan(cases);
+    }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java
new file mode 100644
index 0000000..28a5ce0
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.entitlement.api.migration;
+
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
+
+public class TestMigrationMemory extends TestMigration {
+    @Override
+    protected Injector getInjector() {
+        return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleMemory());
+    }
+
+    @Test(enabled=true, groups="sql")
+    public void testSingleBasePlan() {
+        invokeRealMethod(this);
+    }
+
+    @Test(enabled=true, groups="sql")
+    public void testSingleBasePlanFutureCancelled() {
+        invokeRealMethod(this);
+    }
+
+    @Test(enabled=true, groups="sql")
+    public void testSingleBasePlanWithPendingPhase() {
+        invokeRealMethod(this);
+    }
+
+    @Test(enabled=true, groups="sql")
+    public void testSingleBasePlanWithPendingChange() {
+        invokeRealMethod(this);
+    }
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java
new file mode 100644
index 0000000..2329f3d
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.entitlement.api.migration;
+
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+
+public class TestMigrationSql extends TestMigration {
+
+    @Override
+    protected Injector getInjector() {
+        return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
+    }
+
+    @Test(enabled=true, groups="sql")
+    public void testSingleBasePlan() {
+        invokeRealMethod(this);
+    }
+
+    @Test(enabled=true, groups="sql")
+    public void testSingleBasePlanFutureCancelled() {
+        invokeRealMethod(this);
+    }
+
+    @Test(enabled=true, groups="sql")
+    public void testSingleBasePlanWithPendingPhase() {
+        invokeRealMethod(this);
+    }
+
+    @Test(enabled=true, groups="sql")
+    public void testSingleBasePlanWithPendingChange() {
+        invokeRealMethod(this);
+    }
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
index 09a70f2..4691404 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
@@ -32,10 +32,11 @@ import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.util.clock.DefaultClock;
 
-public abstract class TestUserApiCancel extends TestUserApiBase {
+public abstract class TestUserApiCancel extends TestApiBase {
 
     protected void testCancelSubscriptionIMMReal() {
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
index 14a46aa..dfa2bc7 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
@@ -34,12 +34,13 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.user.ApiEvent;
 import com.ning.billing.util.clock.DefaultClock;
 
-public abstract class TestUserApiChangePlan extends TestUserApiBase {
+public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
index 4ae0432..de1dbf9 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
@@ -31,12 +31,13 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.phase.PhaseEvent;
 import com.ning.billing.util.clock.DefaultClock;
 
-public abstract class TestUserApiCreate extends TestUserApiBase {
+public abstract class TestUserApiCreate extends TestApiBase {
 
 
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
index 2476629..feb4b54 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
@@ -39,11 +39,12 @@ import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
 import com.ning.billing.util.clock.DefaultClock;
 
-public class TestUserApiDemos extends TestUserApiBase {
+public class TestUserApiDemos extends TestApiBase {
 
     @Override
     protected Injector getInjector() {
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
index 799e1f9..3c1c489 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
@@ -34,11 +34,12 @@ import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
 import com.ning.billing.util.clock.DefaultClock;
 
-public class TestUserApiError extends TestUserApiBase {
+public class TestUserApiError extends TestApiBase {
 
 
     @Override
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiPriceList.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiPriceList.java
index d6a2b12..a1a0dc7 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiPriceList.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiPriceList.java
@@ -17,8 +17,9 @@
 package com.ning.billing.entitlement.api.user;
 
 import com.google.inject.Injector;
+import com.ning.billing.entitlement.api.TestApiBase;
 
-public class TestUserApiPriceList extends TestUserApiBase  {
+public class TestUserApiPriceList extends TestApiBase  {
 
     @Override
     protected Injector getInjector() {
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
index 69fcd08..6ebd238 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
@@ -31,11 +31,12 @@ import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
 import com.ning.billing.util.clock.DefaultClock;
 
-public class TestUserApiScenarios extends TestUserApiBase {
+public class TestUserApiScenarios extends TestApiBase {
 
     @Override
     protected Injector getInjector() {
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
index f3f42f6..1a9b328 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
@@ -35,6 +35,9 @@ import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.config.EntitlementConfig;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionApiService;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
@@ -349,4 +352,41 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
         }
     }
 
+
+    @Override
+    public void migrate(final AccountMigrationData accountData) {
+        synchronized(events) {
+            for (BundleMigrationData curBundle : accountData.getData()) {
+                SubscriptionBundleData bundleData = curBundle.getData();
+                for (SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
+                    SubscriptionData subData = curSubscription.getData();
+                    for (EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
+                        events.add(curEvent);
+                    }
+                    subscriptions.add(subData);
+                }
+                bundles.add(bundleData);
+            }
+        }
+    }
+
+    @Override
+    public void undoMigration(UUID accountId) {
+        synchronized(events) {
+
+            List<SubscriptionBundle> allBundles = getSubscriptionBundleForAccount(accountId);
+            for (SubscriptionBundle bundle : allBundles) {
+                List<Subscription> allSubscriptions = getSubscriptions(bundle.getId());
+                for (Subscription subscription : allSubscriptions) {
+                    List<EntitlementEvent> allEvents = getEventsForSubscription(subscription.getId());
+                    for (EntitlementEvent event : allEvents) {
+                        events.remove(event);
+                    }
+                    subscriptions.remove(subscription);
+                }
+                bundles.remove(bundle);
+            }
+        }
+
+    }
 }