killbill-memoizeit

Merge branch 'catalog-plugin-api'

6/25/2015 12:34:57 AM

Changes

catalog/pom.xml 8(+8 -0)

pom.xml 2(+1 -1)

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithCatalogPlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithCatalogPlugin.java
new file mode 100644
index 0000000..8559991
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithCatalogPlugin.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.beatrix.integration;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountData;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.StandaloneCatalog;
+import org.killbill.billing.catalog.StandaloneCatalogWithPriceOverride;
+import org.killbill.billing.catalog.VersionedCatalog;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.override.PriceOverride;
+import org.killbill.billing.catalog.plugin.TestModelStandalonePluginCatalog;
+import org.killbill.billing.catalog.plugin.TestModelVersionedPluginCatalog;
+import org.killbill.billing.catalog.plugin.api.CatalogPluginApi;
+import org.killbill.billing.catalog.plugin.api.StandalonePluginCatalog;
+import org.killbill.billing.catalog.plugin.api.VersionedPluginCatalog;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.xmlloader.XMLLoader;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Resources;
+
+public class TestWithCatalogPlugin extends TestIntegrationBase {
+
+    @Inject
+    private OSGIServiceRegistration<CatalogPluginApi> pluginRegistry;
+
+    @Inject
+    private PriceOverride priceOverride;
+
+    @Inject
+    private InternalCallContextFactory internalCallContextFactory;
+
+    private TestCatalogPluginApi testCatalogPluginApi;
+
+    @BeforeClass(groups = "slow")
+    public void beforeClass() throws Exception {
+        super.beforeClass();
+
+        this.testCatalogPluginApi = new TestCatalogPluginApi(priceOverride, internalCallContext, internalCallContextFactory);
+        pluginRegistry.registerService(new OSGIServiceDescriptor() {
+            @Override
+            public String getPluginSymbolicName() {
+                return "TestCatalogPluginApi";
+            }
+
+            @Override
+            public String getRegistrationName() {
+                return "TestCatalogPluginApi";
+            }
+        }, testCatalogPluginApi);
+    }
+
+    @Test(groups = "slow")
+    public void testCreateSubscriptionWithCatalogPlugin() throws Exception {
+
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        // We take april as it has 30 days (easier to play with BCD)
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(new LocalDate(2012, 4, 1));
+
+        //
+        // Create original subscription (Trial PHASE) -> $0 invoice.
+        final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
+    }
+
+    public static class TestCatalogPluginApi implements CatalogPluginApi {
+
+        private final VersionedCatalog versionedCatalog;
+
+        public TestCatalogPluginApi(final PriceOverride priceOverride, final InternalTenantContext internalTenantContext, final InternalCallContextFactory internalCallContextFactory) throws Exception {
+            final StandaloneCatalog inputCatalog = XMLLoader.getObjectFromString(Resources.getResource("WeaponsHire.xml").toExternalForm(), StandaloneCatalog.class);
+            final List<StandaloneCatalogWithPriceOverride> versions = new ArrayList<StandaloneCatalogWithPriceOverride>();
+            final StandaloneCatalogWithPriceOverride standaloneCatalogWithPriceOverride = new StandaloneCatalogWithPriceOverride(inputCatalog, priceOverride, internalTenantContext.getTenantRecordId(), internalCallContextFactory);
+            versions.add(standaloneCatalogWithPriceOverride);
+            versionedCatalog = new VersionedCatalog(getClock(), inputCatalog.getCatalogName(), inputCatalog.getRecurringBillingMode(), versions, internalTenantContext);
+        }
+
+        @Override
+        public VersionedPluginCatalog getVersionedPluginCatalog(final Iterable<PluginProperty> properties, final TenantContext tenantContext) {
+            return new TestModelVersionedPluginCatalog(versionedCatalog.getCatalogName(), versionedCatalog.getRecurringBillingMode(), toStandalonePluginCatalogs(versionedCatalog.getVersions()));
+        }
+
+        private Iterable<StandalonePluginCatalog> toStandalonePluginCatalogs(final List<StandaloneCatalogWithPriceOverride> input) {
+            return Iterables.transform(input, new Function<StandaloneCatalogWithPriceOverride, StandalonePluginCatalog>() {
+                @Override
+                public StandalonePluginCatalog apply(final StandaloneCatalogWithPriceOverride input) {
+                    try {
+
+                        return new TestModelStandalonePluginCatalog(new DateTime(input.getEffectiveDate()),
+                                                                    ImmutableList.copyOf(input.getCurrentSupportedCurrencies()),
+                                                                    ImmutableList.<Product>copyOf(input.getCurrentProducts()),
+                                                                    ImmutableList.<Plan>copyOf(input.getCurrentPlans()),
+                                                                    input.getStandaloneCatalog().getPriceLists().getDefaultPricelist(),
+                                                                    ImmutableList.<PriceList>copyOf(input.getStandaloneCatalog().getPriceLists().getChildPriceLists()),
+                                                                    input.getStandaloneCatalog().getPlanRules(),
+                                                                    null /*ImmutableList.<Unit>copyOf(input.getStandaloneCatalog().getCurrentUnits()) */);
+                    } catch (CatalogApiException e) {
+                        throw new IllegalStateException(e);
+                    }
+                }
+
+                private <I, C extends I> List<I> listOf(@Nullable C[] input) {
+                    return (input != null) ? ImmutableList.<I>copyOf(input) : ImmutableList.<I>of();
+                }
+            });
+        }
+    }
+}

catalog/pom.xml 8(+8 -0)

diff --git a/catalog/pom.xml b/catalog/pom.xml
index 3b34b9d..f6dd499 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -68,6 +68,10 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-osgi-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-platform-test</artifactId>
             <scope>test</scope>
         </dependency>
@@ -82,6 +86,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.kill-bill.billing.plugin</groupId>
+            <artifactId>killbill-plugin-api-catalog</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.kill-bill.commons</groupId>
             <artifactId>killbill-clock</artifactId>
         </dependency>
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
index ef3806d..44e704d 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
@@ -27,16 +27,23 @@ import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.VersionedCatalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.io.VersionedCatalogLoader;
+import org.killbill.billing.catalog.plugin.VersionedCatalogMapper;
+import org.killbill.billing.catalog.plugin.api.CatalogPluginApi;
+import org.killbill.billing.catalog.plugin.api.VersionedPluginCatalog;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.cache.CacheController;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.cache.CacheLoaderArgument;
 import org.killbill.billing.util.cache.TenantCatalogCacheLoader.LoaderCallback;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.callcontext.TenantContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
 
 public class EhCacheCatalogCache implements CatalogCache {
 
@@ -45,13 +52,23 @@ public class EhCacheCatalogCache implements CatalogCache {
     private final CacheController cacheController;
     private final VersionedCatalogLoader loader;
     private final CacheLoaderArgument cacheLoaderArgument;
+    private final OSGIServiceRegistration<CatalogPluginApi> pluginRegistry;
+    private final VersionedCatalogMapper versionedCatalogMapper;
+    private final InternalCallContextFactory internalCallContextFactory;
 
     private VersionedCatalog defaultCatalog;
 
     @Inject
-    public EhCacheCatalogCache(final CacheControllerDispatcher cacheControllerDispatcher, final VersionedCatalogLoader loader) {
+    public EhCacheCatalogCache(final OSGIServiceRegistration<CatalogPluginApi> pluginRegistry,
+                               final VersionedCatalogMapper versionedCatalogMapper,
+                               final CacheControllerDispatcher cacheControllerDispatcher,
+                               final VersionedCatalogLoader loader,
+                               final InternalCallContextFactory internalCallContextFactory) {
+        this.pluginRegistry = pluginRegistry;
+        this.versionedCatalogMapper = versionedCatalogMapper;
         this.cacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_CATALOG);
         this.loader = loader;
+        this.internalCallContextFactory = internalCallContextFactory;
         this.cacheLoaderArgument = initializeCacheLoaderArgument(this);
         setDefaultCatalog();
     }
@@ -65,6 +82,13 @@ public class EhCacheCatalogCache implements CatalogCache {
 
     @Override
     public VersionedCatalog getCatalog(final InternalTenantContext tenantContext) throws CatalogApiException {
+
+        // STEPH TODO what are the possibilities for caching here ?
+        final VersionedCatalog pluginVersionedCatalog = getCatalogFromPlugins(tenantContext);
+        if (pluginVersionedCatalog != null) {
+            return pluginVersionedCatalog;
+        }
+
         if (tenantContext.getTenantRecordId() == InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID) {
             return defaultCatalog;
         }
@@ -75,7 +99,7 @@ public class EhCacheCatalogCache implements CatalogCache {
             // It means we are using a default catalog in a multi-tenant deployment, that does not really match a real use case, but we want to support it
             // for test purpose.
             if (tenantCatalog == null) {
-                tenantCatalog = new VersionedCatalog(defaultCatalog, tenantContext);
+                tenantCatalog = new VersionedCatalog(defaultCatalog.getClock(), defaultCatalog.getCatalogName(), defaultCatalog.getRecurringBillingMode(), defaultCatalog.getVersions(), tenantContext);
                 cacheController.add(tenantContext.getTenantRecordId(), tenantCatalog);
             }
             return tenantCatalog;
@@ -91,6 +115,20 @@ public class EhCacheCatalogCache implements CatalogCache {
         }
     }
 
+    private VersionedCatalog getCatalogFromPlugins(final InternalTenantContext internalTenantContext) {
+        final TenantContext tenantContext = internalCallContextFactory.createTenantContext(internalTenantContext);
+        for (final String service : pluginRegistry.getAllServices()) {
+            final CatalogPluginApi plugin = pluginRegistry.getServiceForName(service);
+            final VersionedPluginCatalog pluginCatalog = plugin.getVersionedPluginCatalog(ImmutableList.<PluginProperty>of(), tenantContext);
+            // First plugin that gets something (for that tenant) returns it
+            if (pluginCatalog != null) {
+                logger.info("Returning catalog from plugin {} on tenant {} ", service, internalTenantContext.getTenantRecordId());
+                return versionedCatalogMapper.toVersionedCatalog(pluginCatalog, internalTenantContext);
+            }
+        }
+        return null;
+    }
+
     //
     // Build the LoaderCallback that is required to build the catalog from the xml from a module that knows
     // nothing about catalog.
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
index 9159e18..784e577 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
@@ -122,4 +122,47 @@ public class DefaultBlock extends ValidatingConfig<StandaloneCatalog> implements
         this.phase = phase;
         return this;
     }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultBlock)) {
+            return false;
+        }
+
+        final DefaultBlock that = (DefaultBlock) o;
+
+        if (minTopUpCredit != null ? !minTopUpCredit.equals(that.minTopUpCredit) : that.minTopUpCredit != null) {
+            return false;
+        }
+        if (phase != null ? !phase.equals(that.phase) : that.phase != null) {
+            return false;
+        }
+        if (prices != null ? !prices.equals(that.prices) : that.prices != null) {
+            return false;
+        }
+        if (size != null ? !size.equals(that.size) : that.size != null) {
+            return false;
+        }
+        if (type != that.type) {
+            return false;
+        }
+        if (unit != null ? !unit.equals(that.unit) : that.unit != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = type != null ? type.hashCode() : 0;
+        result = 31 * result + (unit != null ? unit.hashCode() : 0);
+        result = 31 * result + (size != null ? size.hashCode() : 0);
+        result = 31 * result + (prices != null ? prices.hashCode() : 0);
+        result = 31 * result + (minTopUpCredit != null ? minTopUpCredit.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
index a049cc6..4c3ed6c 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
@@ -31,11 +31,13 @@ import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> implements Duration {
+
+    public static final int DEFAULT_DURATION_NUMBER  = -1;
     @XmlElement(required = true)
     private TimeUnit unit;
 
     @XmlElement(required = false)
-    private Integer number = -1;
+    private Integer number;
 
     /* (non-Javadoc)
       * @see org.killbill.billing.catalog.IDuration#getUnit()
@@ -53,6 +55,10 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
         return number;
     }
 
+    public DefaultDuration() {
+        number = DEFAULT_DURATION_NUMBER;
+    }
+
     @Override
     public DateTime addToDateTime(final DateTime dateTime) {
         if ((number == null) && (unit != TimeUnit.UNLIMITED)) {
@@ -85,12 +91,12 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
         return errors;
     }
 
-    protected DefaultDuration setUnit(final TimeUnit unit) {
+    public DefaultDuration setUnit(final TimeUnit unit) {
         this.unit = unit;
         return this;
     }
 
-    protected DefaultDuration setNumber(final Integer number) {
+    public DefaultDuration setNumber(final Integer number) {
         this.number = number;
         return this;
     }
@@ -114,4 +120,32 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
                 return new Period();
         }
     }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultDuration)) {
+            return false;
+        }
+
+        final DefaultDuration that = (DefaultDuration) o;
+
+        if (number != null ? !number.equals(that.number) : that.number != null) {
+            return false;
+        }
+        if (unit != that.unit) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = unit != null ? unit.hashCode() : 0;
+        result = 31 * result + (number != null ? number.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
index b791185..8d214f8 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
@@ -26,7 +26,6 @@ import javax.xml.bind.annotation.XmlElement;
 import org.killbill.billing.catalog.api.Fixed;
 import org.killbill.billing.catalog.api.FixedType;
 import org.killbill.billing.catalog.api.InternationalPrice;
-import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
@@ -34,16 +33,12 @@ import org.killbill.xmlloader.ValidationErrors;
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultFixed extends ValidatingConfig<StandaloneCatalog> implements Fixed {
 
-
     @XmlAttribute(required = false)
-    private FixedType type = FixedType.ONE_TIME;
+    private FixedType type;
 
     @XmlElement(required = false)
     private DefaultInternationalPrice fixedPrice;
 
-    // Not exposed in xml.
-    private PlanPhase phase;
-
     @Override
     public FixedType getType() {
         return type;
@@ -54,8 +49,9 @@ public class DefaultFixed extends ValidatingConfig<StandaloneCatalog> implements
         return fixedPrice;
     }
 
-
-    public DefaultFixed() {}
+    public DefaultFixed() {
+        type = FixedType.ONE_TIME;
+    }
 
     public DefaultFixed(final DefaultFixed in, final PlanPhasePriceOverride override) {
         this.type = in.getType();
@@ -68,23 +64,47 @@ public class DefaultFixed extends ValidatingConfig<StandaloneCatalog> implements
             fixedPrice.initialize(root, uri);
         }
     }
-        @Override
+
+    @Override
     public ValidationErrors validate(final StandaloneCatalog root, final ValidationErrors errors) {
         return errors;
     }
 
-    protected DefaultFixed setType(final FixedType type) {
+    public DefaultFixed setType(final FixedType type) {
         this.type = type;
         return this;
     }
 
-    protected DefaultFixed setFixedPrice(final DefaultInternationalPrice fixedPrice) {
+    public DefaultFixed setFixedPrice(final DefaultInternationalPrice fixedPrice) {
         this.fixedPrice = fixedPrice;
         return this;
     }
 
-    protected DefaultFixed setPhase(final PlanPhase phase) {
-        this.phase = phase;
-        return this;
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultFixed)) {
+            return false;
+        }
+
+        final DefaultFixed that = (DefaultFixed) o;
+
+        if (fixedPrice != null ? !fixedPrice.equals(that.fixedPrice) : that.fixedPrice != null) {
+            return false;
+        }
+        if (type != that.type) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = type != null ? type.hashCode() : 0;
+        result = 31 * result + (fixedPrice != null ? fixedPrice.hashCode() : 0);
+        return result;
     }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
index ec6e80f..10040ba 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
@@ -21,6 +21,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
 import java.math.BigDecimal;
 import java.net.URI;
+import java.util.Arrays;
 
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.catalog.api.CatalogApiException;
@@ -146,4 +147,26 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
         return true;
     }
 
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultInternationalPrice)) {
+            return false;
+        }
+
+        final DefaultInternationalPrice that = (DefaultInternationalPrice) o;
+
+        if (!Arrays.equals(prices, that.prices)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return prices != null ? Arrays.hashCode(prices) : 0;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
index 4fd6ff9..29390bf 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
@@ -105,4 +105,36 @@ public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements
         this.min = min;
         return this;
     }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultLimit)) {
+            return false;
+        }
+
+        final DefaultLimit that = (DefaultLimit) o;
+
+        if (max != null ? !max.equals(that.max) : that.max != null) {
+            return false;
+        }
+        if (min != null ? !min.equals(that.min) : that.min != null) {
+            return false;
+        }
+        if (unit != null ? !unit.equals(that.unit) : that.unit != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = unit != null ? unit.hashCode() : 0;
+        result = 31 * result + (max != null ? max.hashCode() : 0);
+        result = 31 * result + (min != null ? min.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
index a6a0079..0c1bc14 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -54,7 +54,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
     private String name;
 
     @XmlAttribute(required = false)
-    private Boolean retired = false;
+    private Boolean retired;
 
     //TODO MDW Validation - effectiveDateForExistingSubscriptons > catalog effectiveDate
     @XmlElement(required = false)
@@ -66,7 +66,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
 
     @XmlElementWrapper(name = "initialPhases", required = false)
     @XmlElement(name = "phase", required = true)
-    private DefaultPlanPhase[] initialPhases = new DefaultPlanPhase[0];
+    private DefaultPlanPhase[] initialPhases;
 
     @XmlElement(name = "finalPhase", required = true)
     private DefaultPlanPhase finalPhase;
@@ -79,7 +79,10 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
     private Integer plansAllowedInBundle = 1;
 
 
-    public DefaultPlan() {}
+    public DefaultPlan() {
+        initialPhases = new DefaultPlanPhase[0];
+        retired = false;
+    }
 
     public DefaultPlan(final String planName, final DefaultPlan in, final PlanPhasePriceOverride[] overrides) {
         this.name = planName;
@@ -215,27 +218,27 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
         return errors;
     }
 
-    protected void setEffectiveDateForExistingSubscriptons(
+    public void setEffectiveDateForExistingSubscriptons(
             final Date effectiveDateForExistingSubscriptons) {
         this.effectiveDateForExistingSubscriptons = effectiveDateForExistingSubscriptons;
     }
 
-    protected DefaultPlan setName(final String name) {
+    public DefaultPlan setName(final String name) {
         this.name = name;
         return this;
     }
 
-    protected DefaultPlan setFinalPhase(final DefaultPlanPhase finalPhase) {
+    public DefaultPlan setFinalPhase(final DefaultPlanPhase finalPhase) {
         this.finalPhase = finalPhase;
         return this;
     }
 
-    protected DefaultPlan setProduct(final DefaultProduct product) {
+    public DefaultPlan setProduct(final DefaultProduct product) {
         this.product = product;
         return this;
     }
 
-    protected DefaultPlan setInitialPhases(final DefaultPlanPhase[] phases) {
+    public DefaultPlan setInitialPhases(final DefaultPlanPhase[] phases) {
         this.initialPhases = phases;
         return this;
     }
@@ -272,9 +275,53 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
         return result;
     }
 
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultPlan)) {
+            return false;
+        }
+
+        final DefaultPlan that = (DefaultPlan) o;
+
+        if (effectiveDateForExistingSubscriptons != null ? !effectiveDateForExistingSubscriptons.equals(that.effectiveDateForExistingSubscriptons) : that.effectiveDateForExistingSubscriptons != null) {
+            return false;
+        }
+        if (finalPhase != null ? !finalPhase.equals(that.finalPhase) : that.finalPhase != null) {
+            return false;
+        }
+        if (!Arrays.equals(initialPhases, that.initialPhases)) {
+            return false;
+        }
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (plansAllowedInBundle != null ? !plansAllowedInBundle.equals(that.plansAllowedInBundle) : that.plansAllowedInBundle != null) {
+            return false;
+        }
+        if (product != null ? !product.equals(that.product) : that.product != null) {
+            return false;
+        }
+        if (retired != null ? !retired.equals(that.retired) : that.retired != null) {
+            return false;
+        }
+        return true;
+    }
 
+    @Override
+    public int hashCode() {
+        int result = name != null ? name.hashCode() : 0;
+        result = 31 * result + (retired != null ? retired.hashCode() : 0);
+        result = 31 * result + (effectiveDateForExistingSubscriptons != null ? effectiveDateForExistingSubscriptons.hashCode() : 0);
+        result = 31 * result + (initialPhases != null ? Arrays.hashCode(initialPhases) : 0);
+        result = 31 * result + (finalPhase != null ? finalPhase.hashCode() : 0);
+        result = 31 * result + (plansAllowedInBundle != null ? plansAllowedInBundle.hashCode() : 0);
+        return result;
+    }
 
-	@Override
+    @Override
     public String toString() {
         return "DefaultPlan [name=" + name + ", retired=" + retired + ", effectiveDateForExistingSubscriptons="
                 + effectiveDateForExistingSubscriptons + ", product=" + product + ", initialPhases="
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
index 28499d2..ee2226a 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
@@ -19,6 +19,7 @@
 package org.killbill.billing.catalog;
 
 import java.net.URI;
+import java.util.Arrays;
 
 import javax.annotation.Nullable;
 import javax.xml.bind.annotation.XmlAccessType;
@@ -58,12 +59,14 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
 
     @XmlElementWrapper(name = "usages", required = false)
     @XmlElement(name = "usage", required = false)
-    private DefaultUsage[] usages = new DefaultUsage[0];
+    private DefaultUsage[] usages;
 
     //Not exposed in XML
     private Plan plan;
 
-    public DefaultPlanPhase() {}
+    public DefaultPlanPhase() {
+        usages = new DefaultUsage[0];
+    }
 
     public DefaultPlanPhase(final DefaultPlan parentPlan, final DefaultPlanPhase in, @Nullable final PlanPhasePriceOverride override) {
         this.type = in.getPhaseType();
@@ -154,7 +157,6 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
     public void initialize(final StandaloneCatalog root, final URI uri) {
         if (fixed != null) {
             fixed.initialize(root, uri);
-            fixed.setPhase(this);
         }
         if (recurring != null) {
             recurring.initialize(root, uri);
@@ -169,33 +171,75 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
         }
     }
 
-    protected DefaultPlanPhase setFixed(final DefaultFixed fixed) {
+    public DefaultPlanPhase setFixed(final DefaultFixed fixed) {
         this.fixed = fixed;
         return this;
     }
 
-    protected DefaultPlanPhase setRecurring(final DefaultRecurring recurring) {
+    public DefaultPlanPhase setRecurring(final DefaultRecurring recurring) {
         this.recurring = recurring;
         return this;
     }
 
-    protected DefaultPlanPhase setUsages(final DefaultUsage[] usages) {
+    public DefaultPlanPhase setUsages(final DefaultUsage[] usages) {
         this.usages = usages;
         return this;
     }
 
-    protected DefaultPlanPhase setPhaseType(final PhaseType cohort) {
+    public DefaultPlanPhase setPhaseType(final PhaseType cohort) {
         this.type = cohort;
         return this;
     }
 
-    protected DefaultPlanPhase setDuration(final DefaultDuration duration) {
+    public DefaultPlanPhase setDuration(final DefaultDuration duration) {
         this.duration = duration;
         return this;
     }
 
-    protected DefaultPlanPhase setPlan(final Plan plan) {
+    public DefaultPlanPhase setPlan(final Plan plan) {
         this.plan = plan;
         return this;
     }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultPlanPhase)) {
+            return false;
+        }
+
+        final DefaultPlanPhase that = (DefaultPlanPhase) o;
+
+        if (duration != null ? !duration.equals(that.duration) : that.duration != null) {
+            return false;
+        }
+        if (fixed != null ? !fixed.equals(that.fixed) : that.fixed != null) {
+            return false;
+        }
+        if (recurring != null ? !recurring.equals(that.recurring) : that.recurring != null) {
+            return false;
+        }
+        if (type != that.type) {
+            return false;
+        }
+        /*
+        if (!Arrays.equals(usages, that.usages)) {
+            return false;
+        }
+        */
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = type != null ? type.hashCode() : 0;
+        result = 31 * result + (duration != null ? duration.hashCode() : 0);
+        result = 31 * result + (fixed != null ? fixed.hashCode() : 0);
+        result = 31 * result + (recurring != null ? recurring.hashCode() : 0);
+        //result = 31 * result + (usages != null ? Arrays.hashCode(usages) : 0);
+        return result;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPrice.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPrice.java
index 38f4009..c9e0e96 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPrice.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPrice.java
@@ -79,4 +79,32 @@ public class DefaultPrice extends ValidatingConfig<StandaloneCatalog> implements
         return errors;
 
     }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultPrice)) {
+            return false;
+        }
+
+        final DefaultPrice that = (DefaultPrice) o;
+
+        if (currency != that.currency) {
+            return false;
+        }
+        if (value != null ? !value.equals(that.value) : that.value != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = currency != null ? currency.hashCode() : 0;
+        result = 31 * result + (value != null ? value.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java
index b1ad2ce..c17d6ee 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java
@@ -16,6 +16,8 @@
 
 package org.killbill.billing.catalog;
 
+import java.util.Arrays;
+
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlAttribute;
@@ -111,7 +113,7 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
         return count;
     }
 
-    protected DefaultPriceList setRetired(final boolean retired) {
+    public DefaultPriceList setRetired(final boolean retired) {
         this.retired = retired;
         return this;
     }
@@ -126,5 +128,35 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
         return this;
     }
 
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultPriceList)) {
+            return false;
+        }
 
