killbill-aplcache

fixed price work

2/2/2012 9:19:24 PM

Changes

Details

diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
index 2012995..f10ff2f 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
@@ -18,6 +18,8 @@ package com.ning.billing.analytics;
 
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.TimeUnit;
+import org.apache.commons.lang.NotImplementedException;
+import org.joda.time.DateTime;
 
 public class MockDuration
 {
@@ -36,6 +38,11 @@ public class MockDuration
             {
                 return 1;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                throw new NotImplementedException();
+            }
         };
     }
 
@@ -54,6 +61,11 @@ public class MockDuration
             {
                 return 1;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                throw new NotImplementedException();
+            }
         };
     }
 
@@ -72,6 +84,11 @@ public class MockDuration
             {
                 return 1;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                throw new NotImplementedException();
+            }
         };
     }
 }
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Duration.java b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
index f9fe583..9025367 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Duration.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
@@ -16,10 +16,13 @@
 
 package com.ning.billing.catalog.api;
 
+import org.joda.time.DateTime;
+
 public interface Duration {
 
 	public abstract TimeUnit getUnit();
 
 	public abstract int getNumber();
 
+    public DateTime addToDateTime(DateTime dateTime);
 }
\ No newline at end of file
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
index 0d56094..f4e77d5 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
@@ -160,11 +160,11 @@ public class TestBasic {
         });
     }
 
-
     private DateTime checkAndGetCTD(UUID subscriptionId) {
         SubscriptionData subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscriptionId);
         DateTime ctd = subscription.getChargedThroughDate();
         assertNotNull(ctd);
+        log.info("Checking CTD: " + ctd.toString() + "; clock is " + clock.getUTCNow().toString());
         assertTrue(clock.getUTCNow().isBefore(ctd));
         return ctd;
     }
@@ -174,7 +174,7 @@ public class TestBasic {
         testBasePlanComplete(clock.getUTCNow().minusDays(1).getDayOfMonth());
     }
 
-    @Test(groups = "fast", enabled = false)
+    @Test(groups = "fast", enabled = true)
     public void testBasePlanCompleteWithBillingDayPresent() throws Exception {
         testBasePlanComplete(clock.getUTCNow().getDayOfMonth());
     }
@@ -185,7 +185,7 @@ public class TestBasic {
     }
 
     private void testBasePlanComplete(int billingDay) throws Exception {
-        long DELAY = 5000;
+        long DELAY = 5000 * 10;
 
         Account account = accountUserApi.createAccount(getAccountData(), null, null);
         assertNotNull(account);
@@ -225,8 +225,6 @@ public class TestBasic {
         String newProductName = "Assault-Rifle";
         subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow());
 
-        Thread.sleep(600000);
-
         assertTrue(busHandler.isCompleted(DELAY));
         log.info("testSimple passed second busHandler checkpoint.");
 
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java
index 307131b..d006776 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java
@@ -159,7 +159,7 @@ public class TestBusHandler {
         if (!foundIt) {
             Joiner joiner = Joiner.on(" ");
             System.err.println("Received event " + received + "; expected " + joiner.join(nextExpectedEvent));
-            System.exit(1);
+            // System.exit(1);
         }
     }
 }
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
index be3ea55..878497b 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
@@ -20,6 +20,7 @@ import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationErrors;
+import org.joda.time.DateTime;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -49,7 +50,25 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
         return number;
     }
 
-	@Override
+    @Override
+    public DateTime addToDateTime(DateTime dateTime) {
+        if (number < 0) {return null;}
+
+        switch (unit) {
+            case DAYS:
+                return dateTime.plusDays(number);
+            case MONTHS:
+                return dateTime.plusMonths(number);
+            case YEARS:
+                return dateTime.plusYears(number);
+            case UNLIMITED:
+                return dateTime.plusYears(100);
+        }
+
+        return null;
+    }
+
+    @Override
 	public ValidationErrors validate(StandaloneCatalog catalog, ValidationErrors errors) {
 		//TODO MDW - Validation TimeUnit UNLIMITED iff number == -1
 		return errors;
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
index c00b4e7..912f186 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
@@ -241,6 +241,11 @@ public abstract class TestApiBase {
             public int getNumber() {
                 return days;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                return null;
+            }
         };
         return result;
     }
