com.tinkerpop.rexster.RexsterApplicationGraph Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rexster-core Show documentation
Show all versions of rexster-core Show documentation
Core components for extending Rexster.
package com.tinkerpop.rexster;
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.TransactionalGraph;
import com.tinkerpop.blueprints.util.wrappers.WrapperGraph;
import com.tinkerpop.rexster.extension.ExtensionAllowed;
import com.tinkerpop.rexster.extension.ExtensionApi;
import com.tinkerpop.rexster.extension.ExtensionApiBehavior;
import com.tinkerpop.rexster.extension.ExtensionConfiguration;
import com.tinkerpop.rexster.extension.ExtensionDefinition;
import com.tinkerpop.rexster.extension.ExtensionDescriptor;
import com.tinkerpop.rexster.extension.ExtensionNaming;
import com.tinkerpop.rexster.extension.ExtensionPoint;
import com.tinkerpop.rexster.extension.ExtensionRequestParameter;
import com.tinkerpop.rexster.extension.ExtensionSegmentSet;
import com.tinkerpop.rexster.extension.RexsterExtension;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.log4j.Logger;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
/**
* Holds a graph and its assigned extensions. This wrapper is what is supplied to the RexsterResourceContext
* to be passed to extensions and other Rexster resources.
*
* @author Stephen Mallette (http://stephen.genoprime.com)
*/
public class RexsterApplicationGraph {
private static final Logger logger = Logger.getLogger(RexsterApplicationGraph.class);
private static final String QUERY_STRING_PARAMETER_NAME = "name";
private static final String QUERY_STRING_PARAMETER_DESCRIPTION = "description";
private final Graph graph;
private final String graphName;
private Set extensionAllowables;
private Set extensionConfigurations;
private static final ServiceLoader extends RexsterExtension> extensions = ServiceLoader.load(RexsterExtension.class);
private final Map>> hypermediaCache = new HashMap>>();
private final Map extensionAllowedCache = new HashMap();
public RexsterApplicationGraph(final String graphName, final Graph graph) {
this(graphName, graph, null);
}
public RexsterApplicationGraph(final String graphName, final Graph graph,
final HierarchicalConfiguration graphConfig) {
this(graphName, graph, graphConfig == null ? null : graphConfig.getList(Tokens.REXSTER_GRAPH_EXTENSIONS_ALLOWS_PATH),
graphConfig == null ? null : graphConfig.configurationsAt(Tokens.REXSTER_GRAPH_EXTENSIONS_PATH));
}
public RexsterApplicationGraph(final String graphName, final Graph graph, final List allowableNamespaces,
final List extensionConfigurations) {
this.graphName = graphName;
this.graph = graph;
this.loadAllowableExtensions(allowableNamespaces);
this.loadExtensionsConfigurations(extensionConfigurations);
}
/**
* Gets the name of the graph as supplied in rexster.xml as part of the configuration.
*
* @return the name of the graph
*/
public String getGraphName() {
return graphName;
}
/**
* Gets the graph instance itself.
*
* @return the graph instance.
*/
public Graph getGraph() {
return graph;
}
/**
* If this graph is an instance of WrapperGraph, this method recursively unwraps the graph to get its
* base implementation.
*
* For purposes of Rexster, graph wrapping occurs when the graph is marked as read-only in rexster.xml.
*
* @return the unwrapped graph.
*/
public Graph getUnwrappedGraph() {
return unwrapGraph(this.graph);
}
/**
* Helper method that will attempt to get a transactional graph instance if the graph can be cast to such.
*
* @return the transactional graph or null if it is not transactional
*/
public TransactionalGraph tryGetTransactionalGraph() {
TransactionalGraph transactionalGraph = null;
if (this.graph.getFeatures().supportsTransactions
&& this.graph instanceof TransactionalGraph) {
transactionalGraph = (TransactionalGraph) graph;
}
return transactionalGraph;
}
/**
* Determines if the graph is transactional or not.
*
* @return true if transactional and false otherwise.
*/
public boolean isTransactionalGraph() {
final TransactionalGraph transactionalGraph = tryGetTransactionalGraph();
return transactionalGraph != null;
}
/**
* Stops a transaction with success if the graph is transactional. If the graph is not transactional,
* the method does nothing.
*/
public void tryCommit() {
final TransactionalGraph transactionalGraph = tryGetTransactionalGraph();
if (transactionalGraph != null) {
// will leave this as stopTransaction until we completely remove it from blueprints so as to get as
// much backward compatibility as we can
//transactionalGraph.commit();
transactionalGraph.stopTransaction(TransactionalGraph.Conclusion.SUCCESS);
}
}
/**
* Stops a transaction with failure if the graph is transactional. If the graph is not transactional,
* the method does nothing.
*/
public void tryRollback() {
final TransactionalGraph transactionalGraph = tryGetTransactionalGraph();
if (transactionalGraph != null) {
// will leave this as stopTransaction until we completely remove it from blueprints so as to get as
// much backward compatibility as we can
//transactionalGraph.rollback();
transactionalGraph.stopTransaction(TransactionalGraph.Conclusion.FAILURE);
}
}
/**
* Determines if a particular extension is allowed given configured allowables from rexster.xml. Ensure
* that loadAllowableExtensions is called prior to this method.
*/
public boolean isExtensionAllowed(final ExtensionSegmentSet extensionSegmentSet) {
boolean allowed = false;
if (!extensionAllowedCache.containsKey(extensionSegmentSet)) {
for (ExtensionAllowed extensionAllowed : this.extensionAllowables) {
if (extensionAllowed.isExtensionAllowed(extensionSegmentSet)) {
allowed = true;
break;
}
}
this.extensionAllowedCache.put(extensionSegmentSet, allowed);
} else {
allowed = this.extensionAllowedCache.get(extensionSegmentSet);
}
return allowed;
}
/**
* Gets an extension configuration given a namespace and extension name.
*/
public ExtensionConfiguration findExtensionConfiguration(final String namespace, final String extensionName) {
ExtensionConfiguration extensionConfigurationFound = null;
if (this.extensionConfigurations != null) {
for (ExtensionConfiguration extensionConfiguration : this.extensionConfigurations) {
if (extensionConfiguration.getExtensionName().equals(extensionName)
&& extensionConfiguration.getNamespace().equals(namespace)) {
extensionConfigurationFound = extensionConfiguration;
break;
}
}
}
return extensionConfigurationFound;
}
/**
* Generally speaking this method should not be called directly.
*/
void loadExtensionsConfigurations(final List extensionConfigurations) {
this.extensionConfigurations = new HashSet();
if (extensionConfigurations != null) {
for (HierarchicalConfiguration configuration : extensionConfigurations) {
final String namespace = configuration.getString("namespace", "");
final String name = configuration.getString("name", "");
final HierarchicalConfiguration extensionConfig = configuration.configurationAt("configuration");
if (!namespace.isEmpty() && !name.isEmpty() && extensionConfig != null) {
this.extensionConfigurations.add(new ExtensionConfiguration(namespace, name, extensionConfig));
} else {
logger.warn("Graph [" + graphName + "] - Extension [" + namespace + ":" + name + "] does not have a valid configuration. Please check rexster.xml");
}
}
// cache the extension hypermedia once configurations are loaded and cached.
this.initializeExtensionHypermediaCache();
}
}
/**
* Loads a list of namespaces extension patterns that are allowed for this graph. Generally speaking this
* method should not be called directly.
*/
void loadAllowableExtensions(final List allowableNamespaces) {
this.extensionAllowables = new HashSet();
if (allowableNamespaces != null) {
for (int ix = 0; ix < allowableNamespaces.size(); ix++) {
final String namespace = allowableNamespaces.get(ix).toString();
try {
this.getExtensionAllowables().add(new ExtensionAllowed(namespace));
logger.info("Graph [" + graphName + "] - configured with allowable namespace [" + namespace + "]");
} catch (IllegalArgumentException iae) {
logger.warn("Graph [" + graphName + "] - Extension defined with an invalid namespace: " + namespace
+ ". It will not be configured.");
}
}
}
}
public Set getExtensionAllowables() {
return this.extensionAllowables;
}
public Set getExtensionConfigurations() {
return extensionConfigurations;
}
static Graph unwrapGraph(final Graph g) {
return g instanceof WrapperGraph ? unwrapGraph(((WrapperGraph) g).getBaseGraph()) : g;
}
@Override
public String toString() {
return this.graphName + "-" + this.graph.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final RexsterApplicationGraph that = (RexsterApplicationGraph) o;
if (!graphName.equals(that.graphName)) return false;
if (!graph.getClass().equals(that.graph.getClass())) return false;
for (ExtensionAllowed extensionAllowed : extensionAllowables) {
if (!that.getExtensionAllowables().contains(extensionAllowed)) {
return false;
}
}
for (ExtensionConfiguration configuration : extensionConfigurations) {
if (!that.getExtensionConfigurations().contains(configuration)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int result = graph.hashCode();
result = 31 * result + graphName.hashCode();
result = 31 * result + extensionAllowables.hashCode();
result = 31 * result + extensionConfigurations.hashCode();
return result;
}
protected JSONArray getExtensionHypermedia(final ExtensionPoint extensionPoint, final String baseUri) {
final List> hypermediaLinks = hypermediaCache.containsKey(extensionPoint) ?
hypermediaCache.get(extensionPoint) : createHyperMediaLinks(extensionPoint);
return getComposedLinks(baseUri, hypermediaLinks);
}
private List> createHyperMediaLinks(final ExtensionPoint extensionPoint) {
final List> hypermediaLinks = new ArrayList>();
for (RexsterExtension extension : extensions) {
final Class clazz = extension.getClass();
final ExtensionNaming extensionNaming = (ExtensionNaming) clazz.getAnnotation(ExtensionNaming.class);
// initialize the defaults
String currentExtensionNamespace = "g";
String currentExtensionName = clazz.getName();
if (extensionNaming != null) {
// naming annotation is present to try to override the defaults
// if the values are valid.
if (extensionNaming.name() != null && !extensionNaming.name().isEmpty()) {
currentExtensionName = extensionNaming.name();
}
// naming annotation is defaulted to "g" anyway but checking anyway to make sure
// no one tries to pull any funny business.
if (extensionNaming.namespace() != null && !extensionNaming.namespace().isEmpty()) {
currentExtensionNamespace = extensionNaming.namespace();
}
}
final String currentNamespaceAndName = makeExtensionName(currentExtensionNamespace, currentExtensionName);
// test the configuration to see if the extension should even be available
final ExtensionSegmentSet extensionSegmentSet = new ExtensionSegmentSet(
currentExtensionNamespace, currentExtensionName);
if (this.isExtensionAllowed(extensionSegmentSet)) {
final ExtensionConfiguration extensionConfig = this.findExtensionConfiguration(
currentExtensionNamespace, currentExtensionName);
RexsterExtension rexsterExtension = null;
try {
rexsterExtension = (RexsterExtension) clazz.newInstance();
} catch (Exception ex) {
logger.warn(String.format("Failed extension configuration check for %s on graph %s",
currentNamespaceAndName, graphName));
}
if (rexsterExtension != null) {
if (rexsterExtension.isConfigurationValid(extensionConfig)) {
final Method[] methods = clazz.getMethods();
for (Method method : methods) {
final ExtensionDescriptor descriptor = method.getAnnotation(ExtensionDescriptor.class);
final ExtensionDefinition definition = method.getAnnotation(ExtensionDefinition.class);
if (definition != null && definition.extensionPoint() == extensionPoint) {
String href = currentExtensionNamespace + "/" + currentExtensionName;
if (!definition.path().isEmpty()) {
href = href + "/" + definition.path();
}
final HashMap hypermediaLink = new HashMap();
hypermediaLink.put("href", href);
hypermediaLink.put("op", definition.method().name());
hypermediaLink.put("namespace", currentExtensionNamespace);
hypermediaLink.put("name", currentExtensionName);
final String path = definition.path();
if (path != null && !path.isEmpty()) {
hypermediaLink.put("path", path);
hypermediaLink.put("title", currentNamespaceAndName + "-" + path);
} else {
hypermediaLink.put("title", currentNamespaceAndName);
}
// descriptor is not a required annotation for extensions.
if (descriptor != null) {
hypermediaLink.put("description", descriptor.description());
}
final JSONArray queryStringParameters = buildQueryStringParameters(method, descriptor);
if (queryStringParameters.length() > 0) {
hypermediaLink.put("parameters", queryStringParameters);
}
hypermediaLinks.add(hypermediaLink);
}
}
} else {
logger.warn(String.format("An extension [%s] does not have a valid configuration. Check rexster.xml and ensure that the configuration section matches what the extension expects.",
currentNamespaceAndName));
}
}
}
}
// only put something in the cache if there are actually links to
if (hypermediaLinks.size() == 0) {
hypermediaCache.put(extensionPoint, null);
} else {
hypermediaCache.put(extensionPoint, hypermediaLinks);
}
return hypermediaLinks;
}
private String makeExtensionName(final String extensionNamespace, String extensionName) {
return String.format("%s:%s", extensionNamespace, extensionName);
}
private JSONArray buildQueryStringParameters(final Method method, final ExtensionDescriptor descriptor) {
final JSONArray queryStringParameters = new JSONArray();
if (descriptor != null && (descriptor.apiBehavior() == ExtensionApiBehavior.DEFAULT
|| descriptor.apiBehavior() == ExtensionApiBehavior.EXTENSION_DESCRIPTOR_ONLY)) {
for (final ExtensionApi extensionApi : descriptor.api()) {
queryStringParameters.put(new HashMap() {{
put(QUERY_STRING_PARAMETER_NAME, extensionApi.parameterName());
put(QUERY_STRING_PARAMETER_DESCRIPTION, extensionApi.description());
}});
}
}
// possible override for the previous settings
if (descriptor != null && (descriptor.apiBehavior() == ExtensionApiBehavior.DEFAULT
|| descriptor.apiBehavior() == ExtensionApiBehavior.EXTENSION_PARAMETER_ONLY)) {
final Annotation[][] parametersAnnotationSets = method.getParameterAnnotations();
for (Annotation[] parameterAnnotationSet : parametersAnnotationSets) {
for (Annotation annotation : parameterAnnotationSet) {
if (annotation instanceof ExtensionRequestParameter) {
final ExtensionRequestParameter extensionRequestParameter = (ExtensionRequestParameter) annotation;
queryStringParameters.put(new HashMap() {{
put(QUERY_STRING_PARAMETER_NAME, extensionRequestParameter.name());
put(QUERY_STRING_PARAMETER_DESCRIPTION, extensionRequestParameter.description());
}});
}
}
}
}
return queryStringParameters;
}
private JSONArray getComposedLinks(final String baseUri, final List> hypermediaLinks) {
JSONArray composedLinks = null;
if (hypermediaLinks != null) {
composedLinks = new JSONArray();
for (Map hypermediaLink : hypermediaLinks) {
final HashMap link = new HashMap();
for (Map.Entry linkEntry : hypermediaLink.entrySet()) {
if (linkEntry.getKey().equals("href")) {
link.put(linkEntry.getKey(), baseUri + linkEntry.getValue());
} else {
link.put(linkEntry.getKey(), linkEntry.getValue());
}
}
composedLinks.put(new JSONObject(link));
}
}
return composedLinks;
}
private void initializeExtensionHypermediaCache() {
this.getExtensionHypermedia(ExtensionPoint.GRAPH, "");
this.getExtensionHypermedia(ExtensionPoint.VERTEX, "");
this.getExtensionHypermedia(ExtensionPoint.EDGE, "");
}
}