killbill-aplcache

usage: add rolled-up API Add an API to allow external systems

7/29/2012 3:29:35 PM

Details

diff --git a/api/src/main/java/com/ning/billing/usage/api/UsageUserApi.java b/api/src/main/java/com/ning/billing/usage/api/UsageUserApi.java
index c0cf5e0..eb6935c 100644
--- a/api/src/main/java/com/ning/billing/usage/api/UsageUserApi.java
+++ b/api/src/main/java/com/ning/billing/usage/api/UsageUserApi.java
@@ -22,7 +22,34 @@ import org.joda.time.DateTime;
 
 public interface UsageUserApi {
 
+    /**
+     * Shortcut API to record a usage value of "1" for a given metric.
+     *
+     * @param bundleId   bundle id source
+     * @param metricName metric name for this usage
+     */
     public void incrementUsage(final UUID bundleId, final String metricName);
 
+    /**
+     * Fine grained usage API if the external system doesn't roll its usage data. This is used to record e.g. "X has used
+     * 2 credits from his plan at 2012/02/04 4:12pm".
+     *
+     * @param bundleId   bundle id source
+     * @param metricName metric name for this usage
+     * @param timestamp  timestamp of this usage
+     * @param value      value to record
+     */
     public void recordUsage(final UUID bundleId, final String metricName, final DateTime timestamp, final long value);
+
+    /**
+     * Bulk usage API if the external system rolls-up usage data. This is used to record e.g. "X has used 12 minutes
+     * of his data plan between 2012/02/04 and 2012/02/06".
+     *
+     * @param bundleId   bundle id source
+     * @param metricName metric name for this usage
+     * @param startDate  start date of the usage period
+     * @param endDate    end date of the usage period
+     * @param value      value to record
+     */
+    public void recordRolledUpUsage(final UUID bundleId, final String metricName, final DateTime startDate, final DateTime endDate, final long value);
 }
diff --git a/usage/src/main/java/com/ning/billing/usage/api/user/DefaultUsageUserApi.java b/usage/src/main/java/com/ning/billing/usage/api/user/DefaultUsageUserApi.java
index 804766f..8f1e6a9 100644
--- a/usage/src/main/java/com/ning/billing/usage/api/user/DefaultUsageUserApi.java
+++ b/usage/src/main/java/com/ning/billing/usage/api/user/DefaultUsageUserApi.java
@@ -23,8 +23,8 @@ import javax.inject.Inject;
 import org.joda.time.DateTime;
 
 import com.ning.billing.usage.api.UsageUserApi;
+import com.ning.billing.usage.dao.RolledUpUsageDao;
 import com.ning.billing.usage.timeline.TimelineEventHandler;
-import com.ning.billing.usage.timeline.persistent.TimelineDao;
 import com.ning.billing.util.clock.Clock;
 
 import com.google.common.collect.ImmutableMap;
