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();
+ }
+}