package org.infinispan.distribution.group.impl;
import static org.infinispan.commons.util.ReflectionUtil.invokeAccessibly;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import org.infinispan.commons.util.CollectionFactory;
import org.infinispan.commons.util.ReflectionUtil;
import org.infinispan.commons.util.Util;
import org.infinispan.distribution.DistributionManager;
import org.infinispan.distribution.group.Group;
import org.infinispan.distribution.group.Grouper;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.remoting.transport.Address;
public class GroupManagerImpl implements GroupManager {
private interface GroupMetadata {
GroupMetadata NONE = instance -> null;
String getGroup(Object instance);
}
private static class GroupMetadataImpl implements GroupMetadata {
private final Method method;
GroupMetadataImpl(Method method) {
if (!String.class.isAssignableFrom(method.getReturnType()))
throw new IllegalArgumentException(Util.formatString("@Group method %s must return java.lang.String", method));
if (method.getParameterTypes().length > 0)
throw new IllegalArgumentException(Util.formatString("@Group method %s must have zero arguments", method));
this.method = method;
}
@Override
public String getGroup(Object instance) {
Object object;
if (System.getSecurityManager() == null) {
object = invokeAccessibly(instance, method, Util.EMPTY_OBJECT_ARRAY);
} else {
object = AccessController.doPrivileged((PrivilegedAction<Object>) () -> invokeAccessibly(instance, method, Util.EMPTY_OBJECT_ARRAY));
}
return String.class.cast(object);
}
}
private static GroupMetadata createGroupMetadata(Class<?> clazz) {
Collection<Method> possibleMethods;
if (System.getSecurityManager() == null) {
possibleMethods = ReflectionUtil.getAllMethods(clazz, Group.class);
} else {
possibleMethods = AccessController.doPrivileged((PrivilegedAction<List<Method>>) () -> ReflectionUtil.getAllMethods(clazz, Group.class));
}
if (possibleMethods.isEmpty())
return GroupMetadata.NONE;
else if (possibleMethods.size() == 1)
return new GroupMetadataImpl(possibleMethods.iterator().next());
else
throw new IllegalStateException(Util.formatString("Cannot define more that one @Group method for class hierarchy rooted at %s", clazz.getName()));
}
private final ConcurrentMap<Class<?>, GroupMetadata> groupMetadataCache;
private final List<Grouper<?>> groupers;
private DistributionManager distributionManager;
public GroupManagerImpl(List<Grouper<?>> groupers) {
this.groupMetadataCache = CollectionFactory.makeConcurrentMap();
if (groupers != null)
this.groupers = groupers;
else
this.groupers = Collections.emptyList();
}
@Inject
public void injectDependencies(DistributionManager distributionManager) {
this.distributionManager = distributionManager;
}
@Override
public String getGroup(Object key) {
GroupMetadata metadata = getMetadata(key);
if (metadata != null) {
return applyGroupers(metadata.getGroup(key), key);
} else
return applyGroupers(null, key);
}
@Override
public boolean isOwner(String group) {
return distributionManager.getCacheTopology().isWriteOwner(group);
}
@Override
public Address getPrimaryOwner(String group) {
return distributionManager.getCacheTopology().getDistribution(group).primary();
}
@Override
public boolean isPrimaryOwner(String group) {
return distributionManager.getCacheTopology().getDistribution(group).isPrimary();
}
private String applyGroupers(String group, Object key) {
for (Grouper<?> grouper : groupers) {
if (grouper.getKeyType().isAssignableFrom(key.getClass()))
group = ((Grouper<Object>) grouper).computeGroup(key, group);
}
return group;
}
private GroupMetadata getMetadata(final Object key) {
final Class<?> keyClass = key.getClass();
GroupMetadata groupMetadata = groupMetadataCache.get(keyClass);
if (groupMetadata == null) {
//this is not ideal as it is possible for the group metadata to be redundantly calculated several times.
//however profiling showed that using the Map<Class,Future> cache-approach is significantly slower on
// the long run
groupMetadata = createGroupMetadata(keyClass);
GroupMetadata previous = groupMetadataCache.putIfAbsent(keyClass, groupMetadata);
if (previous != null) {
// in case another thread added a metadata already, discard what we created and reuse the existing.
return previous;
}
}
return groupMetadata;
}
}