+        final DefaultPriceList that = (DefaultPriceList) o;
+
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (!Arrays.equals(plans, that.plans)) {
+            return false;
+        }
+        if (retired != null ? !retired.equals(that.retired) : that.retired != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = name != null ? name.hashCode() : 0;
+        result = 31 * result + (retired != null ? retired.hashCode() : 0);
+        result = 31 * result + (plans != null ? Arrays.hashCode(plans) : 0);
+        return result;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceListSet.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceListSet.java
index d10a58c..b1334ae 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceListSet.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceListSet.java
@@ -20,6 +20,7 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import org.killbill.billing.ErrorCode;
@@ -111,5 +112,31 @@ public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> {
         return result;
     }
 
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultPriceListSet)) {
+            return false;
+        }
+
+        final DefaultPriceListSet that = (DefaultPriceListSet) o;
+
+        if (!Arrays.equals(childPriceLists, that.childPriceLists)) {
+            return false;
+        }
+        if (defaultPricelist != null ? !defaultPricelist.equals(that.defaultPricelist) : that.defaultPricelist != null) {
+            return false;
+        }
+
+        return true;
+    }
 
+    @Override
+    public int hashCode() {
+        int result = defaultPricelist != null ? defaultPricelist.hashCode() : 0;
+        result = 31 * result + (childPriceLists != null ? Arrays.hashCode(childPriceLists) : 0);
+        return result;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultProduct.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultProduct.java
index 2a94272..e813ee1 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultProduct.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultProduct.java
@@ -50,16 +50,16 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
     @XmlElementWrapper(name = "included", required = false)
     @XmlIDREF
     @XmlElement(name = "addonProduct", required = true)
-    private DefaultProduct[] included = EMPTY_PRODUCT_LIST;
+    private DefaultProduct[] included;
 
     @XmlElementWrapper(name = "available", required = false)
     @XmlIDREF
     @XmlElement(name = "addonProduct", required = true)
-    private DefaultProduct[] available = EMPTY_PRODUCT_LIST;
+    private DefaultProduct[] available;
 
     @XmlElementWrapper(name = "limits", required = false)
     @XmlElement(name = "limit", required = true)
-    private DefaultLimit[] limits = new DefaultLimit[0];
+    private DefaultLimit[] limits;
 
     //Not included in XML
     private String catalogName;
@@ -90,6 +90,9 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
     }
 
     public DefaultProduct() {
+        included = EMPTY_PRODUCT_LIST;
+        available = EMPTY_PRODUCT_LIST;
+        limits = new DefaultLimit[0];
     }
 
     public DefaultProduct(final String name, final ProductCategory category) {
@@ -158,32 +161,32 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
         return errors;
     }
 
-    protected DefaultProduct setName(final String name) {
+    public DefaultProduct setName(final String name) {
         this.name = name;
         return this;
     }
 
-    protected DefaultProduct setCatagory(final ProductCategory category) {
+    public DefaultProduct setCatagory(final ProductCategory category) {
         this.category = category;
         return this;
     }
 
-    protected DefaultProduct setCategory(final ProductCategory category) {
+    public DefaultProduct setCategory(final ProductCategory category) {
         this.category = category;
         return this;
     }
 
-    protected DefaultProduct setIncluded(final DefaultProduct[] included) {
+    public DefaultProduct setIncluded(final DefaultProduct[] included) {
         this.included = included;
         return this;
     }
 
-    protected DefaultProduct setAvailable(final DefaultProduct[] available) {
+    public DefaultProduct setAvailable(final DefaultProduct[] available) {
         this.available = available;
         return this;
     }
 
-    protected DefaultProduct setCatalogName(final String catalogName) {
+    public DefaultProduct setCatalogName(final String catalogName) {
         this.catalogName = catalogName;
         return this;
     }
@@ -244,7 +247,6 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
         result = 31 * result + (included != null ? Arrays.hashCode(included) : 0);
         result = 31 * result + (available != null ? Arrays.hashCode(available) : 0);
         result = 31 * result + (limits != null ? Arrays.hashCode(limits) : 0);
-        result = 31 * result + (catalogName != null ? catalogName.hashCode() : 0);
         return result;
     }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
index 02b7840..57418b5 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
@@ -111,4 +111,32 @@ public class DefaultRecurring extends ValidatingConfig<StandaloneCatalog> implem
         this.phase = phase;
         return this;
     }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultRecurring)) {
+            return false;
+        }
+
+        final DefaultRecurring that = (DefaultRecurring) o;
+
+        if (billingPeriod != that.billingPeriod) {
+            return false;
+        }
+        if (recurringPrice != null ? !recurringPrice.equals(that.recurringPrice) : that.recurringPrice != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = billingPeriod != null ? billingPeriod.hashCode() : 0;
+        result = 31 * result + (recurringPrice != null ? recurringPrice.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java
index a00ae85..7860f28 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java
@@ -17,6 +17,8 @@
 
 package org.killbill.billing.catalog;
 
+import java.util.Arrays;
+
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
@@ -36,11 +38,11 @@ public class DefaultTier extends ValidatingConfig<StandaloneCatalog> implements 
 
     @XmlElementWrapper(name = "limits", required = false)
     @XmlElement(name = "limit", required = true)
-    private DefaultLimit[] limits = new DefaultLimit[0];
+    private DefaultLimit[] limits;
 
     @XmlElementWrapper(name = "blocks", required = false)
     @XmlElement(name = "tieredBlock", required = true)
-    private DefaultTieredBlock[] blocks = new DefaultTieredBlock[0];
+    private DefaultTieredBlock[] blocks;
 
     // Used to define a fixed price for the whole tier section
     @XmlElement(required = false)
@@ -55,6 +57,11 @@ public class DefaultTier extends ValidatingConfig<StandaloneCatalog> implements 
     private UsageType usageType;
     private PlanPhase phase;
 
+    public DefaultTier() {
+        limits = new DefaultLimit[0];
+        blocks = new DefaultTieredBlock[0];
+    }
+
     @Override
     public DefaultLimit[] getLimits() {
         return limits;
@@ -121,4 +128,49 @@ public class DefaultTier extends ValidatingConfig<StandaloneCatalog> implements 
         validateCollection(catalog, errors, limits);
         return errors;
     }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultTier)) {
+            return false;
+        }
+
+        final DefaultTier that = (DefaultTier) o;
+
+        if (billingMode != that.billingMode) {
+            return false;
+        }
+        if (!Arrays.equals(blocks, that.blocks)) {
+            return false;
+        }
+        if (fixedPrice != null ? !fixedPrice.equals(that.fixedPrice) : that.fixedPrice != null) {
+            return false;
+        }
+        if (!Arrays.equals(limits, that.limits)) {
+            return false;
+        }
+        if (phase != null ? !phase.equals(that.phase) : that.phase != null) {
+            return false;
+        }
+        if (recurringPrice != null ? !recurringPrice.equals(that.recurringPrice) : that.recurringPrice != null) {
+            return false;
+        }
+        if (usageType != that.usageType) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = limits != null ? Arrays.hashCode(limits) : 0;
+        result = 31 * result + (blocks != null ? Arrays.hashCode(blocks) : 0);
+        result = 31 * result + (fixedPrice != null ? fixedPrice.hashCode() : 0);
+        result = 31 * result + (recurringPrice != null ? recurringPrice.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultTieredBlock.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultTieredBlock.java
index 3448c2d..ae39968 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultTieredBlock.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultTieredBlock.java
@@ -49,4 +49,32 @@ public class DefaultTieredBlock extends DefaultBlock implements TieredBlock {
         super.setType(BlockType.TIERED);
         return this;
     }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultTieredBlock)) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        final DefaultTieredBlock that = (DefaultTieredBlock) o;
+
+        if (max != null ? !max.equals(that.max) : that.max != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + (max != null ? max.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java
index 54aeff2..a0b2170 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java
@@ -49,4 +49,27 @@ public class DefaultUnit extends ValidatingConfig<StandaloneCatalog> implements 
         this.name = name;
         return this;
     }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultUnit)) {
+            return false;
+        }
+
+        final DefaultUnit that = (DefaultUnit) o;
+
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return name != null ? name.hashCode() : 0;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java
index fefc3ef..f591cb3 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java
@@ -18,6 +18,7 @@
 package org.killbill.billing.catalog;
 
 import java.net.URI;
