killbill-memoizeit

Merge branch 'jgomez-issue-304' of https://github.com/javier-gomez-hs/killbill

8/22/2016 5:45:54 PM

Details

jaxrs/pom.xml 4(+4 -0)

diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index 20befb1..d477452 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -76,6 +76,10 @@
             <artifactId>joda-time</artifactId>
         </dependency>
         <dependency>
+            <groupId>net.sf.ehcache</groupId>
+            <artifactId>ehcache</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.shiro</groupId>
             <artifactId>shiro-core</artifactId>
         </dependency>
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
index 979d223..c964e67 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
@@ -17,19 +17,24 @@
 
 package org.killbill.billing.jaxrs.resources;
 
+import java.util.List;
 import java.util.UUID;
 
 import javax.inject.Inject;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
 import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
+import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.jaxrs.json.AdminPaymentJson;
 import org.killbill.billing.jaxrs.util.Context;
@@ -41,10 +46,16 @@ import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PaymentTransaction;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.tenant.api.Tenant;
+import org.killbill.billing.tenant.api.TenantApiException;
+import org.killbill.billing.tenant.api.TenantUserApi;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldUserApi;
+import org.killbill.billing.util.api.RecordIdApi;
 import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.clock.Clock;
 
 import com.google.common.base.Predicate;
