killbill-aplcache

Changes

account/pom.xml 2(+1 -1)

api/pom.xml 2(+1 -1)

beatrix/pom.xml 2(+1 -1)

catalog/pom.xml 2(+1 -1)

currency/pom.xml 2(+1 -1)

invoice/pom.xml 2(+1 -1)

jaxrs/pom.xml 2(+1 -1)

junction/pom.xml 2(+1 -1)

NEWS 12(+12 -0)

osgi/pom.xml 2(+1 -1)

overdue/pom.xml 2(+1 -1)

payment/pom.xml 2(+1 -1)

pom.xml 4(+2 -2)

server/pom.xml 2(+1 -1)

tenant/pom.xml 2(+1 -1)

usage/pom.xml 2(+1 -1)

util/pom.xml 2(+1 -1)

Details

diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index e0ce11c..0e574ab 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -20,6 +20,8 @@
     <inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="REPORT_VARIABLES" value="true" />
       <option name="REPORT_PARAMETERS" value="true" />
+      <option name="REPORT_CATCH_PARAMETERS" value="true" />
+      <option name="REPORT_FOREACH_PARAMETERS" value="true" />
     </inspection_tool>
     <inspection_tool class="MagicNumber" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true">

account/pom.xml 2(+1 -1)

diff --git a/account/pom.xml b/account/pom.xml
index 93ac27d..b295e0e 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-account</artifactId>
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
index 4cfd258..dd1d652 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
@@ -23,7 +23,6 @@ import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-import org.skife.jdbi.v2.sqlobject.customizers.Define;
 import org.skife.jdbi.v2.sqlobject.customizers.FetchSize;
 
 import com.ning.billing.account.api.Account;
@@ -45,7 +44,8 @@ public interface AccountSqlDao extends EntitySqlDao<AccountModelDao, Account> {
     // Magic value to force MySQL to stream from the database
     // See http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html (ResultSet)
     @FetchSize(Integer.MIN_VALUE)
-    public Iterator<AccountModelDao> searchAccounts(@Define("searchKey") final String searchKey,
+    public Iterator<AccountModelDao> searchAccounts(@Bind("searchKey") final String searchKey,
+                                                    @Bind("likeSearchKey") final String likeSearchKey,
                                                     @Bind("offset") final Long offset,
                                                     @Bind("rowCount") final Long rowCount,
                                                     @BindBean final InternalTenantContext context);
diff --git a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
index 3ce879e..ea710c3 100644
--- a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
@@ -112,7 +112,7 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
                                               new PaginationIteratorBuilder<AccountModelDao, Account, AccountSqlDao>() {
                                                   @Override
                                                   public Iterator<AccountModelDao> build(final AccountSqlDao accountSqlDao, final Long limit) {
-                                                      return accountSqlDao.searchAccounts(searchKey, offset, limit, context);
+                                                      return accountSqlDao.searchAccounts(searchKey, String.format("%%%s%%", searchKey), offset, limit, context);
                                                   }
                                               },
                                               offset,
diff --git a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
index 10d4edc..c1e5255 100644
--- a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
+++ b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
@@ -86,17 +86,17 @@ getAccountByKey() ::= <<
     where external_key = :externalKey <AND_CHECK_TENANT()>;
 >>
 
-searchAccounts(searchKey, offset, rowCount) ::= <<
+searchAccounts() ::= <<
 select SQL_CALC_FOUND_ROWS
 <allTableFields("t.")>
 from <tableName()> t
 where 1 = 1
 and (
-     <idField("t.")> = '<searchKey>'
-  or t.name like '%<searchKey>%'
-  or t.email like '%<searchKey>%'
-  or t.external_key like '%<searchKey>%'
-  or t.company_name like '%<searchKey>%'
+     <idField("t.")> = :searchKey
+  or t.name like :likeSearchKey
+  or t.email like :likeSearchKey
+  or t.external_key like :likeSearchKey
+  or t.company_name like :likeSearchKey
 )
 <AND_CHECK_TENANT("t.")>
 order by <recordIdField("t.")> ASC

api/pom.xml 2(+1 -1)

diff --git a/api/pom.xml b/api/pom.xml
index 9ea3534..cc9f6a1 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-internal-api</artifactId>
diff --git a/api/src/main/java/com/ning/billing/subscription/api/SubscriptionBaseInternalApi.java b/api/src/main/java/com/ning/billing/subscription/api/SubscriptionBaseInternalApi.java
index 9662d65..8c2ce8f 100644
--- a/api/src/main/java/com/ning/billing/subscription/api/SubscriptionBaseInternalApi.java
+++ b/api/src/main/java/com/ning/billing/subscription/api/SubscriptionBaseInternalApi.java
@@ -24,21 +24,20 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.entitlement.api.EntitlementAOStatusDryRun;
+import com.ning.billing.events.EffectiveSubscriptionInternalEvent;
 import com.ning.billing.subscription.api.user.SubscriptionBaseApiException;
 import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
-import com.ning.billing.events.EffectiveSubscriptionInternalEvent;
-
+import com.ning.billing.util.entity.Pagination;
 
 public interface SubscriptionBaseInternalApi {
 
     public SubscriptionBase createSubscription(final UUID bundleId, final PlanPhaseSpecifier spec, final DateTime requestedDateWithMs,
                                                final InternalCallContext context) throws SubscriptionBaseApiException;
 
-
     public SubscriptionBaseBundle createBundleForAccount(final UUID accountId, final String bundleName, final InternalCallContext context)
             throws SubscriptionBaseApiException;
 
@@ -49,6 +48,10 @@ public interface SubscriptionBaseInternalApi {
 
     public List<SubscriptionBaseBundle> getBundlesForKey(final String bundleKey, final InternalTenantContext context);
 
+    public Pagination<SubscriptionBaseBundle> getBundles(final Long offset, final Long limit, final InternalTenantContext context);
+
+    public Pagination<SubscriptionBaseBundle> searchBundles(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context);
+
     public Iterable<UUID> getNonAOSubscriptionIdsForKey(final String bundleKey, final InternalTenantContext context);
 
     public List<SubscriptionBase> getSubscriptionsForBundle(final UUID bundleId, final InternalTenantContext context);
@@ -74,6 +77,5 @@ public interface SubscriptionBaseInternalApi {
     public List<EntitlementAOStatusDryRun> getDryRunChangePlanStatus(final UUID subscriptionId, @Nullable final String baseProductName,
                                                                      final DateTime requestedDate, final InternalTenantContext context) throws SubscriptionBaseApiException;
 
-
     public void updateExternalKey(final UUID bundleId, final String newExternalKey, final InternalCallContext context);
 }

beatrix/pom.xml 2(+1 -1)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index c4c9f77..8d872e7 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-beatrix</artifactId>
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
index 0358452..9c1eb94 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
@@ -26,9 +26,11 @@ import javax.inject.Inject;
 
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
+import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.Test;
 
+import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
@@ -39,7 +41,9 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.DefaultEntitlement;
+import com.ning.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItemType;
 import com.ning.billing.invoice.generator.DefaultInvoiceGeneratorWithSwitchRepairLogic;
 import com.ning.billing.invoice.generator.DefaultInvoiceGeneratorWithSwitchRepairLogic.REPAIR_INVOICE_LOGIC;
@@ -700,4 +704,160 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 11), new LocalDate(2012, 6, 11), InvoiceItemType.CBA_ADJ, new BigDecimal("-249.95")));
         invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
     }
+
+    @Test(groups = "slow")
+    public void testRepairWithFullItemAdjustment() throws Exception {
+
+        invoiceGenerator.setDefaultRepairLogic(REPAIR_INVOICE_LOGIC.PARTIAL_REPAIR);
+
+        final LocalDate today = new LocalDate(2013, 7, 19);
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDeltaFromReality(today.toDateTimeAtCurrentTime(DateTimeZone.UTC).getMillis() - clock.getUTCNow().getMillis());
+
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.ANNUAL;
+        final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        //
+        // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+        //
+        DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
+        assertNotNull(bpEntitlement);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).size(), 1);
+
+        assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getBillingPeriod(), BillingPeriod.ANNUAL);
+
+        // Move out of trials for interesting invoices adjustments
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+        clock.addDays(30);
+        assertListenerStatus();
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 2);
+        ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2013, 8, 18), new LocalDate(2014, 8, 18), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+
+        // Move clock to 2013-09-17
+        clock.addDays(30);
+        busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.INVOICE_ADJUSTMENT);
+        bpEntitlement.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.IMMEDIATE, BillingActionPolicy.IMMEDIATE, callContext);
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 2);
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2013, 8, 18), new LocalDate(2014, 8, 18), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2013, 9, 17), new LocalDate(2014, 8, 18), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2202.67")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2013, 9, 17), new LocalDate(2013, 9, 17), InvoiceItemType.CBA_ADJ, new BigDecimal("2202.67")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+
+
+        //
+        // ITEM ADJUSTMENT PRIOR TO DOING THE REPAIR
+        //
+        final Invoice invoice1 = invoices.get(1);
+        final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), callContext);
+        final ExpectedPaymentCheck expectedPaymentCheck = new ExpectedPaymentCheck(clock.getUTCNow().toLocalDate(), new BigDecimal("2399.95"), PaymentStatus.SUCCESS, invoice1.getId(), Currency.USD);
+        final Payment payment1 = payments.get(0);
+
+        final Map<UUID, BigDecimal> iias = new HashMap<UUID, BigDecimal>();
+        iias.put(invoice1.getInvoiceItems().get(0).getId(), new BigDecimal("197.28"));
+        busHandler.pushExpectedEvents(NextEvent.INVOICE_ADJUSTMENT);
+        paymentApi.createRefundWithItemsAdjustments(account, payment1.getId(), iias, callContext);
+        assertListenerStatus();
+
+        try {
+            invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(clock.getUTCToday()), false, callContext);
+            Assert.fail("Should not gnenerated an new invoice");
+        } catch (InvoiceApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
+        }
+    }
+
+    //
+    // This is the exact same test as testRepairWithFullItemAdjustment except we now only do a partial item adjustment.
+    // The invoice code will NOT reinvoice for the remaining part as the item was both repaired and adjusted and so
+    // it does not have enough info to understand what should be re-invoiced.
+    //
+    // Note that there is no real use case for those scenarii in real life
+    //
+    @Test(groups = "slow")
+    public void testRepairWithPartialItemAdjustment() throws Exception {
+
+        invoiceGenerator.setDefaultRepairLogic(REPAIR_INVOICE_LOGIC.PARTIAL_REPAIR);
+
+        final LocalDate today = new LocalDate(2013, 7, 19);
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDeltaFromReality(today.toDateTimeAtCurrentTime(DateTimeZone.UTC).getMillis() - clock.getUTCNow().getMillis());
+
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.ANNUAL;
+        final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        //
+        // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+        //
+        DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
+        assertNotNull(bpEntitlement);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).size(), 1);
+
+        assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getBillingPeriod(), BillingPeriod.ANNUAL);
+
+        // Move out of trials for interesting invoices adjustments
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+        clock.addDays(30);
+        assertListenerStatus();
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 2);
+        ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2013, 8, 18), new LocalDate(2014, 8, 18), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+
+        // Move clock to 2013-09-17
+        clock.addDays(30);
+        busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.INVOICE_ADJUSTMENT);
+        bpEntitlement.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.IMMEDIATE, BillingActionPolicy.IMMEDIATE, callContext);
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 2);
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2013, 8, 18), new LocalDate(2014, 8, 18), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2013, 9, 17), new LocalDate(2014, 8, 18), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2202.67")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2013, 9, 17), new LocalDate(2013, 9, 17), InvoiceItemType.CBA_ADJ, new BigDecimal("2202.67")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+
+
+        //
+        // ITEM ADJUSTMENT PRIOR TO DOING THE REPAIR
+        //
+        final Invoice invoice1 = invoices.get(1);
+        final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), callContext);
+        final ExpectedPaymentCheck expectedPaymentCheck = new ExpectedPaymentCheck(clock.getUTCNow().toLocalDate(), new BigDecimal("2399.95"), PaymentStatus.SUCCESS, invoice1.getId(), Currency.USD);
+        final Payment payment1 = payments.get(0);
+
+        final Map<UUID, BigDecimal> iias = new HashMap<UUID, BigDecimal>();
+        iias.put(invoice1.getInvoiceItems().get(0).getId(), new BigDecimal("100.00"));
+        busHandler.pushExpectedEvents(NextEvent.INVOICE_ADJUSTMENT);
+        paymentApi.createRefundWithItemsAdjustments(account, payment1.getId(), iias, callContext);
+        assertListenerStatus();
+
+        try {
+            invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(clock.getUTCToday()), false, callContext);
+            Assert.fail("Should not gnenerated an new invoice");
+        } catch (InvoiceApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
+        }
+    }
+
 }

catalog/pom.xml 2(+1 -1)

diff --git a/catalog/pom.xml b/catalog/pom.xml
index 729fca9..4630db6 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-catalog</artifactId>

currency/pom.xml 2(+1 -1)

diff --git a/currency/pom.xml b/currency/pom.xml
index 73f55f9..157778e 100644
--- a/currency/pom.xml
+++ b/currency/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index ca9aa82..a1c4c86 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-entitlement</artifactId>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java
index 0c18efd..cc42d68 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java
@@ -27,6 +27,8 @@ import java.util.UUID;
 import javax.inject.Inject;
 
 import org.joda.time.DateTimeZone;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.ning.billing.ErrorCode;
 import com.ning.billing.ObjectType;
@@ -34,8 +36,6 @@ import com.ning.billing.callcontext.InternalCallContext;
 import com.ning.billing.callcontext.InternalTenantContext;
 import com.ning.billing.entitlement.AccountEntitlements;
 import com.ning.billing.entitlement.EntitlementInternalApi;
-import com.ning.billing.entitlement.EventsStream;
-import com.ning.billing.entitlement.api.svcs.DefaultEntitlementInternalApi;
 import com.ning.billing.entitlement.engine.core.EntitlementUtils;
 import com.ning.billing.subscription.api.SubscriptionBase;
 import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