+import java.util.Arrays;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -58,17 +59,17 @@ public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements
     // Used for when billing usage IN_ADVANCE & CAPACITY
     @XmlElementWrapper(name = "limits", required = false)
     @XmlElement(name = "limit", required = true)
-    private DefaultLimit[] limits = new DefaultLimit[0];
+    private DefaultLimit[] limits;
 
     // Used for when billing usage IN_ADVANCE & CONSUMABLE
     @XmlElementWrapper(name = "blocks", required = false)
     @XmlElement(name = "block", required = true)
-    private DefaultBlock[] blocks = new DefaultBlock[0];
+    private DefaultBlock[] blocks;
 
     // Used for when billing usage IN_ARREAR
     @XmlElementWrapper(name = "tiers", required = false)
     @XmlElement(name = "tier", required = true)
-    private DefaultTier[] tiers = new DefaultTier[0];
+    private DefaultTier[] tiers;
 
     // Used to define a fixed price for the whole usage section -- bundle several limits/blocks of units.
     @XmlElement(required = false)
@@ -81,6 +82,12 @@ public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements
     // Not exposed in xml.
     private PlanPhase phase;
 
+    public DefaultUsage() {
+        limits = new DefaultLimit[0];
+        blocks = new DefaultBlock[0];
+        tiers = new DefaultTier[0];
+    }
+
     @Override
     public String getName() {
         return name;
@@ -229,4 +236,63 @@ public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements
         }
         return null;
     }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultUsage)) {
+            return false;
+        }
+
+        final DefaultUsage that = (DefaultUsage) o;
+
+        if (billingMode != that.billingMode) {
+            return false;
+        }
+        if (billingPeriod != that.billingPeriod) {
+            return false;
+        }
+        if (!Arrays.equals(blocks, that.blocks)) {
+            return false;
+        }
+        if (fixedPrice != null ? !fixedPrice.equals(that.fixedPrice) : that.fixedPrice != null) {
+            return false;
+        }
+        if (!Arrays.equals(limits, that.limits)) {
+            return false;
+        }
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (phase != null ? !phase.equals(that.phase) : that.phase != null) {
+            return false;
+        }
+        if (recurringPrice != null ? !recurringPrice.equals(that.recurringPrice) : that.recurringPrice != null) {
+            return false;
+        }
+        if (!Arrays.equals(tiers, that.tiers)) {
+            return false;
+        }
+        if (usageType != that.usageType) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = name != null ? name.hashCode() : 0;
+        result = 31 * result + (billingMode != null ? billingMode.hashCode() : 0);
+        result = 31 * result + (usageType != null ? usageType.hashCode() : 0);
+        result = 31 * result + (billingPeriod != null ? billingPeriod.hashCode() : 0);
+        result = 31 * result + (limits != null ? Arrays.hashCode(limits) : 0);
+        result = 31 * result + (blocks != null ? Arrays.hashCode(blocks) : 0);
+        result = 31 * result + (tiers != null ? Arrays.hashCode(tiers) : 0);
+        result = 31 * result + (fixedPrice != null ? fixedPrice.hashCode() : 0);
+        result = 31 * result + (recurringPrice != null ? recurringPrice.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java b/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
index 4e93f86..7651aad 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
@@ -33,12 +33,16 @@ import org.killbill.billing.catalog.io.CatalogLoader;
 import org.killbill.billing.catalog.io.VersionedCatalogLoader;
 import org.killbill.billing.catalog.override.DefaultPriceOverride;
 import org.killbill.billing.catalog.override.PriceOverride;
+import org.killbill.billing.catalog.plugin.VersionedCatalogMapper;
+import org.killbill.billing.catalog.plugin.api.CatalogPluginApi;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.tenant.api.TenantInternalApi.CacheInvalidationCallback;
 import org.killbill.billing.util.config.CatalogConfig;
 import org.killbill.billing.util.glue.KillBillModule;
 import org.skife.config.ConfigurationObjectFactory;
 
+import com.google.inject.TypeLiteral;
 import com.google.inject.name.Names;
 
 public class CatalogModule extends KillBillModule {
@@ -75,6 +79,10 @@ public class CatalogModule extends KillBillModule {
         bind(OverriddenPlanCache.class).to(EhCacheOverriddenPlanCache.class).asEagerSingleton();
     }
 
+    protected void installCatalogPluginApi() {
+        bind(new TypeLiteral<OSGIServiceRegistration<CatalogPluginApi>>() {}).toProvider(DefaultCatalogProviderPluginRegistryProvider.class).asEagerSingleton();
+        bind(VersionedCatalogMapper.class).asEagerSingleton();
+    }
 
 
     @Override
@@ -84,5 +92,6 @@ public class CatalogModule extends KillBillModule {
         installCatalog();
         installCatalogUserApi();
         installCatalogConfigCache();
+        installCatalogPluginApi();
     }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/glue/DefaultCatalogProviderPluginRegistryProvider.java b/catalog/src/main/java/org/killbill/billing/catalog/glue/DefaultCatalogProviderPluginRegistryProvider.java
new file mode 100644
index 0000000..7db4e63
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/glue/DefaultCatalogProviderPluginRegistryProvider.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.glue;
+
+import org.killbill.billing.catalog.plugin.api.CatalogPluginApi;
+import org.killbill.billing.catalog.provider.DefaultCatalogProviderPluginRegistry;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class DefaultCatalogProviderPluginRegistryProvider implements Provider<OSGIServiceRegistration<CatalogPluginApi>> {
+
+    @Inject
+    public DefaultCatalogProviderPluginRegistryProvider() {
+    }
+
+    @Override
+    public OSGIServiceRegistration<CatalogPluginApi> get() {
+        final DefaultCatalogProviderPluginRegistry pluginRegistry = new DefaultCatalogProviderPluginRegistry();
+        return pluginRegistry;
+    }
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java b/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
new file mode 100644
index 0000000..e70f9d1
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
@@ -0,0 +1,520 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.catalog.DefaultDuration;
+import org.killbill.billing.catalog.DefaultFixed;
+import org.killbill.billing.catalog.DefaultInternationalPrice;
+import org.killbill.billing.catalog.DefaultPlan;
+import org.killbill.billing.catalog.DefaultPlanPhase;
+import org.killbill.billing.catalog.DefaultPrice;
+import org.killbill.billing.catalog.DefaultPriceList;
+import org.killbill.billing.catalog.DefaultPriceListSet;
+import org.killbill.billing.catalog.DefaultProduct;
+import org.killbill.billing.catalog.DefaultRecurring;
+import org.killbill.billing.catalog.DefaultUnit;
+import org.killbill.billing.catalog.DefaultUsage;
+import org.killbill.billing.catalog.PriceListDefault;
+import org.killbill.billing.catalog.StandaloneCatalog;
+import org.killbill.billing.catalog.api.BillingMode;
+import org.killbill.billing.catalog.api.CurrencyValueNull;
+import org.killbill.billing.catalog.api.Duration;
+import org.killbill.billing.catalog.api.Fixed;
+import org.killbill.billing.catalog.api.InternationalPrice;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.Price;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.Recurring;
+import org.killbill.billing.catalog.api.Unit;
+import org.killbill.billing.catalog.api.rules.Case;
+import org.killbill.billing.catalog.api.rules.CaseBillingAlignment;
+import org.killbill.billing.catalog.api.rules.CaseCancelPolicy;
+import org.killbill.billing.catalog.api.rules.CaseChange;
+import org.killbill.billing.catalog.api.rules.CaseChangePlanAlignment;
+import org.killbill.billing.catalog.api.rules.CaseChangePlanPolicy;
+import org.killbill.billing.catalog.api.rules.CaseCreateAlignment;
+import org.killbill.billing.catalog.api.rules.CasePhase;
+import org.killbill.billing.catalog.api.rules.CasePriceList;
+import org.killbill.billing.catalog.api.rules.PlanRules;
+import org.killbill.billing.catalog.plugin.api.StandalonePluginCatalog;
+import org.killbill.billing.catalog.rules.DefaultCaseBillingAlignment;
+import org.killbill.billing.catalog.rules.DefaultCaseCancelPolicy;
+import org.killbill.billing.catalog.rules.DefaultCaseChange;
+import org.killbill.billing.catalog.rules.DefaultCaseChangePlanAlignment;
+import org.killbill.billing.catalog.rules.DefaultCaseChangePlanPolicy;
+import org.killbill.billing.catalog.rules.DefaultCaseCreateAlignment;
+import org.killbill.billing.catalog.rules.DefaultCasePhase;
+import org.killbill.billing.catalog.rules.DefaultCasePriceList;
+import org.killbill.billing.catalog.rules.DefaultCaseStandardNaming;
+import org.killbill.billing.catalog.rules.DefaultPlanRules;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class StandaloneCatalogMapper {
+
+    private final String catalogName;
+    private final BillingMode recurringBillingMode;
+
+    private Iterable<DefaultProduct> tmpDefaultProducts;
+    private Iterable<DefaultPlan> tmpDefaultPlans;
+    private DefaultPriceListSet tmpDefaultPriceListSet;
+
+    public StandaloneCatalogMapper(final String catalogName, final BillingMode recurringBillingMode) {
+        this.catalogName = catalogName;
+        this.recurringBillingMode = recurringBillingMode;
+        this.tmpDefaultProducts = null;
+        this.tmpDefaultPlans = null;
+    }
+
+    public StandaloneCatalog toStandaloneCatalog(final StandalonePluginCatalog pluginCatalog, @Nullable URI catalogURI) {
+        final StandaloneCatalog result = new StandaloneCatalog();
+        result.setCatalogName(catalogName);
+        result.setEffectiveDate(pluginCatalog.getEffectiveDate().toDate());
+        result.setProducts(toDefaultProducts(pluginCatalog.getProducts()));
+        result.setPlans(toDefaultPlans(pluginCatalog.getPlans()));
+        result.setPriceLists(toDefaultPriceListSet(pluginCatalog.getDefaultPriceList(), pluginCatalog.getChildrenPriceList()));
+        result.setRecurringBillingMode(recurringBillingMode);
+        result.setSupportedCurrencies(toArray(pluginCatalog.getCurrencies()));
+        result.setUnits(toDefaultUnits(pluginCatalog.getUnits()));
+        result.setPlanRules(toDefaultPlanRules(pluginCatalog.getPlanRules()));
+
+        for (final Product cur : pluginCatalog.getProducts()) {
+            for (DefaultProduct target : result.getCurrentProducts()) {
+                if (target.getName().equals(cur.getName())) {
+                    target.setAvailable(toFilteredDefaultProduct(ImmutableList.copyOf(cur.getAvailable())));
+                    target.setIncluded(toFilteredDefaultProduct(ImmutableList.copyOf(cur.getIncluded())));
+                    break;
+                }
+            }
+        }
+        result.initialize(result, catalogURI);
+        return result;
+    }
+
+
+    private DefaultPlanRules toDefaultPlanRules(final PlanRules input) {
+        final DefaultPlanRules result = new DefaultPlanRules();
+        result.setBillingAlignmentCase(toDefaultCaseBillingAlignments(input.getCaseBillingAlignment()));
+        result.setCancelCase(toDefaultCaseCancelPolicys(input.getCaseCancelPolicy()));
+        result.setChangeAlignmentCase(toDefaultCaseChangePlanAlignments(input.getCaseChangePlanAlignment()));
+        result.setChangeCase(toDefaultCaseChangePlanPolicies(input.getCaseChangePlanPolicy()));
+        result.setCreateAlignmentCase(toDefaultCaseCreateAlignments(input.getCaseCreateAlignment()));
+        result.setPriceListCase(toDefaultCasePriceLists(input.getCasePriceList()));
+        return result;
+    }
+
+    final DefaultCaseChangePlanPolicy[] toDefaultCaseChangePlanPolicies(final Iterable<CaseChangePlanPolicy> input) {
+        return toArrayWithTransform(input, new Function<CaseChangePlanPolicy, DefaultCaseChangePlanPolicy>() {
+            @Override
+            public DefaultCaseChangePlanPolicy apply(final CaseChangePlanPolicy input) {
+                return toDefaultCaseChangePlanPolicy(input);
+            }
+        }, true);
+    }
+
+    final DefaultCaseChangePlanAlignment[] toDefaultCaseChangePlanAlignments(final Iterable<CaseChangePlanAlignment> input) {
+        return toArrayWithTransform(input, new Function<CaseChangePlanAlignment, DefaultCaseChangePlanAlignment>() {
+            @Override
+            public DefaultCaseChangePlanAlignment apply(final CaseChangePlanAlignment input) {
+                return toDefaultCaseChangePlanAlignment(input);
+            }
+        }, true);
+    }
+
+    final DefaultCaseBillingAlignment[] toDefaultCaseBillingAlignments(final Iterable<CaseBillingAlignment> input) {
+        return toArrayWithTransform(input, new Function<CaseBillingAlignment, DefaultCaseBillingAlignment>() {
+            @Override
+            public DefaultCaseBillingAlignment apply(final CaseBillingAlignment input) {
+                return toDefaultCaseBillingAlignment(input);
+            }
+        }, true);
+    }
+
+    final DefaultCaseCancelPolicy[] toDefaultCaseCancelPolicys(final Iterable<CaseCancelPolicy> input) {
+        return toArrayWithTransform(input, new Function<CaseCancelPolicy, DefaultCaseCancelPolicy>() {
+            @Override
+            public DefaultCaseCancelPolicy apply(final CaseCancelPolicy input) {
+                return toDefaultCaseCancelPolicy(input);
+            }
+        }, true);
+    }
+
+    final DefaultCaseCreateAlignment[] toDefaultCaseCreateAlignments(final Iterable<CaseCreateAlignment> input) {
+        return toArrayWithTransform(input, new Function<CaseCreateAlignment, DefaultCaseCreateAlignment>() {
+            @Override
+            public DefaultCaseCreateAlignment apply(final CaseCreateAlignment input) {
+                return toCaseCreateAlignment(input);
+            }
+        }, true);
+    }
+
+    final DefaultCasePriceList[] toDefaultCasePriceLists(final Iterable<CasePriceList> input) {
+        return toArrayWithTransform(input, new Function<CasePriceList, DefaultCasePriceList>() {
+            @Override
+            public DefaultCasePriceList apply(final CasePriceList input) {
+                return toDefaultCasePriceList(input);
+            }
+        }, true);
+    }
+
+    final DefaultCasePriceList toDefaultCasePriceList(final CasePriceList input) {
+        final DefaultCasePriceList result = new DefaultCasePriceList();
+        result.setToPriceList(toDefaultPriceList(input.getDestinationPriceList()));
+        populateDefaultCase(input, result);
+        return result;
+    }
+
+    final DefaultCaseCreateAlignment toCaseCreateAlignment(final CaseCreateAlignment input) {
+        final DefaultCaseCreateAlignment result = new DefaultCaseCreateAlignment();
+        result.setAlignment(input.getPlanAlignmentCreate());
+        populateDefaultCase(input, result);
+        return result;
+    }
+
+    final DefaultCaseBillingAlignment toDefaultCaseBillingAlignment(final CaseBillingAlignment input) {
+        final DefaultCaseBillingAlignment result = new DefaultCaseBillingAlignment();
+        result.setAlignment(input.getBillingAlignment());
+        populateDefaultCasePhase(input, result);
+        return result;
+    }
+
+    final DefaultCaseCancelPolicy toDefaultCaseCancelPolicy(final CaseCancelPolicy input) {
+        final DefaultCaseCancelPolicy result = new DefaultCaseCancelPolicy();
+        result.setPolicy(input.getBillingActionPolicy());
+        populateDefaultCasePhase(input, result);
+        return result;
+    }
+
+    final void populateDefaultCasePhase(final CasePhase input, final DefaultCasePhase result) {
+        result.setPhaseType(input.getPhaseType());
+        populateDefaultCase(input, result);
+    }
+
+    final void populateDefaultCase(final Case input, final DefaultCaseStandardNaming result) {
+        result.setBillingPeriod(input.getBillingPeriod());
+        result.setPriceList(toDefaultPriceList(input.getPriceList()));
+        result.setProduct(toDefaultProduct(input.getProduct()));
+        result.setProductCategory(input.getProductCategory());
+    }
+
+    final DefaultCaseChangePlanPolicy toDefaultCaseChangePlanPolicy(final CaseChangePlanPolicy input) {
+        final DefaultCaseChangePlanPolicy result = new DefaultCaseChangePlanPolicy();
+        result.setPolicy(input.getBillingActionPolicy());
+        populateDefaultCaseChange(input, result);
+        return result;
+    }
+
+    final DefaultCaseChangePlanAlignment toDefaultCaseChangePlanAlignment(final CaseChangePlanAlignment input) {
+        final DefaultCaseChangePlanAlignment result = new DefaultCaseChangePlanAlignment();
+        result.setAlignment(input.getAlignment());
+        populateDefaultCaseChange(input, result);
+        return result;
+    }
+
+    final void populateDefaultCaseChange(final CaseChange input, final DefaultCaseChange result) {
+        result.setPhaseType(input.getPhaseType());
+        result.setFromBillingPeriod(input.getFromBillingPeriod());
+        result.setFromPriceList(toDefaultPriceList(input.getFromPriceList()));
+        result.setFromProduct(toDefaultProduct(input.getFromProduct()));
+        result.setFromProductCategory(input.getFromProductCategory());
+        result.setToBillingPeriod(input.getToBillingPeriod());
+        result.setToPriceList(toDefaultPriceList(input.getToPriceList()));
+        result.setToProduct(toDefaultProduct(input.getToProduct()));
+        result.setToProductCategory(input.getToProductCategory());
+    }
+
+
+    private DefaultProduct[] toDefaultProducts(final Iterable<Product> input) {
+        if (tmpDefaultProducts == null) {
+            final Function<Product, DefaultProduct> productTransformer = new Function<Product, DefaultProduct>() {
+                @Override
+                public DefaultProduct apply(final Product input) {
+                    return toDefaultProduct(input);
+                }
+            };
+            tmpDefaultProducts = ImmutableList.copyOf(Iterables.transform(input, productTransformer));
+        }
+        return toArray(tmpDefaultProducts);
+    }
+
+    private DefaultProduct[] toFilteredDefaultProduct(final Iterable<Product> input) {
+        if (!input.iterator().hasNext()) {
+            return new DefaultProduct[0];
+        }
+        final List<String> inputProductNames = ImmutableList.copyOf(Iterables.transform(input, new Function<Product, String>() {
+            @Override
+            public String apply(final Product input) {
+                return input.getName();
+            }
+        }));
+        final List<DefaultProduct> filteredAndOrdered = new ArrayList<DefaultProduct>(inputProductNames.size());
+        for (final String cur : inputProductNames) {
+            final DefaultProduct found = findOrIllegalState(tmpDefaultProducts, new Predicate<DefaultProduct>() {
+                @Override
+                public boolean apply(final DefaultProduct inputPredicate) {
+                    return inputPredicate.getName().equals(cur);
+                }
+            }, "Failed to find product " + cur);
+            filteredAndOrdered.add(found);
+        }
+        return toArray(filteredAndOrdered);
+    }
+
+    private DefaultPlan[] toDefaultPlans(final Iterable<Plan> input) {
+        if (tmpDefaultPlans == null) {
+            final Function<Plan, DefaultPlan> planTransformer = new Function<Plan, DefaultPlan>() {
+                @Override
+                public DefaultPlan apply(final Plan input) {
+                    return toDefaultPlan(input);
+                }
+            };
+            tmpDefaultPlans = ImmutableList.copyOf(Iterables.transform(input, planTransformer));
+        }
+        return toArray(tmpDefaultPlans);
+    }
+
+    private DefaultPlan[] toFilterDefaultPlans(final Iterable<Plan> input) {
+        if (tmpDefaultPlans == null) {
+            throw new IllegalStateException("Cannot filter on uninitialized plans");
+        }
+        final List<String> inputPlanNames = ImmutableList.copyOf(Iterables.transform(input, new Function<Plan, String>() {
+            @Override
+            public String apply(final Plan input) {
+                return input.getName();
+            }
+        }));
+        final List<DefaultPlan> filteredAndOrdered = new ArrayList<DefaultPlan>(inputPlanNames.size());
+        for (final String cur : inputPlanNames) {
+            final DefaultPlan found = findOrIllegalState(tmpDefaultPlans, new Predicate<DefaultPlan>() {
+                @Override
+                public boolean apply(final DefaultPlan inputPredicate) {
+                    return inputPredicate.getName().equals(cur);
+                }
+            }, "Failed to find plan " + cur);
+            filteredAndOrdered.add(found);
+        }
+        return toArray(filteredAndOrdered);
+    }
+
+    private DefaultPriceListSet toDefaultPriceListSet(final PriceList defaultPriceList, final Iterable<PriceList> childrenPriceLists) {
+        if (tmpDefaultPriceListSet == null) {
+            tmpDefaultPriceListSet = new DefaultPriceListSet(toPriceListDefault(defaultPriceList), toDefaultPriceLists(childrenPriceLists));
+        }
+        return tmpDefaultPriceListSet;
+    }
+
+    private DefaultPlanPhase[] toDefaultPlanPhases(final Iterable<PlanPhase> input) {
+        if (!input.iterator().hasNext()) {
+            return new DefaultPlanPhase[0];
+        }
+        return toArrayWithTransform(input, new Function<PlanPhase, DefaultPlanPhase>() {
+            @Override
+            public DefaultPlanPhase apply(final PlanPhase input) {
+                return toDefaultPlanPhase(input);
+            }
+        }, false);
+    }
+
+    private DefaultPriceList[] toDefaultPriceLists(final Iterable<PriceList> input) {
+        return toArrayWithTransform(input, new Function<PriceList, DefaultPriceList>() {
+            @Override
+            public DefaultPriceList apply(final PriceList input) {
+                return toDefaultPriceList(input);
+            }
+        }, false);
+    }
+
+    private DefaultPrice[] toDefaultPrices(final Iterable<Price> input) {
+        return toArrayWithTransform(input, new Function<Price, DefaultPrice>() {
+            @Override
+            public DefaultPrice apply(final Price input) {
+                return toDefaultPrice(input);
+            }
+        }, false);
+    }
+
+    private DefaultUnit[] toDefaultUnits(final Iterable<Unit> input) {
+        return toArrayWithTransform(input, new Function<Unit, DefaultUnit>() {
+            @Override
+            public DefaultUnit apply(final Unit inputTransform) {
+                return toDefaultUnit(inputTransform);
+            }
+        }, true);
+    }
+
+    private DefaultUnit toDefaultUnit(final Unit input) {
+        final DefaultUnit result = new DefaultUnit();
+        result.setName(input.getName());
+        return result;
+    }
+
+
+    private DefaultPriceList toDefaultPriceList(@Nullable final PriceList input) {
+        if (input == null) {
+            return null;
+        }
+        final DefaultPriceList result = new DefaultPriceList();
+        result.setName(input.getName());
+        result.setPlans(toFilterDefaultPlans(ImmutableList.copyOf(input.getPlans())));
+        result.setRetired(input.isRetired());
+        return result;
+    }
+
+    private PriceListDefault toPriceListDefault(@Nullable final PriceList input) {
+        if (input == null) {
+            return null;
+        }
+        final PriceListDefault result = new PriceListDefault();
+        result.setName(input.getName());
+        result.setPlans(toFilterDefaultPlans(ImmutableList.copyOf(input.getPlans())));
+        result.setRetired(input.isRetired());
+        return result;
+    }
+
+    private DefaultProduct toDefaultProduct(@Nullable final Product input) {
+        if (input == null) {
+            return null;
+        }
+        if (tmpDefaultProducts != null) {
+            final DefaultProduct existingProduct = findOrIllegalState(tmpDefaultProducts, new Predicate<DefaultProduct>() {
+                @Override
+                public boolean apply(final DefaultProduct predicateInput) {
+                    return predicateInput.getName().equals(input.getName());
+                }
+            }, "Unknown product " + input.getName());
+            return existingProduct;
+        }
+        final DefaultProduct result = new DefaultProduct();
+        result.setCatalogName(catalogName);
+        result.setCatagory(input.getCategory());
+        result.setName(input.getName());
+        result.setRetired(input.isRetired());
+        return result;
+    }
+
+    private DefaultPlan toDefaultPlan(final Plan input) {
+        if (tmpDefaultPlans != null) {
+            final DefaultPlan existingPlan = findOrIllegalState(tmpDefaultPlans, new Predicate<DefaultPlan>() {
+                @Override
+                public boolean apply(final DefaultPlan predicateInput) {
+                    return predicateInput.getName().equals(input.getName());
+                }
+            }, "Unknown plan " + input.getName());
+            return existingPlan;
+        }
+        final DefaultPlan result = new DefaultPlan();
+        result.setName(input.getName());
+        result.setRetired(input.isRetired());
+        result.setEffectiveDateForExistingSubscriptons(input.getEffectiveDateForExistingSubscriptons());
+        result.setFinalPhase(toDefaultPlanPhase(input.getFinalPhase()));
+        result.setInitialPhases(toDefaultPlanPhases(ImmutableList.copyOf(input.getInitialPhases())));
+        result.setPlansAllowedInBundle(input.getPlansAllowedInBundle());
+        result.setProduct(toDefaultProduct(input.getProduct()));
+        return result;
+    }
+
+    private DefaultPlanPhase toDefaultPlanPhase(final PlanPhase input) {
+        final DefaultPlanPhase result = new DefaultPlanPhase();
+        result.setDuration(toDefaultDuration(input.getDuration()));
+        result.setFixed(toDefaultFixed(input.getFixed()));
+        result.setPhaseType(input.getPhaseType());
+        result.setRecurring(toDefaultRecurring(input.getRecurring()));
+        result.setUsages(new DefaultUsage[0]);
+        return result;
+    }
+
+    private DefaultRecurring toDefaultRecurring(final Recurring input) {
+        DefaultRecurring result = null;
+        if (input != null) {
+            result = new DefaultRecurring();
+            result.setBillingPeriod(input.getBillingPeriod());
+            result.setRecurringPrice(toDefaultInternationalPrice(input.getRecurringPrice()));
+        }
+        return result;
+    }
+
+    private final DefaultDuration toDefaultDuration(final Duration input) {
+        final DefaultDuration result = new DefaultDuration();
+        result.setNumber(input.getNumber());
+        result.setUnit(input.getUnit());
+        return result;
+    }
+
+    private final DefaultFixed toDefaultFixed(@Nullable final Fixed input) {
+        DefaultFixed result = null;
+        if (input != null) {
+            result = new DefaultFixed();
+            result.setFixedPrice(toDefaultInternationalPrice(input.getPrice()));
+            result.setType(input.getType());
+        }
+        return result;
+    }
+
+    private DefaultInternationalPrice toDefaultInternationalPrice(final InternationalPrice input) {
+        final DefaultInternationalPrice result = new DefaultInternationalPrice();
+        result.setPrices(toDefaultPrices(ImmutableList.copyOf(input.getPrices())));
+        return result;
+    }
+
+    private DefaultPrice toDefaultPrice(final Price input) {
+        try {
+            final DefaultPrice result = new DefaultPrice();
+            result.setCurrency(input.getCurrency());
+            result.setValue(input.getValue());
+            return result;
+        } catch (CurrencyValueNull currencyValueNull) {
+            throw new IllegalStateException(currencyValueNull);
+        }
+    }
+
+    private <I, C extends I> C[] toArrayWithTransform(final Iterable<I> input, final Function<I, C> transformer, boolean returnNullIfNothing) {
+        if (returnNullIfNothing && (input == null || !input.iterator().hasNext())) {
+            return null;
+        }
+        final Iterable<C> tmp = Iterables.transform(input, transformer);
+        return toArray(tmp);
+    }
+
+    private <C> C[] toArray(final Iterable<C> input) {
+        if (!input.iterator().hasNext()) {
+            throw new IllegalStateException("Nothing to convert into array");
+        }
+        final C[] foo = (C[]) java.lang.reflect.Array
+                .newInstance(input.iterator().next().getClass(), 1);
+        return ImmutableList.<C>copyOf(input).toArray(foo);
+    }
+
+    private <T> T findOrIllegalState(final Iterable<T> input, final Predicate<T> predicate, final String msg) {
+        T result = Iterables.<T> tryFind(input, predicate).orNull();
+        if (result == null) {
+            throw new IllegalStateException(msg);
+        }
+        return result;
+    }
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/plugin/VersionedCatalogMapper.java b/catalog/src/main/java/org/killbill/billing/catalog/plugin/VersionedCatalogMapper.java
new file mode 100644
index 0000000..39ec3fa
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/plugin/VersionedCatalogMapper.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.StandaloneCatalog;
+import org.killbill.billing.catalog.StandaloneCatalogWithPriceOverride;
+import org.killbill.billing.catalog.VersionedCatalog;
+import org.killbill.billing.catalog.override.PriceOverride;
+import org.killbill.billing.catalog.plugin.api.StandalonePluginCatalog;
+import org.killbill.billing.catalog.plugin.api.VersionedPluginCatalog;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.clock.Clock;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class VersionedCatalogMapper {
+
+    private final Clock clock;
+
+    private final PriceOverride priceOverride;
+    private final InternalCallContextFactory internalCallContextFactory;
+
+    @Inject
+    public VersionedCatalogMapper(final Clock clock, final PriceOverride priceOverride, final InternalCallContextFactory internalCallContextFactory) {
+        this.clock = clock;
+        this.priceOverride = priceOverride;
+        this.internalCallContextFactory = internalCallContextFactory;
+    }
+
+    public VersionedCatalog toVersionedCatalog(final VersionedPluginCatalog pluginCatalog, final InternalTenantContext internalTenantContext) {
+        final VersionedCatalog result = new VersionedCatalog(clock, pluginCatalog.getCatalogName(), pluginCatalog.getRecurringBillingMode(), toStandaloneCatalogWithPriceOverrideList(pluginCatalog, internalTenantContext), internalTenantContext);
+        return result;
+    }
+
+    private List<StandaloneCatalogWithPriceOverride> toStandaloneCatalogWithPriceOverrideList(final VersionedPluginCatalog pluginCatalog, final InternalTenantContext internalTenantContext) {
+        return ImmutableList.copyOf(Iterables.transform(pluginCatalog.getStandalonePluginCatalogs(), new Function<StandalonePluginCatalog, StandaloneCatalogWithPriceOverride>() {
+            @Override
+            public StandaloneCatalogWithPriceOverride apply(final StandalonePluginCatalog input) {
+                return toStandaloneCatalogWithPriceOverride(pluginCatalog, input, internalTenantContext);
+            }
+        }));
+    }
+
+    private StandaloneCatalogWithPriceOverride toStandaloneCatalogWithPriceOverride(final VersionedPluginCatalog pluginCatalog, final StandalonePluginCatalog input, final InternalTenantContext internalTenantContext) {
+        final StandaloneCatalogMapper mapper = new StandaloneCatalogMapper(pluginCatalog.getCatalogName(), pluginCatalog.getRecurringBillingMode());
+        final StandaloneCatalog standaloneCatalog = mapper.toStandaloneCatalog(input, null);
+        final StandaloneCatalogWithPriceOverride result = new StandaloneCatalogWithPriceOverride(standaloneCatalog, priceOverride, internalTenantContext.getTenantRecordId(), internalCallContextFactory);
+        return result;
+    }
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/PriceListDefault.java b/catalog/src/main/java/org/killbill/billing/catalog/PriceListDefault.java
index 14f54b2..bc25842 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/PriceListDefault.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/PriceListDefault.java
@@ -49,4 +49,5 @@ public class PriceListDefault extends DefaultPriceList {
         return PriceListSet.DEFAULT_PRICELIST_NAME;
     }
 
+
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/provider/DefaultCatalogProviderPluginRegistry.java b/catalog/src/main/java/org/killbill/billing/catalog/provider/DefaultCatalogProviderPluginRegistry.java
new file mode 100644
index 0000000..e7e4005
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/provider/DefaultCatalogProviderPluginRegistry.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.provider;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.inject.Inject;
+
+import org.killbill.billing.catalog.plugin.api.CatalogPluginApi;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultCatalogProviderPluginRegistry implements OSGIServiceRegistration<CatalogPluginApi> {
+
+    private final static Logger log = LoggerFactory.getLogger(DefaultCatalogProviderPluginRegistry.class);
+
+    private final Map<String, CatalogPluginApi> pluginsByName = new ConcurrentHashMap<String, CatalogPluginApi>();
+
+    @Inject
+    public DefaultCatalogProviderPluginRegistry() {
+    }
+
+    @Override
+    public void registerService(final OSGIServiceDescriptor desc, final CatalogPluginApi service) {
+        log.info("DefaultInvoiceProviderPluginRegistry registering service " + desc.getRegistrationName());
+        pluginsByName.put(desc.getRegistrationName(), service);
+    }
+
+    @Override
+    public void unregisterService(final String serviceName) {
+        log.info("DefaultInvoiceProviderPluginRegistry unregistering service " + serviceName);
+        pluginsByName.remove(serviceName);
+    }
+
+    @Override
+    public CatalogPluginApi getServiceForName(final String serviceName) {
+        if (serviceName == null) {
+            throw new IllegalArgumentException("Null catalog plugin API name");
+        }
+        final CatalogPluginApi plugin = pluginsByName.get(serviceName);
+        return plugin;
+    }
+
+    @Override
+    public Set<String> getAllServices() {
+        return pluginsByName.keySet();
+    }
+
+    @Override
+    public Class<CatalogPluginApi> getServiceType() {
+        return CatalogPluginApi.class;
+    }
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
index 0acb75d..93dc2c7 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
@@ -18,6 +18,7 @@ package org.killbill.billing.catalog;
 
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 
@@ -41,7 +42,6 @@ import org.killbill.billing.catalog.api.PlanAlignmentChange;
 import org.killbill.billing.catalog.api.PlanAlignmentCreate;
 import org.killbill.billing.catalog.api.PlanChangeResult;
 import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
@@ -49,12 +49,10 @@ import org.killbill.billing.catalog.api.PriceList;
 import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.catalog.api.StaticCatalog;
-import org.killbill.billing.catalog.rules.PlanRules;
+import org.killbill.billing.catalog.rules.DefaultPlanRules;
 import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
 
-import com.google.common.collect.ImmutableList;
-
 @XmlRootElement(name = "catalog")
 @XmlAccessorType(XmlAccessType.NONE)
 public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> implements StaticCatalog {
@@ -81,7 +79,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
     private DefaultProduct[] products;
 
     @XmlElement(name = "rules", required = true)
-    private PlanRules planRules;
+    private DefaultPlanRules planRules;
 
     @XmlElementWrapper(name = "plans", required = true)
     @XmlElement(name = "plan", required = true)
@@ -147,7 +145,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return catalogURI;
     }
 
-    public PlanRules getPlanRules() {
+    public DefaultPlanRules getPlanRules() {
         return planRules;
     }
 
@@ -298,22 +296,17 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return phase.compliesWithLimits(unit, value);
     }
 
-    protected StandaloneCatalog setProducts(final DefaultProduct[] products) {
+    public StandaloneCatalog setProducts(final DefaultProduct[] products) {
         this.products = products;
         return this;
     }
 
-    protected StandaloneCatalog setSupportedCurrencies(final Currency[] supportedCurrencies) {
+    public StandaloneCatalog setSupportedCurrencies(final Currency[] supportedCurrencies) {
         this.supportedCurrencies = supportedCurrencies;
         return this;
     }
 
-    protected StandaloneCatalog setPlanChangeRules(final PlanRules planChangeRules) {
-        this.planRules = planChangeRules;
-        return this;
-    }
-
-    protected StandaloneCatalog setPlans(final DefaultPlan[] plans) {
+    public StandaloneCatalog setPlans(final DefaultPlan[] plans) {
         this.plans = plans;
         return this;
     }
@@ -323,7 +316,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return this;
     }
 
-    protected StandaloneCatalog setEffectiveDate(final Date effectiveDate) {
+    public StandaloneCatalog setEffectiveDate(final Date effectiveDate) {
         this.effectiveDate = effectiveDate;
         return this;
     }
@@ -333,16 +326,21 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return this;
     }
 
-    protected StandaloneCatalog setPlanRules(final PlanRules planRules) {
+    public StandaloneCatalog setPlanRules(final DefaultPlanRules planRules) {
         this.planRules = planRules;
         return this;
     }
 
-    protected StandaloneCatalog setPriceLists(final DefaultPriceListSet priceLists) {
+    public StandaloneCatalog setPriceLists(final DefaultPriceListSet priceLists) {
         this.priceLists = priceLists;
         return this;
     }
 
+    public StandaloneCatalog setUnits(final DefaultUnit[] units) {
+        this.units = units;
+        return this;
+    }
+
     @Override
     public boolean canCreatePlan(final PlanSpecifier specifier) throws CatalogApiException {
         final Product product = findCurrentProduct(specifier.getProductName());
@@ -399,4 +397,64 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         }
         return availBasePlans;
     }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof StandaloneCatalog)) {
+            return false;
+        }
+
+        final StandaloneCatalog that = (StandaloneCatalog) o;
+
+        if (catalogName != null ? !catalogName.equals(that.catalogName) : that.catalogName != null) {
+            return false;
+        }
+        if (catalogURI != null ? !catalogURI.equals(that.catalogURI) : that.catalogURI != null) {
+            return false;
+        }
+        if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
+            return false;
+        }
+        if (planRules != null ? !planRules.equals(that.planRules) : that.planRules != null) {
+            return false;
+        }
+        if (!Arrays.equals(plans, that.plans)) {
+            return false;
+        }
+        if (priceLists != null ? !priceLists.equals(that.priceLists) : that.priceLists != null) {
+            return false;
+        }
+        if (!Arrays.equals(products, that.products)) {
+            return false;
+        }
+        if (recurringBillingMode != that.recurringBillingMode) {
+            return false;
+        }
+        if (!Arrays.equals(supportedCurrencies, that.supportedCurrencies)) {
+            return false;
+        }
+        if (!Arrays.equals(units, that.units)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = effectiveDate != null ? effectiveDate.hashCode() : 0;
+        result = 31 * result + (catalogName != null ? catalogName.hashCode() : 0);
+        result = 31 * result + (recurringBillingMode != null ? recurringBillingMode.hashCode() : 0);
+        result = 31 * result + (supportedCurrencies != null ? Arrays.hashCode(supportedCurrencies) : 0);
+        result = 31 * result + (units != null ? Arrays.hashCode(units) : 0);
+        result = 31 * result + (products != null ? Arrays.hashCode(products) : 0);
+        result = 31 * result + (planRules != null ? planRules.hashCode() : 0);
+        result = 31 * result + (plans != null ? Arrays.hashCode(plans) : 0);
+        result = 31 * result + (priceLists != null ? priceLists.hashCode() : 0);
+        result = 31 * result + (catalogURI != null ? catalogURI.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
index 38e9181..975d9eb 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
@@ -69,27 +69,31 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalogWithPric
     private BillingMode recurringBillingMode;
 
     @XmlElement(name = "catalogVersion", required = true)
-    private final List<StandaloneCatalogWithPriceOverride> versions = new ArrayList<StandaloneCatalogWithPriceOverride>();
+    private List<StandaloneCatalogWithPriceOverride> versions;
 
     // Required for JAXB deserialization
     public VersionedCatalog() {
         this.clock = null;
+        versions = new ArrayList<StandaloneCatalogWithPriceOverride>();
     }
 
     public VersionedCatalog(final Clock clock) {
         this.clock = clock;
+        versions = new ArrayList<StandaloneCatalogWithPriceOverride>();
     }
 
-    public VersionedCatalog(final VersionedCatalog inputCatalog, final InternalTenantContext tenantContext) {
-        this.clock = inputCatalog.getClock();
-        this.catalogName = inputCatalog.getCatalogName();
-        this.recurringBillingMode = inputCatalog.getRecurringBillingMode();
-        for (final StandaloneCatalogWithPriceOverride cur : inputCatalog.getVersions()) {
+    public VersionedCatalog(final Clock clock, final String catalogName, final BillingMode recurringBillingMode, final List<StandaloneCatalogWithPriceOverride> versions, final InternalTenantContext tenantContext) {
+        this.clock = clock;
+        this.catalogName = catalogName;
+        this.recurringBillingMode = recurringBillingMode;
+        this.versions = new ArrayList<StandaloneCatalogWithPriceOverride>();
+        for (final StandaloneCatalogWithPriceOverride cur : versions) {
             final StandaloneCatalogWithPriceOverride catalogWithTenantInfo = new StandaloneCatalogWithPriceOverride(cur, tenantContext);
-            versions.add(catalogWithTenantInfo);
+            this.versions.add(catalogWithTenantInfo);
         }
     }
 
+
     //
     // Private methods
     //
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/caching/TestEhCacheCatalogCache.java b/catalog/src/test/java/org/killbill/billing/catalog/caching/TestEhCacheCatalogCache.java
index d2e4e19..4e771ce 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/caching/TestEhCacheCatalogCache.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/caching/TestEhCacheCatalogCache.java
@@ -86,7 +86,7 @@ public class TestEhCacheCatalogCache extends CatalogTestSuiteNoDB {
         Assert.assertEquals(products.length, 3);
 
         // Verify the lookup with other contexts
-        final VersionedCatalog resultForMultiTenantContext = new VersionedCatalog(result, multiTenantContext);
+        final VersionedCatalog resultForMultiTenantContext = new VersionedCatalog(result.getClock(), result.getCatalogName(), result.getRecurringBillingMode(), result.getVersions(), multiTenantContext);
         Assert.assertEquals(catalogCache.getCatalog(multiTenantContext).getCatalogName(), resultForMultiTenantContext.getCatalogName());
         Assert.assertEquals(catalogCache.getCatalog(multiTenantContext).getVersions().size(), resultForMultiTenantContext.getVersions().size());
         for (int i = 0; i < catalogCache.getCatalog(multiTenantContext).getVersions().size(); i++) {
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java b/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java
index b6df395..381749e 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/MockCatalog.java
@@ -17,7 +17,6 @@
 package org.killbill.billing.catalog;
 
 import java.util.Date;
-import java.util.List;
 
 import org.joda.time.DateTime;
 
@@ -32,19 +31,16 @@ import org.killbill.billing.catalog.api.PlanAlignmentChange;
 import org.killbill.billing.catalog.api.PlanAlignmentCreate;
 import org.killbill.billing.catalog.api.PlanChangeResult;
 import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.PriceList;
 import org.killbill.billing.catalog.api.Product;
-import org.killbill.billing.catalog.rules.CaseCancelPolicy;
-import org.killbill.billing.catalog.rules.CaseChangePlanAlignment;
-import org.killbill.billing.catalog.rules.CaseChangePlanPolicy;
-import org.killbill.billing.catalog.rules.CaseCreateAlignment;
-import org.killbill.billing.catalog.rules.PlanRules;
-
-import com.google.common.collect.ImmutableList;
+import org.killbill.billing.catalog.rules.DefaultCaseCancelPolicy;
+import org.killbill.billing.catalog.rules.DefaultCaseChangePlanAlignment;
+import org.killbill.billing.catalog.rules.DefaultCaseChangePlanPolicy;
+import org.killbill.billing.catalog.rules.DefaultCaseCreateAlignment;
+import org.killbill.billing.catalog.rules.DefaultPlanRules;
 
 public class MockCatalog extends StandaloneCatalog implements Catalog {
 
@@ -63,14 +59,14 @@ public class MockCatalog extends StandaloneCatalog implements Catalog {
     }
 
     public void populateRules() {
-        setPlanRules(new PlanRules());
+        setPlanRules(new DefaultPlanRules());
     }
 
     public void setRules(
-            final CaseChangePlanPolicy[] caseChangePlanPolicy,
-            final CaseChangePlanAlignment[] caseChangePlanAlignment,
-            final CaseCancelPolicy[] caseCancelPolicy,
-            final CaseCreateAlignment[] caseCreateAlignment
+            final DefaultCaseChangePlanPolicy[] caseChangePlanPolicy,
+            final DefaultCaseChangePlanAlignment[] caseChangePlanAlignment,
+            final DefaultCaseCancelPolicy[] caseCancelPolicy,
+            final DefaultCaseCreateAlignment[] caseCreateAlignment
                         ) {
 
     }
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestCatalogPluginMapping.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestCatalogPluginMapping.java
new file mode 100644
index 0000000..ff48895
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestCatalogPluginMapping.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.CatalogTestSuiteNoDB;
+import org.killbill.billing.catalog.StandaloneCatalog;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.Unit;
+import org.killbill.billing.catalog.api.rules.CaseBillingAlignment;
+import org.killbill.billing.catalog.api.rules.CaseCancelPolicy;
+import org.killbill.billing.catalog.api.rules.CaseChangePlanAlignment;
+import org.killbill.billing.catalog.api.rules.CaseChangePlanPolicy;
+import org.killbill.billing.catalog.api.rules.CaseCreateAlignment;
+import org.killbill.billing.catalog.api.rules.CasePriceList;
+import org.killbill.billing.catalog.plugin.api.StandalonePluginCatalog;
+import org.killbill.xmlloader.XMLLoader;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Resources;
+
+public class TestCatalogPluginMapping extends CatalogTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testMappingFromExistingCatalog() throws Exception {
+        final StandaloneCatalog inputCatalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
+        final StandalonePluginCatalog pluginCatalog = buildStandalonePluginCatalog(inputCatalog);
+
+        final StandaloneCatalogMapper mapper = new StandaloneCatalogMapper(inputCatalog.getCatalogName(), inputCatalog.getRecurringBillingMode());
+
+        final StandaloneCatalog output = mapper.toStandaloneCatalog(pluginCatalog, inputCatalog.getCatalogURI());
+        Assert.assertEquals(output, inputCatalog);
+
+    }
+
+    private StandalonePluginCatalog buildStandalonePluginCatalog(final StandaloneCatalog inputCatalog) throws Exception {
+
+        final TestModelPlanRules rules = new TestModelPlanRules(ImmutableList.<Product>copyOf(inputCatalog.getCurrentProducts()),
+                                                      ImmutableList.<Plan>copyOf(inputCatalog.getCurrentPlans()),
+                                                      inputCatalog.getPriceLists().getAllPriceLists());
+
+        if (inputCatalog.getPlanRules().getCaseChangePlanPolicy() != null) {
+            for (final CaseChangePlanPolicy cur : inputCatalog.getPlanRules().getCaseChangePlanPolicy()) {
+                rules.addCaseChangePlanPolicyRule(cur);
+            }
+        }
+        if (inputCatalog.getPlanRules().getCaseChangePlanAlignment() != null) {
+            for (final CaseChangePlanAlignment cur : inputCatalog.getPlanRules().getCaseChangePlanAlignment()) {
+                rules.addCaseChangeAlignmentRule(cur);
+            }
+        }
+        if (inputCatalog.getPlanRules().getCaseCancelPolicy() != null) {
+            for (final CaseCancelPolicy cur : inputCatalog.getPlanRules().getCaseCancelPolicy()) {
+                rules.addCaseCancelRule(cur);
+            }
+        }
+        if (inputCatalog.getPlanRules().getCaseCreateAlignment() != null) {
+            for (final CaseCreateAlignment cur : inputCatalog.getPlanRules().getCaseCreateAlignment()) {
+                rules.addCaseCreateAlignmentRule(cur);
+            }
+        }
+        if (inputCatalog.getPlanRules().getCaseBillingAlignment() != null) {
+            for (final CaseBillingAlignment cur : inputCatalog.getPlanRules().getCaseBillingAlignment()) {
+                rules.addCaseBillingAlignmentRule(cur);
+            }
+        }
+        if (inputCatalog.getPlanRules().getCasePriceList() != null) {
+            for (final CasePriceList cur : inputCatalog.getPlanRules().getCasePriceList()) {
+                rules.addPriceListRule(cur);
+            }
+        }
+        final TestModelStandalonePluginCatalog result = new TestModelStandalonePluginCatalog(new DateTime(inputCatalog.getEffectiveDate()),
+                                                                                   ImmutableList.<Currency>copyOf(inputCatalog.getCurrentSupportedCurrencies()),
+                                                                                   ImmutableList.<Product>copyOf(inputCatalog.getCurrentProducts()),
+                                                                                   ImmutableList.<Plan>copyOf(inputCatalog.getCurrentPlans()),
+                                                                                   inputCatalog.getPriceLists().getDefaultPricelist(),
+                                                                                   ImmutableList.<PriceList>copyOf(inputCatalog.getPriceLists().getChildPriceLists()),
+                                                                                   rules,
+                                                                                   ImmutableList.<Unit>copyOf(inputCatalog.getCurrentUnits()));
+        return result;
+    }
+
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCase.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCase.java
new file mode 100644
index 0000000..b702309
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCase.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.api.rules.Case;
+
+public class TestModelCase implements Case {
+
+    private final Product product;
+
+    private final ProductCategory productCategory;
+
+    private final BillingPeriod billingPeriod;
+
+    private final PriceList priceList;
+
+    public TestModelCase(final Product product,
+                         final ProductCategory productCategory,
+                         final BillingPeriod billingPeriod,
+                         final PriceList priceList) {
+        this.product = product;
+        this.productCategory = productCategory;
+        this.billingPeriod = billingPeriod;
+        this.priceList = priceList;
+    }
+
+    @Override
+    public Product getProduct() {
+        return product;
+    }
+
+    @Override
+    public ProductCategory getProductCategory() {
+        return productCategory;
+    }
+
+    @Override
+    public BillingPeriod getBillingPeriod() {
+        return billingPeriod;
+    }
+
+    @Override
+    public PriceList getPriceList() {
+        return priceList;
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseBillingAlignment.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseBillingAlignment.java
new file mode 100644
index 0000000..1757fef
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseBillingAlignment.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import org.killbill.billing.catalog.api.BillingAlignment;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.api.rules.CaseBillingAlignment;
+
+public class TestModelCaseBillingAlignment extends TestModelCasePhase implements CaseBillingAlignment {
+
+    private final BillingAlignment billingAlignment;
+
+    public TestModelCaseBillingAlignment(final Product product,
+                                         final ProductCategory productCategory,
+                                         final BillingPeriod billingPeriod,
+                                         final PriceList priceList,
+                                         final PhaseType phaseType,
+                                         final BillingAlignment billingAlignment) {
+        super(product, productCategory, billingPeriod, priceList, phaseType);
+        this.billingAlignment = billingAlignment;
+    }
+
+    @Override
+    public BillingAlignment getBillingAlignment() {
+        return billingAlignment;
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseCancelPolicy.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseCancelPolicy.java
new file mode 100644
index 0000000..7a64e40
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseCancelPolicy.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.api.rules.CaseCancelPolicy;
+
+public class TestModelCaseCancelPolicy extends TestModelCasePhase implements CaseCancelPolicy {
+
+    private final BillingActionPolicy billingActionPolicy;
+
+    public TestModelCaseCancelPolicy(final Product product,
+                                     final ProductCategory productCategory,
+                                     final BillingPeriod billingPeriod,
+                                     final PriceList priceList,
+                                     final PhaseType phaseType,
+                                     final BillingActionPolicy billingActionPolicy) {
+        super(product, productCategory, billingPeriod, priceList, phaseType);
+        this.billingActionPolicy = billingActionPolicy;
+    }
+
+    @Override
+    public BillingActionPolicy getBillingActionPolicy() {
+        return billingActionPolicy;
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseChange.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseChange.java
new file mode 100644
index 0000000..60bd92b
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseChange.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.api.rules.CaseChange;
+
+public class TestModelCaseChange implements CaseChange {
+
+    private final PhaseType phaseType;
+
+    private final Product fromProduct;
+
+    private final ProductCategory fromProductCategory;
+
+    private final BillingPeriod fromBillingPeriod;
+
+    private final PriceList fromPriceList;
+
+    private final Product toProduct;
+
+    private final ProductCategory toProductCategory;
+
+    private final BillingPeriod toBillingPeriod;
+
+    private final PriceList toPriceList;
+
+    public TestModelCaseChange(final PhaseType phaseType,
+                               final Product fromProduct,
+                               final ProductCategory fromProductCategory,
+                               final BillingPeriod fromBillingPeriod,
+                               final PriceList fromPriceList,
+                               final Product toProduct,
+                               final ProductCategory toProductCategory,
+                               final BillingPeriod toBillingPeriod,
+                               final PriceList toPriceList) {
+        this.phaseType = phaseType;
+        this.fromProduct = fromProduct;
+        this.fromProductCategory = fromProductCategory;
+        this.fromBillingPeriod = fromBillingPeriod;
+        this.fromPriceList = fromPriceList;
+        this.toProduct = toProduct;
+        this.toProductCategory = toProductCategory;
+        this.toBillingPeriod = toBillingPeriod;
+        this.toPriceList = toPriceList;
+    }
+
+    @Override
+    public PhaseType getPhaseType() {
+        return phaseType;
+    }
+
+    @Override
+    public Product getFromProduct() {
+        return fromProduct;
+    }
+
+    @Override
+    public ProductCategory getFromProductCategory() {
+        return fromProductCategory;
+    }
+
+    @Override
+    public BillingPeriod getFromBillingPeriod() {
+        return fromBillingPeriod;
+    }
+
+    @Override
+    public PriceList getFromPriceList() {
+        return fromPriceList;
+    }
+
+    @Override
+    public Product getToProduct() {
+        return toProduct;
+    }
+
+    @Override
+    public ProductCategory getToProductCategory() {
+        return toProductCategory;
+    }
+
+    @Override
+    public BillingPeriod getToBillingPeriod() {
+        return toBillingPeriod;
+    }
+
+    @Override
+    public PriceList getToPriceList() {
+        return toPriceList;
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseChangePlanAlignment.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseChangePlanAlignment.java
new file mode 100644
index 0000000..e7465a7
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseChangePlanAlignment.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.PlanAlignmentChange;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.api.rules.CaseChangePlanAlignment;
+
+public class TestModelCaseChangePlanAlignment extends TestModelCaseChange implements CaseChangePlanAlignment {
+
+    private final PlanAlignmentChange planAlignmentChange;
+
+    public TestModelCaseChangePlanAlignment(final PhaseType phaseType,
+                                            final Product fromProduct,
+                                            final ProductCategory fromProductCategory,
+                                            final BillingPeriod fromBillingPeriod,
+                                            final PriceList fromPriceList,
+                                            final Product toProduct,
+                                            final ProductCategory toProductCategory,
+                                            final BillingPeriod toBillingPeriod,
+                                            final PriceList toPriceList,
+                                            final PlanAlignmentChange planAlignmentChange) {
+        super(phaseType, fromProduct, fromProductCategory, fromBillingPeriod, fromPriceList, toProduct, toProductCategory, toBillingPeriod, toPriceList);
+        this.planAlignmentChange = planAlignmentChange;
+    }
+
+    @Override
+    public PlanAlignmentChange getAlignment() {
+        return planAlignmentChange;
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseChangePlanPolicy.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseChangePlanPolicy.java
new file mode 100644
index 0000000..8e80227
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseChangePlanPolicy.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.api.rules.CaseChangePlanPolicy;
+
+public class TestModelCaseChangePlanPolicy extends TestModelCaseChange implements CaseChangePlanPolicy {
+
+    private final BillingActionPolicy billingPolicy;
+
+    public TestModelCaseChangePlanPolicy(final PhaseType phaseType,
+                                         final Product fromProduct,
+                                         final ProductCategory fromProductCategory,
+                                         final BillingPeriod fromBillingPeriod,
+                                         final PriceList fromPriceList,
+                                         final Product toProduct,
+                                         final ProductCategory toProductCategory,
+                                         final BillingPeriod toBillingPeriod,
+                                         final PriceList toPriceList,
+                                         final BillingActionPolicy billingPolicy) {
+        super(phaseType, fromProduct, fromProductCategory, fromBillingPeriod, fromPriceList, toProduct, toProductCategory, toBillingPeriod, toPriceList);
+        this.billingPolicy = billingPolicy;
+    }
+
+    @Override
+    public BillingActionPolicy getBillingActionPolicy() {
+        return billingPolicy;
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseCreateAlignment.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseCreateAlignment.java
new file mode 100644
index 0000000..8feaf9b
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCaseCreateAlignment.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PlanAlignmentCreate;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.api.rules.CaseCreateAlignment;
+
+public class TestModelCaseCreateAlignment extends TestModelCase implements CaseCreateAlignment {
+
+    private final PlanAlignmentCreate planAlignmentCreate;
+
+    public TestModelCaseCreateAlignment(final Product product,
+                                        final ProductCategory productCategory,
+                                        final BillingPeriod billingPeriod,
+                                        final PriceList priceList,
+                                        final PlanAlignmentCreate planAlignmentCreate) {
+        super(product, productCategory, billingPeriod, priceList);
+        this.planAlignmentCreate = planAlignmentCreate;
+    }
+
+    @Override
+    public PlanAlignmentCreate getPlanAlignmentCreate() {
+        return planAlignmentCreate;
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCasePhase.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCasePhase.java
new file mode 100644
index 0000000..760b7fd
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCasePhase.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.api.rules.CasePhase;
+
+public class TestModelCasePhase extends TestModelCase implements CasePhase {
+
+    private final PhaseType phaseType;
+
+    public TestModelCasePhase(final Product product,
+                              final ProductCategory productCategory,
+                              final BillingPeriod billingPeriod,
+                              final PriceList priceList,
+                              final PhaseType phaseType) {
+        super(product, productCategory, billingPeriod, priceList);
+        this.phaseType = phaseType;
+    }
+
+    @Override
+    public PhaseType getPhaseType() {
+        return phaseType;
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCasePriceList.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCasePriceList.java
new file mode 100644
index 0000000..258cb7b
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelCasePriceList.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.api.rules.CasePriceList;
+
+public class TestModelCasePriceList extends TestModelCase implements CasePriceList {
+
+    private final PriceList destPriceList;
+
+    public TestModelCasePriceList(final Product product,
+                                  final ProductCategory productCategory,
+                                  final BillingPeriod billingPeriod,
+                                  final PriceList priceList,
+                                  final PriceList destPriceList) {
+        super(product, productCategory, billingPeriod, priceList);
+        this.destPriceList = destPriceList;
+    }
+
+    @Override
+    public PriceList getDestinationPriceList() {
+        return destPriceList;
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelPlanRules.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelPlanRules.java
new file mode 100644
index 0000000..97345d2
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelPlanRules.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.BillingAlignment;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanAlignmentChange;
+import org.killbill.billing.catalog.api.PlanAlignmentCreate;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.api.rules.CaseBillingAlignment;
+import org.killbill.billing.catalog.api.rules.CaseCancelPolicy;
+import org.killbill.billing.catalog.api.rules.CaseChangePlanAlignment;
+import org.killbill.billing.catalog.api.rules.CaseChangePlanPolicy;
+import org.killbill.billing.catalog.api.rules.CaseCreateAlignment;
+import org.killbill.billing.catalog.api.rules.CasePriceList;
+import org.killbill.billing.catalog.api.rules.PlanRules;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+public class TestModelPlanRules implements PlanRules {
+
+    private final Iterable<Product> products;
+    private final Iterable<Plan> plans;
+    private final Iterable<PriceList> priceLists;
+
+    private final List<CaseChangePlanPolicy> caseChangePlanPolicy;
+    private final List<CaseChangePlanAlignment> caseChangePlanAlignment;
+    private final List<CaseCancelPolicy> caseCancelPolicy;
+    private final List<CaseCreateAlignment> caseCreateAlignment;
+    private final List<CaseBillingAlignment> caseBillingAlignments;
+    private final List<CasePriceList> casePriceList;
+
+    public TestModelPlanRules(final Iterable<Product> products, Iterable<Plan> plans, Iterable<PriceList> priceLists) {
+        this.products = products;
+        this.plans = plans;
+        this.priceLists = priceLists;
+        this.caseChangePlanPolicy = new ArrayList<CaseChangePlanPolicy>();
+        this.caseChangePlanAlignment = new ArrayList<CaseChangePlanAlignment>();
+        this.caseCancelPolicy = new ArrayList<CaseCancelPolicy>();
+        this.caseCreateAlignment = new ArrayList<CaseCreateAlignment>();
+        this.caseBillingAlignments = new ArrayList<CaseBillingAlignment>();
+        this.casePriceList = new ArrayList<CasePriceList>();
+    }
+
+    public void addCaseBillingAlignmentRule(final CaseBillingAlignment input) {
+        caseBillingAlignments.add(input);
+    }
+
+    public void addCaseBillingAlignmentRule(final Product product,
+                                            final ProductCategory productCategory,
+                                            final BillingPeriod billingPeriod,
+                                            final PriceList priceList,
+                                            final PhaseType phaseType,
+                                            final BillingAlignment billingAlignment) {
+        caseBillingAlignments.add(new TestModelCaseBillingAlignment(product, productCategory, billingPeriod, priceList, phaseType, billingAlignment));
+    }
+
+    public void addCaseCancelRule(final CaseCancelPolicy input) {
+        caseCancelPolicy.add(input);
+    }
+
+    public void addCaseCancelRule(final Product product,
+                                  final ProductCategory productCategory,
+                                  final BillingPeriod billingPeriod,
+                                  final PriceList priceList,
+                                  final PhaseType phaseType,
+                                  final BillingActionPolicy billingActionPolicy) {
+        caseCancelPolicy.add(new TestModelCaseCancelPolicy(product, productCategory, billingPeriod, priceList, phaseType, billingActionPolicy));
+
+    }
+
+    public void addCaseChangeAlignmentRule(final CaseChangePlanAlignment input) {
+        caseChangePlanAlignment.add(input);
+    }
+
+    public void addCaseChangeAlignmentRule(final PhaseType phaseType,
+                                           final String fromProduct,
+                                           final ProductCategory fromProductCategory,
+                                           final BillingPeriod fromBillingPeriod,
+                                           final String fromPriceList,
+                                           final String toProduct,
+                                           final ProductCategory toProductCategory,
+                                           final BillingPeriod toBillingPeriod,
+                                           final String toPriceList,
+                                           final PlanAlignmentChange planAlignmentChange) {
+        caseChangePlanAlignment.add(new TestModelCaseChangePlanAlignment(phaseType, findProduct(fromProduct), fromProductCategory, fromBillingPeriod, findPriceList(fromPriceList), findProduct(toProduct), toProductCategory, toBillingPeriod, findPriceList(toPriceList), planAlignmentChange));
+    }
+
+    public void addCaseChangePlanPolicyRule(final CaseChangePlanPolicy input) {
+        caseChangePlanPolicy.add(input);
+    }
+
+    public void addCaseChangePlanPolicyRule(final PhaseType phaseType,
+                                            final String fromProduct,
+                                            final ProductCategory fromProductCategory,
+                                            final BillingPeriod fromBillingPeriod,
+                                            final String fromPriceList,
+                                            final String toProduct,
+                                            final ProductCategory toProductCategory,
+                                            final BillingPeriod toBillingPeriod,
+                                            final String toPriceList,
+                                            final BillingActionPolicy policy) {
+        caseChangePlanPolicy.add(new TestModelCaseChangePlanPolicy(phaseType, findProduct(fromProduct), fromProductCategory, fromBillingPeriod, findPriceList(fromPriceList), findProduct(toProduct), toProductCategory, toBillingPeriod, findPriceList(toPriceList), policy));
+    }
+
+    public void addCaseCreateAlignmentRule(final CaseCreateAlignment input) {
+        caseCreateAlignment.add(input);
+    }
+
+    public void addCaseCreateAlignmentRule(final Product product,
+                                           final ProductCategory productCategory,
+                                           final BillingPeriod billingPeriod,
+                                           final PriceList priceList,
+                                           final PlanAlignmentCreate planAlignmentCreate) {
+        caseCreateAlignment.add(new TestModelCaseCreateAlignment(product, productCategory, billingPeriod, priceList, planAlignmentCreate));
+    }
+
+    public void addPriceListRule(final CasePriceList input) {
+        casePriceList.add(input);
+    }
+
+    public void addPriceListRule(final Product product,
+                                 final ProductCategory productCategory,
+                                 final BillingPeriod billingPeriod,
+                                 final PriceList priceList,
+                                 final PriceList destPriceList) {
+        casePriceList.add(new TestModelCasePriceList(product, productCategory, billingPeriod, priceList, destPriceList));
+    }
+
+    @Override
+    public Iterable<CaseChangePlanPolicy> getCaseChangePlanPolicy() {
+        return caseChangePlanPolicy;
+    }
+
+    @Override
+    public Iterable<CaseChangePlanAlignment> getCaseChangePlanAlignment() {
+        return caseChangePlanAlignment;
+    }
+
+    @Override
+    public Iterable<CaseCancelPolicy> getCaseCancelPolicy() {
+        return caseCancelPolicy;
+    }
+
+    @Override
+    public Iterable<CaseCreateAlignment> getCaseCreateAlignment() {
+        return caseCreateAlignment;
+    }
+
+    @Override
+    public Iterable<CaseBillingAlignment> getCaseBillingAlignment() {
+        return caseBillingAlignments;
+    }
+
+    @Override
+    public Iterable<CasePriceList> getCasePriceList() {
+        return casePriceList;
+    }
+
+    private Product findProduct(@Nullable final String productName) {
+        return find(products, productName, "products", new Predicate<Product>() {
+            @Override
+            public boolean apply(final Product input) {
+                return input.getName().equals(productName);
+            }
+        });
+    }
+
+    private Plan findPlan(@Nullable final String planName) {
+        return find(plans, planName, "plans", new Predicate<Plan>() {
+            @Override
+            public boolean apply(final Plan input) {
+                return input.getName().equals(planName);
+            }
+        });
+    }
+
+    private PriceList findPriceList(@Nullable final String priceListName) {
+        return find(priceLists, priceListName, "pricelists", new Predicate<PriceList>() {
+            @Override
+            public boolean apply(final PriceList input) {
+                return input.getName().equals(priceListName);
+            }
+        });
+    }
+
+    private <T> T find(final Iterable<T> all, @Nullable final String name, final String what, final Predicate<T> predicate) {
+        if (name == null) {
+            return null;
+        }
+        final T result = Iterables.tryFind(all, predicate).orNull();
+        if (result == null) {
+            throw new IllegalStateException(String.format("%s : cannot find entry %s", what, name));
+        }
+        return result;
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelStandalonePluginCatalog.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelStandalonePluginCatalog.java
new file mode 100644
index 0000000..f49cfd5
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelStandalonePluginCatalog.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.Unit;
+import org.killbill.billing.catalog.api.rules.PlanRules;
+import org.killbill.billing.catalog.plugin.api.StandalonePluginCatalog;
+
+public class TestModelStandalonePluginCatalog implements StandalonePluginCatalog {
+
+    private final DateTime effectiveDate;
+
+    private final Iterable<Currency> currency;
+
+    private final Iterable<Product> products;
+
+    private final Iterable<Plan> plans;
+
+    private final PriceList defaultPriceList;
+
+    private final Iterable<PriceList> childrenPriceLists;
+
+    private final PlanRules planRules;
+
+    private final Iterable<Unit> units;
+
+    public TestModelStandalonePluginCatalog(final DateTime effectiveDate,
+                                            final Iterable<Currency> currency,
+                                            final Iterable<Product> products,
+                                            final Iterable<Plan> plans,
+                                            final PriceList defaultPriceList,
+                                            final Iterable<PriceList> childrenPriceLists,
+                                            final PlanRules planRules,
+                                            final Iterable<Unit> units) {
+        this.effectiveDate = effectiveDate;
+        this.currency = currency;
+        this.products = products;
+        this.plans = plans;
+        this.defaultPriceList = defaultPriceList;
+        this.childrenPriceLists = childrenPriceLists;
+        this.planRules = planRules;
+        this.units = units;
+    }
+
+    @Override
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    @Override
+    public Iterable<Currency> getCurrencies() {
+        return currency;
+    }
+
+    @Override
+    public Iterable<Unit> getUnits() {
+        return units;
+    }
+
+    @Override
+    public Iterable<Product> getProducts() {
+        return products;
+    }
+
+    @Override
+    public Iterable<Plan> getPlans() {
+        return plans;
+    }
+
+    @Override
+    public PriceList getDefaultPriceList() {
+        return defaultPriceList;
+    }
+
+    @Override
+    public Iterable<PriceList> getChildrenPriceList() {
+        return childrenPriceLists;
+    }
+
+    @Override
+    public PlanRules getPlanRules() {
+        return planRules;
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelVersionedPluginCatalog.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelVersionedPluginCatalog.java
new file mode 100644
index 0000000..18de96f
--- /dev/null
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelVersionedPluginCatalog.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.catalog.plugin;
+
+import org.killbill.billing.catalog.api.BillingMode;
+import org.killbill.billing.catalog.plugin.api.StandalonePluginCatalog;
+import org.killbill.billing.catalog.plugin.api.VersionedPluginCatalog;
+
+public class TestModelVersionedPluginCatalog implements VersionedPluginCatalog {
+    private final String catalogName;
+
+    private final BillingMode billingMode;
+
+    private final Iterable<StandalonePluginCatalog> standalonePluginCatalogs;
+
+    public TestModelVersionedPluginCatalog(final String catalogName,
+                                      final BillingMode billingMode,
+                                      final Iterable<StandalonePluginCatalog> standalonePluginCatalogs) {
+        this.catalogName = catalogName;
+        this.billingMode = billingMode;
+        this.standalonePluginCatalogs = standalonePluginCatalogs;
+    }
+
+    @Override
+    public String getCatalogName() {
+        return catalogName;
+    }
+
+    @Override
+    public BillingMode getRecurringBillingMode() {
+        return billingMode;
+    }
+
+    @Override
+    public Iterable<StandalonePluginCatalog> getStandalonePluginCatalogs() {
+        return standalonePluginCatalogs;
+    }
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/rules/TestCase.java b/catalog/src/test/java/org/killbill/billing/catalog/rules/TestCase.java
index e61f979..b3f16dd 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/rules/TestCase.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/rules/TestCase.java
@@ -36,13 +36,13 @@ import org.killbill.billing.catalog.api.ProductCategory;
 
 public class TestCase extends CatalogTestSuiteNoDB {
 
-    protected class CaseResult extends Case<Result> {
+    protected class DefaultCaseResult extends DefaultCase<Result> {
 
         @XmlElement(required = true)
         private final Result policy;
 
-        public CaseResult(final DefaultProduct product, final ProductCategory productCategory, final BillingPeriod billingPeriod, final DefaultPriceList priceList,
-                          final Result policy) {
+        public DefaultCaseResult(final DefaultProduct product, final ProductCategory productCategory, final BillingPeriod billingPeriod, final DefaultPriceList priceList,
+                                 final Result policy) {
             setProduct(product);
             setProductCategory(productCategory);
             setBillingPeriod(billingPeriod);
@@ -84,22 +84,22 @@ public class TestCase extends CatalogTestSuiteNoDB {
             return priceList;
         }
 
-        protected CaseResult setProduct(final DefaultProduct product) {
+        protected DefaultCaseResult setProduct(final DefaultProduct product) {
             this.product = product;
             return this;
         }
 
-        protected CaseResult setProductCategory(final ProductCategory productCategory) {
+        protected DefaultCaseResult setProductCategory(final ProductCategory productCategory) {
             this.productCategory = productCategory;
             return this;
         }
 
-        protected CaseResult setBillingPeriod(final BillingPeriod billingPeriod) {
+        protected DefaultCaseResult setBillingPeriod(final BillingPeriod billingPeriod) {
             this.billingPeriod = billingPeriod;
             return this;
         }
 
-        protected CaseResult setPriceList(final DefaultPriceList priceList) {
+        protected DefaultCaseResult setPriceList(final DefaultPriceList priceList) {
             this.priceList = priceList;
             return this;
         }
@@ -112,7 +112,7 @@ public class TestCase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
 
-        final CaseResult cr = new CaseResult(
+        final DefaultCaseResult cr = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.MONTHLY,
@@ -133,7 +133,7 @@ public class TestCase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
 
-        final CaseResult cr = new CaseResult(
+        final DefaultCaseResult cr = new DefaultCaseResult(
                 null,
                 ProductCategory.BASE,
                 BillingPeriod.MONTHLY,
@@ -155,7 +155,7 @@ public class TestCase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
 
-        final CaseResult cr = new CaseResult(
+        final DefaultCaseResult cr = new DefaultCaseResult(
                 product,
                 null,
                 BillingPeriod.MONTHLY,
@@ -177,7 +177,7 @@ public class TestCase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
 
-        final CaseResult cr = new CaseResult(
+        final DefaultCaseResult cr = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 null,
@@ -199,7 +199,7 @@ public class TestCase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
 
-        final CaseResult cr = new CaseResult(
+        final DefaultCaseResult cr = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.MONTHLY,
@@ -221,48 +221,48 @@ public class TestCase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
 
-        final CaseResult cr0 = new CaseResult(
+        final DefaultCaseResult cr0 = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.MONTHLY,
                 priceList,
                 Result.FOO);
 
-        final CaseResult cr1 = new CaseResult(
+        final DefaultCaseResult cr1 = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.MONTHLY,
                 priceList,
                 Result.BAR);
 
-        final CaseResult cr2 = new CaseResult(
+        final DefaultCaseResult cr2 = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.ANNUAL,
                 priceList,
                 Result.DIPSY);
 
-        final CaseResult cr3 = new CaseResult(
+        final DefaultCaseResult cr3 = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.ANNUAL,
                 priceList,
                 Result.LALA);
 
-        final Result r1 = Case.getResult(new CaseResult[]{cr0, cr1, cr2, cr3},
-                                         new PlanSpecifier(product.getName(), product.getCategory(), BillingPeriod.MONTHLY, priceList.getName()), cat);
+        final Result r1 = DefaultCase.getResult(new DefaultCaseResult[]{cr0, cr1, cr2, cr3},
+                                                new PlanSpecifier(product.getName(), product.getCategory(), BillingPeriod.MONTHLY, priceList.getName()), cat);
         Assert.assertEquals(r1, Result.FOO);
 
-        final Result r2 = Case.getResult(new CaseResult[]{cr0, cr1, cr2},
-                                         new PlanSpecifier(product.getName(), product.getCategory(), BillingPeriod.ANNUAL, priceList.getName()), cat);
+        final Result r2 = DefaultCase.getResult(new DefaultCaseResult[]{cr0, cr1, cr2},
+                                                new PlanSpecifier(product.getName(), product.getCategory(), BillingPeriod.ANNUAL, priceList.getName()), cat);
         Assert.assertEquals(r2, Result.DIPSY);
     }
 
-    protected void assertionNull(final CaseResult cr, final String productName, final ProductCategory productCategory, final BillingPeriod bp, final String priceListName, final StandaloneCatalog cat) throws CatalogApiException {
+    protected void assertionNull(final DefaultCaseResult cr, final String productName, final ProductCategory productCategory, final BillingPeriod bp, final String priceListName, final StandaloneCatalog cat) throws CatalogApiException {
         Assert.assertNull(cr.getResult(new PlanSpecifier(productName, productCategory, bp, priceListName), cat));
     }
 
-    protected void assertionException(final CaseResult cr, final String productName, final ProductCategory productCategory, final BillingPeriod bp, final String priceListName, final StandaloneCatalog cat) {
+    protected void assertionException(final DefaultCaseResult cr, final String productName, final ProductCategory productCategory, final BillingPeriod bp, final String priceListName, final StandaloneCatalog cat) {
         try {
             cr.getResult(new PlanSpecifier(productName, productCategory, bp, priceListName), cat);
             Assert.fail("Expecting an exception");
@@ -271,7 +271,7 @@ public class TestCase extends CatalogTestSuiteNoDB {
         }
     }
 
-    protected void assertion(final Result result, final CaseResult cr, final String productName, final ProductCategory productCategory, final BillingPeriod bp, final String priceListName, final StandaloneCatalog cat) throws CatalogApiException {
+    protected void assertion(final Result result, final DefaultCaseResult cr, final String productName, final ProductCategory productCategory, final BillingPeriod bp, final String priceListName, final StandaloneCatalog cat) throws CatalogApiException {
         Assert.assertEquals(result, cr.getResult(new PlanSpecifier(productName, productCategory, bp, priceListName), cat));
     }
 }
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/rules/TestCaseChange.java b/catalog/src/test/java/org/killbill/billing/catalog/rules/TestCaseChange.java
index e6c66b4..6de6027 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/rules/TestCaseChange.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/rules/TestCaseChange.java
@@ -37,17 +37,17 @@ import org.killbill.billing.catalog.api.ProductCategory;
 
 public class TestCaseChange extends CatalogTestSuiteNoDB {
 
-    protected static class CaseChangeResult extends CaseChange<Result> {
+    protected static class DefaultCaseChangeResult extends DefaultCaseChange<Result> {
 
         @XmlElement(required = true)
         private final Result result;
 
-        public CaseChangeResult(final DefaultProduct from, final DefaultProduct to,
-                                final ProductCategory fromProductCategory, final ProductCategory toProductCategory,
-                                final BillingPeriod fromBP, final BillingPeriod toBP,
-                                final DefaultPriceList fromPriceList, final DefaultPriceList toPriceList,
-                                final PhaseType fromType,
-                                final Result result) {
+        public DefaultCaseChangeResult(final DefaultProduct from, final DefaultProduct to,
+                                       final ProductCategory fromProductCategory, final ProductCategory toProductCategory,
+                                       final BillingPeriod fromBP, final BillingPeriod toBP,
+                                       final DefaultPriceList fromPriceList, final DefaultPriceList toPriceList,
+                                       final PhaseType fromType,
+                                       final Result result) {
             setFromProduct(from);
             setToProduct(to);
             setFromProductCategory(fromProductCategory);
@@ -77,7 +77,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         final DefaultProduct product2 = cat.getCurrentProducts()[2];
         final DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
 
-        final CaseChangeResult cr = new CaseChangeResult(
+        final DefaultCaseChangeResult cr = new DefaultCaseChangeResult(
                 product1, product2,
                 ProductCategory.BASE, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
@@ -166,7 +166,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         final DefaultProduct product2 = cat.getCurrentProducts()[2];
         final DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
 
-        final CaseChangeResult cr = new CaseChangeResult(
+        final DefaultCaseChangeResult cr = new DefaultCaseChangeResult(
                 null, product2,
                 ProductCategory.BASE, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
@@ -248,7 +248,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         final DefaultProduct product2 = cat.getCurrentProducts()[2];
         final DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
 
-        final CaseChangeResult cr = new CaseChangeResult(
+        final DefaultCaseChangeResult cr = new DefaultCaseChangeResult(
                 product1, null,
                 ProductCategory.BASE, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
@@ -337,7 +337,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         final DefaultProduct product2 = cat.getCurrentProducts()[2];
         final DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
 
-        final CaseChangeResult cr = new CaseChangeResult(
+        final DefaultCaseChangeResult cr = new DefaultCaseChangeResult(
                 product1, product2,
                 null, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
@@ -426,7 +426,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         final DefaultProduct product2 = cat.getCurrentProducts()[2];
         final DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
 
-        final CaseChangeResult cr = new CaseChangeResult(
+        final DefaultCaseChangeResult cr = new DefaultCaseChangeResult(
                 product1, product2,
                 ProductCategory.BASE, null,
                 BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
@@ -515,7 +515,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         final DefaultProduct product2 = cat.getCurrentProducts()[2];
         final DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
 
-        final CaseChangeResult cr = new CaseChangeResult(
+        final DefaultCaseChangeResult cr = new DefaultCaseChangeResult(
                 product1, product2,
                 ProductCategory.BASE, ProductCategory.BASE,
                 null, BillingPeriod.MONTHLY,
@@ -604,7 +604,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         final DefaultProduct product2 = cat.getCurrentProducts()[2];
         final DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
 
-        final CaseChangeResult cr = new CaseChangeResult(
+        final DefaultCaseChangeResult cr = new DefaultCaseChangeResult(
                 product1, product2,
                 ProductCategory.BASE, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, null,
@@ -693,7 +693,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         final DefaultProduct product2 = cat.getCurrentProducts()[2];
         final DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
 
-        final CaseChangeResult cr = new CaseChangeResult(
+        final DefaultCaseChangeResult cr = new DefaultCaseChangeResult(
                 product1, product2,
                 ProductCategory.BASE, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
@@ -782,7 +782,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         final DefaultProduct product2 = cat.getCurrentProducts()[2];
         final DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
 
-        final CaseChangeResult cr = new CaseChangeResult(
+        final DefaultCaseChangeResult cr = new DefaultCaseChangeResult(
                 product1, product2,
                 ProductCategory.BASE, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
@@ -871,7 +871,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         final DefaultProduct product2 = cat.getCurrentProducts()[2];
         final DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
 
-        final CaseChangeResult cr = new CaseChangeResult(
+        final DefaultCaseChangeResult cr = new DefaultCaseChangeResult(
                 product1, product2,
                 ProductCategory.BASE, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
@@ -960,7 +960,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         final DefaultProduct product2 = cat.getCurrentProducts()[2];
         final DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
 
-        final CaseChangeResult cr0 = new CaseChangeResult(
+        final DefaultCaseChangeResult cr0 = new DefaultCaseChangeResult(
                 product1, product2,
                 ProductCategory.BASE, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
@@ -968,7 +968,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
                 PhaseType.EVERGREEN,
                 Result.FOO);
 
-        final CaseChangeResult cr1 = new CaseChangeResult(
+        final DefaultCaseChangeResult cr1 = new DefaultCaseChangeResult(
                 product1, product2,
                 ProductCategory.BASE, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
@@ -976,7 +976,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
                 PhaseType.EVERGREEN,
                 Result.BAR);
 
-        final CaseChangeResult cr2 = new CaseChangeResult(
+        final DefaultCaseChangeResult cr2 = new DefaultCaseChangeResult(
                 product1, product2,
                 ProductCategory.BASE, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
@@ -984,7 +984,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
                 PhaseType.EVERGREEN,
                 Result.TINKYWINKY);
 
-        final CaseChangeResult cr3 = new CaseChangeResult(
+        final DefaultCaseChangeResult cr3 = new DefaultCaseChangeResult(
                 product1, product2,
                 ProductCategory.BASE, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, BillingPeriod.ANNUAL,
@@ -992,7 +992,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
                 PhaseType.EVERGREEN,
                 Result.DIPSY);
 
-        final CaseChangeResult cr4 = new CaseChangeResult(
+        final DefaultCaseChangeResult cr4 = new DefaultCaseChangeResult(
                 product1, product2,
                 ProductCategory.BASE, ProductCategory.BASE,
                 BillingPeriod.MONTHLY, BillingPeriod.ANNUAL,
@@ -1000,20 +1000,20 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
                 PhaseType.EVERGREEN,
                 Result.LALA);
 
-        final Result r1 = CaseChange.getResult(new CaseChangeResult[]{cr0, cr1, cr2, cr3, cr4},
-                                               new PlanPhaseSpecifier(product1.getName(), product1.getCategory(), BillingPeriod.MONTHLY, priceList1.getName(), PhaseType.EVERGREEN),
-                                               new PlanSpecifier(product2.getName(), product2.getCategory(), BillingPeriod.MONTHLY, priceList2.getName()), cat);
+        final Result r1 = DefaultCaseChange.getResult(new DefaultCaseChangeResult[]{cr0, cr1, cr2, cr3, cr4},
+                                                      new PlanPhaseSpecifier(product1.getName(), product1.getCategory(), BillingPeriod.MONTHLY, priceList1.getName(), PhaseType.EVERGREEN),
+                                                      new PlanSpecifier(product2.getName(), product2.getCategory(), BillingPeriod.MONTHLY, priceList2.getName()), cat);
 
         Assert.assertEquals(r1, Result.FOO);
 
-        final Result r2 = CaseChange.getResult(new CaseChangeResult[]{cr0, cr1, cr2, cr3, cr4},
-                                               new PlanPhaseSpecifier(product1.getName(), product1.getCategory(), BillingPeriod.MONTHLY, priceList1.getName(), PhaseType.EVERGREEN),
-                                               new PlanSpecifier(product2.getName(), product2.getCategory(), BillingPeriod.ANNUAL, priceList2.getName()), cat);
+        final Result r2 = DefaultCaseChange.getResult(new DefaultCaseChangeResult[]{cr0, cr1, cr2, cr3, cr4},
+                                                      new PlanPhaseSpecifier(product1.getName(), product1.getCategory(), BillingPeriod.MONTHLY, priceList1.getName(), PhaseType.EVERGREEN),
+                                                      new PlanSpecifier(product2.getName(), product2.getCategory(), BillingPeriod.ANNUAL, priceList2.getName()), cat);
 
         Assert.assertEquals(r2, Result.DIPSY);
     }
 
-    protected void assertionNull(final CaseChangeResult cr,
+    protected void assertionNull(final DefaultCaseChangeResult cr,
                                  final String fromProductName, final String toProductName,
                                  final ProductCategory fromProductCategory, final ProductCategory toProductCategory,
                                  final BillingPeriod fromBp, final BillingPeriod toBp,
@@ -1027,7 +1027,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         }
     }
 
-    protected void assertionException(final CaseChangeResult cr,
+    protected void assertionException(final DefaultCaseChangeResult cr,
                                       final String fromProductName, final String toProductName,
                                       final ProductCategory fromProductCategory, final ProductCategory toProductCategory,
                                       final BillingPeriod fromBp, final BillingPeriod toBp,
@@ -1042,7 +1042,7 @@ public class TestCaseChange extends CatalogTestSuiteNoDB {
         }
     }
 
-    protected void assertion(final Result result, final CaseChangeResult cr,
+    protected void assertion(final Result result, final DefaultCaseChangeResult cr,
                              final String fromProductName, final String toProductName,
                              final ProductCategory fromProductCategory, final ProductCategory toProductCategory,
                              final BillingPeriod fromBp, final BillingPeriod toBp,
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/rules/TestCasePhase.java b/catalog/src/test/java/org/killbill/billing/catalog/rules/TestCasePhase.java
index b86b229..3fa6052 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/rules/TestCasePhase.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/rules/TestCasePhase.java
@@ -35,13 +35,13 @@ import org.killbill.billing.catalog.api.ProductCategory;
 
 public class TestCasePhase extends CatalogTestSuiteNoDB {
 
-    protected class CaseResult extends CasePhase<Result> {
+    protected class DefaultCaseResult extends DefaultCasePhase<Result> {
 
         @XmlElement(required = true)
         private final Result policy;
 
-        public CaseResult(final DefaultProduct product, final ProductCategory productCategory, final BillingPeriod billingPeriod, final DefaultPriceList priceList,
-                          final PhaseType phaseType, final Result policy) {
+        public DefaultCaseResult(final DefaultProduct product, final ProductCategory productCategory, final BillingPeriod billingPeriod, final DefaultPriceList priceList,
+                                 final PhaseType phaseType, final Result policy) {
             setProduct(product);
             setProductCategory(productCategory);
             setBillingPeriod(billingPeriod);
@@ -64,7 +64,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
 
-        final CaseResult cr = new CaseResult(
+        final DefaultCaseResult cr = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.MONTHLY,
@@ -87,7 +87,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
 
-        final CaseResult cr = new CaseResult(
+        final DefaultCaseResult cr = new DefaultCaseResult(
                 null,
                 ProductCategory.BASE,
                 BillingPeriod.MONTHLY,
@@ -110,7 +110,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
 
-        final CaseResult cr = new CaseResult(
+        final DefaultCaseResult cr = new DefaultCaseResult(
                 product,
                 null,
                 BillingPeriod.MONTHLY,
@@ -133,7 +133,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
 
-        final CaseResult cr = new CaseResult(
+        final DefaultCaseResult cr = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 null,
@@ -156,7 +156,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
 
-        final CaseResult cr = new CaseResult(
+        final DefaultCaseResult cr = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.MONTHLY,
@@ -179,7 +179,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
 
-        final CaseResult cr = new CaseResult(
+        final DefaultCaseResult cr = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.MONTHLY,
@@ -202,7 +202,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
         final DefaultProduct product = cat.getCurrentProducts()[0];
         final DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
 
-        final CaseResult cr0 = new CaseResult(
+        final DefaultCaseResult cr0 = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.MONTHLY,
@@ -210,7 +210,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
                 PhaseType.EVERGREEN,
                 Result.FOO);
 
-        final CaseResult cr1 = new CaseResult(
+        final DefaultCaseResult cr1 = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.MONTHLY,
@@ -218,7 +218,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
                 PhaseType.EVERGREEN,
                 Result.BAR);
 
-        final CaseResult cr2 = new CaseResult(
+        final DefaultCaseResult cr2 = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.MONTHLY,
@@ -226,7 +226,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
                 PhaseType.EVERGREEN,
                 Result.TINKYWINKY);
 
-        final CaseResult cr3 = new CaseResult(
+        final DefaultCaseResult cr3 = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.ANNUAL,
@@ -234,7 +234,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
                 PhaseType.EVERGREEN,
                 Result.DIPSY);
 
-        final CaseResult cr4 = new CaseResult(
+        final DefaultCaseResult cr4 = new DefaultCaseResult(
                 product,
                 ProductCategory.BASE,
                 BillingPeriod.ANNUAL,
@@ -242,19 +242,19 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
                 PhaseType.EVERGREEN,
                 Result.LALA);
 
-        final Result r1 = CasePhase.getResult(new CaseResult[]{cr0, cr1, cr2, cr3, cr4},
-                                              new PlanPhaseSpecifier(product.getName(), product.getCategory(), BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN), cat);
+        final Result r1 = DefaultCasePhase.getResult(new DefaultCaseResult[]{cr0, cr1, cr2, cr3, cr4},
+                                                     new PlanPhaseSpecifier(product.getName(), product.getCategory(), BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN), cat);
 
         Assert.assertEquals(Result.FOO, r1);
 
-        final Result r2 = CasePhase.getResult(new CaseResult[]{cr0, cr1, cr2, cr3, cr4},
-                                              new PlanPhaseSpecifier(product.getName(), product.getCategory(), BillingPeriod.ANNUAL, priceList.getName(), PhaseType.EVERGREEN), cat);
+        final Result r2 = DefaultCasePhase.getResult(new DefaultCaseResult[]{cr0, cr1, cr2, cr3, cr4},
+                                                     new PlanPhaseSpecifier(product.getName(), product.getCategory(), BillingPeriod.ANNUAL, priceList.getName(), PhaseType.EVERGREEN), cat);
 
         Assert.assertEquals(Result.DIPSY, r2);
 
     }
 
-    protected void assertionNull(final CaseResult cr, final String productName, final ProductCategory productCategory, final BillingPeriod bp, final String priceListName, final PhaseType phaseType, final StandaloneCatalog cat) {
+    protected void assertionNull(final DefaultCaseResult cr, final String productName, final ProductCategory productCategory, final BillingPeriod bp, final String priceListName, final PhaseType phaseType, final StandaloneCatalog cat) {
         try {
             Assert.assertNull(cr.getResult(new PlanPhaseSpecifier(productName, productCategory, bp, priceListName, phaseType), cat));
         } catch (CatalogApiException e) {
@@ -262,7 +262,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
         }
     }
 
-    protected void assertionException(final CaseResult cr, final String productName, final ProductCategory productCategory, final BillingPeriod bp, final String priceListName, final PhaseType phaseType, final StandaloneCatalog cat) {
+    protected void assertionException(final DefaultCaseResult cr, final String productName, final ProductCategory productCategory, final BillingPeriod bp, final String priceListName, final PhaseType phaseType, final StandaloneCatalog cat) {
         try {
             Assert.assertNull(cr.getResult(new PlanPhaseSpecifier(productName, productCategory, bp, priceListName, phaseType), cat));
             Assert.fail("Exception expected");
@@ -271,7 +271,7 @@ public class TestCasePhase extends CatalogTestSuiteNoDB {
         }
     }
 
-    protected void assertion(final Result result, final CaseResult cr, final String productName, final ProductCategory productCategory, final BillingPeriod bp, final String priceListName, final PhaseType phaseType, final StandaloneCatalog cat) {
+    protected void assertion(final Result result, final DefaultCaseResult cr, final String productName, final ProductCategory productCategory, final BillingPeriod bp, final String priceListName, final PhaseType phaseType, final StandaloneCatalog cat) {
         try {
             Assert.assertEquals(result, cr.getResult(new PlanPhaseSpecifier(productName, productCategory, bp, priceListName, phaseType), cat));
         } catch (CatalogApiException e) {
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/rules/TestLoadRules.java b/catalog/src/test/java/org/killbill/billing/catalog/rules/TestLoadRules.java
index 470d88f..47ca215 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/rules/TestLoadRules.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/rules/TestLoadRules.java
@@ -38,7 +38,7 @@ public class TestLoadRules extends CatalogTestSuiteNoDB {
         final URI uri = new URI(Resources.getResource("WeaponsHireSmall.xml").toExternalForm());
         final StandaloneCatalog catalog = XMLLoader.getObjectFromUri(uri, StandaloneCatalog.class);
         Assert.assertNotNull(catalog);
-        final PlanRules rules = catalog.getPlanRules();
+        final DefaultPlanRules rules = catalog.getPlanRules();
 
         final PlanSpecifier specifier = new PlanSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY,
                                                           "DEFAULT");
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/rules/TestPlanRules.java b/catalog/src/test/java/org/killbill/billing/catalog/rules/TestPlanRules.java
index 3abe891..152ad5f 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/rules/TestPlanRules.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/rules/TestPlanRules.java
@@ -18,7 +18,6 @@ package org.killbill.billing.catalog.rules;
 
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
 
 import org.killbill.billing.catalog.CatalogTestSuiteNoDB;
@@ -46,14 +45,14 @@ public class TestPlanRules extends CatalogTestSuiteNoDB {
 
         final DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[0];
 
-        final CaseChangePlanPolicy casePolicy = new CaseChangePlanPolicy().setPolicy(BillingActionPolicy.END_OF_TERM);
-        final CaseChangePlanAlignment caseAlignment = new CaseChangePlanAlignment().setAlignment(PlanAlignmentChange.START_OF_SUBSCRIPTION);
-        final CasePriceList casePriceList = new CasePriceList().setToPriceList(priceList2);
+        final DefaultCaseChangePlanPolicy casePolicy = new DefaultCaseChangePlanPolicy().setPolicy(BillingActionPolicy.END_OF_TERM);
+        final DefaultCaseChangePlanAlignment caseAlignment = new DefaultCaseChangePlanAlignment().setAlignment(PlanAlignmentChange.START_OF_SUBSCRIPTION);
+        final DefaultCasePriceList casePriceList = new DefaultCasePriceList().setToPriceList(priceList2);
 
         cat.getPlanRules().
-                setChangeCase(new CaseChangePlanPolicy[]{casePolicy}).
-                   setChangeAlignmentCase(new CaseChangePlanAlignment[]{caseAlignment}).
-                   setPriceListCase(new CasePriceList[]{casePriceList});
+                setChangeCase(new DefaultCaseChangePlanPolicy[]{casePolicy}).
+                   setChangeAlignmentCase(new DefaultCaseChangePlanAlignment[]{caseAlignment}).
+                   setPriceListCase(new DefaultCasePriceList[]{casePriceList});
     }
 
     @Test(groups = "fast")

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 837f44f..81a9f36 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.15</version>
+        <version>0.22</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.15.0-SNAPSHOT</version>