All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tinkerpop.rexster.RexsterApplicationGraph Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
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 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, "");
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy