package org.ovirt.engine.api.rsdl; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.DELETE; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import org.ovirt.engine.api.resource.ActionResource; import org.ovirt.engine.api.resource.CreationResource; import org.ovirt.engine.api.resource.SystemResource; public class ServiceTree { private static boolean SUB_RESOURCES = true; private static boolean ACTIONS = false; /** * Some nodes will repeat themselves in the API tree. This map holds * references to Nodes by the service class, allowing getting a node * in O(1) with no need to traverse the tree. */ private static Map<Class<?>, ServiceTreeNode> nodes = new HashMap<>(); /** * Representation of the API tree */ private static ServiceTreeNode tree = buildTree(); private static ServiceTreeNode buildTree() { return buildNode(SystemResource.class, ""); } public static ServiceTreeNode getTree() { return tree; } /** * Build the API tree */ private static ServiceTreeNode buildNode(Class<?> resource, String path) { ServiceTreeNode node = new ServiceTreeNode.Builder() .name(resource.getSimpleName()) .path(path) .subCollections(getSubServices(resource)) .actions(getActions(resource)) .build(); if (!nodes.containsKey(resource)) { nodes.put(resource, node); } return node; } /** * Get all methods in the provided 'Resource', which lead to a sub-resource. * For example, for VmResource this method should return getDisksResource(), * getCdRomsResource(), etc. */ public static List<ServiceTreeNode> getSubServices(Class<?> resource) { List<ServiceTreeNode> resources = new ArrayList<>(); for (Method method : getMethods(resource, SUB_RESOURCES)) { Path path = method.getAnnotation(Path.class); if (!method.getReturnType().equals(ActionResource.class) && !method.getReturnType().equals(CreationResource.class)) { resources.add(buildNode(method.getReturnType(), path.value())); } } return resources; } private static List<String> getActions(Class<?> resource) { List<String> resourceMethods = new ArrayList<>(); for (Method method : getMethods(resource, ACTIONS)) { resourceMethods.add(method.getName()); } return resourceMethods; } public static List<Method> getMethods(Class<?> clazz, boolean subService) { List<Method> methods = new ArrayList<>(); for (Method method : clazz.getMethods()) { if (isBlacklist(method)) { continue; } //XOR achieves the required behavior if (isSubService(method) ^ !subService) { methods.add(method); } } return methods; } public static ServiceTreeNode getNode(Class<?> service) { return nodes.get(service); } /** * Returns true if the provided method should be ignored * in the process of building the API tree. */ private static boolean isBlacklist(Method method) { return method.getName().equals("getActionService"); } /** * This method returns 'true' if the provided method's return- * value is a 'sub-resource'. For example, true should be * VmResource.getCdRomsResource(), but false should be returned * for VmResource.start(). */ private static boolean isSubService(Method method) { /* * Methods which return sub-resources are always expected to * be annotated with @Path, but never with any of the HTTP * annotations: @POST, @PUT, @DELETE, @GET. */ return hasPathAnnotation(method) && !hasHttpAnnotation(method); } private static boolean hasPathAnnotation(Method method) { return method.isAnnotationPresent(Path.class); } private static boolean hasHttpAnnotation(Method method) { return method.isAnnotationPresent(POST.class) || method.isAnnotationPresent(PUT.class) || method.isAnnotationPresent(DELETE.class); } }