com.oracle.bedrock.runtime.coherence.CoherenceNamedCache Maven / Gradle / Ivy
Show all versions of coherence-12.1.2 Show documentation
/*
* File: CoherenceNamedCache.java
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* The contents of this file are subject to the terms and conditions of
* the Common Development and Distribution License 1.0 (the "License").
*
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the License by consulting the LICENSE.txt file
* distributed with this file, or by consulting https://oss.oracle.com/licenses/CDDL
*
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file LICENSE.txt.
*
* MODIFICATIONS:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*/
package com.oracle.bedrock.runtime.coherence;
import com.oracle.bedrock.runtime.Assembly;
import com.oracle.bedrock.runtime.concurrent.callable.RemoteCallableStaticMethod;
import com.oracle.bedrock.runtime.concurrent.callable.RemoteMethodInvocation;
import com.oracle.bedrock.util.ReflectionHelper;
import com.tangosol.net.CacheService;
import com.tangosol.net.NamedCache;
import com.tangosol.util.Filter;
import com.tangosol.util.MapListener;
import com.tangosol.util.ValueExtractor;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
/**
* A {@link NamedCache} implementation that provides a local representation and
* thus allows interaction with a cache defined with in a {@link CoherenceClusterMember}.
*
* Copyright (c) 2014. All Rights Reserved. Oracle Corporation.
* Oracle is a registered trademark of Oracle Corporation and/or its affiliates.
*
* @author Brian Oliver
*/
class CoherenceNamedCache implements NamedCache
{
/**
* The {@link CoherenceClusterMember} that owns the {@link NamedCache}
* that this {@link CoherenceNamedCache} represents.
*/
private CoherenceClusterMember member;
/**
* The {@link Optional} {@link CoherenceCluster} that owns the {@link CoherenceClusterMember}.
* (this is optional as the {@link CoherenceClusterMember} may not have been defined as part of a cluster).
*/
private Optional cluster;
/**
* The name of the {@link NamedCache}.
*/
private final String cacheName;
/**
* The {@link RemoteCallableStaticMethod} to use in the
* {@link CoherenceClusterMember} to acquire the {@link NamedCache}.
*/
private final RemoteCallableStaticMethod producer;
/**
* The {@link RemoteMethodInvocation.Interceptor} to use for intercepting
* and transforming remote method invocations.
*/
private final RemoteMethodInvocation.Interceptor interceptor;
/**
* Constructs a {@link CoherenceNamedCache}.
*
* @param member the {@link CoherenceClusterMember} that owns the {@link NamedCache}
* @param cacheName the name of the {@link NamedCache}
*/
public CoherenceNamedCache(CoherenceClusterMember member,
String cacheName)
{
this.member = member;
this.cacheName = cacheName;
this.producer = new RemoteCallableStaticMethod("com.tangosol.net.CacheFactory",
"getCache",
cacheName);
this.interceptor = new NamedCacheMethodInterceptor();
// determine the CoherenceCluster that the CoherenceClusterMember is part of
Assembly assembly = member.get(Assembly.class);
this.cluster = assembly instanceof CoherenceCluster
? Optional.of((CoherenceCluster) assembly) : Optional.empty();
}
/**
* Invoke the specified method remotely in the {@link CoherenceClusterMember} on the
* {@link NamedCache} provided by the {@link #producer}.
*
* @param methodName the name of the method
* @param arguments the arguments for the method
*
* @return the result of the remote method execution
*
* @throws RuntimeException if any exception occurs remotely
*/
protected T remotelyInvoke(String methodName,
Object... arguments)
{
// notify the interceptor that we're about make a remote invocation
Method method = ReflectionHelper.getCompatibleMethod(NamedCache.class, methodName, arguments);
interceptor.onBeforeRemoteInvocation(method, arguments);
int retryCount = 0;
// we try the request until we've run out of operational cluster members
while (retryCount < (cluster.isPresent() ? cluster.get().count() : 1))
{
// we'll need to choose a new member to perform the request when the current one is no longer operational
// or we have to retry
boolean chooseNewMember = !member.isOperational() || retryCount > 0;
if (chooseNewMember)
{
if (cluster.isPresent())
{
Optional optional = cluster.get().findAny();
if (optional.isPresent())
{
this.member = optional.get();
}
else
{
// there's no longer a member we can use
throw new IllegalStateException("The underlying Cluster no longer has available Cluster Members to perform the request ["
+ methodName + "]");
}
}
else
{
// we just re-throw if the member is not part of a cluster
throw new IllegalStateException("The underlying Cluster Member [" + member.getName()
+ "] is no longer available to perform the request [" + methodName
+ "]");
}
}
try
{
// submit the remote method invocation
CompletableFuture future = member.submit(new RemoteMethodInvocation(producer,
methodName,
arguments,
interceptor));
// intercept the result after the remote invocation
return (T) interceptor.onAfterRemoteInvocation(method, arguments, future.get());
}
catch (IllegalStateException e)
{
// retry the request with a different cluster member when the request when the submission fails
retryCount++;
}
catch (RuntimeException e)
{
// re-throw runtime exceptions
throw e;
}
catch (Exception e)
{
throw new RuntimeException("Failed to execute [" + methodName + "] with arguments "
+ Arrays.toString(arguments),
interceptor.onRemoteInvocationException(method, arguments, e));
}
}
throw new IllegalStateException("Failed to perform request [" + methodName + "] using [" + retryCount
+ "] Cluster Members");
}
@Override
public String getCacheName()
{
return cacheName;
}
@Override
public CacheService getCacheService()
{
throw new UnsupportedOperationException("The method NamedCache.getCacheService is not supported for remote execution");
}
@Override
public boolean isActive()
{
return remotelyInvoke("isActive");
}
@Override
public void release()
{
remotelyInvoke("release");
}
@Override
public void destroy()
{
remotelyInvoke("destroy");
}
@Override
public Object put(Object key,
Object value,
long expiry)
{
return remotelyInvoke("put", key, value, expiry);
}
@Override
public Map getAll(Collection keys)
{
return remotelyInvoke("getAll", keys);
}
@Override
public boolean lock(Object key,
long duration)
{
return remotelyInvoke("lock", key, duration);
}
@Override
public boolean lock(Object key)
{
return remotelyInvoke("lock", key);
}
@Override
public boolean unlock(Object key)
{
return remotelyInvoke("unlock", key);
}
@Override
public Object invoke(Object key,
EntryProcessor processor)
{
return remotelyInvoke("invoke", key, processor);
}
@Override
public Map invokeAll(Collection keys,
EntryProcessor processor)
{
return remotelyInvoke("invokeAll", keys, processor);
}
@Override
public Map invokeAll(Filter filter,
EntryProcessor processor)
{
return remotelyInvoke("invokeAll", filter, processor);
}
@Override
public Object aggregate(Collection collection,
EntryAggregator aggregator)
{
return remotelyInvoke("aggregate", collection, aggregator);
}
@Override
public Object aggregate(Filter filter,
EntryAggregator aggregator)
{
return remotelyInvoke("aggregate", filter, aggregator);
}
@Override
public void addMapListener(MapListener listener)
{
throw new UnsupportedOperationException("The method NamedCache.addMapListener is not supported for remote execution");
}
@Override
public void removeMapListener(MapListener listener)
{
throw new UnsupportedOperationException("The method NamedCache.removeMapListener is not supported for remote execution");
}
@Override
public void addMapListener(MapListener listener,
Object key,
boolean lite)
{
throw new UnsupportedOperationException("The method NamedCache.addMapListener is not supported for remote execution");
}
@Override
public void removeMapListener(MapListener listener,
Object key)
{
throw new UnsupportedOperationException("The method NamedCache.removeMapListener is not supported for remote execution");
}
@Override
public void addMapListener(MapListener listener,
Filter filter,
boolean lite)
{
throw new UnsupportedOperationException("The method NamedCache.addMapListener is not supported for remote execution");
}
@Override
public void removeMapListener(MapListener listener,
Filter filter)
{
throw new UnsupportedOperationException("The method NamedCache.removeMapListener is not supported for remote execution");
}
@Override
public Set keySet(Filter filter)
{
return remotelyInvoke("keySet", filter);
}
@Override
public Set entrySet(Filter filter)
{
return remotelyInvoke("entrySet", filter);
}
@Override
public Set entrySet(Filter filter,
Comparator comparator)
{
return remotelyInvoke("entrySet", filter, comparator);
}
@Override
public void addIndex(ValueExtractor valueExtractor,
boolean ordered,
Comparator comparator)
{
remotelyInvoke("addIndex", valueExtractor, ordered, comparator);
}
@Override
public void removeIndex(ValueExtractor valueExtractor)
{
remotelyInvoke("removeIndex", valueExtractor);
}
@Override
public int size()
{
return remotelyInvoke("size");
}
@Override
public boolean isEmpty()
{
return remotelyInvoke("isEmpty");
}
@Override
public boolean containsKey(Object key)
{
return remotelyInvoke("containsKey", key);
}
@Override
public boolean containsValue(Object value)
{
return remotelyInvoke("containsValue", value);
}
@Override
public Object get(Object key)
{
return remotelyInvoke("get", key);
}
@Override
public Object put(Object key,
Object value)
{
return remotelyInvoke("put", key, value);
}
@Override
public Object remove(Object key)
{
return remotelyInvoke("remove", key);
}
@Override
public void putAll(Map map)
{
remotelyInvoke("putAll", map);
}
@Override
public void clear()
{
remotelyInvoke("clear");
}
@Override
public Set keySet()
{
return remotelyInvoke("keySet");
}
@Override
public Collection values()
{
return remotelyInvoke("values");
}
@Override
public Set entrySet()
{
return remotelyInvoke("entrySet");
}
/**
* A Coherence specific {@link RemoteMethodInvocation.Interceptor} for {@link NamedCache} methods.
*/
public static class NamedCacheMethodInterceptor implements RemoteMethodInvocation.Interceptor
{
@Override
public void onBeforeRemoteInvocation(Method method,
Object[] arguments)
{
// ensure that the arguments for specific methods are serializable
String name = method.getName();
if ((name.equals("getAll") || name.equals("invokeAll") || name.equals("aggregate"))
&&!(arguments[0] instanceof Serializable))
{
// ensure invocations of NamedCache.getAll / invokeAll / aggregate using collections are serializable
arguments[0] = new ArrayList((Collection) arguments[0]);
}
else if (name.equals("putAll") &&!(arguments[0] instanceof Serializable))
{
arguments[0] = new HashMap((Map) arguments[0]);
}
}
@Override
public Object onAfterRemoteInvocation(Method method,
Object[] arguments,
Object result)
{
return result;
}
@Override
public Exception onRemoteInvocationException(Method method,
Object[] arguments,
Exception exception)
{
return exception;
}
@Override
public void onBeforeInvocation(Object instance,
Method method,
Object[] arguments)
{
// nothing to do before invocation
}
@Override
public Object onAfterInvocation(Object instance,
Method method,
Object[] arguments,
Object result)
{
// ensure that the result of the method is serializable, including transforming it if necessary
String name = method.getName();
if (name.equals("invokeAll") || name.equals("getAll"))
{
// the result of invokeAll may not be serializable,
// so copy them into a serializable map
result = new HashMap((Map) result);
}
else if (name.equals("keySet"))
{
// the result of keySet may not be serializable,
// so copy them into a serializable set
result = new HashSet((Set) result);
}
else if (name.equals("entrySet"))
{
// the result of entrySet may not be serializable,
// so copy the entries into a serializable set
Set set = (Set) result;
Set resultSet = new HashSet();
for (Map.Entry entry : set)
{
resultSet.add(new AbstractMap.SimpleEntry(entry.getKey(), entry.getValue()));
}
result = resultSet;
}
else if (name.equals("values"))
{
// the result of values may not be serializable,
// so copy them into a serializable set
result = new ArrayList((Collection) result);
}
return result;
}
@Override
public Exception onInvocationException(Object instance,
Method method,
Object[] arguments,
Exception exception)
{
return exception;
}
}
}