/*
* 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.loaders.cluster;
import org.infinispan.AdvancedCache;
import org.infinispan.Cache;
import org.infinispan.commands.remote.ClusteredGetCommand;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.container.entries.InternalCacheValue;
import org.infinispan.context.InvocationContext;
import org.infinispan.lifecycle.ComponentStatus;
import org.infinispan.loaders.AbstractCacheLoader;
import org.infinispan.loaders.CacheLoaderConfig;
import org.infinispan.loaders.CacheLoaderException;
import org.infinispan.loaders.CacheLoaderMetadata;
import org.infinispan.marshall.StreamingMarshaller;
import org.infinispan.remoting.responses.ClusteredGetResponseValidityFilter;
import org.infinispan.remoting.responses.Response;
import org.infinispan.remoting.responses.SuccessfulResponse;
import org.infinispan.remoting.rpc.ResponseFilter;
import org.infinispan.remoting.rpc.ResponseMode;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.remoting.transport.Address;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import static java.util.Collections.emptySet;
/**
* Cache loader that consults other members in the cluster for values. A <code>remoteCallTimeout</code> property is
* required, a <code>long</code> that specifies in milliseconds how long to wait for results before returning a null.
*
* @author Mircea.Markus@jboss.com
*/
@CacheLoaderMetadata(configurationClass = ClusterCacheLoaderConfig.class)
public class ClusterCacheLoader extends AbstractCacheLoader {
private static final Log log = LogFactory.getLog(ClusterCacheLoader.class);
private ClusterCacheLoaderConfig config;
private RpcManager rpcManager;
private AdvancedCache<?, ?> cache;
@Override
public void init(CacheLoaderConfig config, Cache<?, ?> cache, StreamingMarshaller m) {
this.config = (ClusterCacheLoaderConfig) config;
this.cache = cache.getAdvancedCache();
rpcManager = this.cache.getRpcManager();
}
@Override
public InternalCacheEntry load(Object key) throws CacheLoaderException {
if (!(isCacheReady() && isLocalCall())) return null;
ClusteredGetCommand clusteredGetCommand = new ClusteredGetCommand(key, cache.getName());
Collection<Response> responses = doRemoteCall(clusteredGetCommand);
if (responses.isEmpty()) return null;
Response response;
if (responses.size() > 1) {
// Remove duplicates before deciding if multiple responses were received
Set<Response> setResponses = new HashSet<Response>(responses);
if (setResponses.size() > 1)
throw new CacheLoaderException(String.format(
"Responses contains more than 1 element and these elements are not equal, so can't decide which one to use: %s",
setResponses));
response = setResponses.iterator().next();
} else {
response = responses.iterator().next();
}
if (response.isSuccessful() && response instanceof SuccessfulResponse) {
InternalCacheValue value = (InternalCacheValue) ((SuccessfulResponse) response).getResponseValue();
return value.toInternalCacheEntry(key);
}
log.unknownResponsesFromRemoteCache(responses);
throw new CacheLoaderException("Unknown responses");
}
@Override
@SuppressWarnings(value = "unchecked")
public Set<InternalCacheEntry> loadAll() throws CacheLoaderException {
return emptySet();
}
@Override
public Set<InternalCacheEntry> load(int maxElems) throws CacheLoaderException {
return emptySet();
}
@Override
public Set<Object> loadAllKeys(Set<Object> keysToExclude) throws CacheLoaderException {
return emptySet();
}
@Override
public void start() throws CacheLoaderException {
//nothing to do here
}
@Override
public void stop() throws CacheLoaderException {
//nothing to do here
}
@Override
public Class<? extends CacheLoaderConfig> getConfigurationClass() {
return ClusterCacheLoaderConfig.class;
}
private Collection<Response> doRemoteCall(ClusteredGetCommand clusteredGetCommand) throws CacheLoaderException {
Set<Address> members = new HashSet<Address>(rpcManager.getTransport().getMembers());
Address self = rpcManager.getTransport().getAddress();
ResponseFilter filter = new ClusteredGetResponseValidityFilter(members, self);
try {
return rpcManager.invokeRemotely(null, clusteredGetCommand, ResponseMode.WAIT_FOR_VALID_RESPONSE,
config.getRemoteCallTimeout(), false, filter, false).values();
} catch (Exception e) {
log.errorDoingRemoteCall(e);
throw new CacheLoaderException(e);
}
}
private boolean isLocalCall() {
InvocationContext invocationContext = cache.getInvocationContextContainer().getInvocationContext(false);
return invocationContext.isOriginLocal();
}
/**
* A test to check whether the cache is in its started state. If not, calls should not be made as the channel may
* not have properly started, blocks due to state transfers may be in progress, etc.
*
* @return true if the cache is in its STARTED state.
*/
protected boolean isCacheReady() {
return cache.getStatus() == ComponentStatus.RUNNING;
}
}