@@ -48,15 +48,22 @@ import com.ning.billing.util.callcontext.InternalCallContextFactory;
 import com.ning.billing.util.callcontext.TenantContext;
 import com.ning.billing.util.customfield.ShouldntHappenException;
 import com.ning.billing.util.dao.NonEntityDao;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
 
+import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Ordering;
 
+import static com.ning.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationNoException;
+
 public class DefaultSubscriptionApi implements SubscriptionApi {
 
+    private static final Logger log = LoggerFactory.getLogger(DefaultSubscriptionApi.class);
+
     private static final Comparator<SubscriptionBundle> SUBSCRIPTION_BUNDLE_COMPARATOR = new Comparator<SubscriptionBundle>() {
         @Override
         public int compare(final SubscriptionBundle o1, final SubscriptionBundle o2) {
@@ -164,7 +171,7 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
             }
             final SubscriptionBase subscriptionBase = subscriptionBaseInternalApi.getSubscriptionFromId(activeSubscriptionIdForKey, internalContext);
             return getSubscriptionBundle(subscriptionBase.getBundleId(), context);
-        } catch (SubscriptionBaseApiException e) {
+        } catch (final SubscriptionBaseApiException e) {
             throw new SubscriptionApiException(e);
         }
     }
@@ -188,12 +195,60 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
         return getSubscriptionBundlesForAccount(accountId, context);
     }
 
+    @Override
+    public Pagination<SubscriptionBundle> getSubscriptionBundles(final Long offset, final Long limit, final TenantContext context) {
+        final InternalTenantContext internalContext = internalCallContextFactory.createInternalTenantContext(context);
+        return getEntityPaginationNoException(limit,
+                                              new SourcePaginationBuilder<SubscriptionBaseBundle, SubscriptionApiException>() {
+                                                  @Override
+                                                  public Pagination<SubscriptionBaseBundle> build() {
+                                                      return subscriptionBaseInternalApi.getBundles(offset, limit, internalContext);
+                                                  }
+                                              },
+                                              new Function<SubscriptionBaseBundle, SubscriptionBundle>() {
+                                                  @Override
+                                                  public SubscriptionBundle apply(final SubscriptionBaseBundle subscriptionBaseBundle) {
+                                                      try {
+                                                          return getSubscriptionBundle(subscriptionBaseBundle.getId(), context);
+                                                      } catch (final SubscriptionApiException e) {
+                                                          log.warn("Error retrieving subscription", e);
+                                                          return null;
+                                                      }
+                                                  }
+                                              }
+                                             );
+    }
+
+    @Override
+    public Pagination<SubscriptionBundle> searchSubscriptionBundles(final String searchKey, final Long offset, final Long limit, final TenantContext context) {
+        final InternalTenantContext internalContext = internalCallContextFactory.createInternalTenantContext(context);
+        return getEntityPaginationNoException(limit,
+                                              new SourcePaginationBuilder<SubscriptionBaseBundle, SubscriptionApiException>() {
+                                                  @Override
+                                                  public Pagination<SubscriptionBaseBundle> build() {
+                                                      return subscriptionBaseInternalApi.searchBundles(searchKey, offset, limit, internalContext);
+                                                  }
+                                              },
+                                              new Function<SubscriptionBaseBundle, SubscriptionBundle>() {
+                                                  @Override
+                                                  public SubscriptionBundle apply(final SubscriptionBaseBundle subscriptionBaseBundle) {
+                                                      try {
+                                                          return getSubscriptionBundle(subscriptionBaseBundle.getId(), context);
+                                                      } catch (final SubscriptionApiException e) {
+                                                          log.warn("Error retrieving subscription", e);
+                                                          return null;
+                                                      }
+                                                  }
+                                              }
+                                             );
+    }
+
     private List<SubscriptionBundle> getSubscriptionBundlesForAccount(final UUID accountId, final TenantContext tenantContext) throws SubscriptionApiException {
         // Retrieve entitlements
         final AccountEntitlements accountEntitlements;
         try {
             accountEntitlements = entitlementInternalApi.getAllEntitlementsForAccountId(accountId, tenantContext);
-        } catch (EntitlementApiException e) {
+        } catch (final EntitlementApiException e) {
             throw new SubscriptionApiException(e);
         }
 

invoice/pom.xml 2(+1 -1)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index 84d1e39..81c461a 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-invoice</artifactId>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 171d337..e8fec16 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -134,6 +134,25 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
+    public Pagination<Invoice> searchInvoices(final String searchKey, final Long offset, final Long limit, final TenantContext context) {
+        return getEntityPaginationNoException(limit,
+                                              new SourcePaginationBuilder<InvoiceModelDao, AccountApiException>() {
+                                                  @Override
+                                                  public Pagination<InvoiceModelDao> build() {
+                                                      // Invoices will be shallow, i.e. won't contain items nor payments
+                                                      return dao.searchInvoices(searchKey, offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+                                                  }
+                                              },
+                                              new Function<InvoiceModelDao, Invoice>() {
+                                                  @Override
+                                                  public Invoice apply(final InvoiceModelDao invoiceModelDao) {
+                                                      return new DefaultInvoice(invoiceModelDao);
+                                                  }
+                                              }
+                                             );
+    }
+
+    @Override
     public BigDecimal getAccountBalance(final UUID accountId, final TenantContext context) {
         final BigDecimal result = dao.getAccountBalance(accountId, internalCallContextFactory.createInternalTenantContext(accountId, context));
         return result == null ? BigDecimal.ZERO : 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 754b74d..441eba2 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
@@ -17,6 +17,7 @@
 package com.ning.billing.invoice.dao;
 
 import java.math.BigDecimal;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -45,6 +46,8 @@ import com.ning.billing.invoice.notification.NextBillingDatePoster;
 import com.ning.billing.util.cache.CacheControllerDispatcher;
 import com.ning.billing.util.callcontext.InternalCallContextFactory;
 import com.ning.billing.util.dao.NonEntityDao;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
 import com.ning.billing.util.entity.dao.EntityDaoBase;
 import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
@@ -248,6 +251,21 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     }
 
     @Override
+    public Pagination<InvoiceModelDao> searchInvoices(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+        return paginationHelper.getPagination(InvoiceSqlDao.class,
+                                              new PaginationIteratorBuilder<InvoiceModelDao, Invoice, InvoiceSqlDao>() {
+                                                  @Override
+                                                  public Iterator<InvoiceModelDao> build(final InvoiceSqlDao invoiceSqlDao, final Long limit) {
+                                                      return invoiceSqlDao.searchInvoices(searchKey, offset, limit, context);
+                                                  }
+                                              },
+                                              offset,
+                                              limit,
+                                              context);
+
+    }
+
+    @Override
     public BigDecimal getAccountBalance(final UUID accountId, final InternalTenantContext context) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<BigDecimal>() {
             @Override
@@ -285,7 +303,6 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         });
     }
 
-
     @Override
     public UUID getInvoiceIdByPaymentId(final UUID paymentId, final InternalTenantContext context) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<UUID>() {
@@ -540,7 +557,6 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
                 transInvoiceItemDao.create(externalCharge, context);
 
-
                 cbaDao.doCBAComplexity(accountId, entitySqlDaoWrapperFactory, context);
 
                 // Notify the bus since the balance of the invoice changed
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
index 8f2c668..73f1185 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -31,6 +31,7 @@ import com.ning.billing.callcontext.InternalTenantContext;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.util.entity.Pagination;
 import com.ning.billing.util.entity.dao.EntityDao;
 
 public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceApiException> {
@@ -46,6 +47,8 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
 
     List<InvoiceModelDao> getInvoicesBySubscription(UUID subscriptionId, InternalTenantContext context);
 
+    public Pagination<InvoiceModelDao> searchInvoices(String searchKey, Long offset, Long limit, InternalTenantContext context);
+
     UUID getInvoiceIdByPaymentId(UUID paymentId, InternalTenantContext context);
 
     List<InvoicePaymentModelDao> getInvoicePayments(UUID paymentId, InternalTenantContext context);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
index 90099ac..a0824db 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
@@ -16,15 +16,17 @@
 
 package com.ning.billing.invoice.dao;
 
+import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
 
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.customizers.FetchSize;
 
-import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.entity.dao.EntitySqlDaoStringTemplate;
 
@@ -36,6 +38,15 @@ public interface InvoiceSqlDao extends EntitySqlDao<InvoiceModelDao, Invoice> {
                                                     @BindBean final InternalTenantContext context);
 
     @SqlQuery
+    // Magic value to force MySQL to stream from the database
+    // See http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html (ResultSet)
+    @FetchSize(Integer.MIN_VALUE)
+    public Iterator<InvoiceModelDao> searchInvoices(@Bind("searchKey") final String searchKey,
+                                                    @Bind("offset") final Long offset,
+                                                    @Bind("rowCount") final Long rowCount,
+                                                    @BindBean final InternalTenantContext context);
+
+    @SqlQuery
     UUID getInvoiceIdByPaymentId(@Bind("paymentId") final String paymentId,
                                  @BindBean final InternalTenantContext context);
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
index 2d4d2e8..358c04c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -64,7 +64,7 @@ import com.google.inject.Inject;
  * Terminology for repair scenarii:
  * <p/>
  * - A 'repaired' item is an item that was generated and that needs to be repaired because the plan changed for that subscription on that period of time
- * - The 'repair' item is the item that cancels the (to be) repaired item; the repair item amount might not match (to be) repaired item because:
+ * - The 'repair' item is the item that cancels the (to be) repaired item; the repair item amount might not match the (to be) repaired item because:
  * * the (to be) repaired item was already adjusted so we will only repair what is left
  * * in case of partial repair we only repair the part that is not used
  * - The 'reparee' item is only present on disk-- in the existing item list -- in case of full repair; in that case it represents the portion of the item that should still
@@ -179,7 +179,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                 final BigDecimal amountNegated = existingItem.getAmount() == null ? null : existingItem.getAmount().subtract(existingAdjustedPositiveAmount).negate();
                 if (amountNegated != null && amountNegated.compareTo(BigDecimal.ZERO) < 0) {
                     final RepairAdjInvoiceItem candidateRepairItem = new RepairAdjInvoiceItem(existingItem.getInvoiceId(), existingItem.getAccountId(), existingItem.getStartDate(), existingItem.getEndDate(), amountNegated, existingItem.getCurrency(), existingItem.getId());
-                    addRepairItem(existingItem, candidateRepairItem, proposedItems);
+                    addRepairsForItem(existingItem, candidateRepairItem, proposedItems);
                 }
             }
         }
@@ -192,7 +192,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
      * @param candidateRepairItem the repair item we would have if we were to repair the full period
      * @param proposedItems       the list of proposed items
      */
-    void addRepairItem(final InvoiceItem repairedItem, final RepairAdjInvoiceItem candidateRepairItem, final List<InvoiceItem> proposedItems) {
+    void addRepairsForItem(final InvoiceItem repairedItem, final RepairAdjInvoiceItem candidateRepairItem, final List<InvoiceItem> proposedItems) {
 
         int nbTotalRepaireeDays = 0;
 
@@ -222,7 +222,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
             }
         });
 
-        //Build the reparees
+        //Build the repaired
         BigDecimal totalRepairItemAmount = BigDecimal.ZERO;
         List<InvoiceItem> repairedItems = new ArrayList<InvoiceItem>();
         InvoiceItem prevReparee = null;
@@ -389,10 +389,10 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                 itemsToRemove.add(repairItem.getLinkedItemId());
 
                 final InvoiceItem repairedItem = getRepairedInvoiceItem(repairItem.getLinkedItemId(), existingItems);
-                // if this is a full repair there is no reparee so nothing to remove; if not reparees need to be removed from proposed list
-                if (!isFullRepair(repairedItem, repairItem, existingItems)) {
-                    removeProposedRepareesForPartialrepair(repairedItem, repairItem, proposedItems);
-                }
+                // Always look for reparees; if this is a full repair there may not be any reparee to remove, but
+                // if this is a partial repair with an additional invoice item adjustment, this is seen as a full repair
+                // and yet there is a reparee to remove
+                removeProposedRepareesForPartialrepair(repairedItem, repairItem, proposedItems);
             }
         }
         final Iterator<InvoiceItem> iterator = existingItems.iterator();
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
index 5fd95ce..304da69 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
@@ -36,6 +36,23 @@ getInvoicesBySubscription() ::= <<
   ;
 >>
 
+searchInvoices() ::= <<
+select SQL_CALC_FOUND_ROWS
+<allTableFields("t.")>
+from <tableName()> t
+where 1 = 1
+and (
+     <idField("t.")> = :searchKey
+  or <recordIdField("t.")> = :searchKey
+  or t.account_id = :searchKey
+  or t.currency = :searchKey
+)
+<AND_CHECK_TENANT("t.")>
+order by <recordIdField("t.")> ASC
+limit :offset, :rowCount
+;
+>>
+
 getInvoiceIdByPaymentId() ::= <<
   SELECT i.id
     FROM <tableName()> i, invoice_payments ip
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index 5006858..6ce801b 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -156,6 +156,21 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     }
 
     @Override
+    public Pagination<InvoiceModelDao> searchInvoices(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+        final List<InvoiceModelDao> results = new LinkedList<InvoiceModelDao>();
+        for (final InvoiceModelDao invoice : getAll(context)) {
+            if (invoice.getId().toString().equals(searchKey) ||
+                invoice.getAccountId().toString().equals(searchKey) ||
+                invoice.getInvoiceNumber().toString().equals(searchKey) ||
+                invoice.getCurrency().toString().equals(searchKey)) {
+                results.add(invoice);
+            }
+        }
+
+        return DefaultPagination.<InvoiceModelDao>build(offset, limit, results);
+    }
+
+    @Override
     public void test(final InternalTenantContext context) {
     }
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java b/invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java
index 34c48c7..4c43c7c 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java
@@ -48,9 +48,9 @@ public class DefaultInvoiceGeneratorWithSwitchRepairLogic extends DefaultInvoice
     }
 
     @Override