@@ -55,6 +66,8 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Ehcache;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
@@ -64,11 +77,17 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 public class AdminResource extends JaxRsResourceBase {
 
     private final AdminPaymentApi adminPaymentApi;
+    private final TenantUserApi tenantApi;
+    private final CacheManager cacheManager;
+    private final RecordIdApi recordIdApi;
 
     @Inject
-    public AdminResource(final JaxrsUriBuilder uriBuilder, final TagUserApi tagUserApi, final CustomFieldUserApi customFieldUserApi, final AuditUserApi auditUserApi, final AccountUserApi accountUserApi, final PaymentApi paymentApi, final AdminPaymentApi adminPaymentApi, final Clock clock, final Context context) {
+    public AdminResource(final JaxrsUriBuilder uriBuilder, final TagUserApi tagUserApi, final CustomFieldUserApi customFieldUserApi, final AuditUserApi auditUserApi, final AccountUserApi accountUserApi, final PaymentApi paymentApi, final AdminPaymentApi adminPaymentApi, final CacheManager cacheManager, final TenantUserApi tenantApi, final RecordIdApi recordIdApi, final Clock clock, final Context context) {
         super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
         this.adminPaymentApi = adminPaymentApi;
+        this.tenantApi = tenantApi;
+        this.recordIdApi = recordIdApi;
+        this.cacheManager = cacheManager;
     }
 
 
@@ -104,4 +123,106 @@ public class AdminResource extends JaxRsResourceBase {
         return Response.status(Status.OK).build();
     }
 
+    @DELETE
+    @Path("/" + CACHE)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Invalidates the given Cache if specified, otherwise invalidates all caches")
+    @ApiResponses(value = {@ApiResponse(code = 400, message = "Cache name does not exist or is not alive")})
+    public Response invalidatesCache(@QueryParam("cacheName") final String cacheName,
+                                    @javax.ws.rs.core.Context final HttpServletRequest request) {
+        if (null != cacheName && !cacheName.isEmpty()) {
+            final Ehcache cache = cacheManager.getEhcache(cacheName);
+            // check if cache is null
+            if (cache == null) {
+                log.warn("Cache for specified cacheName='{}' does not exist or is not alive", cacheName);
+                return Response.status(Status.BAD_REQUEST).build();
+            }
+            // Clear given cache
+            cache.removeAll();
+        }
+        else {
+            // if not given a specific cacheName, clear all
+            cacheManager.clearAll();
+        }
+        return Response.status(Status.OK).build();
+    }
+
+    @DELETE
+    @Path("/" + CACHE + "/" + ACCOUNTS + "/{accountId:" + UUID_PATTERN + "}/")
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Invalidates Caches per account level")
+    @ApiResponses(value = {})
+    public Response invalidatesCacheByAccount(@PathParam("accountId") final String accountId,
+                                              @javax.ws.rs.core.Context final HttpServletRequest request) {
+
+        // clear account-record-id cache by accountId
+        final Ehcache accountRecordIdCache = cacheManager.getEhcache(CacheType.ACCOUNT_RECORD_ID.getCacheName());
+        accountRecordIdCache.remove(accountId);
+
+        // clear account-immutable cache by accountId
+        final Ehcache accountImmutableCache = cacheManager.getEhcache(CacheType.ACCOUNT_IMMUTABLE.getCacheName());
+        accountImmutableCache.remove(UUID.fromString(accountId));
+
+        // clear account-bcd cache by accountId
+        final Ehcache accountBcdCache = cacheManager.getEhcache(CacheType.ACCOUNT_BCD.getCacheName());
+        accountBcdCache.remove(UUID.fromString(accountId));
+
+        return Response.status(Status.OK).build();
+    }
+
+    @DELETE
+    @Path("/" + CACHE + "/" + TENANTS)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Invalidates Caches per tenant level")
+    @ApiResponses(value = {})
+    public Response invalidatesCacheByTenant(@QueryParam("tenantApiKey") final String tenantApiKey,
+                                              @javax.ws.rs.core.Context final HttpServletRequest request) throws TenantApiException {
+
+        // creating Tenant Context from Request
+        TenantContext tenantContext = context.createContext(request);
+
+        Tenant currentTenant = tenantApi.getTenantById(tenantContext.getTenantId());
+
+        // getting Tenant Record Id
+        Long tenantRecordId = recordIdApi.getRecordId(tenantContext.getTenantId(), ObjectType.TENANT, tenantContext);
+
+        // clear tenant-record-id cache by tenantId
+        final Ehcache tenantRecordIdCache = cacheManager.getEhcache(CacheType.TENANT_RECORD_ID.getCacheName());
+        tenantRecordIdCache.remove(currentTenant.getId().toString());
+
+        // clear tenant-payment-state-machine-config cache by tenantRecordId
+        final Ehcache tenantPaymentStateMachineConfigCache = cacheManager.getEhcache(CacheType.TENANT_PAYMENT_STATE_MACHINE_CONFIG.getCacheName());
+        removeCacheByKey(tenantPaymentStateMachineConfigCache, tenantRecordId.toString());
+
+        // clear tenant cache by tenantApiKey
+        final Ehcache tenantCache = cacheManager.getEhcache(CacheType.TENANT.getCacheName());
+        tenantCache.remove(currentTenant.getApiKey());
+
+        // clear tenant-kv cache by tenantRecordId
+        final Ehcache tenantKvCache = cacheManager.getEhcache(CacheType.TENANT_KV.getCacheName());
+        removeCacheByKey(tenantKvCache, tenantRecordId.toString());
+
+        // clear tenant-config cache by tenantRecordId
+        final Ehcache tenantConfigCache = cacheManager.getEhcache(CacheType.TENANT_CONFIG.getCacheName());
+        tenantConfigCache.remove(tenantRecordId);
+
+        // clear tenant-overdue-config cache by tenantRecordId
+        final Ehcache tenantOverdueConfigCache = cacheManager.getEhcache(CacheType.TENANT_OVERDUE_CONFIG.getCacheName());
+        tenantOverdueConfigCache.remove(tenantRecordId);
+
+        // clear tenant-catalog cache by tenantRecordId
+        final Ehcache tenantCatalogCache = cacheManager.getEhcache(CacheType.TENANT_CATALOG.getCacheName());
+        tenantCatalogCache.remove(tenantRecordId);
+
+        return Response.status(Status.OK).build();
+    }
+
+    private void removeCacheByKey(final Ehcache tenantCache, final String tenantRecordId) {
+        for (Object key : tenantCache.getKeys()) {
+            if (null != key && key.toString().endsWith("::" + tenantRecordId)) {
+                tenantCache.remove(key);
+            }
+        }
+    }
+
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index 956cb7b..808aba0 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -272,6 +272,8 @@ public interface JaxrsResource {
     public static final String BCD = "bcd";
     public static final String TRANSFER_CREDIT = "transferCredit";
 
+    public static final String CACHE = "cache";
+
     public static final String QUERY_INCLUDED_DELETED = "includedDeleted";
 
 
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java
new file mode 100644
index 0000000..f62d16a
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PriceListSet;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.client.RequestOptions;
+import org.killbill.billing.client.model.Account;
+import org.killbill.billing.client.model.PaymentMethod;
+import org.killbill.billing.client.model.PaymentMethodPluginDetail;
+import org.killbill.billing.client.model.Subscription;
+import org.killbill.billing.client.model.Tenant;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.io.Resources;
+import net.sf.ehcache.Ehcache;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+public class TestCache extends TestJaxrsBase {
+
+    @Test(groups = "slow", description = "Can Invalidate (clear) a Cache by name")
+    public void testInvalidateCacheByName() throws Exception {
+        // get Ehcache item with name "record-id"
+        final Ehcache cache = cacheManager.getEhcache(CacheType.RECORD_ID.getCacheName());
+        // verify that it is not null and has one stored key (the default tenant created for all integration tests)
+        assertNotNull(cache);
+        Assert.assertEquals(cache.getSize(), 1);
+
+        // invalidate the specified cache
+        killBillClient.invalidateCacheByName(cache.getName(), requestOptions);
+
+        // verify that now the cache is empty and has no keys stored
+        Assert.assertEquals(cache.getSize(), 0);
+    }
+
+    @Test(groups = "slow", description = "Can Invalidate (clear) all available Caches")
+    public void testInvalidateAllCaches() throws Exception {
+        // get Ehcache item with name "record-id"
+        final Ehcache cache = cacheManager.getEhcache(CacheType.RECORD_ID.getCacheName());
+        // verify that it is not null and has one stored key (the default tenant created for all integration tests)
+        assertNotNull(cache);
+        Assert.assertEquals(cache.getSize(), 1);
+
+        // invalidate all caches
+        killBillClient.invalidateAllCaches(requestOptions);
+
+        // verify that now the cache is empty and has no keys stored
+        Assert.assertEquals(cache.getSize(), 0);
+    }
+
+    @Test(groups = "slow", description = "Can Invalidate (clear) all Account Caches by accountId")
+    public void testInvalidateCacheByAccount() throws Exception {
+        final Account input = createAccountNoPMBundleAndSubscription();
+
+        // get all caches per account level
+        final Ehcache accountRecordIdCache = cacheManager.getEhcache(CacheType.ACCOUNT_RECORD_ID.getCacheName());
+        final Ehcache accountImmutableCache = cacheManager.getEhcache(CacheType.ACCOUNT_IMMUTABLE.getCacheName());
+        final Ehcache accountBcdCache = cacheManager.getEhcache(CacheType.ACCOUNT_BCD.getCacheName());
+
+        // verify that they are not null and have the accountId stored as a key (the account created before)
+        assertNotNull(accountRecordIdCache);
+        assertNotNull(accountRecordIdCache.get(input.getAccountId().toString()));
+        assertNotNull(accountImmutableCache);
+        assertNotNull(accountImmutableCache.get(input.getAccountId()));
+        assertNotNull(accountBcdCache);
+        assertNotNull(accountBcdCache.get(input.getAccountId()));
+
+        // invalidate caches per account level by accountId
+        killBillClient.invalidateCacheByAccount(input.getAccountId().toString(), requestOptions);
+
+        // verify that now the caches don't have the accountId key stored
+        Assert.assertNull(accountRecordIdCache.get(input.getAccountId().toString()));
+        Assert.assertNull(accountImmutableCache.get(input.getAccountId()));
+        Assert.assertNull(accountBcdCache.get(input.getAccountId()));
+    }
+
+    @Test(groups = "slow", description = "Can Invalidate (clear) all Tenant Caches for current Tenant")
+    public void testInvalidateCacheByTenant() throws Exception {
+        // creating a new Tenant for this test
+        final String testApiKey = "testApiKey";
+        final String testApiSecret = "testApiSecret";
+        final Tenant tenant = new Tenant();
+        tenant.setApiKey(testApiKey);
+        tenant.setApiSecret(testApiSecret);
+        loginTenant(testApiKey, testApiSecret);
+        Tenant currentTenant = killBillClient.createTenant(tenant, false, requestOptions);
+
+        // using custom RequestOptions with the new Tenant created before
+        RequestOptions inputOptions = RequestOptions.builder()
+                                                    .withCreatedBy(createdBy)
+                                                    .withReason(reason)
+                                                    .withComment(comment)
+                                                    .withTenantApiKey(currentTenant.getApiKey())
+                                                    .withTenantApiSecret(currentTenant.getApiSecret())
+                                                    .build();
+
+        // Uploading the test catalog using the new Tenant created before
+        killBillClient.uploadXMLCatalog(Resources.getResource("catalogTest.xml").getPath(), inputOptions);
+
+        // creating an Account with PaymentMethod and a Subscription
+        createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoiceWithInputOptions(inputOptions);
+
+        // get all caches per tenant level
+        final Ehcache tenantRecordIdCache = cacheManager.getEhcache(CacheType.TENANT_RECORD_ID.getCacheName());
+        final Ehcache tenantPaymentStateMachineConfigCache = cacheManager.getEhcache(CacheType.TENANT_PAYMENT_STATE_MACHINE_CONFIG.getCacheName());
+        final Ehcache tenantCache = cacheManager.getEhcache(CacheType.TENANT.getCacheName());
+        final Ehcache tenantKvCache = cacheManager.getEhcache(CacheType.TENANT_KV.getCacheName());
+        final Ehcache tenantConfigCache = cacheManager.getEhcache(CacheType.TENANT_CONFIG.getCacheName());
+        final Ehcache tenantOverdueConfigCache = cacheManager.getEhcache(CacheType.TENANT_OVERDUE_CONFIG.getCacheName());
+        final Ehcache tenantCatalogCache = cacheManager.getEhcache(CacheType.TENANT_CATALOG.getCacheName());
+
+        // getting current Tenant's record Id from the specific Cache
+        Long tenantRecordId = (Long) tenantRecordIdCache.get(currentTenant.getTenantId().toString()).getObjectValue();
+
+        // verify that they are not null and have the expected tenant information
+        assertNotNull(tenantRecordIdCache);
+        assertNotNull(tenantRecordIdCache.get(currentTenant.getTenantId().toString()));
+        assertNotNull(tenantPaymentStateMachineConfigCache);
+        assertTrue(hasKeysByTenantRecordId(tenantPaymentStateMachineConfigCache, tenantRecordId.toString()));
+        assertNotNull(tenantCache);
+        assertNotNull(tenantCache.get(testApiKey));
+        assertNotNull(tenantKvCache);
+        assertTrue(hasKeysByTenantRecordId(tenantKvCache, tenantRecordId.toString()));
+        assertNotNull(tenantConfigCache);
+        assertNotNull(tenantConfigCache.get(tenantRecordId));
+        assertNotNull(tenantOverdueConfigCache);
+        assertNotNull(tenantOverdueConfigCache.get(tenantRecordId));
+        assertNotNull(tenantCatalogCache);
+        assertNotNull(tenantCatalogCache.get(tenantRecordId));
+
+        // invalidate caches per tenant level
+        killBillClient.invalidateCacheByTenant(inputOptions);
+
+        // verify that now the caches don't have the previous values
+        Assert.assertNull(tenantRecordIdCache.get(currentTenant.getTenantId().toString()));
+        assertFalse(hasKeysByTenantRecordId(tenantPaymentStateMachineConfigCache, tenantRecordId.toString()));
+        Assert.assertNull(tenantCache.get(testApiKey));
+        assertFalse(hasKeysByTenantRecordId(tenantKvCache, tenantRecordId.toString()));
+        Assert.assertNull(tenantConfigCache.get(tenantRecordId));
+        Assert.assertNull(tenantOverdueConfigCache.get(tenantRecordId));
+        Assert.assertNull(tenantCatalogCache.get(tenantRecordId));
+    }
+
+    private boolean hasKeysByTenantRecordId(final Ehcache tenantCache, final String tenantRecordId) {
+        for (String key : (List<String>) tenantCache.getKeys()) {
+            if (key.endsWith("::" + tenantRecordId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoiceWithInputOptions(final RequestOptions inputOptions) throws Exception {
+        Account account = killBillClient.createAccount(getAccount(), inputOptions);
+
+        final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail();
+        info.setProperties(null);
+        final PaymentMethod paymentMethodJson = new PaymentMethod(null, UUID.randomUUID().toString(), account.getAccountId(), true, PLUGIN_NAME, info);
+        killBillClient.createPaymentMethod(paymentMethodJson, inputOptions);
+
+        final Subscription subscription = new Subscription();
+        subscription.setAccountId(account.getAccountId());
+        subscription.setExternalKey(UUID.randomUUID().toString());
+        subscription.setProductName("Shotgun");
+        subscription.setProductCategory(ProductCategory.BASE);
+        subscription.setBillingPeriod(BillingPeriod.MONTHLY);
+        subscription.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        final Subscription subscriptionJson = killBillClient.createSubscription(subscription, clock.getUTCToday(), DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, inputOptions);
+
+        assertNotNull(subscriptionJson);
+        clock.addDays(32);
+        crappyWaitForLackOfProperSynchonization();
+    }
+}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
index 3498ba8..d4ec7bf 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
@@ -74,6 +74,7 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.inject.Module;
 import com.google.inject.util.Modules;
+import net.sf.ehcache.CacheManager;
 
 public class TestJaxrsBase extends KillbillClient {
 
@@ -106,6 +107,9 @@ public class TestJaxrsBase extends KillbillClient {
     @Inject
     protected TenantCacheInvalidation tenantCacheInvalidation;
 
+    @Inject
+    protected CacheManager cacheManager;
+
     protected DaoConfig daoConfig;
     protected KillbillServerConfig serverConfig;