package org.ovirt.engine.api.restapi.resource.validation; import java.io.IOException; import java.util.List; import javax.ws.rs.core.PathSegment; import javax.ws.rs.core.Request; import javax.ws.rs.core.UriInfo; import org.ovirt.engine.api.model.Fault; import org.ovirt.engine.api.rsdl.ServiceTree; import org.ovirt.engine.api.rsdl.ServiceTreeNode; public class UsageFinder { private static final String RESPONSE = "Request syntactically incorrect."; private static final String CAMEL_CASE_REGEX = "(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])"; public Fault getUsageMessage(UriInfo uriInfo, Request request) throws ClassNotFoundException, IOException { Fault fault = new Fault(); fault.setReason(RESPONSE); fault.setDetail("For correct usage, see: " + getUsageLink(uriInfo, request.getMethod())); return fault; } private String getUsageLink(UriInfo uriInfo, String httpMethod) { List<PathSegment> pathSegments = uriInfo.getPathSegments(); ServiceTreeNode node = ServiceTree.getTree(); //step into the Service tree according to the URL. for (PathSegment pathSegment : pathSegments) { node = step(node, pathSegment); } //check whether the last step in the URL represent an 'action'. PathSegment lastPathSegment = pathSegments.get(pathSegments.size()-1); //Get the prefix of the link, with or without 's' appended to the //entity name, according to whether this action is on a single entity //or on the collection context, e.g: // .../api/model.html#services/vm/methods/start //action on *vm* // .../api/model.html#services/vms/methods/add //action on *vms* // .../api/model.html#services/vm/methods/update //action on *vm* // .../api/model.html#services/vm/methods/remove //action on *vm* String link = getLinkPrefix(uriInfo, node, lastPathSegment.getPath(), httpMethod); if (isAction(node, lastPathSegment.getPath())) { link += camelCaseToDash(getAction(node, lastPathSegment.getPath())); } else { link += getMethodName(httpMethod); } return link; } private String camelCaseToDash(String path) { return path.replaceAll("(.)(\\p{Upper})", "$1-$2").toLowerCase(); } private boolean isAction(ServiceTreeNode node, String path) { return node.containsAction(path); } /** * Gets all-lowercase action name, and if this action exists * in the node, returns it properly CamelCased, e.g: * For ClusterService node: * provide: "resetemulatedmachine" * receive: "resetEmulatedMachine" */ private String getAction(ServiceTreeNode node, String path) { return node.getActions().stream() .filter(action -> action.toLowerCase().equals(path)) .findFirst() .orElse(null); } private String getLinkPrefix(UriInfo uriInfo, ServiceTreeNode node, String lastPathSegment, String httpMethod) { String linkPrefix = uriInfo.getBaseUri().toString() + "model#services/" + processNodeName(node) + "/methods/"; return linkPrefix; } private String processNodeName(ServiceTreeNode node) { String[] parts = node.getName().replaceAll("Resource$", "").split(CAMEL_CASE_REGEX); StringBuilder builder = new StringBuilder(""); for (String part : parts) { builder.append(part.toLowerCase()).append("-"); } String name = builder.toString(); return name.substring(0, name.length() -1); } private String getMethodName(String httpMethod) { switch (httpMethod) { case "POST": return "add"; case "PUT": return "update"; case "GET": return "get"; case "DELETE": return "remove"; default: return ""; //shouldn't reach here. } } private ServiceTreeNode step(ServiceTreeNode node, PathSegment pathSegment) { if (isID(pathSegment.getPath(), node)) { return node.getSubService("{id}"); } else { if (node.containsSubService(pathSegment.getPath())) { return node.getSubService(pathSegment.getPath()); } else { return node; } } } private boolean isID(String segment, ServiceTreeNode node) { //the provided string is assumed to be an ID if the //node has no action or sub-service by this name. return getAction(node, segment) == null && !node.containsSubService(segment); } }