-    void addRepairItem(final InvoiceItem repairedItem, final RepairAdjInvoiceItem candidateRepairItem, final List<InvoiceItem> proposedItems) {
+    void addRepairsForItem(final InvoiceItem repairedItem, final RepairAdjInvoiceItem candidateRepairItem, final List<InvoiceItem> proposedItems) {
         if (repairtLogic == REPAIR_INVOICE_LOGIC.PARTIAL_REPAIR) {
-            super.addRepairItem(repairedItem, candidateRepairItem, proposedItems);
+            super.addRepairsForItem(repairedItem, candidateRepairItem, proposedItems);
         } else {
             proposedItems.add(candidateRepairItem);
             return;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java
index 3a5620d..45f2647 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java
@@ -54,7 +54,7 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB 
         this.defaultInvoiceGenerator = (DefaultInvoiceGenerator) generator;
     }
 
-    /*********************************************  addRepairItem logic ********************************/
+    /*********************************************  addRepairsForItem logic ********************************/
 
     //                 repairedItem
     // |-----------------------------------------------|
@@ -82,7 +82,7 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB 
         final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startProposed2, endDate, BigDecimal.TEN, rate, currency);
         proposed.add(proposed2);
 
-        defaultInvoiceGenerator.addRepairItem(repairedItem, candidateRepairItem, proposed);
+        defaultInvoiceGenerator.addRepairsForItem(repairedItem, candidateRepairItem, proposed);
 
         assertEquals(proposed.size(), 1);
         assertEquals(proposed.get(0).getStartDate(), endDateProposed1);
@@ -115,7 +115,7 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB 
         final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDateProposed1, BigDecimal.TEN, rate, currency);
         proposed.add(proposed1);
 
-        defaultInvoiceGenerator.addRepairItem(repairedItem, candidateRepairItem, proposed);
+        defaultInvoiceGenerator.addRepairsForItem(repairedItem, candidateRepairItem, proposed);
 
         assertEquals(proposed.size(), 1);
         assertEquals(proposed.get(0).getStartDate(), endDateProposed1);
@@ -149,7 +149,7 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB 
         final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDateProposed1, endDate, BigDecimal.TEN, rate, currency);
         proposed.add(proposed1);
 
-        defaultInvoiceGenerator.addRepairItem(repairedItem, candidateRepairItem, proposed);
+        defaultInvoiceGenerator.addRepairsForItem(repairedItem, candidateRepairItem, proposed);
 
         assertEquals(proposed.size(), 1);
         assertEquals(proposed.get(0).getStartDate(), startDate);
@@ -189,7 +189,7 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB 
         final InvoiceItem proposed3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDateProposed3, endDate, BigDecimal.TEN, rate, currency);
         proposed.add(proposed3);
 
-        defaultInvoiceGenerator.addRepairItem(repairedItem, candidateRepairItem, proposed);
+        defaultInvoiceGenerator.addRepairsForItem(repairedItem, candidateRepairItem, proposed);
 
         assertEquals(proposed.size(), 2);
         assertEquals(proposed.get(0).getStartDate(), endDateProposed1);
@@ -206,7 +206,7 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB 
     }
 
 
