package com.tinkerpop.frames.modules.javahandler; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Lists; import com.tinkerpop.blueprints.Edge; import com.tinkerpop.blueprints.Element; import com.tinkerpop.blueprints.Vertex; import com.tinkerpop.frames.EdgeFrame; import com.tinkerpop.frames.FrameInitializer; import com.tinkerpop.frames.FramedGraph; import com.tinkerpop.frames.VertexFrame; import com.tinkerpop.frames.util.ExceptionUtils; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.concurrent.ExecutionException; /** * Calls the methods annotated with {@link Initializer} on frame * implementations. * * @author Bryn Cooke * * Implementation made public and fixed because it was completely broken * before, framing the passed Element * to execute the method then ignoring the resulting framed object * */ public class JavaFrameInitializer implements FrameInitializer { private JavaHandlerModule module; /** * Caches lists of InitializerMethods for a given frame class. See "doLoad" * method below. */ private LoadingCache<Class<?>, List<InitializerMethod>> initializerCache = CacheBuilder.newBuilder().build( new CacheLoader<Class<?>, List<InitializerMethod>>() { @Override public List<InitializerMethod> load(final Class<?> frameClass) throws Exception { return doLoad(frameClass); } }); public JavaFrameInitializer(JavaHandlerModule module) { this.module = module; } /** * Captures the work of calling an "@Initializer" annotated method. */ private class InitializerMethod { private final Class<?> h; private final Method method; private InitializerMethod(Class<?> h, Method method) { this.h = h; this.method = method; } void execute(Object framedElement, FramedGraph<?> framedGraph, Element element) throws InvocationTargetException, IllegalAccessException { Object handler = module.createHandler(framedElement, framedGraph, element, h, method); method.invoke(handler); } } public void initElement(Class<?> kind, FramedGraph<?> framedGraph, Object framedElement) { try { for (InitializerMethod method : initializerCache.get(kind)) { try { if (framedElement instanceof VertexFrame) { method.execute(framedElement, framedGraph, ((VertexFrame) framedElement).asVertex()); } else { method.execute(framedElement, framedGraph, ((EdgeFrame) framedElement).asEdge()); } } catch (IllegalArgumentException e) { throw new JavaHandlerException("Problem calling Java handler", e); } catch (IllegalAccessException e) { throw new JavaHandlerException("Problem calling Java handler", e); } catch (InvocationTargetException e) { ExceptionUtils.sneakyThrow(e.getTargetException()); } } } catch (ExecutionException e) { throw new JavaHandlerException("Problem calling Java handler", e); } } @Override public void initElement(Class<?> kind, FramedGraph<?> framedGraph, Element element) { Object framedElement; if (element instanceof Vertex) { framedElement = framedGraph.frame((Vertex) element, kind); } else { framedElement = framedGraph.frame((Edge) element, kind); } try { for (InitializerMethod method : initializerCache.get(kind)) { try { method.execute(framedElement, framedGraph, element); } catch (IllegalArgumentException e) { throw new JavaHandlerException("Problem calling Java handler", e); } catch (IllegalAccessException e) { throw new JavaHandlerException("Problem calling Java handler", e); } catch (InvocationTargetException e) { ExceptionUtils.sneakyThrow(e.getTargetException()); } } } catch (ExecutionException e) { throw new JavaHandlerException("Problem calling Java handler", e); } } /** * Finds all the relevant @Initializer methods for the given class. */ private List<InitializerMethod> doLoad(Class<?> kind) { // We have to order this correctly. Dependencies should be initialised // first so we first recursively collect an an array of classes to call // and then reverse the array before putting them in a linked hash set. // That way the classes discovered last will be called first. List<Class<?>> classes = new ArrayList<Class<?>>(); depthFirstClassSearch(classes, kind); Collections.reverse(classes); LinkedHashSet<Class<?>> hierarchy = new LinkedHashSet<Class<?>>(classes); List<InitializerMethod> methods = Lists.newArrayList(); // Now we can store InitializerMethod objects for each method call. for (Class<?> h : hierarchy) { try { try { Class<?> implKind = module.getHandlerClass(h); for (Method method : implKind.getDeclaredMethods()) { if (method.isAnnotationPresent(Initializer.class)) { if (method.getParameterTypes().length != 0) { throw new JavaHandlerException("Java handler initializer " + method + "cannot have parameters"); } methods.add(new InitializerMethod(h, method)); } } } catch (ClassNotFoundException e) { // There was no impl class to check } } catch (IllegalArgumentException e) { throw new JavaHandlerException("Problem calling Java handler", e); } } return methods; } private void depthFirstClassSearch(List<Class<?>> initializers, Class<?> kind) { if (kind == null || kind == Object.class) { return; } initializers.add(kind); for (Class<?> i : kind.getInterfaces()) { depthFirstClassSearch(initializers, i); } depthFirstClassSearch(initializers, kind.getSuperclass()); } }