1
votes

I haven't been able to find a comprehensive example of connecting to and then querying a remote Apache Tinkerpop Graph Database with Gremlin and Java. And I can't quite get it to work. Can anyone that's done something like this before offer any advice?

I've set up a Azure Cosmos database in Graph-DB mode, which is expecting Gremlin queries in order to modify and access its data. I have the database host name, port, username, and password, and I'm able to execute queries, but only if I pass in a big ugly query string. I would like to be able to leverage the org.apache.tinkerpop.gremlin.structure.Graph traversal methods, but I can't quite get it working.

import java.util.List;
import java.util.concurrent.CompletableFuture;

import org.apache.tinkerpop.gremlin.driver.Result;
import org.apache.tinkerpop.gremlin.driver.ResultSet;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

//More imports...

@Service
public class SearchService {
    private final static Logger log = LoggerFactory.getLogger(SearchService.class);

    @Autowired
    private GraphDbConnection graphDbConnection;

    @Autowired
    private Graph graph;

    public Object workingQuery() {
        try {
            String query = "g.V('1234').outE('related').inV().both().as('v').project('vertex').by(select('v')).by(bothE().fold())";
            log.info("Submitting this Gremlin query: {}", query);
            ResultSet results = graphDbConnection.executeQuery(query);
            CompletableFuture<List<Result>> completableFutureResults = results.all();
            List<Result> resultList = completableFutureResults.get();
            Result result = resultList.get(0);
            log.info("Query result: {}", result.toString());
            return result.toString();
        } catch (Exception e) {
            log.error("Error fetching data.", e);
        }
        return null;
    }

    public Object failingQuery() {
        return graph.traversal().V(1234).outE("related").inV()
            .both().as("v").project("vertex").by("v").bothE().fold()
            .next();
/* I get an Exception:
"org.apache.tinkerpop.gremlin.process.remote.RemoteConnectionException: 
 java.lang.RuntimeException: java.lang.RuntimeException: 
 java.util.concurrent.TimeoutException: Timed out while waiting for an 
 available host - check the client configuration and connectivity to the 
 server if this message persists" */

    }

}

This is my configuration class:

import java.util.HashMap;
import java.util.Map;

import org.apache.tinkerpop.gremlin.driver.Cluster;
import org.apache.tinkerpop.gremlin.driver.MessageSerializer;
import org.apache.tinkerpop.gremlin.driver.remote.DriverRemoteConnection;
import org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerGremlinV2d0;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.util.GraphFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GraphDbConfig {
    private final static Logger log = LoggerFactory.getLogger(GraphDbConfig.class);

    @Value("${item.graph.hostName}")
    private String hostName;
    @Value("${item.graph.port}")
    private int port;
    @Value("${item.graph.username}")
    private String username;
    @Value("${item.graph.password}")
    private String password;
    @Value("${item.graph.enableSsl}")
    private boolean enableSsl;

    @Bean
    public Graph graph() {
        Map<String, String> graphConfig = new HashMap<>();
        graphConfig.put("gremlin.graph",
                "org.apache.tinkerpop.gremlin.process.remote.RemoteGraph");
        graphConfig.put("gremlin.remoteGraph.remoteConnectionClass",
                "org.apache.tinkerpop.gremlin.driver.remote.DriverRemoteConnection");
        Graph g = GraphFactory.open(graphConfig);
        g.traversal().withRemote(DriverRemoteConnection.using(cluster()));
        return g;
    }

    @Bean
    public Cluster cluster() {
        Cluster cluster = null;
        try {
            MessageSerializer serializer = new GraphSONMessageSerializerGremlinV2d0();

            Cluster.Builder clusterBuilder = Cluster.build().addContactPoint(hostName)
                    .serializer(serializer)
                    .port(port).enableSsl(enableSsl)
                    .credentials(username, password);
            cluster = clusterBuilder.create();
        } catch (Exception e) {
            log.error("Error in connecting to host address.", e);
        }
        return cluster;
    }

}

And I have to define this connection component currently in order to send queries to the database:

import org.apache.tinkerpop.gremlin.driver.Client;
import org.apache.tinkerpop.gremlin.driver.Cluster;
import org.apache.tinkerpop.gremlin.driver.ResultSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class GraphDbConnection {
    private final static Logger log = LoggerFactory.getLogger(GraphDbConnection.class);

    @Autowired
    private Cluster cluster;

    public ResultSet executeQuery(String query) {
        Client client = connect();
        ResultSet results = client.submit(query);
        closeConnection(client);
        return results;
    }

    private Client connect() {
        Client client = null;
        try {
            client = cluster.connect();
        } catch (Exception e) {
            log.error("Error in connecting to host address.", e);
        }
        return client;
    }

    private void closeConnection(Client client) {
        client.close();
    }
}
1

1 Answers

3
votes

You cannot leverage the remote API with CosmosDB yet. It does not support Gremlin Bytecode yet.

https://github.com/Azure/azure-documentdb-dotnet/issues/439

https://feedback.azure.com/forums/263030-azure-cosmos-db/suggestions/33632779-support-gremlin-bytecode-to-enable-the-fluent-api

You would have to continue with strings until then, though.....since you are using Java you could try a somewhat unadvertised feature: GroovyTranslator

gremlin> g = EmptyGraph.instance().traversal()
==>graphtraversalsource[emptygraph[empty], standard]
gremlin> translator = GroovyTranslator.of('g')
==>translator[g:gremlin-groovy]
gremlin> translator.translate(g.V().out('knows').has('person','name','marko').asAdmin().getBytecode())
==>g.V().out("knows").has("person","name","marko")

As you can see, it takes Gremlin Bytecode and converts it into a String of Gremlin that you could submit to CosmosDB. Later, when CosmosDB supports Bytecode, you could drop the GroovyTranslator and change from EmptyGraph construction of your GraphTraversalSource and everything should start working. To make this really seamless, you could go the extra step and write a TraversalStrategy that would do something similar to TinkerPop's RemoteStrategy. Instead of submitting Bytecode as that strategy does, you would just just use GroovyTranslator and submit the string of Gremlin. That approach would make it even easier to switch over when CosmosDB supports Bytecode because then all you would have to do is remove your custom TraversalStrategy and reconfigure your remote GraphTraversalSource in the standard way.