killbill-memoizeit

catalog: Improve StandaloneCatalogMapper performance.

10/11/2016 10:22:27 PM

Details

diff --git a/catalog/src/main/java/org/killbill/billing/catalog/CatalogEntityCollection.java b/catalog/src/main/java/org/killbill/billing/catalog/CatalogEntityCollection.java
index b9bad42..d2d2cbd 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/CatalogEntityCollection.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/CatalogEntityCollection.java
@@ -42,7 +42,7 @@ public class CatalogEntityCollection<T extends CatalogEntity> implements Collect
     }
 
 
-    public CatalogEntityCollection(final Collection<T> entities) {
+    public CatalogEntityCollection(final Iterable<T> entities) {
         this.data = new TreeMap<String, T>(Ordering.<String>natural());
         for (final T cur : entities) {
             addEntry(cur);
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 b99ec4b..19ceed6 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -185,7 +185,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
                 p.initialize(catalog, sourceURI);
             }
         }
-        this.priceListName = findPriceListForPlan(catalog);
+        this.priceListName = this.priceListName  != null ? this.priceListName : findPriceListForPlan(catalog);
     }
 
     @Override
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 7335354..9bdf070 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java
@@ -102,7 +102,7 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
         return this;
     }
 
-    public DefaultPriceList setPlans(final Collection<Plan> plans) {
+    public DefaultPriceList setPlans(final Iterable<Plan> plans) {
         this.plans = new CatalogEntityCollection(plans);
         return this;
     }
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
index 7c90442..6f52dc1 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
@@ -21,7 +21,8 @@ import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.annotation.Nullable;
 
@@ -37,7 +38,6 @@ 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;
@@ -83,18 +83,22 @@ public class StandaloneCatalogMapper {
     private final String catalogName;
     private final BillingMode recurringBillingMode;
 
-    private Collection<Product> tmpDefaultProducts;
-    private Collection<Plan> tmpDefaultPlans;
+    private Iterable<Product> tmpDefaultProducts;
+    private Iterable<Plan> tmpDefaultPlans;
     private DefaultPriceListSet tmpDefaultPriceListSet;
+    private Map<String, DefaultPriceList> tmpDefaultPriceListMap;
 
     public StandaloneCatalogMapper(final String catalogName, final BillingMode recurringBillingMode) {
         this.catalogName = catalogName;
         this.recurringBillingMode = recurringBillingMode;
         this.tmpDefaultProducts = null;
         this.tmpDefaultPlans = null;
+        this.tmpDefaultPriceListMap = new HashMap<String, DefaultPriceList>();
     }
 
     public StandaloneCatalog toStandaloneCatalog(final StandalonePluginCatalog pluginCatalog, @Nullable URI catalogURI) {
+
+
         final StandaloneCatalog result = new StandaloneCatalog();
         result.setCatalogName(catalogName);
         result.setEffectiveDate(pluginCatalog.getEffectiveDate().toDate());
@@ -105,12 +109,11 @@ public class StandaloneCatalogMapper {
         result.setSupportedCurrencies(toArray(pluginCatalog.getCurrencies()));
         result.setUnits(toDefaultUnits(pluginCatalog.getUnits()));
         result.setPlanRules(toDefaultPlanRules(pluginCatalog.getPlanRules()));
-
         for (final Product cur : pluginCatalog.getProducts()) {
             for (Product target :  result.getCurrentProducts()) {
                 if (target.getName().equals(cur.getName())) {
-                    ((DefaultProduct) target).setAvailable(toFilteredDefaultProduct(ImmutableList.copyOf(cur.getAvailable())));
-                    ((DefaultProduct) target).setIncluded(toFilteredDefaultProduct(ImmutableList.copyOf(cur.getIncluded())));
+                    ((DefaultProduct) target).setAvailable(toFilteredDefaultProduct(cur.getAvailable()));
+                    ((DefaultProduct) target).setIncluded(toFilteredDefaultProduct(cur.getIncluded()));
                     break;
                 }
             }
@@ -252,7 +255,7 @@ public class StandaloneCatalogMapper {
     }
 
 
-    private Collection<Product> toDefaultProducts(final Iterable<Product> input) {
+    private Iterable<Product> toDefaultProducts(final Iterable<Product> input) {
         if (tmpDefaultProducts == null) {
             final Function<Product, Product> productTransformer = new Function<Product, Product>() {
                 @Override
@@ -260,22 +263,22 @@ public class StandaloneCatalogMapper {
                     return toDefaultProduct(input);
                 }
             };
-            tmpDefaultProducts = ImmutableList.<Product>copyOf(Iterables.transform(input, productTransformer));
+            tmpDefaultProducts = ImmutableList.copyOf(Iterables.transform(input, productTransformer));
         }
         return tmpDefaultProducts;
     }
 
-    private Collection<Product> toFilteredDefaultProduct(final Iterable<Product> input) {
+    private Collection<Product> toFilteredDefaultProduct(final Collection<Product> input) {
         if (!input.iterator().hasNext()) {
             return Collections.emptyList();
         }
-        final List<String> inputProductNames = ImmutableList.copyOf(Iterables.transform(input, new Function<Product, String>() {
+        final Iterable<String> inputProductNames = Iterables.transform(input, new Function<Product, String>() {
             @Override
             public String apply(final Product input) {
                 return input.getName();
             }
-        }));
-        final Collection<Product> filteredAndOrdered = new ArrayList<Product>(inputProductNames.size());
+        });
+        final Collection<Product> filteredAndOrdered = new ArrayList<Product>(input.size());
         for (final String cur : inputProductNames) {
             final Product found = findOrIllegalState(tmpDefaultProducts, new Predicate<Product>() {
                 @Override
@@ -288,7 +291,7 @@ public class StandaloneCatalogMapper {
         return filteredAndOrdered;
     }
 
-    private Collection<Plan> toDefaultPlans(final Iterable<Plan> input) {
+    private Iterable<Plan> toDefaultPlans(final Iterable<Plan> input) {
         if (tmpDefaultPlans == null) {
             final Function<Plan, Plan> planTransformer = new Function<Plan, Plan>() {
                 @Override
@@ -296,37 +299,26 @@ public class StandaloneCatalogMapper {
                     return toDefaultPlan(input);
                 }
             };
-            tmpDefaultPlans = ImmutableList.<Plan>copyOf(Iterables.transform(input, planTransformer));
+            tmpDefaultPlans = ImmutableList.copyOf(Iterables.transform(input, planTransformer));
         }
         return tmpDefaultPlans;
     }
 
-    private Collection<Plan> toFilterDefaultPlans(final Iterable<Plan> input) {
+    private Iterable<Plan> toFilterDefaultPlans(final String priceListName) {
         if (tmpDefaultPlans == null) {
             throw new IllegalStateException("Cannot filter on uninitialized plans");
         }
-        final List<String> inputPlanNames = ImmutableList.copyOf(Iterables.transform(input, new Function<Plan, String>() {
+        return Iterables.filter(tmpDefaultPlans, new Predicate<Plan>() {
             @Override
-            public String apply(final Plan input) {
-                return input.getName();
+            public boolean apply(final Plan input) {
+                return input.getPriceListName().equals(priceListName);
             }
-        }));
-        final List<Plan> filteredAndOrdered = new ArrayList<Plan>(inputPlanNames.size());
-        for (final String cur : inputPlanNames) {
-            final Plan found = findOrIllegalState(tmpDefaultPlans, new Predicate<Plan>() {
-                @Override
-                public boolean apply(final Plan inputPredicate) {
-                    return inputPredicate.getName().equals(cur);
-                }
-            }, "Failed to find plan " + cur);
-            filteredAndOrdered.add(found);
-        }
-        return filteredAndOrdered;
+        });
     }
 
     private DefaultPriceListSet toDefaultPriceListSet(final PriceList defaultPriceList, final Iterable<PriceList> childrenPriceLists) {
         if (tmpDefaultPriceListSet == null) {
-            tmpDefaultPriceListSet = new DefaultPriceListSet(toPriceListDefault(defaultPriceList), toDefaultPriceLists(childrenPriceLists));
+            tmpDefaultPriceListSet = new DefaultPriceListSet(toDefaultPriceList(defaultPriceList), toDefaultPriceLists(childrenPriceLists));
         }
         return tmpDefaultPriceListSet;
     }
@@ -381,22 +373,18 @@ public class StandaloneCatalogMapper {
         if (input == null) {
             return null;
         }
-        final DefaultPriceList result = new DefaultPriceList();
-        result.setName(input.getName());
-        result.setPlans(toFilterDefaultPlans(ImmutableList.copyOf(input.getPlans())));
-        return result;
-    }
 
-    private PriceListDefault toPriceListDefault(@Nullable final PriceList input) {
-        if (input == null) {
-            return null;
+        DefaultPriceList result = tmpDefaultPriceListMap.get(input.getName());
+        if (result == null) {
+            result = new DefaultPriceList();
+            result.setName(input.getName());
+            result.setPlans(toFilterDefaultPlans(input.getName()));
+            tmpDefaultPriceListMap.put(input.getName(), result);
         }
-        final PriceListDefault result = new PriceListDefault();
-        result.setName(input.getName());
-        result.setPlans(toFilterDefaultPlans(ImmutableList.copyOf(input.getPlans())));
         return result;
     }
 
+
     private Product toDefaultProduct(@Nullable final Product input) {
         if (input == null) {
             return null;
@@ -434,6 +422,7 @@ public class StandaloneCatalogMapper {
         result.setInitialPhases(toDefaultPlanPhases(ImmutableList.copyOf(input.getInitialPhases())));
         result.setPlansAllowedInBundle(input.getPlansAllowedInBundle());
         result.setProduct(toDefaultProduct(input.getProduct()));
+        result.setPriceListName(input.getPriceListName());
         return result;
     }
 
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 0511e71..e6e6cf1 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
@@ -323,7 +323,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return phase.compliesWithLimits(unit, value);
     }
 
-    public StandaloneCatalog setProducts(final Collection<Product> products) {
+    public StandaloneCatalog setProducts(final Iterable<Product> products) {
         this.products = new CatalogEntityCollection<Product>(products);
         return this;
     }
@@ -333,7 +333,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
         return this;
     }
 
-    public StandaloneCatalog setPlans(final Collection<Plan> plans) {
+    public StandaloneCatalog setPlans(final Iterable<Plan> plans) {
         this.plans = new CatalogEntityCollection<Plan>(plans);
         return this;
     }
diff --git a/catalog/src/test/resources/SpyCarAdvanced.xml b/catalog/src/test/resources/SpyCarAdvanced.xml
index f02a9cd..a08c425 100644
--- a/catalog/src/test/resources/SpyCarAdvanced.xml
+++ b/catalog/src/test/resources/SpyCarAdvanced.xml
@@ -458,6 +458,82 @@
                 </recurring>
             </finalPhase>
         </plan>
+        <plan name="cia-standard-monthly">
+            <product>Standard</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice> <!-- empty price implies $0 -->
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+                <phase type="DISCOUNT">
+                    <duration>
+                        <unit>MONTHS</unit>
+                        <number>3</number>
+                    </duration>
+                    <recurring>
+                        <billingPeriod>MONTHLY</billingPeriod>
+                        <recurringPrice>
+                            <price>
+                                <currency>GBP</currency>
+                                <value>25.00</value>
+                            </price>
+                            <price>
+                                <currency>EUR</currency>
+                                <value>30.00</value>
+                            </price>
+                            <price>
+                                <currency>USD</currency>
+                                <value>33.00</value>
+                            </price>
+                            <price>
+                                <currency>JPY</currency>
+                                <value>3.30</value>
+                            </price>
+                            <price>
+                                <currency>BTC</currency>
+                                <value>0.04</value>
+                            </price>
+                        </recurringPrice>
+                    </recurring>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>50.00</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>75.00</value>
+                        </price>
+                        <price>
+                            <currency>USD</currency>
+                            <value>90.00</value>
+                        </price>
+                        <price>
+                            <currency>JPY</currency>
+                            <value>8.00</value>
+                        </price>
+                        <price>
+                            <currency>BTC</currency>
+                            <value>0.08</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
         <plan name="discount-sports-monthly">
             <product>Sports</product>
             <initialPhases>
@@ -534,6 +610,82 @@
                 </recurring>
             </finalPhase>
         </plan>
+        <plan name="cia-sports-monthly">
+            <product>Sports</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice> <!-- empty price implies $0 -->
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+                <phase type="DISCOUNT">
+                    <duration>
+                        <unit>MONTHS</unit>
+                        <number>3</number>
+                    </duration>
+                    <recurring>
+                        <billingPeriod>MONTHLY</billingPeriod>
+                        <recurringPrice>
+                            <price>
+                                <currency>GBP</currency>
+                                <value>150.00</value>
+                            </price>
+                            <price>
+                                <currency>EUR</currency>
+                                <value>150.00</value>
+                            </price>
+                            <price>
+                                <currency>USD</currency>
+                                <value>250.00</value>
+                            </price>
+                            <price>
+                                <currency>JPY</currency>
+                                <value>20.30</value>
+                            </price>
+                            <price>
+                                <currency>BTC</currency>
+                                <value>0.2</value>
+                            </price>
+                        </recurringPrice>
+                    </recurring>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>300.00</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>375.00</value>
+                        </price>
+                        <price>
+                            <currency>USD</currency>
+                            <value>450.00</value>
+                        </price>
+                        <price>
+                            <currency>JPY</currency>
+                            <value>40.00</value>
+                        </price>
+                        <price>
+                            <currency>BTC</currency>
+                            <value>0.4</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
         <plan name="discount-super-monthly">
             <product>Super</product>
             <initialPhases>
@@ -807,17 +959,12 @@
                 <plan>discount-standard-monthly</plan>
                 <plan>discount-sports-monthly</plan>
                 <plan>discount-super-monthly</plan>
-                <plan>remotecontrol-monthly</plan>
-                <plan>oilslick-monthly</plan>
             </plans>
         </childPriceList>
         <childPriceList name="CIA">
             <plans>
-                <plan>discount-standard-monthly</plan>
-                <plan>discount-sports-monthly</plan>
-                <plan>discount-super-monthly</plan>
-                <plan>remotecontrol-monthly</plan>
-                <plan>oilslick-monthly</plan>
+                <plan>cia-standard-monthly</plan>
+                <plan>cia-sports-monthly</plan>
             </plans>
         </childPriceList>
     </priceLists>