@@ -33,13 +33,13 @@ public class DefaultUsageUserApi implements UsageUserApi {
 
     private static final String DEFAULT_EVENT_TYPE = "__DefaultUsageUserApi__";
 
-    private final TimelineDao timelineDao;
+    private final RolledUpUsageDao rolledUpUsageDao;
     private final TimelineEventHandler timelineEventHandler;
     private final Clock clock;
 
     @Inject
-    public DefaultUsageUserApi(final TimelineDao timelineDao, final TimelineEventHandler timelineEventHandler, final Clock clock) {
-        this.timelineDao = timelineDao;
+    public DefaultUsageUserApi(final RolledUpUsageDao rolledUpUsageDao, final TimelineEventHandler timelineEventHandler, final Clock clock) {
+        this.rolledUpUsageDao = rolledUpUsageDao;
         this.timelineEventHandler = timelineEventHandler;
         this.clock = clock;
     }
@@ -51,7 +51,18 @@ public class DefaultUsageUserApi implements UsageUserApi {
 
     @Override
     public void recordUsage(final UUID bundleId, final String metricName, final DateTime timestamp, final long value) {
-        final int sourceId = timelineDao.getOrAddSource(bundleId.toString());
-        timelineEventHandler.record(sourceId, DEFAULT_EVENT_TYPE, timestamp, ImmutableMap.<String, Object>of(metricName, value));
+        final String sourceName = getSourceNameFromBundleId(bundleId);
+        timelineEventHandler.record(sourceName, DEFAULT_EVENT_TYPE, timestamp, ImmutableMap.<String, Object>of(metricName, value));
+    }
+
+    @Override
+    public void recordRolledUpUsage(final UUID bundleId, final String metricName, final DateTime startDate, final DateTime endDate, final long value) {
+        final String sourceName = getSourceNameFromBundleId(bundleId);
+        rolledUpUsageDao.record(sourceName, DEFAULT_EVENT_TYPE, metricName, startDate, endDate, value);
+    }
+
+    private String getSourceNameFromBundleId(final UUID bundleId) {
+        // TODO we should do better
+        return bundleId.toString();
     }
 }
diff --git a/usage/src/main/java/com/ning/billing/usage/dao/DefaultRolledUpUsageDao.java b/usage/src/main/java/com/ning/billing/usage/dao/DefaultRolledUpUsageDao.java
new file mode 100644
index 0000000..58e3dce
--- /dev/null
+++ b/usage/src/main/java/com/ning/billing/usage/dao/DefaultRolledUpUsageDao.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.usage.dao;
+
+import javax.inject.Inject;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+
+import com.ning.billing.usage.timeline.persistent.TimelineSqlDao;
+
+public class DefaultRolledUpUsageDao implements RolledUpUsageDao {
+
+    private final RolledUpUsageSqlDao rolledUpUsageSqlDao;
+
+    @Inject
+    public DefaultRolledUpUsageDao(final RolledUpUsageSqlDao rolledUpUsageSqlDao) {
+        this.rolledUpUsageSqlDao = rolledUpUsageSqlDao;
+    }
+
+    @Override
+    public void record(final String source, final String eventType, final String metricName, final DateTime startDate, final DateTime endDate, final long value) {
+        rolledUpUsageSqlDao.inTransaction(new Transaction<Void, RolledUpUsageSqlDao>() {
+            @Override
+            public Void inTransaction(final RolledUpUsageSqlDao transactional, final TransactionStatus status) throws Exception {
+                final TimelineSqlDao timelineSqlDao = transactional.become(TimelineSqlDao.class);
+
+                // Create the source if it doesn't exist
+                Integer sourceId = timelineSqlDao.getSourceId(source);
+                if (sourceId == null) {
+                    timelineSqlDao.addSource(source);
+                    sourceId = timelineSqlDao.getSourceId(source);
+                }
+
+                // Create the category if it doesn't exist
+                Integer categoryId = timelineSqlDao.getEventCategoryId(eventType);
+                if (categoryId == null) {
+                    timelineSqlDao.addEventCategory(eventType);
+                    categoryId = timelineSqlDao.getEventCategoryId(eventType);
+                }
+
+                // Create the metric if it doesn't exist
+                Integer metricId = timelineSqlDao.getMetricId(categoryId, metricName);
+                if (metricId == null) {
+                    timelineSqlDao.addMetric(categoryId, metricName);
+                    metricId = timelineSqlDao.getMetricId(categoryId, metricName);
+                }
+
+                transactional.record(sourceId, metricId, startDate.toDate(), endDate.toDate(), value);
+
+                return null;
+            }
+        });
+    }
+}
diff --git a/usage/src/main/java/com/ning/billing/usage/dao/RolledUpUsageDao.java b/usage/src/main/java/com/ning/billing/usage/dao/RolledUpUsageDao.java
new file mode 100644
index 0000000..a75a882
--- /dev/null
+++ b/usage/src/main/java/com/ning/billing/usage/dao/RolledUpUsageDao.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.usage.dao;
+
+import org.joda.time.DateTime;
+
+/**
+ * Dao to record already rolled-up usage data (rolled-up by the user).
+ * For raw tracking of the data, @see TimelineEventHandler.
+ */
+public interface RolledUpUsageDao {
+
+    public void record(final String sourceName, final String eventType, final String metricName, final DateTime startDate, final DateTime endDate, final long value);
+}
diff --git a/usage/src/main/java/com/ning/billing/usage/dao/RolledUpUsageSqlDao.java b/usage/src/main/java/com/ning/billing/usage/dao/RolledUpUsageSqlDao.java
new file mode 100644
index 0000000..b495535
--- /dev/null
+++ b/usage/src/main/java/com/ning/billing/usage/dao/RolledUpUsageSqlDao.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.usage.dao;
+
+import java.util.Date;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+@ExternalizedSqlViaStringTemplate3()
+public interface RolledUpUsageSqlDao extends Transactional<RolledUpUsageSqlDao>, Transmogrifier {
+
+    @SqlUpdate
+    public void record(@Bind("sourceId") final int sourceId, @Bind("metricId") final int metricId,
+                       @Bind("startTime") final Date startTime, @Bind("endTime") final Date endTime,
+                       @Bind("value") final long value);
+}
diff --git a/usage/src/main/java/com/ning/billing/usage/timeline/persistent/TimelineSqlDao.java b/usage/src/main/java/com/ning/billing/usage/timeline/persistent/TimelineSqlDao.java
index 395ee3a..ce6c5bd 100644
--- a/usage/src/main/java/com/ning/billing/usage/timeline/persistent/TimelineSqlDao.java
+++ b/usage/src/main/java/com/ning/billing/usage/timeline/persistent/TimelineSqlDao.java
@@ -29,6 +29,7 @@ import org.skife.jdbi.v2.sqlobject.customizers.BatchChunkSize;
 import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 
 import com.ning.billing.usage.timeline.categories.CategoryIdAndMetric;
@@ -44,7 +45,7 @@ import com.ning.billing.usage.timeline.sources.SourceIdAndMetricIdMapper;
 
 @ExternalizedSqlViaStringTemplate3()
 @RegisterMapper({CategoryIdAndMetricMapper.class, StartTimesMapper.class, SourceIdAndMetricIdMapper.class})
-public interface TimelineSqlDao extends Transactional<TimelineSqlDao> {
+public interface TimelineSqlDao extends Transactional<TimelineSqlDao>, Transmogrifier {
 
     @SqlQuery
     Integer getSourceId(@Bind("sourceName") final String source);
diff --git a/usage/src/main/java/com/ning/billing/usage/timeline/TimelineEventHandler.java b/usage/src/main/java/com/ning/billing/usage/timeline/TimelineEventHandler.java
index c7e3903..f36c1df 100644
--- a/usage/src/main/java/com/ning/billing/usage/timeline/TimelineEventHandler.java
+++ b/usage/src/main/java/com/ning/billing/usage/timeline/TimelineEventHandler.java
@@ -199,12 +199,12 @@ public class TimelineEventHandler {
     /**
      * Main entry point to the timeline subsystem. Record a series of sample for a given source, at a given timestamp.
      *
-     * @param sourceId       id of the source
+     * @param sourceName     name of the source
      * @param eventType      event category
      * @param eventTimestamp event timestamp
      * @param samples        samples to record
      */
-    public void record(final Integer sourceId, final String eventType, final DateTime eventTimestamp, final Map<String, Object> samples) {
+    public void record(final String sourceName, final String eventType, final DateTime eventTimestamp, final Map<String, Object> samples) {
         if (shuttingDown.get()) {
             eventsReceivedAfterShuttingDown.incrementAndGet();
             return;
@@ -212,6 +212,9 @@ public class TimelineEventHandler {
         try {
             handledEventCount.incrementAndGet();
 
+            // Find the sourceId
+            final int sourceId = timelineDAO.getOrAddSource(sourceName);
+
             // Extract and parse samples
             final Map<Integer, ScalarSample> scalarSamples = new LinkedHashMap<Integer, ScalarSample>();
             convertSamplesToScalarSamples(sourceId, eventType, samples, scalarSamples);
diff --git a/usage/src/main/resources/com/ning/billing/usage/ddl.sql b/usage/src/main/resources/com/ning/billing/usage/ddl.sql
index 4fbff70..fab0ce2 100644
--- a/usage/src/main/resources/com/ning/billing/usage/ddl.sql
+++ b/usage/src/main/resources/com/ning/billing/usage/ddl.sql
@@ -30,7 +30,7 @@ create table metrics (
 ) engine = innodb default charset = latin1;
 
 create table timeline_chunks (
-  chunk_id bigint not null auto_increment
+  record_id bigint not null auto_increment
 , source_id integer not null
 , metric_id integer not null
 , sample_count integer not null
@@ -53,3 +53,15 @@ create table last_start_times (
 
 insert ignore into timeline_chunks(chunk_id, source_id, metric_id, sample_count, start_time, end_time, in_row_samples, blob_samples)
                            values (0, 0, 0, 0, 0, 0, null, null);
+
+create table timeline_rolled_up_chunk (
+  record_id bigint not null auto_increment
+, source_id integer not null
+, metric_id integer not null
+, start_time date not null
+, end_time date not null
+, value bigint not null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+) engine = innodb default charset = latin1;