All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.killbill.billing.tenant.api.TenantCacheInvalidation Maven / Gradle / Ivy

/*
 * Copyright 2014-2016 Groupon, Inc
 * Copyright 2014-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.tenant.api;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import javax.inject.Inject;
import javax.inject.Named;

import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.events.BusInternalEvent;
import org.killbill.billing.tenant.api.TenantInternalApi.CacheInvalidationCallback;
import org.killbill.billing.tenant.api.TenantKV.TenantKey;
import org.killbill.billing.tenant.api.user.DefaultTenantConfigChangeInternalEvent;
import org.killbill.billing.tenant.api.user.DefaultTenantConfigDeletionInternalEvent;
import org.killbill.billing.tenant.dao.TenantBroadcastDao;
import org.killbill.billing.tenant.dao.TenantBroadcastModelDao;
import org.killbill.billing.tenant.dao.TenantDao;
import org.killbill.billing.tenant.dao.TenantKVModelDao;
import org.killbill.billing.tenant.glue.DefaultTenantModule;
import org.killbill.billing.util.config.definition.TenantConfig;
import org.killbill.bus.api.PersistentBus;
import org.killbill.bus.api.PersistentBus.EventBusException;
import org.killbill.commons.concurrent.Executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Predicate;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;

/**
 * This class manages the callbacks that have been registered when per tenant objects have been inserted into the
 * tenant_kvs store; the flow is the following (for e.g catalog):
 * 1. CatalogUserApi is invoked to retrieve per tenant catalog
 * 2. If cache is empty, TenantCacheLoader is invoked and uses TenantInternalApi is load the data; at that time, the invalidation callback
 * is registered
 * 

* When this class initializes, it reads the current entry in the tenant_broadcasts table and from then on, keeps polling for new entries; when new * entries are found, it invokes the callback to invalidate the current caching and force the TenantCacheLoader to be invoked again. */ public class TenantCacheInvalidation { private final static int TERMINATION_TIMEOUT_SEC = 5; private static final Logger logger = LoggerFactory.getLogger(TenantCacheInvalidation.class); private final Multimap cache; private final TenantBroadcastDao broadcastDao; private final TenantConfig tenantConfig; private final PersistentBus eventBus; private final TenantDao tenantDao; private AtomicLong latestRecordIdProcessed; private volatile boolean isStopped; private ScheduledExecutorService tenantExecutor; @Inject public TenantCacheInvalidation(@Named(DefaultTenantModule.NO_CACHING_TENANT) final TenantBroadcastDao broadcastDao, @Named(DefaultTenantModule.NO_CACHING_TENANT) final TenantDao tenantDao, final PersistentBus eventBus, final TenantConfig tenantConfig) { this.cache = HashMultimap.create(); this.broadcastDao = broadcastDao; this.tenantConfig = tenantConfig; this.tenantDao = tenantDao; this.eventBus = eventBus; this.isStopped = false; } public void initialize() { final TenantBroadcastModelDao entry = broadcastDao.getLatestEntry(); this.latestRecordIdProcessed = entry != null ? new AtomicLong(entry.getRecordId()) : new AtomicLong(0L); this.tenantExecutor = Executors.newSingleThreadScheduledExecutor("TenantExecutor"); this.isStopped = false; } public void start() { final TimeUnit pendingRateUnit = tenantConfig.getTenantBroadcastServiceRunningRate().getUnit(); final long pendingPeriod = tenantConfig.getTenantBroadcastServiceRunningRate().getPeriod(); tenantExecutor.scheduleAtFixedRate(new TenantCacheInvalidationRunnable(this, broadcastDao, tenantDao), pendingPeriod, pendingPeriod, pendingRateUnit); } public void stop() { if (isStopped) { logger.warn("TenantExecutor is already in a stopped state"); return; } try { tenantExecutor.shutdown(); boolean success = tenantExecutor.awaitTermination(TERMINATION_TIMEOUT_SEC, TimeUnit.SECONDS); if (!success) { logger.warn("TenantExecutor failed to complete termination within " + TERMINATION_TIMEOUT_SEC + "sec"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.warn("TenantExecutor stop sequence got interrupted"); } finally { isStopped = true; } } public void registerCallback(final TenantKey key, final CacheInvalidationCallback value) { cache.put(key, value); } public Collection getCacheInvalidations(final TenantKey key) { return cache.get(key); } public AtomicLong getLatestRecordIdProcessed() { return latestRecordIdProcessed; } public boolean isStopped() { return isStopped; } public void setLatestRecordIdProcessed(final Long newProcessedRecordId) { this.latestRecordIdProcessed.set(newProcessedRecordId); } public PersistentBus getEventBus() { return eventBus; } public static class TenantCacheInvalidationRunnable implements Runnable { private final TenantCacheInvalidation parent; private final TenantBroadcastDao broadcastDao; private final TenantDao tenantDao; public TenantCacheInvalidationRunnable(final TenantCacheInvalidation parent, final TenantBroadcastDao broadcastDao, final TenantDao tenantDao) { this.parent = parent; this.broadcastDao = broadcastDao; this.tenantDao = tenantDao; } @Override public void run() { if (parent.isStopped) { return; } final List entries = broadcastDao.getLatestEntriesFrom(parent.getLatestRecordIdProcessed().get()); for (TenantBroadcastModelDao cur : entries) { if (parent.isStopped()) { return; } try { final TenantKeyAndCookie tenantKeyAndCookie = extractTenantKeyAndCookie(cur.getType()); if (tenantKeyAndCookie != null) { final Collection callbacks = parent.getCacheInvalidations(tenantKeyAndCookie.getTenantKey()); if (!callbacks.isEmpty()) { final InternalTenantContext tenantContext = new InternalTenantContext(cur.getTenantRecordId()); for (final CacheInvalidationCallback callback : callbacks) { callback.invalidateCache(tenantKeyAndCookie.getTenantKey(), tenantKeyAndCookie.getCookie(), tenantContext); } final Long tenantKvsTargetRecordId = cur.getTargetRecordId(); final BusInternalEvent event; if (tenantKvsTargetRecordId != null) { final TenantKVModelDao tenantModelDao = tenantDao.getKeyByRecordId(tenantKvsTargetRecordId, tenantContext); if (tenantModelDao == null) { // Probably inactive entry continue; } event = new DefaultTenantConfigChangeInternalEvent(tenantModelDao.getId(), cur.getType(), null, tenantContext.getTenantRecordId(), cur.getUserToken()); } else { event = new DefaultTenantConfigDeletionInternalEvent(cur.getType(), null, tenantContext.getTenantRecordId(), cur.getUserToken()); } try { parent.getEventBus().post(event); } catch (final EventBusException e) { logger.warn("Failed to post event {}", event, e); } } } else { logger.warn("Failed to find CacheInvalidationCallback for " + cur.getType()); } } finally { parent.setLatestRecordIdProcessed(cur.getRecordId()); } } } private TenantKeyAndCookie extractTenantKeyAndCookie(final String key) { final TenantKey tenantKey = Iterables.tryFind(ImmutableList.copyOf(TenantKey.values()), new Predicate() { @Override public boolean apply(final TenantKey input) { return key.startsWith(input.toString()); } }).orNull(); if (tenantKey == null) { return null; } final String cookie = !key.equals(tenantKey.toString()) ? key.substring(tenantKey.toString().length()) : null; return new TenantKeyAndCookie(tenantKey, cookie); } } private static final class TenantKeyAndCookie { private final TenantKey tenantKey; private final Object cookie; public TenantKeyAndCookie(final TenantKey tenantKey, final Object cookie) { this.tenantKey = tenantKey; this.cookie = cookie; } public TenantKey getTenantKey() { return tenantKey; } public Object getCookie() { return cookie; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy