package org.radargun.service; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import com.hazelcast.core.IMap; import com.hazelcast.core.TransactionalMap; import com.hazelcast.query.Predicate; import org.radargun.logging.Log; import org.radargun.logging.LogFactory; import org.radargun.traits.Query; import org.radargun.utils.OptimizedMap; public class HazelcastQuery implements Query { private static final Log log = LogFactory.getLog(HazelcastQuery.class); private final Predicate predicate; private final int offset; private final String[] projection; public HazelcastQuery(Predicate predicate, int offset, String[] projection) { this.predicate = predicate; this.offset = offset; this.projection = projection; } @Override public Query.Result execute(Query.Context context) { Context impl = (Context) context; Collection values; if (predicate == null) { values = impl.map != null ? impl.map.values() : impl.txMap.values(); } else { values = impl.map != null ? impl.map.values(predicate) : impl.txMap.values(predicate); } return new Result(values, offset, projection); } public Predicate getPredicate() { return predicate; } public static class Context implements Query.Context { protected final IMap map; protected final TransactionalMap txMap; public Context(IMap map) { this.map = map; this.txMap = null; } public Context(TransactionalMap map) { this.map = null; this.txMap = map; } @Override public void close() { } } public static class Result implements Query.Result { private final Collection values; public Result(Collection<Object> values, int offset, String[] projection) { if (offset > 0) { values = values.stream().skip(offset).collect(Collectors.toList()); } if (projection != null) { values = values.stream().map(new ReflectionProjector(projection)).collect(Collectors.toList()); } this.values = values; } @Override public int size() { return values.size(); } @Override public Collection values() { return Collections.unmodifiableCollection(values); } } private static class ReflectionProjector implements Function<Object, Object> { private final String[] projection; private transient Map<Class<?>, ArrayList<Accessor>> accessorMap = new OptimizedMap<>(); public ReflectionProjector(String[] projection) { this.projection = projection; } // magic deserialization method private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.readObject(); accessorMap = new OptimizedMap<>(); } @Override public Object apply(Object o) { Class<?> clazz = o.getClass(); ArrayList<Accessor> accessors = accessorMap.get(clazz); if (accessors == null) { accessors = new ArrayList<Accessor>(); for (String attribute : projection) { accessors.add(getAccessor(clazz, attribute)); } accessorMap.put(clazz, accessors); } Object[] projected = new Object[projection.length]; int i = 0; for (Accessor accessor : accessors) { projected[i] = accessor.get(o); ++i; } return projected; } } public static Accessor getAccessor(Class<?> clazz, String attribute) { try { ArrayList<Accessor> list = new ArrayList<Accessor>(); for (String attributePart : attribute.split("\\.")) { Field f = clazz.getDeclaredField(attribute); if (f != null) { f.setAccessible(true); list.add(new FieldAccessor(f)); clazz = f.getType(); continue; } Method m = clazz.getDeclaredMethod("get" + Character.toUpperCase(attributePart.charAt(0)) + attributePart.substring(1)); if (m == null) { m = clazz.getMethod("is" + Character.toUpperCase(attributePart.charAt(0)) + attributePart.substring(1)); } if (m != null) { m.setAccessible(true); list.add(new MethodAccessor(m)); clazz = m.getReturnType(); continue; } throw new IllegalArgumentException("Cannot find attribute part " + attributePart + " in " + clazz); } if (list.size() == 1) return list.get(0); else return new ChainedAccessor(list); } catch (Exception e) { log.debug("Cannot access attribute " + attribute, e); throw new RuntimeException(e); } } public interface Accessor { Object get(Object o); Class<?> getReturnType(); } public static class FieldAccessor implements Accessor, Externalizable { public Field f; public FieldAccessor() {} private FieldAccessor(Field f) { this.f = f; } @Override public Object get(Object o) { try { return f.get(o); } catch (IllegalAccessException e) { log.debug("Cannot access field " + f.getDeclaringClass() + "." + f.getName(), e); throw new RuntimeException(e); } } @Override public Class<?> getReturnType() { return f.getType(); } @Override public void writeExternal(ObjectOutput objectOutput) throws IOException { objectOutput.writeObject(f.getDeclaringClass()); objectOutput.writeObject(f.getName()); } @Override public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException { try { f = ((Class) objectInput.readObject()).getDeclaredField((String) objectInput.readObject()); f.setAccessible(true); } catch (NoSuchFieldException e) { e.printStackTrace(); } } } public static class MethodAccessor implements Accessor, Externalizable { public Method m; public MethodAccessor() {} private MethodAccessor(Method m) { this.m = m; } @Override public Object get(Object o) { try { return m.invoke(o); } catch (Exception e) { log.debug("Cannot invoke method " + m.getDeclaringClass() + "." + m.getName(), e); throw new RuntimeException(e); } } @Override public Class<?> getReturnType() { return m.getReturnType(); } @Override public void writeExternal(ObjectOutput objectOutput) throws IOException { objectOutput.writeObject(m.getDeclaringClass()); objectOutput.writeObject(m.getName()); } @Override public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException { try { m = ((Class) objectInput.readObject()).getDeclaredMethod((String) objectInput.readObject()); m.setAccessible(true); } catch (NoSuchMethodException e) { e.printStackTrace(); } } } public static class ChainedAccessor implements Accessor, Externalizable { public List<Accessor> accessors; public ChainedAccessor() {} public ChainedAccessor(List<Accessor> list) { this.accessors = list; } @Override public Object get(Object o) { for (Accessor a : accessors) { o = a.get(o); } return o; } @Override public Class<?> getReturnType() { return accessors.get(accessors.size() - 1).getReturnType(); } @Override public void writeExternal(ObjectOutput objectOutput) throws IOException { objectOutput.writeObject(accessors); } @Override public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException { accessors = (List<Accessor>) objectInput.readObject(); } } abstract static class ReflexiveComparator implements Comparator<Map.Entry>, Serializable { protected transient Map<Class, Accessor> accessors = new OptimizedMap<>(); protected final String attribute; protected ReflexiveComparator(String attribute) { this.attribute = attribute; } // magic deserialization method private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); accessors = new OptimizedMap<>(); } @Override public int compare(Map.Entry e1, Map.Entry e2) { try { Comparable o1 = (Comparable) extractValue(e1.getValue()); Comparable o2 = (Comparable) extractValue(e2.getValue()); return compare(o1, o2); } catch (Exception e) { throw new IllegalArgumentException("Cannot extract " + attribute + " from " + e1.getValue() + " or " + e2.getValue(), e); } } private Object extractValue(Object o) { Class<?> clazz = o.getClass(); Accessor accessor = accessors.get(clazz); if (accessor == null) { accessors.put(clazz, accessor = getAccessor(clazz, attribute)); } return accessor.get(o); } protected abstract int compare(Comparable o1, Comparable o2); } static class RegularComparator extends ReflexiveComparator { public RegularComparator(String attribute) { super(attribute); } @Override protected int compare(Comparable o1, Comparable o2) { return o1.compareTo(o2); } } static class InverseComparator extends ReflexiveComparator { public InverseComparator(String attribute) { super(attribute); } @Override protected int compare(Comparable o1, Comparable o2) { return -o1.compareTo(o2); } } }