killbill-memoizeit

jaxrs: new APIs to retrieve catalog objects for a given subscription

1/24/2018 9:04:33 PM

Details

diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CatalogJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CatalogJson.java
index 381089a..d5e6f75 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CatalogJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CatalogJson.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -92,37 +92,11 @@ public class CatalogJson {
             }
             ProductJson productJson = productMap.get(product.getName());
             if (productJson == null) {
-                productJson = new ProductJson(product.getCategory().toString(),
-                                              product.getName(),
-                                              toProductNames(product.getIncluded()),
-                                              toProductNames(product.getAvailable()));
+                productJson = new ProductJson(product);
                 productMap.put(product.getName(), productJson);
             }
 
-            // Build the phases associated with this plan
-            final List<PhaseJson> phases = new LinkedList<PhaseJson>();
-            for (final PlanPhase phase : plan.getAllPhases()) {
-                final List<PriceJson> prices = new LinkedList<PriceJson>();
-                if (phase.getRecurring() != null && phase.getRecurring().getRecurringPrice() != null) {
-                    for (final Price price : phase.getRecurring().getRecurringPrice().getPrices()) {
-                        prices.add(new PriceJson(price));
-                    }
-                }
-
-                final List<PriceJson> fixedPrices = new LinkedList<PriceJson>();
-                if (phase.getFixed() != null && phase.getFixed().getPrice() != null) {
-                    for (final Price price : phase.getFixed().getPrice().getPrices()) {
-                        fixedPrices.add(new PriceJson(price));
-                    }
-                }
-
-                final DurationJson durationJson = new DurationJson(phase.getDuration().getUnit(), phase.getDuration().getNumber());
-                final List<UsageJson> usagesJson = buildUsagesJson(phase.getUsages());
-                final PhaseJson phaseJson = new PhaseJson(phase.getPhaseType().toString(), prices, fixedPrices, durationJson, usagesJson);
-                phases.add(phaseJson);
-            }
-
-            final PlanJson planJson = new PlanJson(plan.getName(), plan.getRecurringBillingPeriod(), phases);
+            final PlanJson planJson = new PlanJson(plan);
             productJson.getPlans().add(planJson);
         }
 
@@ -135,53 +109,7 @@ public class CatalogJson {
 
     }
 
