Details
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 092d2b5..9aa8eb1 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
@@ -41,6 +41,7 @@ import org.killbill.billing.mock.MockAccountBuilder;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.usage.api.SubscriptionUsageRecord;
import org.killbill.billing.usage.api.UnitUsageRecord;
+import org.killbill.billing.usage.api.UsageApiException;
import org.killbill.billing.usage.api.UsageRecord;
import org.killbill.billing.util.callcontext.CallContext;
import org.skife.jdbi.v2.Handle;
@@ -245,12 +246,12 @@ public class TestConsumableInArrear extends TestIntegrationBase {
Assert.assertEquals(countNotifications.intValue(), 4);
}
- private void setUsage(final UUID subscriptionId, final String unitType, final LocalDate startDate, final Long amount, final CallContext context) {
+ private void setUsage(final UUID subscriptionId, final String unitType, final LocalDate startDate, final Long amount, final CallContext context) throws UsageApiException {
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);
+ final SubscriptionUsageRecord record = new SubscriptionUsageRecord(subscriptionId, UUID.randomUUID().toString(), unitUsageRecords);
usageUserApi.recordRolledUpUsage(record, context);
}
}
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
index 9e961a6..9a38ad5 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionUsageRecordJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionUsageRecordJson.java
@@ -28,6 +28,7 @@ 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.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.wordnik.swagger.annotations.ApiModelProperty;
@@ -38,11 +39,15 @@ public class SubscriptionUsageRecordJson {
private final String subscriptionId;
@ApiModelProperty(required = true)
private final List<UnitUsageRecordJson> unitUsageRecords;
+ @ApiModelProperty(required = false)
+ private final String trackingId;
@JsonCreator
public SubscriptionUsageRecordJson(@JsonProperty("subscriptionId") final String subscriptionId,
+ @JsonProperty("trackingId") final String trackingId,
@JsonProperty("unitUsageRecords") final List<UnitUsageRecordJson> unitUsageRecords) {
this.subscriptionId = subscriptionId;
+ this.trackingId = trackingId;
this.unitUsageRecords = unitUsageRecords;
}
@@ -54,6 +59,10 @@ public class SubscriptionUsageRecordJson {
return unitUsageRecords;
}
+ public String getTrackingId() {
+ return trackingId;
+ }
+
public static class UnitUsageRecordJson {
private final String unitType;
@@ -117,7 +126,7 @@ public class SubscriptionUsageRecordJson {
return input.toUnitUsageRecord();
}
}));
- final SubscriptionUsageRecord result = new SubscriptionUsageRecord(UUID.fromString(subscriptionId), tmp);
+ final SubscriptionUsageRecord result = new SubscriptionUsageRecord(UUID.fromString(subscriptionId), trackingId, tmp);
return result;
}
-}
+}
\ No newline at end of file
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 b55db89..5f6c8fd 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
@@ -47,6 +47,7 @@ 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.UsageApiException;
import org.killbill.billing.usage.api.UsageUserApi;
import org.killbill.billing.util.api.AuditUserApi;
import org.killbill.billing.util.api.CustomFieldUserApi;
@@ -101,7 +102,9 @@ public class UsageResource extends JaxRsResourceBase {
@HeaderParam(HDR_REASON) final String reason,
@HeaderParam(HDR_COMMENT) final String comment,
@javax.ws.rs.core.Context final HttpServletRequest request,
- @javax.ws.rs.core.Context final UriInfo uriInfo) throws EntitlementApiException, AccountApiException {
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws EntitlementApiException,
+ AccountApiException,
+ UsageApiException {
verifyNonNullOrEmpty(json, "SubscriptionUsageRecordJson body should be specified");
verifyNonNullOrEmpty(json.getSubscriptionId(), "SubscriptionUsageRecordJson subscriptionId needs to be set",
json.getUnitUsageRecords(), "SubscriptionUsageRecordJson unitUsageRecords needs to be set");
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestSubscriptionUsageRecordJson.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestSubscriptionUsageRecordJson.java
new file mode 100644
index 0000000..95c232b
--- /dev/null
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestSubscriptionUsageRecordJson.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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 org.joda.time.LocalDate;
+import org.killbill.billing.jaxrs.JaxrsTestSuiteNoDB;
+import org.killbill.billing.jaxrs.json.SubscriptionUsageRecordJson.UnitUsageRecordJson;
+import org.killbill.billing.jaxrs.json.SubscriptionUsageRecordJson.UsageRecordJson;
+import org.killbill.billing.usage.api.SubscriptionUsageRecord;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestSubscriptionUsageRecordJson extends JaxrsTestSuiteNoDB {
+
+ @Test(groups = "fast")
+ public void testJson() throws Exception {
+ final LocalDate localDate = new LocalDate();
+ final String subscriptionId = UUID.randomUUID().toString();
+ final String trackingId = UUID.randomUUID().toString();
+ final List<UnitUsageRecordJson> unitUsageRecords = new ArrayList<UnitUsageRecordJson>();
+ final List<UsageRecordJson> usageRecords = new ArrayList<UsageRecordJson>();
+ final UsageRecordJson usageRecordJson = new UsageRecordJson(localDate, 5L);
+ usageRecords.add(usageRecordJson);
+ final UnitUsageRecordJson unitUsageRecordJson = new UnitUsageRecordJson("foo", usageRecords);
+ unitUsageRecords.add(unitUsageRecordJson);
+
+ final SubscriptionUsageRecordJson subscriptionUsageRecordJson = new SubscriptionUsageRecordJson(subscriptionId, trackingId, unitUsageRecords);
+ Assert.assertEquals(subscriptionUsageRecordJson.getSubscriptionId(), subscriptionId);
+ Assert.assertEquals(subscriptionUsageRecordJson.getTrackingId(), trackingId);
+ Assert.assertEquals(subscriptionUsageRecordJson.getUnitUsageRecords().size(), 1);
+ Assert.assertEquals(subscriptionUsageRecordJson.getUnitUsageRecords().get(0).getUnitType(), "foo");
+ Assert.assertEquals(subscriptionUsageRecordJson.getUnitUsageRecords().get(0).getUsageRecords().size(), 1);
+ Assert.assertEquals(subscriptionUsageRecordJson.getUnitUsageRecords().get(0).getUsageRecords().get(0).getAmount(), new Long(5L));
+ Assert.assertEquals(subscriptionUsageRecordJson.getUnitUsageRecords().get(0).getUsageRecords().get(0).getRecordDate(), localDate);
+
+ final SubscriptionUsageRecord subscriptionUsageRecord = subscriptionUsageRecordJson.toSubscriptionUsageRecord();
+ Assert.assertEquals(subscriptionUsageRecord.getSubscriptionId().toString(), subscriptionId);
+ Assert.assertEquals(subscriptionUsageRecord.getTrackingId(), trackingId);
+ Assert.assertEquals(subscriptionUsageRecord.getUnitUsageRecord().size(), 1);
+ Assert.assertEquals(subscriptionUsageRecord.getUnitUsageRecord().get(0).getUnitType(), "foo");
+ Assert.assertEquals(subscriptionUsageRecord.getUnitUsageRecord().get(0).getDailyAmount().size(), 1);
+ Assert.assertEquals(subscriptionUsageRecord.getUnitUsageRecord().get(0).getDailyAmount().get(0).getAmount(), new Long(5L));
+ Assert.assertEquals(subscriptionUsageRecord.getUnitUsageRecord().get(0).getDailyAmount().get(0).getDate(), localDate);
+ }
+}
\ No newline at end of file
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestUsage.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestUsage.java
index 2e9dd6a..dfc49e3 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestUsage.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestUsage.java
@@ -19,9 +19,11 @@ package org.killbill.billing.jaxrs;
import java.util.UUID;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.model.Account;
import org.killbill.billing.client.model.Bundle;
import org.killbill.billing.client.model.RolledUpUsage;
@@ -115,4 +117,62 @@ public class TestUsage extends TestJaxrsBase {
Assert.assertEquals(retrievedUsage4.getRolledUpUnits().get(0).getUnitType(), "bullets");
Assert.assertEquals((long) retrievedUsage4.getRolledUpUnits().get(0).getAmount(), 5);
}
+
+ @Test(groups = "slow", description = "Test tracking ID already exists")
+ public void testRecordUsageTrackingIdExists() throws Exception {
+
+ final Account accountJson = createAccountWithDefaultPaymentMethod();
+
+ final Subscription base = new Subscription();
+ base.setAccountId(accountJson.getAccountId());
+ base.setProductName("Pistol");
+ base.setProductCategory(ProductCategory.BASE);
+ base.setBillingPeriod(BillingPeriod.MONTHLY);
+ base.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ final Subscription addOn = new Subscription();
+ addOn.setAccountId(accountJson.getAccountId());
+ addOn.setProductName("Bullets");
+ addOn.setProductCategory(ProductCategory.ADD_ON);
+ addOn.setBillingPeriod(BillingPeriod.NO_BILLING_PERIOD);
+ addOn.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ final Bundle bundle = killBillClient.createSubscriptionWithAddOns(ImmutableList.<Subscription>of(base, addOn),
+ null,
+ DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC,
+ createdBy,
+ reason,
+ comment);
+ final UUID addOnSubscriptionId = Iterables.<Subscription>find(bundle.getSubscriptions(),
+ new Predicate<Subscription>() {
+ @Override
+ public boolean apply(final Subscription input) {
+ return ProductCategory.ADD_ON.equals(input.getProductCategory());
+ }
+ }).getSubscriptionId();
+
+ final UsageRecord usageRecord1 = new UsageRecord();
+ usageRecord1.setAmount(10L);
+ usageRecord1.setRecordDate(clock.getUTCToday().minusDays(1));
+
+ final UnitUsageRecord unitUsageRecord = new UnitUsageRecord();
+ unitUsageRecord.setUnitType("bullets");
+ unitUsageRecord.setUsageRecords(ImmutableList.<UsageRecord>of(usageRecord1));
+
+ final SubscriptionUsageRecord usage = new SubscriptionUsageRecord();
+ usage.setSubscriptionId(addOnSubscriptionId);
+ usage.setTrackingId(UUID.randomUUID().toString());
+ usage.setUnitUsageRecords(ImmutableList.<UnitUsageRecord>of(unitUsageRecord));
+
+ killBillClient.createSubscriptionUsageRecord(usage, createdBy, reason, comment);
+
+ try {
+ killBillClient.createSubscriptionUsageRecord(usage, createdBy, reason, comment);
+ Assert.fail();
+ }
+ catch (final KillBillClientException e) {
+ Assert.assertEquals(e.getBillingException().getCode(), (Integer) ErrorCode.USAGE_RECORD_TRACKING_ID_ALREADY_EXISTS.getCode());
+ }
+
+ }
}
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 45b0146..74baa77 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
@@ -25,6 +25,7 @@ import java.util.UUID;
import javax.inject.Inject;
import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
@@ -32,6 +33,7 @@ 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.UsageApiException;
import org.killbill.billing.usage.api.UsageRecord;
import org.killbill.billing.usage.api.UsageUserApi;
import org.killbill.billing.usage.dao.RolledUpUsageDao;
@@ -40,6 +42,8 @@ import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
+import com.google.common.base.Strings;
+
public class DefaultUsageUserApi implements UsageUserApi {
private final RolledUpUsageDao rolledUpUsageDao;
@@ -53,13 +57,21 @@ public class DefaultUsageUserApi implements UsageUserApi {
}
@Override
- public void recordRolledUpUsage(final SubscriptionUsageRecord record, final CallContext callContext) {
+ public void recordRolledUpUsage(final SubscriptionUsageRecord record, final CallContext callContext) throws UsageApiException {
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(record.getSubscriptionId(), ObjectType.SUBSCRIPTION, callContext);
- for (UnitUsageRecord unitUsageRecord : record.getUnitUsageRecord()) {
- for (UsageRecord usageRecord : unitUsageRecord.getDailyAmount()) {
- rolledUpUsageDao.record(record.getSubscriptionId(), unitUsageRecord.getUnitType(), usageRecord.getDate(), usageRecord.getAmount(), internalCallContext);
+
+ // check if we have (at least) one row with the supplied tracking id
+ if(!Strings.isNullOrEmpty(record.getTrackingId()) && recordsWithTrackingIdExist(record, internalCallContext)){
+ throw new UsageApiException(ErrorCode.USAGE_RECORD_TRACKING_ID_ALREADY_EXISTS, record.getTrackingId());
+ }
+
+ final List<RolledUpUsageModelDao> usages = new ArrayList<RolledUpUsageModelDao>();
+ for (final UnitUsageRecord unitUsageRecord : record.getUnitUsageRecord()) {
+ for (final UsageRecord usageRecord : unitUsageRecord.getDailyAmount()) {
+ usages.add(new RolledUpUsageModelDao(record.getSubscriptionId(), unitUsageRecord.getUnitType(), usageRecord.getDate(), usageRecord.getAmount(), record.getTrackingId()));
}
}
+ rolledUpUsageDao.record(usages, internalCallContext);
}
@Override
@@ -98,4 +110,8 @@ public class DefaultUsageUserApi implements UsageUserApi {
}
return result;
}
+
+ private boolean recordsWithTrackingIdExist(SubscriptionUsageRecord record, InternalCallContext context){
+ return rolledUpUsageDao.recordsWithTrackingIdExist(record.getSubscriptionId(), record.getTrackingId(), context);
+ }
}
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 3ab2973..4d3b4c5 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
@@ -16,7 +16,6 @@
package org.killbill.billing.usage.dao;
-import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
@@ -37,9 +36,13 @@ public class DefaultRolledUpUsageDao implements RolledUpUsageDao {
}
@Override
- 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);
+ public void record(final Iterable<RolledUpUsageModelDao> usages, final InternalCallContext context){
+ rolledUpUsageSqlDao.create(usages, context);
+ }
+
+ @Override
+ public Boolean recordsWithTrackingIdExist(final UUID subscriptionId, final String trackingId, final InternalTenantContext context){
+ return rolledUpUsageSqlDao.recordsWithTrackingIdExist(subscriptionId, trackingId, context) != null ;
}
@Override
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 e458635..9f6be8e 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
@@ -25,13 +25,13 @@ import org.killbill.billing.callcontext.InternalTenantContext;
public interface RolledUpUsageDao {
- void record(UUID subscriptionId, String unitType, LocalDate date,
- Long amount, InternalCallContext context);
+ void record(Iterable<RolledUpUsageModelDao> usages, InternalCallContext context);
+
+ Boolean recordsWithTrackingIdExist(UUID subscriptionId, String trackingId, InternalTenantContext context);
List<RolledUpUsageModelDao> getUsageForSubscription(UUID subscriptionId, LocalDate startDate, LocalDate endDate, String unitType, InternalTenantContext context);
List<RolledUpUsageModelDao> getAllUsageForSubscription(UUID subscriptionId, LocalDate startDate, LocalDate endDate, InternalTenantContext context);
-
List<RolledUpUsageModelDao> getRawUsageForAccount(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 411765a..5cd4eb1 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
@@ -26,25 +26,35 @@ import org.killbill.billing.util.entity.Entity;
import org.killbill.billing.util.entity.dao.EntityModelDao;
import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
+import com.google.common.base.Strings;
+
public class RolledUpUsageModelDao extends EntityModelDaoBase implements EntityModelDao<Entity> {
private UUID subscriptionId;
private String unitType;
private LocalDate recordDate;
private Long amount;
+ private String trackingId;
public RolledUpUsageModelDao() { /* For the DAO mapper */ }
- public RolledUpUsageModelDao(final UUID id, final DateTime createdDate, final DateTime updatedDate, final UUID subscriptionId, final String unitType, final LocalDate recordDate, final Long amount) {
+ public RolledUpUsageModelDao(final UUID id, final DateTime createdDate, final DateTime updatedDate, final UUID subscriptionId, final String unitType, final LocalDate recordDate, final Long amount, final String trackingId) {
super(id, createdDate, updatedDate);
this.subscriptionId = subscriptionId;
this.unitType = unitType;
this.recordDate = recordDate;
this.amount = amount;
+
+ if(Strings.isNullOrEmpty(trackingId)){
+ this.trackingId = UUIDs.randomUUID().toString();
+ }
+ else {
+ this.trackingId = trackingId;
+ }
}
- public RolledUpUsageModelDao(final UUID subscriptionId, final String unitType, final LocalDate recordDate, final Long amount) {
- this(UUIDs.randomUUID(), null, null, subscriptionId, unitType, recordDate, amount);
+ public RolledUpUsageModelDao(final UUID subscriptionId, final String unitType, final LocalDate recordDate, final Long amount, final String trackingId) {
+ this(UUIDs.randomUUID(), null, null, subscriptionId, unitType, recordDate, amount, trackingId);
}
public UUID getSubscriptionId() {
@@ -79,6 +89,14 @@ public class RolledUpUsageModelDao extends EntityModelDaoBase implements EntityM
this.amount = amount;
}
+ public String getTrackingId() {
+ return trackingId;
+ }
+
+ public void setTrackingId(final String trackingId) {
+ this.trackingId = trackingId;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
@@ -88,6 +106,7 @@ public class RolledUpUsageModelDao extends EntityModelDaoBase implements EntityM
sb.append(", unitType='").append(unitType).append('\'');
sb.append(", recordDate=").append(recordDate);
sb.append(", amount=").append(amount);
+ sb.append(", trackingId=").append(trackingId);
sb.append('}');
return sb.toString();
}
@@ -118,7 +137,9 @@ public class RolledUpUsageModelDao extends EntityModelDaoBase implements EntityM
if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
return false;
}
-
+ if (trackingId != null ? !trackingId.equals(that.trackingId) : that.trackingId != null) {
+ return false;
+ }
return true;
}
@@ -129,6 +150,7 @@ public class RolledUpUsageModelDao extends EntityModelDaoBase implements EntityM
result = 31 * result + (unitType != null ? unitType.hashCode() : 0);
result = 31 * result + (recordDate != null ? recordDate.hashCode() : 0);
result = 31 * result + (amount != null ? amount.hashCode() : 0);
+ result = 31 * result + (trackingId != null ? trackingId.hashCode() : 0);
return result;
}
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 a10a2c0..12f45c0 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
@@ -30,31 +30,37 @@ 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.SqlBatch;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@EntitySqlDaoStringTemplate
public interface RolledUpUsageSqlDao extends EntitySqlDao<RolledUpUsageModelDao, Entity> {
- @SqlUpdate
- public void create(@BindBean RolledUpUsageModelDao rolledUpUsage,
+ @SqlBatch
+ void create(@BindBean Iterable<RolledUpUsageModelDao> usages,
@InternalTenantContextBinder final InternalCallContext context);
@SqlQuery
- public List<RolledUpUsageModelDao> getUsageForSubscription(@Bind("subscriptionId") final UUID subscriptionId,
+ Long recordsWithTrackingIdExist(@Bind("subscriptionId") final UUID subscriptionId,
+ @Bind("trackingId") final String trackingId,
+ @InternalTenantContextBinder final InternalTenantContext context);
+
+ @SqlQuery
+ 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,
+ List<RolledUpUsageModelDao> getAllUsageForSubscription(@Bind("subscriptionId") final UUID subscriptionId,
@Bind("startDate") final Date startDate,
@Bind("endDate") final Date endDate,
@InternalTenantContextBinder final InternalTenantContext context);
@SqlQuery
- public List<RolledUpUsageModelDao> getRawUsageForAccount(@Bind("startDate") final Date startDate,
+ List<RolledUpUsageModelDao> getRawUsageForAccount(@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 94fcb03..b1d739c 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
@@ -8,6 +8,7 @@ tableFields(prefix) ::= <<
, <prefix>unit_type
, <prefix>record_date
, <prefix>amount
+, <prefix>tracking_id
, <prefix>created_by
, <prefix>created_date
>>
@@ -17,10 +18,21 @@ tableValues() ::= <<
, :unitType
, :recordDate
, :amount
+, :trackingId
, :userName
, :createdDate
>>
+recordsWithTrackingIdExist() ::= <<
+select
+ 1
+from <tableName()>
+where subscription_id = :subscriptionId
+and tracking_id = :trackingId
+<AND_CHECK_TENANT()>
+limit 1
+;
+>>
getUsageForSubscription() ::= <<
select
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 44837a9..7c2418e 100644
--- a/usage/src/main/resources/org/killbill/billing/usage/ddl.sql
+++ b/usage/src/main/resources/org/killbill/billing/usage/ddl.sql
@@ -8,6 +8,7 @@ CREATE TABLE rolled_up_usage (
unit_type varchar(255),
record_date date NOT NULL,
amount bigint NOT NULL,
+ tracking_id varchar(128) NOT NULL,
created_by varchar(50) NOT NULL,
created_date datetime NOT NULL,
account_record_id bigint /*! unsigned */ not null,
@@ -18,3 +19,4 @@ CREATE UNIQUE INDEX rolled_up_usage_id ON rolled_up_usage(id);
CREATE INDEX rolled_up_usage_subscription_id ON rolled_up_usage(subscription_id ASC);
CREATE INDEX rolled_up_usage_tenant_account_record_id ON rolled_up_usage(tenant_record_id, account_record_id);
CREATE INDEX rolled_up_usage_account_record_id ON rolled_up_usage(account_record_id);
+CREATE INDEX rolled_up_usage_tracking_id_subscription_id_tenant_record_id ON rolled_up_usage(tracking_id, subscription_id, tenant_record_id);
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 5be6e0b..69a6cde 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
@@ -17,14 +17,18 @@
package org.killbill.billing.usage.dao;
+import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.joda.time.LocalDate;
import org.killbill.billing.usage.UsageTestSuiteWithEmbeddedDB;
+import org.killbill.billing.util.UUIDs;
+import org.skife.jdbi.v2.exceptions.UnableToExecuteStatementException;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
public class TestDefaultRolledUpUsageDao extends UsageTestSuiteWithEmbeddedDB {
@@ -37,8 +41,12 @@ public class TestDefaultRolledUpUsageDao extends UsageTestSuiteWithEmbeddedDB {
final Long amount1 = 10L;
final Long amount2 = 5L;
- rolledUpUsageDao.record(subscriptionId, unitType, startDate, amount1, internalCallContext);
- rolledUpUsageDao.record(subscriptionId, unitType, endDate.minusDays(1), amount2, internalCallContext);
+ RolledUpUsageModelDao usage1 = new RolledUpUsageModelDao(subscriptionId, unitType, startDate, amount1, UUID.randomUUID().toString());
+ RolledUpUsageModelDao usage2 = new RolledUpUsageModelDao(subscriptionId, unitType, endDate.minusDays(1), amount2, UUID.randomUUID().toString());
+ List<RolledUpUsageModelDao> usages = new ArrayList<RolledUpUsageModelDao>();
+ usages.add(usage1);
+ usages.add(usage2);
+ rolledUpUsageDao.record(usages, internalCallContext);
final List<RolledUpUsageModelDao> result = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startDate, endDate, unitType, internalCallContext);
assertEquals(result.size(), 2);
@@ -63,10 +71,14 @@ public class TestDefaultRolledUpUsageDao extends UsageTestSuiteWithEmbeddedDB {
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);
+ RolledUpUsageModelDao usage1 = new RolledUpUsageModelDao(subscriptionId, unitType1, startDate, amount1, UUID.randomUUID().toString());
+ RolledUpUsageModelDao usage2 = new RolledUpUsageModelDao(subscriptionId, unitType1, startDate.plusDays(1), amount2, UUID.randomUUID().toString());
+ RolledUpUsageModelDao usage3 = new RolledUpUsageModelDao(subscriptionId, unitType2, endDate.minusDays(1), amount3, UUID.randomUUID().toString());
+ List<RolledUpUsageModelDao> usages = new ArrayList<RolledUpUsageModelDao>();
+ usages.add(usage1);
+ usages.add(usage2);
+ usages.add(usage3);
+ rolledUpUsageDao.record(usages, internalCallContext);
final List<RolledUpUsageModelDao> result = rolledUpUsageDao.getAllUsageForSubscription(subscriptionId, startDate, endDate, internalCallContext);
assertEquals(result.size(), 3);
@@ -91,9 +103,71 @@ public class TestDefaultRolledUpUsageDao extends UsageTestSuiteWithEmbeddedDB {
final LocalDate startDate = new LocalDate(2013, 1, 1);
final LocalDate endDate = new LocalDate(2013, 2, 1);
- rolledUpUsageDao.record(subscriptionId, unitType, endDate, 9L, internalCallContext);
+ RolledUpUsageModelDao usage1 = new RolledUpUsageModelDao(subscriptionId, unitType, endDate, 9L, UUID.randomUUID().toString());
+ List<RolledUpUsageModelDao> usages = new ArrayList<RolledUpUsageModelDao>();
+ usages.add(usage1);
+ rolledUpUsageDao.record(usages, internalCallContext);
final List<RolledUpUsageModelDao> result = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startDate, endDate, unitType, internalCallContext);
assertEquals(result.size(), 0);
}
-}
+
+ @Test(groups = "slow")
+ public void testDuplicateRecords() {
+ 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;
+
+ RolledUpUsageModelDao usage1 = new RolledUpUsageModelDao(subscriptionId, unitType1, startDate, amount1, UUID.randomUUID().toString());
+ RolledUpUsageModelDao usage2 = new RolledUpUsageModelDao(subscriptionId, unitType1, startDate.plusDays(1), amount2, UUID.randomUUID().toString());
+ RolledUpUsageModelDao usage3 = new RolledUpUsageModelDao(subscriptionId, unitType2, endDate.minusDays(1), amount3, UUID.randomUUID().toString());
+
+ List<RolledUpUsageModelDao> usages = new ArrayList<RolledUpUsageModelDao>();
+ usages.add(usage1);
+ usages.add(usage2);
+ usages.add(usage3);
+ rolledUpUsageDao.record(usages, internalCallContext);
+
+ final List<RolledUpUsageModelDao> result = rolledUpUsageDao.getAllUsageForSubscription(subscriptionId, startDate, endDate, internalCallContext);
+ assertEquals(result.size(), 3);
+
+ try {
+ rolledUpUsageDao.record(usages, internalCallContext);
+ fail("duplicate records accepted");
+ } catch (UnableToExecuteStatementException e) {
+ assertEquals(result.size(), 3);
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testRecordsWithTrackingIdExist() {
+ final UUID subscriptionId = UUIDs.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;
+
+ String trackingId = UUIDs.randomUUID().toString();
+
+ RolledUpUsageModelDao usage1 = new RolledUpUsageModelDao(subscriptionId, unitType1, startDate, amount1, trackingId);
+ RolledUpUsageModelDao usage2 = new RolledUpUsageModelDao(subscriptionId, unitType1, startDate.plusDays(1), amount2, trackingId);
+ RolledUpUsageModelDao usage3 = new RolledUpUsageModelDao(subscriptionId, unitType2, endDate.minusDays(1), amount3, UUID.randomUUID().toString());
+
+ List<RolledUpUsageModelDao> usages = new ArrayList<RolledUpUsageModelDao>();
+ usages.add(usage1);
+ usages.add(usage2);
+ usages.add(usage3);
+ rolledUpUsageDao.record(usages, internalCallContext);
+
+ assertEquals(rolledUpUsageDao.recordsWithTrackingIdExist(subscriptionId, trackingId, internalCallContext),
+ Boolean.TRUE);
+ }
+}
\ No newline at end of file