package com.tinkerpop.rexster.kibbles.frames;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Element;
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.frames.FramedGraph;
import com.tinkerpop.frames.FramedGraphFactory;
import com.tinkerpop.frames.Property;
import com.tinkerpop.frames.modules.gremlingroovy.GremlinGroovyModule;
import com.tinkerpop.rexster.RexsterResourceContext;
import com.tinkerpop.rexster.extension.AbstractRexsterExtension;
import com.tinkerpop.rexster.extension.ExtensionConfiguration;
import com.tinkerpop.rexster.extension.ExtensionDefinition;
import com.tinkerpop.rexster.extension.ExtensionDescriptor;
import com.tinkerpop.rexster.extension.ExtensionMethod;
import com.tinkerpop.rexster.extension.ExtensionNaming;
import com.tinkerpop.rexster.extension.ExtensionPoint;
import com.tinkerpop.rexster.extension.ExtensionRequestParameter;
import com.tinkerpop.rexster.extension.ExtensionResponse;
import com.tinkerpop.rexster.extension.RexsterContext;
import org.apache.log4j.Logger;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* An extension that exposes Tinkerpop Frames via Rexster. Configuration in rexster.xml looks like:
* <p/>
* <configuration>
* <person>com.tinkerpop.frames.domain.classes.Person</person>
* <project>com.tinkerpop.frames.domain.classes.Project</project>
* </configuration>
*/
@ExtensionNaming(name = FramesExtension.EXTENSION_NAME, namespace = FramesExtension.EXTENSION_NAMESPACE)
public class FramesExtension extends AbstractRexsterExtension {
protected static Logger logger = Logger.getLogger(FramesExtension.class);
public static final String EXTENSION_NAME = "frames";
public static final String EXTENSION_NAMESPACE = "tp";
public static final String TOKEN_BOTH = "both";
public static final String TOKEN_IN = "in";
public static final String TOKEN_OUT = "out";
private FramedGraphFactory factory = new FramedGraphFactory(new GremlinGroovyModule());
@ExtensionDefinition(extensionPoint = ExtensionPoint.EDGE)
@ExtensionDescriptor(description = "Frames extension for an edge.")
public ExtensionResponse doFramesWorkOnEdge(@RexsterContext RexsterResourceContext rexsterResourceContext,
@RexsterContext Graph graph,
@RexsterContext Edge edge,
@ExtensionRequestParameter(name = "direction", description = "the direction of the edge (must be \"" + TOKEN_BOTH + "\", \"" + TOKEN_IN + "\", or \"" + TOKEN_OUT + "\" with the default being \"" + TOKEN_BOTH + "\"") String directionString) {
Direction direction = Direction.BOTH;
if (directionString != null && !directionString.isEmpty()) {
if (directionString.equals(TOKEN_IN)) {
direction = Direction.IN;
} else if (directionString.equals(TOKEN_OUT)) {
direction = Direction.OUT;
} else {
ExtensionMethod extMethod = rexsterResourceContext.getExtensionMethod();
return ExtensionResponse.error(
"the direction parameter must be \"" + TOKEN_BOTH + "\", \"" + TOKEN_IN + "\", or \"" + TOKEN_OUT + "\"",
null,
Response.Status.BAD_REQUEST.getStatusCode(),
null,
generateErrorJson(extMethod.getExtensionApiAsJson()));
}
}
return this.frameItUp(rexsterResourceContext, graph, edge, direction);
}
@ExtensionDefinition(extensionPoint = ExtensionPoint.VERTEX)
@ExtensionDescriptor(description = "Frames extension for a vertex.")
public ExtensionResponse doFramesWorkOnVertex(@RexsterContext RexsterResourceContext rexsterResourceContext,
@RexsterContext Graph graph,
@RexsterContext Vertex vertex) {
return this.frameItUp(rexsterResourceContext, graph, vertex, null);
}
/**
* Frames up a graph element.
*
* @param rexsterResourceContext The Rexster context.
* @param graph The graph from which the element is framed.
* @param element A vertex or an edge.
* @param direction The direction of the edge. Only relevant for frame edges and should be
* set to null for vertices.
* @return The response.
*/
private ExtensionResponse frameItUp(RexsterResourceContext rexsterResourceContext, Graph graph, Element element, Direction direction) {
if (element instanceof Edge && direction == null) {
throw new IllegalArgumentException("Direction cannot be null");
}
ExtensionResponse extensionResponse;
FramedGraph framedGraph = factory.create(graph);
ExtensionConfiguration extensionConfig = rexsterResourceContext.getRexsterApplicationGraph()
.findExtensionConfiguration(EXTENSION_NAMESPACE, EXTENSION_NAME);
Map<String, String> mapFrames = extensionConfig.tryGetMapFromConfiguration();
if (mapFrames != null && !mapFrames.isEmpty()) {
UriInfo uriInfo = rexsterResourceContext.getUriInfo();
List<PathSegment> pathSegmentList = uriInfo.getPathSegments();
String domainObjectMappingName = "";
if (pathSegmentList.size() > 6) {
domainObjectMappingName = pathSegmentList.get(6).getPath();
}
if (domainObjectMappingName.isEmpty()) {
return ExtensionResponse.error(
"A Frames class was not specified in the URI", generateErrorJson());
}
String frameClassName = mapFrames.get(domainObjectMappingName);
if (frameClassName == null || frameClassName.isEmpty()) {
return ExtensionResponse.error(
"Frames configuration does not contain a Frames class for " + domainObjectMappingName, generateErrorJson());
}
try {
Class clazz = Class.forName(frameClassName);
Object obj = null;
if (element instanceof Vertex) {
obj = framedGraph.frame((Vertex) element, clazz);
} else if (element instanceof Edge) {
obj = framedGraph.frame((Edge) element, Direction.BOTH, clazz);
}
Map map = new HashMap();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
// assumes that the properties are unique within a frame
Property property = method.getAnnotation(Property.class);
if (property != null && method.getName().startsWith("get")) {
map.put(property.value(), method.invoke(obj, null));
}
}
extensionResponse = ExtensionResponse.ok(map);
} catch (Exception x) {
logger.error("Frames encountered a problem with " + frameClassName, x);
extensionResponse = ExtensionResponse.error(
"Frames encountered a problem with " + frameClassName, generateErrorJson());
}
} else {
// bad configuration
extensionResponse = ExtensionResponse.error(
"Frames configuration is not valid. Please check rexster.xml", generateErrorJson());
}
return extensionResponse;
}
/**
* By default this returns true. Overriding classes should evaluate the configuration to determine
* if it is correct.
*/
@Override
public boolean isConfigurationValid(ExtensionConfiguration extensionConfiguration) {
boolean valid = false;
if (extensionConfiguration != null) {
Map<String, String> mapFrames = extensionConfiguration.tryGetMapFromConfiguration();
valid = mapFrames != null && !mapFrames.isEmpty() && mapFrames.size() > 0;
}
return valid;
}
}