-    private List<UsageJson> buildUsagesJson(final Usage[] usages) throws CurrencyValueNull {
-        List<UsageJson> usagesJson = new ArrayList<UsageJson>();
-        for (int i = 0; i < usages.length; i++) {
-            usagesJson.add(new UsageJson(usages[i].getBillingPeriod().toString(), buildTiers(usages[i].getTiers())));
-        }
-        return usagesJson;
-    }
-
-    private List<TierJson> buildTiers(final Tier[] tiers) throws CurrencyValueNull {
-        List<TierJson> tiersJson = new ArrayList<TierJson>();
-        if (tiers != null && tiers.length > 0) {
-            for (int i=0; i < tiers.length; i++) {
-                tiersJson.add(new TierJson(buildTieredBlocks(tiers[i].getTieredBlocks()),
-                                           buildLimits(tiers[i].getLimits()),
-                                           buildPrices(tiers[i].getFixedPrice()),
-                                           buildPrices(tiers[i].getRecurringPrice())));
-            }
-        }
-        return tiersJson;
-    }
-
-    private List<LimitJson> buildLimits(final Limit[] limits) {
-        List<LimitJson> limitsJson = new ArrayList<LimitJson>();
-        if (limits != null && limits.length > 0) {
-            for (int i=0; i < limits.length; i++) {
-                limitsJson.add(new LimitJson(limits[i].getUnit().getName(),
-                                             limits[i].getMax().toString(),
-                                             limits[i].getMin().toString()));
-            }
-        }
-        return limitsJson;
-    }
-
-    private List<TieredBlockJson> buildTieredBlocks(final TieredBlock[] tieredBlocks) throws CurrencyValueNull {
-        List<TieredBlockJson> tieredBlocksJson = new ArrayList<TieredBlockJson>();
-        if (tieredBlocks != null && tieredBlocks.length > 0) {
-            for (int i=0; i < tieredBlocks.length; i++) {
-                tieredBlocksJson.add(new TieredBlockJson(tieredBlocks[i].getUnit().getName(),
-                                                         tieredBlocks[i].getSize().toString(),
-                                                         tieredBlocks[i].getMax().toString(),
-                                                         buildPrices(tieredBlocks[i].getPrice())));
-            }
-        }
-        return tieredBlocksJson;
-    }
-
-    private List<PriceJson> buildPrices(final InternationalPrice internationalPrice) throws CurrencyValueNull {
+    private static List<PriceJson> buildPrices(final InternationalPrice internationalPrice) throws CurrencyValueNull {
         List<PriceJson> pricesJson = new ArrayList<PriceJson>();
         Price[] prices = (internationalPrice != null) ? internationalPrice.getPrices() : null;
         if (prices != null && prices.length > 0) {
@@ -193,16 +121,6 @@ public class CatalogJson {
         return pricesJson;
     }
 
-    private List<String> toProductNames(final Collection<Product> in) {
-        return Lists.transform(ImmutableList.<Product>copyOf(in),
-                               new Function<Product, String>() {
-                                   @Override
-                                   public String apply(final Product input) {
-                                       return input.getName();
-                                   }
-                               });
-    }
-
     public List<ProductJson> getProducts() {
         return products;
     }
@@ -295,8 +213,12 @@ public class CatalogJson {
             this.available = available;
         }
 
-        public ProductJson(final String type, final String name, final List<String> included, final List<String> available) {
-            this(type, name, new LinkedList<PlanJson>(), included, available);
+        public ProductJson(final Product product) {
+            this.type = product.getCategory().toString();
+            this.name = product.getName();
+            this.plans = new LinkedList<PlanJson>();
+            this.included = toProductNames(product.getIncluded());
+            this.available = toProductNames(product.getAvailable());
         }
 
         public String getType() {
@@ -370,6 +292,16 @@ public class CatalogJson {
             result = 31 * result + (available != null ? available.hashCode() : 0);
             return result;
         }
+
+        private List<String> toProductNames(final Collection<Product> in) {
+            return Lists.transform(ImmutableList.<Product>copyOf(in),
+                                   new Function<Product, String>() {
+                                       @Override
+                                       public String apply(final Product input) {
+                                           return input.getName();
+                                       }
+                                   });
+        }
     }
 
     public static class PlanJson {
@@ -378,6 +310,18 @@ public class CatalogJson {
         private final BillingPeriod billingPeriod;
         private final List<PhaseJson> phases;
 
+        public PlanJson(final Plan plan) throws CurrencyValueNull {
+            final List<PhaseJson> phases = new LinkedList<PhaseJson>();
+            for (final PlanPhase phase : plan.getAllPhases()) {
+                final PhaseJson phaseJson = new PhaseJson(phase);
+                phases.add(phaseJson);
+            }
+
+            this.name = plan.getName();
+            this.billingPeriod = plan.getRecurringBillingPeriod();
+            this.phases = phases;
+        }
+
         @JsonCreator
         public PlanJson(@JsonProperty("name") final String name,
                         @JsonProperty("billingPeriod") final BillingPeriod billingPeriod,
@@ -729,6 +673,31 @@ public class CatalogJson {
         private final DurationJson duration;
         private final List<UsageJson> usages;
 
+        public PhaseJson(final PlanPhase phase) throws CurrencyValueNull {
+            final List<PriceJson> prices = new LinkedList<PriceJson>();
+            if (phase.getRecurring() != null && phase.getRecurring().getRecurringPrice() != null) {
+                for (final Price price : phase.getRecurring().getRecurringPrice().getPrices()) {
+                    prices.add(new PriceJson(price));
+                }
+            }
+
+            final List<PriceJson> fixedPrices = new LinkedList<PriceJson>();
+            if (phase.getFixed() != null && phase.getFixed().getPrice() != null) {
+                for (final Price price : phase.getFixed().getPrice().getPrices()) {
+                    fixedPrices.add(new PriceJson(price));
+                }
+            }
+
+            final DurationJson durationJson = new DurationJson(phase.getDuration().getUnit(), phase.getDuration().getNumber());
+            final List<UsageJson> usagesJson = buildUsagesJson(phase.getUsages());
+
+            this.type = phase.getPhaseType().toString();
+            this.prices = prices;
+            this.fixedPrices = fixedPrices;
+            this.duration = durationJson;
+            this.usages = usagesJson;
+        }
+
         @JsonCreator
         public PhaseJson(@JsonProperty("type") final String type,
                          @JsonProperty("prices") final List<PriceJson> prices,
@@ -745,15 +714,19 @@ public class CatalogJson {
         public String getType() {
             return type;
         }
+
         public List<PriceJson> getPrices() {
             return prices;
         }
+
         public List<PriceJson> getFixedPrices() {
             return fixedPrices;
         }
+
         public DurationJson getDuration() {
             return duration;
         }
+
         public List<UsageJson> getUsages() {
             return usages;
         }
@@ -793,7 +766,7 @@ public class CatalogJson {
             if (duration != null ? !duration.equals(phaseJson.duration) : phaseJson.duration != null) {
                 return false;
             }
-            if (usages != null ? !usages.equals(phaseJson.usages) : phaseJson.usages!= null) {
+            if (usages != null ? !usages.equals(phaseJson.usages) : phaseJson.usages != null) {
                 return false;
             }
 
@@ -809,6 +782,52 @@ public class CatalogJson {
             result = 31 * result + (usages != null ? usages.hashCode() : 0);
             return result;
         }
+
+        private List<UsageJson> buildUsagesJson(final Usage[] usages) throws CurrencyValueNull {
+            List<UsageJson> usagesJson = new ArrayList<UsageJson>();
+            for (int i = 0; i < usages.length; i++) {
+                usagesJson.add(new UsageJson(usages[i].getBillingPeriod().toString(), buildTiers(usages[i].getTiers())));
+            }
+            return usagesJson;
+        }
+
+        private List<TierJson> buildTiers(final Tier[] tiers) throws CurrencyValueNull {
+            List<TierJson> tiersJson = new ArrayList<TierJson>();
+            if (tiers != null && tiers.length > 0) {
+                for (int i = 0; i < tiers.length; i++) {
+                    tiersJson.add(new TierJson(buildTieredBlocks(tiers[i].getTieredBlocks()),
+                                               buildLimits(tiers[i].getLimits()),
+                                               buildPrices(tiers[i].getFixedPrice()),
+                                               buildPrices(tiers[i].getRecurringPrice())));
+                }
+            }
+            return tiersJson;
+        }
+
+        private List<LimitJson> buildLimits(final Limit[] limits) {
+            List<LimitJson> limitsJson = new ArrayList<LimitJson>();
+            if (limits != null && limits.length > 0) {
+                for (int i = 0; i < limits.length; i++) {
+                    limitsJson.add(new LimitJson(limits[i].getUnit().getName(),
+                                                 limits[i].getMax().toString(),
+                                                 limits[i].getMin().toString()));
+                }
+            }
+            return limitsJson;
+        }
+
+        private List<TieredBlockJson> buildTieredBlocks(final TieredBlock[] tieredBlocks) throws CurrencyValueNull {
+            List<TieredBlockJson> tieredBlocksJson = new ArrayList<TieredBlockJson>();
+            if (tieredBlocks != null && tieredBlocks.length > 0) {
+                for (int i = 0; i < tieredBlocks.length; i++) {
+                    tieredBlocksJson.add(new TieredBlockJson(tieredBlocks[i].getUnit().getName(),
+                                                             tieredBlocks[i].getSize().toString(),
+                                                             tieredBlocks[i].getMax().toString(),
+                                                             buildPrices(tieredBlocks[i].getPrice())));
+                }
+            }
+            return tieredBlocksJson;
+        }
     }
 
     public static class PriceJson {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
index 98b2f84..e78a955 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -18,6 +20,7 @@ package org.killbill.billing.jaxrs.resources;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.UUID;
 
 import javax.annotation.Nullable;
 import javax.servlet.http.HttpServletRequest;
@@ -34,16 +37,30 @@ import javax.ws.rs.core.UriInfo;
 
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.catalog.StandaloneCatalog;
 import org.killbill.billing.catalog.VersionedCatalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.CatalogUserApi;
+import org.killbill.billing.catalog.api.CurrencyValueNull;
 import org.killbill.billing.catalog.api.Listing;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.SimplePlanDescriptor;
 import org.killbill.billing.catalog.api.StaticCatalog;
 import org.killbill.billing.catalog.api.user.DefaultSimplePlanDescriptor;
+import org.killbill.billing.entitlement.api.Subscription;
+import org.killbill.billing.entitlement.api.SubscriptionApi;
+import org.killbill.billing.entitlement.api.SubscriptionApiException;
+import org.killbill.billing.entitlement.api.SubscriptionEvent;
 import org.killbill.billing.jaxrs.json.CatalogJson;
+import org.killbill.billing.jaxrs.json.CatalogJson.PhaseJson;
+import org.killbill.billing.jaxrs.json.CatalogJson.PlanJson;
+import org.killbill.billing.jaxrs.json.CatalogJson.PriceListJson;
+import org.killbill.billing.jaxrs.json.CatalogJson.ProductJson;
 import org.killbill.billing.jaxrs.json.PlanDetailJson;
 import org.killbill.billing.jaxrs.json.SimplePlanJson;
 import org.killbill.billing.jaxrs.util.Context;
@@ -73,6 +90,7 @@ import static javax.ws.rs.core.MediaType.APPLICATION_XML;
 public class CatalogResource extends JaxRsResourceBase {
 
     private final CatalogUserApi catalogUserApi;
+    private final SubscriptionApi subscriptionApi;
 
     @Inject
     public CatalogResource(final JaxrsUriBuilder uriBuilder,
@@ -82,10 +100,12 @@ public class CatalogResource extends JaxRsResourceBase {
                            final AccountUserApi accountUserApi,
                            final PaymentApi paymentApi,
                            final CatalogUserApi catalogUserApi,
+                           final SubscriptionApi subscriptionApi,
                            final Clock clock,
                            final Context context) {
         super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.catalogUserApi = catalogUserApi;
+        this.subscriptionApi = subscriptionApi;
     }
 
     @TimedResource
@@ -124,8 +144,8 @@ public class CatalogResource extends JaxRsResourceBase {
 
         final TenantContext tenantContext = context.createContext(request);
         final DateTime catalogDateVersion = requestedDate != null ?
-                                      DATE_TIME_FORMATTER.parseDateTime(requestedDate).toDateTime(DateTimeZone.UTC) :
-                                      null;
+                                            DATE_TIME_FORMATTER.parseDateTime(requestedDate).toDateTime(DateTimeZone.UTC) :
+                                            null;
 
         // Yack...
         final VersionedCatalog catalog = (VersionedCatalog) catalogUserApi.getCatalog(catalogName, tenantContext);
@@ -191,6 +211,136 @@ public class CatalogResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(details).build();
     }
 
+    @TimedResource
+    @GET
+    @Path("/plan")
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Retrieve plan for a given subscription and date", response = PlanJson.class)
+    @ApiResponses(value = {})
+    public Response getPlanForSubscriptionAndDate(@QueryParam("subscriptionId") final String subscriptionIdString,
+                                                  @QueryParam("requestedDate") final String requestedDateString,
+                                                  @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, CurrencyValueNull {
+        verifyNonNullOrEmpty(subscriptionIdString, "Subscription id needs to be specified");
+
+        final SubscriptionEvent lastEventBeforeRequestedDate = getLastEventBeforeDate(subscriptionIdString, requestedDateString, request);
+        if (lastEventBeforeRequestedDate == null) {
+            return Response.status(Status.NOT_FOUND).build();
+        }
+
+        final Plan plan = lastEventBeforeRequestedDate.getNextPlan();
+        if (plan == null) {
+            // Subscription was cancelled at that point
+            return Response.status(Status.NOT_FOUND).build();
+        }
+
+        final PlanJson planJson = new PlanJson(plan);
+        return Response.status(Status.OK).entity(planJson).build();
+    }
+
+    @TimedResource
+    @GET
+    @Path("/phase")
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Retrieve phase for a given subscription and date", response = PhaseJson.class)
+    @ApiResponses(value = {})
+    public Response getPhaseForSubscriptionAndDate(@QueryParam("subscriptionId") final String subscriptionIdString,
+                                                   @QueryParam("requestedDate") final String requestedDateString,
+                                                   @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, CurrencyValueNull {
+        verifyNonNullOrEmpty(subscriptionIdString, "Subscription id needs to be specified");
+
+        final SubscriptionEvent lastEventBeforeRequestedDate = getLastEventBeforeDate(subscriptionIdString, requestedDateString, request);
+        if (lastEventBeforeRequestedDate == null) {
+            return Response.status(Status.NOT_FOUND).build();
+        }
+
+        final PlanPhase phase = lastEventBeforeRequestedDate.getNextPhase();
+        if (phase == null) {
+            // Subscription was cancelled at that point
+            return Response.status(Status.NOT_FOUND).build();
+        }
+
+        final PhaseJson phaseJson = new PhaseJson(phase);
+        return Response.status(Status.OK).entity(phaseJson).build();
+    }
+
+    @TimedResource
+    @GET
+    @Path("/product")
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Retrieve product for a given subscription and date", response = ProductJson.class)
+    @ApiResponses(value = {})
+    public Response getProductForSubscriptionAndDate(@QueryParam("subscriptionId") final String subscriptionIdString,
+                                                     @QueryParam("requestedDate") final String requestedDateString,
+                                                     @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, CurrencyValueNull {
+        verifyNonNullOrEmpty(subscriptionIdString, "Subscription id needs to be specified");
+
+        final SubscriptionEvent lastEventBeforeRequestedDate = getLastEventBeforeDate(subscriptionIdString, requestedDateString, request);
+        if (lastEventBeforeRequestedDate == null) {
+            return Response.status(Status.NOT_FOUND).build();
+        }
+
+        final Product product = lastEventBeforeRequestedDate.getNextProduct();
+        if (product == null) {
+            // Subscription was cancelled at that point
+            return Response.status(Status.NOT_FOUND).build();
+        }
+
+        final ProductJson productJson = new ProductJson(product);
+        return Response.status(Status.OK).entity(productJson).build();
+    }
+
+    @TimedResource
+    @GET
+    @Path("/priceList")
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Retrieve priceList for a given subscription and date", response = PriceListJson.class)
+    @ApiResponses(value = {})
+    public Response getPriceListForSubscriptionAndDate(@QueryParam("subscriptionId") final String subscriptionIdString,
+                                                       @QueryParam("requestedDate") final String requestedDateString,
+                                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, CurrencyValueNull {
+        verifyNonNullOrEmpty(subscriptionIdString, "Subscription id needs to be specified");
+
+        final SubscriptionEvent lastEventBeforeRequestedDate = getLastEventBeforeDate(subscriptionIdString, requestedDateString, request);
+        if (lastEventBeforeRequestedDate == null) {
+            return Response.status(Status.NOT_FOUND).build();
+        }
+
+        final PriceList priceList = lastEventBeforeRequestedDate.getNextPriceList();
+        if (priceList == null) {
+            // Subscription was cancelled at that point
+            return Response.status(Status.NOT_FOUND).build();
+        }
+
+        final PriceListJson priceListJson = new PriceListJson(priceList);
+        return Response.status(Status.OK).entity(priceListJson).build();
+    }
+
+    private SubscriptionEvent getLastEventBeforeDate(final String subscriptionIdString, final String requestedDateString, final HttpServletRequest request) throws SubscriptionApiException {
+        final TenantContext tenantContext = context.createContext(request);
+        final DateTime requestedDateTime = requestedDateString != null ?
+                                           DATE_TIME_FORMATTER.parseDateTime(requestedDateString).toDateTime(DateTimeZone.UTC) :
+                                           clock.getUTCNow();
+        final LocalDate requestedDate = requestedDateTime.toLocalDate();
+
+        final Subscription subscription = subscriptionApi.getSubscriptionForEntitlementId(UUID.fromString(subscriptionIdString), tenantContext);
+        SubscriptionEvent lastEventBeforeRequestedDate = null;
+        for (final SubscriptionEvent subscriptionEvent : subscription.getSubscriptionEvents()) {
+            if (lastEventBeforeRequestedDate == null) {
+                if (subscriptionEvent.getEffectiveDate().compareTo(requestedDate) > 0) {
+                    // requestedDate too far in the past, before subscription start date
+                    return null;
+                }
+                lastEventBeforeRequestedDate = subscriptionEvent;
+            }
+            if (subscriptionEvent.getEffectiveDate().compareTo(requestedDate) > 0) {
+                break;
+            } else {
+                lastEventBeforeRequestedDate = subscriptionEvent;
+            }
+        }
+
+        return lastEventBeforeRequestedDate;
+    }
 
     @TimedResource
     @POST
@@ -200,11 +350,11 @@ public class CatalogResource extends JaxRsResourceBase {
     @ApiOperation(value = "Upload the full catalog as XML")
     @ApiResponses(value = {})
     public Response addSimplePlan(final SimplePlanJson simplePlan,
-                                     @HeaderParam(HDR_CREATED_BY) final String createdBy,
-                                     @HeaderParam(HDR_REASON) final String reason,
-                                     @HeaderParam(HDR_COMMENT) final String comment,
-                                     @javax.ws.rs.core.Context final HttpServletRequest request,
-                                     @javax.ws.rs.core.Context final UriInfo uriInfo) throws Exception {
+                                  @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                  @HeaderParam(HDR_REASON) final String reason,
+                                  @HeaderParam(HDR_COMMENT) final String comment,
+                                  @javax.ws.rs.core.Context final HttpServletRequest request,
+                                  @javax.ws.rs.core.Context final UriInfo uriInfo) throws Exception {
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
 
         final SimplePlanDescriptor desc = new DefaultSimplePlanDescriptor(simplePlan.getPlanId(),