From c6e2ebffb3abeb885973a56f55413f7f156ef988 Mon Sep 17 00:00:00 2001 From: "lynn.bond" Date: Fri, 3 Mar 2023 19:20:56 +0800 Subject: [PATCH 01/11] Added a new CypherAPI that is enhanced by cypher-gremlin-extensions --- .../hugegraph/api/cypher/CypherAPI.java | 159 ++++++++++ .../hugegraph/api/cypher/CypherClient.java | 146 +++++++++ .../hugegraph/api/cypher/CypherManager.java | 107 +++++++ .../hugegraph/api/cypher/CypherModel.java | 62 ++++ .../api/cypher/CypherOpProcessor.java | 283 ++++++++++++++++++ .../hugegraph/api/cypher/CypherPlugin.java | 67 +++++ .../hugegraph/api/gremlin/CypherAPI.java | 5 +- .../hugegraph/auth/StandardAuthenticator.java | 82 ++++- ...che.tinkerpop.gremlin.jsr223.GremlinPlugin | 1 + ...pache.tinkerpop.gremlin.server.OpProcessor | 1 + .../java/org/apache/hugegraph/HugeGraph.java | 5 +- .../hugegraph/auth/StandardAuthManager.java | 7 +- .../optimize/HugePrimaryKeyStrategy.java | 109 +++++++ .../assembly/static/conf/gremlin-server.yaml | 14 +- .../assembly/static/conf/remote-objects.yaml | 7 +- 15 files changed, 1045 insertions(+), 10 deletions(-) create mode 100644 hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java create mode 100644 hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java create mode 100644 hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java create mode 100644 hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherModel.java create mode 100644 hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java create mode 100644 hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java create mode 100644 hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin create mode 100644 hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.server.OpProcessor create mode 100644 hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugePrimaryKeyStrategy.java diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java new file mode 100644 index 0000000000..4ac6f3a6e4 --- /dev/null +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.api.cypher; + +import jakarta.inject.Singleton; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +import org.apache.hugegraph.HugeException; +import org.apache.hugegraph.api.API; +import org.apache.hugegraph.api.filter.CompressInterceptor; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.Log; +import com.codahale.metrics.annotation.Timed; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +@Path("graphs/{graph}/cypher") +@Singleton +public class CypherAPI extends API { + private static final Logger LOG = Log.logger(CypherAPI.class); + private static final Charset UTF8 = Charset.forName(StandardCharsets.UTF_8.name()); + private final Base64.Decoder decoder = Base64.getUrlDecoder(); + private final String basic = "Basic "; + private final String bearer = "Bearer "; + + private CypherManager cypherManager; + + private CypherManager cypherManager() { + if (this.cypherManager == null) { + this.cypherManager = CypherManager.configOf("conf/remote-objects.yaml"); + } + return this.cypherManager; + } + + @GET + @Timed + @CompressInterceptor.Compress(buffer = (1024 * 40)) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public CypherModel query(@PathParam("graph") String graph, + @Context HttpHeaders headers, + @QueryParam("cypher") String cypher) { + LOG.debug("Graph [{}] query by cypher: {}", graph, cypher); + + return this.queryByCypher(graph, headers, cypher); + } + + @POST + @Timed + @CompressInterceptor.Compress + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public CypherModel post(@PathParam("graph") String graph, + @Context HttpHeaders headers, + String cypher) { + LOG.debug("Graph [{}] query by cypher: {}", graph, cypher); + return this.queryByCypher(graph, headers, cypher); + } + + private CypherModel queryByCypher(String graph, + HttpHeaders headers, + String cypher) { + + E.checkArgument(graph != null && !graph.isEmpty(), + "The graph parameter can't be null or empty"); + E.checkArgument(cypher != null && !cypher.isEmpty(), + "The cypher parameter can't be null or empty"); + + Map aliases = new HashMap<>(1, 1); + aliases.put("g", "__g_" + graph); + + return this.client(headers).submitQuery(cypher, aliases); + } + + private CypherClient client(HttpHeaders headers) { + String auth = headers.getHeaderString(HttpHeaders.AUTHORIZATION); + + if (auth != null && !auth.isEmpty()) { + auth = auth.split(",")[0]; + } + + if (auth == null) { + throw new HugeException("The Cypher-API is being called without any authorization."); + } + + if (auth.startsWith(basic)) { + return this.clientViaBasic(auth); + } else if (auth.startsWith(bearer)) { + return this.clientViaToken(auth); + } + + throw new HugeException("The Cypher-API is being called without any authorization."); + } + + private CypherClient clientViaBasic(String auth) { + Pair userPass = this.toUserPass(auth); + E.checkNotNull(userPass, "user-password-pair"); + + return this.cypherManager().getClient(userPass.getLeft(), userPass.getRight()); + } + + private CypherClient clientViaToken(String auth) { + return this.cypherManager().getClient(auth.substring(bearer.length())); + } + + private Pair toUserPass(String auth) { + if (auth == null || auth.isEmpty()) return null; + if (!auth.startsWith(basic)) return null; + + String[] split = null; + + try { + String encoded = auth.substring(basic.length()); + byte[] userPass = this.decoder.decode(encoded); + String authorization = new String(userPass, UTF8); + split = authorization.split(":"); + } catch (Exception e) { + LOG.error("Failed convert auth to credential.", e); + return null; + } + + if (split.length != 2) { + return null; + } + + return ImmutablePair.of(split[0], split[1]); + } + +} diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java new file mode 100644 index 0000000000..e282cf9940 --- /dev/null +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.api.cypher; + +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.Log; +import org.apache.commons.configuration2.Configuration; +import org.apache.tinkerpop.gremlin.driver.*; +import org.apache.tinkerpop.gremlin.driver.message.RequestMessage; +import org.slf4j.Logger; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; + +@ThreadSafe +public final class CypherClient { + private static final Logger LOG = Log.logger(CypherClient.class); + private String userName; + private String password; + private String token; + private Supplier configurationSupplier; + + CypherClient(String userName, String password, + Supplier configurationSupplier) { + this.userName = userName; + this.password = password; + this.configurationSupplier = configurationSupplier; + } + + CypherClient(String token, Supplier configurationSupplier) { + this.token = token; + this.configurationSupplier = configurationSupplier; + } + + public CypherModel submitQuery(String cypherQuery,@Nullable Map aliases) { + E.checkArgument(cypherQuery != null && !cypherQuery.isEmpty(), + "The cypher-query parameter can't be null or empty"); + + Cluster cluster = Cluster.open(getConfig()); + Client client = cluster.connect(); + + if (aliases != null && !aliases.isEmpty()) { + client = client.alias(aliases); + } + + RequestMessage request = createRequest(cypherQuery); + CypherModel res = null; + + try { + List list = this.doQueryList(client, request); + res = CypherModel.dataOf(request.getRequestId().toString(), list); + } catch (Exception e) { + LOG.error(String.format("Failed to submit cypher-query: [ %s ], cause by:" + , cypherQuery), e); + res = CypherModel.failOf(request.getRequestId().toString(), e.getMessage()); + } finally { + client.close(); + cluster.close(); + } + + return res; + } + + private RequestMessage createRequest(String cypherQuery) { + return RequestMessage.build(Tokens.OPS_EVAL) + .processor("cypher") + .add(Tokens.ARGS_GREMLIN, cypherQuery) + .create(); + } + + private List doQueryList(Client client, RequestMessage request) + throws ExecutionException, InterruptedException { + + ResultSet results = null; + results = client.submitAsync(request).get(); + + Iterator iter = results.iterator(); + List list = new LinkedList<>(); + + for (; iter.hasNext(); ) { + Result data = iter.next(); + list.add(data.getObject()); + } + + return list; + } + + /** + * As Sasl does not support a token, which is a coded string to indicate a legal user, + * we had to use a trick to fix it. When the token is set, the password will be set to + * an empty string, which is an uncommon value under normal conditions. + * The token will then be transferred through the userName-property. + * To see org.apache.hugegraph.auth.StandardAuthenticator.PlainTextSaslAuthenticator + */ + private Configuration getConfig() { + Configuration conf = this.configurationSupplier.get(); + conf.addProperty("username", this.token == null ? this.userName : this.token); + conf.addProperty("password", this.token == null ? this.password : ""); + + return conf; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CypherClient that = (CypherClient) o; + + return Objects.equals(userName, that.userName) + && Objects.equals(password, that.password) + && Objects.equals(token, that.token); + } + + @Override + public int hashCode() { + return Objects.hash(userName, password, token); + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("CypherClient{"); + sb.append("userName='").append(userName).append('\''); + sb.append(", token='").append(token).append('\''); + sb.append('}'); + + return sb.toString(); + } +} diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java new file mode 100644 index 0000000000..173cf1d939 --- /dev/null +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.api.cypher; + +import org.apache.hugegraph.util.E; +import org.apache.commons.configuration2.Configuration; +import org.apache.commons.configuration2.YAMLConfiguration; + +import javax.annotation.concurrent.ThreadSafe; +import java.io.File; +import java.io.FileReader; +import java.io.Reader; + +import java.net.URL; + +@ThreadSafe +public final class CypherManager { + private String configurationFile; + private YAMLConfiguration configuration; + + public static CypherManager configOf(String configurationFile) { + E.checkArgument(configurationFile != null && !configurationFile.isEmpty(), + "The configurationFile parameter can't be null or empty"); + return new CypherManager(configurationFile); + } + + private CypherManager(String configurationFile) { + this.configurationFile = configurationFile; + } + + public CypherClient getClient(String userName, String password) { + E.checkArgument(userName != null && !userName.isEmpty(), + "The userName parameter can't be null or empty"); + E.checkArgument(password != null && !password.isEmpty(), + "The password parameter can't be null or empty"); + + //TODO: Need to cache the client and make it hold the connection. + return new CypherClient(userName, password, () -> this.cloneConfig()); + } + + public CypherClient getClient(String token) { + E.checkArgument(token != null && !token.isEmpty(), + "The token parameter can't be null or empty"); + + //TODO: Need to cache the client and make it hold the connection. + return new CypherClient(token, () -> this.cloneConfig()); + } + + private Configuration cloneConfig() { + if (this.configuration == null) { + this.configuration = loadYaml(this.configurationFile); + } + + return (Configuration) this.configuration.clone(); + } + + private static YAMLConfiguration loadYaml(String configurationFile) { + File yamlFile = getConfigFile(configurationFile); + YAMLConfiguration yaml; + + try { + Reader reader = new FileReader(yamlFile); + yaml = new YAMLConfiguration(); + yaml.read(reader); + } catch (Exception e) { + throw new RuntimeException(String.format("Failed to load configuration file," + + " the file at %s.", configurationFile), e); + } + + return yaml; + } + + private static File getConfigFile(String configurationFile) { + final File systemFile = new File(configurationFile); + + if (!systemFile.exists()) { + final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); + final URL resource = currentClassLoader.getResource(configurationFile); + final File resourceFile = new File(resource.getFile()); + + if (!resourceFile.exists()) { + throw new IllegalArgumentException(String.format("Configuration file at %s does not exist" + , configurationFile)); + } + return resourceFile; + + } + + return systemFile; + } + +} diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherModel.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherModel.java new file mode 100644 index 0000000000..31230248ef --- /dev/null +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherModel.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.api.cypher; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * As same as response of GremlinAPI + */ +public class CypherModel { + public String requestId; + public Status status = new Status(); + public Result result = new Result(); + + public static CypherModel dataOf(String requestId, List data) { + CypherModel res = new CypherModel(); + res.requestId = requestId; + res.status.code = 200; + res.result.data = data; + return res; + } + + public static CypherModel failOf(String requestId, String message) { + CypherModel res = new CypherModel(); + res.requestId = requestId; + res.status.code = 400; + res.status.message = message; + return res; + } + + private CypherModel() { + } + + public class Status { + public String message = ""; + public int code; + public Map attributes = Collections.EMPTY_MAP; + } + + private class Result { + public List data; + public Map meta = Collections.EMPTY_MAP; + } + +} diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java new file mode 100644 index 0000000000..99bbb47bb6 --- /dev/null +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2018-2019 "Neo4j, Inc." [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hugegraph.api.cypher; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.tinkerpop.gremlin.driver.Tokens; +import org.apache.tinkerpop.gremlin.driver.message.RequestMessage; +import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage; +import org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode; +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalInterruptedException; +import org.apache.tinkerpop.gremlin.server.Context; +import org.apache.tinkerpop.gremlin.server.GraphManager; +import org.apache.tinkerpop.gremlin.server.OpProcessor; +import org.apache.tinkerpop.gremlin.server.op.AbstractEvalOpProcessor; +import org.apache.tinkerpop.gremlin.server.op.OpProcessorException; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.util.function.ThrowingConsumer; +import org.opencypher.gremlin.translation.CypherAst; +import org.opencypher.gremlin.translation.groovy.GroovyPredicate; +import org.opencypher.gremlin.translation.ir.TranslationWriter; +import org.opencypher.gremlin.translation.ir.model.GremlinStep; +import org.opencypher.gremlin.translation.translator.Translator; +import org.opencypher.gremlin.traversal.ParameterNormalizer; +import org.opencypher.gremlin.traversal.ProcedureContext; +import org.opencypher.gremlin.traversal.ReturnNormalizer; +import org.slf4j.Logger; +import scala.collection.Seq; + +import java.util.*; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static java.util.Optional.empty; +import static org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode.SERVER_ERROR; +import static org.opencypher.gremlin.translation.StatementOption.EXPLAIN; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * {@link OpProcessor} implementation for processing Cypher {@link RequestMessage}s: + *
+ * {
+ *   "requestId": "<some UUID>",
+ *   "op": "eval",
+ *   "processor": "cypher",
+ *   "args": { "gremlin": "<CYPHER QUERY>" }
+ * }
+ * 
+ */ +public class CypherOpProcessor extends AbstractEvalOpProcessor { + private static final String DEFAULT_TRANSLATOR_DEFINITION = + "gremlin+cfog_server_extensions+inline_parameters"; + + private static final Logger logger = getLogger(CypherOpProcessor.class); + + public CypherOpProcessor() { + super(true); + } + + @Override + public String getName() { + return "cypher"; + } + + @Override + public ThrowingConsumer getEvalOp() { + return this::evalCypher; + } + + @Override + public Optional> selectOther(Context ctx) + throws OpProcessorException { + return empty(); + } + + private void evalCypher(Context context) throws OpProcessorException { + Map args = context.getRequestMessage().getArgs(); + String cypher = (String) args.get(Tokens.ARGS_GREMLIN); + logger.info("Cypher: {}", cypher.replaceAll("\n", " ")); + + GraphTraversalSource gts = traversal(context); + DefaultGraphTraversal g = new DefaultGraphTraversal(gts.clone()); + Map parameters = ParameterNormalizer.normalize(getParameters(args)); + ProcedureContext procedureContext = ProcedureContext.global(); + CypherAst ast = CypherAst.parse(cypher, parameters, procedureContext.getSignatures()); + + String translatorDefinition = getTranslatorDefinition(context); + + Translator stringTranslator = Translator.builder() + .gremlinGroovy() + .build(translatorDefinition); + + Translator traversalTranslator = Translator.builder() + .traversal(g) + .build(translatorDefinition); + + Seq ir = ast.translate(stringTranslator.flavor(), + stringTranslator.features(), procedureContext); + + String gremlin = TranslationWriter.write(ir, stringTranslator, parameters); + logger.info("Gremlin: {}", gremlin); + + if (ast.getOptions().contains(EXPLAIN)) { + explainQuery(context, ast, gremlin); + return; + } + + GraphTraversal traversal = TranslationWriter.write(ir, traversalTranslator, parameters); + ReturnNormalizer returnNormalizer = ReturnNormalizer.create(ast.getReturnTypes()); + Iterator normalizedTraversal = returnNormalizer.normalize(traversal); + inTransaction(gts, () -> handleIterator(context, normalizedTraversal)); + } + + private void inTransaction(GraphTraversalSource gts, Runnable runnable) { + Graph graph = gts.getGraph(); + boolean supportsTransactions = graph.features().graph().supportsTransactions(); + + if (!supportsTransactions) { + runnable.run(); + return; + } + + try { + graph.tx().open(); + runnable.run(); + graph.tx().commit(); + } catch (Exception e) { + if (graph.tx().isOpen()) { + graph.tx().rollback(); + } + } + } + + private GraphTraversalSource traversal(Context context) throws OpProcessorException { + RequestMessage msg = context.getRequestMessage(); + GraphManager graphManager = context.getGraphManager(); + + Optional> aliasesOptional = msg.optionalArgs(Tokens.ARGS_ALIASES); + String gAlias = aliasesOptional + .map(aliases -> aliases.get(Tokens.VAL_TRAVERSAL_SOURCE_ALIAS)) + .orElse(null); + + if (gAlias == null) { + return graphManager.getGraphNames().stream() + .sorted() + .findFirst() + .map(graphManager::getGraph) + .map(Graph::traversal) + .orElseThrow(() -> opProcessorException(msg, "No graphs found on the server")); + } + + Graph graph = graphManager.getGraph(gAlias); + if (graph != null) { + return graph.traversal(); + } + + TraversalSource traversalSource = graphManager.getTraversalSource(gAlias); + if (traversalSource instanceof GraphTraversalSource) { + return (GraphTraversalSource) traversalSource; + } + + throw opProcessorException(msg, "Traversable alias '" + gAlias + "' not found"); + } + + private OpProcessorException opProcessorException(RequestMessage msg, String errorMessage) { + return new OpProcessorException(errorMessage, + ResponseMessage.build(msg) + .code(SERVER_ERROR) + .statusMessage(errorMessage).create()); + } + + protected void handleIterator(Context context, Iterator traversal) { + RequestMessage msg = context.getRequestMessage(); + final long timeout = msg.getArgs().containsKey(Tokens.ARGS_EVAL_TIMEOUT) + ? ((Number) msg.getArgs().get(Tokens.ARGS_EVAL_TIMEOUT)).longValue() + : context.getSettings().evaluationTimeout; + + FutureTask evalFuture = new FutureTask<>(() -> { + try { + super.handleIterator(context, traversal); + } catch (Exception ex) { + String errorMessage = getErrorMessage(msg, ex); + + logger.error("Error during traversal iteration", ex); + ChannelHandlerContext ctx = context.getChannelHandlerContext(); + ctx.writeAndFlush(ResponseMessage.build(msg) + .code(SERVER_ERROR) + .statusMessage(errorMessage) + .statusAttributeException(ex) + .create()); + } + return null; + } + ); + + final Future executionFuture = context.getGremlinExecutor() + .getExecutorService().submit(evalFuture); + if (timeout > 0) { + context.getScheduledExecutorService().schedule( + () -> executionFuture.cancel(true) + , timeout, TimeUnit.MILLISECONDS); + } + + } + + private String getErrorMessage(RequestMessage msg, Exception ex) { + if (ex instanceof InterruptedException || ex instanceof TraversalInterruptedException) { + return String.format("A timeout occurred during traversal evaluation of [%s] " + + "- consider increasing the limit given to scriptEvaluationTimeout", msg); + } else { + return ex.getMessage(); + } + } + + private void explainQuery(Context context, CypherAst ast, String gremlin) { + Map explanation = new LinkedHashMap<>(); + explanation.put("translation", gremlin); + explanation.put("options", ast.getOptions().toString()); + + ResponseMessage explainMsg = ResponseMessage.build(context.getRequestMessage()) + .code(ResponseStatusCode.SUCCESS) + .statusMessage("OK") + .result(singletonList(explanation)) + .create(); + + ChannelHandlerContext ctx = context.getChannelHandlerContext(); + ctx.writeAndFlush(explainMsg); + } + + @Override + public void close() { + // do nothing = no resources to release + } + + @SuppressWarnings("unchecked") + private Map getParameters(Map args) { + if (args.containsKey(Tokens.ARGS_BINDINGS)) { + return (Map) args.get(Tokens.ARGS_BINDINGS); + } else { + return new HashMap<>(); + } + } + + private String getTranslatorDefinition(Context context) { + Map config = context.getSettings() + .optionalProcessor(CypherOpProcessor.class) + .map(p -> p.config) + .orElse(emptyMap()); + + HashSet properties = new HashSet<>(config.keySet()); + properties.remove("translatorDefinition"); + properties.remove("translatorFeatures"); + if (!properties.isEmpty()) { + throw new IllegalStateException("Unknown configuration parameters " + + "found for CypherOpProcessor: " + properties); + } + + return config.getOrDefault("translatorDefinition", DEFAULT_TRANSLATOR_DEFINITION) + + "+" + config.getOrDefault("translatorFeatures", ""); + } + +} diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java new file mode 100644 index 0000000000..fc6c2dda6e --- /dev/null +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018-2019 "Neo4j, Inc." [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hugegraph.api.cypher; + +import org.apache.tinkerpop.gremlin.jsr223.Customizer; +import org.apache.tinkerpop.gremlin.jsr223.DefaultImportCustomizer; +import org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin; +import org.apache.tinkerpop.gremlin.jsr223.ImportCustomizer; +import org.opencypher.gremlin.traversal.CustomFunctions; +import org.opencypher.gremlin.traversal.CustomPredicate; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class CypherPlugin implements GremlinPlugin { + + private static final ImportCustomizer imports = DefaultImportCustomizer.build() + .addClassImports(CustomPredicate.class) + .addMethodImports(getDeclaredPublicMethods(CustomPredicate.class)) + .addClassImports(CustomFunctions.class) + .addMethodImports(getDeclaredPublicMethods(CustomFunctions.class)) + .create(); + + private static List getDeclaredPublicMethods(Class klass) { + Method[] declaredMethods = klass.getDeclaredMethods(); + return Stream.of(declaredMethods) + .filter(method -> Modifier.isPublic(method.getModifiers())) + .collect(Collectors.toList()); + } + + @Override + public String getName() { + return "cypher.extra"; + } + + public static GremlinPlugin instance() { + return new CypherPlugin(); + } + + @Override + public boolean requireRestart() { + return true; + } + + @Override + public Optional getCustomizers(String scriptEngineName) { + return Optional.of(new Customizer[]{imports}); + } +} diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java index 24c39a69fd..19802580ae 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java @@ -30,7 +30,7 @@ import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; + import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; @@ -38,9 +38,10 @@ import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.Response; -@Path("graphs/{graph}/cypher") +//@Path("graphs/{graph}/cypher") @Singleton @Tag(name = "CypherAPI") +@Deprecated public class CypherAPI extends GremlinQueryAPI { private static final Logger LOG = Log.logger(CypherAPI.class); diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java b/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java index 2cda2333e4..f5800af5ad 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java @@ -19,11 +19,15 @@ import java.io.Console; import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; import java.util.Map; import java.util.Scanner; -import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang.StringUtils; +import org.apache.tinkerpop.gremlin.server.auth.AuthenticatedUser; +import org.apache.tinkerpop.gremlin.server.auth.AuthenticationException; import org.apache.tinkerpop.gremlin.structure.util.GraphFactory; import org.apache.hugegraph.HugeGraph; @@ -35,10 +39,13 @@ import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.StringEncoding; +import static org.apache.tinkerpop.gremlin.groovy.jsr223.dsl.credential.CredentialGraphTokens.PROPERTY_PASSWORD; +import static org.apache.tinkerpop.gremlin.groovy.jsr223.dsl.credential.CredentialGraphTokens.PROPERTY_USERNAME; + public class StandardAuthenticator implements HugeAuthenticator { private static final String INITING_STORE = "initing_store"; - + private static final byte NUL = 0; private HugeGraph graph = null; private HugeGraph graph() { @@ -177,7 +184,7 @@ public AuthManager authManager() { @Override public SaslNegotiator newSaslNegotiator(InetAddress remoteAddress) { - throw new NotImplementedException("SaslNegotiator is unsupported"); + return new PlainTextSaslAuthenticator(); } public static void initAdminUserIfNeeded(String confFile) throws Exception { @@ -193,4 +200,73 @@ public static void initAdminUserIfNeeded(String confFile) throws Exception { auth.initAdminUser(); } } + + /** + * Copied from org.apache.tinkerpop.gremlin.server.auth.SimpleAuthenticator.PlainTextSaslAuthenticator. + * Added support of token. + */ + private class PlainTextSaslAuthenticator implements SaslNegotiator { + private boolean complete = false; + private String username; + private String password; + + private String token; + + @Override + public byte[] evaluateResponse(final byte[] clientResponse) throws AuthenticationException { + decodeCredentials(clientResponse); + complete = true; + return null; + } + + @Override + public boolean isComplete() { + return complete; + } + + @Override + public AuthenticatedUser getAuthenticatedUser() throws AuthenticationException { + if (!complete) throw new AuthenticationException("SASL negotiation not complete"); + final Map credentials = new HashMap<>(); + credentials.put(PROPERTY_USERNAME, username); + credentials.put(PROPERTY_PASSWORD, password); + + credentials.put(KEY_TOKEN, token); + + return authenticate(credentials); + } + + /** + * SASL PLAIN mechanism specifies that credentials are encoded in a + * sequence of UTF-8 bytes, delimited by 0 (US-ASCII NUL). + * The form is : {code}authzIdauthnIdpassword{code}. + * + * @param bytes encoded credentials string sent by the client + */ + private void decodeCredentials(byte[] bytes) throws AuthenticationException { + byte[] user = null; + byte[] pass = null; + int end = bytes.length; + for (int i = bytes.length - 1 ; i >= 0; i--) { + if (bytes[i] == NUL) { + if (pass == null) + pass = Arrays.copyOfRange(bytes, i + 1, end); + else if (user == null) + user = Arrays.copyOfRange(bytes, i + 1, end); + end = i; + } + } + + if (null == user) throw new AuthenticationException("Authentication ID must not be null"); + if (null == pass) throw new AuthenticationException("Password must not be null"); + + username = new String(user, StandardCharsets.UTF_8); + password = new String(pass, StandardCharsets.UTF_8); + + /* The trick is here. >_*/ + if(password.isEmpty()){ + token=username; + } + } + } } diff --git a/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin b/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin new file mode 100644 index 0000000000..be2a5bfad4 --- /dev/null +++ b/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin @@ -0,0 +1 @@ +org.apache.hugegraph.api.cypher.CypherPlugin diff --git a/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.server.OpProcessor b/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.server.OpProcessor new file mode 100644 index 0000000000..58b2be3401 --- /dev/null +++ b/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.server.OpProcessor @@ -0,0 +1 @@ +org.apache.hugegraph.api.cypher.CypherOpProcessor diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/HugeGraph.java b/hugegraph-core/src/main/java/org/apache/hugegraph/HugeGraph.java index e2c2000ba7..ed094f6383 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/HugeGraph.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/HugeGraph.java @@ -31,6 +31,7 @@ import org.apache.hugegraph.rpc.RpcServiceConfig4Client; import org.apache.hugegraph.rpc.RpcServiceConfig4Server; import org.apache.hugegraph.task.TaskScheduler; +import org.apache.hugegraph.traversal.optimize.HugePrimaryKeyStrategy; import org.apache.hugegraph.type.HugeType; import org.apache.hugegraph.type.define.GraphMode; import org.apache.hugegraph.type.define.GraphReadMode; @@ -317,7 +318,9 @@ static void registerTraversalStrategies(Class clazz) { .clone(); strategies.addStrategies(HugeVertexStepStrategy.instance(), HugeGraphStepStrategy.instance(), - HugeCountStepStrategy.instance()); + HugeCountStepStrategy.instance(), + HugePrimaryKeyStrategy.instance()); + TraversalStrategies.GlobalCache.registerStrategies(clazz, strategies); } } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java b/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java index 7f3e12d35e..910f19cdc5 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java @@ -665,7 +665,12 @@ public UserWithRole validateUser(String token) { Claims payload = null; boolean needBuildCache = false; if (username == null) { - payload = this.tokenGenerator.verify(token); + try{ + payload = this.tokenGenerator.verify(token); + }catch (Throwable t){ + LOG.error(String.format("Failed to verify token:[ %s ], cause:",token),t); + return new UserWithRole(""); + } username = (String) payload.get(AuthConstant.TOKEN_USER_NAME); needBuildCache = true; } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugePrimaryKeyStrategy.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugePrimaryKeyStrategy.java new file mode 100644 index 0000000000..1dcffe9b07 --- /dev/null +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugePrimaryKeyStrategy.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.traversal.optimize; + +import org.apache.tinkerpop.gremlin.process.traversal.Step; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy.ProviderOptimizationStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating; +import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStartStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStep; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStep; +import org.apache.tinkerpop.gremlin.structure.VertexProperty.Cardinality; + +import java.util.LinkedList; +import java.util.List; + + +public class HugePrimaryKeyStrategy + extends AbstractTraversalStrategy + implements ProviderOptimizationStrategy { + + private static final long serialVersionUID = 6307847098226016416L; + private static final HugePrimaryKeyStrategy INSTANCE = new HugePrimaryKeyStrategy(); + + public static HugePrimaryKeyStrategy instance() { + return INSTANCE; + } + + @Override + public void apply(Traversal.Admin traversal) { + + List removeSteps = new LinkedList<>(); + Mutating curAddStep = null; + List stepList = traversal.getSteps(); + + for (int i = 0, s = stepList.size(); i < s; i++) { + Step step = stepList.get(i); + + if (i == 0 && AddVertexStartStep.class.isInstance(step)) { + curAddStep = (Mutating) step; + continue; + } else if (curAddStep == null && AddVertexStep.class.isInstance((step))) { + curAddStep = (Mutating) step; + continue; + } + + if (curAddStep == null) continue; + + if (!AddPropertyStep.class.isInstance(step)) { + curAddStep = null; + continue; + } + + AddPropertyStep propertyStep = (AddPropertyStep) step; + + if (propertyStep.getCardinality() == Cardinality.single + || propertyStep.getCardinality() == null) { + + Object[] kvs = new Object[2]; + List kvList = new LinkedList<>(); + + propertyStep.getParameters().getRaw().forEach((k, v) -> { + if (T.key.equals(k)) { + kvs[0] = v.get(0); + } else if (T.value.equals(k)) { + kvs[1] = v.get(0); + } else { + kvList.add(k.toString()); + kvList.add(v.get(0)); + } + }); + + curAddStep.configure(kvs); + + if (kvList.size() > 0) { + curAddStep.configure(kvList.toArray(new Object[kvList.size()])); + } + + removeSteps.add(step); + } else { + curAddStep = null; + } + + } + + for (Step index : removeSteps) { + traversal.removeStep(index); + } + + } +} diff --git a/hugegraph-dist/src/assembly/static/conf/gremlin-server.yaml b/hugegraph-dist/src/assembly/static/conf/gremlin-server.yaml index 61c3517e39..dff43cb04b 100644 --- a/hugegraph-dist/src/assembly/static/conf/gremlin-server.yaml +++ b/hugegraph-dist/src/assembly/static/conf/gremlin-server.yaml @@ -27,6 +27,10 @@ graphs: { } scriptEngines: { gremlin-groovy: { + staticImports: [ + org.opencypher.gremlin.process.traversal.CustomPredicates.*', + org.opencypher.gremlin.traversal.CustomFunctions.* + ], plugins: { org.apache.hugegraph.plugin.HugeGraphGremlinPlugin: {}, org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, @@ -60,9 +64,15 @@ scriptEngines: { org.apache.hugegraph.traversal.optimize.ConditionP, org.apache.hugegraph.traversal.optimize.Text, org.apache.hugegraph.traversal.optimize.TraversalUtil, - org.apache.hugegraph.util.DateUtil + org.apache.hugegraph.util.DateUtil, + org.opencypher.gremlin.traversal.CustomFunctions, + org.opencypher.gremlin.traversal.CustomPredicate ], - methodImports: [java.lang.Math#*] + methodImports: [ + java.lang.Math#*, + org.opencypher.gremlin.traversal.CustomPredicate#*, + org.opencypher.gremlin.traversal.CustomFunctions#* + ] }, org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: { files: [scripts/empty-sample.groovy] diff --git a/hugegraph-dist/src/assembly/static/conf/remote-objects.yaml b/hugegraph-dist/src/assembly/static/conf/remote-objects.yaml index 55f38ab97d..8ba24d00a1 100644 --- a/hugegraph-dist/src/assembly/static/conf/remote-objects.yaml +++ b/hugegraph-dist/src/assembly/static/conf/remote-objects.yaml @@ -20,6 +20,11 @@ serializer: { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0, config: { serializeResultToString: false, - ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry] + # The duplication of HugeGraphIoRegistry is meant to fix a bug in the + # 'org.apache.tinkerpop.gremlin.driver.Settings:from(Configuration)' method. + ioRegistries: [ + org.apache.hugegraph.io.HugeGraphIoRegistry, + org.apache.hugegraph.io.HugeGraphIoRegistry + ] } } From a1da03939062d7a1810fd92b47a02825f7f1e549 Mon Sep 17 00:00:00 2001 From: "lynn.bond" Date: Mon, 6 Mar 2023 11:13:58 +0800 Subject: [PATCH 02/11] Removed the previous CypherAPI. --- .../hugegraph/api/gremlin/CypherAPI.java | 112 ------------------ 1 file changed, 112 deletions(-) delete mode 100644 hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java deleted file mode 100644 index 19802580ae..0000000000 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with this - * work for additional information regarding copyright ownership. The ASF - * licenses this file to You under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -package org.apache.hugegraph.api.gremlin; - -import org.opencypher.gremlin.translation.TranslationFacade; -import org.slf4j.Logger; - -import org.apache.hugegraph.api.filter.CompressInterceptor.Compress; -import org.apache.hugegraph.util.E; -import org.apache.hugegraph.util.Log; -import com.codahale.metrics.annotation.Timed; - -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; - -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.Response; - -//@Path("graphs/{graph}/cypher") -@Singleton -@Tag(name = "CypherAPI") -@Deprecated -public class CypherAPI extends GremlinQueryAPI { - - private static final Logger LOG = Log.logger(CypherAPI.class); - - @GET - @Timed - @Compress(buffer = (1024 * 40)) - @Produces(APPLICATION_JSON_WITH_CHARSET) - public Response query(@PathParam("graph") String graph, - @Context HttpHeaders headers, - @QueryParam("cypher") String cypher) { - LOG.debug("Graph [{}] query by cypher: {}", graph, cypher); - - return this.queryByCypher(graph, headers, cypher); - } - - @POST - @Timed - @Compress - @Consumes(APPLICATION_JSON) - @Produces(APPLICATION_JSON_WITH_CHARSET) - public Response post(@PathParam("graph") String graph, - @Context HttpHeaders headers, - String cypher) { - LOG.debug("Graph [{}] query by cypher: {}", graph, cypher); - return this.queryByCypher(graph, headers, cypher); - } - - private Response queryByCypher(String graph, - HttpHeaders headers, - String cypher) { - E.checkArgument(cypher != null && !cypher.isEmpty(), - "The cypher parameter can't be null or empty"); - - String gremlin = this.translateCpyher2Gremlin(graph, cypher); - LOG.debug("translated gremlin is {}", gremlin); - - String auth = headers.getHeaderString(HttpHeaders.AUTHORIZATION); - String request = "{" + - "\"gremlin\":\"" + gremlin + "\"," + - "\"bindings\":{}," + - "\"language\":\"gremlin-groovy\"," + - "\"aliases\":{\"g\":\"__g_" + graph + "\"}}"; - - Response response = this.client().doPostRequest(auth, request); - return transformResponseIfNeeded(response); - } - - private String translateCpyher2Gremlin(String graph, String cypher) { - TranslationFacade translator = new TranslationFacade(); - String gremlin = translator.toGremlinGroovy(cypher); - gremlin = this.buildQueryableGremlin(graph, gremlin); - return gremlin; - } - - private String buildQueryableGremlin(String graph, String gremlin) { - /* - * `CREATE (a:person { name : 'test', age: 20) return a` - * would be translated to: - * `g.addV('person').as('a').property(single, 'name', 'test') ...`, - * but hugegraph don't support `.property(single, k, v)`, - * so we replace it to `.property(k, v)` here - */ - gremlin = gremlin.replace(".property(single,", ".property("); - - return gremlin; - } -} From 9a6fd4afe7fd4e2c01d6a13a023765ac97e9a5da Mon Sep 17 00:00:00 2001 From: "lynn.bond" Date: Mon, 6 Mar 2023 18:12:46 +0800 Subject: [PATCH 03/11] Updated some license things and refactored the implementation of SaslNegotiator. --- .licenserc.yaml | 2 + LICENSE | 2 + .../api/cypher/CypherOpProcessor.java | 16 +++ .../hugegraph/api/cypher/CypherPlugin.java | 11 ++ .../hugegraph/api/gremlin/CypherAPI.java | 112 ++++++++++++++++++ .../hugegraph/auth/StandardAuthenticator.java | 57 ++++----- 6 files changed, 167 insertions(+), 33 deletions(-) create mode 100644 hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java diff --git a/.licenserc.yaml b/.licenserc.yaml index ea56bb6e15..6a98da9a5e 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -94,6 +94,8 @@ header: # `header` section is configurations for source codes license header. - '**/type/Nameable.java' - '**/define/Cardinality.java' - '**/util/StringEncoding.java' + - 'hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java' + - 'hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java' comment: on-failure # on what condition license-eye will comment on the pull request, `on-failure`, `always`, `never`. # license-location-threshold specifies the index threshold where the license header can be located, diff --git a/LICENSE b/LICENSE index 84199011b5..0634009f19 100644 --- a/LICENSE +++ b/LICENSE @@ -214,3 +214,5 @@ hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeScriptT hugegraph-core/src/main/java/org/apache/hugegraph/type/Nameable.java from https://github.com/JanusGraph/janusgraph hugegraph-core/src/main/java/org/apache/hugegraph/type/define/Cardinality.java from https://github.com/JanusGraph/janusgraph hugegraph-core/src/main/java/org/apache/hugegraph/util/StringEncoding.java from https://github.com/JanusGraph/janusgraph +hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java from https://github.com/opencypher/cypher-for-gremlin +hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java from https://github.com/opencypher/cypher-for-gremlin diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java index 99bbb47bb6..c1d72ee532 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java @@ -14,6 +14,22 @@ * limitations under the License. */ + +/** + * Description of the modifications: + * + * 1) Changed the method signature to adopt the gremlin-server 3.5.1. + * public Optional> selectOther(RequestMessage requestMessage) + * --> + * public Optional> selectOther(Context ctx) + * + * 2) Changed the package name. + * org.opencypher.gremlin.server.op.cypher + * --> + * org.apache.hugegraph.api.cypher + * + */ + package org.apache.hugegraph.api.cypher; import io.netty.channel.ChannelHandlerContext; diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java index fc6c2dda6e..ea06fb0361 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java @@ -14,6 +14,17 @@ * limitations under the License. */ + +/** + * Description of the modifications: + * + * 1) Changed the package name. + * org.opencypher.gremlin.server.jsr223 + * --> + * org.apache.hugegraph.api.cypher + * + */ + package org.apache.hugegraph.api.cypher; import org.apache.tinkerpop.gremlin.jsr223.Customizer; diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java new file mode 100644 index 0000000000..19802580ae --- /dev/null +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.api.gremlin; + +import org.opencypher.gremlin.translation.TranslationFacade; +import org.slf4j.Logger; + +import org.apache.hugegraph.api.filter.CompressInterceptor.Compress; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.Log; +import com.codahale.metrics.annotation.Timed; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; + +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; + +//@Path("graphs/{graph}/cypher") +@Singleton +@Tag(name = "CypherAPI") +@Deprecated +public class CypherAPI extends GremlinQueryAPI { + + private static final Logger LOG = Log.logger(CypherAPI.class); + + @GET + @Timed + @Compress(buffer = (1024 * 40)) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public Response query(@PathParam("graph") String graph, + @Context HttpHeaders headers, + @QueryParam("cypher") String cypher) { + LOG.debug("Graph [{}] query by cypher: {}", graph, cypher); + + return this.queryByCypher(graph, headers, cypher); + } + + @POST + @Timed + @Compress + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public Response post(@PathParam("graph") String graph, + @Context HttpHeaders headers, + String cypher) { + LOG.debug("Graph [{}] query by cypher: {}", graph, cypher); + return this.queryByCypher(graph, headers, cypher); + } + + private Response queryByCypher(String graph, + HttpHeaders headers, + String cypher) { + E.checkArgument(cypher != null && !cypher.isEmpty(), + "The cypher parameter can't be null or empty"); + + String gremlin = this.translateCpyher2Gremlin(graph, cypher); + LOG.debug("translated gremlin is {}", gremlin); + + String auth = headers.getHeaderString(HttpHeaders.AUTHORIZATION); + String request = "{" + + "\"gremlin\":\"" + gremlin + "\"," + + "\"bindings\":{}," + + "\"language\":\"gremlin-groovy\"," + + "\"aliases\":{\"g\":\"__g_" + graph + "\"}}"; + + Response response = this.client().doPostRequest(auth, request); + return transformResponseIfNeeded(response); + } + + private String translateCpyher2Gremlin(String graph, String cypher) { + TranslationFacade translator = new TranslationFacade(); + String gremlin = translator.toGremlinGroovy(cypher); + gremlin = this.buildQueryableGremlin(graph, gremlin); + return gremlin; + } + + private String buildQueryableGremlin(String graph, String gremlin) { + /* + * `CREATE (a:person { name : 'test', age: 20) return a` + * would be translated to: + * `g.addV('person').as('a').property(single, 'name', 'test') ...`, + * but hugegraph don't support `.property(single, k, v)`, + * so we replace it to `.property(k, v)` here + */ + gremlin = gremlin.replace(".property(single,", ".property("); + + return gremlin; + } +} diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java b/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java index f5800af5ad..537148eb2c 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java @@ -39,13 +39,10 @@ import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.StringEncoding; -import static org.apache.tinkerpop.gremlin.groovy.jsr223.dsl.credential.CredentialGraphTokens.PROPERTY_PASSWORD; -import static org.apache.tinkerpop.gremlin.groovy.jsr223.dsl.credential.CredentialGraphTokens.PROPERTY_USERNAME; public class StandardAuthenticator implements HugeAuthenticator { private static final String INITING_STORE = "initing_store"; - private static final byte NUL = 0; private HugeGraph graph = null; private HugeGraph graph() { @@ -184,7 +181,7 @@ public AuthManager authManager() { @Override public SaslNegotiator newSaslNegotiator(InetAddress remoteAddress) { - return new PlainTextSaslAuthenticator(); + return new TokenSaslAuthenticator(); } public static void initAdminUserIfNeeded(String confFile) throws Exception { @@ -201,36 +198,31 @@ public static void initAdminUserIfNeeded(String confFile) throws Exception { } } - /** - * Copied from org.apache.tinkerpop.gremlin.server.auth.SimpleAuthenticator.PlainTextSaslAuthenticator. - * Added support of token. - */ - private class PlainTextSaslAuthenticator implements SaslNegotiator { - private boolean complete = false; + private class TokenSaslAuthenticator implements SaslNegotiator { + private static final byte NUL = 0; private String username; private String password; - private String token; @Override public byte[] evaluateResponse(final byte[] clientResponse) throws AuthenticationException { - decodeCredentials(clientResponse); - complete = true; + decode(clientResponse); return null; } @Override public boolean isComplete() { - return complete; + return this.username != null; } @Override public AuthenticatedUser getAuthenticatedUser() throws AuthenticationException { - if (!complete) throw new AuthenticationException("SASL negotiation not complete"); - final Map credentials = new HashMap<>(); - credentials.put(PROPERTY_USERNAME, username); - credentials.put(PROPERTY_PASSWORD, password); + if (!this.isComplete()) + throw new AuthenticationException("The SASL negotiation has not yet been completed."); + final Map credentials = new HashMap<>(6, 1); + credentials.put(KEY_USERNAME, username); + credentials.put(KEY_PASSWORD, password); credentials.put(KEY_TOKEN, token); return authenticate(credentials); @@ -243,29 +235,28 @@ public AuthenticatedUser getAuthenticatedUser() throws AuthenticationException { * * @param bytes encoded credentials string sent by the client */ - private void decodeCredentials(byte[] bytes) throws AuthenticationException { - byte[] user = null; - byte[] pass = null; + private void decode(byte[] bytes) throws AuthenticationException { + this.username = null; + this.password = null; + int end = bytes.length; - for (int i = bytes.length - 1 ; i >= 0; i--) { + + for (int i = bytes.length - 1; i >= 0; i--) { if (bytes[i] == NUL) { - if (pass == null) - pass = Arrays.copyOfRange(bytes, i + 1, end); - else if (user == null) - user = Arrays.copyOfRange(bytes, i + 1, end); + if (this.password == null) + password = new String(Arrays.copyOfRange(bytes, i + 1, end), StandardCharsets.UTF_8); + else if (this.username == null) + username = new String(Arrays.copyOfRange(bytes, i + 1, end), StandardCharsets.UTF_8); end = i; } } - if (null == user) throw new AuthenticationException("Authentication ID must not be null"); - if (null == pass) throw new AuthenticationException("Password must not be null"); - - username = new String(user, StandardCharsets.UTF_8); - password = new String(pass, StandardCharsets.UTF_8); + if (this.username == null) throw new AuthenticationException("SASL authentication ID must not be null."); + if (this.password == null) throw new AuthenticationException("SASL password must not be null."); /* The trick is here. >_*/ - if(password.isEmpty()){ - token=username; + if (password.isEmpty()) { + token = username; } } } From 7c7f321b77db7070a3340f09b1cbd5bb24f9da8d Mon Sep 17 00:00:00 2001 From: "lynn.bond" Date: Mon, 6 Mar 2023 20:44:59 +0800 Subject: [PATCH 04/11] Made it into HugeGraph style and added CypherApiTest to ApiTestSuite --- .../hugegraph/api/cypher/CypherAPI.java | 16 ++-- .../hugegraph/api/cypher/CypherClient.java | 19 ++--- .../hugegraph/api/cypher/CypherManager.java | 16 ++-- .../api/cypher/CypherOpProcessor.java | 80 ++++++++++--------- .../hugegraph/api/cypher/CypherPlugin.java | 20 ++--- .../apache/hugegraph/api/ApiTestSuite.java | 3 +- 6 files changed, 82 insertions(+), 72 deletions(-) diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java index 4ac6f3a6e4..b68df4d97f 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java @@ -27,7 +27,9 @@ import org.apache.hugegraph.api.filter.CompressInterceptor; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; + import com.codahale.metrics.annotation.Timed; + import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; @@ -67,8 +69,7 @@ private CypherManager cypherManager() { @Timed @CompressInterceptor.Compress(buffer = (1024 * 40)) @Produces(APPLICATION_JSON_WITH_CHARSET) - public CypherModel query(@PathParam("graph") String graph, - @Context HttpHeaders headers, + public CypherModel query(@PathParam("graph") String graph, @Context HttpHeaders headers, @QueryParam("cypher") String cypher) { LOG.debug("Graph [{}] query by cypher: {}", graph, cypher); @@ -80,21 +81,18 @@ public CypherModel query(@PathParam("graph") String graph, @CompressInterceptor.Compress @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON_WITH_CHARSET) - public CypherModel post(@PathParam("graph") String graph, - @Context HttpHeaders headers, + public CypherModel post(@PathParam("graph") String graph, @Context HttpHeaders headers, String cypher) { LOG.debug("Graph [{}] query by cypher: {}", graph, cypher); return this.queryByCypher(graph, headers, cypher); } - private CypherModel queryByCypher(String graph, - HttpHeaders headers, - String cypher) { + private CypherModel queryByCypher(String graph, HttpHeaders headers, String cypher) { E.checkArgument(graph != null && !graph.isEmpty(), - "The graph parameter can't be null or empty"); + "The graph parameter can't be null or empty"); E.checkArgument(cypher != null && !cypher.isEmpty(), - "The cypher parameter can't be null or empty"); + "The cypher parameter can't be null or empty"); Map aliases = new HashMap<>(1, 1); aliases.put("g", "__g_" + graph); diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java index e282cf9940..8669717bc3 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java @@ -26,6 +26,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; + import java.util.*; import java.util.concurrent.ExecutionException; import java.util.function.Supplier; @@ -50,9 +51,9 @@ public final class CypherClient { this.configurationSupplier = configurationSupplier; } - public CypherModel submitQuery(String cypherQuery,@Nullable Map aliases) { + public CypherModel submitQuery(String cypherQuery, @Nullable Map aliases) { E.checkArgument(cypherQuery != null && !cypherQuery.isEmpty(), - "The cypher-query parameter can't be null or empty"); + "The cypher-query parameter can't be null or empty"); Cluster cluster = Cluster.open(getConfig()); Client client = cluster.connect(); @@ -69,7 +70,7 @@ public CypherModel submitQuery(String cypherQuery,@Nullable Map res = CypherModel.dataOf(request.getRequestId().toString(), list); } catch (Exception e) { LOG.error(String.format("Failed to submit cypher-query: [ %s ], cause by:" - , cypherQuery), e); + , cypherQuery), e); res = CypherModel.failOf(request.getRequestId().toString(), e.getMessage()); } finally { client.close(); @@ -81,13 +82,13 @@ public CypherModel submitQuery(String cypherQuery,@Nullable Map private RequestMessage createRequest(String cypherQuery) { return RequestMessage.build(Tokens.OPS_EVAL) - .processor("cypher") - .add(Tokens.ARGS_GREMLIN, cypherQuery) - .create(); + .processor("cypher") + .add(Tokens.ARGS_GREMLIN, cypherQuery) + .create(); } private List doQueryList(Client client, RequestMessage request) - throws ExecutionException, InterruptedException { + throws ExecutionException, InterruptedException { ResultSet results = null; results = client.submitAsync(request).get(); @@ -125,8 +126,8 @@ public boolean equals(Object o) { CypherClient that = (CypherClient) o; return Objects.equals(userName, that.userName) - && Objects.equals(password, that.password) - && Objects.equals(token, that.token); + && Objects.equals(password, that.password) + && Objects.equals(token, that.token); } @Override diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java index 173cf1d939..69c43a3f0b 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java @@ -22,6 +22,7 @@ import org.apache.commons.configuration2.YAMLConfiguration; import javax.annotation.concurrent.ThreadSafe; + import java.io.File; import java.io.FileReader; import java.io.Reader; @@ -35,7 +36,7 @@ public final class CypherManager { public static CypherManager configOf(String configurationFile) { E.checkArgument(configurationFile != null && !configurationFile.isEmpty(), - "The configurationFile parameter can't be null or empty"); + "The configurationFile parameter can't be null or empty"); return new CypherManager(configurationFile); } @@ -45,9 +46,9 @@ private CypherManager(String configurationFile) { public CypherClient getClient(String userName, String password) { E.checkArgument(userName != null && !userName.isEmpty(), - "The userName parameter can't be null or empty"); + "The userName parameter can't be null or empty"); E.checkArgument(password != null && !password.isEmpty(), - "The password parameter can't be null or empty"); + "The password parameter can't be null or empty"); //TODO: Need to cache the client and make it hold the connection. return new CypherClient(userName, password, () -> this.cloneConfig()); @@ -55,7 +56,7 @@ public CypherClient getClient(String userName, String password) { public CypherClient getClient(String token) { E.checkArgument(token != null && !token.isEmpty(), - "The token parameter can't be null or empty"); + "The token parameter can't be null or empty"); //TODO: Need to cache the client and make it hold the connection. return new CypherClient(token, () -> this.cloneConfig()); @@ -79,7 +80,7 @@ private static YAMLConfiguration loadYaml(String configurationFile) { yaml.read(reader); } catch (Exception e) { throw new RuntimeException(String.format("Failed to load configuration file," + - " the file at %s.", configurationFile), e); + " the file at %s.", configurationFile), e); } return yaml; @@ -94,8 +95,9 @@ private static File getConfigFile(String configurationFile) { final File resourceFile = new File(resource.getFile()); if (!resourceFile.exists()) { - throw new IllegalArgumentException(String.format("Configuration file at %s does not exist" - , configurationFile)); + throw new IllegalArgumentException( + String.format("Configuration file at %s does not exist" + , configurationFile)); } return resourceFile; diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java index c1d72ee532..f121767cae 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java @@ -17,22 +17,22 @@ /** * Description of the modifications: - * + *

* 1) Changed the method signature to adopt the gremlin-server 3.5.1. * public Optional> selectOther(RequestMessage requestMessage) * --> * public Optional> selectOther(Context ctx) - * + *

* 2) Changed the package name. * org.opencypher.gremlin.server.op.cypher * --> * org.apache.hugegraph.api.cypher - * */ package org.apache.hugegraph.api.cypher; import io.netty.channel.ChannelHandlerContext; + import org.apache.tinkerpop.gremlin.driver.Tokens; import org.apache.tinkerpop.gremlin.driver.message.RequestMessage; import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage; @@ -59,6 +59,7 @@ import org.opencypher.gremlin.traversal.ProcedureContext; import org.opencypher.gremlin.traversal.ReturnNormalizer; import org.slf4j.Logger; + import scala.collection.Seq; import java.util.*; @@ -123,16 +124,19 @@ private void evalCypher(Context context) throws OpProcessorException { String translatorDefinition = getTranslatorDefinition(context); - Translator stringTranslator = Translator.builder() - .gremlinGroovy() - .build(translatorDefinition); + Translator stringTranslator = + Translator.builder() + .gremlinGroovy() + .build(translatorDefinition); - Translator traversalTranslator = Translator.builder() - .traversal(g) - .build(translatorDefinition); + Translator traversalTranslator = + Translator.builder() + .traversal(g) + .build(translatorDefinition); Seq ir = ast.translate(stringTranslator.flavor(), - stringTranslator.features(), procedureContext); + stringTranslator.features(), + procedureContext); String gremlin = TranslationWriter.write(ir, stringTranslator, parameters); logger.info("Gremlin: {}", gremlin); @@ -142,7 +146,8 @@ private void evalCypher(Context context) throws OpProcessorException { return; } - GraphTraversal traversal = TranslationWriter.write(ir, traversalTranslator, parameters); + GraphTraversal traversal = + TranslationWriter.write(ir, traversalTranslator, parameters); ReturnNormalizer returnNormalizer = ReturnNormalizer.create(ast.getReturnTypes()); Iterator normalizedTraversal = returnNormalizer.normalize(traversal); inTransaction(gts, () -> handleIterator(context, normalizedTraversal)); @@ -179,11 +184,13 @@ private GraphTraversalSource traversal(Context context) throws OpProcessorExcept if (gAlias == null) { return graphManager.getGraphNames().stream() - .sorted() - .findFirst() - .map(graphManager::getGraph) - .map(Graph::traversal) - .orElseThrow(() -> opProcessorException(msg, "No graphs found on the server")); + .sorted() + .findFirst() + .map(graphManager::getGraph) + .map(Graph::traversal) + .orElseThrow(() -> opProcessorException(msg, + "No graphs found on the " + + "server")); } Graph graph = graphManager.getGraph(gAlias); @@ -201,16 +208,16 @@ private GraphTraversalSource traversal(Context context) throws OpProcessorExcept private OpProcessorException opProcessorException(RequestMessage msg, String errorMessage) { return new OpProcessorException(errorMessage, - ResponseMessage.build(msg) - .code(SERVER_ERROR) - .statusMessage(errorMessage).create()); + ResponseMessage.build(msg) + .code(SERVER_ERROR) + .statusMessage(errorMessage).create()); } protected void handleIterator(Context context, Iterator traversal) { RequestMessage msg = context.getRequestMessage(); final long timeout = msg.getArgs().containsKey(Tokens.ARGS_EVAL_TIMEOUT) - ? ((Number) msg.getArgs().get(Tokens.ARGS_EVAL_TIMEOUT)).longValue() - : context.getSettings().evaluationTimeout; + ? ((Number) msg.getArgs().get(Tokens.ARGS_EVAL_TIMEOUT)).longValue() + : context.getSettings().evaluationTimeout; FutureTask evalFuture = new FutureTask<>(() -> { try { @@ -221,17 +228,17 @@ protected void handleIterator(Context context, Iterator traversal) { logger.error("Error during traversal iteration", ex); ChannelHandlerContext ctx = context.getChannelHandlerContext(); ctx.writeAndFlush(ResponseMessage.build(msg) - .code(SERVER_ERROR) - .statusMessage(errorMessage) - .statusAttributeException(ex) - .create()); + .code(SERVER_ERROR) + .statusMessage(errorMessage) + .statusAttributeException(ex) + .create()); } return null; } ); final Future executionFuture = context.getGremlinExecutor() - .getExecutorService().submit(evalFuture); + .getExecutorService().submit(evalFuture); if (timeout > 0) { context.getScheduledExecutorService().schedule( () -> executionFuture.cancel(true) @@ -243,7 +250,8 @@ protected void handleIterator(Context context, Iterator traversal) { private String getErrorMessage(RequestMessage msg, Exception ex) { if (ex instanceof InterruptedException || ex instanceof TraversalInterruptedException) { return String.format("A timeout occurred during traversal evaluation of [%s] " + - "- consider increasing the limit given to scriptEvaluationTimeout", msg); + "- consider increasing the limit given to scriptEvaluationTimeout", + msg); } else { return ex.getMessage(); } @@ -255,10 +263,10 @@ private void explainQuery(Context context, CypherAst ast, String gremlin) { explanation.put("options", ast.getOptions().toString()); ResponseMessage explainMsg = ResponseMessage.build(context.getRequestMessage()) - .code(ResponseStatusCode.SUCCESS) - .statusMessage("OK") - .result(singletonList(explanation)) - .create(); + .code(ResponseStatusCode.SUCCESS) + .statusMessage("OK") + .result(singletonList(explanation)) + .create(); ChannelHandlerContext ctx = context.getChannelHandlerContext(); ctx.writeAndFlush(explainMsg); @@ -280,20 +288,20 @@ private Map getParameters(Map args) { private String getTranslatorDefinition(Context context) { Map config = context.getSettings() - .optionalProcessor(CypherOpProcessor.class) - .map(p -> p.config) - .orElse(emptyMap()); + .optionalProcessor(CypherOpProcessor.class) + .map(p -> p.config) + .orElse(emptyMap()); HashSet properties = new HashSet<>(config.keySet()); properties.remove("translatorDefinition"); properties.remove("translatorFeatures"); if (!properties.isEmpty()) { throw new IllegalStateException("Unknown configuration parameters " + - "found for CypherOpProcessor: " + properties); + "found for CypherOpProcessor: " + properties); } return config.getOrDefault("translatorDefinition", DEFAULT_TRANSLATOR_DEFINITION) - + "+" + config.getOrDefault("translatorFeatures", ""); + + "+" + config.getOrDefault("translatorFeatures", ""); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java index ea06fb0361..a00369abc9 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java @@ -17,12 +17,11 @@ /** * Description of the modifications: - * + *

* 1) Changed the package name. * org.opencypher.gremlin.server.jsr223 * --> * org.apache.hugegraph.api.cypher - * */ package org.apache.hugegraph.api.cypher; @@ -43,18 +42,19 @@ public class CypherPlugin implements GremlinPlugin { - private static final ImportCustomizer imports = DefaultImportCustomizer.build() - .addClassImports(CustomPredicate.class) - .addMethodImports(getDeclaredPublicMethods(CustomPredicate.class)) - .addClassImports(CustomFunctions.class) - .addMethodImports(getDeclaredPublicMethods(CustomFunctions.class)) - .create(); + private static final ImportCustomizer imports = + DefaultImportCustomizer.build() + .addClassImports(CustomPredicate.class) + .addMethodImports(getDeclaredPublicMethods(CustomPredicate.class)) + .addClassImports(CustomFunctions.class) + .addMethodImports(getDeclaredPublicMethods(CustomFunctions.class)) + .create(); private static List getDeclaredPublicMethods(Class klass) { Method[] declaredMethods = klass.getDeclaredMethods(); return Stream.of(declaredMethods) - .filter(method -> Modifier.isPublic(method.getModifiers())) - .collect(Collectors.toList()); + .filter(method -> Modifier.isPublic(method.getModifiers())) + .collect(Collectors.toList()); } @Override diff --git a/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java b/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java index f3e0378224..a5830a4336 100644 --- a/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java +++ b/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java @@ -39,7 +39,8 @@ UserApiTest.class, LoginApiTest.class, ProjectApiTest.class, - TraversersApiTestSuite.class + TraversersApiTestSuite.class, + CypherApiTest.class }) public class ApiTestSuite { From 92378680dc17a26451b975a4fcf2d4f419f0e30b Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 7 Mar 2023 14:51:55 +0800 Subject: [PATCH 05/11] clean code --- .../hugegraph/api/cypher/CypherAPI.java | 56 ++++++------ .../hugegraph/api/cypher/CypherClient.java | 53 +++++++----- .../hugegraph/api/cypher/CypherManager.java | 33 +++---- .../hugegraph/api/cypher/CypherModel.java | 4 +- .../api/cypher/CypherOpProcessor.java | 85 ++++++++++--------- .../hugegraph/api/cypher/CypherPlugin.java | 18 ++-- 6 files changed, 129 insertions(+), 120 deletions(-) diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java index 4ac6f3a6e4..f7c5f6b8ef 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java @@ -17,39 +17,39 @@ package org.apache.hugegraph.api.cypher; -import jakarta.inject.Singleton; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; import org.apache.hugegraph.HugeException; import org.apache.hugegraph.api.API; import org.apache.hugegraph.api.filter.CompressInterceptor; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; -import com.codahale.metrics.annotation.Timed; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; +import com.codahale.metrics.annotation.Timed; + +import jakarta.inject.Singleton; import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.HttpHeaders; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; - @Path("graphs/{graph}/cypher") @Singleton public class CypherAPI extends API { private static final Logger LOG = Log.logger(CypherAPI.class); - private static final Charset UTF8 = Charset.forName(StandardCharsets.UTF_8.name()); + private static final Charset UTF8 = StandardCharsets.UTF_8; private final Base64.Decoder decoder = Base64.getUrlDecoder(); private final String basic = "Basic "; private final String bearer = "Bearer "; @@ -68,10 +68,8 @@ private CypherManager cypherManager() { @CompressInterceptor.Compress(buffer = (1024 * 40)) @Produces(APPLICATION_JSON_WITH_CHARSET) public CypherModel query(@PathParam("graph") String graph, - @Context HttpHeaders headers, - @QueryParam("cypher") String cypher) { + @Context HttpHeaders headers, @QueryParam("cypher") String cypher) { LOG.debug("Graph [{}] query by cypher: {}", graph, cypher); - return this.queryByCypher(graph, headers, cypher); } @@ -81,20 +79,16 @@ public CypherModel query(@PathParam("graph") String graph, @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON_WITH_CHARSET) public CypherModel post(@PathParam("graph") String graph, - @Context HttpHeaders headers, - String cypher) { + @Context HttpHeaders headers, String cypher) { LOG.debug("Graph [{}] query by cypher: {}", graph, cypher); return this.queryByCypher(graph, headers, cypher); } - private CypherModel queryByCypher(String graph, - HttpHeaders headers, - String cypher) { - + private CypherModel queryByCypher(String graph, HttpHeaders headers, String cypher) { E.checkArgument(graph != null && !graph.isEmpty(), - "The graph parameter can't be null or empty"); + "The graph parameter can't be null or empty"); E.checkArgument(cypher != null && !cypher.isEmpty(), - "The cypher parameter can't be null or empty"); + "The cypher parameter can't be null or empty"); Map aliases = new HashMap<>(1, 1); aliases.put("g", "__g_" + graph); @@ -134,11 +128,14 @@ private CypherClient clientViaToken(String auth) { } private Pair toUserPass(String auth) { - if (auth == null || auth.isEmpty()) return null; - if (!auth.startsWith(basic)) return null; - - String[] split = null; + if (auth == null || auth.isEmpty()) { + return null; + } + if (!auth.startsWith(basic)) { + return null; + } + String[] split; try { String encoded = auth.substring(basic.length()); byte[] userPass = this.decoder.decode(encoded); @@ -152,7 +149,6 @@ private Pair toUserPass(String auth) { if (split.length != 2) { return null; } - return ImmutablePair.of(split[0], split[1]); } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java index e282cf9940..9424fcf4f1 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java @@ -26,17 +26,23 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; -import java.util.*; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.function.Supplier; @ThreadSafe public final class CypherClient { + private static final Logger LOG = Log.logger(CypherClient.class); + private final Supplier configurationSupplier; private String userName; private String password; private String token; - private Supplier configurationSupplier; CypherClient(String userName, String password, Supplier configurationSupplier) { @@ -50,9 +56,9 @@ public final class CypherClient { this.configurationSupplier = configurationSupplier; } - public CypherModel submitQuery(String cypherQuery,@Nullable Map aliases) { + public CypherModel submitQuery(String cypherQuery, @Nullable Map aliases) { E.checkArgument(cypherQuery != null && !cypherQuery.isEmpty(), - "The cypher-query parameter can't be null or empty"); + "The cypher-query parameter can't be null or empty"); Cluster cluster = Cluster.open(getConfig()); Client client = cluster.connect(); @@ -62,14 +68,14 @@ public CypherModel submitQuery(String cypherQuery,@Nullable Map } RequestMessage request = createRequest(cypherQuery); - CypherModel res = null; + CypherModel res; try { List list = this.doQueryList(client, request); res = CypherModel.dataOf(request.getRequestId().toString(), list); } catch (Exception e) { - LOG.error(String.format("Failed to submit cypher-query: [ %s ], cause by:" - , cypherQuery), e); + LOG.error(String.format("Failed to submit cypher-query: [ %s ], cause by:", + cypherQuery), e); res = CypherModel.failOf(request.getRequestId().toString(), e.getMessage()); } finally { client.close(); @@ -81,21 +87,21 @@ public CypherModel submitQuery(String cypherQuery,@Nullable Map private RequestMessage createRequest(String cypherQuery) { return RequestMessage.build(Tokens.OPS_EVAL) - .processor("cypher") - .add(Tokens.ARGS_GREMLIN, cypherQuery) - .create(); + .processor("cypher") + .add(Tokens.ARGS_GREMLIN, cypherQuery) + .create(); } private List doQueryList(Client client, RequestMessage request) - throws ExecutionException, InterruptedException { + throws ExecutionException, InterruptedException { - ResultSet results = null; + ResultSet results; results = client.submitAsync(request).get(); Iterator iter = results.iterator(); List list = new LinkedList<>(); - for (; iter.hasNext(); ) { + while (iter.hasNext()) { Result data = iter.next(); list.add(data.getObject()); } @@ -120,13 +126,17 @@ private Configuration getConfig() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } CypherClient that = (CypherClient) o; - return Objects.equals(userName, that.userName) - && Objects.equals(password, that.password) - && Objects.equals(token, that.token); + return Objects.equals(userName, that.userName) && + Objects.equals(password, that.password) && + Objects.equals(token, that.token); } @Override @@ -136,10 +146,9 @@ public int hashCode() { @Override public String toString() { - final StringBuffer sb = new StringBuffer("CypherClient{"); - sb.append("userName='").append(userName).append('\''); - sb.append(", token='").append(token).append('\''); - sb.append('}'); + final StringBuilder sb = new StringBuilder("CypherClient{"); + sb.append("userName='").append(userName).append('\'') + .append(", token='").append(token).append('\'').append('}'); return sb.toString(); } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java index 173cf1d939..0adff82c00 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java @@ -17,25 +17,25 @@ package org.apache.hugegraph.api.cypher; -import org.apache.hugegraph.util.E; -import org.apache.commons.configuration2.Configuration; -import org.apache.commons.configuration2.YAMLConfiguration; - -import javax.annotation.concurrent.ThreadSafe; import java.io.File; import java.io.FileReader; import java.io.Reader; - import java.net.URL; +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.commons.configuration2.Configuration; +import org.apache.commons.configuration2.YAMLConfiguration; +import org.apache.hugegraph.util.E; + @ThreadSafe public final class CypherManager { - private String configurationFile; + private final String configurationFile; private YAMLConfiguration configuration; public static CypherManager configOf(String configurationFile) { E.checkArgument(configurationFile != null && !configurationFile.isEmpty(), - "The configurationFile parameter can't be null or empty"); + "The configurationFile parameter can't be null or empty"); return new CypherManager(configurationFile); } @@ -45,20 +45,20 @@ private CypherManager(String configurationFile) { public CypherClient getClient(String userName, String password) { E.checkArgument(userName != null && !userName.isEmpty(), - "The userName parameter can't be null or empty"); + "The userName parameter can't be null or empty"); E.checkArgument(password != null && !password.isEmpty(), - "The password parameter can't be null or empty"); + "The password parameter can't be null or empty"); //TODO: Need to cache the client and make it hold the connection. - return new CypherClient(userName, password, () -> this.cloneConfig()); + return new CypherClient(userName, password, this::cloneConfig); } public CypherClient getClient(String token) { E.checkArgument(token != null && !token.isEmpty(), - "The token parameter can't be null or empty"); + "The token parameter can't be null or empty"); //TODO: Need to cache the client and make it hold the connection. - return new CypherClient(token, () -> this.cloneConfig()); + return new CypherClient(token, this::cloneConfig); } private Configuration cloneConfig() { @@ -79,7 +79,7 @@ private static YAMLConfiguration loadYaml(String configurationFile) { yaml.read(reader); } catch (Exception e) { throw new RuntimeException(String.format("Failed to load configuration file," + - " the file at %s.", configurationFile), e); + " the file at %s.", configurationFile), e); } return yaml; @@ -91,11 +91,12 @@ private static File getConfigFile(String configurationFile) { if (!systemFile.exists()) { final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); final URL resource = currentClassLoader.getResource(configurationFile); + assert resource != null; final File resourceFile = new File(resource.getFile()); if (!resourceFile.exists()) { - throw new IllegalArgumentException(String.format("Configuration file at %s does not exist" - , configurationFile)); + throw new IllegalArgumentException(String.format("Configuration file at %s does " + + "not exist", configurationFile)); } return resourceFile; diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherModel.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherModel.java index 31230248ef..7a259e48e4 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherModel.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherModel.java @@ -48,13 +48,13 @@ public static CypherModel failOf(String requestId, String message) { private CypherModel() { } - public class Status { + public static class Status { public String message = ""; public int code; public Map attributes = Collections.EMPTY_MAP; } - private class Result { + private static class Result { public List data; public Map meta = Collections.EMPTY_MAP; } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java index 99bbb47bb6..c95b6dc3d9 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java @@ -17,6 +17,7 @@ package org.apache.hugegraph.api.cypher; import io.netty.channel.ChannelHandlerContext; + import org.apache.tinkerpop.gremlin.driver.Tokens; import org.apache.tinkerpop.gremlin.driver.message.RequestMessage; import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage; @@ -43,6 +44,7 @@ import org.opencypher.gremlin.traversal.ProcedureContext; import org.opencypher.gremlin.traversal.ReturnNormalizer; import org.slf4j.Logger; + import scala.collection.Seq; import java.util.*; @@ -89,8 +91,7 @@ public ThrowingConsumer getEvalOp() { } @Override - public Optional> selectOther(Context ctx) - throws OpProcessorException { + public Optional> selectOther(Context ctx) { return empty(); } @@ -107,18 +108,18 @@ private void evalCypher(Context context) throws OpProcessorException { String translatorDefinition = getTranslatorDefinition(context); - Translator stringTranslator = Translator.builder() - .gremlinGroovy() - .build(translatorDefinition); + Translator strTranslator = Translator.builder() + .gremlinGroovy() + .build(translatorDefinition); Translator traversalTranslator = Translator.builder() - .traversal(g) - .build(translatorDefinition); + .traversal(g) + .build(translatorDefinition); - Seq ir = ast.translate(stringTranslator.flavor(), - stringTranslator.features(), procedureContext); + Seq ir = ast.translate(strTranslator.flavor(), + strTranslator.features(), procedureContext); - String gremlin = TranslationWriter.write(ir, stringTranslator, parameters); + String gremlin = TranslationWriter.write(ir, strTranslator, parameters); logger.info("Gremlin: {}", gremlin); if (ast.getOptions().contains(EXPLAIN)) { @@ -126,7 +127,8 @@ private void evalCypher(Context context) throws OpProcessorException { return; } - GraphTraversal traversal = TranslationWriter.write(ir, traversalTranslator, parameters); + GraphTraversal traversal = TranslationWriter.write(ir, traversalTranslator, + parameters); ReturnNormalizer returnNormalizer = ReturnNormalizer.create(ast.getReturnTypes()); Iterator normalizedTraversal = returnNormalizer.normalize(traversal); inTransaction(gts, () -> handleIterator(context, normalizedTraversal)); @@ -135,7 +137,6 @@ private void evalCypher(Context context) throws OpProcessorException { private void inTransaction(GraphTraversalSource gts, Runnable runnable) { Graph graph = gts.getGraph(); boolean supportsTransactions = graph.features().graph().supportsTransactions(); - if (!supportsTransactions) { runnable.run(); return; @@ -157,17 +158,17 @@ private GraphTraversalSource traversal(Context context) throws OpProcessorExcept GraphManager graphManager = context.getGraphManager(); Optional> aliasesOptional = msg.optionalArgs(Tokens.ARGS_ALIASES); - String gAlias = aliasesOptional - .map(aliases -> aliases.get(Tokens.VAL_TRAVERSAL_SOURCE_ALIAS)) - .orElse(null); + String gAlias = aliasesOptional.map(alias -> alias.get(Tokens.VAL_TRAVERSAL_SOURCE_ALIAS)) + .orElse(null); if (gAlias == null) { return graphManager.getGraphNames().stream() - .sorted() - .findFirst() - .map(graphManager::getGraph) - .map(Graph::traversal) - .orElseThrow(() -> opProcessorException(msg, "No graphs found on the server")); + .sorted() + .findFirst() + .map(graphManager::getGraph) + .map(Graph::traversal) + .orElseThrow(() -> opProcessorException(msg, "No graphs found on " + + "the server")); } Graph graph = graphManager.getGraph(gAlias); @@ -184,17 +185,18 @@ private GraphTraversalSource traversal(Context context) throws OpProcessorExcept } private OpProcessorException opProcessorException(RequestMessage msg, String errorMessage) { - return new OpProcessorException(errorMessage, - ResponseMessage.build(msg) - .code(SERVER_ERROR) - .statusMessage(errorMessage).create()); + return new OpProcessorException(errorMessage, ResponseMessage.build(msg) + .code(SERVER_ERROR) + .statusMessage(errorMessage) + .create()); } + @Override protected void handleIterator(Context context, Iterator traversal) { RequestMessage msg = context.getRequestMessage(); final long timeout = msg.getArgs().containsKey(Tokens.ARGS_EVAL_TIMEOUT) - ? ((Number) msg.getArgs().get(Tokens.ARGS_EVAL_TIMEOUT)).longValue() - : context.getSettings().evaluationTimeout; + ? ((Number) msg.getArgs().get(Tokens.ARGS_EVAL_TIMEOUT)).longValue() + : context.getSettings().evaluationTimeout; FutureTask evalFuture = new FutureTask<>(() -> { try { @@ -205,17 +207,17 @@ protected void handleIterator(Context context, Iterator traversal) { logger.error("Error during traversal iteration", ex); ChannelHandlerContext ctx = context.getChannelHandlerContext(); ctx.writeAndFlush(ResponseMessage.build(msg) - .code(SERVER_ERROR) - .statusMessage(errorMessage) - .statusAttributeException(ex) - .create()); + .code(SERVER_ERROR) + .statusMessage(errorMessage) + .statusAttributeException(ex) + .create()); } return null; } ); final Future executionFuture = context.getGremlinExecutor() - .getExecutorService().submit(evalFuture); + .getExecutorService().submit(evalFuture); if (timeout > 0) { context.getScheduledExecutorService().schedule( () -> executionFuture.cancel(true) @@ -227,7 +229,8 @@ protected void handleIterator(Context context, Iterator traversal) { private String getErrorMessage(RequestMessage msg, Exception ex) { if (ex instanceof InterruptedException || ex instanceof TraversalInterruptedException) { return String.format("A timeout occurred during traversal evaluation of [%s] " + - "- consider increasing the limit given to scriptEvaluationTimeout", msg); + "- consider increasing the limit given to scriptEvaluationTimeout", + msg); } else { return ex.getMessage(); } @@ -239,10 +242,10 @@ private void explainQuery(Context context, CypherAst ast, String gremlin) { explanation.put("options", ast.getOptions().toString()); ResponseMessage explainMsg = ResponseMessage.build(context.getRequestMessage()) - .code(ResponseStatusCode.SUCCESS) - .statusMessage("OK") - .result(singletonList(explanation)) - .create(); + .code(ResponseStatusCode.SUCCESS) + .statusMessage("OK") + .result(singletonList(explanation)) + .create(); ChannelHandlerContext ctx = context.getChannelHandlerContext(); ctx.writeAndFlush(explainMsg); @@ -264,20 +267,20 @@ private Map getParameters(Map args) { private String getTranslatorDefinition(Context context) { Map config = context.getSettings() - .optionalProcessor(CypherOpProcessor.class) - .map(p -> p.config) - .orElse(emptyMap()); + .optionalProcessor(CypherOpProcessor.class) + .map(p -> p.config) + .orElse(emptyMap()); HashSet properties = new HashSet<>(config.keySet()); properties.remove("translatorDefinition"); properties.remove("translatorFeatures"); if (!properties.isEmpty()) { throw new IllegalStateException("Unknown configuration parameters " + - "found for CypherOpProcessor: " + properties); + "found for CypherOpProcessor: " + properties); } return config.getOrDefault("translatorDefinition", DEFAULT_TRANSLATOR_DEFINITION) - + "+" + config.getOrDefault("translatorFeatures", ""); + + "+" + config.getOrDefault("translatorFeatures", ""); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java index fc6c2dda6e..fce1a602ab 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java @@ -32,18 +32,18 @@ public class CypherPlugin implements GremlinPlugin { - private static final ImportCustomizer imports = DefaultImportCustomizer.build() - .addClassImports(CustomPredicate.class) - .addMethodImports(getDeclaredPublicMethods(CustomPredicate.class)) - .addClassImports(CustomFunctions.class) - .addMethodImports(getDeclaredPublicMethods(CustomFunctions.class)) - .create(); + private static final ImportCustomizer IMPORTS = + DefaultImportCustomizer.build().addClassImports(CustomPredicate.class) + .addMethodImports(getDeclaredPublicMethods(CustomPredicate.class)) + .addClassImports(CustomFunctions.class) + .addMethodImports(getDeclaredPublicMethods(CustomFunctions.class)) + .create(); private static List getDeclaredPublicMethods(Class klass) { Method[] declaredMethods = klass.getDeclaredMethods(); return Stream.of(declaredMethods) - .filter(method -> Modifier.isPublic(method.getModifiers())) - .collect(Collectors.toList()); + .filter(method -> Modifier.isPublic(method.getModifiers())) + .collect(Collectors.toList()); } @Override @@ -62,6 +62,6 @@ public boolean requireRestart() { @Override public Optional getCustomizers(String scriptEngineName) { - return Optional.of(new Customizer[]{imports}); + return Optional.of(new Customizer[]{IMPORTS}); } } From b3ae0bfa8fb24e8c9dae50c8a9861616d01e8e6f Mon Sep 17 00:00:00 2001 From: "lynn.bond" Date: Tue, 7 Mar 2023 14:53:24 +0800 Subject: [PATCH 06/11] Made it follow the HugeGraph style --- LICENSE | 4 ++-- .../hugegraph/api/cypher/CypherAPI.java | 23 ++++++++++--------- .../hugegraph/api/cypher/CypherClient.java | 10 +++++--- .../hugegraph/api/cypher/CypherManager.java | 2 +- .../hugegraph/api/cypher/CypherModel.java | 1 + .../CypherOpProcessor.java | 19 +++++++++++---- .../cypher => opencypher}/CypherPlugin.java | 7 ++++-- ...che.tinkerpop.gremlin.jsr223.GremlinPlugin | 2 +- ...pache.tinkerpop.gremlin.server.OpProcessor | 2 +- .../optimize/HugePrimaryKeyStrategy.java | 2 -- 10 files changed, 44 insertions(+), 28 deletions(-) rename hugegraph-api/src/main/java/org/apache/hugegraph/{api/cypher => opencypher}/CypherOpProcessor.java (97%) rename hugegraph-api/src/main/java/org/apache/hugegraph/{api/cypher => opencypher}/CypherPlugin.java (96%) diff --git a/LICENSE b/LICENSE index 0634009f19..ad08080e31 100644 --- a/LICENSE +++ b/LICENSE @@ -214,5 +214,5 @@ hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeScriptT hugegraph-core/src/main/java/org/apache/hugegraph/type/Nameable.java from https://github.com/JanusGraph/janusgraph hugegraph-core/src/main/java/org/apache/hugegraph/type/define/Cardinality.java from https://github.com/JanusGraph/janusgraph hugegraph-core/src/main/java/org/apache/hugegraph/util/StringEncoding.java from https://github.com/JanusGraph/janusgraph -hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java from https://github.com/opencypher/cypher-for-gremlin -hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java from https://github.com/opencypher/cypher-for-gremlin +hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java from https://github.com/opencypher/cypher-for-gremlin +hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java from https://github.com/opencypher/cypher-for-gremlin diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java index b68df4d97f..ca9d324372 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherAPI.java @@ -19,6 +19,7 @@ import jakarta.inject.Singleton; import jakarta.ws.rs.GET; +import jakarta.ws.rs.NotAuthorizedException; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; @@ -50,8 +51,10 @@ @Path("graphs/{graph}/cypher") @Singleton public class CypherAPI extends API { + private static final Logger LOG = Log.logger(CypherAPI.class); private static final Charset UTF8 = Charset.forName(StandardCharsets.UTF_8.name()); + private static final String CLIENT_CONF = "conf/remote-objects.yaml"; private final Base64.Decoder decoder = Base64.getUrlDecoder(); private final String basic = "Basic "; private final String bearer = "Bearer "; @@ -60,7 +63,7 @@ public class CypherAPI extends API { private CypherManager cypherManager() { if (this.cypherManager == null) { - this.cypherManager = CypherManager.configOf("conf/remote-objects.yaml"); + this.cypherManager = CypherManager.configOf(CLIENT_CONF); } return this.cypherManager; } @@ -107,17 +110,16 @@ private CypherClient client(HttpHeaders headers) { auth = auth.split(",")[0]; } - if (auth == null) { - throw new HugeException("The Cypher-API is being called without any authorization."); - } - - if (auth.startsWith(basic)) { - return this.clientViaBasic(auth); - } else if (auth.startsWith(bearer)) { - return this.clientViaToken(auth); + if (auth != null) { + if (auth.startsWith(basic)) { + return this.clientViaBasic(auth); + } else if (auth.startsWith(bearer)) { + return this.clientViaToken(auth); + } } - throw new HugeException("The Cypher-API is being called without any authorization."); + throw new NotAuthorizedException( + "The Cypher-API is being called without any authorization."); } private CypherClient clientViaBasic(String auth) { @@ -153,5 +155,4 @@ private Pair toUserPass(String auth) { return ImmutablePair.of(split[0], split[1]); } - } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java index 8669717bc3..5f082b5c41 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java @@ -27,12 +27,17 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; -import java.util.*; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.function.Supplier; @ThreadSafe public final class CypherClient { + private static final Logger LOG = Log.logger(CypherClient.class); private String userName; private String password; @@ -90,8 +95,7 @@ private RequestMessage createRequest(String cypherQuery) { private List doQueryList(Client client, RequestMessage request) throws ExecutionException, InterruptedException { - ResultSet results = null; - results = client.submitAsync(request).get(); + ResultSet results = client.submitAsync(request).get(); Iterator iter = results.iterator(); List list = new LinkedList<>(); diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java index 69c43a3f0b..bc4df43dc1 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java @@ -31,6 +31,7 @@ @ThreadSafe public final class CypherManager { + private String configurationFile; private YAMLConfiguration configuration; @@ -105,5 +106,4 @@ private static File getConfigFile(String configurationFile) { return systemFile; } - } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherModel.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherModel.java index 31230248ef..19c9e5245c 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherModel.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherModel.java @@ -25,6 +25,7 @@ * As same as response of GremlinAPI */ public class CypherModel { + public String requestId; public Status status = new Status(); public Result result = new Result(); diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java b/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java similarity index 97% rename from hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java rename to hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java index f121767cae..18798fbeff 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java @@ -14,22 +14,30 @@ * limitations under the License. */ - /** * Description of the modifications: *

* 1) Changed the method signature to adopt the gremlin-server 3.5.1. + *

  * public Optional> selectOther(RequestMessage requestMessage)
  * -->
  * public Optional> selectOther(Context ctx)
+ * 
+ *

*

* 2) Changed the package name. + *

  * org.opencypher.gremlin.server.op.cypher
  * -->
- * org.apache.hugegraph.api.cypher
+ * org.apache.hugegraph.opencypher
+ * 
+ *

+ *

+ * 3) Set the logger level from info to trace + *

*/ -package org.apache.hugegraph.api.cypher; +package org.apache.hugegraph.opencypher; import io.netty.channel.ChannelHandlerContext; @@ -86,6 +94,7 @@ * */ public class CypherOpProcessor extends AbstractEvalOpProcessor { + private static final String DEFAULT_TRANSLATOR_DEFINITION = "gremlin+cfog_server_extensions+inline_parameters"; @@ -114,7 +123,7 @@ public Optional> selectOther(Context ctx) private void evalCypher(Context context) throws OpProcessorException { Map args = context.getRequestMessage().getArgs(); String cypher = (String) args.get(Tokens.ARGS_GREMLIN); - logger.info("Cypher: {}", cypher.replaceAll("\n", " ")); + logger.trace("Cypher: {}", cypher.replaceAll("\n", " ")); GraphTraversalSource gts = traversal(context); DefaultGraphTraversal g = new DefaultGraphTraversal(gts.clone()); @@ -139,7 +148,7 @@ private void evalCypher(Context context) throws OpProcessorException { procedureContext); String gremlin = TranslationWriter.write(ir, stringTranslator, parameters); - logger.info("Gremlin: {}", gremlin); + logger.trace("Gremlin: {}", gremlin); if (ast.getOptions().contains(EXPLAIN)) { explainQuery(context, ast, gremlin); diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java b/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java similarity index 96% rename from hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java rename to hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java index a00369abc9..50a295008a 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java @@ -19,12 +19,15 @@ * Description of the modifications: *

* 1) Changed the package name. + *

  * org.opencypher.gremlin.server.jsr223
  * -->
- * org.apache.hugegraph.api.cypher
+ * org.apache.hugegraph.opencypher
+ * 
+ *

*/ -package org.apache.hugegraph.api.cypher; +package org.apache.hugegraph.opencypher; import org.apache.tinkerpop.gremlin.jsr223.Customizer; import org.apache.tinkerpop.gremlin.jsr223.DefaultImportCustomizer; diff --git a/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin b/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin index be2a5bfad4..4173c7e065 100644 --- a/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin +++ b/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin @@ -1 +1 @@ -org.apache.hugegraph.api.cypher.CypherPlugin +org.apache.hugegraph.opencypher.CypherPlugin diff --git a/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.server.OpProcessor b/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.server.OpProcessor index 58b2be3401..84b1028e9b 100644 --- a/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.server.OpProcessor +++ b/hugegraph-api/src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.server.OpProcessor @@ -1 +1 @@ -org.apache.hugegraph.api.cypher.CypherOpProcessor +org.apache.hugegraph.opencypher.CypherOpProcessor diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugePrimaryKeyStrategy.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugePrimaryKeyStrategy.java index 1dcffe9b07..e00e4caf80 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugePrimaryKeyStrategy.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugePrimaryKeyStrategy.java @@ -32,7 +32,6 @@ import java.util.LinkedList; import java.util.List; - public class HugePrimaryKeyStrategy extends AbstractTraversalStrategy implements ProviderOptimizationStrategy { @@ -104,6 +103,5 @@ public void apply(Traversal.Admin traversal) { for (Step index : removeSteps) { traversal.removeStep(index); } - } } From c049f6bf043f35462c2a4389f6a52a1d7780fb5f Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 7 Mar 2023 15:08:53 +0800 Subject: [PATCH 07/11] fix license --- .licenserc.yaml | 4 +- .../opencypher/CypherOpProcessor.java | 44 +++++++++---------- .../hugegraph/opencypher/CypherPlugin.java | 25 +++++------ hugegraph-dist/release-docs/LICENSE | 2 + 4 files changed, 37 insertions(+), 38 deletions(-) diff --git a/.licenserc.yaml b/.licenserc.yaml index 6a98da9a5e..334d89b51e 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -94,8 +94,8 @@ header: # `header` section is configurations for source codes license header. - '**/type/Nameable.java' - '**/define/Cardinality.java' - '**/util/StringEncoding.java' - - 'hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherOpProcessor.java' - - 'hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherPlugin.java' + - 'hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java' + - 'hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java' comment: on-failure # on what condition license-eye will comment on the pull request, `on-failure`, `always`, `never`. # license-location-threshold specifies the index threshold where the license header can be located, diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java b/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java index 071a719b93..a4dfff60a4 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java @@ -14,29 +14,6 @@ * limitations under the License. */ -/** - * Description of the modifications: - *

- * 1) Changed the method signature to adopt the gremlin-server 3.5.1. - *

- * public Optional> selectOther(RequestMessage requestMessage)
- * -->
- * public Optional> selectOther(Context ctx)
- * 
- *

- *

- * 2) Changed the package name. - *

- * org.opencypher.gremlin.server.op.cypher
- * -->
- * org.apache.hugegraph.opencypher
- * 
- *

- *

- * 3) Set the logger level from info to trace - *

- */ - package org.apache.hugegraph.opencypher; import io.netty.channel.ChannelHandlerContext; @@ -83,6 +60,27 @@ import static org.slf4j.LoggerFactory.getLogger; /** + * Description of the modifications: + *

+ * 1) Changed the method signature to adopt the gremlin-server 3.5.1. + *

+ * public Optional> selectOther(RequestMessage requestMessage)
+ * -->
+ * public Optional> selectOther(Context ctx)
+ * 
+ *

+ *

+ * 2) Changed the package name. + *

+ * org.opencypher.gremlin.server.op.cypher
+ * -->
+ * org.apache.hugegraph.opencypher
+ * 
+ *

+ *

+ * 3) Set the logger level from info to trace + *

+ * * {@link OpProcessor} implementation for processing Cypher {@link RequestMessage}s: *
  * {
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java b/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java
index b0c4a465fe..a8f43c9099 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java
@@ -14,19 +14,6 @@
  * limitations under the License.
  */
 
-
-/**
- * Description of the modifications:
- * 

- * 1) Changed the package name. - *

- * org.opencypher.gremlin.server.jsr223
- * -->
- * org.apache.hugegraph.opencypher
- * 
- *

- */ - package org.apache.hugegraph.opencypher; import org.apache.tinkerpop.gremlin.jsr223.Customizer; @@ -43,6 +30,18 @@ import java.util.stream.Collectors; import java.util.stream.Stream; + +/** + * Description of the modifications: + *

+ * 1) Changed the package name. + *

+ * org.opencypher.gremlin.server.jsr223
+ * -->
+ * org.apache.hugegraph.opencypher
+ * 
+ *

+ */ public class CypherPlugin implements GremlinPlugin { private static final ImportCustomizer IMPORTS = diff --git a/hugegraph-dist/release-docs/LICENSE b/hugegraph-dist/release-docs/LICENSE index d9e5fb9fd7..2c2f5f90a9 100644 --- a/hugegraph-dist/release-docs/LICENSE +++ b/hugegraph-dist/release-docs/LICENSE @@ -224,6 +224,8 @@ hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeScriptT hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/ProcessBasicSuite.java from https://github.com/apache/tinkerpop hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/StructureBasicSuite.java from https://github.com/apache/tinkerpop hugegraph-core/src/main/java/org/apache/hugegraph/backend/id/SnowflakeIdGenerator.java from https://github.com/twitter-archive/snowflake +hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java from https://github.com/opencypher/cypher-for-gremlin +hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java from https://github.com/opencypher/cypher-for-gremlin ======================================================================== From 4805d317ebcf457348be5760f481ee5374df13a9 Mon Sep 17 00:00:00 2001 From: "lynn.bond" Date: Tue, 7 Mar 2023 20:08:28 +0800 Subject: [PATCH 08/11] Adjusted some formats. --- .../hugegraph/api/cypher/CypherClient.java | 2 +- .../hugegraph/opencypher/CypherPlugin.java | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java index ac05abdd74..c68f91b866 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java @@ -112,7 +112,7 @@ private List doQueryList(Client client, RequestMessage request) * we had to use a trick to fix it. When the token is set, the password will be set to * an empty string, which is an uncommon value under normal conditions. * The token will then be transferred through the userName-property. - * To see org.apache.hugegraph.auth.StandardAuthenticator.PlainTextSaslAuthenticator + * To see org.apache.hugegraph.auth.StandardAuthenticator.TokenSaslAuthenticator */ private Configuration getConfig() { Configuration conf = this.configurationSupplier.get(); diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java b/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java index a8f43c9099..98cf98eeef 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherPlugin.java @@ -14,6 +14,18 @@ * limitations under the License. */ +/** + * Description of the modifications: + *

+ * 1) Changed the package name. + *

+ * org.opencypher.gremlin.server.jsr223
+ * -->
+ * org.apache.hugegraph.opencypher
+ * 
+ *

+ */ + package org.apache.hugegraph.opencypher; import org.apache.tinkerpop.gremlin.jsr223.Customizer; @@ -30,18 +42,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; - -/** - * Description of the modifications: - *

- * 1) Changed the package name. - *

- * org.opencypher.gremlin.server.jsr223
- * -->
- * org.apache.hugegraph.opencypher
- * 
- *

- */ public class CypherPlugin implements GremlinPlugin { private static final ImportCustomizer IMPORTS = From 4cadd1403d0f82cf8d0bfb3da939904d4b449cbd Mon Sep 17 00:00:00 2001 From: "lynn.bond" Date: Wed, 8 Mar 2023 11:01:17 +0800 Subject: [PATCH 09/11] Removed the previous CypherAPI and made some formatting changes. --- .../hugegraph/api/cypher/CypherClient.java | 10 +- .../hugegraph/api/cypher/CypherManager.java | 6 +- .../hugegraph/api/gremlin/CypherAPI.java | 112 ------------------ 3 files changed, 8 insertions(+), 120 deletions(-) delete mode 100644 hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java index c68f91b866..10e92f2c78 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java @@ -74,7 +74,7 @@ public CypherModel submitQuery(String cypherQuery, @Nullable Map List list = this.doQueryList(client, request); res = CypherModel.dataOf(request.getRequestId().toString(), list); } catch (Exception e) { - LOG.error(String.format("Failed to submit cypher-query: [ %s ], cause by:", + LOG.error(String.format("Failed to submit cypher-query: [ %s ], caused by:", cypherQuery), e); res = CypherModel.failOf(request.getRequestId().toString(), e.getMessage()); } finally { @@ -144,10 +144,10 @@ public int hashCode() { @Override public String toString() { - final StringBuilder sb = new StringBuilder("CypherClient{"); - sb.append("userName='").append(userName).append('\'') - .append(", token='").append(token).append('\'').append('}'); + final StringBuilder builder = new StringBuilder("CypherClient{"); + builder.append("userName='").append(userName).append('\'') + .append(", token='").append(token).append('\'').append('}'); - return sb.toString(); + return builder.toString(); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java index 5bb21fd9b8..c2ef14e5e3 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java @@ -79,7 +79,7 @@ private static YAMLConfiguration loadYaml(String configurationFile) { yaml.read(reader); } catch (Exception e) { throw new RuntimeException(String.format("Failed to load configuration file," + - " the file at %s.", configurationFile), e); + " the file at '%s'.", configurationFile), e); } return yaml; @@ -95,8 +95,8 @@ private static File getConfigFile(String configurationFile) { final File resourceFile = new File(resource.getFile()); if (!resourceFile.exists()) { - throw new IllegalArgumentException(String.format("Configuration file at %s does " + - "not exist", configurationFile)); + throw new IllegalArgumentException(String.format("Configuration file at '%s' does" + + " not exist", configurationFile)); } return resourceFile; diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java deleted file mode 100644 index 19802580ae..0000000000 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/gremlin/CypherAPI.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with this - * work for additional information regarding copyright ownership. The ASF - * licenses this file to You under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -package org.apache.hugegraph.api.gremlin; - -import org.opencypher.gremlin.translation.TranslationFacade; -import org.slf4j.Logger; - -import org.apache.hugegraph.api.filter.CompressInterceptor.Compress; -import org.apache.hugegraph.util.E; -import org.apache.hugegraph.util.Log; -import com.codahale.metrics.annotation.Timed; - -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; - -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.Response; - -//@Path("graphs/{graph}/cypher") -@Singleton -@Tag(name = "CypherAPI") -@Deprecated -public class CypherAPI extends GremlinQueryAPI { - - private static final Logger LOG = Log.logger(CypherAPI.class); - - @GET - @Timed - @Compress(buffer = (1024 * 40)) - @Produces(APPLICATION_JSON_WITH_CHARSET) - public Response query(@PathParam("graph") String graph, - @Context HttpHeaders headers, - @QueryParam("cypher") String cypher) { - LOG.debug("Graph [{}] query by cypher: {}", graph, cypher); - - return this.queryByCypher(graph, headers, cypher); - } - - @POST - @Timed - @Compress - @Consumes(APPLICATION_JSON) - @Produces(APPLICATION_JSON_WITH_CHARSET) - public Response post(@PathParam("graph") String graph, - @Context HttpHeaders headers, - String cypher) { - LOG.debug("Graph [{}] query by cypher: {}", graph, cypher); - return this.queryByCypher(graph, headers, cypher); - } - - private Response queryByCypher(String graph, - HttpHeaders headers, - String cypher) { - E.checkArgument(cypher != null && !cypher.isEmpty(), - "The cypher parameter can't be null or empty"); - - String gremlin = this.translateCpyher2Gremlin(graph, cypher); - LOG.debug("translated gremlin is {}", gremlin); - - String auth = headers.getHeaderString(HttpHeaders.AUTHORIZATION); - String request = "{" + - "\"gremlin\":\"" + gremlin + "\"," + - "\"bindings\":{}," + - "\"language\":\"gremlin-groovy\"," + - "\"aliases\":{\"g\":\"__g_" + graph + "\"}}"; - - Response response = this.client().doPostRequest(auth, request); - return transformResponseIfNeeded(response); - } - - private String translateCpyher2Gremlin(String graph, String cypher) { - TranslationFacade translator = new TranslationFacade(); - String gremlin = translator.toGremlinGroovy(cypher); - gremlin = this.buildQueryableGremlin(graph, gremlin); - return gremlin; - } - - private String buildQueryableGremlin(String graph, String gremlin) { - /* - * `CREATE (a:person { name : 'test', age: 20) return a` - * would be translated to: - * `g.addV('person').as('a').property(single, 'name', 'test') ...`, - * but hugegraph don't support `.property(single, k, v)`, - * so we replace it to `.property(k, v)` here - */ - gremlin = gremlin.replace(".property(single,", ".property("); - - return gremlin; - } -} From 378293ff7fb98a333d84b793ab1d7e915707afcd Mon Sep 17 00:00:00 2001 From: "lynn.bond" Date: Tue, 14 Mar 2023 11:51:59 +0800 Subject: [PATCH 10/11] Ajusted some formats --- .../hugegraph/api/cypher/CypherManager.java | 20 +++++++------------ .../hugegraph/auth/StandardAuthenticator.java | 18 ++++++++--------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java index c2ef14e5e3..519ca66d9e 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherManager.java @@ -30,6 +30,7 @@ @ThreadSafe public final class CypherManager { + private final String configurationFile; private YAMLConfiguration configuration; @@ -49,7 +50,7 @@ public CypherClient getClient(String userName, String password) { E.checkArgument(password != null && !password.isEmpty(), "The password parameter can't be null or empty"); - //TODO: Need to cache the client and make it hold the connection. + // TODO: Need to cache the client and make it hold the connection. return new CypherClient(userName, password, this::cloneConfig); } @@ -57,7 +58,7 @@ public CypherClient getClient(String token) { E.checkArgument(token != null && !token.isEmpty(), "The token parameter can't be null or empty"); - //TODO: Need to cache the client and make it hold the connection. + // TODO: Need to cache the client and make it hold the connection. return new CypherClient(token, this::cloneConfig); } @@ -65,14 +66,12 @@ private Configuration cloneConfig() { if (this.configuration == null) { this.configuration = loadYaml(this.configurationFile); } - return (Configuration) this.configuration.clone(); } private static YAMLConfiguration loadYaml(String configurationFile) { File yamlFile = getConfigFile(configurationFile); YAMLConfiguration yaml; - try { Reader reader = new FileReader(yamlFile); yaml = new YAMLConfiguration(); @@ -81,27 +80,22 @@ private static YAMLConfiguration loadYaml(String configurationFile) { throw new RuntimeException(String.format("Failed to load configuration file," + " the file at '%s'.", configurationFile), e); } - return yaml; } private static File getConfigFile(String configurationFile) { - final File systemFile = new File(configurationFile); - + File systemFile = new File(configurationFile); if (!systemFile.exists()) { - final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); - final URL resource = currentClassLoader.getResource(configurationFile); + ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); + URL resource = currentClassLoader.getResource(configurationFile); assert resource != null; - final File resourceFile = new File(resource.getFile()); - + File resourceFile = new File(resource.getFile()); if (!resourceFile.exists()) { throw new IllegalArgumentException(String.format("Configuration file at '%s' does" + " not exist", configurationFile)); } return resourceFile; - } - return systemFile; } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java b/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java index 5fc4f49e4a..e706505b24 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java @@ -216,6 +216,7 @@ public static void initAdminUserIfNeeded(String confFile) throws Exception { } private class TokenSaslAuthenticator implements SaslNegotiator { + private static final byte NUL = 0; private String username; private String password; @@ -261,16 +262,15 @@ private void decode(byte[] bytes) throws AuthenticationException { int end = bytes.length; for (int i = bytes.length - 1; i >= 0; i--) { - if (bytes[i] == NUL) { - if (this.password == null) { - password = new String(Arrays.copyOfRange(bytes, i + 1, end), - StandardCharsets.UTF_8); - } else if (this.username == null) { - username = new String(Arrays.copyOfRange(bytes, i + 1, end), - StandardCharsets.UTF_8); - } - end = i; + if (bytes[i] != NUL) continue; + if (this.password == null) { + password = new String(Arrays.copyOfRange(bytes, i + 1, end), + StandardCharsets.UTF_8); + } else if (this.username == null) { + username = new String(Arrays.copyOfRange(bytes, i + 1, end), + StandardCharsets.UTF_8); } + end = i; } if (this.username == null) { From 5457d18d0f8af579cf81ac3bc8ac328928c9300c Mon Sep 17 00:00:00 2001 From: "lynn.bond" Date: Wed, 15 Mar 2023 10:33:24 +0800 Subject: [PATCH 11/11] Adjusted the if-statement format. --- .../java/org/apache/hugegraph/auth/StandardAuthenticator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java b/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java index e706505b24..48e74225f0 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java @@ -262,7 +262,9 @@ private void decode(byte[] bytes) throws AuthenticationException { int end = bytes.length; for (int i = bytes.length - 1; i >= 0; i--) { - if (bytes[i] != NUL) continue; + if (bytes[i] != NUL) { + continue; + } if (this.password == null) { password = new String(Arrays.copyOfRange(bytes, i + 1, end), StandardCharsets.UTF_8);