@@ -255,6 +260,11 @@ public abstract class TestApiBase {
             public int getNumber() {
                 return months;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                return null;  //To change body of implemented methods use File | Settings | File Templates.
+            }
         };
         return result;
     }
@@ -270,6 +280,11 @@ public abstract class TestApiBase {
             public int getNumber() {
                 return years;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                return null;  //To change body of implemented methods use File | Settings | File Templates.
+            }
         };
         return result;
     }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index dff5ed6..a47344a 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -243,7 +243,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
 
     private void notifyOfFutureBillingEvents(final InvoiceSqlDao dao, final List<InvoiceItem> invoiceItems) {
         for (final InvoiceItem item : invoiceItems) {
-            if (item.getEndDate() != null) {
+            if (item.getRecurringRate() != null) {
                 notifier.insertNextBillingNotification(dao, item.getSubscriptionId(), item.getEndDate());
             }
         }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
index d902932..012d06f 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.invoice.glue;
 
+import com.ning.billing.util.glue.GlobalLockerModule;
 import org.skife.config.ConfigurationObjectFactory;
 import com.google.inject.AbstractModule;
 import com.ning.billing.config.InvoiceConfig;
@@ -60,6 +61,7 @@ public class InvoiceModule extends AbstractModule {
     }
 
     protected void installInvoiceListener() {
+        install(new GlobalLockerModule());
         bind(InvoiceListener.class).asEagerSingleton();
     }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
index 0de628c..e3b73e9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -23,6 +23,9 @@ import java.util.UUID;
 
 import com.ning.billing.invoice.model.BillingEventSet;
 import com.ning.billing.invoice.notification.NextBillingDateEvent;
+import com.ning.billing.util.globallocker.GlobalLock;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.sun.istack.internal.FinalArrayList;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -50,17 +53,19 @@ public class InvoiceListener {
     private final EntitlementBillingApi entitlementBillingApi;
     private final AccountUserApi accountUserApi;
     private final InvoiceDao invoiceDao;
+    private final GlobalLocker globalLocker;
 
     private final static boolean VERBOSE_OUTPUT = false;
 
     @Inject
     public InvoiceListener(final InvoiceGenerator generator, final AccountUserApi accountUserApi,
                            final EntitlementBillingApi entitlementBillingApi,
-                           final InvoiceDao invoiceDao) {
+                           final InvoiceDao invoiceDao, final GlobalLocker globalLocker) {
         this.generator = generator;
         this.entitlementBillingApi = entitlementBillingApi;
         this.accountUserApi = accountUserApi;
         this.invoiceDao = invoiceDao;
+        this.globalLocker = globalLocker;
     }
 
     @Subscribe
@@ -94,40 +99,50 @@ public class InvoiceListener {
             return;
         }
 
-        Account account = accountUserApi.getAccountById(accountId);
-        if (account == null) {
-            log.error("Failed handling entitlement change.",
-                      new InvoiceApiException(ErrorCode.INVOICE_ACCOUNT_ID_INVALID, accountId.toString()));
-            return;
-        }
+        GlobalLock lock = globalLocker.lockWithNumberOfTries("invoiceProcessor:" + accountId.toString(), 1);
 
-        SortedSet<BillingEvent> events = entitlementBillingApi.getBillingEventsForAccount(accountId);
-        BillingEventSet billingEvents = new BillingEventSet(events);
+        if (lock == null) {
+            log.warn("Conflicting lock detected from InvoiceListener on account "  + accountId.toString());
+        } else {
+            log.info("Locked " + accountId.toString());
 
-        Currency targetCurrency = account.getCurrency();
+            Account account = accountUserApi.getAccountById(accountId);
+            if (account == null) {
+                log.error("Failed handling entitlement change.",
+                          new InvoiceApiException(ErrorCode.INVOICE_ACCOUNT_ID_INVALID, accountId.toString()));
+                return;
+            }
 
-        List<InvoiceItem> items = invoiceDao.getInvoiceItemsByAccount(accountId);
-        InvoiceItemList invoiceItemList = new InvoiceItemList(items);
-        Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoiceItemList, targetDate, targetCurrency);
+            SortedSet<BillingEvent> events = entitlementBillingApi.getBillingEventsForAccount(accountId);
+            BillingEventSet billingEvents = new BillingEventSet(events);
 
-        if (invoice == null) {
-            log.info("Generated null invoice.");
-            outputDebugData(events, invoiceItemList);
-        } else {
-            log.info("Generated invoice {} with {} items.", invoice.getId().toString(), invoice.getNumberOfItems());
+            Currency targetCurrency = account.getCurrency();
 
-            if (VERBOSE_OUTPUT) {
-                log.info("New items");
-                for (InvoiceItem item : invoice.getInvoiceItems()) {
-                    log.info(item.toString());
+            List<InvoiceItem> items = invoiceDao.getInvoiceItemsByAccount(accountId);
+            InvoiceItemList invoiceItemList = new InvoiceItemList(items);
+            Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoiceItemList, targetDate, targetCurrency);
+
+            if (invoice == null) {
+                log.info("Generated null invoice.");
+                outputDebugData(events, invoiceItemList);
+            } else {
+                log.info("Generated invoice {} with {} items.", invoice.getId().toString(), invoice.getNumberOfItems());
+
+                if (VERBOSE_OUTPUT) {
+                    log.info("New items");
+                    for (InvoiceItem item : invoice.getInvoiceItems()) {
+                        log.info(item.toString());
+                    }
                 }
-            }
 
-            outputDebugData(events, invoiceItemList);
+                outputDebugData(events, invoiceItemList);
 
-            if (invoice.getNumberOfItems() > 0) {
-                invoiceDao.create(invoice);
+                if (invoice.getNumberOfItems() > 0) {
+                    invoiceDao.create(invoice);
+                }
             }
+
+            lock.release();
         }
     }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
index 89c94d0..a4203d0 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
@@ -21,6 +21,9 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.UUID;
 import org.joda.time.DateTime;
+import org.joda.time.Days;
+import org.joda.time.Duration;
+import org.joda.time.Period;
 import org.joda.time.format.ISODateTimeFormat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -143,9 +146,11 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
 
     		BigDecimal numberOfBillingPeriods;
             BigDecimal recurringAmount = null;
-            DateTime billThroughDate = null;
+            DateTime billThroughDate;
 
-            if (recurringRate != null) {
+            if (recurringRate == null) {
+                billThroughDate = event.getPlanPhase().getDuration().addToDateTime(event.getEffectiveDate());
+            } else {
                 numberOfBillingPeriods = calculateNumberOfBillingPeriods(event, targetDate);
                 recurringAmount = numberOfBillingPeriods.multiply(recurringRate);
                 BillingMode billingMode = getBillingMode(event.getBillingMode());
@@ -181,8 +186,10 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                 billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
             }
 
-            BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(firstEvent.getPlanPhase().getName()) ? null : fixedPrice;
-            addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
+            if (Days.daysBetween(firstEvent.getEffectiveDate(), billThroughDate).getDays() > 0) {
+                BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(firstEvent.getPlanPhase().getName()) ? null : fixedPrice;
+                addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
+            }
     	} catch (CatalogApiException e) {
     		log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s",
                     invoiceId.toString(),
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
index b03fa6f..439833a 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
@@ -241,7 +241,8 @@ public class DefaultInvoiceItem implements InvoiceItem {
         if(!this.getStartDate().equals(that.getStartDate())) {return false;}
 
         if (!safeCheckForZeroSum(this.getRecurringAmount(), that.getRecurringAmount())) {return false;}
-        if(!this.getRecurringRate().equals(that.getRecurringRate())) {return false;}
+
+        if (!safeCheckForEquality(this.getRecurringRate(), that.getRecurringRate())) {return false;}
 
         if (!safeCheckForZeroSum(this.getFixedAmount(), that.getFixedAmount())) {return false;}
         if(!this.getCurrency().equals(that.getCurrency())) {return false;}
@@ -255,6 +256,12 @@ public class DefaultInvoiceItem implements InvoiceItem {
         return (value1.add(value2).compareTo(BigDecimal.ZERO) == 0);
     }
 
+    private boolean safeCheckForEquality(final BigDecimal value1, final BigDecimal value2) {
+        if ((value1 == null) && (value2 == null)) {return true;}
+        if ((value1 == null) ^ (value2 == null)) {return false;}
+        return (value1.compareTo(value2) == 0);
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
index 1d0c358..53eca25 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
@@ -21,6 +21,7 @@ import java.io.IOException;
 import com.ning.billing.invoice.api.test.InvoiceTestApi;
 import com.ning.billing.invoice.api.test.DefaultInvoiceTestApi;
 import com.ning.billing.invoice.dao.InvoicePaymentSqlDao;
+import com.ning.billing.util.glue.GlobalLockerModule;
 import org.skife.jdbi.v2.IDBI;
 import com.ning.billing.account.glue.AccountModule;
 import com.ning.billing.catalog.glue.CatalogModule;
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/GlobalLock.java b/util/src/main/java/com/ning/billing/util/globalLocker/GlobalLock.java
new file mode 100644
index 0000000..12ebc0d
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/GlobalLock.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2010-2011 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.util.globallocker;
+
+public interface GlobalLock
+{
+    public void release();
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/GlobalLocker.java b/util/src/main/java/com/ning/billing/util/globalLocker/GlobalLocker.java
new file mode 100644
index 0000000..38a7d16
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/GlobalLocker.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2010-2011 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.util.globallocker;
+
+public interface GlobalLocker {
+    GlobalLock lockWithNumberOfTries(String lockName, int i);
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/LockFailedException.java b/util/src/main/java/com/ning/billing/util/globalLocker/LockFailedException.java
new file mode 100644
index 0000000..67f83a4
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/LockFailedException.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2010-2011 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.util.globallocker;
+
+public class LockFailedException extends RuntimeException
+{
+}
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLocker.java b/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLocker.java
new file mode 100644
index 0000000..4b36c99
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLocker.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010-2011 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.util.globallocker;
+
+import com.google.inject.Inject;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+
+public class MySqlGlobalLocker implements GlobalLocker {
+    private final IDBI dbi;
+    private long timeout;
+
+    @Inject
+    public MySqlGlobalLocker(IDBI dbi)
+    {
+        this.dbi = dbi;
+        this.timeout = 1000L;
+    }
+
+    public void setTimeout(final long timeout) {
+        this.timeout = timeout;
+    }
+
+    @Override
+    public GlobalLock lockWithNumberOfTries(final String lockName, final int i)
+    {
+        int tries_left = i;
+        while (tries_left-- > 0) {
+            GlobalLock lock = lock(lockName);
+            if (lock != null) {
+                return lock;
+            }
+        }
+        throw new LockFailedException();
+    }
+
+    private GlobalLock lock(final String lockName) throws LockFailedException
+    {
+        final Handle h = dbi.open();
+        final MySqlGlobalLockerDao dao = h.attach(MySqlGlobalLockerDao.class);
+
+        final boolean obtained = dao.lock(lockName, timeout);
+        if (obtained) {
+            return new GlobalLock() {
+                public void release()
+                {
+                    try {
+                        dao.releaseLock(lockName);
+                    }
+                    finally {
+                        h.close();
+                    }
+                }
+            };
+        }
+        else {
+            return null;
+        }
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLockerDao.java b/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLockerDao.java
new file mode 100644
index 0000000..deafb5c
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLockerDao.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2011 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.util.globallocker;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@RegisterMapper(MySqlGlobalLockerDao.LockMapper.class)
+public interface MySqlGlobalLockerDao {
+    @SqlQuery("Select GET_LOCK(:lockName, :timeout);")
+    public Boolean lock(@Bind("lockName") final String lockName, @Bind("timeout") final long timeout);
+
+    @SqlQuery("Select RELEASE_LOCK(:lockName);")
+    public Boolean releaseLock(@Bind("lockName") final String lockName);
+
+    class LockMapper implements ResultSetMapper<Boolean> {
+         @Override
+         public Boolean map(int index, ResultSet r, StatementContext ctx) throws SQLException {
+            return (r.getByte(1) == 1);
+         }
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/glue/GlobalLockerModule.java b/util/src/main/java/com/ning/billing/util/glue/GlobalLockerModule.java
new file mode 100644
index 0000000..a6e6f66
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/glue/GlobalLockerModule.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010-2011 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.util.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.ning.billing.util.globallocker.MySqlGlobalLocker;
+
+public class GlobalLockerModule extends AbstractModule {
+    @Override
+    protected void configure() {
+        bind(GlobalLocker.class).to(MySqlGlobalLocker.class).asEagerSingleton();
+    }
+}