killbill-memoizeit
Changes
account/pom.xml 2(+1 -1)
api/pom.xml 2(+1 -1)
beatrix/pom.xml 2(+1 -1)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java 26(+15 -11)
bin/start-server 2(+1 -1)
catalog/pom.xml 2(+1 -1)
currency/pom.xml 2(+1 -1)
entitlement/pom.xml 2(+1 -1)
invoice/pom.xml 2(+1 -1)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java 160(+30 -130)
invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java 9(+4 -5)
invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java 5(+5 -0)
invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java 2(+1 -1)
invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java 28(+22 -6)
invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java 2(+1 -1)
jaxrs/pom.xml 7(+6 -1)
junction/pom.xml 2(+1 -1)
NEWS 3(+3 -0)
overdue/pom.xml 2(+1 -1)
payment/pom.xml 2(+1 -1)
payment/src/main/java/org/killbill/billing/payment/core/janitor/AttemptCompletionTask.java 128(+128 -0)
payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java 124(+124 -0)
payment/src/main/java/org/killbill/billing/payment/core/janitor/ErroredPaymentTask.java 163(+163 -0)
payment/src/main/java/org/killbill/billing/payment/core/janitor/PendingTransactionTask.java 68(+68 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentEnteringStateCallback.java 37(+15 -22)
payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java 123(+111 -12)
pom.xml 4(+2 -2)
profiles/killbill/pom.xml 2(+1 -1)
profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcRealm.java 2(+1 -1)
profiles/killpay/pom.xml 2(+1 -1)
profiles/pom.xml 2(+1 -1)
subscription/pom.xml 2(+1 -1)
tenant/pom.xml 2(+1 -1)
usage/pom.xml 2(+1 -1)
util/pom.xml 2(+1 -1)
Details
account/pom.xml 2(+1 -1)
diff --git a/account/pom.xml b/account/pom.xml
index 2bc7a7b..1230ad0 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-account</artifactId>
api/pom.xml 2(+1 -1)
diff --git a/api/pom.xml b/api/pom.xml
index 9129b20..1032709 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-internal-api</artifactId>
beatrix/pom.xml 2(+1 -1)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index d6f1f37..6444b3a 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-beatrix</artifactId>
diff --git a/beatrix/src/main/resources/org/killbill/billing/beatrix/ddl.sql b/beatrix/src/main/resources/org/killbill/billing/beatrix/ddl.sql
index 2783fc6..f7811c7 100644
--- a/beatrix/src/main/resources/org/killbill/billing/beatrix/ddl.sql
+++ b/beatrix/src/main/resources/org/killbill/billing/beatrix/ddl.sql
@@ -21,7 +21,7 @@ CREATE INDEX bus_ext_events_tenant_account_record_id ON bus_ext_events(search_ke
DROP TABLE IF EXISTS bus_ext_events_history;
CREATE TABLE bus_ext_events_history (
- record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ record_id int(11) unsigned NOT NULL,
class_name varchar(128) NOT NULL,
event_json varchar(2048) NOT NULL,
user_token char(36),
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
index 84f8329..b5096a2 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
@@ -35,17 +35,16 @@ import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.usage.api.RolledUpUsage;
+import org.killbill.billing.usage.api.SubscriptionUsageRecord;
+import org.killbill.billing.usage.api.UnitUsageRecord;
+import org.killbill.billing.usage.api.UsageRecord;
import org.killbill.billing.usage.api.UsageUserApi;
-import org.killbill.billing.usage.api.user.DefaultRolledUpUsage;
-import org.killbill.billing.usage.api.user.MockUsageUserApi;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.TenantContext;
import org.mockito.Mockito;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-import com.google.common.collect.ImmutableList;
-
public class TestConsumableInArrear extends TestIntegrationBase {
@BeforeMethod(groups = "slow")
@@ -55,7 +54,7 @@ public class TestConsumableInArrear extends TestIntegrationBase {
protected UsageUserApi createMockUsageUserApi(final List<RolledUpUsage> returnValue) {
final UsageUserApi result = Mockito.mock(UsageUserApi.class);
- Mockito.when(result.getAllUsageForSubscription(Mockito.<UUID>any(), Mockito.<Set<String>>any(), Mockito.<List<DateTime>>any(), Mockito.<TenantContext>any())).thenReturn(returnValue);
+ Mockito.when(result.getAllUsageForSubscription(Mockito.<UUID>any(), Mockito.<List<LocalDate>>any(), Mockito.<TenantContext>any())).thenReturn(returnValue);
return result;
}
@@ -85,8 +84,8 @@ public class TestConsumableInArrear extends TestIntegrationBase {
//
final DefaultEntitlement aoSubscription = addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Bullets", ProductCategory.ADD_ON, BillingPeriod.NO_BILLING_PERIOD, NextEvent.CREATE);
- setUsage(aoSubscription.getId(), "bullets", new DateTime(2012, 4, 1, 1, 1, DateTimeZone.UTC), new DateTime(2012, 4, 15, 0, 0, DateTimeZone.UTC), new BigDecimal("99"), callContext);
- setUsage(aoSubscription.getId(), "bullets", new DateTime(2012, 4, 15, 0, 0, DateTimeZone.UTC), new DateTime(2012, 4, 20, 1, 1, DateTimeZone.UTC), new BigDecimal("100"), callContext);
+ setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 1), 99L, callContext);
+ setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 15), 100L, callContext);
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
clock.setDay(new LocalDate(2012, 5, 1));
@@ -105,8 +104,8 @@ public class TestConsumableInArrear extends TestIntegrationBase {
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.USAGE, BigDecimal.ZERO));
- setUsage(aoSubscription.getId(), "bullets", new DateTime(2012, 6, 1, 1, 1, DateTimeZone.UTC), new DateTime(2012, 6, 15, 1, 1, DateTimeZone.UTC), new BigDecimal("50"), callContext);
- setUsage(aoSubscription.getId(), "bullets", new DateTime(2012, 6, 16, 1, 1, DateTimeZone.UTC), new DateTime(2012, 6, 20, 1, 1, DateTimeZone.UTC), new BigDecimal("300"), callContext);
+ setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 6, 1), 50L, callContext);
+ setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 6, 16), 300L, callContext);
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
@@ -118,7 +117,12 @@ public class TestConsumableInArrear extends TestIntegrationBase {
}
- private void setUsage(final UUID subscriptionId, final String unitType, final DateTime startTime, final DateTime endTime, final BigDecimal amount, final CallContext context) {
- usageUserApi.recordRolledUpUsage(subscriptionId, unitType, startTime, endTime, amount, context);
+ private void setUsage(final UUID subscriptionId, final String unitType, final LocalDate startDate, final Long amount, final CallContext context) {
+ final List<UsageRecord> usageRecords = new ArrayList<UsageRecord>();
+ usageRecords.add(new UsageRecord(startDate, amount));
+ final List<UnitUsageRecord> unitUsageRecords = new ArrayList<UnitUsageRecord>();
+ unitUsageRecords.add(new UnitUsageRecord(unitType, usageRecords));
+ final SubscriptionUsageRecord record = new SubscriptionUsageRecord(subscriptionId, unitUsageRecords);
+ usageUserApi.recordRolledUpUsage(record, context);
}
}
bin/start-server 2(+1 -1)
diff --git a/bin/start-server b/bin/start-server
index 755c267..407ef39 100755
--- a/bin/start-server
+++ b/bin/start-server
@@ -30,7 +30,7 @@ DEBUG_OPTS_ECLIPSE=" -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,ad
DEBUG_OPTS_ECLIPSE_WAIT=" -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=12345 "
# Default JVM settings if unset
-MAVEN_OPTS=${MAVEN_OPTS-"-Xms512m -Xmx1024m -XX:MaxPermSize=512m -XX:MaxDirectMemorySize=512m -XX:+UseConcMarkSweepGC"}
+MAVEN_OPTS=${MAVEN_OPTS-"-Dcom.sun.management.jmxremote.port=8989 -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Xms512m -Xmx1024m -XX:MaxPermSize=512m -XX:MaxDirectMemorySize=512m -XX:+UseConcMarkSweepGC"}
LOG="$SERVER/src/main/resources/logback.xml"
LOG_DIR="$SERVER/logs"
catalog/pom.xml 2(+1 -1)
diff --git a/catalog/pom.xml b/catalog/pom.xml
index 129e5df..5d5f11d 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-catalog</artifactId>
currency/pom.xml 2(+1 -1)
diff --git a/currency/pom.xml b/currency/pom.xml
index 1e5edc2..fd1692b 100644
--- a/currency/pom.xml
+++ b/currency/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-currency</artifactId>
entitlement/pom.xml 2(+1 -1)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 30db7f4..5cf7590 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-entitlement</artifactId>
invoice/pom.xml 2(+1 -1)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index d68dee7..5d3ddae 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-invoice</artifactId>
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
index 89a9905..66eb264 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -135,7 +135,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
final UUID subscriptionId = event.getSubscription().getId();
if (curSubscriptionId != null && !curSubscriptionId.equals(subscriptionId)) {
- final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, usageApi, targetDate, context.toTenantContext(tenantId));
+ final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, usageApi, config.isInsertZeroUsageItems(), targetDate, context.toTenantContext(tenantId));
items.addAll(subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(extractUsageItemsForSubscription(curSubscriptionId, existingInvoices)));
curEvents = Lists.newArrayList();
}
@@ -143,7 +143,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
curEvents.add(event);
}
if (curSubscriptionId != null) {
- final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, usageApi, targetDate, context.toTenantContext(tenantId));
+ final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, usageApi, config.isInsertZeroUsageItems(), targetDate, context.toTenantContext(tenantId));
items.addAll(subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(extractUsageItemsForSubscription(curSubscriptionId, existingInvoices)));
}
return items;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java
index 69beb39..2b6349b 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java
@@ -18,15 +18,12 @@ package org.killbill.billing.invoice.usage;
import java.math.BigDecimal;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
+import java.util.Comparator;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
-import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.killbill.billing.catalog.api.CatalogApiException;
@@ -38,15 +35,16 @@ import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.generator.BillingIntervalDetail;
import org.killbill.billing.invoice.model.UsageInvoiceItem;
import org.killbill.billing.junction.BillingEvent;
+import org.killbill.billing.usage.api.RolledUpUnit;
import org.killbill.billing.usage.api.RolledUpUsage;
import org.killbill.billing.usage.api.UsageUserApi;
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.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -59,6 +57,8 @@ import static org.killbill.billing.invoice.usage.UsageUtils.getConsumableInArrea
*/
public class ContiguousIntervalConsumableInArrear {
+ private static final Logger log = LoggerFactory.getLogger(ContiguousIntervalConsumableInArrear.class);
+
private final List<LocalDate> transitionTimes;
private final List<BillingEvent> billingEvents;
@@ -69,12 +69,14 @@ public class ContiguousIntervalConsumableInArrear {
private final UUID invoiceId;
private final TenantContext context;
private final AtomicBoolean isBuilt;
+ private final boolean insertZeroAmountItems;
- public ContiguousIntervalConsumableInArrear(final Usage usage, final UUID invoiceId, final UsageUserApi usageApi, final LocalDate targetDate, final TenantContext context) {
+ public ContiguousIntervalConsumableInArrear(final Usage usage, final UUID invoiceId, final UsageUserApi usageApi, final boolean insertZeroAmountItems, final LocalDate targetDate, final TenantContext context) {
this.usage = usage;
this.invoiceId = invoiceId;
this.unitTypes = getConsumableInArrearUnitTypes(usage);
this.usageApi = usageApi;
+ this.insertZeroAmountItems = insertZeroAmountItems;
this.targetDate = targetDate;
this.context = context;
this.billingEvents = Lists.newLinkedList();
@@ -134,35 +136,39 @@ public class ContiguousIntervalConsumableInArrear {
final List<InvoiceItem> result = Lists.newLinkedList();
- final RolledUpUsageForUnitTypesFactory factory = new RolledUpUsageForUnitTypesFactory(getRolledUpUsage(), unitTypes, getAccountTimeZone());
-
- for (RolledUpUsageForUnitTypes ru : factory.getOrderedRolledUpUsageForUnitTypes()) {
+ final List<RolledUpUsage> allUsage = getRolledUpUsage();
+ for (final RolledUpUsage ru : allUsage) {
// Compute total price amount that should be billed for that period of time (and usage section) across unitTypes.
BigDecimal toBeBilledUsage = BigDecimal.ZERO;
- for (final String unitType : unitTypes) {
- final BigDecimal usageAmountForUnitType = ru.getUsageAmountForUnitType(unitType);
- final BigDecimal toBeBilledForUnit = computeToBeBilledUsage(usageAmountForUnitType, unitType);
+ for (final RolledUpUnit cur : ru.getRolledUpUnit()) {
+ if (!unitTypes.contains(cur.getUnitType())) {
+ log.warn("ContiguousIntervalConsumableInArrear is skipping unitType " + cur.getUnitType());
+ continue;
+ }
+
+ final BigDecimal toBeBilledForUnit = computeToBeBilledUsage(cur.getAmount(), cur.getUnitType());
toBeBilledUsage = toBeBilledUsage.add(toBeBilledForUnit);
+
}
// Retrieves current price amount billed for that period of time (and usage section)
- final Iterable<InvoiceItem> billedItems = getBilledItems(ru.getStartDate(), ru.getEndDate(), existingUsage);
+ final Iterable<InvoiceItem> billedItems = getBilledItems(ru.getStart(), ru.getEnd(), existingUsage);
final BigDecimal billedUsage = computeBilledUsage(billedItems);
- // Compare the two and add the missing piece if required. If there has never been any billed item for the period
- // and if there is nothing to bill for we would also insert a $0 amount
+ // Compare the two and add the missing piece if required.
if (!billedItems.iterator().hasNext() || billedUsage.compareTo(toBeBilledUsage) < 0) {
- InvoiceItem item = new UsageInvoiceItem(invoiceId, getAccountId(), getBundleId(), getSubscriptionId(), getPlanName(),
- getPhaseName(), usage.getName(), ru.getStartDate(), ru.getEndDate(), toBeBilledUsage.subtract(billedUsage), getCurrency());
- result.add(item);
+ final BigDecimal amountToBill = toBeBilledUsage.subtract(billedUsage);
+ if (amountToBill.compareTo(BigDecimal.ZERO) > 0 || insertZeroAmountItems) {
+ InvoiceItem item = new UsageInvoiceItem(invoiceId, getAccountId(), getBundleId(), getSubscriptionId(), getPlanName(),
+ getPhaseName(), usage.getName(), ru.getStart(), ru.getEnd(), amountToBill, getCurrency());
+ result.add(item);
+ }
}
-
}
return result;
}
/**
- *
* @return a list of {@code RolledUpUsage} for each period (between two transitions) * each unitType.
*/
List<RolledUpUsage> getRolledUpUsage() {
@@ -170,25 +176,17 @@ public class ContiguousIntervalConsumableInArrear {
if (transitionTimes.size() <= 1) {
return Collections.emptyList();
}
-
- final Iterable<DateTime> transitions = Iterables.transform(transitionTimes, new Function<LocalDate, DateTime>() {
- @Override
- public DateTime apply(final LocalDate input) {
- return localDateToEndOfDayInAccountTimezone(input, getAccountTimeZone());
- }
- });
- return usageApi.getAllUsageForSubscription(getSubscriptionId(), unitTypes, ImmutableList.copyOf(transitions), context);
+ return usageApi.getAllUsageForSubscription(getSubscriptionId(), transitionTimes, context);
}
/**
- *
* @param nbUnits the number of used units for a given period
* @param unitType the type of unit
- * @return the price amount that should be billed for that period/unitType
+ * @return the price amount that should be billed for that period/unitType
* @throws CatalogApiException
*/
@VisibleForTesting
- BigDecimal computeToBeBilledUsage(final BigDecimal nbUnits, final String unitType) throws CatalogApiException {
+ BigDecimal computeToBeBilledUsage(final Long nbUnits, final String unitType) throws CatalogApiException {
Preconditions.checkState(isBuilt.get());
@@ -213,7 +211,6 @@ public class ContiguousIntervalConsumableInArrear {
}
/**
- *
* @param filteredUsageForInterval the list of invoiceItem to consider
* @return the price amount that was already billed for that period and usage section (across unitTypes)
*/
@@ -238,7 +235,6 @@ public class ContiguousIntervalConsumableInArrear {
if (input.getInvoiceItemType() != InvoiceItemType.USAGE) {
return false;
}
-
// STEPH what happens if we discover usage period that overlap (one side or both side) the [startDate, endDate] interval
final UsageInvoiceItem usageInput = (UsageInvoiceItem) input;
return usageInput.getUsageName().equals(usage.getName()) &&
@@ -253,97 +249,6 @@ public class ContiguousIntervalConsumableInArrear {
return transitionTimes;
}
- private static class RolledUpUsageForUnitTypesFactory {
-
- private final Map<String, RolledUpUsageForUnitTypes> map;
-
- public RolledUpUsageForUnitTypesFactory(final List<RolledUpUsage> rolledUpUsages, final Set<String> unitTypes, final DateTimeZone accountTimeZone) {
- map = new HashMap<String, RolledUpUsageForUnitTypes>();
- for (RolledUpUsage ru : rolledUpUsages) {
-
- final LocalDate startRolledUpDate = new LocalDate(ru.getStartTime(), accountTimeZone);
- final LocalDate endRolledUpDate = new LocalDate(ru.getEndTime(), accountTimeZone);
- final String key = startRolledUpDate + "-" + endRolledUpDate;
-
- RolledUpUsageForUnitTypes usageForUnitTypes = map.get(key);
- if (usageForUnitTypes == null) {
- usageForUnitTypes = new RolledUpUsageForUnitTypes(startRolledUpDate, endRolledUpDate, unitTypes);
- map.put(key, usageForUnitTypes);
- }
- usageForUnitTypes.addUsageForUnit(ru.getUnitType(), ru.getAmount());
- }
- }
-
- public List<RolledUpUsageForUnitTypes> getOrderedRolledUpUsageForUnitTypes() {
- final LinkedList<RolledUpUsageForUnitTypes> result = new LinkedList<RolledUpUsageForUnitTypes>(map.values());
- Collections.sort(result);
- return result;
- }
- }
-
- /**
- * Internal classes to transform RolledUpUsage into a map of usage (types, amount) across each billable interval.
- */
- private static class RolledUpUsageForUnitTypes implements Comparable {
-
- private final LocalDate startDate;
- private final LocalDate endDate;
- private final Map<String, BigDecimal> unitAmounts;
-
- private RolledUpUsageForUnitTypes(final LocalDate startDate, final LocalDate endDate, final Set<String> unitTypes) {
- this.endDate = endDate;
- this.startDate = startDate;
- this.unitAmounts = new HashMap<String, BigDecimal>();
- for (final String type : unitTypes) {
- unitAmounts.put(type, BigDecimal.ZERO);
- }
- }
-
- public void addUsageForUnit(final String unitType, BigDecimal amount) {
- final BigDecimal currentAmount = unitAmounts.get(unitType);
- unitAmounts.put(unitType, currentAmount.add(amount));
- }
-
- public LocalDate getStartDate() {
- return startDate;
- }
-
- public LocalDate getEndDate() {
- return endDate;
- }
-
- public BigDecimal getUsageAmountForUnitType(final String unitType) {
- return unitAmounts.get(unitType);
- }
-
- @Override
- public int hashCode() {
- int result = startDate != null ? startDate.hashCode() : 0;
- result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
- result = 31 * result + (unitAmounts != null ? unitAmounts.hashCode() : 0);
- return result;
- }
-
- @Override
- public int compareTo(final Object o) {
-
- Preconditions.checkArgument(o instanceof RolledUpUsageForUnitTypes);
- final RolledUpUsageForUnitTypes other = (RolledUpUsageForUnitTypes) o;
- // We will check later intervals don't overlap.
- int i = getEndDate().compareTo(other.getStartDate());
- if (i != 0) {
- return i;
- } else {
- i = getEndDate().compareTo(other.getEndDate());
- if (i != 0) {
- return i;
- } else {
- return getStartDate().compareTo(other.getStartDate());
- }
- }
- }
- }
-
public void addBillingEvent(final BillingEvent event) {
Preconditions.checkState(!isBuilt.get());
billingEvents.add(event);
@@ -386,9 +291,4 @@ public class ContiguousIntervalConsumableInArrear {
return billingEvents.get(0).getTimeZone();
}
- static DateTime localDateToEndOfDayInAccountTimezone(final LocalDate input, final DateTimeZone accountTimeZone) {
- final DateTime dateTimeInAccountTimeZone = new DateTime(input.getYear(), input.getMonthOfYear(), input.getDayOfMonth(), 0, 0, 0, accountTimeZone);
- return new DateTime(dateTimeInAccountTimeZone, DateTimeZone.UTC);
- }
-
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java
index 6707195..8da11ed 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java
@@ -24,8 +24,6 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
-import javax.annotation.Nullable;
-
import org.joda.time.LocalDate;
import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.CatalogApiException;
@@ -39,7 +37,6 @@ import org.killbill.billing.util.callcontext.TenantContext;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
@@ -52,11 +49,13 @@ public class SubscriptionConsumableInArrear {
private final UsageUserApi usageApi;
private final LocalDate targetDate;
private final TenantContext context;
+ private final boolean insertZeroAmountItems;
- public SubscriptionConsumableInArrear(final UUID invoiceId, final List<BillingEvent> subscriptionBillingEvents, final UsageUserApi usageApi, final LocalDate targetDate, final TenantContext context) {
+ public SubscriptionConsumableInArrear(final UUID invoiceId, final List<BillingEvent> subscriptionBillingEvents, final UsageUserApi usageApi, final boolean insertZeroAmountItems, LocalDate targetDate, final TenantContext context) {
this.invoiceId = invoiceId;
this.subscriptionBillingEvents = subscriptionBillingEvents;
this.usageApi = usageApi;
+ this.insertZeroAmountItems = insertZeroAmountItems;
this.targetDate = targetDate;
this.context = context;
}
@@ -107,7 +106,7 @@ public class SubscriptionConsumableInArrear {
// Add inflight usage interval if non existent
ContiguousIntervalConsumableInArrear existingInterval = inFlightInArrearUsageIntervals.get(usage.getName());
if (existingInterval == null) {
- existingInterval = new ContiguousIntervalConsumableInArrear(usage, invoiceId, usageApi, targetDate, context);
+ existingInterval = new ContiguousIntervalConsumableInArrear(usage, invoiceId, usageApi, insertZeroAmountItems, targetDate, context);
inFlightInArrearUsageIntervals.put(usage.getName(), existingInterval);
}
// Add billing event for that usage interval
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index 52a1112..33a14d3 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -106,6 +106,11 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
public boolean isEmailNotificationsEnabled() {
return false;
}
+
+ @Override
+ public boolean isInsertZeroUsageItems() {
+ return true;
+ }
};
this.generator = new DefaultInvoiceGenerator(clock, null, invoiceConfig, null, controllerDispatcher);
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
index eec566d..ca988e3 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
@@ -111,7 +111,7 @@ public class TestDefaultInvoiceItemFormatter extends InvoiceTestSuiteNoDB {
private void checkOutput(final InvoiceItem invoiceItem, final String template, final String expected, final Locale locale) {
final Map<String, Object> data = new HashMap<String, Object>();
- data.put("invoiceItem", new DefaultInvoiceItemFormatter(config, invoiceItem, DateTimeFormat.mediumDate(), locale));
+ data.put("invoiceItem", new DefaultInvoiceItemFormatter(config, invoiceItem, DateTimeFormat.mediumDate().withLocale(locale), locale));
final String formattedText = templateEngine.executeTemplateText(template, data);
Assert.assertEquals(formattedText, expected);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
index 5cb31a9..d73bcbe 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
@@ -35,6 +35,7 @@ import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
import org.killbill.billing.invoice.model.UsageInvoiceItem;
import org.killbill.billing.junction.BillingEvent;
+import org.killbill.billing.usage.api.RolledUpUnit;
import org.killbill.billing.usage.api.RolledUpUsage;
import org.killbill.billing.usage.api.user.DefaultRolledUpUsage;
import org.testng.annotations.BeforeClass;
@@ -115,7 +116,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
Collections.<Usage>emptyList())
);
- final BigDecimal result = intervalConsumableInArrear.computeToBeBilledUsage(new BigDecimal("5325"), "unit");
+ final BigDecimal result = intervalConsumableInArrear.computeToBeBilledUsage(5325L, "unit");
// 5000 = 1000 (tier1) + 4325 (tier2) => 10 + 5 = 15
assertEquals(result, new BigDecimal("15"));
@@ -129,13 +130,17 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate endDate = new LocalDate(2014, 05, 15);
// 2 items for startDate - firstBCDDate
- final RolledUpUsage usage1 = new DefaultRolledUpUsage(subscriptionId, "unit", startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), firstBCDDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), new BigDecimal("130"));
- final RolledUpUsage usage2 = new DefaultRolledUpUsage(subscriptionId, "unit", startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), firstBCDDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), new BigDecimal("271"));
+ final List<RolledUpUnit> units1 = new ArrayList<RolledUpUnit>();
+ units1.add(createRolledUpUnit("unit", 130L));
+ units1.add(createRolledUpUnit("unit", 271L));
+ final RolledUpUsage usage1 = new DefaultRolledUpUsage(subscriptionId, startDate, firstBCDDate, units1);
// 1 items for firstBCDDate - endDate
- final RolledUpUsage usage3 = new DefaultRolledUpUsage(subscriptionId, "unit", firstBCDDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), new BigDecimal("199"));
+ final List<RolledUpUnit> units2 = new ArrayList<RolledUpUnit>();
+ units2.add(createRolledUpUnit("unit", 199L));
+ final RolledUpUsage usage3 = new DefaultRolledUpUsage(subscriptionId, firstBCDDate, endDate, units2);
- final List<RolledUpUsage> usages = ImmutableList.<RolledUpUsage>builder().add(usage1).add(usage2).add(usage3).build();
+ final List<RolledUpUsage> usages = ImmutableList.<RolledUpUsage>builder().add(usage1).add(usage3).build();
this.mockUsageUserApi = createMockUsageUserApi(usages);
final DefaultTieredBlock block = createDefaultTieredBlock("unit", 100, 10, BigDecimal.ONE);
@@ -181,7 +186,18 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
assertEquals(result.get(1).getUsageName(), usage.getName());
assertTrue(result.get(1).getStartDate().compareTo(firstBCDDate) == 0);
assertTrue(result.get(1).getEndDate().compareTo(endDate) == 0);
-
}
+ private RolledUpUnit createRolledUpUnit(final String unit, final Long amount) {
+ return new RolledUpUnit() {
+ @Override
+ public String getUnitType() {
+ return unit;
+ }
+ @Override
+ public Long getAmount() {
+ return amount;
+ }
+ };
+ }
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java
index c124390..9ab1951 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java
@@ -71,7 +71,7 @@ public class TestSubscriptionConsumableInArrear extends TestUsageInArrearBase {
LocalDate targetDate = new LocalDate(2013, 6, 23);
- final SubscriptionConsumableInArrear foo = new SubscriptionConsumableInArrear(invoiceId, billingEvents, usageUserApi, targetDate, callContext);
+ final SubscriptionConsumableInArrear foo = new SubscriptionConsumableInArrear(invoiceId, billingEvents, usageUserApi, true, targetDate, callContext);
final List<ContiguousIntervalConsumableInArrear> result = foo.computeInArrearUsageInterval();
assertEquals(result.size(), 3);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
index 994fac4..6d66b40 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
@@ -78,12 +78,12 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
protected UsageUserApi createMockUsageUserApi(final List<RolledUpUsage> returnValue) {
final UsageUserApi result = Mockito.mock(UsageUserApi.class);
- Mockito.when(result.getAllUsageForSubscription(Mockito.<UUID>any(), Mockito.<Set<String>>any(), Mockito.<List<DateTime>>any(), Mockito.<TenantContext>any())).thenReturn(returnValue);
+ Mockito.when(result.getAllUsageForSubscription(Mockito.<UUID>any(), Mockito.<List<LocalDate>>any(), Mockito.<TenantContext>any())).thenReturn(returnValue);
return result;
}
protected ContiguousIntervalConsumableInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) {
- final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = new ContiguousIntervalConsumableInArrear(usage, invoiceId, mockUsageUserApi, targetDate, callContext);
+ final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = new ContiguousIntervalConsumableInArrear(usage, invoiceId, mockUsageUserApi, true, targetDate, callContext);
for (BillingEvent event : events) {
intervalConsumableInArrear.addBillingEvent(event);
}
jaxrs/pom.xml 7(+6 -1)
diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index 1715ed9..7905426 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-jaxrs</artifactId>
@@ -61,6 +61,11 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <!-- Required to bump the com.codahale.metrics:metrics-jersey dependency -->
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ </dependency>
+ <dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<scope>provided</scope>
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentMethodJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentMethodJson.java
index 7117692..0482484 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentMethodJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentMethodJson.java
@@ -52,7 +52,7 @@ public class PaymentMethodJson extends JsonBase {
@JsonProperty("accountId") final String accountId,
@JsonProperty("isDefault") final Boolean isDefault,
@JsonProperty("pluginName") final String pluginName,
- @JsonProperty("pluginInfo") final PaymentMethodPluginDetailJson pluginInfo,
+ @JsonProperty("pluginInfo") @Nullable final PaymentMethodPluginDetailJson pluginInfo,
@JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
super(auditLogs);
this.externalKey = externalKey;
@@ -135,14 +135,15 @@ public class PaymentMethodJson extends JsonBase {
// N/A
return false;
}
+
@Override
public String getExternalPaymentMethodId() {
- return pluginInfo.getExternalPaymentId();
+ return pluginInfo != null ? pluginInfo.getExternalPaymentId() : null;
}
@Override
public List<PluginProperty> getProperties() {
- if (pluginInfo.getProperties() != null) {
+ if (pluginInfo != null && pluginInfo.getProperties() != null) {
final List<PluginProperty> result = new LinkedList<PluginProperty>();
for (final PluginPropertyJson cur : pluginInfo.getProperties()) {
result.add(new PluginProperty(cur.getKey(), cur.getValue(), cur.getIsUpdatable()));
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/RolledUpUsageJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/RolledUpUsageJson.java
new file mode 100644
index 0000000..98f6812
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/RolledUpUsageJson.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2014 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.jaxrs.json;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.usage.api.RolledUpUnit;
+import org.killbill.billing.usage.api.RolledUpUsage;
+
+import com.apple.jobjc.Invoke.FunCall;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class RolledUpUsageJson {
+
+ private final String subscriptionId;
+ private final LocalDate startDate;
+ private final LocalDate endDate;
+ private final List<RolledUpUnitJson> rolledUpUnits;
+
+ @JsonCreator
+ public RolledUpUsageJson(@JsonProperty("subscriptionId") final String subscriptionId,
+ @JsonProperty("startDate") final LocalDate startDate,
+ @JsonProperty("endDate") final LocalDate endDate,
+ @JsonProperty("rolledUpUnit") final List<RolledUpUnitJson> rolledUpUnits) {
+ this.subscriptionId = subscriptionId;
+ this.startDate = startDate;
+ this.endDate = endDate;
+ this.rolledUpUnits = rolledUpUnits;
+ }
+
+ public RolledUpUsageJson(final RolledUpUsage input) {
+ this(input.getSubscriptionId().toString(), input.getStart(), input.getEnd(), ImmutableList.copyOf(Iterables.transform(input.getRolledUpUnit(), new Function<RolledUpUnit, RolledUpUnitJson>() {
+ @Override
+ public RolledUpUnitJson apply(final RolledUpUnit input) {
+ return new RolledUpUnitJson(input);
+ }
+ })));
+ }
+
+ public String getSubscriptionId() {
+ return subscriptionId;
+ }
+
+ public LocalDate getStartDate() {
+ return startDate;
+ }
+
+ public LocalDate getEndDate() {
+ return endDate;
+ }
+
+ public List<RolledUpUnitJson> getRolledUpUnits() {
+ return rolledUpUnits;
+ }
+
+ public static class RolledUpUnitJson {
+
+ private final String unitType;
+ private final Long amount;
+
+ @JsonCreator
+ public RolledUpUnitJson(@JsonProperty("unitType") final String unitType,
+ @JsonProperty("amount") final Long amount) {
+ this.unitType = unitType;
+ this.amount = amount;
+ }
+
+ public RolledUpUnitJson(final RolledUpUnit input) {
+ this(input.getUnitType(), input.getAmount());
+ }
+
+ public String getUnitType() {
+ return unitType;
+ }
+
+ public Long getAmount() {
+ return amount;
+ }
+ }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionUsageRecordJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionUsageRecordJson.java
new file mode 100644
index 0000000..a7c5cd3
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionUsageRecordJson.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.jaxrs.json;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.usage.api.SubscriptionUsageRecord;
+import org.killbill.billing.usage.api.UnitUsageRecord;
+import org.killbill.billing.usage.api.UsageRecord;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class SubscriptionUsageRecordJson {
+
+ private final String subscriptionId;
+ private final List<UnitUsageRecordJson> unitUsageRecords;
+
+ @JsonCreator
+ public SubscriptionUsageRecordJson(@JsonProperty("subscriptionId") final String subscriptionId,
+ @JsonProperty("unitUsageRecords") final List<UnitUsageRecordJson> unitUsageRecords) {
+ this.subscriptionId = subscriptionId;
+ this.unitUsageRecords = unitUsageRecords;
+ }
+
+ public String getSubscriptionId() {
+ return subscriptionId;
+ }
+
+ public List<UnitUsageRecordJson> getUnitUsageRecords() {
+ return unitUsageRecords;
+ }
+
+ public static class UnitUsageRecordJson {
+
+ private final String unitType;
+ private final List<UsageRecordJson> usageRecords;
+
+ @JsonCreator
+ public UnitUsageRecordJson(@JsonProperty("unitType") final String unitType,
+ @JsonProperty("usageRecords") final List<UsageRecordJson> usageRecords) {
+ this.unitType = unitType;
+ this.usageRecords = usageRecords;
+ }
+
+ public String getUnitType() {
+ return unitType;
+ }
+
+ public List<UsageRecordJson> getUsageRecords() {
+ return usageRecords;
+ }
+
+ public UnitUsageRecord toUnitUsageRecord() {
+ final List<UsageRecord> tmp = ImmutableList.copyOf(Iterables.transform(usageRecords, new Function<UsageRecordJson, UsageRecord>() {
+ @Override
+ public UsageRecord apply(final UsageRecordJson input) {
+ return input.toUsageRecord();
+ }
+ }));
+ return new UnitUsageRecord(unitType, tmp);
+ }
+ }
+
+ public static class UsageRecordJson {
+
+ private final LocalDate recordDate;
+ private final Long amount;
+
+ @JsonCreator
+ public UsageRecordJson(@JsonProperty("recordDate") final LocalDate recordDate,
+ @JsonProperty("amount") final Long amount) {
+ this.recordDate = recordDate;
+ this.amount = amount;
+ }
+
+ public LocalDate getRecordDate() {
+ return recordDate;
+ }
+
+ public Long getAmount() {
+ return amount;
+ }
+
+ public UsageRecord toUsageRecord() {
+ return new UsageRecord(recordDate, amount);
+ }
+ }
+
+ public SubscriptionUsageRecord toSubscriptionUsageRecord() {
+ final List<UnitUsageRecord> tmp = ImmutableList.copyOf(Iterables.transform(unitUsageRecords, new Function<UnitUsageRecordJson, UnitUsageRecord>() {
+ @Override
+ public UnitUsageRecord apply(final UnitUsageRecordJson input) {
+ return input.toUnitUsageRecord();
+ }
+ }));
+ final SubscriptionUsageRecord result = new SubscriptionUsageRecord(UUID.fromString(subscriptionId), tmp);
+ return result;
+ }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
index 28fde6e..7e32c1d 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
@@ -21,9 +21,6 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import org.killbill.billing.BillingExceptionBase;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.catalog.api.CatalogApiException;
@@ -41,6 +38,8 @@ import org.killbill.billing.util.api.TagApiException;
import org.killbill.billing.util.api.TagDefinitionApiException;
import org.killbill.billing.util.email.EmailApiException;
import org.killbill.billing.util.jackson.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -113,74 +112,64 @@ public abstract class ExceptionMapperBase {
protected Response buildConflictingRequestResponse(final Exception e, final UriInfo uriInfo) {
// Log the full stacktrace
log.warn("Conflicting request", e);
- return buildConflictingRequestResponse(exceptionToString(e), uriInfo);
- }
- private Response buildConflictingRequestResponse(final String error, final UriInfo uriInfo) {
- return Response.status(Status.CONFLICT)
- .entity(error)
- .type(MediaType.TEXT_PLAIN_TYPE)
- .build();
+ final Response.ResponseBuilder responseBuilder = Response.status(Status.CONFLICT);
+ serializeException(e, responseBuilder);
+ return responseBuilder.build();
}
protected Response buildNotFoundResponse(final Exception e, final UriInfo uriInfo) {
// Log the full stacktrace
log.info("Not found", e);
- return buildNotFoundResponse(exceptionToString(e), uriInfo);
- }
- private Response buildNotFoundResponse(final String error, final UriInfo uriInfo) {
- return Response.status(Status.NOT_FOUND)
- .entity(error)
- .type(MediaType.TEXT_PLAIN_TYPE)
- .build();
+ final Response.ResponseBuilder responseBuilder = Response.status(Status.NOT_FOUND);
+ serializeException(e, responseBuilder);
+ return responseBuilder.build();
}
protected Response buildBadRequestResponse(final Exception e, final UriInfo uriInfo) {
// Log the full stacktrace
log.warn("Bad request", e);
- return buildBadRequestResponse(exceptionToString(e), uriInfo);
- }
- private Response buildBadRequestResponse(final String error, final UriInfo uriInfo) {
- return Response.status(Status.BAD_REQUEST)
- .entity(error)
- .type(MediaType.TEXT_PLAIN_TYPE)
- .build();
+ final Response.ResponseBuilder responseBuilder = Response.status(Status.BAD_REQUEST);
+ serializeException(e, responseBuilder);
+ return responseBuilder.build();
}
protected Response buildAuthorizationErrorResponse(final Exception e, final UriInfo uriInfo) {
// Log the full stacktrace
log.warn("Authorization error", e);
- return buildAuthorizationErrorResponse(exceptionToString(e), uriInfo);
- }
- private Response buildAuthorizationErrorResponse(final String error, final UriInfo uriInfo) {
- return Response.status(Status.UNAUTHORIZED) // TODO Forbidden?
- .entity(error)
- .type(MediaType.TEXT_PLAIN_TYPE)
- .build();
+ // TODO Forbidden?
+ final Response.ResponseBuilder responseBuilder = Response.status(Status.UNAUTHORIZED);
+ serializeException(e, responseBuilder);
+ return responseBuilder.build();
}
protected Response buildInternalErrorResponse(final Exception e, final UriInfo uriInfo) {
// Log the full stacktrace
log.warn("Internal error", e);
- return buildInternalErrorResponse(exceptionToString(e), uriInfo);
+
+ final Response.ResponseBuilder responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR);
+ serializeException(e, responseBuilder);
+ return responseBuilder.build();
}
- private Response buildInternalErrorResponse(final String error, final UriInfo uriInfo) {
- return Response.status(Status.INTERNAL_SERVER_ERROR)
- .entity(error)
- .type(MediaType.TEXT_PLAIN_TYPE)
- .build();
+ protected Response buildPluginTimeoutResponse(final Exception e, final UriInfo uriInfo) {
+ final Response.ResponseBuilder responseBuilder = Response.status(Status.ACCEPTED);
+ serializeException(e, responseBuilder);
+ return responseBuilder.build();
}
- private String exceptionToString(final Exception e) {
+ private void serializeException(final Exception e, final Response.ResponseBuilder responseBuilder) {
+ final BillingExceptionJson billingExceptionJson = new BillingExceptionJson(e);
+
try {
- return mapper.writeValueAsString(new BillingExceptionJson(e));
- } catch (final JsonProcessingException jsonException) {
+ final String billingExceptionJsonAsString = mapper.writeValueAsString(billingExceptionJson);
+ responseBuilder.entity(billingExceptionJsonAsString).type(MediaType.APPLICATION_JSON);
+ } catch (JsonProcessingException jsonException) {
log.warn("Unable to serialize exception", jsonException);
+ responseBuilder.entity(e.toString()).type(MediaType.TEXT_PLAIN_TYPE);
}
- return e.toString();
}
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
index 9fb84ad..c89369d 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
@@ -63,7 +63,7 @@ public class PaymentApiExceptionMapper extends ExceptionMapperBase implements Ex
} else if (exception.getCode() == ErrorCode.PAYMENT_NULL_INVOICE.getCode()) {
return buildBadRequestResponse(exception, uriInfo);
} else if (exception.getCode() == ErrorCode.PAYMENT_PLUGIN_TIMEOUT.getCode()) {
- return buildInternalErrorResponse(exception, uriInfo);
+ return buildPluginTimeoutResponse(exception, uriInfo);
} else if (exception.getCode() == ErrorCode.PAYMENT_REFRESH_PAYMENT_METHOD.getCode()) {
return buildInternalErrorResponse(exception, uriInfo);
} else if (exception.getCode() == ErrorCode.PAYMENT_UPD_PAYMENT_METHOD.getCode()) {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index 56a49c8..95e1a82 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -141,7 +141,7 @@ public class AccountResource extends JaxRsResourceBase {
this.overdueApi = overdueApi;
}
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
@@ -157,7 +157,7 @@ public class AccountResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(accountJson).build();
}
- //@Timed
+ @Timed
@GET
@Path("/" + PAGINATION)
@Produces(APPLICATION_JSON)
@@ -184,7 +184,7 @@ public class AccountResource extends JaxRsResourceBase {
);
}
- //@Timed
+ @Timed
@GET
@Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
@Produces(APPLICATION_JSON)
@@ -213,7 +213,7 @@ public class AccountResource extends JaxRsResourceBase {
);
}
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + BUNDLES)
@Produces(APPLICATION_JSON)
@@ -238,7 +238,7 @@ public class AccountResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(result).build();
}
- //@Timed
+ @Timed
@GET
@Produces(APPLICATION_JSON)
public Response getAccountByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
@@ -267,7 +267,7 @@ public class AccountResource extends JaxRsResourceBase {
}
}
- //@Timed
+ @Timed
@POST
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@@ -282,7 +282,7 @@ public class AccountResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", account.getId());
}
- //@Timed
+ @Timed
@PUT
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@@ -300,7 +300,7 @@ public class AccountResource extends JaxRsResourceBase {
}
// Not supported
- //@Timed
+ @Timed
@DELETE
@Path("/{accountId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
@@ -318,7 +318,7 @@ public class AccountResource extends JaxRsResourceBase {
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + TIMELINE)
@Produces(APPLICATION_JSON)
@@ -352,7 +352,7 @@ public class AccountResource extends JaxRsResourceBase {
* ************************** EMAIL NOTIFICATIONS FOR INVOICES ********************************
*/
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + EMAIL_NOTIFICATIONS)
@Produces(APPLICATION_JSON)
@@ -364,7 +364,7 @@ public class AccountResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(invoiceEmailJson).build();
}
- //@Timed
+ @Timed
@PUT
@Path("/{accountId:" + UUID_PATTERN + "}/" + EMAIL_NOTIFICATIONS)
@Consumes(APPLICATION_JSON)
@@ -390,7 +390,7 @@ public class AccountResource extends JaxRsResourceBase {
/*
* ************************** INVOICE CBA REBALANCING ********************************
*/
- //@Timed
+ @Timed
@POST
@Path("/{accountId:" + UUID_PATTERN + "}/" + CBA_REBALANCING)
@Consumes(APPLICATION_JSON)
@@ -413,7 +413,7 @@ public class AccountResource extends JaxRsResourceBase {
* ************************** INVOICES ********************************
*/
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + INVOICES)
@Produces(APPLICATION_JSON)
@@ -447,7 +447,7 @@ public class AccountResource extends JaxRsResourceBase {
*/
// STEPH should refactor code since very similar to @Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENTS)
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + INVOICE_PAYMENTS)
@Produces(APPLICATION_JSON)
@@ -471,7 +471,7 @@ public class AccountResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(result).build();
}
- //@Timed
+ @Timed
@POST
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
@@ -519,7 +519,7 @@ public class AccountResource extends JaxRsResourceBase {
return Response.status(Status.OK).build();
}
- //@Timed
+ @Timed
@POST
@Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENT_METHODS)
@Consumes(APPLICATION_JSON)
@@ -556,7 +556,7 @@ public class AccountResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(PaymentMethodResource.class, "getPaymentMethod", paymentMethodId, uriInfo.getBaseUri().toString());
}
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENT_METHODS)
@Produces(APPLICATION_JSON)
@@ -581,7 +581,7 @@ public class AccountResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(json).build();
}
- //@Timed
+ @Timed
@PUT
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@@ -612,7 +612,7 @@ public class AccountResource extends JaxRsResourceBase {
/*
* ************************* PAYMENTS *****************************
*/
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENTS)
@Produces(APPLICATION_JSON)
@@ -635,7 +635,7 @@ public class AccountResource extends JaxRsResourceBase {
return Response.status(Response.Status.OK).entity(result).build();
}
- //@Timed
+ @Timed
@POST
@Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENTS)
@Consumes(APPLICATION_JSON)
@@ -684,7 +684,7 @@ public class AccountResource extends JaxRsResourceBase {
/*
* ************************** OVERDUE ********************************
*/
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + OVERDUE)
@Produces(APPLICATION_JSON)
@@ -702,7 +702,7 @@ public class AccountResource extends JaxRsResourceBase {
* ************************* CUSTOM FIELDS *****************************
*/
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Produces(APPLICATION_JSON)
@@ -712,7 +712,7 @@ public class AccountResource extends JaxRsResourceBase {
return super.getCustomFields(UUID.fromString(id), auditMode, context.createContext(request));
}
- //@Timed
+ @Timed
@POST
@Path("/{accountId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
@@ -728,7 +728,7 @@ public class AccountResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment, request), uriInfo);
}
- //@Timed
+ @Timed
@DELETE
@Path("/{accountId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
@@ -747,7 +747,7 @@ public class AccountResource extends JaxRsResourceBase {
* ************************* TAGS *****************************
*/
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + TAGS)
@Produces(APPLICATION_JSON)
@@ -759,7 +759,7 @@ public class AccountResource extends JaxRsResourceBase {
return super.getTags(accountId, accountId, auditMode, includedDeleted, context.createContext(request));
}
- //@Timed
+ @Timed
@POST
@Path("/{accountId:" + UUID_PATTERN + "}/" + TAGS)
@Produces(APPLICATION_JSON)
@@ -774,7 +774,7 @@ public class AccountResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment, request));
}
- //@Timed
+ @Timed
@DELETE
@Path("/{accountId:" + UUID_PATTERN + "}/" + TAGS)
@Consumes(APPLICATION_JSON)
@@ -812,7 +812,7 @@ public class AccountResource extends JaxRsResourceBase {
* ************************* EMAILS *****************************
*/
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + EMAILS)
@Produces(APPLICATION_JSON)
@@ -828,7 +828,7 @@ public class AccountResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(emailsJson).build();
}
- //@Timed
+ @Timed
@POST
@Path("/{accountId:" + UUID_PATTERN + "}/" + EMAILS)
@Consumes(APPLICATION_JSON)
@@ -864,7 +864,7 @@ public class AccountResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getEmails", json.getAccountId());
}
- //@Timed
+ @Timed
@DELETE
@Path("/{accountId:" + UUID_PATTERN + "}/" + EMAILS + "/{email}")
@Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
index 2ca083c..ad97126 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
@@ -98,7 +98,7 @@ public class BundleResource extends JaxRsResourceBase {
this.subscriptionApi = subscriptionApi;
}
- //@Timed
+ @Timed
@GET
@Path("/{bundleId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
@@ -110,7 +110,7 @@ public class BundleResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(json).build();
}
- //@Timed
+ @Timed
@GET
@Produces(APPLICATION_JSON)
public Response getBundleByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
@@ -120,7 +120,7 @@ public class BundleResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(json).build();
}
- //@Timed
+ @Timed
@GET
@Path("/" + PAGINATION)
@Produces(APPLICATION_JSON)
@@ -146,7 +146,7 @@ public class BundleResource extends JaxRsResourceBase {
nextPageUri);
}
- //@Timed
+ @Timed
@GET
@Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
@Produces(APPLICATION_JSON)
@@ -174,7 +174,7 @@ public class BundleResource extends JaxRsResourceBase {
nextPageUri);
}
- //@Timed
+ @Timed
@PUT
@Path("/{bundleId:" + UUID_PATTERN + "}/" + PAUSE)
@Consumes(APPLICATION_JSON)
@@ -194,7 +194,7 @@ public class BundleResource extends JaxRsResourceBase {
return Response.status(Status.OK).build();
}
- //@Timed
+ @Timed
@PUT
@Path("/{bundleId:" + UUID_PATTERN + "}/" + RESUME)
@Consumes(APPLICATION_JSON)
@@ -214,7 +214,7 @@ public class BundleResource extends JaxRsResourceBase {
return Response.status(Status.OK).build();
}
- //@Timed
+ @Timed
@GET
@Path("/{bundleId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Produces(APPLICATION_JSON)
@@ -224,7 +224,7 @@ public class BundleResource extends JaxRsResourceBase {
return super.getCustomFields(UUID.fromString(id), auditMode, context.createContext(request));
}
- //@Timed
+ @Timed
@POST
@Path("/{bundleId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
@@ -240,7 +240,7 @@ public class BundleResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment, request), uriInfo);
}
- //@Timed
+ @Timed
@DELETE
@Path("/{bundleId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
@@ -255,7 +255,7 @@ public class BundleResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment, request));
}
- //@Timed
+ @Timed
@GET
@Path("/{bundleId:" + UUID_PATTERN + "}/" + TAGS)
@Produces(APPLICATION_JSON)
@@ -269,7 +269,7 @@ public class BundleResource extends JaxRsResourceBase {
return super.getTags(bundle.getAccountId(), bundleId, auditMode, includedDeleted, tenantContext);
}
- //@Timed
+ @Timed
@PUT
@Path("/{bundleId:" + UUID_PATTERN + "}")
@Consumes(APPLICATION_JSON)
@@ -296,7 +296,7 @@ public class BundleResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(BundleResource.class, "getBundle", newBundleId, uriInfo.getBaseUri().toString());
}
- //@Timed
+ @Timed
@POST
@Path("/{bundleId:" + UUID_PATTERN + "}/" + TAGS)
@Consumes(APPLICATION_JSON)
@@ -312,7 +312,7 @@ public class BundleResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment, request));
}
- //@Timed
+ @Timed
@DELETE
@Path("/{bundleId:" + UUID_PATTERN + "}/" + TAGS)
@Consumes(APPLICATION_JSON)
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 ad882fc..1de293d 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
@@ -70,14 +70,14 @@ public class CatalogResource extends JaxRsResourceBase {
this.catalogService = catalogService;
}
- //@Timed
+ @Timed
@GET
@Produces(APPLICATION_XML)
public Response getCatalogXml(@javax.ws.rs.core.Context final HttpServletRequest request) throws Exception {
return Response.status(Status.OK).entity(XMLWriter.writeXML(catalogService.getCurrentCatalog(), StaticCatalog.class)).build();
}
- //@Timed
+ @Timed
@GET
@Produces(APPLICATION_JSON)
public Response getCatalogJson(@javax.ws.rs.core.Context final HttpServletRequest request) throws Exception {
@@ -100,7 +100,7 @@ public class CatalogResource extends JaxRsResourceBase {
// return result;
// }
- //@Timed
+ @Timed
@GET
@Path("/availableAddons")
@Produces(APPLICATION_JSON)
@@ -115,7 +115,7 @@ public class CatalogResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(details).build();
}
- //@Timed
+ @Timed
@GET
@Path("/availableBasePlans")
@Produces(APPLICATION_JSON)
@@ -129,7 +129,7 @@ public class CatalogResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(details).build();
}
- //@Timed
+ @Timed
@GET
@Path("/simpleCatalog")
@Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CustomFieldResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CustomFieldResource.java
index a4895eb..35556ad 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CustomFieldResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CustomFieldResource.java
@@ -68,7 +68,7 @@ public class CustomFieldResource extends JaxRsResourceBase {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
}
- //@Timed
+ @Timed
@GET
@Path("/" + PAGINATION)
@Produces(APPLICATION_JSON)
@@ -92,7 +92,7 @@ public class CustomFieldResource extends JaxRsResourceBase {
nextPageUri);
}
- //@Timed
+ @Timed
@GET
@Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
@Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ExportResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ExportResource.java
index cf4aa7b..672d7e2 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ExportResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ExportResource.java
@@ -66,7 +66,7 @@ public class ExportResource extends JaxRsResourceBase {
this.exportUserApi = exportUserApi;
}
- //@Timed
+ @Timed
@GET
@Path("/{accountId:" + UUID_PATTERN + "}")
@Produces(TEXT_PLAIN)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
index 2ddeed1..2995a0c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
@@ -95,7 +95,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
this.invoicePaymentApi = invoicePaymentApi;
}
- //@Timed
+ @Timed
@GET
@Path("/{paymentId:" + UUID_PATTERN + "}/")
@Produces(APPLICATION_JSON)
@@ -124,7 +124,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
return Response.status(Response.Status.OK).entity(result).build();
}
- //@Timed
+ @Timed
@POST
@Path("/{paymentId:" + UUID_PATTERN + "}/" + REFUNDS)
@Consumes(APPLICATION_JSON)
@@ -167,7 +167,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(InvoicePaymentResource.class, "getInvoicePayment", result.getId(), uriInfo.getBaseUri().toString());
}
- //@Timed
+ @Timed
@POST
@Path("/{paymentId:" + UUID_PATTERN + "}/" + CHARGEBACKS)
@Consumes(APPLICATION_JSON)
@@ -191,7 +191,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId());
}
- //@Timed
+ @Timed
@GET
@Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Produces(APPLICATION_JSON)
@@ -201,7 +201,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
return super.getCustomFields(UUID.fromString(id), auditMode, context.createContext(request));
}
- //@Timed
+ @Timed
@POST
@Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
@@ -217,7 +217,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment, request), uriInfo);
}
- //@Timed
+ @Timed
@DELETE
@Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
@@ -232,7 +232,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment, request));
}
- //@Timed
+ @Timed
@GET
@Path("/{paymentId:" + UUID_PATTERN + "}/" + TAGS)
@Produces(APPLICATION_JSON)
@@ -248,7 +248,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
return super.getTags(payment.getAccountId(), paymentId, auditMode, includedDeleted, tenantContext);
}
- //@Timed
+ @Timed
@POST
@Path("/{paymentId:" + UUID_PATTERN + "}/" + TAGS)
@Consumes(APPLICATION_JSON)
@@ -264,7 +264,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment, request));
}
- //@Timed
+ @Timed
@DELETE
@Path("/{paymentId:" + UUID_PATTERN + "}/" + TAGS)
@Consumes(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
index ad2e32a..43ab896 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
@@ -119,7 +119,7 @@ public class InvoiceResource extends JaxRsResourceBase {
this.invoiceNotifier = invoiceNotifier;
}
- //@Timed
+ @Timed
@GET
@Path("/{invoiceId:" + UUID_PATTERN + "}/")
@Produces(APPLICATION_JSON)
@@ -139,7 +139,7 @@ public class InvoiceResource extends JaxRsResourceBase {
}
}
- //@Timed
+ @Timed
@GET
@Path("/{invoiceNumber:" + NUMBER_PATTERN + "}/")
@Produces(APPLICATION_JSON)
@@ -159,7 +159,7 @@ public class InvoiceResource extends JaxRsResourceBase {
}
}
- //@Timed
+ @Timed
@GET
@Path("/{invoiceId:" + UUID_PATTERN + "}/html")
@Produces(TEXT_HTML)
@@ -168,7 +168,7 @@ public class InvoiceResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(invoiceApi.getInvoiceAsHTML(UUID.fromString(invoiceId), context.createContext(request))).build();
}
- //@Timed
+ @Timed
@GET
@Path("/" + PAGINATION)
@Produces(APPLICATION_JSON)
@@ -198,7 +198,7 @@ public class InvoiceResource extends JaxRsResourceBase {
);
}
- //@Timed
+ @Timed
@GET
@Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
@Produces(APPLICATION_JSON)
@@ -229,7 +229,7 @@ public class InvoiceResource extends JaxRsResourceBase {
);
}
- //@Timed
+ @Timed
@POST
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@@ -253,7 +253,7 @@ public class InvoiceResource extends JaxRsResourceBase {
}
}
- //@Timed
+ @Timed
@DELETE
@Path("/{invoiceId:" + UUID_PATTERN + "}" + "/{invoiceItemId:" + UUID_PATTERN + "}/cba")
@Consumes(APPLICATION_JSON)
@@ -274,7 +274,7 @@ public class InvoiceResource extends JaxRsResourceBase {
return Response.status(Status.OK).build();
}
- //@Timed
+ @Timed
@POST
@Path("/{invoiceId:" + UUID_PATTERN + "}")
@Consumes(APPLICATION_JSON)
@@ -311,7 +311,7 @@ public class InvoiceResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", adjustmentItem.getInvoiceId());
}
- //@Timed
+ @Timed
@POST
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
@@ -380,7 +380,7 @@ public class InvoiceResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(createdExternalChargesJson).build();
}
- //@Timed
+ @Timed
@GET
@Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS)
@Produces(APPLICATION_JSON)
@@ -406,7 +406,7 @@ public class InvoiceResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(result).build();
}
- //@Timed
+ @Timed
@POST
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
@@ -429,7 +429,7 @@ public class InvoiceResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId());
}
- //@Timed
+ @Timed
@POST
@Path("/{invoiceId:" + UUID_PATTERN + "}/" + EMAIL_NOTIFICATIONS)
@Consumes(APPLICATION_JSON)
@@ -454,7 +454,7 @@ public class InvoiceResource extends JaxRsResourceBase {
return Response.status(Status.OK).build();
}
- //@Timed
+ @Timed
@GET
@Path("/{invoiceId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Produces(APPLICATION_JSON)
@@ -464,7 +464,7 @@ public class InvoiceResource extends JaxRsResourceBase {
return super.getCustomFields(UUID.fromString(id), auditMode, context.createContext(request));
}
- //@Timed
+ @Timed
@POST
@Path("/{invoiceId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
@@ -480,7 +480,7 @@ public class InvoiceResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment, request), uriInfo);
}
- //@Timed
+ @Timed
@DELETE
@Path("/{invoiceId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
@@ -495,7 +495,7 @@ public class InvoiceResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment, request));
}
- //@Timed
+ @Timed
@GET
@Path("/{invoiceId:" + UUID_PATTERN + "}/" + TAGS)
@Produces(APPLICATION_JSON)
@@ -509,7 +509,7 @@ public class InvoiceResource extends JaxRsResourceBase {
return super.getTags(invoice.getAccountId(), invoiceId, auditMode, includedDeleted, tenantContext);
}
- //@Timed
+ @Timed
@POST
@Path("/{invoiceId:" + UUID_PATTERN + "}/" + TAGS)
@Consumes(APPLICATION_JSON)
@@ -525,7 +525,7 @@ public class InvoiceResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment, request));
}
- //@Timed
+ @Timed
@DELETE
@Path("/{invoiceId:" + UUID_PATTERN + "}/" + TAGS)
@Consumes(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index 5fa2d62..aeabae3 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -98,8 +98,8 @@ public interface JaxrsResource {
public static final String QUERY_PLUGIN_PROPERTY = "pluginProperty";
- public static final String QUERY_START_TIME = "startTime";
- public static final String QUERY_END_TIME = "endTime";
+ public static final String QUERY_START_DATE = "startDate";
+ public static final String QUERY_END_DATE = "endDate";
public static final String QUERY_BUNDLE_TRANSFER_ADDON = "transferAddOn";
public static final String QUERY_BUNDLE_TRANSFER_CANCEL_IMM = "cancelImmediately";
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java
index 7282bce..829bef6 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java
@@ -81,7 +81,7 @@ public class PaymentGatewayResource extends JaxRsResourceBase {
this.paymentGatewayApi = paymentGatewayApi;
}
- //@Timed
+ @Timed
@POST
@Path("/" + HOSTED + "/" + FORM + "/{" + QUERY_ACCOUNT_ID + ":" + UUID_PATTERN + "}")
@Consumes(APPLICATION_JSON)
@@ -119,7 +119,7 @@ public class PaymentGatewayResource extends JaxRsResourceBase {
return Response.status(Response.Status.OK).entity(result).build();
}
- //@Timed
+ @Timed
@POST
@Path("/" + NOTIFICATION + "/{" + QUERY_PAYMENT_PLUGIN_NAME + ":" + ANYTHING_PATTERN + "}")
@Consumes(WILDCARD)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java
index 6f6a597..183eb21 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java
@@ -82,7 +82,7 @@ public class PaymentMethodResource extends JaxRsResourceBase {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
}
- //@Timed
+ @Timed
@GET
@Path("/{paymentMethodId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
@@ -102,7 +102,7 @@ public class PaymentMethodResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(json).build();
}
- //@Timed
+ @Timed
@GET
@Produces(APPLICATION_JSON)
public Response getPaymentMethodByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
@@ -120,7 +120,7 @@ public class PaymentMethodResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(json).build();
}
- //@Timed
+ @Timed
@GET
@Path("/" + PAGINATION)
@Produces(APPLICATION_JSON)
@@ -174,7 +174,7 @@ public class PaymentMethodResource extends JaxRsResourceBase {
);
}
- //@Timed
+ @Timed
@GET
@Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
@Produces(APPLICATION_JSON)
@@ -231,7 +231,7 @@ public class PaymentMethodResource extends JaxRsResourceBase {
);
}
- //@Timed
+ @Timed
@DELETE
@Produces(APPLICATION_JSON)
@Path("/{paymentMethodId:" + UUID_PATTERN + "}")
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
index 2ccf734..b9496c4 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
@@ -83,7 +83,7 @@ public class PaymentResource extends JaxRsResourceBase {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
}
- //@Timed
+ @Timed
@GET
@Path("/{paymentId:" + UUID_PATTERN + "}/")
@Produces(APPLICATION_JSON)
@@ -102,7 +102,7 @@ public class PaymentResource extends JaxRsResourceBase {
return Response.status(Response.Status.OK).entity(result).build();
}
- //@Timed
+ @Timed
@GET
@Path("/" + PAGINATION)
@Produces(APPLICATION_JSON)
@@ -143,7 +143,7 @@ public class PaymentResource extends JaxRsResourceBase {
);
}
- //@Timed
+ @Timed
@GET
@Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
@Produces(APPLICATION_JSON)
@@ -187,7 +187,7 @@ public class PaymentResource extends JaxRsResourceBase {
);
}
- //@Timed
+ @Timed
@POST
@Path("/{paymentId:" + UUID_PATTERN + "}/")
@Consumes(APPLICATION_JSON)
@@ -213,7 +213,7 @@ public class PaymentResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
}
- //@Timed
+ @Timed
@POST
@Path("/{paymentId:" + UUID_PATTERN + "}/" + REFUNDS)
@Consumes(APPLICATION_JSON)
@@ -239,7 +239,7 @@ public class PaymentResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
}
- //@Timed
+ @Timed
@DELETE
@Path("/{paymentId:" + UUID_PATTERN + "}/")
@Consumes(APPLICATION_JSON)
@@ -264,7 +264,7 @@ public class PaymentResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
}
- //@Timed
+ @Timed
@POST
@Path("/{paymentId:" + UUID_PATTERN + "}/" + CHARGEBACKS)
@Consumes(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
index afd9406..f581061 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
@@ -87,7 +87,7 @@ public class PluginResource extends JaxRsResourceBase {
this.osgiServlet = osgiServlet;
}
- //@Timed
+ @Timed
@DELETE
public Response doDELETE(@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final HttpServletResponse response,
@@ -96,7 +96,7 @@ public class PluginResource extends JaxRsResourceBase {
return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
}
- //@Timed
+ @Timed
@GET
public Response doGET(@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final HttpServletResponse response,
@@ -105,7 +105,7 @@ public class PluginResource extends JaxRsResourceBase {
return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
}
- //@Timed
+ @Timed
@OPTIONS
public Response doOPTIONS(@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final HttpServletResponse response,
@@ -114,7 +114,7 @@ public class PluginResource extends JaxRsResourceBase {
return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
}
- //@Timed
+ @Timed
@POST
@Consumes("application/x-www-form-urlencoded")
public Response doFormPOST(final MultivaluedMap<String, String> form,
@@ -125,7 +125,7 @@ public class PluginResource extends JaxRsResourceBase {
return serviceViaOSGIPlugin(form, request, response, servletContext, servletConfig);
}
- //@Timed
+ @Timed
@POST
public Response doPOST(@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final HttpServletResponse response,
@@ -134,7 +134,7 @@ public class PluginResource extends JaxRsResourceBase {
return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
}
- //@Timed
+ @Timed
@PUT
public Response doPUT(@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final HttpServletResponse response,
@@ -143,7 +143,7 @@ public class PluginResource extends JaxRsResourceBase {
return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
}
- //@Timed
+ @Timed
@HEAD
public Response doHEAD(@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final HttpServletResponse response,
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
index 730dd3c..c4348b5 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
@@ -70,7 +70,7 @@ public class SecurityResource extends JaxRsResourceBase {
this.securityApi = securityApi;
}
- //@Timed
+ @Timed
@GET
@Path("/permissions")
@Produces(APPLICATION_JSON)
@@ -80,7 +80,7 @@ public class SecurityResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(json).build();
}
- //@Timed
+ @Timed
@GET
@Path("/subject")
@Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
index f7c22bd..ddd2768 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
@@ -111,7 +111,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
this.subscriptionApi = subscriptionApi;
}
- //@Timed
+ @Timed
@GET
@Path("/{subscriptionId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
@@ -123,7 +123,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(json).build();
}
- //@Timed
+ @Timed
@POST
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@@ -168,7 +168,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
}
- //@Timed
+ @Timed
@PUT
@Path("/{subscriptionId:" + UUID_PATTERN + "}/uncancel")
@Produces(APPLICATION_JSON)
@@ -183,7 +183,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
return Response.status(Status.OK).build();
}
- //@Timed
+ @Timed
@PUT
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
@@ -244,7 +244,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
}
- //@Timed
+ @Timed
@DELETE
@Path("/{subscriptionId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java
index 7238170..343dd43 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java
@@ -74,7 +74,7 @@ public class TagDefinitionResource extends JaxRsResourceBase {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
}
- //@Timed
+ @Timed
@GET
@Produces(APPLICATION_JSON)
public Response getTagDefinitions(@javax.ws.rs.core.Context final HttpServletRequest request,
@@ -91,7 +91,7 @@ public class TagDefinitionResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(result).build();
}
- //@Timed
+ @Timed
@GET
@Path("/{tagDefinitionId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
@@ -105,7 +105,7 @@ public class TagDefinitionResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(json).build();
}
- //@Timed
+ @Timed
@POST
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@@ -123,7 +123,7 @@ public class TagDefinitionResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, TagDefinitionResource.class, "getTagDefinition", createdTagDef.getId());
}
- //@Timed
+ @Timed
@DELETE
@Path("/{tagDefinitionId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagResource.java
index 8c68487..4871ce2 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagResource.java
@@ -72,7 +72,7 @@ public class TagResource extends JaxRsResourceBase {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
}
- //@Timed
+ @Timed
@GET
@Path("/" + PAGINATION)
@Produces(APPLICATION_JSON)
@@ -103,7 +103,7 @@ public class TagResource extends JaxRsResourceBase {
nextPageUri);
}
- //@Timed
+ @Timed
@GET
@Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
@Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java
index 901801e..b0e6789 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java
@@ -80,7 +80,7 @@ public class TenantResource extends JaxRsResourceBase {
this.tenantApi = tenantApi;
}
- //@Timed
+ @Timed
@GET
@Path("/{tenantId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
@@ -89,7 +89,7 @@ public class TenantResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(new TenantJson(tenant)).build();
}
- //@Timed
+ @Timed
@GET
@Produces(APPLICATION_JSON)
public Response getTenantByApiKey(@QueryParam(QUERY_API_KEY) final String externalKey) throws TenantApiException {
@@ -97,7 +97,7 @@ public class TenantResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(new TenantJson(tenant)).build();
}
- //@Timed
+ @Timed
@POST
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@@ -112,7 +112,7 @@ public class TenantResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, TenantResource.class, "getTenant", tenant.getId());
}
- //@Timed
+ @Timed
@POST
@Path("/" + REGISTER_NOTIFICATION_CALLBACK)
@Consumes(APPLICATION_JSON)
@@ -129,7 +129,7 @@ public class TenantResource extends JaxRsResourceBase {
return Response.created(uri).build();
}
- //@Timed
+ @Timed
@GET
@Path("/" + REGISTER_NOTIFICATION_CALLBACK)
@Produces(APPLICATION_JSON)
@@ -141,7 +141,7 @@ public class TenantResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(result).build();
}
- //@Timed
+ @Timed
@DELETE
@Path("/REGISTER_NOTIFICATION_CALLBACK")
public Response deletePushNotificationCallbacks(@PathParam("tenantId") final String tenantId,
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java
index 24fb0bb..d3bca8c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java
@@ -85,7 +85,7 @@ public class TransactionResource extends JaxRsResourceBase {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
}
- //@Timed
+ @Timed
@POST
@Path("/{transactionId:" + UUID_PATTERN + "}/")
@Consumes(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/UsageResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/UsageResource.java
index 9ebe55f..71ade06 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/UsageResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/UsageResource.java
@@ -16,8 +16,10 @@
package org.killbill.billing.jaxrs.resources;
+import java.util.List;
import java.util.UUID;
+import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
@@ -32,13 +34,18 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
-import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountUserApi;
-import org.killbill.billing.jaxrs.json.UsageJson;
+import org.killbill.billing.entitlement.api.EntitlementApi;
+import org.killbill.billing.entitlement.api.EntitlementApiException;
+import org.killbill.billing.jaxrs.json.RolledUpUsageJson;
+import org.killbill.billing.jaxrs.json.SubscriptionUsageRecordJson;
import org.killbill.billing.jaxrs.util.Context;
import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
import org.killbill.billing.payment.api.PaymentApi;
import org.killbill.billing.usage.api.RolledUpUsage;
+import org.killbill.billing.usage.api.SubscriptionUsageRecord;
import org.killbill.billing.usage.api.UsageUserApi;
import org.killbill.billing.util.api.AuditUserApi;
import org.killbill.billing.util.api.CustomFieldUserApi;
@@ -48,6 +55,8 @@ import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.clock.Clock;
import com.codahale.metrics.annotation.Timed;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
import com.google.inject.Singleton;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
@@ -56,6 +65,7 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
public class UsageResource extends JaxRsResourceBase {
private final UsageUserApi usageUserApi;
+ private final EntitlementApi entitlementApi;
@Inject
public UsageResource(final JaxrsUriBuilder uriBuilder,
@@ -65,45 +75,76 @@ public class UsageResource extends JaxRsResourceBase {
final AccountUserApi accountUserApi,
final UsageUserApi usageUserApi,
final PaymentApi paymentApi,
+ final EntitlementApi entitlementApi,
final Clock clock,
final Context context) {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
this.usageUserApi = usageUserApi;
+ this.entitlementApi = entitlementApi;
}
- //@Timed
+ @Timed
@POST
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
- public Response recordUsage(final UsageJson json,
+ public Response recordUsage(final SubscriptionUsageRecordJson json,
@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) {
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws EntitlementApiException, AccountApiException {
+
+ Preconditions.checkNotNull(json.getSubscriptionId());
+ Preconditions.checkNotNull(json.getUnitUsageRecords());
+ Preconditions.checkArgument(!json.getUnitUsageRecords().isEmpty());
final CallContext callContext = context.createContext(createdBy, reason, comment, request);
- usageUserApi.recordRolledUpUsage(UUID.fromString(json.getSubscriptionId()), json.getUnitType(), json.getStartTime(), json.getEndTime(), json.getAmount(), callContext);
+ // Verify subscription exists..
+ entitlementApi.getEntitlementForId(UUID.fromString(json.getSubscriptionId()), callContext);
+
+ final SubscriptionUsageRecord record = json.toSubscriptionUsageRecord();
+ usageUserApi.recordRolledUpUsage(record, callContext);
return Response.status(Status.CREATED).build();
}
- //@Timed
+ @Timed
@GET
@Path("/{subscriptionId:" + UUID_PATTERN + "}/{unitType}")
@Produces(APPLICATION_JSON)
public Response getUsage(@PathParam("subscriptionId") final String subscriptionId,
- @PathParam("unitType") final String unitType,
- @QueryParam(QUERY_START_TIME) final String startTime,
- @QueryParam(QUERY_END_TIME) final String endTime,
- @javax.ws.rs.core.Context final HttpServletRequest request) {
+ @PathParam("unitType") final String unitType,
+ @QueryParam(QUERY_START_DATE) final String startDate,
+ @QueryParam(QUERY_END_DATE) final String endDate,
+ @javax.ws.rs.core.Context final HttpServletRequest request) {
+
+ final TenantContext tenantContext = context.createContext(request);
+
+ final LocalDate usageStartDate = LOCAL_DATE_FORMATTER.parseLocalDate(startDate);
+ final LocalDate usageEndDate = LOCAL_DATE_FORMATTER.parseLocalDate(endDate);
+
+ final RolledUpUsage usage = usageUserApi.getUsageForSubscription(UUID.fromString(subscriptionId), unitType, usageStartDate, usageEndDate, tenantContext);
+ final RolledUpUsageJson result = new RolledUpUsageJson(usage);
+ return Response.status(Status.OK).entity(result).build();
+ }
+
+ @Timed
+ @GET
+ @Path("/{subscriptionId:" + UUID_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response getAllUsage(@PathParam("subscriptionId") final String subscriptionId,
+ @QueryParam(QUERY_START_DATE) final String startDate,
+ @QueryParam(QUERY_END_DATE) final String endDate,
+ @javax.ws.rs.core.Context final HttpServletRequest request) {
final TenantContext tenantContext = context.createContext(request);
- final DateTime usageStartTime = DATE_TIME_FORMATTER.parseDateTime(startTime);
- final DateTime usageEndTime = DATE_TIME_FORMATTER.parseDateTime(endTime);
+ final LocalDate usageStartDate = LOCAL_DATE_FORMATTER.parseLocalDate(startDate);
+ final LocalDate usageEndDate = LOCAL_DATE_FORMATTER.parseLocalDate(endDate);
- final RolledUpUsage usage = usageUserApi.getUsageForSubscription(UUID.fromString(subscriptionId), unitType, usageStartTime, usageEndTime, tenantContext);
- final UsageJson result = new UsageJson(usage);
+ // The current JAXRS API only allows to look for one transition
+ final List<LocalDate> startEndDate = ImmutableList.<LocalDate>builder().add(usageStartDate).add(usageEndDate).build();
+ final List<RolledUpUsage> usage = usageUserApi.getAllUsageForSubscription(UUID.fromString(subscriptionId), startEndDate, tenantContext);
+ final RolledUpUsageJson result = new RolledUpUsageJson(usage.get(0));
return Response.status(Status.OK).entity(result).build();
}
junction/pom.xml 2(+1 -1)
diff --git a/junction/pom.xml b/junction/pom.xml
index 0fd4150..fe10ae8 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-junction</artifactId>
NEWS 3(+3 -0)
diff --git a/NEWS b/NEWS
index 6dd213b..25fa17e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,7 @@
0.11.10
+ Payment bug fixes
+ New Janitor tasks
+ JMX properties refinment
Fix broken logging (war artifacts)
https://github.com/killbill/killbill/issues/210
Update killbill-oss-parent to 0.7.22
overdue/pom.xml 2(+1 -1)
diff --git a/overdue/pom.xml b/overdue/pom.xml
index 788e1f8..abc1d35 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-overdue</artifactId>
payment/pom.xml 2(+1 -1)
diff --git a/payment/pom.xml b/payment/pom.xml
index 8cc8597..5e5b40f 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-payment</artifactId>
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/AttemptCompletionTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/AttemptCompletionTask.java
new file mode 100644
index 0000000..5714907
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/AttemptCompletionTask.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.payment.core.janitor;
+
+import java.util.List;
+
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
+import org.killbill.billing.payment.core.sm.PluginControlledPaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.RetryStateMachineHelper;
+import org.killbill.billing.payment.core.sm.RetryablePaymentStateContext;
+import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.dao.PluginPropertySerializer;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.clock.Clock;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+/**
+ * Task to complete 'partially' incomplete payment attempts. Tis only matters for calls that went through PaymentControl apis.
+ * <p/>
+ * If the state of the transaction associated with the attempt completed, but the attempt state machine did not,
+ * we rerun the retry state machine to complete the call and transition the attempt into a terminal state.
+ */
+final class AttemptCompletionTask extends CompletionTaskBase<PaymentAttemptModelDao> {
+
+ public AttemptCompletionTask(final Janitor janitor, final InternalCallContextFactory internalCallContextFactory, final PaymentConfig paymentConfig,
+ final NonEntityDao nonEntityDao, final PaymentDao paymentDao, final Clock clock, final PaymentStateMachineHelper paymentStateMachineHelper,
+ final RetryStateMachineHelper retrySMHelper, final CacheControllerDispatcher controllerDispatcher, final AccountInternalApi accountInternalApi,
+ final PluginControlledPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry) {
+ super(janitor, internalCallContextFactory, paymentConfig, nonEntityDao, paymentDao, clock, paymentStateMachineHelper, retrySMHelper, controllerDispatcher, accountInternalApi, pluginControlledPaymentAutomatonRunner, pluginRegistry);
+ }
+
+ @Override
+ public List<PaymentAttemptModelDao> getItemsForIteration() {
+ final List<PaymentAttemptModelDao> incompleteAttempts = paymentDao.getPaymentAttemptsByState(retrySMHelper.getInitialState().getName(), getCreatedDateBefore(), completionTaskCallContext);
+ log.info("Janitor AttemptCompletionTask start run : found " + incompleteAttempts.size() + " incomplete attempts");
+ return incompleteAttempts;
+ }
+
+ @Override
+ public void doIteration(final PaymentAttemptModelDao attempt) {
+ final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(attempt.getAccountId(), attempt.getId(), ObjectType.PAYMENT_ATTEMPT);
+ final CallContext callContext = createCallContext("AttemptCompletionJanitorTask", tenantContext);
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(attempt.getAccountId(), callContext);
+
+ final List<PaymentTransactionModelDao> transactions = paymentDao.getPaymentTransactionsByExternalKey(attempt.getTransactionExternalKey(), tenantContext);
+ final PaymentTransactionModelDao transaction = Iterables.tryFind(transactions, new Predicate<PaymentTransactionModelDao>() {
+ @Override
+ public boolean apply(final PaymentTransactionModelDao input) {
+ return input.getAttemptId().equals(attempt.getId()) &&
+ input.getTransactionStatus() == TransactionStatus.SUCCESS;
+ }
+ }).orNull();
+
+ if (transaction == null) {
+ log.info("Janitor AttemptCompletionTask moving attempt " + attempt.getId() + " -> ABORTED");
+ paymentDao.updatePaymentAttempt(attempt.getId(), attempt.getTransactionId(), "ABORTED", internalCallContext);
+ return;
+ }
+
+ try {
+ log.info("Janitor AttemptCompletionTask completing attempt " + attempt.getId() + " -> SUCCESS");
+
+ final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), tenantContext);
+ final boolean isApiPayment = true; // unclear
+ final RetryablePaymentStateContext paymentStateContext = new RetryablePaymentStateContext(attempt.getPluginName(),
+ isApiPayment,
+ transaction.getPaymentId(),
+ attempt.getPaymentExternalKey(),
+ transaction.getTransactionExternalKey(),
+ transaction.getTransactionType(),
+ account,
+ attempt.getPaymentMethodId(),
+ transaction.getAmount(),
+ transaction.getCurrency(),
+ PluginPropertySerializer.deserialize(attempt.getPluginProperties()),
+ internalCallContext,
+ callContext);
+
+ paymentStateContext.setAttemptId(attempt.getId()); // Normally set by leavingState Callback
+ paymentStateContext.setPaymentTransactionModelDao(transaction); // Normally set by raw state machine
+ //
+ // Will rerun the state machine with special callbacks to only make the onCompletion call
+ // to the PaymentControlPluginApi plugin and transition the state.
+ //
+ pluginControlledPaymentAutomatonRunner.completeRun(paymentStateContext);
+ } catch (AccountApiException e) {
+ log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+ } catch (PluginPropertySerializerException e) {
+ log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+ } catch (PaymentApiException e) {
+ log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+ }
+ }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java
new file mode 100644
index 0000000..9f68a5e
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.payment.core.janitor;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.callcontext.DefaultCallContext;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
+import org.killbill.billing.payment.core.sm.PluginControlledPaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.RetryStateMachineHelper;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.CallOrigin;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.callcontext.UserType;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.clock.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+abstract class CompletionTaskBase<T> implements Runnable {
+
+ protected Logger log = LoggerFactory.getLogger(CompletionTaskBase.class);
+
+ private Janitor janitor;
+ private final String taskName;
+
+ protected final PaymentConfig paymentConfig;
+ protected final Clock clock;
+ protected final PaymentDao paymentDao;
+ protected final InternalCallContext completionTaskCallContext;
+ protected final InternalCallContextFactory internalCallContextFactory;
+ protected final NonEntityDao nonEntityDao;
+ protected final PaymentStateMachineHelper paymentStateMachineHelper;
+ protected final RetryStateMachineHelper retrySMHelper;
+ protected final CacheControllerDispatcher controllerDispatcher;
+ protected final AccountInternalApi accountInternalApi;
+ protected final PluginControlledPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner;
+ protected final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry;
+
+
+ public CompletionTaskBase(final Janitor janitor, final InternalCallContextFactory internalCallContextFactory, final PaymentConfig paymentConfig,
+ final NonEntityDao nonEntityDao, final PaymentDao paymentDao, final Clock clock, final PaymentStateMachineHelper paymentStateMachineHelper,
+ final RetryStateMachineHelper retrySMHelper, final CacheControllerDispatcher controllerDispatcher, final AccountInternalApi accountInternalApi,
+ final PluginControlledPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry) {
+ this.janitor = janitor;
+ this.internalCallContextFactory = internalCallContextFactory;
+ this.paymentConfig = paymentConfig;
+ this.nonEntityDao = nonEntityDao;
+ this.paymentDao = paymentDao;
+ this.clock = clock;
+ this.paymentStateMachineHelper = paymentStateMachineHelper;
+ this.retrySMHelper = retrySMHelper;
+ this.controllerDispatcher = controllerDispatcher;
+ this.accountInternalApi = accountInternalApi;
+ this.pluginControlledPaymentAutomatonRunner = pluginControlledPaymentAutomatonRunner;
+ this.pluginRegistry = pluginRegistry;
+ this.taskName = this.getClass().getName();
+ this.completionTaskCallContext = internalCallContextFactory.createInternalCallContext((Long) null, (Long) null, taskName, CallOrigin.INTERNAL, UserType.SYSTEM, UUID.randomUUID());
+ }
+
+ @Override
+ public void run() {
+
+ if (janitor.isStopped()) {
+ log.info("Janitor Task " + taskName + " was requested to stop");
+ return;
+ }
+ final List<T> items = getItemsForIteration();
+ for (T item : items) {
+ if (janitor.isStopped()) {
+ log.info("Janitor Task " + taskName + " was requested to stop");
+ return;
+ }
+ try {
+ doIteration(item);
+ } catch(IllegalStateException e) {
+ log.warn(e.getMessage());
+ }
+ }
+ }
+
+ public abstract List<T> getItemsForIteration();
+
+ public abstract void doIteration(final T item);
+
+ protected CallContext createCallContext(final String taskName, final InternalTenantContext tenantContext) {
+ final UUID tenantId = nonEntityDao.retrieveIdFromObject(tenantContext.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+ final CallContext callContext = new DefaultCallContext(tenantId, taskName, CallOrigin.INTERNAL, UserType.SYSTEM, UUID.randomUUID(), clock);
+ return callContext;
+ }
+
+
+ protected DateTime getCreatedDateBefore() {
+ final long delayBeforeNowMs = paymentConfig.getJanitorPendingCleanupTime().getMillis();
+ return clock.getUTCNow().minusMillis((int) delayBeforeNowMs);
+ }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/ErroredPaymentTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/ErroredPaymentTask.java
new file mode 100644
index 0000000..fd6d9f3
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/ErroredPaymentTask.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.payment.core.janitor;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.killbill.automaton.StateMachine;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.core.sm.PaymentEnteringStateCallback;
+import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
+import org.killbill.billing.payment.core.sm.PluginControlledPaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.RetryStateMachineHelper;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PaymentMethodModelDao;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.clock.Clock;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class ErroredPaymentTask extends CompletionTaskBase<PaymentModelDao> {
+
+ // We could configure all that if this becomes useful but we also want to avoid a flurry of parameters.
+ private static final int SAFETY_DELAY_MS = (3 * 60 * 1000); // 3 minutes
+ private final int OLDER_PAYMENTS_IN_DAYS = 3; // don't look at ERRORED payment older than 3 days
+ private final int MAX_ITEMS_PER_LOOP = 100; // Limit of items per iteration
+
+ public ErroredPaymentTask(final Janitor janitor, final InternalCallContextFactory internalCallContextFactory, final PaymentConfig paymentConfig,
+ final NonEntityDao nonEntityDao, final PaymentDao paymentDao, final Clock clock,
+ final PaymentStateMachineHelper paymentStateMachineHelper, final RetryStateMachineHelper retrySMHelper, final CacheControllerDispatcher controllerDispatcher, final AccountInternalApi accountInternalApi,
+ final PluginControlledPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry) {
+ super(janitor, internalCallContextFactory, paymentConfig, nonEntityDao, paymentDao, clock, paymentStateMachineHelper, retrySMHelper, controllerDispatcher, accountInternalApi, pluginControlledPaymentAutomatonRunner, pluginRegistry);
+ }
+
+ @Override
+ public List<PaymentModelDao> getItemsForIteration() {
+ // In theory this should be the plugin timeout but we add a 3 minutes delay for safety.
+ int delayBeforeNow = (int) paymentConfig.getPaymentPluginTimeout().getMillis() + SAFETY_DELAY_MS;
+ final DateTime createdBeforeDate = clock.getUTCNow().minusMillis(delayBeforeNow);
+
+ // We want to avoid iterating on the same failed payments -- if for some reasons they can't fix themselves.
+ final DateTime createdAfterDate = clock.getUTCNow().minusDays(OLDER_PAYMENTS_IN_DAYS);
+
+ final List<PaymentModelDao> result = paymentDao.getPaymentsByStates(paymentStateMachineHelper.getErroredStateNames(), createdBeforeDate, createdAfterDate, MAX_ITEMS_PER_LOOP, completionTaskCallContext);
+ return result;
+ }
+
+ @Override
+ public void doIteration(final PaymentModelDao item) {
+
+ final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(item.getAccountId(), item.getId(), ObjectType.PAYMENT);
+ final CallContext callContext = createCallContext("ErroredPaymentTask", internalTenantContext);
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(item.getAccountId(), callContext);
+
+ final List<PaymentTransactionModelDao> transactions = paymentDao.getTransactionsForPayment(item.getId(), internalTenantContext);
+ Preconditions.checkState(! transactions.isEmpty(), "Janitor ErroredPaymentTask found item " + item.getId() + " with no transactions, skipping");
+
+ // We look for latest transaction in an UNKNOWN state, if not we skip
+ final PaymentTransactionModelDao unknownTransaction = transactions.get(transactions.size() - 1);
+ if (unknownTransaction.getTransactionStatus() != TransactionStatus.UNKNOWN) {
+ return;
+ }
+
+ final PaymentMethodModelDao paymentMethod = paymentDao.getPaymentMethod(item.getPaymentMethodId(), internalCallContext);
+ final PaymentPluginApi paymentPluginApi = getPaymentPluginApi(item, paymentMethod.getPluginName());
+
+
+ PaymentTransactionInfoPlugin pluginErroredTransaction = null;
+ try {
+ final List<PaymentTransactionInfoPlugin> result = paymentPluginApi.getPaymentInfo(item.getAccountId(), item.getId(), ImmutableList.<PluginProperty>of(), callContext);
+
+ pluginErroredTransaction = Iterables.tryFind(result, new Predicate<PaymentTransactionInfoPlugin>() {
+ @Override
+ public boolean apply(@Nullable final PaymentTransactionInfoPlugin input) {
+ return input.getKbTransactionPaymentId().equals(unknownTransaction.getId());
+ }
+ }).orNull();
+ } catch (PaymentPluginApiException ignored) {
+
+ }
+
+ // Compute new transactionStatus based on pluginInfo state; and if that did not change, bail early.
+ final TransactionStatus transactionStatus = PaymentEnteringStateCallback.paymentPluginStatusToTransactionStatus(pluginErroredTransaction);
+ if (transactionStatus == unknownTransaction.getTransactionStatus()) {
+ return;
+ }
+
+ // This piece of logic is obviously outside of the state machine, and this is a bit of a hack; at least all the paymentStates internal config is
+ // kept into paymentStateMachineHelper.
+ final String newPaymentState;
+ switch (transactionStatus) {
+ case PENDING:
+ newPaymentState = paymentStateMachineHelper.getPendingStateForTransaction(unknownTransaction.getTransactionType());
+ break;
+ case SUCCESS:
+ newPaymentState = paymentStateMachineHelper.getSuccessfulStateForTransaction(unknownTransaction.getTransactionType());
+ break;
+ case PAYMENT_FAILURE:
+ newPaymentState = paymentStateMachineHelper.getFailureStateForTransaction(unknownTransaction.getTransactionType());
+ break;
+ case PLUGIN_FAILURE:
+ case UNKNOWN:
+ default:
+ newPaymentState = paymentStateMachineHelper.getErroredStateForTransaction(unknownTransaction.getTransactionType());
+ break;
+ }
+ final String lastSuccessPaymentState = paymentStateMachineHelper.isSuccessState(newPaymentState) ? newPaymentState : null;
+
+
+ final BigDecimal processedAmount = pluginErroredTransaction != null ? pluginErroredTransaction.getAmount() : null;
+ final Currency processedCurrency = pluginErroredTransaction != null ? pluginErroredTransaction.getCurrency() : null;
+ final String gatewayErrorCode = pluginErroredTransaction != null ? pluginErroredTransaction.getGatewayErrorCode() : null;
+ final String gatewayError = pluginErroredTransaction != null ? pluginErroredTransaction.getGatewayError() : null;
+
+ paymentDao.updatePaymentAndTransactionOnCompletion(item.getAccountId(), item.getId(), unknownTransaction.getTransactionType(), newPaymentState, lastSuccessPaymentState,
+ unknownTransaction.getId(), transactionStatus, processedAmount, processedCurrency, gatewayErrorCode, gatewayError, internalCallContext);
+
+ }
+
+ private PaymentPluginApi getPaymentPluginApi(final PaymentModelDao item, final String pluginName) {
+ final PaymentPluginApi pluginApi = pluginRegistry.getServiceForName(pluginName);
+ Preconditions.checkState(pluginApi != null, "Janitor ErroredPaymentTask cannot retrieve PaymentPluginApi " + item.getId() + ", skipping");
+ return pluginApi;
+ }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java
new file mode 100644
index 0000000..0bc4c0e
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/Janitor.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.payment.core.janitor;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
+import org.killbill.billing.payment.core.sm.PluginControlledPaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.RetryStateMachineHelper;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.glue.PaymentModule;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.clock.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Takes care of incomplete payment/transactions.
+ */
+public class Janitor {
+
+ private final static Logger log = LoggerFactory.getLogger(Janitor.class);
+
+ private final static int TERMINATION_TIMEOUT_SEC = 5;
+
+ private final ScheduledExecutorService janitorExecutor;
+ private final PaymentConfig paymentConfig;
+ private final PendingTransactionTask pendingTransactionTask;
+ private final AttemptCompletionTask attemptCompletionTask;
+ private final ErroredPaymentTask erroredPaymentCompletionTask;
+
+ private volatile boolean isStopped;
+
+ @Inject
+ public Janitor(final AccountInternalApi accountInternalApi,
+ final PaymentDao paymentDao,
+ final PaymentConfig paymentConfig,
+ final Clock clock,
+ final NonEntityDao nonEntityDao,
+ final InternalCallContextFactory internalCallContextFactory,
+ final PluginControlledPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner,
+ @Named(PaymentModule.JANITOR_EXECUTOR_NAMED) final ScheduledExecutorService janitorExecutor,
+ final PaymentStateMachineHelper paymentSMHelper,
+ final RetryStateMachineHelper retrySMHelper,
+ final CacheControllerDispatcher controllerDispatcher,
+ final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry) {
+ this.janitorExecutor = janitorExecutor;
+ this.paymentConfig = paymentConfig;
+ this.pendingTransactionTask = new PendingTransactionTask(this, internalCallContextFactory, paymentConfig, nonEntityDao, paymentDao, clock, paymentSMHelper, retrySMHelper,
+ controllerDispatcher, accountInternalApi, pluginControlledPaymentAutomatonRunner, pluginRegistry);
+ this.attemptCompletionTask = new AttemptCompletionTask(this, internalCallContextFactory, paymentConfig, nonEntityDao, paymentDao, clock, paymentSMHelper, retrySMHelper,
+ controllerDispatcher, accountInternalApi, pluginControlledPaymentAutomatonRunner, pluginRegistry);
+ this.erroredPaymentCompletionTask = new ErroredPaymentTask(this, internalCallContextFactory, paymentConfig, nonEntityDao, paymentDao, clock, paymentSMHelper, retrySMHelper,
+ controllerDispatcher, accountInternalApi, pluginControlledPaymentAutomatonRunner, pluginRegistry);
+ this.isStopped = false;
+ }
+
+ public void start() {
+ if (isStopped) {
+ log.warn("Janitor is not a restartable service, and was already started, aborting");
+ return;
+ }
+
+ // Start task for removing old pending payments.
+ final TimeUnit pendingRateUnit = paymentConfig.getJanitorRunningRate().getUnit();
+ final long pendingPeriod = paymentConfig.getJanitorRunningRate().getPeriod();
+ janitorExecutor.scheduleAtFixedRate(pendingTransactionTask, pendingPeriod, pendingPeriod, pendingRateUnit);
+
+ // Start task for completing incomplete payment attempts
+ final TimeUnit attemptCompletionRateUnit = paymentConfig.getJanitorRunningRate().getUnit();
+ final long attemptCompletionPeriod = paymentConfig.getJanitorRunningRate().getPeriod();
+ janitorExecutor.scheduleAtFixedRate(attemptCompletionTask, attemptCompletionPeriod, attemptCompletionPeriod, attemptCompletionRateUnit);
+
+ // Start task for completing incomplete payment attempts
+ final TimeUnit erroredCompletionRateUnit = paymentConfig.getJanitorRunningRate().getUnit();
+ final long erroredCompletionPeriod = paymentConfig.getJanitorRunningRate().getPeriod();
+ janitorExecutor.scheduleAtFixedRate(erroredPaymentCompletionTask, erroredCompletionPeriod, erroredCompletionPeriod, erroredCompletionRateUnit);
+ }
+
+ public void stop() {
+ if (isStopped) {
+ log.warn("Janitor is already in a stopped state");
+ return;
+ }
+ try {
+ /* Previously submitted tasks will be executed with shutdown(); when task executes as a result of shutdown being called
+ * or because it was already in its execution loop, it will check for the volatile boolean isStopped flag and
+ * return immediately.
+ * Then, awaitTermination with a timeout is required to ensure tasks completed.
+ */
+ janitorExecutor.shutdown();
+ boolean success = janitorExecutor.awaitTermination(TERMINATION_TIMEOUT_SEC, TimeUnit.SECONDS);
+ if (!success) {
+ log.warn("Janitor failed to complete termination within " + TERMINATION_TIMEOUT_SEC + "sec");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ log.warn("Janitor stop sequence got interrupted");
+ } finally {
+ isStopped = true;
+ }
+ }
+
+ public boolean isStopped() {
+ return isStopped;
+ }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/PendingTransactionTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/PendingTransactionTask.java
new file mode 100644
index 0000000..99369a2
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/PendingTransactionTask.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.payment.core.janitor;
+
+import java.util.List;
+
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
+import org.killbill.billing.payment.core.sm.PluginControlledPaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.RetryStateMachineHelper;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.clock.Clock;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Task to find old PENDING transactions and move them into
+ */
+final class PendingTransactionTask extends CompletionTaskBase<Integer> {
+
+ private Janitor janitor;
+ private final List<Integer> itemsForIterations;
+
+ public PendingTransactionTask(final Janitor janitor, final InternalCallContextFactory internalCallContextFactory, final PaymentConfig paymentConfig,
+ final NonEntityDao nonEntityDao, final PaymentDao paymentDao, final Clock clock, final PaymentStateMachineHelper paymentStateMachineHelper,
+ final RetryStateMachineHelper retrySMHelper, final CacheControllerDispatcher controllerDispatcher, final AccountInternalApi accountInternalApi,
+ final PluginControlledPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry) {
+ super(janitor, internalCallContextFactory, paymentConfig, nonEntityDao, paymentDao, clock, paymentStateMachineHelper, retrySMHelper, controllerDispatcher, accountInternalApi, pluginControlledPaymentAutomatonRunner, pluginRegistry);
+ this.itemsForIterations = ImmutableList.of(new Integer(1));
+ }
+
+ @Override
+ public List<Integer> getItemsForIteration() {
+ return itemsForIterations;
+ }
+
+ @Override
+ public void doIteration(final Integer item) {
+
+ // TODO this is needs to be fixed see- #230
+ int result = paymentDao.failOldPendingTransactions(TransactionStatus.PLUGIN_FAILURE, getCreatedDateBefore(), completionTaskCallContext);
+ if (result > 0) {
+ log.info("Janitor PendingTransactionTask moved " + result + " PENDING payments -> PLUGIN_FAILURE");
+ }
+ }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
index eeeaba6..1b2e645 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
@@ -21,6 +21,7 @@ import java.math.BigDecimal;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;
import javax.inject.Inject;
@@ -44,6 +45,7 @@ import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.control.InvoicePaymentControlPluginApi;
import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.dao.PaymentModelDao;
import org.killbill.billing.payment.dispatcher.PluginDispatcher;
@@ -57,6 +59,8 @@ import org.killbill.commons.locker.GlobalLocker;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
import com.google.inject.name.Named;
import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
@@ -94,15 +98,15 @@ public class PaymentAutomatonRunner {
}
public UUID run(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID attemptId, @Nullable final UUID paymentMethodId,
- @Nullable final UUID paymentId, @Nullable final UUID transactionId, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
+ @Nullable final UUID paymentId, @Nullable final UUID transactionId, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
@Nullable final BigDecimal amount, @Nullable final Currency currency,
final boolean shouldLockAccount, final OperationResult overridePluginOperationResult, final Iterable<PluginProperty> properties,
final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
final DateTime utcNow = clock.getUTCNow();
- final PaymentStateContext paymentStateContext = new PaymentStateContext(isApiPayment, paymentId, transactionId, attemptId, paymentExternalKey, paymentTransactionExternalKey, transactionType,
- account, paymentMethodId, amount, currency, shouldLockAccount, overridePluginOperationResult, properties, internalCallContext, callContext);
+ final PaymentStateContext paymentStateContext = new PaymentStateContext(isApiPayment, paymentId, transactionId, attemptId, paymentExternalKey, paymentTransactionExternalKey, transactionType,
+ account, paymentMethodId, amount, currency, shouldLockAccount, overridePluginOperationResult, properties, internalCallContext, callContext);
final PaymentAutomatonDAOHelper daoHelper = new PaymentAutomatonDAOHelper(paymentStateContext, utcNow, paymentDao, pluginRegistry, internalCallContext, eventBus, paymentSMHelper);
@@ -168,13 +172,14 @@ public class PaymentAutomatonRunner {
throw new IllegalStateException("Unsupported transaction type " + transactionType);
}
- runStateMachineOperation(currentStateName, transactionType, leavingStateCallback, operationCallback, enteringStateCallback);
+ runStateMachineOperation(currentStateName, transactionType, leavingStateCallback, operationCallback, enteringStateCallback, account.getId(), getInvoiceId(properties));
return paymentStateContext.getPaymentId();
}
protected void runStateMachineOperation(final String initialStateName, final TransactionType transactionType,
- final LeavingStateCallback leavingStateCallback, final OperationCallback operationCallback, final EnteringStateCallback enteringStateCallback) throws PaymentApiException {
+ final LeavingStateCallback leavingStateCallback, final OperationCallback operationCallback, final EnteringStateCallback enteringStateCallback,
+ final UUID accountId, final String invoiceId) throws PaymentApiException {
try {
final StateMachine initialStateMachine = paymentSMHelper.getStateMachineForStateName(initialStateName);
final State initialState = initialStateMachine.getState(initialStateName);
@@ -188,9 +193,22 @@ public class PaymentAutomatonRunner {
throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
} else if (e.getCause() instanceof PaymentApiException) {
throw (PaymentApiException) e.getCause();
+ } else if (e.getCause() instanceof TimeoutException) {
+ throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_PLUGIN_TIMEOUT, accountId, invoiceId);
} else {
throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
}
}
}
+
+ private String getInvoiceId(final Iterable<PluginProperty> properties) {
+ final PluginProperty invoiceProperty = Iterables.tryFind(properties, new Predicate<PluginProperty>() {
+ @Override
+ public boolean apply(final PluginProperty input) {
+ return InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID.equals(input.getKey());
+ }
+ }).orNull();
+
+ return invoiceProperty == null || invoiceProperty.getValue() == null ? null : invoiceProperty.getValue().toString();
+ }
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentEnteringStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentEnteringStateCallback.java
index fd4d928..e31416e 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentEnteringStateCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentEnteringStateCallback.java
@@ -24,14 +24,10 @@ import org.killbill.automaton.OperationResult;
import org.killbill.automaton.State;
import org.killbill.automaton.State.EnteringStateCallback;
import org.killbill.automaton.State.LeavingStateCallback;
-import org.killbill.billing.account.api.Account;
-import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.events.BusInternalEvent;
import org.killbill.billing.payment.api.DefaultPaymentErrorEvent;
import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.TransactionStatus;
-import org.killbill.billing.payment.api.TransactionType;
-import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
import org.killbill.bus.api.PersistentBus.EventBusException;
import org.slf4j.Logger;
@@ -56,12 +52,13 @@ public abstract class PaymentEnteringStateCallback implements EnteringStateCallb
// If the transaction was not created -- for instance we had an exception in leavingState callback then we bail; if not, then update state:
if (paymentStateContext.getPaymentTransactionModelDao() != null && paymentStateContext.getPaymentTransactionModelDao().getId() != null) {
final PaymentTransactionInfoPlugin paymentInfoPlugin = paymentStateContext.getPaymentInfoPlugin();
- final TransactionStatus paymentStatus = paymentPluginStatusToPaymentStatus(paymentInfoPlugin, operationResult);
- daoHelper.processPaymentInfoPlugin(paymentStatus, paymentInfoPlugin, newState.getName());
+ final TransactionStatus transactionStatus = paymentPluginStatusToTransactionStatus(paymentInfoPlugin);
+ // The bus event will be posted from the transaction
+ daoHelper.processPaymentInfoPlugin(transactionStatus, paymentInfoPlugin, newState.getName());
} else if (!paymentStateContext.isApiPayment()) {
//
// If there is NO transaction to update (because payment transaction did not occur), then there is something wrong happening (maybe a missing defaultPaymentMethodId, ...)
- // so, if the does NOT call originates from api then we still want to send a bus event so the system can react to it if needed.
+ // so, if the call does NOT originates from api then we still want to send a bus event so the system can react to it if needed.
//
final BusInternalEvent event = new DefaultPaymentErrorEvent(paymentStateContext.getAccount().getId(),
null,
@@ -78,31 +75,27 @@ public abstract class PaymentEnteringStateCallback implements EnteringStateCallb
}
}
- private TransactionStatus paymentPluginStatusToPaymentStatus(@Nullable final PaymentTransactionInfoPlugin paymentInfoPlugin, final OperationResult operationResult) {
- if (paymentInfoPlugin == null) {
- if (OperationResult.EXCEPTION.equals(operationResult)) {
- // We got an exception during the plugin call
- return TransactionStatus.PLUGIN_FAILURE;
- } else {
- // The plugin completed the call but returned null?! Bad plugin...
- return TransactionStatus.PLUGIN_FAILURE;
- }
- }
+ public static TransactionStatus paymentPluginStatusToTransactionStatus(@Nullable final PaymentTransactionInfoPlugin paymentInfoPlugin) {
- if (paymentInfoPlugin.getStatus() == null) {
- // The plugin completed the call but returned an incomplete PaymentInfoPlugin?! Bad plugin...
- return TransactionStatus.UNKNOWN;
+ //
+ // paymentInfoPlugin when we got an exception from the plugin, or if the plugin behaves badly
+ // and decides to return null; in all cases this is seen as a PLUGIN_FAILURE
+ //
+ if (paymentInfoPlugin == null || paymentInfoPlugin.getStatus() == null) {
+ return TransactionStatus.PLUGIN_FAILURE;
}
+ //
+ // The plugin returned a status or it timedout and we added manually a UNKNOWN status to end up here
+ //
switch (paymentInfoPlugin.getStatus()) {
- case UNDEFINED:
- return TransactionStatus.UNKNOWN;
case PROCESSED:
return TransactionStatus.SUCCESS;
case PENDING:
return TransactionStatus.PENDING;
case ERROR:
return TransactionStatus.PAYMENT_FAILURE;
+ case UNDEFINED:
default:
return TransactionStatus.UNKNOWN;
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentOperation.java
index e2b2a65..1a44ca5 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentOperation.java
@@ -83,7 +83,7 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
return new OperationException(realException, OperationResult.FAILURE);
} else if (e.getCause() instanceof LockFailedException) {
final String format = String.format("Failed to lock account %s", paymentStateContext.getAccount().getExternalKey());
- logger.error(String.format(format), e);
+ logger.error(String.format(format));
return new OperationException(realException, OperationResult.FAILURE);
} else /* if (e instanceof RuntimeException) */ {
logger.warn("Plugin call threw an exception for account {}", paymentStateContext.getAccount().getExternalKey(), e);
@@ -93,13 +93,25 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
@Override
protected OperationException wrapTimeoutException(final PaymentStateContext paymentStateContext, final TimeoutException e) {
- logger.error("Plugin call TIMEOUT for account {}: {}", paymentStateContext.getAccount().getExternalKey(), e.getMessage());
+ logger.error("Plugin call TIMEOUT for account {}", paymentStateContext.getAccount().getExternalKey());
+
+ final PaymentTransactionInfoPlugin paymentInfoPlugin = new DefaultNoOpPaymentInfoPlugin(paymentStateContext.getPaymentId(),
+ paymentStateContext.getTransactionId(),
+ paymentStateContext.getTransactionType(),
+ paymentStateContext.getPaymentTransactionModelDao().getProcessedAmount(),
+ paymentStateContext.getPaymentTransactionModelDao().getProcessedCurrency(),
+ paymentStateContext.getPaymentTransactionModelDao().getEffectiveDate(),
+ paymentStateContext.getPaymentTransactionModelDao().getCreatedDate(),
+ PaymentPluginStatus.UNDEFINED,
+ null);
+
+ paymentStateContext.setPaymentInfoPlugin(paymentInfoPlugin);
return new OperationException(e, OperationResult.EXCEPTION);
}
@Override
protected OperationException wrapInterruptedException(final PaymentStateContext paymentStateContext, final InterruptedException e) {
- logger.error("Plugin call was interrupted for account {}: {}", paymentStateContext.getAccount().getExternalKey(), e.getMessage());
+ logger.error("Plugin call was interrupted for account {}", paymentStateContext.getAccount().getExternalKey());
return new OperationException(e, OperationResult.EXCEPTION);
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
index 763ec9c..bc58d65 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
@@ -33,6 +33,9 @@ import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
+/**
+ * This class needs to know about the payment state machine xml file. All the knowledge about the xml file is encapsulated here.
+ */
public class PaymentStateMachineHelper {
private static final String BIG_BANG_STATE_MACHINE_NAME = "BIG_BANG";
@@ -44,17 +47,35 @@ public class PaymentStateMachineHelper {
private static final String VOID_STATE_MACHINE_NAME = "VOID";
private static final String CHARGEBACK_STATE_MACHINE_NAME = "CHARGEBACK";
-
- private static final String BIG_BANG_INIT_STATE_NAME = "BIG_BANG_INIT";
- private static final String AUTHORIZE_INIT_STATE_NAME = "AUTH_INIT";
- private static final String CAPTURE_INIT_STATE_NAME = "CAPTURE_INIT";
- private static final String PURCHASE_INIT_STATE_NAME = "PURCHASE_INIT";
- private static final String REFUND_INIT_STATE_NAME = "REFUND_INIT";
- private static final String CREDIT_INIT_STATE_NAME = "CREDIT_INIT";
- private static final String VOID_INIT_STATE_NAME = "VOID_INIT";
- private static final String CHARGEBACK_INIT_STATE_NAME = "CHARGEBACK_INIT";
-
+ private static final String BIG_BANG_INIT = "BIG_BANG_INIT";
+
+ private static final String AUTHORIZE_SUCCESS = "AUTH_SUCCESS";
+ private static final String CAPTURE_SUCCESS = "CAPTURE_SUCCESS";
+ private static final String PURCHASE_SUCCESS = "PURCHASE_SUCCESS";
+ private static final String REFUND_SUCCESS = "REFUND_SUCCESS";
+ private static final String CREDIT_SUCCESS = "CREDIT_SUCCESS";
+ private static final String VOID_SUCCESS = "VOID_SUCCESS";
+ private static final String CHARGEBACK_SUCCESS = "CHARGEBACK_SUCCESS";
+
+ private static final String AUTHORIZE_PENDING = "AUTHORIZE_PENDING";
+
+ private static final String AUTHORIZE_FAILED = "AUTH_FAILED";
+ private static final String CAPTURE_FAILED = "CAPTURE_FAILED";
+ private static final String PURCHASE_FAILED = "PURCHASE_FAILED";
+ private static final String REFUND_FAILED = "REFUND_FAILED";
+ private static final String CREDIT_FAILED = "CREDIT_FAILED";
+ private static final String VOID_FAILED = "VOID_FAILED";
+ private static final String CHARGEBACK_FAILED = "CHARGEBACK_FAILED";
+
+ private static final String AUTH_ERRORED = "AUTH_ERRORED";
+ private static final String CAPTURE_ERRORED = "CAPTURE_ERRORED";
+ private static final String PURCHASE_ERRORED = "PURCHASE_ERRORED";
+ private static final String REFUND_ERRORED = "REFUND_ERRORED";
+ private static final String CREDIT_ERRORED = "CREDIT_ERRORED";
+ private static final String VOID_ERRORED = "VOID_ERRORED";
+ private static final String CHARGEBACK_ERRORED = "CHARGEBACK_ERRORED";
private final StateMachineConfig stateMachineConfig;
+ private final String[] errorStateNames = {AUTH_ERRORED, CAPTURE_ERRORED, PURCHASE_ERRORED, REFUND_ERRORED, CREDIT_ERRORED, VOID_ERRORED, CHARGEBACK_ERRORED};
@Inject
public PaymentStateMachineHelper(@javax.inject.Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig) {
@@ -62,12 +83,85 @@ public class PaymentStateMachineHelper {
}
public State getState(final String stateName) throws MissingEntryException {
- final StateMachine stateMachine = stateMachineConfig.getStateMachineForState(stateName);
+ final StateMachine stateMachine = stateMachineConfig.getStateMachineForState(stateName);
return stateMachine.getState(stateName);
}
public String getInitStateNameForTransaction() {
- return BIG_BANG_INIT_STATE_NAME;
+ return BIG_BANG_INIT;
+ }
+
+ public String getSuccessfulStateForTransaction(final TransactionType transactionType) {
+ switch (transactionType) {
+ case AUTHORIZE:
+ return AUTHORIZE_SUCCESS;
+ case CAPTURE:
+ return CAPTURE_SUCCESS;
+ case PURCHASE:
+ return PURCHASE_SUCCESS;
+ case REFUND:
+ return REFUND_SUCCESS;
+ case CREDIT:
+ return CREDIT_SUCCESS;
+ case VOID:
+ return VOID_SUCCESS;
+ case CHARGEBACK:
+ return CHARGEBACK_SUCCESS;
+ default:
+ throw new IllegalStateException("Unsupported transaction type " + transactionType);
+ }
+ }
+
+ public String getPendingStateForTransaction(final TransactionType transactionType) {
+ switch (transactionType) {
+ case AUTHORIZE:
+ return AUTHORIZE_PENDING;
+ default:
+ throw new IllegalStateException("Unsupported transaction type " + transactionType);
+ }
+ }
+
+
+ public String getErroredStateForTransaction(final TransactionType transactionType) {
+ switch (transactionType) {
+ case AUTHORIZE:
+ return AUTH_ERRORED;
+ case CAPTURE:
+ return CAPTURE_ERRORED;
+ case PURCHASE:
+ return PURCHASE_ERRORED;
+ case REFUND:
+ return REFUND_ERRORED;
+ case CREDIT:
+ return CREDIT_ERRORED;
+ case VOID:
+ return VOID_ERRORED;
+ case CHARGEBACK:
+ return CHARGEBACK_ERRORED;
+ default:
+ throw new IllegalStateException("Unsupported transaction type " + transactionType);
+ }
+ }
+
+ public String getFailureStateForTransaction(final TransactionType transactionType) {
+ switch (transactionType) {
+ case AUTHORIZE:
+ return AUTHORIZE_FAILED;
+ case CAPTURE:
+ return CAPTURE_FAILED;
+ case PURCHASE:
+ return PURCHASE_FAILED;
+ case REFUND:
+ return REFUND_FAILED;
+ case CREDIT:
+ return CREDIT_FAILED;
+ case VOID:
+ return VOID_FAILED;
+ case CHARGEBACK:
+ return CHARGEBACK_FAILED;
+ default:
+ throw new IllegalStateException("Unsupported transaction type " + transactionType);
+ }
}
public StateMachine getStateMachineForStateName(final String stateName) throws MissingEntryException {
@@ -118,4 +212,9 @@ public class PaymentStateMachineHelper {
}).orNull();
return transition != null ? transition.getFinalState() : null;
}
+
+ public String[] getErroredStateNames() {
+ return errorStateNames;
+ }
+
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java
index 0e44d5c..580e180 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java
@@ -157,7 +157,7 @@ public abstract class RetryOperationCallback extends OperationCallbackBase<Payme
return (OperationException) e.getCause();
} else if (e.getCause() instanceof LockFailedException) {
final String format = String.format("Failed to lock account %s", paymentStateContext.getAccount().getExternalKey());
- logger.error(String.format(format), e);
+ logger.error(String.format(format));
return new OperationException(e, getOperationResultOnException(paymentStateContext));
} else /* most probably RuntimeException */ {
logger.warn("RetryOperationCallback failed for account {}", paymentStateContext.getAccount().getExternalKey(), e);
@@ -167,13 +167,13 @@ public abstract class RetryOperationCallback extends OperationCallbackBase<Payme
@Override
protected OperationException wrapTimeoutException(final PaymentStateContext paymentStateContext, final TimeoutException e) {
- logger.error("RetryOperationCallback call TIMEOUT for account {}: {}", paymentStateContext.getAccount().getExternalKey(), e.getMessage());
+ logger.warn("RetryOperationCallback call TIMEOUT for account {}", paymentStateContext.getAccount().getExternalKey());
return new OperationException(e, getOperationResultOnException(paymentStateContext));
}
@Override
protected OperationException wrapInterruptedException(final PaymentStateContext paymentStateContext, final InterruptedException e) {
- logger.error("RetryOperationCallback call was interrupted for account {}: {}", paymentStateContext.getAccount().getExternalKey(), e.getMessage());
+ logger.error("RetryOperationCallback call was interrupted for account {}", paymentStateContext.getAccount().getExternalKey());
return new OperationException(e, getOperationResultOnException(paymentStateContext));
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryStateMachineHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryStateMachineHelper.java
index 30bb1ed..0f261cc 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryStateMachineHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryStateMachineHelper.java
@@ -37,6 +37,7 @@ public class RetryStateMachineHelper {
private static final String INIT_STATE_NAME = "INIT";
private static final String RETRIED_STATE_NAME = "RETRIED";
+
private final StateMachineConfig retryStateMachineConfig;
private final StateMachine retryStateMachine;
private final Operation retryOperation;
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
index 6423866..6719247 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
@@ -26,6 +26,7 @@ import java.util.UUID;
import javax.annotation.Nullable;
import javax.inject.Inject;
+import javax.management.ImmutableDescriptor;
import org.joda.time.DateTime;
import org.killbill.billing.callcontext.InternalCallContext;
@@ -60,6 +61,7 @@ import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
public class DefaultPaymentDao implements PaymentDao {
@@ -165,6 +167,8 @@ public class DefaultPaymentDao implements PaymentDao {
return input.getId().toString();
}
});
+
+
return transactional.failOldPendingTransactions(oldPendingTransactionIds, TransactionStatus.PAYMENT_FAILURE.toString(), context);
}
return 0;
@@ -322,6 +326,16 @@ public class DefaultPaymentDao implements PaymentDao {
}
@Override
+ public List<PaymentModelDao> getPaymentsByStates(final String[] states, final DateTime createdBeforeDate, final DateTime createdAfterDate, final int limit, final InternalTenantContext context) {
+ return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentModelDao>>() {
+ @Override
+ public List<PaymentModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+ return entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).getPaymentsByStates(ImmutableList.copyOf(states), createdBeforeDate.toDate(), createdAfterDate.toDate(), context, limit);
+ }
+ });
+ }
+
+ @Override
public List<PaymentTransactionModelDao> getTransactionsForAccount(final UUID accountId, final InternalTenantContext context) {
Preconditions.checkArgument(context.getAccountRecordId() != null);
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentTransactionModelDao>>() {
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
index 8055938..1119b85 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
@@ -30,13 +30,13 @@ import org.killbill.billing.util.entity.Pagination;
public interface PaymentDao {
- public int failOldPendingTransactions(final TransactionStatus newTransactionStatus, final DateTime createdBeforeDate, final InternalCallContext context);
+ public int failOldPendingTransactions(TransactionStatus newTransactionStatus, DateTime createdBeforeDate, InternalCallContext context);
public PaymentAttemptModelDao insertPaymentAttemptWithProperties(PaymentAttemptModelDao attempt, InternalCallContext context);
public void updatePaymentAttempt(UUID paymentAttemptId, UUID transactionId, String state, InternalCallContext context);
- public List<PaymentAttemptModelDao> getPaymentAttemptsByState(String stateName, final DateTime createdBeforeDate, InternalTenantContext context);
+ public List<PaymentAttemptModelDao> getPaymentAttemptsByState(String stateName, DateTime createdBeforeDate, InternalTenantContext context);
public List<PaymentAttemptModelDao> getPaymentAttempts(String paymentExternalKey, InternalTenantContext context);
@@ -54,7 +54,7 @@ public interface PaymentDao {
public PaymentTransactionModelDao updatePaymentWithNewTransaction(UUID paymentId, PaymentTransactionModelDao paymentTransaction, InternalCallContext context);
- public void updatePaymentAndTransactionOnCompletion(UUID accountId, UUID paymentId, final TransactionType transactionType, String currentPaymentStateName, String lastPaymentSuccessStateName, UUID transactionId,
+ public void updatePaymentAndTransactionOnCompletion(UUID accountId, UUID paymentId, TransactionType transactionType, String currentPaymentStateName, String lastPaymentSuccessStateName, UUID transactionId,
TransactionStatus paymentStatus, BigDecimal processedAmount, Currency processedCurrency,
String gatewayErrorCode, String gatewayErrorMsg, InternalCallContext context);
@@ -64,6 +64,8 @@ public interface PaymentDao {
public List<PaymentModelDao> getPaymentsForAccount(UUID accountId, InternalTenantContext context);
+ public List<PaymentModelDao> getPaymentsByStates(String [] states, DateTime createdBeforeDate, DateTime createdAfterDate, int limit, InternalTenantContext context);
+
public List<PaymentTransactionModelDao> getTransactionsForAccount(UUID accountId, InternalTenantContext context);
public List<PaymentTransactionModelDao> getTransactionsForPayment(UUID paymentId, InternalTenantContext context);
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java
index 432d130..4d8dd0c 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java
@@ -16,7 +16,10 @@
package org.killbill.billing.payment.dao;
+import java.util.Collection;
+import java.util.Date;
import java.util.Iterator;
+import java.util.List;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
@@ -30,6 +33,7 @@ import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.Define;
@EntitySqlDaoStringTemplate
public interface PaymentSqlDao extends EntitySqlDao<PaymentModelDao, Payment> {
@@ -56,6 +60,14 @@ public interface PaymentSqlDao extends EntitySqlDao<PaymentModelDao, Payment> {
@SqlQuery
public PaymentModelDao getPaymentByExternalKey(@Bind("externalKey") final String externalKey,
@BindBean final InternalTenantContext context);
+
+ @SqlQuery
+ public List<PaymentModelDao> getPaymentsByStates(@StateCollectionBinder final Collection<String> states,
+ @Bind("createdBeforeDate") final Date createdBeforeDate,
+ @Bind("createdAfterDate") final Date createdAfterDate,
+ @BindBean final InternalTenantContext context,
+ @Bind("limit") final int limit);
+
@SqlQuery
@SmartFetchSize(shouldStream = true)
public Iterator<PaymentModelDao> getByPluginName(@Bind("pluginName") final String pluginName,
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/StateCollectionBinder.java b/payment/src/main/java/org/killbill/billing/payment/dao/StateCollectionBinder.java
new file mode 100644
index 0000000..8aac0c9
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/StateCollectionBinder.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.payment.dao;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Collection;
+
+import org.killbill.billing.payment.dao.StateCollectionBinder.StateCollectionBinderFactory;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+
+@BindingAnnotation(StateCollectionBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface StateCollectionBinder {
+
+ public static class StateCollectionBinderFactory implements BinderFactory {
+
+ @Override
+ public Binder build(Annotation annotation) {
+ return new Binder<StateCollectionBinder, Collection<String>>() {
+
+ @Override
+ public void bind(SQLStatement<?> query, StateCollectionBinder bind, Collection<String> states) {
+ query.define("states", states);
+
+ int idx = 0;
+ for (String state : states) {
+ query.bind("state_" + idx, state);
+ idx++;
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java
index 2a8e0aa..7dc8b60 100644
--- a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java
@@ -22,7 +22,7 @@ import org.killbill.billing.payment.api.PaymentApi;
import org.killbill.billing.payment.api.PaymentService;
import org.killbill.billing.payment.bus.InvoiceHandler;
import org.killbill.billing.payment.control.PaymentTagHandler;
-import org.killbill.billing.payment.core.Janitor;
+import org.killbill.billing.payment.core.janitor.Janitor;
import org.killbill.billing.payment.retry.DefaultRetryService;
import org.killbill.billing.platform.api.LifecycleHandlerType;
import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
index f5a8722..23a7581 100644
--- a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
@@ -37,7 +37,7 @@ import org.killbill.billing.payment.api.PaymentService;
import org.killbill.billing.payment.bus.InvoiceHandler;
import org.killbill.billing.payment.control.PaymentTagHandler;
import org.killbill.billing.payment.control.dao.InvoicePaymentControlDao;
-import org.killbill.billing.payment.core.Janitor;
+import org.killbill.billing.payment.core.janitor.Janitor;
import org.killbill.billing.payment.core.PaymentGatewayProcessor;
import org.killbill.billing.payment.core.PaymentMethodProcessor;
import org.killbill.billing.payment.core.PaymentProcessor;
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg
index 51ca239..3a96d8f 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -101,3 +101,15 @@ join payment_methods pm on pm.id = t.payment_method_id
where pm.plugin_name = :pluginName
;
>>
+
+getPaymentsByStates(states) ::= <<
+select
+<allTableFields("t.")>
+from <tableName()> t
+where
+created_date >= :createdAfterDate
+and created_date \<= :createdBeforeDate
+and state_name in (<states: {state | :state_<i0>}; separator="," >)
+limit :limit
+;
+>>
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
index 2207741..2adef8b 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
@@ -220,6 +220,11 @@ public class MockPaymentDao implements PaymentDao {
}
@Override
+ public List<PaymentModelDao> getPaymentsByStates(final String[] states, final DateTime createdBeforeDate, final DateTime createdAfterDate, final int limit, final InternalTenantContext context) {
+ return null;
+ }
+
+ @Override
public List<PaymentTransactionModelDao> getTransactionsForAccount(final UUID accountId, final InternalTenantContext context) {
synchronized (this) {
return ImmutableList.copyOf(Iterables.filter(transactions.values(), new Predicate<PaymentTransactionModelDao>() {
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
index d8e3b15..69045eb 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
@@ -165,7 +165,7 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(transactions.size(), 2);
paymentDao.updatePaymentAndTransactionOnCompletion(accountId, savedPayment.getId(), savedTransactionModelDao2.getTransactionType(), "AUTH_ABORTED", "AUTH_SUCCESS", transactionModelDao2.getId(), TransactionStatus.SUCCESS,
- BigDecimal.ONE, Currency.USD, null, "nothing", internalCallContext);
+ BigDecimal.ONE, Currency.USD, null, "nothing", internalCallContext);
final PaymentModelDao savedPayment4 = paymentDao.getPayment(savedPayment.getId(), internalCallContext);
assertEquals(savedPayment4.getId(), paymentModelDao.getId());
@@ -188,7 +188,7 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(savedTransactionModelDao4.getGatewayErrorMsg(), "nothing");
paymentDao.updatePaymentAndTransactionOnCompletion(accountId, savedPayment.getId(), savedTransactionModelDao2.getTransactionType(), "AUTH_ABORTED", null, transactionModelDao2.getId(), TransactionStatus.SUCCESS,
- BigDecimal.ONE, Currency.USD, null, "nothing", internalCallContext);
+ BigDecimal.ONE, Currency.USD, null, "nothing", internalCallContext);
final PaymentModelDao savedPayment4Again = paymentDao.getPayment(savedPayment.getId(), internalCallContext);
assertEquals(savedPayment4Again.getId(), paymentModelDao.getId());
@@ -196,7 +196,7 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(savedPayment4Again.getLastSuccessStateName(), "AUTH_SUCCESS");
paymentDao.updatePaymentAndTransactionOnCompletion(accountId, savedPayment.getId(), savedTransactionModelDao2.getTransactionType(), "AUTH_ABORTED", "AUTH_SUCCESS", transactionModelDao2.getId(), TransactionStatus.SUCCESS,
- BigDecimal.ONE, Currency.USD, null, "nothing", internalCallContext);
+ BigDecimal.ONE, Currency.USD, null, "nothing", internalCallContext);
final PaymentModelDao savedPayment4Final = paymentDao.getPayment(savedPayment.getId(), internalCallContext);
assertEquals(savedPayment4Final.getId(), paymentModelDao.getId());
@@ -285,11 +285,10 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
clock.addDays(1);
final DateTime newTime = clock.getUTCNow();
-
final InternalCallContext internalCallContextWithNewTime = new InternalCallContext(InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID, 1687L, UUID.randomUUID(),
- UUID.randomUUID().toString(), CallOrigin.TEST,
- UserType.TEST, "Testing", "This is a test",
- newTime, newTime);
+ UUID.randomUUID().toString(), CallOrigin.TEST,
+ UserType.TEST, "Testing", "This is a test",
+ newTime, newTime);
final PaymentTransactionModelDao transaction4 = new PaymentTransactionModelDao(initialTime, initialTime, null, transactionExternalKey4,
paymentModelDao.getId(), TransactionType.AUTHORIZE, newTime,
@@ -297,18 +296,20 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
"pending", "");
paymentDao.updatePaymentWithNewTransaction(paymentModelDao.getId(), transaction4, internalCallContextWithNewTime);
-
final List<PaymentTransactionModelDao> result = getPendingTransactions(paymentModelDao.getId());
Assert.assertEquals(result.size(), 3);
-
paymentDao.failOldPendingTransactions(TransactionStatus.PAYMENT_FAILURE, newTime, internalCallContext);
final List<PaymentTransactionModelDao> result2 = getPendingTransactions(paymentModelDao.getId());
Assert.assertEquals(result2.size(), 1);
// Just to guarantee that next clock.getUTCNow() > newTime
- try { Thread.sleep(1000); } catch (InterruptedException e) {};
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ }
+ ;
paymentDao.failOldPendingTransactions(TransactionStatus.PAYMENT_FAILURE, clock.getUTCNow(), internalCallContextWithNewTime);
@@ -317,8 +318,146 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
}
+ @Test(groups = "slow")
+ public void testPaymentByStates() {
+
+ final UUID paymentMethodId = UUID.randomUUID();
+ final UUID accountId = UUID.randomUUID();
+ final String externalKey1 = "XXhhhhooo1";
+ final String transactionExternalKey1 = "transactionXX1";
+
+ final String externalKey2 = "XXhhhhooo2";
+ final String transactionExternalKey2 = "transactionXX2";
+
+ final String externalKey3 = "XXhhhhooo3";
+ final String transactionExternalKey3 = "transactionXX3";
+
+ final String externalKey4 = "XXhhhhooo4";
+ final String transactionExternalKey4 = "transactionXX4";
+
+ final String externalKey5 = "XXhhhhooo5";
+ final String transactionExternalKey5 = "transactionXX5";
+
+ final DateTime createdAfterDate = clock.getUTCNow().minusDays(10);
+ final DateTime createdBeforeDate = clock.getUTCNow().minusDays(1);
+
+ // Right before createdAfterDate, so should not be returned
+ final DateTime createdDate1 = createdAfterDate.minusHours(1);
+ final PaymentModelDao paymentModelDao1 = new PaymentModelDao(createdDate1, createdDate1, accountId, paymentMethodId, externalKey1);
+ paymentModelDao1.setStateName("AUTH_ERRORED");
+ final PaymentTransactionModelDao transaction1 = new PaymentTransactionModelDao(createdDate1, createdDate1, null, transactionExternalKey1,
+ paymentModelDao1.getId(), TransactionType.AUTHORIZE, createdDate1,
+ TransactionStatus.UNKNOWN, BigDecimal.TEN, Currency.AED,
+ "unknown", "");
+
+ final InternalCallContext context1 = new InternalCallContext(internalCallContext.getTenantRecordId(),
+ internalCallContext.getAccountRecordId(),
+ internalCallContext.getUserToken(),
+ internalCallContext.getCreatedBy(),
+ internalCallContext.getCallOrigin(),
+ internalCallContext.getContextUserType(),
+ internalCallContext.getReasonCode(),
+ internalCallContext.getComments(),
+ createdDate1,
+ createdDate1);
+ paymentDao.insertPaymentWithFirstTransaction(paymentModelDao1, transaction1, context1);
+
+
+ // Right after createdAfterDate, so it should be returned
+ final DateTime createdDate2 = createdAfterDate.plusHours(1);
+ final PaymentModelDao paymentModelDao2 = new PaymentModelDao(createdDate2, createdDate2, accountId, paymentMethodId, externalKey2);
+ paymentModelDao2.setStateName("CAPTURE_ERRORED");
+ final PaymentTransactionModelDao transaction2 = new PaymentTransactionModelDao(createdDate2, createdDate2, null, transactionExternalKey2,
+ paymentModelDao2.getId(), TransactionType.AUTHORIZE, createdDate2,
+ TransactionStatus.UNKNOWN, BigDecimal.TEN, Currency.AED,
+ "unknown", "");
+
+ final InternalCallContext context2 = new InternalCallContext(internalCallContext.getTenantRecordId(),
+ internalCallContext.getAccountRecordId(),
+ internalCallContext.getUserToken(),
+ internalCallContext.getCreatedBy(),
+ internalCallContext.getCallOrigin(),
+ internalCallContext.getContextUserType(),
+ internalCallContext.getReasonCode(),
+ internalCallContext.getComments(),
+ createdDate2,
+ createdDate2);
+ paymentDao.insertPaymentWithFirstTransaction(paymentModelDao2, transaction2, context2);
+
+ // Right before createdBeforeDate, so it should be returned
+ final DateTime createdDate3 = createdBeforeDate.minusDays(1);
+ final PaymentModelDao paymentModelDao3 = new PaymentModelDao(createdDate3, createdDate3, accountId, paymentMethodId, externalKey3);
+ paymentModelDao3.setStateName("CAPTURE_ERRORED");
+ final PaymentTransactionModelDao transaction3 = new PaymentTransactionModelDao(createdDate3, createdDate3, null, transactionExternalKey3,
+ paymentModelDao3.getId(), TransactionType.AUTHORIZE, createdDate3,
+ TransactionStatus.UNKNOWN, BigDecimal.TEN, Currency.AED,
+ "unknown", "");
+
+ final InternalCallContext context3 = new InternalCallContext(internalCallContext.getTenantRecordId(),
+ internalCallContext.getAccountRecordId(),
+ internalCallContext.getUserToken(),
+ internalCallContext.getCreatedBy(),
+ internalCallContext.getCallOrigin(),
+ internalCallContext.getContextUserType(),
+ internalCallContext.getReasonCode(),
+ internalCallContext.getComments(),
+ createdDate3,
+ createdDate3);
+
+ paymentDao.insertPaymentWithFirstTransaction(paymentModelDao3, transaction3, context3);
+
+
+ // Right before createdBeforeDate but with a SUCCESS state so it should NOT be returned
+ final DateTime createdDate4 = createdBeforeDate.minusDays(1);
+ final PaymentModelDao paymentModelDao4 = new PaymentModelDao(createdDate4, createdDate4, accountId, paymentMethodId, externalKey4);
+ paymentModelDao4.setStateName("CAPTURE_SUCCESS");
+ final PaymentTransactionModelDao transaction4 = new PaymentTransactionModelDao(createdDate4, createdDate4, null, transactionExternalKey4,
+ paymentModelDao4.getId(), TransactionType.AUTHORIZE, createdDate4,
+ TransactionStatus.UNKNOWN, BigDecimal.TEN, Currency.AED,
+ "unknown", "");
+
+ final InternalCallContext context4 = new InternalCallContext(internalCallContext.getTenantRecordId(),
+ internalCallContext.getAccountRecordId(),
+ internalCallContext.getUserToken(),
+ internalCallContext.getCreatedBy(),
+ internalCallContext.getCallOrigin(),
+ internalCallContext.getContextUserType(),
+ internalCallContext.getReasonCode(),
+ internalCallContext.getComments(),
+ createdDate4,
+ createdDate4);
+
+ paymentDao.insertPaymentWithFirstTransaction(paymentModelDao4, transaction4, context4);
+
+ // Right after createdBeforeDate, so it should NOT be returned
+ final DateTime createdDate5 = createdBeforeDate.plusDays(1);
+ final PaymentModelDao paymentModelDao5 = new PaymentModelDao(createdDate5, createdDate5, accountId, paymentMethodId, externalKey5);
+ paymentModelDao5.setStateName("CAPTURE_ERRORED");
+ final PaymentTransactionModelDao transaction5 = new PaymentTransactionModelDao(createdDate5, createdDate5, null, transactionExternalKey5,
+ paymentModelDao5.getId(), TransactionType.AUTHORIZE, createdDate5,
+ TransactionStatus.UNKNOWN, BigDecimal.TEN, Currency.AED,
+ "unknown", "");
+
+ final InternalCallContext context5 = new InternalCallContext(internalCallContext.getTenantRecordId(),
+ internalCallContext.getAccountRecordId(),
+ internalCallContext.getUserToken(),
+ internalCallContext.getCreatedBy(),
+ internalCallContext.getCallOrigin(),
+ internalCallContext.getContextUserType(),
+ internalCallContext.getReasonCode(),
+ internalCallContext.getComments(),
+ createdDate5,
+ createdDate5);
+
+ paymentDao.insertPaymentWithFirstTransaction(paymentModelDao5, transaction5, context5);
+
+ final String[] errorStates = {"AUTH_ERRORED", "CAPTURE_ERRORED", "REFUND_ERRORED", "CREDIT_ERRORED"};
+ final List<PaymentModelDao> result = paymentDao.getPaymentsByStates(errorStates, createdBeforeDate, createdAfterDate, 10, internalCallContext);
+ assertEquals(result.size(), 2);
+ }
+
private List<PaymentTransactionModelDao> getPendingTransactions(final UUID paymentId) {
- final List<PaymentTransactionModelDao> total = paymentDao.getTransactionsForPayment(paymentId, internalCallContext);
+ final List<PaymentTransactionModelDao> total = paymentDao.getTransactionsForPayment(paymentId, internalCallContext);
return ImmutableList.copyOf(Iterables.filter(total, new Predicate<PaymentTransactionModelDao>() {
@Override
public boolean apply(final PaymentTransactionModelDao input) {
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
index 422ccfd..3281965 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -37,9 +37,8 @@ import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.control.InvoicePaymentControlPluginApi;
-import org.killbill.billing.payment.core.Janitor;
+import org.killbill.billing.payment.core.janitor.Janitor;
import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
-import org.killbill.billing.payment.glue.TestPaymentModuleWithEmbeddedDB;
import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
import org.killbill.billing.platform.api.KillbillConfigSource;
import org.killbill.bus.api.PersistentBus.EventBusException;
@@ -50,9 +49,7 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
-import com.google.inject.Guice;
import com.google.inject.Inject;
-import com.google.inject.Injector;
import static org.testng.Assert.assertEquals;
pom.xml 4(+2 -2)
diff --git a/pom.xml b/pom.xml
index fd19ffc..6a1a29f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,10 +20,10 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.7.23-SNAPSHOT</version>
+ <version>0.7.26</version>
</parent>
<artifactId>killbill</artifactId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<packaging>pom</packaging>
<name>killbill</name>
<description>Library for managing recurring subscriptions and the associated billing</description>
profiles/killbill/pom.xml 2(+1 -1)
diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml
index ee9ae1c..f91190e 100644
--- a/profiles/killbill/pom.xml
+++ b/profiles/killbill/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill-profiles</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles-killbill</artifactId>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcRealm.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcRealm.java
index 1ff2198..5b0ead6 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcRealm.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcRealm.java
@@ -74,7 +74,7 @@ public class KillbillJdbcRealm extends JdbcRealm {
private void configureDataSource() {
final DataSource realDataSource = new DataSourceProvider(config).get();
- final DataSource dataSource = new ReferenceableDataSourceSpy<DataSource>(realDataSource, SHIRO_DATA_SOURCE_ID);
+ final DataSource dataSource = new ReferenceableDataSourceSpy(realDataSource, SHIRO_DATA_SOURCE_ID);
setDataSource(dataSource);
}
}
profiles/killpay/pom.xml 2(+1 -1)
diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml
index 913b7ab..0061180 100644
--- a/profiles/killpay/pom.xml
+++ b/profiles/killpay/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>killbill-profiles</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles-killpay</artifactId>
profiles/pom.xml 2(+1 -1)
diff --git a/profiles/pom.xml b/profiles/pom.xml
index 7e8d594..e71e4d8 100644
--- a/profiles/pom.xml
+++ b/profiles/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles</artifactId>
subscription/pom.xml 2(+1 -1)
diff --git a/subscription/pom.xml b/subscription/pom.xml
index 1b46563..b696a0e 100644
--- a/subscription/pom.xml
+++ b/subscription/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-subscription</artifactId>
tenant/pom.xml 2(+1 -1)
diff --git a/tenant/pom.xml b/tenant/pom.xml
index 13b6f60..0ea3415 100644
--- a/tenant/pom.xml
+++ b/tenant/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-tenant</artifactId>
usage/pom.xml 2(+1 -1)
diff --git a/usage/pom.xml b/usage/pom.xml
index 0d62d1a..e08d78a 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-usage</artifactId>
diff --git a/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultRolledUpUnit.java b/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultRolledUpUnit.java
new file mode 100644
index 0000000..50c7ac3
--- /dev/null
+++ b/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultRolledUpUnit.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.usage.api.user;
+
+import org.killbill.billing.usage.api.RolledUpUnit;
+
+public class DefaultRolledUpUnit implements RolledUpUnit {
+
+ private final String unitType;
+ private final Long amount;
+
+ public DefaultRolledUpUnit(final String unitType, final Long amount) {
+ this.unitType = unitType;
+ this.amount = amount;
+ }
+
+ @Override
+ public String getUnitType() {
+ return unitType;
+ }
+
+ @Override
+ public Long getAmount() {
+ return amount;
+ }
+}
diff --git a/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultRolledUpUsage.java b/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultRolledUpUsage.java
index 3681acf..d28244c 100644
--- a/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultRolledUpUsage.java
+++ b/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultRolledUpUsage.java
@@ -16,34 +16,25 @@
package org.killbill.billing.usage.api.user;
-import java.math.BigDecimal;
+import java.util.List;
import java.util.UUID;
-import org.joda.time.DateTime;
-
+import org.joda.time.LocalDate;
+import org.killbill.billing.usage.api.RolledUpUnit;
import org.killbill.billing.usage.api.RolledUpUsage;
-import org.killbill.billing.usage.dao.RolledUpUsageModelDao;
public class DefaultRolledUpUsage implements RolledUpUsage {
private final UUID subscriptionId;
- private final String unitType;
- private final DateTime startTime;
- private final DateTime endTime;
- private final BigDecimal amount;
+ private final LocalDate startDate;
+ private final LocalDate endDate;
+ private final List<RolledUpUnit> rolledUpUnits;
- public DefaultRolledUpUsage(final UUID subscriptionId, final String unitType, final DateTime startTime, final DateTime endTime,
- final BigDecimal amount) {
+ public DefaultRolledUpUsage(final UUID subscriptionId, final LocalDate startDate, final LocalDate endDate, final List<RolledUpUnit> rolledUpUnits) {
this.subscriptionId = subscriptionId;
- this.unitType = unitType;
- this.startTime = startTime;
- this.endTime = endTime;
- this.amount = amount;
- }
-
- public DefaultRolledUpUsage(final RolledUpUsageModelDao usageForSubscription) {
- this(usageForSubscription.getSubscriptionId(), usageForSubscription.getUnitType(), usageForSubscription.getStartTime(),
- usageForSubscription.getEndTime(), usageForSubscription.getAmount());
+ this.startDate = startDate;
+ this.endDate = endDate;
+ this.rolledUpUnits = rolledUpUnits;
}
@Override
@@ -51,75 +42,18 @@ public class DefaultRolledUpUsage implements RolledUpUsage {
return subscriptionId;
}
- public String getUnitType() {
- return unitType;
- }
-
@Override
- public DateTime getStartTime() {
- return startTime;
+ public LocalDate getStart() {
+ return startDate;
}
@Override
- public DateTime getEndTime() {
- return endTime;
- }
-
- @Override
- public BigDecimal getAmount() {
- return amount;
- }
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- sb.append("DefaultRolledUpUsage");
- sb.append("{subscriptionId=").append(subscriptionId);
- sb.append(", unitType='").append(unitType).append('\'');
- sb.append(", startTime=").append(startTime);
- sb.append(", endTime=").append(endTime);
- sb.append(", amount=").append(amount);
- sb.append('}');
- return sb.toString();
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- final DefaultRolledUpUsage that = (DefaultRolledUpUsage) o;
-
- if (amount != null ? !amount.equals(that.amount) : that.amount != null) {
- return false;
- }
- if (endTime != null ? !endTime.equals(that.endTime) : that.endTime != null) {
- return false;
- }
- if (unitType != null ? !unitType.equals(that.unitType) : that.unitType != null) {
- return false;
- }
- if (startTime != null ? !startTime.equals(that.startTime) : that.startTime != null) {
- return false;
- }
- if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
- return false;
- }
-
- return true;
+ public LocalDate getEnd() {
+ return endDate;
}
@Override
- public int hashCode() {
- int result = subscriptionId != null ? subscriptionId.hashCode() : 0;
- result = 31 * result + (unitType != null ? unitType.hashCode() : 0);
- result = 31 * result + (startTime != null ? startTime.hashCode() : 0);
- result = 31 * result + (endTime != null ? endTime.hashCode() : 0);
- result = 31 * result + (amount != null ? amount.hashCode() : 0);
- return result;
+ public List<RolledUpUnit> getRolledUpUnit() {
+ return rolledUpUnits;
}
}
diff --git a/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java b/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java
index 5b771d9..e1020f5 100644
--- a/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java
+++ b/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java
@@ -16,24 +16,27 @@
package org.killbill.billing.usage.api.user;
-import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
-import java.util.Set;
+import java.util.Map;
import java.util.UUID;
import javax.inject.Inject;
-import org.joda.time.DateTime;
-
+import org.joda.time.LocalDate;
import org.killbill.billing.ObjectType;
+import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.usage.api.RolledUpUnit;
import org.killbill.billing.usage.api.RolledUpUsage;
+import org.killbill.billing.usage.api.SubscriptionUsageRecord;
+import org.killbill.billing.usage.api.UnitUsageRecord;
+import org.killbill.billing.usage.api.UsageRecord;
import org.killbill.billing.usage.api.UsageUserApi;
import org.killbill.billing.usage.dao.RolledUpUsageDao;
import org.killbill.billing.usage.dao.RolledUpUsageModelDao;
import org.killbill.billing.util.callcontext.CallContext;
-import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
@@ -50,33 +53,49 @@ public class DefaultUsageUserApi implements UsageUserApi {
}
@Override
- public void recordRolledUpUsage(final UUID subscriptionId, final String unitType, final DateTime startTime, final DateTime endTime,
- final BigDecimal amount, final CallContext context) {
- final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(subscriptionId, ObjectType.SUBSCRIPTION, context);
- rolledUpUsageDao.record(subscriptionId, unitType, startTime, endTime, amount, internalCallContext);
+ public void recordRolledUpUsage(final SubscriptionUsageRecord record, final CallContext callContext) {
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(record.getSubscriptionId(), ObjectType.SUBSCRIPTION, callContext);
+ for (UnitUsageRecord unitUsageRecord : record.getPerUnitUsage()) {
+ for (UsageRecord usageRecord : unitUsageRecord.getDailyAmount()) {
+ rolledUpUsageDao.record(record.getSubscriptionId(), unitUsageRecord.getUnitType(), usageRecord.getDate(), usageRecord.getAmount(), internalCallContext);
+ }
+ }
}
@Override
- public RolledUpUsage getUsageForSubscription(final UUID subscriptionId, final String unitType, final DateTime startTime, final DateTime endTime, final TenantContext context) {
- final RolledUpUsageModelDao usageForSubscription = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startTime, endTime, unitType, internalCallContextFactory.createInternalTenantContext(context));
- return new DefaultRolledUpUsage(usageForSubscription);
+ public RolledUpUsage getUsageForSubscription(final UUID subscriptionId, final String unitType, final LocalDate startDate, final LocalDate endDate, final TenantContext tenantContext) {
+ final List<RolledUpUsageModelDao> usageForSubscription = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startDate, endDate, unitType, internalCallContextFactory.createInternalTenantContext(tenantContext));
+ final List<RolledUpUnit> rolledUpAmount = getRolledUpUnits(usageForSubscription);
+ return new DefaultRolledUpUsage(subscriptionId, startDate, endDate, rolledUpAmount);
}
@Override
- public List<RolledUpUsage> getAllUsageForSubscription(final UUID subscriptionId, final Set<String> unitTypes, final List<DateTime> transitionTimes, final TenantContext tenantContext) {
-
+ public List<RolledUpUsage> getAllUsageForSubscription(final UUID subscriptionId, final List<LocalDate> transitionTimes, final TenantContext tenantContext) {
final InternalTenantContext internalCallContext = internalCallContextFactory.createInternalTenantContext(subscriptionId, ObjectType.SUBSCRIPTION, tenantContext);
List<RolledUpUsage> result = new ArrayList<RolledUpUsage>();
- DateTime prevDate = null;
- for (DateTime curDate : transitionTimes) {
+ LocalDate prevDate = null;
+ for (LocalDate curDate : transitionTimes) {
if (prevDate != null) {
- for (String unitType : unitTypes) {
- final RolledUpUsageModelDao usageForSubscription = rolledUpUsageDao.getUsageForSubscription(subscriptionId, prevDate, curDate, unitType, internalCallContext);
- result.add(new DefaultRolledUpUsage(usageForSubscription));
- }
+ final List<RolledUpUsageModelDao> usageForSubscription = rolledUpUsageDao.getAllUsageForSubscription(subscriptionId, prevDate, curDate, internalCallContext);
+ final List<RolledUpUnit> rolledUpAmount = getRolledUpUnits(usageForSubscription);
+ result.add(new DefaultRolledUpUsage(subscriptionId, prevDate, curDate, rolledUpAmount));
}
prevDate = curDate;
}
return result;
}
+
+ private List<RolledUpUnit> getRolledUpUnits(final List<RolledUpUsageModelDao> usageForSubscription) {
+ final Map<String, Long> tmp = new HashMap<String, Long>();
+ for (RolledUpUsageModelDao cur : usageForSubscription) {
+ Long currentAmount = tmp.get(cur.getUnitType());
+ Long updatedAmount = (currentAmount != null) ? currentAmount + cur.getAmount() : cur.getAmount();
+ tmp.put(cur.getUnitType(), updatedAmount);
+ }
+ final List<RolledUpUnit> result = new ArrayList<RolledUpUnit>(tmp.size());
+ for (final String unitType : tmp.keySet()) {
+ result.add(new DefaultRolledUpUnit(unitType, tmp.get(unitType)));
+ }
+ return result;
+ }
}
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/DefaultRolledUpUsageDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/DefaultRolledUpUsageDao.java
index 9c190c8..4923592 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/DefaultRolledUpUsageDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/DefaultRolledUpUsageDao.java
@@ -18,18 +18,14 @@ package org.killbill.billing.usage.dao;
import java.math.BigDecimal;
import java.util.List;
-import java.util.Set;
import java.util.UUID;
import javax.inject.Inject;
-import org.joda.time.DateTime;
-import org.killbill.billing.usage.api.RolledUpUsage;
-import org.killbill.billing.util.callcontext.TenantContext;
-import org.skife.jdbi.v2.IDBI;
-
+import org.joda.time.LocalDate;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.skife.jdbi.v2.IDBI;
public class DefaultRolledUpUsageDao implements RolledUpUsageDao {
@@ -41,17 +37,18 @@ public class DefaultRolledUpUsageDao implements RolledUpUsageDao {
}
@Override
- public void record(final UUID subscriptionId, final String unitType, final DateTime startTime, final DateTime endTime,
- final BigDecimal amount, final InternalCallContext context) {
- final RolledUpUsageModelDao rolledUpUsageModelDao = new RolledUpUsageModelDao(subscriptionId, unitType, startTime,
- endTime, amount
- );
+ public void record(final UUID subscriptionId, final String unitType, final LocalDate date, final Long amount, final InternalCallContext context) {
+ final RolledUpUsageModelDao rolledUpUsageModelDao = new RolledUpUsageModelDao(subscriptionId, unitType, date, amount);
rolledUpUsageSqlDao.create(rolledUpUsageModelDao, context);
}
@Override
- public RolledUpUsageModelDao getUsageForSubscription(UUID subscriptionId, DateTime startTime, DateTime endTime, String unitType, InternalTenantContext context) {
- final BigDecimal amount = rolledUpUsageSqlDao.getUsageForSubscription(subscriptionId, startTime.toDate(), endTime.toDate(), unitType, context);
- return new RolledUpUsageModelDao(subscriptionId, unitType, startTime, endTime, amount != null ? amount : BigDecimal.ZERO);
+ public List<RolledUpUsageModelDao> getUsageForSubscription(final UUID subscriptionId, final LocalDate startDate, final LocalDate endDate, final String unitType, final InternalTenantContext context) {
+ return rolledUpUsageSqlDao.getUsageForSubscription(subscriptionId, startDate.toDate(), endDate.toDate(), unitType, context);
+ }
+
+ @Override
+ public List<RolledUpUsageModelDao> getAllUsageForSubscription(final UUID subscriptionId, final LocalDate startDate, final LocalDate endDate, final InternalTenantContext context) {
+ return rolledUpUsageSqlDao.getAllUsageForSubscription(subscriptionId, startDate.toDate(), endDate.toDate(), context);
}
}
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageDao.java
index 225b3c8..4144aed 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageDao.java
@@ -16,22 +16,19 @@
package org.killbill.billing.usage.dao;
-import java.math.BigDecimal;
import java.util.List;
-import java.util.Set;
import java.util.UUID;
-import org.joda.time.DateTime;
-
+import org.joda.time.LocalDate;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.usage.api.RolledUpUsage;
-import org.killbill.billing.util.callcontext.TenantContext;
public interface RolledUpUsageDao {
- void record(UUID subscriptionId, String unitType, DateTime startTime,
- DateTime endTime, BigDecimal amount, InternalCallContext context);
+ void record(UUID subscriptionId, String unitType, LocalDate date,
+ Long amount, InternalCallContext context);
+
+ List<RolledUpUsageModelDao> getUsageForSubscription(UUID subscriptionId, LocalDate startDate, LocalDate endDate, String unitType, InternalTenantContext context);
- RolledUpUsageModelDao getUsageForSubscription(UUID subscriptionId, DateTime startTime, DateTime endTime, String unitType, InternalTenantContext context);
+ List<RolledUpUsageModelDao> getAllUsageForSubscription(UUID subscriptionId, LocalDate startDate, LocalDate endDate, InternalTenantContext context);
}
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageModelDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageModelDao.java
index acffd5b..b9a5a62 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageModelDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageModelDao.java
@@ -16,34 +16,34 @@
package org.killbill.billing.usage.dao;
-import java.math.BigDecimal;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.util.dao.TableName;
+import org.killbill.billing.util.entity.Entity;
+import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
-public class RolledUpUsageModelDao {
+public class RolledUpUsageModelDao extends EntityModelDaoBase implements EntityModelDao<Entity> {
- private UUID id;
private UUID subscriptionId;
private String unitType;
- private DateTime startTime;
- private DateTime endTime;
- private BigDecimal amount;
+ private LocalDate recordDate;
+ private Long amount;
public RolledUpUsageModelDao() { /* For the DAO mapper */ }
- public RolledUpUsageModelDao(final UUID subscriptionId, final String unitType, final DateTime startTime,
- final DateTime endTime, final BigDecimal amount) {
- this.id = UUID.randomUUID();
+ public RolledUpUsageModelDao(final UUID id, final DateTime createdDate, final DateTime updatedDate, final UUID subscriptionId, final String unitType, final LocalDate recordDate, final Long amount) {
+ super(id, createdDate, updatedDate);
this.subscriptionId = subscriptionId;
this.unitType = unitType;
- this.startTime = startTime;
- this.endTime = endTime;
+ this.recordDate = recordDate;
this.amount = amount;
}
- public UUID getId() {
- return id;
+ public RolledUpUsageModelDao(final UUID subscriptionId, final String unitType, final LocalDate recordDate, final Long amount) {
+ this(UUID.randomUUID(), null, null, subscriptionId, unitType, recordDate, amount);
}
public UUID getSubscriptionId() {
@@ -54,22 +54,18 @@ public class RolledUpUsageModelDao {
return unitType;
}
- public DateTime getStartTime() {
- return startTime;
+ public LocalDate getRecordDate() {
+ return recordDate;
}
- public DateTime getEndTime() {
- return endTime;
+ public void setRecordDate(final LocalDate recordDate) {
+ this.recordDate = recordDate;
}
- public BigDecimal getAmount() {
+ public Long getAmount() {
return amount;
}
- public void setId(final UUID id) {
- this.id = id;
- }
-
public void setSubscriptionId(final UUID subscriptionId) {
this.subscriptionId = subscriptionId;
}
@@ -78,15 +74,7 @@ public class RolledUpUsageModelDao {
this.unitType = unitType;
}
- public void setStartTime(final DateTime startTime) {
- this.startTime = startTime;
- }
-
- public void setEndTime(final DateTime endTime) {
- this.endTime = endTime;
- }
-
- public void setAmount(final BigDecimal amount) {
+ public void setAmount(final Long amount) {
this.amount = amount;
}
@@ -97,8 +85,7 @@ public class RolledUpUsageModelDao {
sb.append("{id=").append(id);
sb.append(", subscriptionId=").append(subscriptionId);
sb.append(", unitType='").append(unitType).append('\'');
- sb.append(", startTime=").append(startTime);
- sb.append(", endTime=").append(endTime);
+ sb.append(", recordDate=").append(recordDate);
sb.append(", amount=").append(amount);
sb.append('}');
return sb.toString();
@@ -118,7 +105,7 @@ public class RolledUpUsageModelDao {
if (amount != null ? !amount.equals(that.amount) : that.amount != null) {
return false;
}
- if (endTime != null ? !endTime.equals(that.endTime) : that.endTime != null) {
+ if (recordDate != null ? !recordDate.equals(that.recordDate) : that.recordDate != null) {
return false;
}
if (id != null ? !id.equals(that.id) : that.id != null) {
@@ -127,9 +114,6 @@ public class RolledUpUsageModelDao {
if (unitType != null ? !unitType.equals(that.unitType) : that.unitType != null) {
return false;
}
- if (startTime != null ? !startTime.equals(that.startTime) : that.startTime != null) {
- return false;
- }
if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
return false;
}
@@ -142,9 +126,18 @@ public class RolledUpUsageModelDao {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
result = 31 * result + (unitType != null ? unitType.hashCode() : 0);
- result = 31 * result + (startTime != null ? startTime.hashCode() : 0);
- result = 31 * result + (endTime != null ? endTime.hashCode() : 0);
+ result = 31 * result + (recordDate != null ? recordDate.hashCode() : 0);
result = 31 * result + (amount != null ? amount.hashCode() : 0);
return result;
}
+
+ @Override
+ public TableName getTableName() {
+ return TableName.ROLLED_UP_USAGE;
+ }
+
+ @Override
+ public TableName getHistoryTableName() {
+ return null;
+ }
}
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
index 4bfdb8a..b237eb2 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
@@ -16,31 +16,38 @@
package org.killbill.billing.usage.dao;
-import java.math.BigDecimal;
import java.util.Date;
+import java.util.List;
import java.util.UUID;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.callcontext.InternalTenantContextBinder;
+import org.killbill.billing.util.entity.Entity;
+import org.killbill.billing.util.entity.dao.EntitySqlDao;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
-import org.killbill.billing.callcontext.InternalCallContext;
-import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.callcontext.InternalTenantContextBinder;
-
-@UseStringTemplate3StatementLocator()
-public interface RolledUpUsageSqlDao {
+@EntitySqlDaoStringTemplate()
+public interface RolledUpUsageSqlDao extends EntitySqlDao<RolledUpUsageModelDao, Entity> {
@SqlUpdate
public void create(@BindBean RolledUpUsageModelDao rolledUpUsage,
@InternalTenantContextBinder final InternalCallContext context);
@SqlQuery
- public BigDecimal getUsageForSubscription(@Bind("subscriptionId") final UUID subscriptionId,
- @Bind("startTime") final Date startTime,
- @Bind("endTime") final Date endTime,
- @Bind("unitType") final String unitType,
- @InternalTenantContextBinder final InternalTenantContext context);
+ public List<RolledUpUsageModelDao> getUsageForSubscription(@Bind("subscriptionId") final UUID subscriptionId,
+ @Bind("startDate") final Date startDate,
+ @Bind("endDate") final Date endDate,
+ @Bind("unitType") final String unitType,
+ @InternalTenantContextBinder final InternalTenantContext context);
+
+ @SqlQuery
+ public List<RolledUpUsageModelDao> getAllUsageForSubscription(@Bind("subscriptionId") final UUID subscriptionId,
+ @Bind("startDate") final Date startDate,
+ @Bind("endDate") final Date endDate,
+ @InternalTenantContextBinder final InternalTenantContext context);
}
diff --git a/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg b/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg
index 431d976..ea40a86 100644
--- a/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg
+++ b/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg
@@ -1,54 +1,46 @@
-group RolledUpUsageSqlDao: EntitySqlDao;
+group RolledUpUsageSqlDao : EntitySqlDao;
tableName() ::= "rolled_up_usage"
+
tableFields(prefix) ::= <<
- <prefix>id
-, <prefix>subscription_id
+ <prefix>subscription_id
, <prefix>unit_type
-, <prefix>start_time
-, <prefix>end_time
+, <prefix>record_date
, <prefix>amount
, <prefix>created_by
, <prefix>created_date
-, <prefix>account_record_id
-, <prefix>tenant_record_id
>>
tableValues() ::= <<
- :id
-, :subscriptionId
+ :subscriptionId
, :unitType
-, :startTime
-, :endTime
+, :recordDate
, :amount
, :userName
, :createdDate
-, :accountRecordId
-, :tenantRecordId
>>
-CHECK_TENANT(prefix) ::= "<prefix>tenant_record_id = :tenantRecordId"
-AND_CHECK_TENANT(prefix) ::= "and <CHECK_TENANT(prefix)>"
-create() ::= <<
-insert into <tableName()> (
- <tableFields()>
-)
-values (
- <tableValues()>
-)
+getUsageForSubscription() ::= <<
+select
+ <allTableFields()>
+from <tableName()>
+where subscription_id = :subscriptionId
+and record_date >= :startDate
+and record_date \< :endDate
+and unit_type = :unitType
+<AND_CHECK_TENANT()>
;
>>
-getUsageForSubscription() ::= <<
+getAllUsageForSubscription() ::= <<
select
- sum(amount)
-from <tableName()> t
+ <allTableFields()>
+from <tableName()>
where subscription_id = :subscriptionId
-and start_time >= :startTime
-and end_time \<= :endTime
-and unit_type = :unitType
+and record_date >= :startDate
+and record_date \< :endDate
<AND_CHECK_TENANT()>
;
>>
diff --git a/usage/src/main/resources/org/killbill/billing/usage/ddl.sql b/usage/src/main/resources/org/killbill/billing/usage/ddl.sql
index 2d8edc9..50b3f2d 100644
--- a/usage/src/main/resources/org/killbill/billing/usage/ddl.sql
+++ b/usage/src/main/resources/org/killbill/billing/usage/ddl.sql
@@ -6,9 +6,8 @@ CREATE TABLE rolled_up_usage (
id char(36) NOT NULL,
subscription_id char(36),
unit_type varchar(50),
- start_time datetime NOT NULL,
- end_time datetime,
- amount decimal(15,9) NOT NULL,
+ record_date date NOT NULL,
+ amount bigint NOT NULL,
created_by varchar(50) NOT NULL,
created_date datetime NOT NULL,
account_record_id int(11) unsigned default null,
diff --git a/usage/src/test/java/org/killbill/billing/usage/api/user/MockUsageUserApi.java b/usage/src/test/java/org/killbill/billing/usage/api/user/MockUsageUserApi.java
index d3dd47a..4ec7e95 100644
--- a/usage/src/test/java/org/killbill/billing/usage/api/user/MockUsageUserApi.java
+++ b/usage/src/test/java/org/killbill/billing/usage/api/user/MockUsageUserApi.java
@@ -16,38 +16,38 @@
package org.killbill.billing.usage.api.user;
-import java.math.BigDecimal;
import java.util.List;
import java.util.Set;
import java.util.UUID;
-import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
import org.killbill.billing.usage.api.RolledUpUsage;
+import org.killbill.billing.usage.api.SubscriptionUsageRecord;
import org.killbill.billing.usage.api.UsageUserApi;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.TenantContext;
-
public class MockUsageUserApi implements UsageUserApi {
private List<RolledUpUsage> result;
- @Override
- public void recordRolledUpUsage(final UUID uuid, final String s, final DateTime dateTime, final DateTime dateTime2, final BigDecimal bigDecimal, final CallContext callContext) {
- throw new UnsupportedOperationException();
+ public void setAllUsageForSubscription(final List<RolledUpUsage> result) {
+ this.result = result;
}
@Override
- public RolledUpUsage getUsageForSubscription(final UUID uuid, final String s, final DateTime dateTime, final DateTime dateTime2, final TenantContext tenantContext) {
- throw new UnsupportedOperationException();
+ public void recordRolledUpUsage(final SubscriptionUsageRecord subscriptionUsageRecord, final CallContext callContext) {
+
}
@Override
- public List<RolledUpUsage> getAllUsageForSubscription(final UUID uuid, final Set<String> strings, final List<DateTime> dateTimes, final TenantContext tenantContext) {
- return result;
+ public RolledUpUsage getUsageForSubscription(final UUID uuid, final String s, final LocalDate localDate, final LocalDate localDate2, final TenantContext tenantContext) {
+ return null;
}
- public void setAllUsageForSubscription(final List<RolledUpUsage> result) {
- this.result = result;
+ @Override
+ public List<RolledUpUsage> getAllUsageForSubscription(final UUID uuid, final List<LocalDate> localDates, final TenantContext tenantContext) {
+ return null;
}
+
}
diff --git a/usage/src/test/java/org/killbill/billing/usage/dao/TestDefaultRolledUpUsageDao.java b/usage/src/test/java/org/killbill/billing/usage/dao/TestDefaultRolledUpUsageDao.java
index fad80b4..7e9bca4 100644
--- a/usage/src/test/java/org/killbill/billing/usage/dao/TestDefaultRolledUpUsageDao.java
+++ b/usage/src/test/java/org/killbill/billing/usage/dao/TestDefaultRolledUpUsageDao.java
@@ -16,11 +16,10 @@
package org.killbill.billing.usage.dao;
-import java.math.BigDecimal;
+import java.util.List;
import java.util.UUID;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
import org.killbill.billing.usage.UsageTestSuiteWithEmbeddedDB;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -37,39 +36,72 @@ public class TestDefaultRolledUpUsageDao extends UsageTestSuiteWithEmbeddedDB {
public void testSimple() {
final UUID subscriptionId = UUID.randomUUID();
final String unitType = "foo";
- final DateTime startDate = new DateTime(2013, 1, 1, 0, 0, DateTimeZone.UTC);
- final DateTime endDate = new DateTime(2013, 2, 1, 0, 0, DateTimeZone.UTC);
- final BigDecimal amount1 = BigDecimal.TEN;
- final BigDecimal amount2 = BigDecimal.TEN;
-
- rolledUpUsageDao.record(subscriptionId, unitType, startDate, endDate, amount1, internalCallContext);
- rolledUpUsageDao.record(subscriptionId, unitType, startDate, endDate, amount2, internalCallContext);
-
- final RolledUpUsageModelDao result = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startDate, endDate, unitType, internalCallContext);
- assertEquals(result.getSubscriptionId(), subscriptionId);
- assertEquals(result.getStartTime().compareTo(startDate), 0);
- assertEquals(result.getEndTime().compareTo(endDate), 0);
- assertEquals(result.getUnitType(), unitType);
- assertEquals(result.getSubscriptionId(), subscriptionId);
- assertEquals(result.getSubscriptionId(), subscriptionId);
- assertEquals(result.getAmount().compareTo(amount1.add(amount2)), 0);
+ final LocalDate startDate = new LocalDate(2013, 1, 1);
+ final LocalDate endDate = new LocalDate(2013, 2, 1);
+ final Long amount1 = 10L;
+ final Long amount2 = 5L;
+
+ rolledUpUsageDao.record(subscriptionId, unitType, startDate, amount1, internalCallContext);
+ rolledUpUsageDao.record(subscriptionId, unitType, endDate.minusDays(1), amount2, internalCallContext);
+
+ final List<RolledUpUsageModelDao> result = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startDate, endDate, unitType, internalCallContext);
+ assertEquals(result.size(), 2);
+ assertEquals(result.get(0).getSubscriptionId(), subscriptionId);
+ assertEquals(result.get(0).getRecordDate().compareTo(startDate), 0);
+ assertEquals(result.get(0).getUnitType(), unitType);
+ assertEquals(result.get(0).getAmount().compareTo(amount1), 0);
+ assertEquals(result.get(1).getSubscriptionId(), subscriptionId);
+ assertEquals(result.get(1).getRecordDate().compareTo(endDate.minusDays(1)), 0);
+ assertEquals(result.get(1).getUnitType(), unitType);
+ assertEquals(result.get(1).getAmount().compareTo(amount2), 0);
+ }
+
+
+ @Test(groups = "slow")
+ public void testMultipleUnits() {
+ final UUID subscriptionId = UUID.randomUUID();
+ final String unitType1 = "foo";
+ final String unitType2 = "bar";
+ final LocalDate startDate = new LocalDate(2013, 1, 1);
+ final LocalDate endDate = new LocalDate(2013, 2, 1);
+ final Long amount1 = 10L;
+ final Long amount2 = 5L;
+ final Long amount3 = 13L;
+
+ rolledUpUsageDao.record(subscriptionId, unitType1, startDate, amount1, internalCallContext);
+ rolledUpUsageDao.record(subscriptionId, unitType1, startDate.plusDays(1), amount2, internalCallContext);
+
+ rolledUpUsageDao.record(subscriptionId, unitType2, endDate.minusDays(1), amount3, internalCallContext);
+
+ final List<RolledUpUsageModelDao> result = rolledUpUsageDao.getAllUsageForSubscription(subscriptionId, startDate, endDate, internalCallContext);
+ assertEquals(result.size(), 3);
+ assertEquals(result.get(0).getSubscriptionId(), subscriptionId);
+ assertEquals(result.get(0).getRecordDate().compareTo(startDate), 0);
+ assertEquals(result.get(0).getUnitType(), unitType1);
+ assertEquals(result.get(0).getAmount().compareTo(amount1), 0);
+ assertEquals(result.get(1).getSubscriptionId(), subscriptionId);
+ assertEquals(result.get(1).getRecordDate().compareTo(startDate.plusDays(1)), 0);
+ assertEquals(result.get(1).getUnitType(), unitType1);
+ assertEquals(result.get(1).getAmount().compareTo(amount2), 0);
+ assertEquals(result.get(2).getSubscriptionId(), subscriptionId);
+ assertEquals(result.get(2).getRecordDate().compareTo(endDate.minusDays(1)), 0);
+ assertEquals(result.get(2).getUnitType(), unitType2);
+ assertEquals(result.get(2).getAmount().compareTo(amount3), 0);
}
+
+
@Test(groups = "slow")
public void testNoEntries() {
final UUID subscriptionId = UUID.randomUUID();
final String unitType = "foo";
- final DateTime startDate = new DateTime(2013, 1, 1, 0, 0, DateTimeZone.UTC);
- final DateTime endDate = new DateTime(2013, 2, 1, 0, 0, DateTimeZone.UTC);
-
- final RolledUpUsageModelDao result = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startDate, endDate, unitType, internalCallContext);
- assertEquals(result.getSubscriptionId(), subscriptionId);
- assertEquals(result.getStartTime().compareTo(startDate), 0);
- assertEquals(result.getEndTime().compareTo(endDate), 0);
- assertEquals(result.getUnitType(), unitType);
- assertEquals(result.getSubscriptionId(), subscriptionId);
- assertEquals(result.getSubscriptionId(), subscriptionId);
- assertEquals(result.getAmount().compareTo(BigDecimal.ZERO), 0);
+ final LocalDate startDate = new LocalDate(2013, 1, 1);
+ final LocalDate endDate = new LocalDate(2013, 2, 1);
+
+ rolledUpUsageDao.record(subscriptionId, unitType, endDate, 9L, internalCallContext);
+
+ final List<RolledUpUsageModelDao> result = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startDate, endDate, unitType, internalCallContext);
+ assertEquals(result.size(), 0);
}
}
util/pom.xml 2(+1 -1)
diff --git a/util/pom.xml b/util/pom.xml
index 71477c8..183555f 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -12,7 +12,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11.10-SNAPSHOT</version>
+ <version>0.11.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-util</artifactId>
diff --git a/util/src/main/java/org/killbill/billing/util/config/InvoiceConfig.java b/util/src/main/java/org/killbill/billing/util/config/InvoiceConfig.java
index cd71a12..c32ec52 100644
--- a/util/src/main/java/org/killbill/billing/util/config/InvoiceConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/InvoiceConfig.java
@@ -32,4 +32,9 @@ public interface InvoiceConfig extends KillbillConfig {
@Description("Whether to send email notifications on invoice creation (for configured accounts)")
public boolean isEmailNotificationsEnabled();
+ @Config("org.killbill.invoice.usage.insert.zero.amount")
+ @Default("true")
+ @Description("Whether to insert usage items with a zero amount")
+ public boolean isInsertZeroUsageItems();
+
}
diff --git a/util/src/main/java/org/killbill/billing/util/dao/TableName.java b/util/src/main/java/org/killbill/billing/util/dao/TableName.java
index bb75a51..54edeb3 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/TableName.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/TableName.java
@@ -52,7 +52,8 @@ public enum TableName {
TAG_HISTORY("tag_history"),
TENANT("tenants", ObjectType.TENANT),
TENANT_KVS("tenant_kvs", ObjectType.TENANT_KVS),
- TAG("tags", ObjectType.TAG, TAG_HISTORY);
+ TAG("tags", ObjectType.TAG, TAG_HISTORY),
+ ROLLED_UP_USAGE("rolled_up_usage");
private final String tableName;
private final ObjectType objectType;
diff --git a/util/src/main/resources/org/killbill/billing/util/ddl.sql b/util/src/main/resources/org/killbill/billing/util/ddl.sql
index 2704dc9..db53c9c 100644
--- a/util/src/main/resources/org/killbill/billing/util/ddl.sql
+++ b/util/src/main/resources/org/killbill/billing/util/ddl.sql
@@ -174,7 +174,7 @@ CREATE INDEX notifications_tenant_account_record_id ON notifications(search_key2
DROP TABLE IF EXISTS notifications_history;
CREATE TABLE notifications_history (
- record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ record_id int(11) unsigned NOT NULL,
class_name varchar(256) NOT NULL,
event_json varchar(2048) NOT NULL,
user_token char(36),
@@ -213,7 +213,7 @@ CREATE INDEX bus_events_tenant_account_record_id ON bus_events(search_key2, sear
DROP TABLE IF EXISTS bus_events_history;
CREATE TABLE bus_events_history (
- record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ record_id int(11) unsigned NOT NULL,
class_name varchar(128) NOT NULL,
event_json varchar(2048) NOT NULL,
user_token char(36),