/* * JBoss, Home of Professional Open Source * Copyright 2009 Red Hat Inc. and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.interceptors; import org.infinispan.commands.tx.PrepareCommand; import org.infinispan.commands.write.PutKeyValueCommand; import org.infinispan.commands.write.PutMapCommand; import org.infinispan.commands.write.RemoveCommand; import org.infinispan.commands.write.ReplaceCommand; import org.infinispan.container.entries.InternalCacheEntry; import org.infinispan.context.Flag; import org.infinispan.context.InvocationContext; import org.infinispan.context.impl.TxInvocationContext; import org.infinispan.distribution.DistributionManager; import org.infinispan.factories.annotations.Inject; import org.infinispan.factories.annotations.Start; import org.infinispan.remoting.transport.Address; import org.infinispan.remoting.transport.Transport; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import java.util.List; import java.util.Map; /** * Cache store interceptor specific for the distribution cache mode. Put operations has been modified in such way that * if they put operation is the result of an L1 put, storing in the cache store is ignore. This is done so that immortal * entries that get converted into mortal ones when putting into L1 don't get propagated to the cache store. * <p/> * Secondly, in a replicated environment where a shared cache store is used, the node in which the cache operation is * executed is the one responsible for interacting with the cache. This doesn't work with distributed mode and instead, * in a shared cache store situation, the first owner of the key is the one responsible for storing it. * <p/> * In the particular case of putAll(), individual keys are checked and if a shared cache store environment has been * configured, only the first owner of that key will actually store it to the cache store. In a unshared environment * though, only those nodes that are owners of the key would store it to their local cache stores. * * @author Galder ZamarreƱo * @since 4.0 */ public class DistCacheStoreInterceptor extends CacheStoreInterceptor { DistributionManager dm; Transport transport; Address address; private static final Log log = LogFactory.getLog(DistCacheStoreInterceptor.class); @Override protected Log getLog() { return log; } @Inject public void inject(DistributionManager dm, Transport transport) { this.dm = dm; this.transport = transport; } @Start(priority = 25) // after the distribution manager! private void setAddress() { this.address = transport.getAddress(); } // ---- WRITE commands @Override public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable { Object returnValue = invokeNextInterceptor(ctx, command); Object key = command.getKey(); if (skip(ctx, key) || ctx.isInTxScope() || !command.isSuccessful()) return returnValue; InternalCacheEntry se = getStoredEntry(key, ctx); store.store(se); log.tracef("Stored entry %s under key %s", se, key); if (getStatisticsEnabled()) cacheStores.incrementAndGet(); return returnValue; } @Override public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable { Object returnValue = invokeNextInterceptor(ctx, command); if (skip(ctx) || ctx.isInTxScope()) return returnValue; Map<Object, Object> map = command.getMap(); for (Object key : map.keySet()) { if (!skipKey(key)) { InternalCacheEntry se = getStoredEntry(key, ctx); store.store(se); log.tracef("Stored entry %s under key %s", se, key); } } if (getStatisticsEnabled()) cacheStores.getAndAdd(map.size()); return returnValue; } @Override public Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable { Object retval = invokeNextInterceptor(ctx, command); Object key = command.getKey(); if (!skip(ctx, key) && !ctx.isInTxScope() && command.isSuccessful()) { boolean resp = store.remove(key); log.tracef("Removed entry under key %s and got response %s from CacheStore", key, resp); } return retval; } @Override public Object visitReplaceCommand(InvocationContext ctx, ReplaceCommand command) throws Throwable { Object returnValue = invokeNextInterceptor(ctx, command); Object key = command.getKey(); if (skip(ctx, key) || ctx.isInTxScope() || !command.isSuccessful()) return returnValue; InternalCacheEntry se = getStoredEntry(key, ctx); store.store(se); log.tracef("Stored entry %s under key %s", se, key); if (getStatisticsEnabled()) cacheStores.incrementAndGet(); return returnValue; } @Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { if (!skip(ctx)) { if (getLog().isTraceEnabled()) getLog().trace("Transactional so don't put stuff in the cache store yet."); prepareCacheLoader(ctx, command.getGlobalTransaction(), ctx, command.isOnePhaseCommit()); } return invokeNextInterceptor(ctx, command); } /** * Method that skips invocation if: - No store defined or, - The context contains Flag.SKIP_CACHE_STORE or, - The * store is a shared one and node storing the key is not the 1st owner of the key or, - This is an L1 put operation. */ private boolean skip(InvocationContext ctx, Object key) { return skip(ctx) || skipKey(key); } /** * Method that skips invocation if: - No store defined or, - The context contains Flag.SKIP_CACHE_STORE or, */ private boolean skip(InvocationContext ctx) { if (store == null) { log.trace("Skipping cache store because the cache loader does not implement CacheStore"); return true; } if (ctx.hasFlag(Flag.SKIP_CACHE_STORE)) { log.trace("Skipping cache store since the call contain a skip cache store flag"); return true; } if (loaderConfig.isShared() && ctx.hasFlag(Flag.SKIP_SHARED_CACHE_STORE)) { log.trace("Skipping cache store since it is shared and the call contain a skip shared cache store flag"); } return false; } /** * Method that skips invocation if: - The store is a shared one and node storing the key is not the 1st owner of the * key or, - This is an L1 put operation. */ @Override protected boolean skipKey(Object key) { if (loaderConfig.isShared()) { if (!dm.getPrimaryLocation(key).equals(address)) { log.trace("Skipping cache store since the cache loader is shared " + "and the caller is not the first owner of the key"); return true; } } else { List<Address> addresses = dm.locate(key); if (isL1Put(addresses)) { log.trace("Skipping cache store since this is an L1 put"); return true; } } return false; } private boolean isL1Put(List<Address> addresses) { if (address == null) throw new NullPointerException("Local address cannot be null!"); return !addresses.contains(address); } }