-    /*********************************************  addRepairItems logic ********************************/
+    /*********************************************  addRepairsForItems logic ********************************/
 
     @Test(groups = "fast")
     public void testAddRepairedItemsItemsRecurringPrice() {

jaxrs/pom.xml 2(+1 -1)

diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index 9e39912..8304c93 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-jaxrs</artifactId>
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
index 6d0664a..bc833cd 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
@@ -17,13 +17,17 @@
 package com.ning.billing.jaxrs.json;
 
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+
 import org.joda.time.DateTimeZone;
 
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.audit.AccountAuditLogs;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -54,8 +58,8 @@ public class AccountJson extends JsonBase {
     private final Boolean isMigrated;
     private final Boolean isNotifiedForInvoices;
 
-    public AccountJson(final Account account, final BigDecimal accountBalance, final BigDecimal accountCBA) {
-        super(null);
+    public AccountJson(final Account account, final BigDecimal accountBalance, final BigDecimal accountCBA, @Nullable final AccountAuditLogs accountAuditLogs) {
+        super(toAuditLogJson(accountAuditLogs == null ? null : accountAuditLogs.getAuditLogsForAccount()));
         this.accountCBA = accountCBA;
         this.accountBalance = accountBalance;
         this.accountId = account.getId().toString();
@@ -102,8 +106,9 @@ public class AccountJson extends JsonBase {
                        @JsonProperty("isMigrated") final Boolean isMigrated,
                        @JsonProperty("isNotifiedForInvoices") final Boolean isNotifiedForInvoices,
                        @JsonProperty("accountBalance") final BigDecimal accountBalance,
-                       @JsonProperty("accountCBA") final BigDecimal accountCBA) {
-        super(null);
+                       @JsonProperty("accountCBA") final BigDecimal accountCBA,
+                       @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
+        super(auditLogs);
         this.accountBalance = accountBalance;
         this.externalKey = externalKey;
         this.accountId = accountId;
@@ -236,7 +241,7 @@ public class AccountJson extends JsonBase {
             }
         };
     }
-    
+
     public BigDecimal getAccountBalance() {
         return accountBalance;
     }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
index 21507b8..09e326e 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
@@ -90,7 +90,7 @@ public class AccountTimelineJson {
     public AccountTimelineJson(final Account account, final List<Invoice> invoices, final List<Payment> payments,
                                final List<SubscriptionBundle> bundles, final Multimap<UUID, Refund> refundsByPayment,
                                final Multimap<UUID, InvoicePayment> chargebacksByPayment, final AccountAuditLogs accountAuditLogs) {
-        this.account = new AccountJson(account, null, null);
+        this.account = new AccountJson(account, null, null, accountAuditLogs);
         this.bundles = new LinkedList<BundleJson>();
         for (final SubscriptionBundle bundle : bundles) {
             final List<AuditLog> bundleAuditLogs = accountAuditLogs.getAuditLogsForBundle(bundle.getId());
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentMethodJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentMethodJson.java
index 6a44dc1..8c45ff1 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentMethodJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentMethodJson.java
@@ -21,12 +21,15 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+
 import org.joda.time.DateTime;
 
 import com.ning.billing.account.api.Account;
 import com.ning.billing.payment.api.PaymentMethod;
 import com.ning.billing.payment.api.PaymentMethodKVInfo;
 import com.ning.billing.payment.api.PaymentMethodPlugin;
+import com.ning.billing.util.audit.AccountAuditLogs;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -46,7 +49,9 @@ public class PaymentMethodJson extends JsonBase {
                              @JsonProperty("accountId") final String accountId,
                              @JsonProperty("isDefault") final Boolean isDefault,
                              @JsonProperty("pluginName") final String pluginName,
-                             @JsonProperty("pluginInfo") final PaymentMethodPluginDetailJson pluginInfo) {
+                             @JsonProperty("pluginInfo") final PaymentMethodPluginDetailJson pluginInfo,
+                             @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
+        super(auditLogs);
         this.paymentMethodId = paymentMethodId;
         this.accountId = accountId;
         this.isDefault = isDefault;
@@ -54,7 +59,7 @@ public class PaymentMethodJson extends JsonBase {
         this.pluginInfo = pluginInfo;
     }
 
-    public static PaymentMethodJson toPaymentMethodJson(final Account account, final PaymentMethod in) {
+    public static PaymentMethodJson toPaymentMethodJson(final Account account, final PaymentMethod in, @Nullable final AccountAuditLogs accountAuditLogs) {
         final boolean isDefault = account.getPaymentMethodId() != null && account.getPaymentMethodId().equals(in.getId());
         final PaymentMethodPlugin pluginDetail = in.getPluginDetail();
         PaymentMethodPluginDetailJson pluginDetailJson = null;
@@ -84,7 +89,8 @@ public class PaymentMethodJson extends JsonBase {
                                                                  pluginDetail.getCountry(),
                                                                  properties);
         }
-        return new PaymentMethodJson(in.getId().toString(), account.getId().toString(), isDefault, in.getPluginName(), pluginDetailJson);
+        return new PaymentMethodJson(in.getId().toString(), account.getId().toString(), isDefault, in.getPluginName(),
+                                     pluginDetailJson, toAuditLogJson(accountAuditLogs == null ? null : accountAuditLogs.getAuditLogsForPaymentMethod(in.getId())));
     }
 
     public PaymentMethod toPaymentMethod(final String accountId) {
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
index ae53882..607fa3b 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -80,6 +80,7 @@ import com.ning.billing.payment.api.PaymentApi;
 import com.ning.billing.payment.api.PaymentApiException;
 import com.ning.billing.payment.api.PaymentMethod;
 import com.ning.billing.payment.api.Refund;
+import com.ning.billing.util.api.AuditLevel;
 import com.ning.billing.util.api.AuditUserApi;
 import com.ning.billing.util.api.CustomFieldApiException;
 import com.ning.billing.util.api.CustomFieldUserApi;
@@ -143,10 +144,12 @@ public class AccountResource extends JaxRsResourceBase {
     public Response getAccount(@PathParam("accountId") final String accountId,
                                @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance,
                                @QueryParam(QUERY_ACCOUNT_WITH_BALANCE_AND_CBA) @DefaultValue("false") final Boolean accountWithBalanceAndCBA,
+                               @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
                                @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException {
         final TenantContext tenantContext = context.createContext(request);
         final Account account = accountUserApi.getAccountById(UUID.fromString(accountId), tenantContext);
-        final AccountJson accountJson = getAccount(account, accountWithBalance, accountWithBalanceAndCBA, tenantContext);
+        final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(account.getId(), auditMode.getLevel(), tenantContext);
+        final AccountJson accountJson = getAccount(account, accountWithBalance, accountWithBalanceAndCBA, accountAuditLogs, tenantContext);
         return Response.status(Status.OK).entity(accountJson).build();
     }
 
@@ -157,16 +160,19 @@ public class AccountResource extends JaxRsResourceBase {
                                 @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
                                 @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance,
                                 @QueryParam(QUERY_ACCOUNT_WITH_BALANCE_AND_CBA) @DefaultValue("false") final Boolean accountWithBalanceAndCBA,
+                                @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
                                 @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException {
         final TenantContext tenantContext = context.createContext(request);
         final Pagination<Account> accounts = accountUserApi.getAccounts(offset, limit, tenantContext);
         final URI nextPageUri = uriBuilder.nextPage(AccountResource.class, "getAccounts", accounts.getNextOffset(), limit, ImmutableMap.<String, String>of(QUERY_ACCOUNT_WITH_BALANCE, accountWithBalance.toString(),
-                                                                                                                                                           QUERY_ACCOUNT_WITH_BALANCE_AND_CBA, accountWithBalanceAndCBA.toString()));
+                                                                                                                                                           QUERY_ACCOUNT_WITH_BALANCE_AND_CBA, accountWithBalanceAndCBA.toString(),
+                                                                                                                                                           QUERY_AUDIT, auditMode.getLevel().toString()));
         return buildStreamingPaginationResponse(accounts,
                                                 new Function<Account, AccountJson>() {
                                                     @Override
                                                     public AccountJson apply(final Account account) {
-                                                        return getAccount(account, accountWithBalance, accountWithBalanceAndCBA, tenantContext);
+                                                        final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(account.getId(), auditMode.getLevel(), tenantContext);
+                                                        return getAccount(account, accountWithBalance, accountWithBalanceAndCBA, accountAuditLogs, tenantContext);
                                                     }
                                                 },
                                                 nextPageUri);
@@ -180,17 +186,20 @@ public class AccountResource extends JaxRsResourceBase {
                                    @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
                                    @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance,
                                    @QueryParam(QUERY_ACCOUNT_WITH_BALANCE_AND_CBA) @DefaultValue("false") final Boolean accountWithBalanceAndCBA,
+                                   @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
                                    @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException {
         final TenantContext tenantContext = context.createContext(request);
         final Pagination<Account> accounts = accountUserApi.searchAccounts(searchKey, offset, limit, tenantContext);
         final URI nextPageUri = uriBuilder.nextPage(AccountResource.class, "searchAccounts", accounts.getNextOffset(), limit, ImmutableMap.<String, String>of("searchKey", searchKey,
                                                                                                                                                               QUERY_ACCOUNT_WITH_BALANCE, accountWithBalance.toString(),
-                                                                                                                                                              QUERY_ACCOUNT_WITH_BALANCE_AND_CBA, accountWithBalanceAndCBA.toString()));
+                                                                                                                                                              QUERY_ACCOUNT_WITH_BALANCE_AND_CBA, accountWithBalanceAndCBA.toString(),
+                                                                                                                                                              QUERY_AUDIT, auditMode.getLevel().toString()));
         return buildStreamingPaginationResponse(accounts,
                                                 new Function<Account, AccountJson>() {
                                                     @Override
                                                     public AccountJson apply(final Account account) {
-                                                        return getAccount(account, accountWithBalance, accountWithBalanceAndCBA, tenantContext);
+                                                        final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(account.getId(), auditMode.getLevel(), tenantContext);
+                                                        return getAccount(account, accountWithBalance, accountWithBalanceAndCBA, accountAuditLogs, tenantContext);
                                                     }
                                                 },
                                                 nextPageUri);
@@ -225,23 +234,26 @@ public class AccountResource extends JaxRsResourceBase {
     public Response getAccountByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
                                     @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance,
                                     @QueryParam(QUERY_ACCOUNT_WITH_BALANCE_AND_CBA) @DefaultValue("false") final Boolean accountWithBalanceAndCBA,
+                                    @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
                                     @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException {
         final TenantContext tenantContext = context.createContext(request);
         final Account account = accountUserApi.getAccountByKey(externalKey, tenantContext);
-        final AccountJson accountJson = getAccount(account, accountWithBalance, accountWithBalanceAndCBA, tenantContext);
+        final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(account.getId(), auditMode.getLevel(), tenantContext);
+        final AccountJson accountJson = getAccount(account, accountWithBalance, accountWithBalanceAndCBA, accountAuditLogs, tenantContext);
         return Response.status(Status.OK).entity(accountJson).build();
     }
 
-    private AccountJson getAccount(final Account account, final Boolean accountWithBalance, final Boolean accountWithBalanceAndCBA, final TenantContext tenantContext) {
+    private AccountJson getAccount(final Account account, final Boolean accountWithBalance, final Boolean accountWithBalanceAndCBA,
+                                   final AccountAuditLogs auditLogs, final TenantContext tenantContext) {
         if (accountWithBalanceAndCBA) {
             final BigDecimal accountBalance = invoiceApi.getAccountBalance(account.getId(), tenantContext);
             final BigDecimal accountCBA = invoiceApi.getAccountCBA(account.getId(), tenantContext);
-            return new AccountJson(account, accountBalance, accountCBA);
+            return new AccountJson(account, accountBalance, accountCBA, auditLogs);
         } else if (accountWithBalance) {
             final BigDecimal accountBalance = invoiceApi.getAccountBalance(account.getId(), tenantContext);
-            return new AccountJson(account, accountBalance, null);
+            return new AccountJson(account, accountBalance, null, auditLogs);
         } else {
-            return new AccountJson(account, null, null);
+            return new AccountJson(account, null, null, auditLogs);
         }
     }
 
@@ -272,7 +284,7 @@ public class AccountResource extends JaxRsResourceBase {
         final AccountData data = json.toAccountData();
         final UUID uuid = UUID.fromString(accountId);
         accountUserApi.updateAccount(uuid, data, context.createContext(createdBy, reason, comment, request));
-        return getAccount(accountId, false, false, request);
+        return getAccount(accountId, false, false, new AuditMode(AuditLevel.NONE.toString()), request);
     }
 
     // Not supported
@@ -506,15 +518,17 @@ public class AccountResource extends JaxRsResourceBase {
     @Produces(APPLICATION_JSON)
     public Response getPaymentMethods(@PathParam("accountId") final String accountId,
                                       @QueryParam(QUERY_PAYMENT_METHOD_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
+                                      @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
         final TenantContext tenantContext = context.createContext(request);
 
         final Account account = accountUserApi.getAccountById(UUID.fromString(accountId), tenantContext);
         final List<PaymentMethod> methods = paymentApi.getPaymentMethods(account, withPluginInfo, tenantContext);
+        final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(account.getId(), auditMode.getLevel(), tenantContext);
         final List<PaymentMethodJson> json = new ArrayList<PaymentMethodJson>(Collections2.transform(methods, new Function<PaymentMethod, PaymentMethodJson>() {
             @Override
             public PaymentMethodJson apply(final PaymentMethod input) {
-                return PaymentMethodJson.toPaymentMethodJson(account, input);
+                return PaymentMethodJson.toPaymentMethodJson(account, input, accountAuditLogs);
             }
         }));
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
index ef4a230..413559b 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
@@ -16,8 +16,12 @@
 
 package com.ning.billing.jaxrs.resources;
 
+import java.net.URI;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
@@ -57,9 +61,13 @@ import com.ning.billing.util.api.CustomFieldUserApi;
 import com.ning.billing.util.api.TagApiException;
 import com.ning.billing.util.api.TagDefinitionApiException;
 import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.audit.AccountAuditLogs;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.Pagination;
 
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
 import com.google.inject.Inject;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
@@ -107,6 +115,58 @@ public class BundleResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(json).build();
     }
 
+    @GET
+    @Path("/" + PAGINATION)
+    @Produces(APPLICATION_JSON)
+    public Response getBundles(@QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
+                               @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
+                               @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                               @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException {
+        final TenantContext tenantContext = context.createContext(request);
+        final Pagination<SubscriptionBundle> bundles = subscriptionApi.getSubscriptionBundles(offset, limit, tenantContext);
+        final URI nextPageUri = uriBuilder.nextPage(BundleResource.class, "getBundles", bundles.getNextOffset(), limit, ImmutableMap.<String, String>of(QUERY_AUDIT, auditMode.getLevel().toString()));
+        final AtomicReference<Map<UUID, AccountAuditLogs>> accountsAuditLogs = new AtomicReference<Map<UUID, AccountAuditLogs>>(new HashMap<UUID, AccountAuditLogs>());
+        return buildStreamingPaginationResponse(bundles,
+                                                new Function<SubscriptionBundle, BundleJson>() {
+                                                    @Override
+                                                    public BundleJson apply(final SubscriptionBundle bundle) {
+                                                        // Cache audit logs per account
+                                                        if (accountsAuditLogs.get().get(bundle.getAccountId()) == null) {
+                                                            accountsAuditLogs.get().put(bundle.getAccountId(), auditUserApi.getAccountAuditLogs(bundle.getAccountId(), auditMode.getLevel(), tenantContext));
+                                                        }
+                                                        return new BundleJson(bundle, accountsAuditLogs.get().get(bundle.getAccountId()));
+                                                    }
+                                                },
+                                                nextPageUri);
+    }
+
+    @GET
+    @Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response searchBundles(@PathParam("searchKey") final String searchKey,
+                                  @QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
+                                  @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
+                                  @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                  @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException {
+        final TenantContext tenantContext = context.createContext(request);
+        final Pagination<SubscriptionBundle> bundles = subscriptionApi.searchSubscriptionBundles(searchKey, offset, limit, tenantContext);
+        final URI nextPageUri = uriBuilder.nextPage(BundleResource.class, "searchBundles", bundles.getNextOffset(), limit, ImmutableMap.<String, String>of("searchKey", searchKey,
+                                                                                                                                                           QUERY_AUDIT, auditMode.getLevel().toString()));
+        final AtomicReference<Map<UUID, AccountAuditLogs>> accountsAuditLogs = new AtomicReference<Map<UUID, AccountAuditLogs>>(new HashMap<UUID, AccountAuditLogs>());
+        return buildStreamingPaginationResponse(bundles,
+                                                new Function<SubscriptionBundle, BundleJson>() {
+                                                    @Override
+                                                    public BundleJson apply(final SubscriptionBundle bundle) {
+                                                        // Cache audit logs per account
+                                                        if (accountsAuditLogs.get().get(bundle.getAccountId()) == null) {
+                                                            accountsAuditLogs.get().put(bundle.getAccountId(), auditUserApi.getAccountAuditLogs(bundle.getAccountId(), auditMode.getLevel(), tenantContext));
+                                                        }
+                                                        return new BundleJson(bundle, accountsAuditLogs.get().get(bundle.getAccountId()));
+                                                    }
+                                                },
+                                                nextPageUri);
+    }
+
     @PUT
     @Path("/{bundleId:" + UUID_PATTERN + "}/" + PAUSE)
     @Consumes(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
index f034473..ed14584 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -51,6 +51,7 @@ import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.api.SubscriptionApiException;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItem;
@@ -186,6 +187,35 @@ public class InvoiceResource extends JaxRsResourceBase {
                                                 nextPageUri);
     }
 
+    @GET
+    @Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response searchInvoices(@PathParam("searchKey") final String searchKey,
+                                   @QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
+                                   @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
+                                   @QueryParam(QUERY_INVOICE_WITH_ITEMS) @DefaultValue("false") final Boolean withItems,
+                                   @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                   @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException {
+        final TenantContext tenantContext = context.createContext(request);
+        final Pagination<Invoice> invoices = invoiceApi.searchInvoices(searchKey, offset, limit, tenantContext);
+        final URI nextPageUri = uriBuilder.nextPage(InvoiceResource.class, "searchInvoices", invoices.getNextOffset(), limit, ImmutableMap.<String, String>of("searchKey", searchKey,
+                                                                                                                                                              QUERY_INVOICE_WITH_ITEMS, withItems.toString(),
+                                                                                                                                                              QUERY_AUDIT, auditMode.getLevel().toString()));
+        final AtomicReference<Map<UUID, AccountAuditLogs>> accountsAuditLogs = new AtomicReference<Map<UUID, AccountAuditLogs>>(new HashMap<UUID, AccountAuditLogs>());
+        return buildStreamingPaginationResponse(invoices,
+                                                new Function<Invoice, InvoiceJson>() {
+                                                    @Override
+                                                    public InvoiceJson apply(final Invoice invoice) {
+                                                        // Cache audit logs per account
+                                                        if (accountsAuditLogs.get().get(invoice.getAccountId()) == null) {
+                                                            accountsAuditLogs.get().put(invoice.getAccountId(), auditUserApi.getAccountAuditLogs(invoice.getAccountId(), auditMode.getLevel(), tenantContext));
+                                                        }
+                                                        return new InvoiceJson(invoice, withItems, accountsAuditLogs.get().get(invoice.getAccountId()));
+                                                    }
+                                                },
+                                                nextPageUri);
+    }
+
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentMethodResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentMethodResource.java
index fadb479..8c2165a 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentMethodResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentMethodResource.java
@@ -85,12 +85,14 @@ public class PaymentMethodResource extends JaxRsResourceBase {
     @Produces(APPLICATION_JSON)
     public Response getPaymentMethod(@PathParam("paymentMethodId") final String paymentMethodId,
                                      @QueryParam(QUERY_PAYMENT_METHOD_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
+                                     @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
                                      @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
         final TenantContext tenantContext = context.createContext(request);
 
         final PaymentMethod paymentMethod = paymentApi.getPaymentMethodById(UUID.fromString(paymentMethodId), false, withPluginInfo, tenantContext);
         final Account account = accountUserApi.getAccountById(paymentMethod.getAccountId(), tenantContext);
-        final PaymentMethodJson json = PaymentMethodJson.toPaymentMethodJson(account, paymentMethod);
+        final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(paymentMethod.getAccountId(), auditMode.getLevel(), tenantContext);
+        final PaymentMethodJson json = PaymentMethodJson.toPaymentMethodJson(account, paymentMethod, accountAuditLogs);
 
         return Response.status(Status.OK).entity(json).build();
     }
@@ -138,8 +140,7 @@ public class PaymentMethodResource extends JaxRsResourceBase {
                                                             }
                                                         }
 
-                                                        // TODO populate audit logs
-                                                        return PaymentMethodJson.toPaymentMethodJson(accounts.get(paymentMethod.getAccountId()), paymentMethod);
+                                                        return PaymentMethodJson.toPaymentMethodJson(accounts.get(paymentMethod.getAccountId()), paymentMethod, accountsAuditLogs.get().get(paymentMethod.getAccountId()));
                                                     }
                                                 },
                                                 nextPageUri);
@@ -191,8 +192,7 @@ public class PaymentMethodResource extends JaxRsResourceBase {
                                                             }
                                                         }
 
-                                                        // TODO populate audit logs
-                                                        return PaymentMethodJson.toPaymentMethodJson(accounts.get(paymentMethod.getAccountId()), paymentMethod);
+                                                        return PaymentMethodJson.toPaymentMethodJson(accounts.get(paymentMethod.getAccountId()), paymentMethod, accountsAuditLogs.get().get(paymentMethod.getAccountId()));
                                                     }
                                                 },
                                                 nextPageUri);
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
index 7f14aee..124b563 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
@@ -65,6 +66,7 @@ import com.ning.billing.util.api.CustomFieldUserApi;
 import com.ning.billing.util.api.TagApiException;
 import com.ning.billing.util.api.TagDefinitionApiException;
 import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.audit.AccountAuditLogs;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.TenantContext;
 import com.ning.billing.util.entity.Pagination;
@@ -140,25 +142,30 @@ public class PaymentResource extends JaxRsResourceBase {
     public Response getPayments(@QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
                                 @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
                                 @QueryParam(QUERY_PAYMENT_PLUGIN_NAME) final String pluginName,
+                                @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
                                 @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
         final TenantContext tenantContext = context.createContext(request);
 
         final Pagination<Payment> payments;
-        final Map<String, String> nextUriParams = new HashMap<String, String>();
         if (Strings.isNullOrEmpty(pluginName)) {
             payments = paymentApi.getPayments(offset, limit, tenantContext);
         } else {
             payments = paymentApi.getPayments(offset, limit, pluginName, tenantContext);
-            nextUriParams.put(QUERY_PAYMENT_PLUGIN_NAME, pluginName);
         }
 
-        final URI nextPageUri = uriBuilder.nextPage(PaymentResource.class, "getPayments", payments.getNextOffset(), limit, nextUriParams);
+        final URI nextPageUri = uriBuilder.nextPage(PaymentResource.class, "getPayments", payments.getNextOffset(), limit, ImmutableMap.<String, String>of(QUERY_PAYMENT_METHOD_PLUGIN_NAME, Strings.nullToEmpty(pluginName),
+                                                                                                                                                           QUERY_AUDIT, auditMode.getLevel().toString()));
+        final AtomicReference<Map<UUID, AccountAuditLogs>> accountsAuditLogs = new AtomicReference<Map<UUID, AccountAuditLogs>>(new HashMap<UUID, AccountAuditLogs>());
 
         return buildStreamingPaginationResponse(payments,
                                                 new Function<Payment, PaymentJson>() {
                                                     @Override
                                                     public PaymentJson apply(final Payment payment) {
-                                                        return new PaymentJson(payment, null);
+                                                        // Cache audit logs per account
+                                                        if (accountsAuditLogs.get().get(payment.getAccountId()) == null) {
+                                                            accountsAuditLogs.get().put(payment.getAccountId(), auditUserApi.getAccountAuditLogs(payment.getAccountId(), auditMode.getLevel(), tenantContext));
+                                                        }
+                                                        return new PaymentJson(payment, accountsAuditLogs.get().get(payment.getAccountId()).getAuditLogsForPayment(payment.getId()));
                                                     }
                                                 },
                                                 nextPageUri);
@@ -171,6 +178,7 @@ public class PaymentResource extends JaxRsResourceBase {
                                    @QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
                                    @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
                                    @QueryParam(QUERY_PAYMENT_PLUGIN_NAME) final String pluginName,
+                                   @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
                                    @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
         final TenantContext tenantContext = context.createContext(request);
 
@@ -182,13 +190,20 @@ public class PaymentResource extends JaxRsResourceBase {
             payments = paymentApi.searchPayments(searchKey, offset, limit, pluginName, tenantContext);
         }
 
-        final URI nextPageUri = uriBuilder.nextPage(PaymentResource.class, "searchPayments", payments.getNextOffset(), limit, ImmutableMap.<String, String>of());
+        final URI nextPageUri = uriBuilder.nextPage(PaymentResource.class, "searchPayments", payments.getNextOffset(), limit, ImmutableMap.<String, String>of("searchKey", searchKey,
+                                                                                                                                                              QUERY_PAYMENT_METHOD_PLUGIN_NAME, Strings.nullToEmpty(pluginName),
+                                                                                                                                                              QUERY_AUDIT, auditMode.getLevel().toString()));
+        final AtomicReference<Map<UUID, AccountAuditLogs>> accountsAuditLogs = new AtomicReference<Map<UUID, AccountAuditLogs>>(new HashMap<UUID, AccountAuditLogs>());
 
         return buildStreamingPaginationResponse(payments,
                                                 new Function<Payment, PaymentJson>() {
                                                     @Override
                                                     public PaymentJson apply(final Payment payment) {
-                                                        return new PaymentJson(payment, null);
+                                                        // Cache audit logs per account
+                                                        if (accountsAuditLogs.get().get(payment.getAccountId()) == null) {
+                                                            accountsAuditLogs.get().put(payment.getAccountId(), auditUserApi.getAccountAuditLogs(payment.getAccountId(), auditMode.getLevel(), tenantContext));
+                                                        }
+                                                        return new PaymentJson(payment, accountsAuditLogs.get().get(payment.getAccountId()).getAuditLogsForPayment(payment.getId()));
                                                     }
                                                 },
                                                 nextPageUri);
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountJson.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountJson.java
index 9ab3e24..4e6a0ca 100644
--- a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountJson.java
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountJson.java
@@ -55,7 +55,7 @@ public class TestAccountJson extends JaxrsTestSuiteNoDB {
         final AccountJson accountJson = new AccountJson(accountId, name, length, externalKey,
                                                         email, billCycleDayLocal, currency, paymentMethodId,
                                                         timeZone, address1, address2, postalCode, company, city, state,
-                                                        country, locale, phone, isMigrated, isNotifiedForInvoice, null, null);
+                                                        country, locale, phone, isMigrated, isNotifiedForInvoice, null, null, null);
         Assert.assertEquals(accountJson.getAccountId(), accountId);
         Assert.assertEquals(accountJson.getName(), name);
         Assert.assertEquals(accountJson.getFirstNameLength(), length);
@@ -107,7 +107,7 @@ public class TestAccountJson extends JaxrsTestSuiteNoDB {
         accountBuilder.timeZone(DateTimeZone.UTC);
         final Account account = accountBuilder.build();
 
-        final AccountJson accountJson = new AccountJson(account, null, null);
+        final AccountJson accountJson = new AccountJson(account, null, null, null);
         Assert.assertEquals(accountJson.getAddress1(), account.getAddress1());
         Assert.assertEquals(accountJson.getAddress2(), account.getAddress2());
         Assert.assertEquals(accountJson.getBillCycleDayLocal(), (Integer) bcd);

junction/pom.xml 2(+1 -1)

diff --git a/junction/pom.xml b/junction/pom.xml
index a3910c1..0065f5c 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-junction</artifactId>

NEWS 12(+12 -0)

diff --git a/NEWS b/NEWS
index 5268436..c7dbbbe 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,15 @@
+0.8.12
+    Implement bundles pagination and search APIs
+    Implement invoices search API
+    https://github.com/killbill/killbill/issues/154
+    Update killbill-oss-parent to 0.5.23
+
+0.8.11
+    [SECURITY] Fix SQL injection in search APIs
+    Fix bug when retrieving refund information from plugins
+    Add system tag definitions support to tag search
+    https://github.com/killbill/killbill/issues/100
+
 0.8.10
     Upgrade JRuby and add support for OpenSSL in plugins
     payment: pagination bugfix

osgi/pom.xml 2(+1 -1)

diff --git a/osgi/pom.xml b/osgi/pom.xml
index 43233d1..aeb0ea8 100644
--- a/osgi/pom.xml
+++ b/osgi/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi</artifactId>
diff --git a/osgi-bundles/bundles/jruby/pom.xml b/osgi-bundles/bundles/jruby/pom.xml
index 75747c6..60094c2 100644
--- a/osgi-bundles/bundles/jruby/pom.xml
+++ b/osgi-bundles/bundles/jruby/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill-osgi-bundles</artifactId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi-bundles-jruby</artifactId>
diff --git a/osgi-bundles/bundles/logger/pom.xml b/osgi-bundles/bundles/logger/pom.xml
index 643cef5..cdf2599 100644
--- a/osgi-bundles/bundles/logger/pom.xml
+++ b/osgi-bundles/bundles/logger/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill-osgi-bundles</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi-bundles-logger</artifactId>
diff --git a/osgi-bundles/bundles/logger/src/main/java/com/ning/billing/osgi/bundles/logger/Activator.java b/osgi-bundles/bundles/logger/src/main/java/com/ning/billing/osgi/bundles/logger/Activator.java
index 7ab3d97..8dbd9b4 100644
--- a/osgi-bundles/bundles/logger/src/main/java/com/ning/billing/osgi/bundles/logger/Activator.java
+++ b/osgi-bundles/bundles/logger/src/main/java/com/ning/billing/osgi/bundles/logger/Activator.java
@@ -28,7 +28,6 @@ import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.log.LogListener;
 import org.osgi.service.log.LogReaderService;
-import org.osgi.util.tracker.ServiceTracker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -56,8 +55,7 @@ public class Activator implements BundleActivator {
                 if (event.getType() == ServiceEvent.REGISTERED) {
                     registerLogReaderService(logReaderService);
                 } else if (event.getType() == ServiceEvent.UNREGISTERING) {
-                    logReaderService.removeLogListener(killbillLogListener);
-                    logReaderServices.remove(logReaderService);
+                    unregisterLogReaderService(logReaderService);
                 }
             }
         }
@@ -65,25 +63,18 @@ public class Activator implements BundleActivator {
 
     @Override
     public void start(final BundleContext context) throws Exception {
-        // Get a list of all the registered LogReaderService, and add the killbill listener
-        final ServiceTracker logReaderTracker = new ServiceTracker(context, LogReaderService.class.getName(), null);
-        logReaderTracker.open();
-        final Object[] readers = logReaderTracker.getServices();
-        if (readers != null) {
-            for (final Object reader : readers) {
-                final LogReaderService service = (LogReaderService) reader;
-                registerLogReaderService(service);
-            }
-        }
-        logReaderTracker.close();
-
-        // Add the ServiceListener
         final String filter = "(objectclass=" + LogReaderService.class.getName() + ")";
         try {
             context.addServiceListener(logReaderServiceListener, filter);
-        } catch (InvalidSyntaxException e) {
+        } catch (final InvalidSyntaxException e) {
             logger.warn("Unable to register the killbill LogReaderService listener", e);
         }
+
+        // If the LogReaderService was already registered, manually construct a REGISTERED ServiceEvent
+        final ServiceReference[] serviceReferences = context.getServiceReferences((String) null, filter);
+        for (int i = 0; serviceReferences != null && i < serviceReferences.length; i++) {
+            logReaderServiceListener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, serviceReferences[i]));
+        }
     }
 
     @Override
@@ -96,7 +87,14 @@ public class Activator implements BundleActivator {
     }
 
     private void registerLogReaderService(final LogReaderService service) {
+        logger.info("Registering the killbill LogReaderService listener");
         logReaderServices.add(service);
         service.addLogListener(killbillLogListener);
     }
+
+    private void unregisterLogReaderService(final LogReaderService logReaderService) {
+        logger.info("Unregistering the killbill LogReaderService listener");
+        logReaderService.removeLogListener(killbillLogListener);
+        logReaderServices.remove(logReaderService);
+    }
 }
diff --git a/osgi-bundles/bundles/pom.xml b/osgi-bundles/bundles/pom.xml
index b0d837c..80bae86 100644
--- a/osgi-bundles/bundles/pom.xml
+++ b/osgi-bundles/bundles/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill-osgi-all-bundles</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi-bundles</artifactId>
diff --git a/osgi-bundles/bundles/webconsolebranding/pom.xml b/osgi-bundles/bundles/webconsolebranding/pom.xml
index 9c67b6c..72a8cf7 100644
--- a/osgi-bundles/bundles/webconsolebranding/pom.xml
+++ b/osgi-bundles/bundles/webconsolebranding/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill-osgi-bundles</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi-bundles-webconsolebranding</artifactId>
diff --git a/osgi-bundles/defaultbundles/pom.xml b/osgi-bundles/defaultbundles/pom.xml
index 1d78cbf..58872d4 100644
--- a/osgi-bundles/defaultbundles/pom.xml
+++ b/osgi-bundles/defaultbundles/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill-osgi-all-bundles</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi-bundles-defaultbundles</artifactId>
diff --git a/osgi-bundles/libs/killbill/pom.xml b/osgi-bundles/libs/killbill/pom.xml
index 94eec24..9cfec3d 100644
--- a/osgi-bundles/libs/killbill/pom.xml
+++ b/osgi-bundles/libs/killbill/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill-osgi-lib-bundles</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi-bundles-lib-killbill</artifactId>
diff --git a/osgi-bundles/libs/pom.xml b/osgi-bundles/libs/pom.xml
index 17dc35b..14b0ef0 100644
--- a/osgi-bundles/libs/pom.xml
+++ b/osgi-bundles/libs/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill-osgi-all-bundles</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi-lib-bundles</artifactId>
diff --git a/osgi-bundles/libs/slf4j-osgi/pom.xml b/osgi-bundles/libs/slf4j-osgi/pom.xml
index 6df1b8d..0079622 100644
--- a/osgi-bundles/libs/slf4j-osgi/pom.xml
+++ b/osgi-bundles/libs/slf4j-osgi/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill-osgi-lib-bundles</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi-bundles-lib-slf4j-osgi</artifactId>
diff --git a/osgi-bundles/pom.xml b/osgi-bundles/pom.xml
index 057a5cf..05d35e7 100644
--- a/osgi-bundles/pom.xml
+++ b/osgi-bundles/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi-all-bundles</artifactId>
diff --git a/osgi-bundles/tests/beatrix/pom.xml b/osgi-bundles/tests/beatrix/pom.xml
index d156ae6..fe9cfc0 100644
--- a/osgi-bundles/tests/beatrix/pom.xml
+++ b/osgi-bundles/tests/beatrix/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill-osgi-test-bundles</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi-bundles-test-beatrix</artifactId>
diff --git a/osgi-bundles/tests/payment/pom.xml b/osgi-bundles/tests/payment/pom.xml
index a3bf05b..ae7dd7b 100644
--- a/osgi-bundles/tests/payment/pom.xml
+++ b/osgi-bundles/tests/payment/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill-osgi-test-bundles</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi-bundles-test-payment</artifactId>
diff --git a/osgi-bundles/tests/pom.xml b/osgi-bundles/tests/pom.xml
index 2cdc1af..78bdb05 100644
--- a/osgi-bundles/tests/pom.xml
+++ b/osgi-bundles/tests/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill-osgi-all-bundles</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-osgi-test-bundles</artifactId>

overdue/pom.xml 2(+1 -1)

diff --git a/overdue/pom.xml b/overdue/pom.xml
index 1db8ea8..09d35df 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-overdue</artifactId>

payment/pom.xml 2(+1 -1)

diff --git a/payment/pom.xml b/payment/pom.xml
index 02f7d86..af77090 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-payment</artifactId>
diff --git a/payment/src/main/java/com/ning/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/PaymentMethodProcessor.java
index 78fecf3..09e4a78 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/PaymentMethodProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/PaymentMethodProcessor.java
@@ -181,12 +181,12 @@ public class PaymentMethodProcessor extends ProcessorBase {
                                    new Function<PaymentMethodModelDao, PaymentMethod>() {
                                        @Override
                                        public PaymentMethod apply(final PaymentMethodModelDao paymentMethodModelDao) {
-                                           final PaymentMethodPlugin paymentMethodPlugin;
+                                           PaymentMethodPlugin paymentMethodPlugin = null;
                                            try {
                                                paymentMethodPlugin = pluginApi.getPaymentMethodDetail(paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId(), tenantContext);
-                                           } catch (PaymentPluginApiException e) {
+                                           } catch (final PaymentPluginApiException e) {
                                                log.warn("Unable to find payment method id " + paymentMethodModelDao.getId() + " in plugin " + pluginName);
-                                               return null;
+                                               // We still want to return a payment method object, even though the plugin details are missing
                                            }
 
                                            return new DefaultPaymentMethod(paymentMethodModelDao, paymentMethodPlugin);
diff --git a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
index 8178f38..9a71d07 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
@@ -174,12 +174,12 @@ public class PaymentProcessor extends ProcessorBase {
                                    new Function<PaymentModelDao, Payment>() {
                                        @Override
                                        public Payment apply(final PaymentModelDao paymentModelDao) {
-                                           final PaymentInfoPlugin pluginInfo;
+                                           PaymentInfoPlugin pluginInfo = null;
                                            try {
                                                pluginInfo = pluginApi.getPaymentInfo(paymentModelDao.getAccountId(), paymentModelDao.getId(), tenantContext);
                                            } catch (final PaymentPluginApiException e) {
-                                               log.warn("Unable to find payment  id " + paymentModelDao.getId() + " in plugin " + pluginName);
-                                               return null;
+                                               log.warn("Unable to find payment id " + paymentModelDao.getId() + " in plugin " + pluginName);
+                                               // We still want to return a payment object, even though the plugin details are missing
                                            }
 
                                            return fromPaymentModelDao(paymentModelDao, pluginInfo, internalTenantContext);
diff --git a/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
index 827590f..401b0f4 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
@@ -256,18 +256,16 @@ public class RefundProcessor extends ProcessorBase {
             result = paymentDao.getRefund(refundId, context);
         }
 
-        final Account account;
-        try {
-            account = accountInternalApi.getAccountById(result.getAccountId(), context);
-        } catch (final AccountApiException e) {
-            throw new PaymentApiException(e);
+        final PaymentModelDao payment = paymentDao.getPayment(result.getPaymentId(), context);
+        if (payment == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, result.getPaymentId());
         }
 
-        final PaymentPluginApi plugin = withPluginInfo ? getPaymentProviderPlugin(account, context) : null;
+        final PaymentPluginApi plugin = withPluginInfo ? getPaymentProviderPlugin(payment.getPaymentMethodId(), context) : null;
         List<RefundInfoPlugin> refundInfoPlugins = ImmutableList.<RefundInfoPlugin>of();
         if (plugin != null) {
             try {
-                refundInfoPlugins = plugin.getRefundInfo(account.getId(), result.getPaymentId(), buildTenantContext(context));
+                refundInfoPlugins = plugin.getRefundInfo(result.getAccountId(), result.getPaymentId(), buildTenantContext(context));
             } catch (final PaymentPluginApiException e) {
                 throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_GET_REFUND_INFO, refundId, e.toString());
             }
@@ -326,15 +324,15 @@ public class RefundProcessor extends ProcessorBase {
                                    new Function<RefundModelDao, Refund>() {
                                        @Override
                                        public Refund apply(final RefundModelDao refundModelDao) {
-                                           final List<RefundInfoPlugin> refundInfoPlugins;
+                                           List<RefundInfoPlugin> refundInfoPlugins = null;
                                            try {
                                                refundInfoPlugins = pluginApi.getRefundInfo(refundModelDao.getAccountId(), refundModelDao.getId(), tenantContext);
                                            } catch (final PaymentPluginApiException e) {
                                                log.warn("Unable to find refund id " + refundModelDao.getId() + " in plugin " + pluginName);
-                                               return null;
+                                               // We still want to return a refund object, even though the plugin details are missing
                                            }
 
-                                           final RefundInfoPlugin refundInfoPlugin = findRefundInfoPlugin(refundModelDao, refundInfoPlugins);
+                                           final RefundInfoPlugin refundInfoPlugin = refundInfoPlugins == null ? null : findRefundInfoPlugin(refundModelDao, refundInfoPlugins);
                                            return new DefaultRefund(refundModelDao, refundInfoPlugin);
                                        }
                                    }
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentMethodSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentMethodSqlDao.java
index d5af8bf..3ad3b72 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentMethodSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentMethodSqlDao.java
@@ -23,7 +23,6 @@ import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-import org.skife.jdbi.v2.sqlobject.customizers.Define;
 import org.skife.jdbi.v2.sqlobject.customizers.FetchSize;
 
 import com.ning.billing.callcontext.InternalCallContext;

pom.xml 4(+2 -2)

diff --git a/pom.xml b/pom.xml
index 3d6021d..e5be8a7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,10 +19,10 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.5.21</version>
+        <version>0.5.23</version>
     </parent>
     <artifactId>killbill</artifactId>
-    <version>0.8.11-SNAPSHOT</version>
+    <version>0.8.12-SNAPSHOT</version>
     <packaging>pom</packaging>
     <name>killbill</name>
     <description>Library for managing recurring subscriptions and the associated billing</description>

server/pom.xml 2(+1 -1)

diff --git a/server/pom.xml b/server/pom.xml
index b8e13a9..53ecc5a 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-server</artifactId>
diff --git a/server/src/main/resources/update-checker/killbill-server-update-list.properties b/server/src/main/resources/update-checker/killbill-server-update-list.properties
index f2fee54..3c963db 100644
--- a/server/src/main/resources/update-checker/killbill-server-update-list.properties
+++ b/server/src/main/resources/update-checker/killbill-server-update-list.properties
@@ -1,86 +1,91 @@
 ## Top level keys
 # general.notice = This notice should rarely, if ever, be used as everyone will see it
 
-## 0.8.10 -- latest release
-0.8.10.updates           =
-0.8.10.notices           = This is the latest GA release.
+## 0.8.11 -- latest release
+0.8.11.updates           =
+0.8.11.notices           = This is the latest GA release.
+0.8.11.release-notes     = http://kill-bill.org
+
+## 0.8.10
+0.8.10.updates           = 0.8.11
+0.8.10.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.8.10.release-notes     = http://kill-bill.org
 
 ## 0.8.9
-0.8.9.updates           = 0.8.10
-0.8.9.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.8.9.updates           = 0.8.11
+0.8.9.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.8.9.release-notes     = http://kill-bill.org
 
 ## 0.8.8
-0.8.8.updates           = 0.8.10
-0.8.8.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.8.8.updates           = 0.8.11
+0.8.8.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.8.8.release-notes     = http://kill-bill.org
 
 ## 0.8.7
-0.8.7.updates           = 0.8.10
-0.8.7.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.8.7.updates           = 0.8.11
+0.8.7.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.8.7.release-notes     = http://kill-bill.org
 
 ## 0.8.6
-0.8.6.updates           = 0.8.10
-0.8.6.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.8.6.updates           = 0.8.11
+0.8.6.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.8.6.release-notes     = http://kill-bill.org
 
 ## 0.8.5
-0.8.5.updates           = 0.8.10
-0.8.5.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.8.5.updates           = 0.8.11
+0.8.5.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.8.5.release-notes     = http://kill-bill.org
 
 ## 0.8.4
-0.8.4.updates           = 0.8.10
-0.8.4.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.8.4.updates           = 0.8.11
+0.8.4.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.8.4.release-notes     = http://kill-bill.org
 
 ## 0.8.3
-0.8.3.updates           = 0.8.10
-0.8.3.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.8.3.updates           = 0.8.11
+0.8.3.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.8.3.release-notes     = http://kill-bill.org
 
 ## 0.8.2
-0.8.2.updates           = 0.8.10
-0.8.2.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.8.2.updates           = 0.8.11
+0.8.2.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.8.2.release-notes     = http://kill-bill.org
 
 ## 0.8.1
-0.8.1.updates           = 0.8.10
-0.8.1.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.8.1.updates           = 0.8.11
+0.8.1.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.8.1.release-notes     = http://kill-bill.org
 
 ## 0.8.0
-0.8.0.updates           = 0.8.10
-0.8.0.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.8.0.updates           = 0.8.11
+0.8.0.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.8.0.release-notes     = http://kill-bill.org
 
 ### 0.7.x series ###
 
 ## 0.7.2
 0.7.2.updates           =
-0.7.2.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.7.2.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.7.2.release-notes     = http://kill-bill.org
 
 ## 0.7.1
 0.7.1.updates           = 0.7.2
-0.7.1.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.7.1.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.7.1.release-notes     = http://kill-bill.org
 
 ## 0.7.0
 0.7.0.updates           = 0.7.2
-0.7.0.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.7.0.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.7.0.release-notes     = http://kill-bill.org
 
 ### 0.6.x series ###
 
 ## 0.6.17
 0.6.17.updates           =
-0.6.17.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.6.17.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.6.17.release-notes     = http://kill-bill.org
 
 ## 0.6.16
 0.6.16.updates           = 0.6.17
-0.6.16.notices           = We recommend upgrading to 0.8.10, our latest GA release.
+0.6.16.notices           = We recommend upgrading to 0.8.11, our latest GA release.
 0.6.16.release-notes     = http://kill-bill.org
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
index 5f90877..77da877 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
@@ -27,6 +27,7 @@ import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.client.model.Account;
 import com.ning.billing.client.model.Bundle;
+import com.ning.billing.client.model.Bundles;
 import com.ning.billing.client.model.Subscription;
 
 import static org.testng.Assert.assertEquals;
@@ -99,4 +100,31 @@ public class TestBundle extends TestJaxrsBase {
         assertEquals(newBundle.getExternalKey(), originalBundle.getExternalKey());
         assertEquals(newBundle.getAccountId(), newAccount.getAccountId());
     }
+
+    @Test(groups = "slow", description = "Can paginate and search through all bundles")
+    public void testBundlesPagination() throws Exception {
+        final Account accountJson = createAccount();
+
+        for (int i = 0; i < 5; i++) {
+            createEntitlement(accountJson.getAccountId(), UUID.randomUUID().toString(), "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, true);
+        }
+
+        final Bundles allBundles = killBillClient.getBundles();
+        Assert.assertEquals(allBundles.size(), 5);
+
+        for (final Bundle bundle : allBundles) {
+            Assert.assertEquals(killBillClient.searchBundles(bundle.getBundleId().toString()).size(), 1);
+            Assert.assertEquals(killBillClient.searchBundles(bundle.getAccountId().toString()).size(), 5);
+            Assert.assertEquals(killBillClient.searchBundles(bundle.getExternalKey()).size(), 1);
+        }
+
+        Bundles page = killBillClient.getBundles(0L, 1L);
+        for (int i = 0; i < 5; i++) {
+            Assert.assertNotNull(page);
+            Assert.assertEquals(page.size(), 1);
+            Assert.assertEquals(page.get(0), allBundles.get(i));
+            page = page.getNext();
+        }
+        Assert.assertNull(page);
+    }
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
index 6d904a2..0622059 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -450,7 +450,7 @@ public class TestInvoice extends TestJaxrsBase {
         assertEquals(adjustedInvoice.getBalance().compareTo(adjustedInvoiceBalance), 0);
     }
 
-    @Test(groups = "slow", description = "Can paginate through all invoices")
+    @Test(groups = "slow", description = "Can paginate and search through all invoices")
     public void testInvoicesPagination() throws Exception {
         createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
 
@@ -462,6 +462,13 @@ public class TestInvoice extends TestJaxrsBase {
         final Invoices allInvoices = killBillClient.getInvoices();
         Assert.assertEquals(allInvoices.size(), 5);
 
+        for (final Invoice invoice : allInvoices) {
+            Assert.assertEquals(killBillClient.searchInvoices(invoice.getInvoiceId().toString()).size(), 1);
+            Assert.assertEquals(killBillClient.searchInvoices(invoice.getAccountId().toString()).size(), 5);
+            Assert.assertEquals(killBillClient.searchInvoices(invoice.getInvoiceNumber().toString()).size(), 1);
+            Assert.assertEquals(killBillClient.searchInvoices(invoice.getCurrency().toString()).size(), 5);
+        }
+
         Invoices page = killBillClient.getInvoices(0L, 1L);
         for (int i = 0; i < 5; i++) {
             Assert.assertNotNull(page);
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestTag.java b/server/src/test/java/com/ning/billing/jaxrs/TestTag.java
index 3ea5658..50f0b4c 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestTag.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestTag.java
@@ -30,6 +30,8 @@ import com.ning.billing.client.model.Account;
 import com.ning.billing.client.model.Tag;
 import com.ning.billing.client.model.TagDefinition;
 import com.ning.billing.client.model.Tags;
+import com.ning.billing.util.tag.ControlTag;
+import com.ning.billing.util.tag.ControlTagType;
 
 import com.google.common.collect.ImmutableList;
 
@@ -92,6 +94,22 @@ public class TestTag extends TestJaxrsBase {
         assertEquals(objFromJson.size(), 3 + sizeSystemTag);
     }
 
+    @Test(groups = "slow", description = "Can search system tags")
+    public void testSystemTagsPagination() throws Exception {
+        final Account account = createAccount();
+        for (final ControlTagType controlTagType : ControlTagType.values()) {
+            killBillClient.createAccountTag(account.getAccountId(), controlTagType.getId(), createdBy, reason, comment);
+        }
+
+        final Tags allTags = killBillClient.getTags();
+        Assert.assertEquals(allTags.size(), ControlTagType.values().length);
+
+        for (final ControlTagType controlTagType : ControlTagType.values()) {
+            Assert.assertEquals(killBillClient.searchTags(controlTagType.toString()).size(), 1);
+            Assert.assertEquals(killBillClient.searchTags(controlTagType.getDescription()).size(), 1);
+        }
+    }
+
     @Test(groups = "slow", description = "Can paginate through all tags")
     public void testTagsPagination() throws Exception {
         final Account account = createAccount();
diff --git a/subscription/pom.xml b/subscription/pom.xml
index 5e016b0..bd9d7ae 100644
--- a/subscription/pom.xml
+++ b/subscription/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-subscription</artifactId>
diff --git a/subscription/src/main/java/com/ning/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/com/ning/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index b19d8ed..36d6d61 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -30,6 +30,8 @@ import org.slf4j.LoggerFactory;
 
 import com.ning.billing.ErrorCode;
 import com.ning.billing.ObjectType;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
 import com.ning.billing.catalog.api.Catalog;
 import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.CatalogService;
@@ -43,8 +45,10 @@ import com.ning.billing.clock.DefaultClock;
 import com.ning.billing.entitlement.api.Entitlement.EntitlementState;
 import com.ning.billing.entitlement.api.EntitlementAOStatusDryRun;
 import com.ning.billing.entitlement.api.EntitlementAOStatusDryRun.DryRunChangeReason;
+import com.ning.billing.events.EffectiveSubscriptionInternalEvent;
 import com.ning.billing.subscription.api.SubscriptionApiBase;
 import com.ning.billing.subscription.api.SubscriptionBase;
+import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
 import com.ning.billing.subscription.api.user.DefaultEffectiveSubscriptionEvent;
 import com.ning.billing.subscription.api.user.DefaultSubscriptionBase;
 import com.ning.billing.subscription.api.user.DefaultSubscriptionBaseApiService;
@@ -57,18 +61,19 @@ import com.ning.billing.subscription.api.user.SubscriptionBaseTransitionData;
 import com.ning.billing.subscription.api.user.SubscriptionBuilder;
 import com.ning.billing.subscription.engine.addon.AddonUtils;
 import com.ning.billing.subscription.engine.dao.SubscriptionDao;
+import com.ning.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
 import com.ning.billing.subscription.exceptions.SubscriptionBaseError;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
-import com.ning.billing.events.EffectiveSubscriptionInternalEvent;
-import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
 import com.ning.billing.util.dao.NonEntityDao;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.inject.Inject;
 
+import static com.ning.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationNoException;
+
 public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implements SubscriptionBaseInternalApi {
 
     private final Logger log = LoggerFactory.getLogger(DefaultSubscriptionInternalApi.class);
@@ -187,11 +192,48 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
     }
 
     @Override
+    public Pagination<SubscriptionBaseBundle> getBundles(final Long offset, final Long limit, final InternalTenantContext context) {
+        return getEntityPaginationNoException(limit,
+                                              new SourcePaginationBuilder<SubscriptionBundleModelDao, SubscriptionBaseApiException>() {
+                                                  @Override
+                                                  public Pagination<SubscriptionBundleModelDao> build() {
+                                                      return dao.get(offset, limit, context);
+                                                  }
+                                              },
+                                              new Function<SubscriptionBundleModelDao, SubscriptionBaseBundle>() {
+                                                  @Override
+                                                  public SubscriptionBaseBundle apply(final SubscriptionBundleModelDao bundleModelDao) {
+                                                      return SubscriptionBundleModelDao.toSubscriptionbundle(bundleModelDao);
+                                                  }
+                                              }
+                                             );
+    }
+
+    @Override
+    public Pagination<SubscriptionBaseBundle> searchBundles(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+        return getEntityPaginationNoException(limit,
+                                              new SourcePaginationBuilder<SubscriptionBundleModelDao, SubscriptionBaseApiException>() {
+                                                  @Override
+                                                  public Pagination<SubscriptionBundleModelDao> build() {
+                                                      return dao.searchSubscriptionBundles(searchKey, offset, limit, context);
+                                                  }
+                                              },
+                                              new Function<SubscriptionBundleModelDao, SubscriptionBaseBundle>() {
+                                                  @Override
+                                                  public SubscriptionBaseBundle apply(final SubscriptionBundleModelDao bundleModelDao) {
+                                                      return SubscriptionBundleModelDao.toSubscriptionbundle(bundleModelDao);
+                                                  }
+                                              }
+                                             );
+
+    }
+
+    @Override
     public Iterable<UUID> getNonAOSubscriptionIdsForKey(final String bundleKey, final InternalTenantContext context) {
         return dao.getNonAOSubscriptionIdsForKey(bundleKey, context);
     }
 
-    public static SubscriptionBaseBundle getActiveBundleForKeyNotException(final List<SubscriptionBaseBundle> existingBundles, final SubscriptionDao dao, final Clock clock, final InternalTenantContext context)  {
+    public static SubscriptionBaseBundle getActiveBundleForKeyNotException(final List<SubscriptionBaseBundle> existingBundles, final SubscriptionDao dao, final Clock clock, final InternalTenantContext context) {
         for (SubscriptionBaseBundle cur : existingBundles) {
             final List<SubscriptionBase> subscriptions = dao.getSubscriptions(cur.getId(), context);
             for (SubscriptionBase s : subscriptions) {
@@ -330,10 +372,10 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
                 reason = DryRunChangeReason.AO_NOT_AVAILABLE_IN_NEW_PLAN;
             }
             final EntitlementAOStatusDryRun status = new DefaultSubscriptionStatusDryRun(cur.getId(),
-                                                                                        cur.getCurrentPlan().getProduct().getName(),
-                                                                                        cur.getCurrentPhase().getPhaseType(),
-                                                                                        cur.getCurrentPlan().getBillingPeriod(),
-                                                                                        cur.getCurrentPriceList().getName(), reason);
+                                                                                         cur.getCurrentPlan().getProduct().getName(),
+                                                                                         cur.getCurrentPhase().getPhaseType(),
+                                                                                         cur.getCurrentPlan().getBillingPeriod(),
+                                                                                         cur.getCurrentPriceList().getName(), reason);
             result.add(status);
         }
         return result;
diff --git a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/BundleSqlDao.java b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/BundleSqlDao.java
index 5fce8b0..7746e81 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/BundleSqlDao.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/BundleSqlDao.java
@@ -17,18 +17,20 @@
 package com.ning.billing.subscription.engine.dao;
 
 import java.util.Date;
+import java.util.Iterator;
 import java.util.List;
 
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.FetchSize;
 
-import com.ning.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
-import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
-import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.callcontext.InternalCallContext;
 import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+import com.ning.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
+import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.util.entity.dao.Audited;
 import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.entity.dao.EntitySqlDaoStringTemplate;
@@ -60,4 +62,13 @@ public interface BundleSqlDao extends EntitySqlDao<SubscriptionBundleModelDao, S
     @SqlQuery
     public List<SubscriptionBundleModelDao> getBundlesForKey(@Bind("externalKey") String externalKey,
                                                              @BindBean final InternalTenantContext context);
+
+    @SqlQuery
+    // Magic value to force MySQL to stream from the database
+    // See http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html (ResultSet)
+    @FetchSize(Integer.MIN_VALUE)
+    public Iterator<SubscriptionBundleModelDao> searchBundles(@Bind("searchKey") final String searchKey,
+                                                              @Bind("offset") final Long offset,
+                                                              @Bind("rowCount") final Long rowCount,
+                                                              @BindBean final InternalTenantContext context);
 }
diff --git a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index a183a12..3c876f8 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -46,6 +46,7 @@ import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.api.SubscriptionApiException;
 import com.ning.billing.entity.EntityPersistenceException;
 import com.ning.billing.events.EffectiveSubscriptionInternalEvent;
 import com.ning.billing.events.RepairSubscriptionInternalEvent;
@@ -85,6 +86,9 @@ import com.ning.billing.subscription.events.user.ApiEventType;
 import com.ning.billing.subscription.exceptions.SubscriptionBaseError;
 import com.ning.billing.util.cache.CacheControllerDispatcher;
 import com.ning.billing.util.dao.NonEntityDao;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
+import com.ning.billing.util.entity.dao.EntityDaoBase;
 import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
 import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
@@ -96,15 +100,13 @@ import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
 
-public class DefaultSubscriptionDao implements SubscriptionDao {
+public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleModelDao, SubscriptionBaseBundle, SubscriptionApiException> implements SubscriptionDao {
 
     private static final Logger log = LoggerFactory.getLogger(DefaultSubscriptionDao.class);
 
     private final Clock clock;
-    private final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
     private final NotificationQueueService notificationQueueService;
     private final AddonUtils addonUtils;
     private final PersistentBus eventBus;
@@ -114,8 +116,8 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
     public DefaultSubscriptionDao(final IDBI dbi, final Clock clock, final AddonUtils addonUtils,
                                   final NotificationQueueService notificationQueueService, final PersistentBus eventBus, final CatalogService catalogService,
                                   final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao) {
+        super(new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao), BundleSqlDao.class);
         this.clock = clock;
-        this.transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao);
         this.notificationQueueService = notificationQueueService;
         this.addonUtils = addonUtils;
         this.eventBus = eventBus;
@@ -123,6 +125,11 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
     }
 
     @Override
+    protected SubscriptionApiException generateAlreadyExistsException(final SubscriptionBundleModelDao entity, final InternalCallContext context) {
+        return new SubscriptionApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, entity.getExternalKey());
+    }
+
+    @Override
     public List<SubscriptionBaseBundle> getSubscriptionBundlesForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<SubscriptionBaseBundle>>() {
             @Override
@@ -183,6 +190,20 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
     }
 
     @Override
+    public Pagination<SubscriptionBundleModelDao> searchSubscriptionBundles(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+        return paginationHelper.getPagination(BundleSqlDao.class,
+                                              new PaginationIteratorBuilder<SubscriptionBundleModelDao, SubscriptionBaseBundle, BundleSqlDao>() {
+                                                  @Override
+                                                  public Iterator<SubscriptionBundleModelDao> build(final BundleSqlDao bundleSqlDao, final Long limit) {
+                                                      return bundleSqlDao.searchBundles(searchKey, offset, limit, context);
+                                                  }
+                                              },
+                                              offset,
+                                              limit,
+                                              context);
+    }
+
+    @Override
     public Iterable<UUID> getNonAOSubscriptionIdsForKey(final String bundleKey, final InternalTenantContext context) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Iterable<UUID>>() {
             @Override
diff --git a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/RepairSubscriptionDao.java b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/RepairSubscriptionDao.java
index 26f50cd..bffc74c 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/RepairSubscriptionDao.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/RepairSubscriptionDao.java
@@ -27,6 +27,16 @@ import java.util.Set;
 import java.util.TreeSet;
 import java.util.UUID;
 
+import javax.inject.Inject;
+
+import org.skife.jdbi.v2.IDBI;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.api.SubscriptionApiException;
+import com.ning.billing.subscription.api.SubscriptionBase;
 import com.ning.billing.subscription.api.migration.AccountMigrationData;
 import com.ning.billing.subscription.api.migration.AccountMigrationData.BundleMigrationData;
 import com.ning.billing.subscription.api.timeline.RepairSubscriptionLifecycleDao;
@@ -34,22 +44,35 @@ import com.ning.billing.subscription.api.timeline.SubscriptionDataRepair;
 import com.ning.billing.subscription.api.transfer.TransferCancelData;
 import com.ning.billing.subscription.api.user.DefaultSubscriptionBase;
 import com.ning.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+import com.ning.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
 import com.ning.billing.subscription.events.SubscriptionBaseEvent;
 import com.ning.billing.subscription.exceptions.SubscriptionBaseError;
-import com.ning.billing.subscription.api.SubscriptionBase;
-import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.util.cache.CacheControllerDispatcher;
+import com.ning.billing.util.dao.NonEntityDao;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.EntityDaoBase;
+import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
 
-public class RepairSubscriptionDao implements SubscriptionDao, RepairSubscriptionLifecycleDao {
+public class RepairSubscriptionDao extends EntityDaoBase<SubscriptionBundleModelDao, SubscriptionBaseBundle, SubscriptionApiException> implements SubscriptionDao, RepairSubscriptionLifecycleDao {
 
     private static final String NOT_IMPLEMENTED = "Not implemented";
 
     private final ThreadLocal<Map<UUID, SubscriptionRepairEvent>> preThreadsInRepairSubscriptions = new ThreadLocal<Map<UUID, SubscriptionRepairEvent>>();
 
+    @Inject
+    public RepairSubscriptionDao(final IDBI dbi, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao) {
+        super(new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao), BundleSqlDao.class);
+    }
+
+    @Override
+    protected SubscriptionApiException generateAlreadyExistsException(final SubscriptionBundleModelDao entity, final InternalCallContext context) {
+        return new SubscriptionApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, entity.getExternalKey());
+    }
+
     private static final class SubscriptionEventWithOrderingId {
 
         private final SubscriptionBaseEvent event;
@@ -311,6 +334,11 @@ public class RepairSubscriptionDao implements SubscriptionDao, RepairSubscriptio
     }
 
     @Override
+    public Pagination<SubscriptionBundleModelDao> searchSubscriptionBundles(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+        throw new SubscriptionBaseError(NOT_IMPLEMENTED);
+    }
+
+    @Override
     public List<UUID> getNonAOSubscriptionIdsForKey(final String bundleKey, final InternalTenantContext context) {
         throw new SubscriptionBaseError(NOT_IMPLEMENTED);
     }
diff --git a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/SubscriptionDao.java b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/SubscriptionDao.java
index 3caa123..165a868 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/SubscriptionDao.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/SubscriptionDao.java
@@ -20,6 +20,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.entitlement.api.SubscriptionApiException;
+import com.ning.billing.subscription.api.SubscriptionBase;
 import com.ning.billing.subscription.api.migration.AccountMigrationData;
 import com.ning.billing.subscription.api.migration.AccountMigrationData.BundleMigrationData;
 import com.ning.billing.subscription.api.timeline.SubscriptionDataRepair;
@@ -27,19 +31,21 @@ import com.ning.billing.subscription.api.transfer.TransferCancelData;
 import com.ning.billing.subscription.api.user.DefaultSubscriptionBase;
 import com.ning.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
 import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+import com.ning.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
 import com.ning.billing.subscription.events.SubscriptionBaseEvent;
-import com.ning.billing.subscription.api.SubscriptionBase;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.EntityDao;
 
-public interface SubscriptionDao {
+public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, SubscriptionBaseBundle, SubscriptionApiException> {
 
     // Bundle apis
     public List<SubscriptionBaseBundle> getSubscriptionBundleForAccount(UUID accountId, InternalTenantContext context);
 
     public List<SubscriptionBaseBundle> getSubscriptionBundlesForKey(String bundleKey, InternalTenantContext context);
 
-    public Iterable<UUID> getNonAOSubscriptionIdsForKey(final String bundleKey, final InternalTenantContext context);
+    public Pagination<SubscriptionBundleModelDao> searchSubscriptionBundles(String searchKey, Long offset, Long limit, InternalTenantContext context);
+
+    public Iterable<UUID> getNonAOSubscriptionIdsForKey(String bundleKey, InternalTenantContext context);
 
     public List<SubscriptionBaseBundle> getSubscriptionBundlesForAccountAndKey(UUID accountId, String bundleKey, InternalTenantContext context);
 
@@ -80,7 +86,7 @@ public interface SubscriptionDao {
 
     public void cancelSubscription(DefaultSubscriptionBase subscription, SubscriptionBaseEvent cancelEvent, InternalCallContext context, int cancelSeq);
 
-    public void cancelSubscriptions(final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context);
+    public void cancelSubscriptions(List<DefaultSubscriptionBase> subscriptions, List<SubscriptionBaseEvent> cancelEvents, InternalCallContext context);
 
     public void uncancelSubscription(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> uncancelEvents, InternalCallContext context);
 
@@ -90,7 +96,7 @@ public interface SubscriptionDao {
 
     public void transfer(UUID srcAccountId, UUID destAccountId, BundleMigrationData data, List<TransferCancelData> transferCancelData, InternalCallContext fromContext, InternalCallContext toContext);
 
-    public void updateBundleExternalKey(UUID bundleId, String externalKey, final InternalCallContext context);
+    public void updateBundleExternalKey(UUID bundleId, String externalKey, InternalCallContext context);
 
     // Repair
     public void repair(UUID accountId, UUID bundleId, List<SubscriptionDataRepair> inRepair, InternalCallContext context);
diff --git a/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/BundleSqlDao.sql.stg b/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/BundleSqlDao.sql.stg
index 7238aee..f5f9ade 100644
--- a/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/BundleSqlDao.sql.stg
+++ b/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/BundleSqlDao.sql.stg
@@ -77,3 +77,19 @@ account_id = :accountId
 <defaultOrderBy()>
 ;
 >>
+
+searchBundles() ::= <<
+select SQL_CALC_FOUND_ROWS
+<allTableFields("t.")>
+from <tableName()> t
+where 1 = 1
+and (
+     <idField("t.")> = :searchKey
+  or t.external_key = :searchKey
+  or t.account_id = :searchKey
+)
+<AND_CHECK_TENANT("t.")>
+order by <recordIdField("t.")> ASC
+limit :offset, :rowCount
+;
+>>
diff --git a/subscription/src/test/java/com/ning/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/com/ning/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
index 79f9e47..3ffe068 100644
--- a/subscription/src/test/java/com/ning/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
+++ b/subscription/src/test/java/com/ning/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
@@ -36,6 +36,7 @@ import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.api.SubscriptionApiException;
 import com.ning.billing.notificationq.api.NotificationEvent;
 import com.ning.billing.notificationq.api.NotificationQueue;
 import com.ning.billing.notificationq.api.NotificationQueueService;
@@ -52,16 +53,20 @@ import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
 import com.ning.billing.subscription.api.user.SubscriptionBuilder;
 import com.ning.billing.subscription.engine.core.DefaultSubscriptionBaseService;
 import com.ning.billing.subscription.engine.core.SubscriptionNotificationKey;
+import com.ning.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
 import com.ning.billing.subscription.events.SubscriptionBaseEvent;
 import com.ning.billing.subscription.events.SubscriptionBaseEvent.EventType;
 import com.ning.billing.subscription.events.user.ApiEvent;
 import com.ning.billing.subscription.events.user.ApiEventType;
+import com.ning.billing.util.entity.DefaultPagination;
+import com.ning.billing.util.entity.Pagination;
 import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import com.ning.billing.util.entity.dao.MockEntityDaoBase;
 
 import com.google.inject.Inject;
 
-public class MockSubscriptionDaoMemory implements SubscriptionDao {
+public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBundleModelDao, SubscriptionBaseBundle, SubscriptionApiException> implements SubscriptionDao {
 
     protected static final Logger log = LoggerFactory.getLogger(SubscriptionDao.class);
 
@@ -114,6 +119,20 @@ public class MockSubscriptionDaoMemory implements SubscriptionDao {
     }
 
     @Override
+    public Pagination<SubscriptionBundleModelDao> searchSubscriptionBundles(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+        final List<SubscriptionBundleModelDao> results = new LinkedList<SubscriptionBundleModelDao>();
+        for (final SubscriptionBundleModelDao bundleModelDao : getAll(context)) {
+            if (bundleModelDao.getId().toString().equals(searchKey) ||
+                bundleModelDao.getExternalKey().equals(searchKey) ||
+                bundleModelDao.getAccountId().toString().equals(searchKey)) {
+                results.add(bundleModelDao);
+            }
+        }
+
+        return DefaultPagination.<SubscriptionBundleModelDao>build(offset, limit, results);
+    }
+
+    @Override
     public List<UUID> getNonAOSubscriptionIdsForKey(final String bundleKey, final InternalTenantContext context) {
         throw new UnsupportedOperationException();
     }

tenant/pom.xml 2(+1 -1)

diff --git a/tenant/pom.xml b/tenant/pom.xml
index 5deb95c..78f9795 100644
--- a/tenant/pom.xml
+++ b/tenant/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-tenant</artifactId>

usage/pom.xml 2(+1 -1)

diff --git a/usage/pom.xml b/usage/pom.xml
index 152b472..39ba106 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-usage</artifactId>

util/pom.xml 2(+1 -1)

diff --git a/util/pom.xml b/util/pom.xml
index c619b8b..62c03e4 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -12,7 +12,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.8.11-SNAPSHOT</version>
+        <version>0.8.12-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-util</artifactId>
diff --git a/util/src/main/java/com/ning/billing/util/audit/DefaultAccountAuditLogs.java b/util/src/main/java/com/ning/billing/util/audit/DefaultAccountAuditLogs.java
index 963eb20..4927c33 100644
--- a/util/src/main/java/com/ning/billing/util/audit/DefaultAccountAuditLogs.java
+++ b/util/src/main/java/com/ning/billing/util/audit/DefaultAccountAuditLogs.java
@@ -91,6 +91,11 @@ public class DefaultAccountAuditLogs implements AccountAuditLogs {
     }
 
     @Override
+    public List<AuditLog> getAuditLogsForPaymentMethod(final UUID paymentMethodId) {
+        return getAuditLogs(ObjectType.PAYMENT_METHOD).getAuditLogs(paymentMethodId);
+    }
+
+    @Override
     public List<AuditLog> getAuditLogsForRefund(final UUID refundId) {
         return getAuditLogs(ObjectType.REFUND).getAuditLogs(refundId);
     }
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
index 1961d2b..c257b74 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
@@ -24,7 +24,6 @@ import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-import org.skife.jdbi.v2.sqlobject.customizers.Define;
 import org.skife.jdbi.v2.sqlobject.customizers.FetchSize;
 
 import com.ning.billing.ObjectType;
@@ -53,7 +52,8 @@ public interface CustomFieldSqlDao extends EntitySqlDao<CustomFieldModelDao, Cus
     // Magic value to force MySQL to stream from the database
     // See http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html (ResultSet)
     @FetchSize(Integer.MIN_VALUE)
-    public Iterator<CustomFieldModelDao> searchCustomFields(@Define("searchKey") final String searchKey,
+    public Iterator<CustomFieldModelDao> searchCustomFields(@Bind("searchKey") final String searchKey,
+                                                            @Bind("likeSearchKey") final String likeSearchKey,
                                                             @Bind("offset") final Long offset,
                                                             @Bind("rowCount") final Long rowCount,
                                                             @BindBean final InternalTenantContext context);
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/DefaultCustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/DefaultCustomFieldDao.java
index 8eaf2a0..092d95a 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/DefaultCustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/DefaultCustomFieldDao.java
@@ -148,7 +148,7 @@ public class DefaultCustomFieldDao extends EntityDaoBase<CustomFieldModelDao, Cu
                                               new PaginationIteratorBuilder<CustomFieldModelDao, CustomField, CustomFieldSqlDao>() {
                                                   @Override
                                                   public Iterator<CustomFieldModelDao> build(final CustomFieldSqlDao customFieldSqlDao, final Long limit) {
-                                                      return customFieldSqlDao.searchCustomFields(searchKey, offset, limit, context);
+                                                      return customFieldSqlDao.searchCustomFields(searchKey, String.format("%%%s%%", searchKey), offset, limit, context);
                                                   }
                                               },
                                               offset,
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDao.java
index e559df7..4a4abaa 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDao.java
@@ -223,7 +223,7 @@ public class DefaultTagDao extends EntityDaoBase<TagModelDao, Tag, TagApiExcepti
                                               new PaginationIteratorBuilder<TagModelDao, Tag, TagSqlDao>() {
                                                   @Override
                                                   public Iterator<TagModelDao> build(final TagSqlDao tagSqlDao, final Long limit) {
-                                                      return tagSqlDao.searchTags(searchKey, offset, limit, context);
+                                                      return tagSqlDao.searchTags(searchKey, String.format("%%%s%%", searchKey), offset, limit, context);
                                                   }
                                               },
                                               offset,
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
index 53ebbc5..668553b 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
@@ -24,7 +24,6 @@ import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-import org.skife.jdbi.v2.sqlobject.customizers.Define;
 import org.skife.jdbi.v2.sqlobject.customizers.FetchSize;
 
 import com.ning.billing.ObjectType;
@@ -58,7 +57,8 @@ public interface TagSqlDao extends EntitySqlDao<TagModelDao, Tag> {
     // Magic value to force MySQL to stream from the database
     // See http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html (ResultSet)
     @FetchSize(Integer.MIN_VALUE)
-    public Iterator<TagModelDao> searchTags(@Define("searchKey") final String searchKey,
+    public Iterator<TagModelDao> searchTags(@Bind("searchKey") final String searchKey,
+                                            @Bind("likeSearchKey") final String likeSearchKey,
                                             @Bind("offset") final Long offset,
                                             @Bind("rowCount") final Long rowCount,
                                             @BindBean final InternalTenantContext context);
diff --git a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
index 0367199..1d01add 100644
--- a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
@@ -52,16 +52,16 @@ and is_active
 ;
 >>
 
-searchCustomFields(searchKey, offset, rowCount) ::= <<
+searchCustomFields() ::= <<
 select SQL_CALC_FOUND_ROWS
 <allTableFields("t.")>
 from <tableName()> t
 where 1 = 1
 and (
-     <idField("t.")> = '<searchKey>'
-  or t.object_type like '%<searchKey>%'
-  or t.field_name like '%<searchKey>%'
-  or t.field_value like '%<searchKey>%'
+     <idField("t.")> = :searchKey
+  or t.object_type like :likeSearchKey
+  or t.field_name like :likeSearchKey
+  or t.field_value like :likeSearchKey
 )
 <AND_CHECK_TENANT("t.")>
 order by <recordIdField("t.")> ASC
diff --git a/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg
index 7b14556..8dbc6dc 100644
--- a/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg
@@ -58,17 +58,58 @@ and t.object_type = :objectType
 ;
 >>
 
-searchTags(searchKey, offset, rowCount) ::= <<
+searchTags() ::= <<
 select SQL_CALC_FOUND_ROWS
 <allTableFields("t.")>
 from <tableName()> t
-join tag_definitions td on td.id = t.tag_definition_id
+join (
+  select
+    id
+  , name
+  , description
+  from tag_definitions
+  union
+  select
+    \'00000000-0000-0000-0000-000000000001\' id
+  , \'AUTO_PAY_OFF\' name
+  , \'Suspends payments until removed.\' description
+  union
+  select
+    \'00000000-0000-0000-0000-000000000001\' id
+  , \'AUTO_INVOICING_OFF\' name
+  , \'Suspends invoicing until removed.\' description
+  union
+  select
+    \'00000000-0000-0000-0000-000000000003\' id
+  , \'OVERDUE_ENFORCEMENT_OFF\' name
+  , \'Suspends overdue enforcement behaviour until removed.\' description
+  union
+  select
+    \'00000000-0000-0000-0000-000000000004\' id
+  , \'WRITTEN_OFF\' name
+  , \'Indicates that an invoice is written off. No billing or payment effect.\' description
+  union
+  select
+    \'00000000-0000-0000-0000-000000000005\' id
+  , \'MANUAL_PAY\' name
+  , \'Indicates that Killbill doesn\\\\\'t process payments for that account (external payments only)\' description
+  union
+  select
+    \'00000000-0000-0000-0000-000000000006\' id
+  , \'TEST\' name
+  , \'Indicates that this is a test account\' description
+  union
+  select
+    \'00000000-0000-0000-0000-000000000007\' id
+  , \'PARTNER\' name
+  , \'Indicates that this is a partner account\' description
+) td on td.id = t.tag_definition_id
 where 1 = 1
 and (
-     <idField("t.")> = '<searchKey>'
-  or t.object_type like '%<searchKey>%'
-  or td.name like '%<searchKey>%'
-  or td.description like '%<searchKey>%'
+     <idField("t.")> = :searchKey
+  or t.object_type like :likeSearchKey
+  or td.name like :likeSearchKey
+  or td.description like :likeSearchKey
 )
 <AND_CHECK_TENANT("t.")>
 order by <recordIdField("t.")> ASC
diff --git a/util/src/test/java/com/ning/billing/util/tag/dao/TestDefaultTagDao.java b/util/src/test/java/com/ning/billing/util/tag/dao/TestDefaultTagDao.java
index 34211b4..3bf8b8d 100644
--- a/util/src/test/java/com/ning/billing/util/tag/dao/TestDefaultTagDao.java
+++ b/util/src/test/java/com/ning/billing/util/tag/dao/TestDefaultTagDao.java
@@ -23,9 +23,11 @@ import java.util.UUID;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import com.ning.billing.ErrorCode;
 import com.ning.billing.ObjectType;
 import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.util.UtilTestSuiteWithEmbeddedDB;
+import com.ning.billing.util.api.TagApiException;
 import com.ning.billing.util.api.TagDefinitionApiException;
 import com.ning.billing.util.tag.ControlTagType;
 import com.ning.billing.util.tag.DescriptiveTag;
@@ -153,4 +155,25 @@ public class TestDefaultTagDao extends UtilTestSuiteWithEmbeddedDB {
         Assert.assertEquals(tagDao.getTagsForObject(objectId, objectType, true, internalCallContext).size(), 1);
         Assert.assertEquals(tagDao.getTagsForAccount(true, internalCallContext).size(), 1);
     }
+
+    @Test(groups = "slow")
+    public void testInsertMultipleTags() throws TagApiException {
+        final UUID objectId = UUID.randomUUID();
+        final ObjectType objectType = ObjectType.INVOICE_ITEM;
+
+        eventsListener.pushExpectedEvent(NextEvent.TAG);
+        final Tag tag = new DescriptiveTag(ControlTagType.AUTO_INVOICING_OFF.getId(), objectType, objectId, internalCallContext.getCreatedDate());
+        tagDao.create(new TagModelDao(tag), internalCallContext);
+        assertListenerStatus();
+
+        try {
+            final Tag tag2 = new DescriptiveTag(ControlTagType.AUTO_INVOICING_OFF.getId(), objectType, objectId, internalCallContext.getCreatedDate());
+            tagDao.create(new TagModelDao(tag2), internalCallContext);
+            Assert.fail("Should not be able to create twice the same tag");
+            assertListenerStatus();
+        } catch (final TagApiException e) {
+            Assert.assertEquals(ErrorCode.TAG_ALREADY_EXISTS.getCode(), e.getCode());
+        }
+    }
+
 }