From 3ba9613e4f777b091bf2da90d7db0e2a4bd249e5 Mon Sep 17 00:00:00 2001 From: liningrui Date: Thu, 23 Aug 2018 20:16:59 +0800 Subject: [PATCH] HugeGraph-1396: Add studio backend Change-Id: I3f0c4764cb5b36684a918652b7eeadd04434340c --- LICENSE | 202 ++++++ README.md | 19 + pom.xml | 82 +++ studio-api/pom.xml | 87 +++ .../hugegraph/studio/board/model/Board.java | 77 +++ .../hugegraph/studio/board/model/Card.java | 103 +++ .../studio/board/model/CardConfig.java | 153 +++++ .../studio/board/model/QueryResult.java | 314 +++++++++ .../studio/board/model/ViewType.java | 19 + .../studio/board/model/vis/Color.java | 177 +++++ .../studio/board/model/vis/EdgeColor.java | 94 +++ .../studio/board/model/vis/Font.java | 134 ++++ .../studio/board/model/vis/Icon.java | 136 ++++ .../studio/board/model/vis/Scaling.java | 93 +++ .../studio/board/model/vis/VisNode.java | 147 ++++ .../board/serializer/BoardSerializer.java | 130 ++++ .../studio/board/service/BoardService.java | 642 ++++++++++++++++++ .../studio/config/NodeColorOption.java | 43 ++ .../studio/config/StudioApiConfig.java | 192 ++++++ .../studio/config/StudioApiOptions.java | 296 ++++++++ .../studio/gremlin/GremlinOptimizer.java | 141 ++++ .../src/main/resources/applicationContext.xml | 10 + studio-api/src/main/resources/log4j2.xml | 30 + studio-api/src/main/webapp/WEB-INF/web.xml | 40 ++ studio-dist/pom.xml | 158 +++++ .../src/assembly/descriptor/assembly.xml | 61 ++ .../assembly/static/bin/hugegraph-studio.sh | 149 ++++ studio-dist/src/assembly/static/bin/util.sh | 20 + .../static/conf/hugegraph-studio.properties | 50 ++ .../src/assembly/static/conf/log4j2.xml | 33 + .../hugegraph/studio/HugeGraphStudio.java | 190 ++++++ .../studio/config/StudioServerConfig.java | 67 ++ .../studio/config/StudioServerOptions.java | 106 +++ 33 files changed, 4195 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 studio-api/pom.xml create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/Board.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/Card.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/CardConfig.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/QueryResult.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/ViewType.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Color.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/EdgeColor.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Font.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Icon.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Scaling.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/VisNode.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/serializer/BoardSerializer.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/board/service/BoardService.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/config/NodeColorOption.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/config/StudioApiConfig.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/config/StudioApiOptions.java create mode 100644 studio-api/src/main/java/com/baidu/hugegraph/studio/gremlin/GremlinOptimizer.java create mode 100644 studio-api/src/main/resources/applicationContext.xml create mode 100755 studio-api/src/main/resources/log4j2.xml create mode 100644 studio-api/src/main/webapp/WEB-INF/web.xml create mode 100644 studio-dist/pom.xml create mode 100644 studio-dist/src/assembly/descriptor/assembly.xml create mode 100755 studio-dist/src/assembly/static/bin/hugegraph-studio.sh create mode 100644 studio-dist/src/assembly/static/bin/util.sh create mode 100644 studio-dist/src/assembly/static/conf/hugegraph-studio.properties create mode 100755 studio-dist/src/assembly/static/conf/log4j2.xml create mode 100644 studio-dist/src/main/java/com/baidu/hugegraph/studio/HugeGraphStudio.java create mode 100644 studio-dist/src/main/java/com/baidu/hugegraph/studio/config/StudioServerConfig.java create mode 100644 studio-dist/src/main/java/com/baidu/hugegraph/studio/config/StudioServerOptions.java diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..e06d2081 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. + diff --git a/README.md b/README.md new file mode 100644 index 00000000..98c11ad9 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# hugegraph-studio + +hugegraph-studio is an interactive developer’s tool for HugeGraph, a graphical IDE based on web browsers. it provides a graphical interface for Gremlin input. For developers, it's more intuitive and easy to query and analyze graph data through hugegraph-studio. + +## Features + +- Drawing graph through the vertices and edges +- Displaying data details of vertices, edges and schema +- Intelligent Gremlin editor that provides syntax highlighting, intelligent code completion +- Board management, which is used to record the history of query and analysis +- Data analysis based on the results of Gremlin execution + +## Learn More + +[The project homepage](https://hugegraph.github.io/hugegraph-doc/quickstart/hugegraph-studio.html) contains more information about hugegraph-studio. + +## License + +hugegraph-studio is licensed under Apache 2.0 License. \ No newline at end of file diff --git a/pom.xml b/pom.xml index dc92a070..36d43106 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,89 @@ com.baidu.hugegraph hugegraph-studio + pom 0.7.0 + + studio-api + studio-dist + + + hugegraph-studio + ${release.name}-${project.version} + ${project.basedir}/assembly + ${assembly.dir}/descriptor + ${assembly.dir}/static + bash + UTF-8 + 1.8 + 1.8 + 2.8.2 + 4.12 + 21.0 + 3.6 + 8.5.2 + 4.1.5.RELEASE + + + + + com.baidu.hugegraph + hugegraph-client + 1.5.8 + + + + org.apache.tomcat.embed + tomcat-embed-core + ${tomcat.version} + + + org.apache.tomcat.embed + tomcat-embed-logging-juli + ${tomcat.version} + + + org.apache.tomcat.embed + tomcat-embed-jasper + ${tomcat.version} + + + org.apache.tomcat + tomcat-jasper + ${tomcat.version} + + + org.apache.tomcat + tomcat-jasper-el + ${tomcat.version} + + + org.apache.tomcat + tomcat-jsp-api + ${tomcat.version} + + + + + + + + + maven-compiler-plugin + 3.1 + + ${compiler.source} + ${compiler.target} + + 500 + + + -Xlint:unchecked + + + + + \ No newline at end of file diff --git a/studio-api/pom.xml b/studio-api/pom.xml new file mode 100644 index 00000000..0a8751a8 --- /dev/null +++ b/studio-api/pom.xml @@ -0,0 +1,87 @@ + + + + hugegraph-studio + com.baidu.hugegraph + 0.7.0 + + 4.0.0 + studio-api + war + + + + com.baidu.hugegraph + hugegraph-client + + + org.glassfish.jersey.core + jersey-client + 2.25.1 + + + org.glassfish.jersey.ext + jersey-spring3 + 2.25.1 + + + org.springframework + spring-core + + + org.springframework + spring-beans + + + + + + org.springframework + spring-web + ${spring.version} + + + commons-logging + commons-logging + + + + + org.springframework + spring-aop + ${spring.version} + + + commons-logging + commons-logging + + + + + org.springframework + spring-test + ${spring.version} + test + + + + + studio-api + + + org.apache.maven.plugins + maven-war-plugin + 2.1.1 + + + + src/main/webapp/WEB-INF + + + + + + + \ No newline at end of file diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/Board.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/Board.java new file mode 100644 index 00000000..c7447d3d --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/Board.java @@ -0,0 +1,77 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.board.model; + +import java.time.Instant; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Board entity for jersey restful api, and will be return as json. + * A page contains many cards. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Board { + + @JsonProperty("createTime") + private Long createTime; + + @JsonProperty("updateTime") + private Long updateTime; + + private Card card; + private QueryResult result; + + /** + * Instantiates a new Board. + */ + public Board() { + this.createTime = Instant.now().getEpochSecond(); + } + + public Long getCreateTime() { + return createTime; + } + + public Long getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Long updateTime) { + this.updateTime = updateTime; + } + + public Card getCard() { + return card; + } + + public void setCard(Card card) { + this.card = card; + } + + public QueryResult getResult() { + return result; + } + + public void setResult(QueryResult result) { + this.result = result; + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/Card.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/Card.java new file mode 100644 index 00000000..e989532b --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/Card.java @@ -0,0 +1,103 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.board.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Card { + + private String id; + private String code; + + /** + * Instantiates a new board card. + * + * @param id the id + * @param code the code + */ + @JsonCreator + public Card(@JsonProperty("id") String id, + @JsonProperty("code") String code) { + this.id = id; + this.code = code; + } + + /** + * Instantiates a new board card. + */ + public Card() { + } + + /** + * Sets id. + * + * @param id the id + */ + public void setId(String id) { + this.id = id; + } + + /** + * Card's id is uuid + * + * @return the id + */ + public String getId() { + return this.id; + } + + /** + * The code user input in the front page code editor. + * + * @return the code + */ + public String getCode() { + return this.code; + } + + /** + * Sets code. + * + * @param code the code + */ + public void setCode(String code) { + this.code = code; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || !this.getClass().equals(o.getClass())) { + return false; + } + Card that = (Card) o; + + return (that.id == null && this.id == null) || + (this.id != null && this.id.equals(that.id)); + } + + public int hashCode() { + return this.id != null ? this.id.hashCode() : 0; + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/CardConfig.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/CardConfig.java new file mode 100644 index 00000000..1d5db6c5 --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/CardConfig.java @@ -0,0 +1,153 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.board.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * CardConfig is used to save the front page widget's status. + * The status such as : + * 1. which is the current tab ? + * 2. what is the height of current input ? + * 3. what is the status of every button ? + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public final class CardConfig { + /** + * The View type. + */ + @JsonProperty("viewType") + public ViewType viewType; + + /** + * The card height. + */ + @JsonProperty("cardHeight") + public Integer cardHeight; + + /** + * The card width. + */ + @JsonProperty("cardWidth") + public Integer cardWidth; + + /** + * The Full screen. + */ + @JsonProperty("fullScreen") + public boolean fullScreen; + + /** + * The View. + */ + @JsonProperty("view") + public boolean view; + + /** + * Gets view type. + * + * @return the view type + */ + public ViewType getViewType() { + return viewType; + } + + /** + * Sets view type. + * + * @param viewType the view type + */ + public void setViewType(ViewType viewType) { + this.viewType = viewType; + } + + /** + * Gets card height. + * + * @return the card height + */ + public Integer getCardHeight() { + return cardHeight; + } + + /** + * Sets card height. + * + * @param cardHeight the card height + */ + public void setCardHeight(Integer cardHeight) { + this.cardHeight = cardHeight; + } + + /** + * Gets card width. + * + * @return the card width + */ + public Integer getCardWidth() { + return cardWidth; + } + + /** + * Sets card width. + * + * @param cardWidth the card width + */ + public void setCardWidth(Integer cardWidth) { + this.cardWidth = cardWidth; + } + + /** + * Is full screen boolean. + * + * @return the boolean + */ + public boolean isFullScreen() { + return fullScreen; + } + + /** + * Sets full screen. + * + * @param fullScreen the full screen + */ + public void setFullScreen(boolean fullScreen) { + this.fullScreen = fullScreen; + } + + /** + * Is view boolean. + * + * @return the boolean + */ + public boolean isView() { + return view; + } + + /** + * Sets view. + * + * @param view the view + */ + public void setView(boolean view) { + this.view = view; + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/QueryResult.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/QueryResult.java new file mode 100644 index 00000000..fd5afd05 --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/QueryResult.java @@ -0,0 +1,314 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.board.model; + +import static java.util.stream.Collectors.toMap; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import com.baidu.hugegraph.structure.graph.Edge; +import com.baidu.hugegraph.structure.graph.Vertex; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The result entity for jersey restful api, and will be return as json. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryResult { + + @JsonProperty("id") + private String id; + + @JsonProperty("data") + private List data; + + @JsonProperty("type") + private QueryResult.Type type; + + @JsonProperty("duration") + private Long duration = null; + + @JsonProperty("showNum") + private int showNum = 0; + + @JsonProperty("message") + private String message; + + @JsonProperty("graph") + private Graph graph; + + /** + * Instantiates a new Result. + */ + public QueryResult() { + this.id = UUID.randomUUID().toString(); + this.graph = new Graph(); + } + + /** + * Gets id. + * + * @return the id + */ + public String getId() { + return this.id; + } + + /** + * Gets type. + * + * @return the type + */ + public Type getType() { + return this.type; + } + + /** + * Gets duration. + * + * @return the duration + */ + public Long getDuration() { + return this.duration; + } + + /** + * Gets data. + * + * @return the data + */ + public List getData() { + return data; + } + + /** + * Sets data. + * + * @param data the data + */ + public void setData(List data) { + this.data = data; + } + + /** + * Sets type. + * + * @param type the type + */ + public void setType(Type type) { + this.type = type; + } + + /** + * Sets duration. + * + * @param duration the duration + */ + public void setDuration(Long duration) { + this.duration = duration; + } + + public int getShowNum() { + return showNum; + } + + public void setShowNum(int showNum) { + this.showNum = showNum; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + /** + * Gets graph. when result type is VERTEX / EDGE / PATH,web page can draw + * network graphic with javascript. + * + * To return vertices and edges list to meet the needs of network graphic + * drawing. + * + * @return the graph + */ + public Graph getGraph() { + return graph; + } + + /** + * Sets graph vertices. + * + * @param vertices the vertices + */ + public void setGraphVertices(List vertices) { + + this.graph.setVertices(vertices); + } + + /** + * Sets graph edges. + * + * @param edges the edges + */ + public void setGraphEdges(List edges) { + + this.graph.setEdges(edges); + } + + + public void setStyles(Map styles) { + this.graph.setStyles(styles); + } + + /** + * The type of gremlin result: + * + * g.V() -> VERTEX + * g.E() -> EDGE + * g.V().count() -> NUMBER + * g.V().outE().path() -> PATH + */ + public enum Type { + /** + * Vertex type. + */ + VERTEX, + + /** + * Edge type. + */ + EDGE, + + /** + * Path type. + */ + PATH, + + /** + * Empty type. + */ + EMPTY, + + /** + * Number type. + */ + NUMBER, + + /** + * Single type including number,string,boolean. + */ + SINGLE, + + /** + * IF TYPE NOT IN THE ABOVE, SET TO OTHER + */ + OTHER + } + + /** + * The Graph class is used for contain Vertices & edges. + */ + @JsonIgnoreProperties(ignoreUnknown = true) + public class Graph { + + @JsonProperty + private List vertices; + + @JsonProperty + private List edges; + + @JsonProperty + private Map styles; + + /** + * Instantiates a new Graph. + */ + public Graph() { + + } + + /** + * Gets vertices. + * + * @return the vertices + */ + public List getVertices() { + return vertices; + } + + /** + * Sets vertices. + * + * @param vertices the vertices + */ + public void setVertices(List vertices) { + if (vertices == null) { + this.vertices = vertices; + } else { + // Distinct using map + this.vertices = vertices.stream() + .collect(toMap(Vertex::id, v -> v, + (v1, v2) -> v1)).values() + .stream().collect(Collectors.toList()); + } + } + + /** + * Gets edges. + * + * @return the edges + */ + public List getEdges() { + return edges; + } + + /** + * Sets edges. + * distinct using map + * + * @param edges the edges + */ + public void setEdges(List edges) { + if (edges == null) { + this.edges = edges; + } else { + this.edges = edges.stream() + .collect(toMap(Edge::id, e -> e, (e1, e2) -> e1)) + .values().stream() + .collect(Collectors.toList()); + + } + } + + public Map getStyles() { + return styles; + } + + public void setStyles(Map styles) { + this.styles = styles; + } + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/ViewType.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/ViewType.java new file mode 100644 index 00000000..c5e51d38 --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/ViewType.java @@ -0,0 +1,19 @@ +package com.baidu.hugegraph.studio.board.model; + +/** + * The enum View type. + */ +public enum ViewType { + /** + * Table view type. + */ + TABLE, + /** + * Raw view type. + */ + RAW, + /** + * Graph view type. + */ + GRAPH; +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Color.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Color.java new file mode 100644 index 00000000..7937ba00 --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Color.java @@ -0,0 +1,177 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.board.model.vis; + +import static com.baidu.hugegraph.studio.board.model.vis.VisNode.COLOR_BACKGROUND; +import static com.baidu.hugegraph.studio.board.model.vis.VisNode.COLOR_BORDER; +import static com.baidu.hugegraph.studio.board.model.vis.VisNode.COLOR_HIGHLIGHT_BACKGROUND; +import static com.baidu.hugegraph.studio.board.model.vis.VisNode.COLOR_HIGHLIGHT_BORDER; +import static com.baidu.hugegraph.studio.board.model.vis.VisNode.COLOR_HOVER_BACKGROUND; +import static com.baidu.hugegraph.studio.board.model.vis.VisNode.COLOR_HOVER_BORDER; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import com.baidu.hugegraph.studio.config.NodeColorOption; + +public class Color { + + private static final String DEFAULT = "default"; + private static final String HOVER = "hover"; + private static final String HIGHLIGHT = "highlight"; + + private static final String DEFAULT_BACKGROUND_COLOR = "#00ccff"; + private static final String DEFAULT_BORDER_COLOR = "#00ccff"; + private static final String BACKGROUND = "background"; + private static final String BORDER = "border"; + + private String background; + private String border; + private Map highlight; + private Map hover; + + public Color() { + } + + public Color(Builder builder) { + this.background = builder.background; + this.border = builder.border; + this.highlight = builder.highlight; + this.hover = builder.hover; + } + + public static class Builder { + private String background = DEFAULT_BACKGROUND_COLOR; + private String border = DEFAULT_BORDER_COLOR; + private Map highlight = new HashMap<>(); + private Map hover = new HashMap<>(); + + public Builder(Map userData, + NodeColorOption colorOption) { + Map curColorOption = colorOption.getColor(); + this.background = curColorOption.get(DEFAULT); + this.border = curColorOption.get(DEFAULT); + this.highlight = new HashMap<>(); + this.highlight.put(BACKGROUND, curColorOption.get(HIGHLIGHT)); + this.highlight.put(BORDER, curColorOption.get(HIGHLIGHT)); + this.hover = new HashMap<>(); + this.hover.put(BACKGROUND, curColorOption.get(HOVER)); + this.hover.put(BORDER, curColorOption.get(HOVER)); + + Object border = userData.get(COLOR_BORDER); + + if (border instanceof String && + !StringUtils.isBlank((String) border)) { + this.border = (String) border; + } + + Object background = userData.get(COLOR_BACKGROUND); + if (background instanceof String && + StringUtils.isNotBlank((String) background)) { + this.background = (String) background; + } + + Object highlightBorder = userData.get(COLOR_HIGHLIGHT_BORDER); + if (highlightBorder instanceof String && + StringUtils.isNotBlank((String) highlightBorder)) { + this.highlight.replace(BORDER, (String) highlightBorder); + } + + Object highlightBackground = + userData.get(COLOR_HIGHLIGHT_BACKGROUND); + if (highlightBackground instanceof String && + StringUtils.isNotBlank((String) highlightBackground)) { + this.highlight + .replace(BACKGROUND, (String) highlightBackground); + } + + Object hoverBorder = userData.get(COLOR_HOVER_BORDER); + if (hoverBorder instanceof String && + StringUtils.isNotBlank((String) hoverBorder)) { + this.hover.replace(BORDER, (String) hoverBorder); + } + + Object hoverBackground = userData.get(COLOR_HOVER_BACKGROUND); + if (hoverBackground instanceof String && + StringUtils.isNotBlank((String) hoverBackground)) { + this.hover.replace(BACKGROUND, (String) hoverBackground); + } + } + + public Color build() { + return new Color(this); + } + + public Builder background(String background) { + this.background = background; + return this; + } + + public Builder border(String border) { + this.border = border; + return this; + } + + public Builder highlight(Map highlight) { + this.highlight = highlight; + return this; + } + + public Builder hover(Map hover) { + this.hover = hover; + return this; + } + + } + + public String getBorder() { + return border; + } + + public void setBorder(String border) { + this.border = border; + } + + public String getBackground() { + return background; + } + + public void setBackground(String background) { + this.background = background; + } + + public Map getHighlight() { + return highlight; + } + + public void setHighlight(Map highlight) { + this.highlight = highlight; + } + + public Map getHover() { + return hover; + } + + public void setHover(Map hover) { + this.hover = hover; + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/EdgeColor.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/EdgeColor.java new file mode 100644 index 00000000..2f7873ab --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/EdgeColor.java @@ -0,0 +1,94 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.board.model.vis; + +import com.baidu.hugegraph.studio.config.StudioApiConfig; + +public class EdgeColor { + + private String color; + private String highlight; + private String hover; + + public EdgeColor() { + } + + public EdgeColor(Builder builder) { + this.color = builder.color; + this.highlight = builder.highlight; + this.hover = builder.hover; + } + + public static class Builder { + private String color; + private String highlight; + private String hover; + + public Builder(StudioApiConfig conf) { + color = conf.getEdgeDefaultColor(); + highlight = conf.getEdgeHighlightColor(); + hover = conf.getEdgeHoverColor(); + } + + public EdgeColor build() { + return new EdgeColor(this); + } + + public Builder color(String color) { + this.color = color; + return this; + } + + public Builder highlight(String highlight) { + this.highlight = highlight; + return this; + } + + public Builder hover(String hover) { + this.hover = hover; + return this; + } + + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public String getHighlight() { + return highlight; + } + + public void setHighlight(String highlight) { + this.highlight = highlight; + } + + public String getHover() { + return hover; + } + + public void setHover(String hover) { + this.hover = hover; + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Font.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Font.java new file mode 100644 index 00000000..5db9cf28 --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Font.java @@ -0,0 +1,134 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.board.model.vis; + +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +public class Font { + + // Font + protected static final String FONT_COLOR = "vis.font.color"; + protected static final String FONT_SIZE = "vis.font.size"; + protected static final String FONT_FACE = "vis.font.face"; + protected static final String FONT_MULTI = "vis.font.multi"; + + private String color; + private Integer size; + private String face; + private Boolean multi; + + public Font() { + + } + + private Font(Builder builder) { + color = builder.color; + size = builder.size; + face = builder.face; + multi = builder.multi; + } + + public static class Builder { + private String color = "#343434"; + private Integer size = 12; + private String face = "arial"; + private Boolean multi = false; + + + public Builder() { + + } + + + public Builder(Map userData) { + Object fontSize = userData.get(FONT_SIZE); + if (fontSize instanceof Number) { + Number size = (Number) fontSize; + if (size != null) { + this.size = size.intValue(); + } + } + + Object fontColor = userData.get(FONT_COLOR); + if (fontColor instanceof String && + StringUtils.isNotBlank((String) fontColor)) { + this.color = (String) fontColor; + } + + Object fontFace = userData.get(FONT_FACE); + if (fontFace instanceof String && + StringUtils.isNotBlank((String) fontFace)) { + this.face = (String) fontFace; + } + + Object fontMulti = userData.get(FONT_MULTI); + if (fontMulti instanceof Boolean) { + Boolean multi = (Boolean) fontMulti; + if (multi != null) { + this.multi = multi; + } + } + } + + public Font build() { + return new Font(this); + } + + public Builder color(String color) { + this.color = color; + return this; + } + + public Builder size(Integer size) { + this.size = size; + return this; + } + + public Builder face(String face) { + this.face = face; + return this; + } + + public Builder multi(Boolean multi) { + this.multi = multi; + return this; + } + + } + + public String getColor() { + return color; + } + + public Integer getSize() { + return size; + } + + public String getFace() { + return face; + } + + public Boolean getMulti() { + return multi; + } + +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Icon.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Icon.java new file mode 100644 index 00000000..c8024889 --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Icon.java @@ -0,0 +1,136 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.board.model.vis; + +import static com.baidu.hugegraph.studio.board.model.vis.VisNode.ICON_CODE; +import static com.baidu.hugegraph.studio.board.model.vis.VisNode.ICON_COLOR; +import static com.baidu.hugegraph.studio.board.model.vis.VisNode.ICON_SIZE; +import static com.baidu.hugegraph.studio.board.model.vis.VisNode.VIS_SHAPE; + +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +public class Icon { + + private String face; + private String code; + private Integer size; + private String color; + + public Icon() { + + } + + public Icon(Builder builder) { + this.face = builder.face; + this.code = builder.code; + this.size = builder.size; + this.color = builder.color; + } + + public static class Builder { + private String face = "FontAwesome"; + private String code = "\uf111"; + private Integer size = 50; + private String color = "#2B7CE9"; + + public Builder(Map userData) { + Object shape = userData.get(VIS_SHAPE); + if (shape instanceof String) { + String shapeStr = (String) shape; + if (shapeStr.equals("icon")) { + Object iconCode = userData.get(ICON_CODE); + if (iconCode instanceof String && + StringUtils.isNotBlank((String) iconCode)) { + this.code = (String) iconCode; + } + + Object iconColor = userData.get(ICON_COLOR); + if (iconColor instanceof String && + StringUtils.isNotBlank((String) iconColor)) { + this.color = (String) iconColor; + } + + Object iconSize = userData.get(ICON_SIZE); + if (iconSize instanceof Number) { + this.size = ((Number) iconSize).intValue(); + } + } + } + } + + public Icon build() { + return new Icon(this); + } + + public Builder face(String face) { + this.face = face; + return this; + } + + public Builder code(String code) { + this.code = code; + return this; + } + + public Builder size(Integer size) { + this.size = size; + return this; + } + + public Builder color(String color) { + this.color = color; + return this; + } + } + + public String getFace() { + return face; + } + + public void setFace(String face) { + this.face = face; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Scaling.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Scaling.java new file mode 100644 index 00000000..718c5cb9 --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/Scaling.java @@ -0,0 +1,93 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.board.model.vis; + +import static com.baidu.hugegraph.studio.board.model.vis.VisNode.SCALING_MAX; +import static com.baidu.hugegraph.studio.board.model.vis.VisNode.SCALING_MIN; + +import java.util.Map; + +import com.baidu.hugegraph.studio.config.StudioApiConfig; + +public class Scaling { + + private Integer min = 10; + private Integer max = 30; + + public Scaling() { + + } + + public Scaling(Builder builder) { + this.min = builder.min; + this.max = builder.max; + } + + public static class Builder { + private Integer min = 10; + private Integer max = 30; + + public Builder(StudioApiConfig conf, Map userData) { + this.min = conf.getVertexMinSize(); + this.max = conf.getVertexMaxSize(); + + Object min = userData.get(SCALING_MIN); + if (min instanceof Number) { + this.min = ((Number) min).intValue(); + } + + Object max = userData.get(SCALING_MAX); + if (max instanceof Number) { + this.min = ((Number) max).intValue(); + } + } + + public Scaling build() { + return new Scaling(this); + } + + public Builder min(Integer min) { + this.min = min; + return this; + } + + public Builder max(Integer max) { + this.max = max; + return this; + } + + } + + public Integer getMin() { + return min; + } + + public void setMin(Integer min) { + this.min = min; + } + + public Integer getMax() { + return max; + } + + public void setMax(Integer max) { + this.max = max; + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/VisNode.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/VisNode.java new file mode 100644 index 00000000..069a4dec --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/model/vis/VisNode.java @@ -0,0 +1,147 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.board.model.vis; + +import java.util.Map; + +import com.baidu.hugegraph.studio.config.NodeColorOption; +import com.baidu.hugegraph.studio.config.StudioApiConfig; +import com.fasterxml.jackson.annotation.JsonProperty; + +import jersey.repackaged.com.google.common.collect.ImmutableMap; + +/** + * The style of vertexLabel. + */ +public class VisNode { + + // Shape + protected static final String VIS_SHAPE = "vis.shape"; + + // Size + protected static final String VIS_SIZE = "vis.size"; + + // Scaling + protected static final String SCALING_MIN = "vis.scaling.min"; + protected static final String SCALING_MAX = "vis.scaling.max"; + + // Color + protected static final String COLOR_BORDER = "vis.border"; + protected static final String COLOR_BACKGROUND = "vis.background"; + protected static final String COLOR_HIGHLIGHT_BORDER = + "vis.highlight.border"; + protected static final String COLOR_HIGHLIGHT_BACKGROUND = + "vis.highlight.background"; + protected static final String COLOR_HOVER_BORDER = "vis.hover.border"; + protected static final String COLOR_HOVER_BACKGROUND = + "vis.hover.background"; + + // Icon + protected static final String ICON_CODE = "vis.icon.code"; + protected static final String ICON_COLOR = "vis.icon.color"; + protected static final String ICON_SIZE = "vis.icon.size"; + + private String shape; + private Integer size; + + @JsonProperty + private Color color; + @JsonProperty + private Icon icon; + @JsonProperty + private Scaling scaling; + + public VisNode() { + } + + public VisNode(String shape, Integer size, Color color, Icon icon, + Scaling scaling) { + this.shape = shape; + this.size = size; + this.color = color; + this.icon = icon; + this.scaling = scaling; + } + + public VisNode(NodeColorOption colorOption) { + this(ImmutableMap.of(), colorOption); + } + + public VisNode(Map userData, NodeColorOption colorOption) { + this(StudioApiConfig.getInstance().getVertexShape(), + StudioApiConfig.getInstance().getVertexSize(), + new Color.Builder(userData, colorOption).build(), + new Icon.Builder(userData).build(), + new Scaling.Builder(StudioApiConfig.getInstance(), userData) + .build()); + + // Size + Object size = userData.get(VIS_SIZE); + if (size instanceof Number) { + this.size = ((Number) size).intValue(); + } + + // Shape + Object shape = userData.get(VIS_SHAPE); + if (shape instanceof String) { + this.shape = (String) shape; + } + } + + public String getShape() { + return shape; + } + + public void setShape(String shape) { + this.shape = shape; + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } + + public Icon getIcon() { + return icon; + } + + public void setIcon(Icon icon) { + this.icon = icon; + } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } + + public Scaling getScaling() { + return scaling; + } + + public void setScaling(Scaling scaling) { + this.scaling = scaling; + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/serializer/BoardSerializer.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/serializer/BoardSerializer.java new file mode 100644 index 00000000..724a4269 --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/serializer/BoardSerializer.java @@ -0,0 +1,130 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.board.serializer; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.time.Instant; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.slf4j.Logger; +import org.springframework.stereotype.Repository; + +import com.baidu.hugegraph.studio.board.model.Board; +import com.baidu.hugegraph.studio.config.StudioApiConfig; +import com.baidu.hugegraph.util.Log; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Charsets; +import com.google.common.base.Preconditions; + +@Repository("boardSerializer") +public class BoardSerializer { + + private static final Logger LOG = Log.logger(BoardSerializer.class); + + private final ObjectMapper mapper = new ObjectMapper(); + private StudioApiConfig configuration; + private String filePath; + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Lock readLock = lock.readLock(); + private final Lock writeLock = lock.writeLock(); + + public BoardSerializer() { + initBoardRepository(); + } + + private void initBoardRepository() { + configuration = StudioApiConfig.getInstance(); + filePath = configuration.getBoardFilePath(); + Preconditions.checkNotNull(filePath); + + LOG.info("The board file path is: {}", filePath); + File file = new File(filePath); + if (!file.exists()) { + try { + file.getParentFile().mkdirs(); + file.createNewFile(); + } catch (IOException e) { + throw new RuntimeException(String.format( + "Failed to create board file with path '%s'", + filePath), e); + } + } + Preconditions.checkArgument(file.exists() && file.isFile()); + } + + /** + * Save board entity to disk as json. + */ + // TODO: Support append and serialize in bytes + public void save(Board board) { + Preconditions.checkNotNull(board); + board.setUpdateTime(Instant.now().getEpochSecond()); + + /* + * Should we flush and close for each buffered writing? Also, why + * not throw runtime exception if write failed in this case? + */ + writeLock.lock(); + try (OutputStream os = new FileOutputStream(filePath); + DataOutputStream out = new DataOutputStream(os)) { + byte[] all = mapper.writeValueAsString(board) + .getBytes(Charsets.UTF_8); + out.writeInt(all.length); + out.write(all); + LOG.info("Write board file: {}", filePath); + } catch (IOException e) { + LOG.error("Failed to write board file: {}", filePath, e); + throw new RuntimeException(String.format( + "Failed to write board file: %s", filePath)); + } finally { + writeLock.unlock(); + } + } + + /** + * Read board entity from disk. + */ + public Board load() { + readLock.lock(); + try (InputStream is = new FileInputStream(filePath); + DataInputStream input = new DataInputStream(is)) { + int len = input.readInt(); + byte[] bytes = new byte[len]; + LOG.info("Read total data: {} bytes", len); + input.readFully(bytes); + return mapper.readValue(bytes, Board.class); + } catch (IOException e) { + throw new RuntimeException(String.format( + "Failed to read File: '%s'", filePath), e); + } finally { + readLock.unlock(); + } + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/board/service/BoardService.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/service/BoardService.java new file mode 100644 index 00000000..de0e1193 --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/board/service/BoardService.java @@ -0,0 +1,642 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.board.service; + +import static com.baidu.hugegraph.studio.board.model.QueryResult.Type; +import static com.baidu.hugegraph.studio.board.model.QueryResult.Type.EDGE; +import static com.baidu.hugegraph.studio.board.model.QueryResult.Type.EMPTY; +import static com.baidu.hugegraph.studio.board.model.QueryResult.Type.OTHER; +import static com.baidu.hugegraph.studio.board.model.QueryResult.Type.PATH; +import static com.baidu.hugegraph.studio.board.model.QueryResult.Type.SINGLE; +import static com.baidu.hugegraph.studio.board.model.QueryResult.Type.VERTEX; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +import com.baidu.hugegraph.driver.GremlinManager; +import com.baidu.hugegraph.driver.HugeClient; +import com.baidu.hugegraph.driver.SchemaManager; +import com.baidu.hugegraph.structure.graph.Edge; +import com.baidu.hugegraph.structure.graph.Vertex; +import com.baidu.hugegraph.structure.gremlin.Result; +import com.baidu.hugegraph.structure.gremlin.ResultSet; +import com.baidu.hugegraph.structure.schema.VertexLabel; +import com.baidu.hugegraph.studio.board.model.Board; +import com.baidu.hugegraph.studio.board.model.Card; +import com.baidu.hugegraph.studio.board.model.QueryResult; +import com.baidu.hugegraph.studio.board.model.vis.EdgeColor; +import com.baidu.hugegraph.studio.board.model.vis.Font; +import com.baidu.hugegraph.studio.board.model.vis.VisNode; +import com.baidu.hugegraph.studio.board.serializer.BoardSerializer; +import com.baidu.hugegraph.studio.config.NodeColorOption; +import com.baidu.hugegraph.studio.config.StudioApiConfig; +import com.baidu.hugegraph.studio.gremlin.GremlinOptimizer; +import com.baidu.hugegraph.util.Log; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; + +/** + * Board service for Jersey Restful Api + */ +@Path("board") +public class BoardService { + + private static final Logger LOG = Log.logger(BoardService.class); + private static final int GREMLIN_MAX_IDS = 250; + + // The max number of one vertex related edge. + // Vis can deal with about 200 edges. + private static final int MAX_EDGES_PER_VERTEX = 200; + private static final StudioApiConfig conf = StudioApiConfig.getInstance(); + + @Autowired + private BoardSerializer boardSerializer; + + @Autowired + private GremlinOptimizer gremlinOptimizer; + + private HugeClient newHugeClient() { + return new HugeClient(conf.getGraphServerUrl(), conf.getGraphName()); + } + + /** + * To execute the code (gremlin) in the card of page. + * + * Execute the gremlin code via HugeClient. + * Gremlin result will be stored in two places, the original data is saved + * as a List, another is translated into a graph or a table object + * if possible. + * + * @param card The card value of the current card. + * @return The whole graph with json(Vertices & Eges). + */ + @POST + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response executeGremlin(Card card) { + Preconditions.checkArgument(card != null); + + HugeClient client = null; + try { + client = newHugeClient(); + } catch (Exception e) { + QueryResult result = new QueryResult(); + result.setMessage("Failed to connect HugeGraphServer"); + return Response.status(500).entity(result).build(); + } + + try { + QueryResult queryResult = this.executeQuery(card, client); + + Board board = new Board(); + board.setCard(card); + board.setResult(queryResult); + boardSerializer.save(board); + + return Response.status(200).entity(queryResult).build(); + } catch (Exception e) { + QueryResult result = new QueryResult(); + result.setMessage(e.getMessage()); + return Response.status(500).entity(result).build(); + } + } + + private QueryResult executeQuery(Card card, HugeClient client) { + long startTime = System.currentTimeMillis(); + + GremlinManager gremlinManager = client.gremlin(); + + int limit = conf.getLimitData(); + // To know whether has more record, + // so add "limit(limit+1)" after code. + String limitCode = gremlinOptimizer.limitOptimize(card.getCode(), + limit + 1); + LOG.info(limitCode); + + // Execute gremlin by HugeClient. + ResultSet resultSet = gremlinManager.gremlin(limitCode).execute(); + + QueryResult queryResult = new QueryResult(); + /* + * Gremlin result will be stored in two places, the original + * data is saved as a List, another is translated + * into a graph or a table object if possible. + */ + queryResult.setData(resultSet.data()); + + List vertices = new ArrayList<>(); + List edges = new ArrayList<>(); + Map styles = new HashMap<>(); + List paths = new ArrayList<>(); + if (!resultSet.iterator().hasNext()) { + queryResult.setType(EMPTY); + } + + queryResult.setType(getResultType(resultSet, limit)); + int count = 0; + + for (Iterator results = resultSet.iterator(); + results.hasNext();) { + + /* + * The result might be null, and the object must be got via + * Result.getObject method. + */ + Result or = results.next(); + if (or == null) { + continue; + } + Object object = or.getObject(); + if (object instanceof Vertex) { + vertices.add((Vertex) object); + } else if (object instanceof Edge) { + edges.add((Edge) object); + } else if (object instanceof + com.baidu.hugegraph.structure.graph.Path) { + // Convert Object to Path + paths.add((com.baidu.hugegraph.structure.graph.Path) object); + } + if (++count >= limit) { + break; + } + } + + /* + * When the results contains not only vertices\edges\paths, + * how to deal with that? + */ + switch (queryResult.getType()) { + case PATH: + // Extract vertices from paths ; + vertices = getVertexFromPath(client, paths); + edges = getEdgeFromVertex(client, vertices); + styles = getGraphStyles(client); + break; + case VERTEX: + // Extract edges from vertex ; + edges = getEdgeFromVertex(client, vertices); + styles = getGraphStyles(client); + break; + case EDGE: + // Extract vertices from edges ; + vertices = getVertexFromEdge(client, edges); + styles = getGraphStyles(client); + break; + default: + break; + } + + queryResult.setGraphVertices(vertices); + queryResult.setGraphEdges(edges); + queryResult.setStyles(styles); + queryResult.setShowNum(count); + String message = ""; + if (count < resultSet.size()) { + message = String.format("Partial %s records are shown!", count); + } + queryResult.setMessage(message); + + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + queryResult.setDuration(duration); + + return queryResult; + } + + /** + * The method is used for get a vertex's adjacency nodes when a graph is + * shown. The user can select any vertex which is interested in as a start + * point, add it's adjacency vertices & edges to current graph by executing + * the gremlin statement of 'g.V(id).bothE()'. + * + * After successful of the gremlin need to merge the current local query + * results to the page card's result. + * + * Note: this method should be executed after @see executeGremlin + * (String, String) has been executed. + * + * @param vertexId The id of vertex as a start point. + * @return The offset graph(vertices & edges). + */ + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response adjacentVertices( + @QueryParam("vertexId") String vertexId, + @QueryParam("label") String label) { + Preconditions.checkArgument(vertexId != null); + Preconditions.checkArgument(StringUtils.isNotBlank(label), + "parameter label is blank"); + + HugeClient client = null; + try { + client = newHugeClient(); + } catch (Exception e) { + QueryResult result = new QueryResult(); + result.setMessage("Failed to connect HugeGraphServer"); + return Response.status(500).entity(result).build(); + } + + try { + Board board = boardSerializer.load(); + Preconditions.checkArgument(board != null); + + QueryResult resultNew = this.queryAdjacentVertices(board, client, + vertexId, label); + boardSerializer.save(board); + + return Response.status(200).entity(resultNew).build(); + } catch (Exception e) { + QueryResult result = new QueryResult(); + result.setMessage(e.getMessage()); + return Response.status(500).entity(result).build(); + } + } + + private QueryResult queryAdjacentVertices(Board board, HugeClient client, + String vertexId, String label) { + long startTime = System.currentTimeMillis(); + + QueryResult queryResult = board.getResult(); + /* + * This method should be executed after the method of @see + * executeGremlin(String,String). It must has a start + * point and the result must have vertices or edges. + */ + Preconditions.checkArgument(queryResult != null && + queryResult.getGraph() != null); + + SchemaManager schema = client.schema(); + VertexLabel vertexLabel = schema.getVertexLabel(label); + + Object transformedVertexId = transformId(vertexId, vertexLabel); + + // To know whether has more record, + // so add "limit(limit+1)" after code. + int limit = MAX_EDGES_PER_VERTEX + 1; + String code = gremlinOptimizer.limitOptimize( + String.format("g.V(%s).bothE()", + formatId(transformedVertexId)), limit); + LOG.info(code); + Set vertexIds = new HashSet<>(); + Set visitedEdgeIds = new HashSet<>(); + List vertices = queryResult.getGraph().getVertices(); + List edges = queryResult.getGraph().getEdges(); + vertices.forEach(v -> vertexIds.add(v.id())); + edges.forEach(e -> { + if (e.source().equals(transformedVertexId) || + e.target().equals(transformedVertexId)) { + visitedEdgeIds.add(e.id()); + } + }); + + Preconditions.checkArgument(vertexIds.contains(transformedVertexId)); + + GremlinManager gremlinManager = client.gremlin(); + ResultSet resultSet = gremlinManager.gremlin(code).execute(); + queryResult.setData(resultSet.data()); + + Iterator iterator = resultSet.iterator(); + + List edgesNew = new ArrayList<>(); + List verticesNew = new ArrayList<>(); + + String message = ""; + while (iterator.hasNext()) { + Edge e = iterator.next().getEdge(); + if (visitedEdgeIds.contains(e.id())) { + continue; + } + if (edgesNew.size() > conf.getLimitEdgeIncrement()) { + message = String.format("%s edges increase, but more edges" + + "aren't shown.", edgesNew.size()); + break; + } + if (visitedEdgeIds.size() > MAX_EDGES_PER_VERTEX) { + message = String.format("There are more than %s edges and " + + "not all are shown!", + MAX_EDGES_PER_VERTEX); + break; + } + if (edges.size() + edgesNew.size() > conf.getLimitEdgeTotal()) { + message = String.format("There are more than %s edges and " + + "not all are shown!", + conf.getLimitEdgeTotal()); + break; + } + visitedEdgeIds.add(e.id()); + edgesNew.add(e); + } + + List verticesFromEdges = getVertexFromEdge(client, edgesNew); + if (verticesFromEdges != null) { + verticesFromEdges.forEach(v -> { + if (!vertexIds.contains(v.id())) { + vertexIds.add(v.id()); + verticesNew.add(v); + } + }); + } + + // Save the current query result to card. + vertices.addAll(verticesNew); + edges.addAll(edgesNew); + + QueryResult resultNew = new QueryResult(); + queryResult.setGraphVertices(vertices); + queryResult.setGraphEdges(edges); + queryResult.setStyles(resultNew.getGraph().getStyles()); + + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + resultNew.setType(EDGE); + resultNew.setGraphVertices(verticesNew); + resultNew.setGraphEdges(edgesNew); + resultNew.setStyles(getGraphStyles(client)); + resultNew.setDuration(duration); + resultNew.setMessage(message); + + return resultNew; + } + + private Type getResultType(ResultSet resultSet, int limit) { + int i = 0; + Type type = EMPTY; + for (Iterator results = resultSet.iterator(); + results.hasNext(); ) { + + Result or = results.next(); + Object object = or.getObject(); + if (object instanceof Vertex) { + type = VERTEX; + } else if (object instanceof Edge) { + type = EDGE; + } else if (object instanceof + com.baidu.hugegraph.structure.graph.Path) { + type = PATH; + } else if (object instanceof Number || + object instanceof String || + object instanceof Boolean) { + if (type == EMPTY) { + type = SINGLE; + } + } else { + type = OTHER; + } + if (++i >= limit) { + break; + } + } + return type; + } + + private Object transformId(String vertexId, VertexLabel vertexLabel) { + Object transformedVertexId = vertexId; + switch (vertexLabel.idStrategy()) { + case AUTOMATIC: + case CUSTOMIZE_NUMBER: + try { + transformedVertexId = Integer.valueOf(vertexId); + } catch (NumberFormatException ignored) { + try { + transformedVertexId = Long.valueOf(vertexId); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "The vertexId does no match with itself " + + "idStrategy"); + } + } + break; + case PRIMARY_KEY: + case CUSTOMIZE_STRING: + break; + default: + throw new IllegalArgumentException(String.format( + "The vertexLabel isStrategy %s is not supported", + vertexLabel.idStrategy().name())); + } + return transformedVertexId; + } + + private String formatId(Object id) { + if (id instanceof String) { + String transformedId = + StringUtils.replace(id.toString(), "\\", "\\\\"); + transformedId = StringUtils.replace(transformedId, "\"", "\\\""); + transformedId = StringUtils.replace(transformedId, "'", "\\'"); + transformedId = StringUtils.replace(transformedId, "\n", "\\n"); + return String.format("'%s'", transformedId); + } + return id.toString(); + } + + private Map getGraphStyles(HugeClient client) { + Map groups = new HashMap<>(); + NodeColorOption colorOption = + new NodeColorOption(StudioApiConfig.getInstance() + .getVertexVisColor()); + List vertexLabels = client.schema().getVertexLabels(); + Collections.sort(vertexLabels, new Comparator() { + @Override + public int compare(VertexLabel o1, VertexLabel o2) { + return o1.name().compareTo(o2.name()); + } + }); + + for (VertexLabel vertexLabel : vertexLabels) { + if (vertexLabel.userdata() != null && + !vertexLabel.userdata().isEmpty()) { + groups.put(vertexLabel.name(), + new VisNode(vertexLabel.userdata(), colorOption)); + } else { + groups.put(vertexLabel.name(), new VisNode(colorOption)); + } + } + + Font vertexFont = new Font.Builder().size(conf.getVertexFontSize()) + .color(conf.getVertexFontColor()) + .build(); + Font edgeFont = new Font.Builder().size(conf.getEdgeFontSize()) + .color(conf.getEdgeFontColor()) + .build(); + return ImmutableMap.of( + "groups", groups, + "font", vertexFont, + "edgeColor", new EdgeColor.Builder(conf).build(), + "edgeFont", edgeFont + ); + } + + private List getVertexFromEdge(HugeClient client, + List edges) { + if (edges == null || edges.size() == 0) { + return null; + } + Set vertexIds = new HashSet<>(); + edges.forEach(e -> { + vertexIds.add(e.source()); + vertexIds.add(e.target()); + }); + return getVertices(client, new ArrayList<>(vertexIds)); + + } + + private List getEdgeFromVertex(HugeClient client, + List vertices) { + + if (vertices == null || vertices.size() == 0) { + return null; + } + List edges = new ArrayList<>(); + + Set vertexIds = new HashSet<>(); + vertices.forEach(v -> vertexIds.add(v.id())); + + List idList = new ArrayList<>(); + for (Vertex vertex : vertices) { + idList.add(formatId(vertex.id())); + } + + Lists.partition(idList, GREMLIN_MAX_IDS) + .forEach(group -> { + String ids = StringUtils.join(group, ","); + /* + * De-duplication by edgeId. Reserve the edges only if both + * srcVertexId and tgtVertexId is a member of vertices. + */ + String code = String.format("g.V(%s).bothE().dedup()" + + ".limit(800000)", ids); + LOG.info(code); + ResultSet resultSet = + client.gremlin().gremlin(code).execute(); + + Iterator resultIterator = resultSet.iterator(); + + Map edgesNumPerVertex = new HashMap<>(); + + while (resultIterator.hasNext()) { + Edge edge = resultIterator.next().getEdge(); + /* + * As the results is queried by 'g.V(id).bothE()', the + * source vertex of edge from results is in the set of + * vertexIds. Hence, just reserve the edge which that + * the target in the set of vertexIds. + */ + Object target = edge.target(); + Object source = edge.source(); + if (vertexIds.contains(target) && + vertexIds.contains(source)) { + Integer count = edgesNumPerVertex.get(source); + if (count == null) { + count = 0; + } + edgesNumPerVertex.put(source, count++); + if (count > MAX_EDGES_PER_VERTEX) { + break; + } + + count = edgesNumPerVertex.get(target); + if (count == null) { + count = 0; + } + edgesNumPerVertex.put(target, count++); + if (count > MAX_EDGES_PER_VERTEX) { + break; + } + + edges.add(edge); + if (edges.size() >= conf.getLimitEdgeTotal()) { + break; + } + } + } + }); + return edges; + } + + private List getVertices(HugeClient client, + List vertexIds) { + if (vertexIds == null || vertexIds.size() == 0) { + return null; + } + List vertices = new ArrayList<>(); + + List idList = new ArrayList<>(); + for (Object vertexId : vertexIds) { + idList.add(formatId(vertexId)); + } + Lists.partition(idList, GREMLIN_MAX_IDS) + .forEach(group -> { + String ids = StringUtils.join(group, ","); + String gremlin = String.format("g.V(%s)", ids); + LOG.info(gremlin); + ResultSet resultSet = + client.gremlin().gremlin(gremlin).execute(); + Iterator results = resultSet.iterator(); + List finalVertices = vertices; + results.forEachRemaining( + vertex -> finalVertices.add((Vertex) vertex.getObject())); + }); + return vertices; + } + + private List getVertexFromPath(HugeClient client, + List paths) { + if (paths == null) { + return null; + } + + Set vertexIds = new HashSet<>(); + // The path node can be a Vertex, or an Edge. + paths.forEach(path -> path.objects().forEach(obj -> { + if (obj instanceof Vertex) { + Vertex vertex = (Vertex) obj; + vertexIds.add(vertex.id()); + } else if (obj instanceof Edge) { + Edge edge = (Edge) obj; + vertexIds.add(edge.source()); + vertexIds.add(edge.target()); + } + })); + return getVertices(client, new ArrayList<>(vertexIds)); + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/config/NodeColorOption.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/config/NodeColorOption.java new file mode 100644 index 00000000..4d305347 --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/config/NodeColorOption.java @@ -0,0 +1,43 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.config; + +import java.util.List; +import java.util.Map; + +public class NodeColorOption { + + private final List> colors; + private int nextOption; + + public NodeColorOption(List> colors) { + if (colors.size() < 1) { + throw new RuntimeException("Invalid color option."); + } + this.colors = colors; + this.nextOption = 0; + } + + public Map getColor() { + Map color = colors.get(this.nextOption); + this.nextOption = ++this.nextOption % colors.size(); + return color; + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/config/StudioApiConfig.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/config/StudioApiConfig.java new file mode 100644 index 00000000..989c62ef --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/config/StudioApiConfig.java @@ -0,0 +1,192 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.config; + +import java.io.IOException; +import java.net.URL; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; + +import com.baidu.hugegraph.config.HugeConfig; +import com.baidu.hugegraph.config.OptionSpace; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Preconditions; + +public class StudioApiConfig { + + private static final String DEFAULT_CONFIGURATION_FILE = + "hugegraph-studio.properties"; + private final ObjectMapper mapper = new ObjectMapper(); + + private HugeConfig config; + + static { + OptionSpace.register("studio-api", StudioApiOptions.instance()); + } + + private StudioApiConfig() { + String studioHome = System.getProperty("studio.home"); + if (studioHome != null) { + config = new HugeConfig(String.format("%s/conf/%s", + studioHome, DEFAULT_CONFIGURATION_FILE)); + } else { + URL confUrl = this.getClass() + .getClassLoader() + .getResource(DEFAULT_CONFIGURATION_FILE); + Preconditions.checkNotNull(confUrl); + config = new HugeConfig(confUrl.getFile()); + } + } + + private static class StudioConfigurationHolder { + private static final StudioApiConfig conf = + new StudioApiConfig(); + } + + public static StudioApiConfig getInstance() { + return StudioConfigurationHolder.conf; + } + + public String getGraphServerUrl() { + String host = this.getGraphServerHost(); + Integer port = this.getGraphServerPort(); + return String.format("http://%s:%d", host, port); + } + + public String getGraphServerHost() { + return this.config.get(StudioApiOptions.GRAPH_SERVER_HOST); + } + + public Integer getGraphServerPort() { + return this.config.get(StudioApiOptions.GRAPH_SERVER_PORT); + } + + public String getGraphName() { + return this.config.get(StudioApiOptions.GRAPH_NAME); + } + + public String getBoardFilePath() { + return String.format("%s/%s", getBaseDirectory(), "board"); + } + + public String getBaseDirectory() { + String userDataDir = this.config.get(StudioApiOptions.DATA_BASE_DIR); + if (StringUtils.isBlank(userDataDir)) { + userDataDir = "~/.hugegraph-studio"; + } + return replaceHomeDirReferences(userDataDir); + } + + public int getLimitData() { + return this.config.get(StudioApiOptions.SHOW_LIMIT_DATA); + } + + public int getLimitEdgeTotal() { + return this.config.get(StudioApiOptions.SHOW_LIMIT_EDGE_TOTAL); + } + + public int getLimitEdgeIncrement() { + return this.config.get(StudioApiOptions.SHOW_LIMIT_EDGE_INCREMENT); + } + + public List> getVertexVisColor() { + String colors = this.config.get(StudioApiOptions.VERTEX_VIS_COLOR); + try { + return mapper.readValue(colors, List.class); + } catch (IOException e) { + throw new RuntimeException("Invalid vertex.vis.color", e); + } + } + + public Set getAppendLimitSuffixes() { + List gremlins = + this.config.get(StudioApiOptions.GREMLINS_APPEND_LIMIT_SUFFIX); + + if (gremlins == null || gremlins.size() == 0) { + return new HashSet<>(); + } + Set gremlinSet = new HashSet<>(gremlins); + for (String g : gremlins) { + gremlinSet.add(g); + } + return gremlinSet; + } + + private String replaceHomeDirReferences(String confDir) { + assert !confDir.isEmpty(); + + if (System.getProperty("user.home") == null) { + return confDir; + } + return confDir.replaceFirst("^~", System.getProperty("user.home")); + } + + public String getVertexShape() { + return this.config.get(StudioApiOptions.VERTEX_VIS_SHAPE); + } + + public String getVertexColor() { + return this.config.get(StudioApiOptions.VERTEX_VIS_COLOR); + } + + public Integer getVertexSize() { + return this.config.get(StudioApiOptions.VERTEX_VIS_SIZE); + } + + public Integer getVertexMaxSize() { + return this.config.get(StudioApiOptions.VERTEX_SCALING_MAX_SIZE); + } + + public Integer getVertexMinSize() { + return this.config.get(StudioApiOptions.VERTEX_SCALING_MIN_SIZE); + } + + public String getVertexFontColor() { + return this.config.get(StudioApiOptions.VERTEX_VIS_FONT_COLOR); + } + + public Integer getVertexFontSize() { + return this.config.get(StudioApiOptions.VERTEX_VIS_FONT_SIZE); + } + + public String getEdgeDefaultColor() { + return this.config.get(StudioApiOptions.EDGE_VIS_COLOR_DEFAULT); + } + + public String getEdgeHoverColor() { + return this.config.get(StudioApiOptions.EDGE_VIS_COLOR_HOVER); + } + + public String getEdgeHighlightColor() { + return this.config.get(StudioApiOptions.EDGE_VIS_COLOR_HIGHLIGHT); + } + + public String getEdgeFontColor() { + return this.config.get(StudioApiOptions.EDGE_VIS_FONT_COLOR); + } + + public Integer getEdgeFontSize() { + return this.config.get(StudioApiOptions.EDGE_VIS_FONT_SIZE); + } +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/config/StudioApiOptions.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/config/StudioApiOptions.java new file mode 100644 index 00000000..aaac29f8 --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/config/StudioApiOptions.java @@ -0,0 +1,296 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.config; + +import static com.baidu.hugegraph.config.OptionChecker.disallowEmpty; +import static com.baidu.hugegraph.config.OptionChecker.positiveInt; +import static com.baidu.hugegraph.config.OptionChecker.rangeInt; + +import com.baidu.hugegraph.config.ConfigListOption; +import com.baidu.hugegraph.config.ConfigOption; +import com.baidu.hugegraph.config.OptionHolder; + +/** + * The type Studio api options. + */ +public class StudioApiOptions extends OptionHolder { + + private StudioApiOptions() { + super(); + } + + private static volatile StudioApiOptions instance; + + /** + * Instance studio api options. + * + * @return the studio api options + */ + public static StudioApiOptions instance() { + if (instance == null) { + synchronized (StudioApiOptions.class) { + if (instance == null) { + instance = new StudioApiOptions(); + instance.registerOptions(); + } + } + } + return instance; + } + + public static final ConfigOption GRAPH_SERVER_HOST = + new ConfigOption<>( + "graph.server.host", + "The host of HugeGraphServer", + disallowEmpty(), + "localhost" + ); + + public static final ConfigOption GRAPH_SERVER_PORT = + new ConfigOption<>( + "graph.server.port", + "The port of HugeGraphServer", + positiveInt(), + 8080 + ); + + public static final ConfigOption GRAPH_NAME = + new ConfigOption<>( + "graph.name", + "The connected graph name", + disallowEmpty(), + "hugegraph" + ); + + /** + * The constant DATA_BASE_DIR. + */ + public static final ConfigOption DATA_BASE_DIR = + new ConfigOption<>( + "data.base_directory", + "The base directory of HugeGraphStudio's user data.", + disallowEmpty(), + "~/.hugegraph-studio" + ); + + /** + * The constant SHOW_LIMIT_DATA. + */ + public static final ConfigOption SHOW_LIMIT_DATA = + new ConfigOption<>( + "show.limit.data", + "MAX_SIZE for the data render in web.", + rangeInt(1, 10000), + 100 + ); + + /** + * The constant SHOW_LIMIT_EDGE_TOTAL. + */ + public static final ConfigOption SHOW_LIMIT_EDGE_TOTAL = + new ConfigOption<>( + "show.limit.edge.total", + "MAX_SIZE for the edge render in web.", + rangeInt(1, 10000), + 100 + ); + + /** + * The constant SHOW_LIMIT_EDGE_INCREMENT. + */ + public static final ConfigOption SHOW_LIMIT_EDGE_INCREMENT = + new ConfigOption<>( + "show.limit.edge.increment", + "MAX_SIZE for the edge increment render in web.", + rangeInt(1, 200), + 50 + ); + + /** + * The constant GREMLIN_EXCLUDE_LIMIT. + */ + public static final ConfigListOption GREMLINS_APPEND_LIMIT_SUFFIX = + new ConfigListOption<>( + "gremlin.limit_suffix", + false, + "The suffixes of gremlin statement which should be " + + "appended limit()", + disallowEmpty(), + String.class, + ".V()", ".E()" + ); + + /** + * The constant VERTEX_VIS_FONT_COLOR. + */ + public static final ConfigOption VERTEX_VIS_FONT_COLOR = + new ConfigOption<>( + "vertex.vis.font.color", + false, + "The vertex font color", + disallowEmpty(), + String.class, + "#343434" + ); + + /** + * The constant VERTEX_VIS_FONT_COLOR. + */ + public static final ConfigOption VERTEX_VIS_FONT_SIZE = + new ConfigOption<>( + "vertex.vis.font.size", + false, + "The vertex font size", + positiveInt(), + Integer.class, + 12 + ); + + /** + * The constant VERTEX_VIS_COLOR. + */ + public static final ConfigOption VERTEX_VIS_COLOR = + new ConfigOption<>( + "vertex.vis.color", + false, + "The vertex background color", + disallowEmpty(), + String.class, + "[{\"common\":\"#00ccff\",\"hover\":\"#ec3112\"," + + "\"highlight\":\"#fb6a02\"}]" + ); + + /** + * The constant VERTEX_VIS_SHAPE. + */ + public static final ConfigOption VERTEX_VIS_SHAPE = + new ConfigOption<>( + "vertex.vis.shape", + false, + "The vertex shape", + disallowEmpty(), + String.class, + "dot" + ); + + /** + * The constant VERTEX_VIS_SIZE. + */ + public static final ConfigOption VERTEX_VIS_SIZE = + new ConfigOption<>( + "vertex.vis.size", + false, + "The vertex size", + positiveInt(), + Integer.class, + 25 + ); + + /** + * The constant VERTEX_SCALING_MAX_SIZE. + */ + public static final ConfigOption VERTEX_SCALING_MAX_SIZE = + new ConfigOption<>( + "vertex.vis.scaling.max", + false, + "The vertex max size", + positiveInt(), + Integer.class, + 30 + ); + + /** + * The constant VERTEX_SCALING_MIN_SIZE. + */ + public static final ConfigOption VERTEX_SCALING_MIN_SIZE = + new ConfigOption<>( + "vertex.vis.scaling.min", + false, + "The vertex min size", + positiveInt(), + Integer.class, + 25 + ); + + /** + * The constant EDGE_VIS_COLOR_DEFAULT. + */ + public static final ConfigOption EDGE_VIS_COLOR_DEFAULT = + new ConfigOption<>( + "edge.vis.color.default", + false, + "The edge default color", + disallowEmpty(), + String.class, + "#808080" + ); + + /** + * The constant EDGE_VIS_COLOR_HOVER. + */ + public static final ConfigOption EDGE_VIS_COLOR_HOVER = + new ConfigOption<>( + "edge.vis.color.hover", + false, + "The edge hover color", + disallowEmpty(), + String.class, + "#808080" + ); + + /** + * The constant EDGE_VIS_COLOR_HIGHT. + */ + public static final ConfigOption EDGE_VIS_COLOR_HIGHLIGHT = + new ConfigOption<>( + "edge.vis.color.highlight", + false, + "The edge highlight color", + disallowEmpty(), + String.class, + "#808080" + ); + + /** + * The constant EDGE_VIS_FONT_COLOR. + */ + public static final ConfigOption EDGE_VIS_FONT_COLOR = + new ConfigOption<>( + "edge.vis.font.color", + false, + "The edge font color", + disallowEmpty(), + String.class, + "#33333" + ); + + /** + * The constant VERTEX_SCALING_MIN_SIZE. + */ + public static final ConfigOption EDGE_VIS_FONT_SIZE = + new ConfigOption<>( + "edge.vis.font.size", + false, + "The edge font size", + positiveInt(), + Integer.class, + 12 + ); +} diff --git a/studio-api/src/main/java/com/baidu/hugegraph/studio/gremlin/GremlinOptimizer.java b/studio-api/src/main/java/com/baidu/hugegraph/studio/gremlin/GremlinOptimizer.java new file mode 100644 index 00000000..ebf20a66 --- /dev/null +++ b/studio-api/src/main/java/com/baidu/hugegraph/studio/gremlin/GremlinOptimizer.java @@ -0,0 +1,141 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.gremlin; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.stereotype.Repository; + +import com.baidu.hugegraph.studio.config.StudioApiConfig; + +/** + * Add some rules for gremlin code. + */ +@Repository("gremlinOptimizer") +public class GremlinOptimizer { + + private StudioApiConfig configuration; + private Map suffixPatterns; + + public GremlinOptimizer() { + configuration = StudioApiConfig.getInstance(); + suffixPatterns = transformSuffixToRegExpPattern( + configuration.getAppendLimitSuffixes()); + } + + private Map transformSuffixToRegExpPattern( + Set suffixes) { + Map suffixPatterns = new HashMap<>(); + for (String suffix : suffixes) { + suffix = suffix.replaceAll("\\.", "\\\\."); + if (suffix.indexOf("(STR)") > -1) { + String regExpSuffix = transformSTRWithSingleQuotes(suffix); + suffixPatterns.put(regExpSuffix, Pattern.compile(regExpSuffix)); + regExpSuffix = transformSTRWithDoubleQuotes(suffix); + suffixPatterns.put(regExpSuffix, Pattern.compile(regExpSuffix)); + continue; + } + + if (suffix.indexOf("(NUM)") > -1) { + String regExpSuffix = transformNum(suffix); + suffixPatterns.put(regExpSuffix, Pattern.compile(regExpSuffix)); + continue; + } + + if (suffix.indexOf("()") > -1) { + String regExpSuffix = suffix.replaceAll("\\(", "\\\\(") + .replaceAll("\\)", "\\\\)"); + regExpSuffix = String.format("(%s)$", regExpSuffix); + suffixPatterns.put(regExpSuffix, Pattern.compile(regExpSuffix)); + continue; + } + } + + return suffixPatterns; + } + + /** + * Create regular expression with fun('...').s + * @param suffix + * @return regular expression suffix + */ + private String transformSTRWithSingleQuotes(String suffix) { + String regExpSuffix = suffix.replaceAll("\\(", "\\\\(") + .replaceAll("\\)", "\\\\)") + .replaceAll("STR", "'[\\\\s\\\\S]+'"); + regExpSuffix = String.format("(%s)$", regExpSuffix); + return regExpSuffix; + } + + /** + * Create regular expression with fun("..."). + * + * @param suffix + * @return regular expression suffix + */ + private String transformSTRWithDoubleQuotes(String suffix) { + String regExpSuffix = suffix.replaceAll("\\(", "\\\\(") + .replaceAll("\\)", "\\\\)") + .replaceAll("STR", "\"[\\\\s\\\\S]+\""); + regExpSuffix = String.format("(%s)$", regExpSuffix); + return regExpSuffix; + } + + /** + * Create regular expression with fun(num) , like fun(123). + * + * @param suffix + * @return regular expression suffix + */ + private String transformNum(String suffix) { + String regExpSuffix = suffix.replaceAll("\\(", "\\\\(") + .replaceAll("\\)", "\\\\)") + .replaceAll("NUM", "[\\\\d]+"); + regExpSuffix = String.format("(%s)$", regExpSuffix); + return regExpSuffix; + } + + /** + * Add 'limit' to the end of gremlin statement to avoid OOM, + * when the statement end up with the desired suffix string. + * + * @param code + * @param limit the value need be greater than 0. + * @return + */ + public String limitOptimize(String code, int limit) { + for (Pattern p : suffixPatterns.values()) { + Matcher m = p.matcher(code); + if (m.find()) { + return code + ".limit(" + limit + ")"; + } + } + + return code; + } + + public String limitOptimize(String code) { + return limitOptimize(code, configuration.getLimitData()); + } +} diff --git a/studio-api/src/main/resources/applicationContext.xml b/studio-api/src/main/resources/applicationContext.xml new file mode 100644 index 00000000..2234f6fc --- /dev/null +++ b/studio-api/src/main/resources/applicationContext.xml @@ -0,0 +1,10 @@ + + + + diff --git a/studio-api/src/main/resources/log4j2.xml b/studio-api/src/main/resources/log4j2.xml new file mode 100755 index 00000000..ec9874e1 --- /dev/null +++ b/studio-api/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + ./logs + studio.log + 250 MB + 10 + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} ID: %X{reqId} TS: %X{reqTs}- %message%n + + + + + + + + + + + + + + diff --git a/studio-api/src/main/webapp/WEB-INF/web.xml b/studio-api/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..31a5d8e3 --- /dev/null +++ b/studio-api/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,40 @@ + + + + + contextConfigLocation + classpath:applicationContext.xml + + + + + org.springframework.web.context.ContextLoaderListener + + + + + jersey-servlet + org.glassfish.jersey.servlet.ServletContainer + + jersey.config.server.provider.packages + com.baidu.hugegraph.studio + + + jersey.config.beanValidation.enableOutputValidationErrorEntity.server + true + + + com.sun.jersey.api.json.POJOMappingFeature + true + + 1 + + + + jersey-servlet + /v1/* + + + diff --git a/studio-dist/pom.xml b/studio-dist/pom.xml new file mode 100644 index 00000000..6238581f --- /dev/null +++ b/studio-dist/pom.xml @@ -0,0 +1,158 @@ + + + + hugegraph-studio + com.baidu.hugegraph + 0.7.0 + + 4.0.0 + + studio-dist + + + ${project.basedir}/.. + ${project.basedir}/src/assembly + ${assembly.dir}/descriptor + ${assembly.dir}/static + ${top.level.basedir}/studio-ui + ${top.level.basedir}/studio-api + bash + + + + + com.baidu.hugegraph + hugegraph-client + + + + org.apache.tomcat.embed + tomcat-embed-core + + + org.apache.tomcat.embed + tomcat-embed-logging-juli + + + org.apache.tomcat.embed + tomcat-embed-jasper + + + org.apache.tomcat + tomcat-jasper + + + org.apache.tomcat + tomcat-jasper-el + + + org.apache.tomcat + tomcat-jsp-api + + + + + + + maven-assembly-plugin + 2.4 + + + install-studio + package + + single + + + false + ${top.level.basedir} + + ${final.name} + false + + ${assembly.descriptor.dir}/assembly.xml + + + + + + + maven-clean-plugin + 3.0.0 + + + + ${top.level.basedir} + + *.tar.gz + ${final.name}/** + + false + + + ${final.name} + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + package + + run + + + + + cd ${studio-ui.dir} + npm install && npm run build || exit 1 + rm -f ${top.level.basedir}/dist.sh + + rm -rf \ + ${top.level.basedir}/${final.name}/ui/* + + cp -r \ + ${top.level.basedir}/studio-ui/assets/images \ + ${top.level.basedir}/studio-ui/dist + + cp -r \ + ${top.level.basedir}/studio-ui/assets/vendors \ + ${top.level.basedir}/studio-ui/dist + + cp -r \ + ${top.level.basedir}/studio-ui/dist \ + ${top.level.basedir}/${final.name}/ui + + echo + echo "Build studio ok." + echo + + cd .. + tar -zcvf \ + ${top.level.basedir}/${final.name}.tar.gz \ + ${final.name} || exit 1 + rm -f ${top.level.basedir}/dist.sh + echo + echo "hugegraph-studio dist tar.gz available at: + ${top.level.basedir}/${final.name}.tar.gz" + echo + + + + + + + + + + + + + \ No newline at end of file diff --git a/studio-dist/src/assembly/descriptor/assembly.xml b/studio-dist/src/assembly/descriptor/assembly.xml new file mode 100644 index 00000000..e9b03d9f --- /dev/null +++ b/studio-dist/src/assembly/descriptor/assembly.xml @@ -0,0 +1,61 @@ + + distribution + / + + + dir + + + + + ${assembly.static.dir}/bin + bin + + * + + 755 + + + ${assembly.static.dir} + / + false + + + ${project.build.directory} + lib + + *.jar + + + + ${top.level.basedir}/studio-api/target + /war + + *.war + + + + ${top.level.basedir} + / + + README* + LICENSE* + NOTICE* + + + + + + + + /lib + false + runtime + false + + *:*:jar:* + + + + + \ No newline at end of file diff --git a/studio-dist/src/assembly/static/bin/hugegraph-studio.sh b/studio-dist/src/assembly/static/bin/hugegraph-studio.sh new file mode 100755 index 00000000..b10bddf3 --- /dev/null +++ b/studio-dist/src/assembly/static/bin/hugegraph-studio.sh @@ -0,0 +1,149 @@ +#!/bin/bash + +# ----------------------------------------------------------------------------- +# Start Script for the HugeGraphStudio Server +# ----------------------------------------------------------------------------- + +# Environment Variable Prerequisites +# +# Do not set the variables in this script. Instead put them into a script +# studio-env.sh in STUDIO_HOME/bin to keep your customizations separate. +# +# STUDIO_HOME Base directory for HugeGraphStudio. +# +# STUDIO_PID (Optional) Path of the file which should contains the pid +# of the HugeGraphStudio startup java process, when start (fork) is +# used +# JAVA_HOME Must point at your Java Development Kit installation. +# Required to run the with the "debug" argument. +# +# JRE_HOME Must point at your Java Runtime installation. +# Defaults to JAVA_HOME if empty. If JRE_HOME and JAVA_HOME +# are both set, JRE_HOME is used. +# +# JAVA_OPTS (Optional) Java runtime options used when any command +# is executed. +# Include here and not in CATALINA_OPTS all options, that +# should be used by Tomcat and also by the stop process, +# the version command etc. +# Most options should go into CATALINA_OPTS. +# +# +# JPDA_TRANSPORT (Optional) JPDA transport used when the "jpda start" +# command is executed. The default is "dt_socket". +# +# JPDA_ADDRESS (Optional) Java runtime options used when the "jpda start" +# command is executed. The default is localhost:8000. +# +# JPDA_SUSPEND (Optional) Java runtime options used when the "jpda start" +# command is executed. Specifies whether JVM should suspend +# execution immediately after startup. Default is "n". +# +# JPDA_OPTS (Optional) Java runtime options used when the "jpda start" +# command is executed. If used, JPDA_TRANSPORT, JPDA_ADDRESS, +# and JPDA_SUSPEND are ignored. Thus, all required jpda +# options MUST be specified. The default is: +# +# -agentlib:jdwp=transport=$JPDA_TRANSPORT, +# address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND +# +# JSSE_OPTS (Optional) Java runtime options used to control the TLS +# implementation when JSSE is used. Default is: +# "-Djdk.tls.ephemeralDHKeySize=2048" +# +# ----------------------------------------------------------------------------- + +abs_path() { + SOURCE="${BASH_SOURCE[0]}" + while [ -h "$SOURCE" ]; do + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" + done + echo "$( cd -P "$( dirname "$SOURCE" )" && pwd )" +} + +BIN=`abs_path` +STUDIO_HOME="$(cd $BIN/../ && pwd)" +CONF="${STUDIO_HOME}/conf" +LIB="${STUDIO_HOME}/lib" +. ${BIN}/util.sh + +# Ensure that any user defined CLASSPATH variables are not used on startup, +# but allow them to be specified in studio-env.sh, in rare case when it is needed. +CLASSPATH= +if [ -f "${BIN}/studio-env.sh" ]; then + . "${BIN}/studio-env.sh" +fi + +# Use JAVA_HOME if set, otherwise look for java in PATH +if [ -n "$JAVA_HOME" ]; then + # Why we can't have nice things: Solaris combines x86 and x86_64 + # installations in the same tree, using an unconventional path for the + # 64bit JVM. Since we prefer 64bit, search the alternate path first, + # (see https://issues.apache.org/jira/browse/CASSANDRA-4638). + for java in "$JAVA_HOME"/bin/amd64/java "$JAVA_HOME"/bin/java; do + if [ -x "$java" ]; then + JAVA="$java" + break + fi + done +else + JAVA=java +fi + +if [ -z $JAVA ] ; then + echo Unable to find java executable. Check JAVA_HOME and PATH environment variables. > /dev/stderr + exit 1; +fi + +STUDIO_PORT=`read_property "$CONF/hugegraph-studio.properties" "studio.server.port"` +check_port "$STUDIO_PORT" + +# Xmx needs to be set so that it is big enough to cache all the vertexes in the run +export JVM_OPTS="$JVM_OPTS -Xmx10g" + +# add additional jars to the classpath if the lib directory exists +if [ -d "${LIB}" ]; then + STUDIO_CLASSPATH=${STUDIO_CLASSPATH}:${LIB}'/*' +fi +# add conf to the classpath if the conf directory exists +# avoid to set /conf/* +if [ -d "${CONF}" ]; then + STUDIO_CLASSPATH=${STUDIO_CLASSPATH}:${CONF} +fi + +export STUDIO_CLASSPATH=${STUDIO_CLASSPATH}:${CLASSPATH} + +export JVM_OPTS="$JVM_OPTS -cp $STUDIO_CLASSPATH" +export JVM_OPTS="$JVM_OPTS -Xmx10g" + +export MAIN_CLASS="com.baidu.hugegraph.studio.HugeGraphStudio" + +if [ "$1" = "-debug" ] ; then + if [ -z "$JPDA_TRANSPORT" ]; then + JPDA_TRANSPORT="dt_socket" + fi + if [ -z "$JPDA_ADDRESS" ]; then + JPDA_ADDRESS="8414" + fi + if [ -z "$JPDA_SUSPEND" ]; then + JPDA_SUSPEND="n" + fi + if [ -z "$JPDA_OPTS" ]; then + JPDA_OPTS="-agentlib:jdwp=transport=$JPDA_TRANSPORT,address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND" + fi + JVM_OPTS="$JVM_OPTS $JPDA_OPTS" + shift; +fi + +# Uncomment to enable debugging +#JVM_OPTS="$JVM_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8414" + +if [ "x$STUDIO_APP_NAME" = "x" ]; then + STUDIO_APP_NAME="HugeGraphStudio" +fi + +# echo "$JAVA" $JVM_OPTS -Dapp.name="$STUDIO_APP_NAME" $MAIN_CLASS "$@" + +exec "$JAVA" $JVM_OPTS -Dstudio.home="$STUDIO_HOME" -Dapp.name="$STUDIO_APP_NAME" $MAIN_CLASS "$@" diff --git a/studio-dist/src/assembly/static/bin/util.sh b/studio-dist/src/assembly/static/bin/util.sh new file mode 100644 index 00000000..46194bda --- /dev/null +++ b/studio-dist/src/assembly/static/bin/util.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +function read_property() { + # file path + file_name=$1 + # replace "." to "\." + property_name=`echo $2 | sed 's/\./\\\./g'` + cat $file_name | sed -n -e "s/^[ ]*//g;/^#/d;s/^$property_name=//p" | tail -1 +} + +# check the port of rest server is occupied +function check_port() { + local port=$1 + lsof -i :$port >/dev/null + + if [ $? -eq 0 ]; then + echo "The port "$port" has already used" + exit 1 + fi +} diff --git a/studio-dist/src/assembly/static/conf/hugegraph-studio.properties b/studio-dist/src/assembly/static/conf/hugegraph-studio.properties new file mode 100644 index 00000000..7c42f0cc --- /dev/null +++ b/studio-dist/src/assembly/static/conf/hugegraph-studio.properties @@ -0,0 +1,50 @@ +studio.server.port=8088 +studio.server.host=localhost + +graph.server.host=localhost +graph.server.port=8080 +graph.name=hugegraph + +# the directory name released by react +studio.server.ui=ui +# the file location of studio-api.war +studio.server.api.war=war/studio-api.war +# default folder in your home directory, set to a non-empty value to override +data.base_directory=~/.hugegraph-studio + +show.limit.data=250 +show.limit.edge.total=1000 +show.limit.edge.increment=20 + +# separator ',' +gremlin.limit_suffix=[.V(),.E(),.hasLabel(STR),.hasLabel(NUM),.path()] + +# ui graph style +vertex.vis.font.color=#343434 +vertex.vis.font.size=12 +vertex.vis.size=25 +vertex.vis.scaling.min=25 +vertex.vis.scaling.max=30 +vertex.vis.shape=dot +vertex.vis.color=[\ + {"default":"#ED5736","hover":"#312520","highlight":"#ED5736"},\ + {"default":"#48C0A3","hover":"#312520","highlight":"#48C0A3"},\ + {"default":"#F47983","hover":"#312520","highlight":"#F47983"},\ + {"default":"#4C8DAE","hover":"#312520","highlight":"#4C8DAE"},\ + {"default":"#FF8C31","hover":"#312520","highlight":"#FF8C31"},\ + {"default":"#3B2E7E","hover":"#312520","highlight":"#3B2E7E"},\ + {"default":"#60281E","hover":"#312520","highlight":"#60281E"},\ + {"default":"#B36D61","hover":"#312520","highlight":"#B36D61"},\ + {"default":"#C89B40","hover":"#312520","highlight":"#C89B40"},\ + {"default":"#8D4BBB","hover":"#312520","highlight":"#8D4BBB"},\ + {"default":"#6E511E","hover":"#312520","highlight":"#6E511E"},\ + {"default":"#789262","hover":"#312520","highlight":"#789262"},\ + {"default":"#177CB0","hover":"#312520","highlight":"#177CB0"},\ + {"default":"#8C4356","hover":"#312520","highlight":"#8C4356"}\ +] + +edge.vis.color.default=#A0A0A0 +edge.vis.color.hover=#808080 +edge.vis.color.highlight=#606060 +edge.vis.font.color=#77777 +edge.vis.font.size=11 diff --git a/studio-dist/src/assembly/static/conf/log4j2.xml b/studio-dist/src/assembly/static/conf/log4j2.xml new file mode 100755 index 00000000..a4efd46f --- /dev/null +++ b/studio-dist/src/assembly/static/conf/log4j2.xml @@ -0,0 +1,33 @@ + + + + ./logs + studio.log + 250 MB + 10 + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} ID: + %X{reqId} TS: %X{reqTs}- %message%n + + + + + + + + + + + + + + + diff --git a/studio-dist/src/main/java/com/baidu/hugegraph/studio/HugeGraphStudio.java b/studio-dist/src/main/java/com/baidu/hugegraph/studio/HugeGraphStudio.java new file mode 100644 index 00000000..027348d2 --- /dev/null +++ b/studio-dist/src/main/java/com/baidu/hugegraph/studio/HugeGraphStudio.java @@ -0,0 +1,190 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; + +import javax.servlet.ServletException; + +import org.apache.catalina.Host; +import org.apache.catalina.LifecycleState; +import org.apache.catalina.Server; +import org.apache.catalina.core.StandardContext; +import org.apache.catalina.startup.Tomcat; +import org.apache.coyote.AbstractProtocol; +import org.apache.coyote.ProtocolHandler; +import org.apache.tomcat.util.descriptor.web.ErrorPage; +import org.apache.tomcat.util.scan.StandardJarScanner; +import org.slf4j.Logger; + +import com.baidu.hugegraph.studio.config.StudioServerConfig; +import com.baidu.hugegraph.util.Log; + +/** + * The Bootstrap of HugeGraphStudio. + */ +public class HugeGraphStudio { + + private static final Logger LOG = Log.logger(HugeGraphStudio.class); + + private static final String DEFAULT_CONFIG_FILE = + "hugegraph-studio.properties"; + + // The embed tomcat server + private static Server server; + + /** + * The entry point of application. + * + * @throws Exception the exception + */ + public static void main(String[] args) throws Exception { + StudioServerConfig config = new StudioServerConfig(DEFAULT_CONFIG_FILE); + run(config); + server.await(); + } + + /** + * Run tomcat with configuration + * + * @param config the studio configuration + * @throws Exception the exception + */ + public static void run(StudioServerConfig config) throws Exception { + + String address = config.getHttpBindAddress(); + int port = config.getHttpPort(); + validateHttpPort(address, port); + + String baseDir = config.getServerBasePath(); + String uiDir = String.format("%s/%s", baseDir, + config.getServerUIDirectory()); + String apiWarFile = String.format("%s/%s", baseDir, + config.getServerWarDirectory()); + validatePathExists(new File(uiDir)); + validateFileExists(new File(apiWarFile)); + + Tomcat tomcat = new Tomcat(); + tomcat.setPort(config.getHttpPort()); + + ProtocolHandler ph = tomcat.getConnector().getProtocolHandler(); + if (ph instanceof AbstractProtocol) { + ((AbstractProtocol) ph).setAddress(InetAddress.getByName(address)); + } + tomcat.setHostname(address); + + StandardContext ui = configureUi(tomcat, uiDir); + StandardContext api = configureWarFile(tomcat, apiWarFile, "/api"); + + tomcat.start(); + + server = tomcat.getServer(); + while (!server.getState().equals(LifecycleState.STARTED)) { + Thread.sleep(100L); + } + + if (!ui.getState().equals(LifecycleState.STARTED)) { + LOG.error("Studio-ui failed to start. " + + "Please check logs for details"); + System.exit(1); + } + if (!api.getState().equals(LifecycleState.STARTED)) { + LOG.error("Studio-api failed to start. " + + "Please check logs for details"); + System.exit(1); + } + + String upMessage = String.format("HugeGraphStudio is now running on: " + + "http://%s:%d\n", address, port); + LOG.info(upMessage); + } + + + private static StandardContext configureUi(Tomcat tomcat, String uiLocation) + throws ServletException { + + ErrorPage errorPage = new ErrorPage(); + errorPage.setErrorCode(404); + errorPage.setLocation("/index.html"); + String loc = new File(uiLocation).getAbsolutePath(); + + StandardContext context = (StandardContext) tomcat.addWebapp("", loc); + context.addWelcomeFile("/index.html"); + context.addErrorPage(errorPage); + + return context; + } + + private static StandardContext configureWarFile(Tomcat tomcat, + final String warFile, + final String appBase) + throws ServletException, IOException { + + if (warFile != null && warFile.length() > 0) { + StandardContext context = (StandardContext) tomcat.addWebapp(appBase, + new File(warFile).getAbsolutePath()); + Host host = (Host) context.getParent(); + File appBaseDirectory = host.getAppBaseFile(); + if (!appBaseDirectory.exists()) { + appBaseDirectory.mkdirs(); + } + context.setUnpackWAR(true); + if (context.getJarScanner() instanceof StandardJarScanner) { + ((StandardJarScanner) context.getJarScanner()) + .setScanAllDirectories(true); + } + return context; + } + + return null; + } + + /** + * To validate if the given http port is available or not. Exit if the port + * is being used. + */ + private static void validateHttpPort(String addr, int httpPort) { + try (ServerSocket tmp = new ServerSocket(httpPort, 1, + InetAddress.getByName(addr))) { + } catch (IOException e) { + LOG.error("Can't start Studio on port {}: {}", httpPort, e); + System.exit(1); + } + } + + private static void validatePathExists(File file) { + if (!file.exists() || !file.isDirectory()) { + LOG.error("Can't start Studio, directory {} doesn't exist", + file.getPath()); + System.exit(1); + } + } + + private static void validateFileExists(File file) { + if (!file.exists() || !file.isFile()) { + LOG.error("Can't start Studio, file {} doesn't exist", + file.getPath()); + System.exit(1); + } + } +} diff --git a/studio-dist/src/main/java/com/baidu/hugegraph/studio/config/StudioServerConfig.java b/studio-dist/src/main/java/com/baidu/hugegraph/studio/config/StudioServerConfig.java new file mode 100644 index 00000000..68b56bee --- /dev/null +++ b/studio-dist/src/main/java/com/baidu/hugegraph/studio/config/StudioServerConfig.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.config; + +import org.slf4j.Logger; + +import com.baidu.hugegraph.config.HugeConfig; +import com.baidu.hugegraph.config.OptionSpace; +import com.baidu.hugegraph.util.Log; + +/** + * The type Studio configuration. + */ +public class StudioServerConfig { + + private static final Logger LOG = Log.logger(StudioServerConfig.class); + + static { + OptionSpace.register("studio", StudioServerOptions.instance()); + } + + private HugeConfig config; + + public StudioServerConfig(final String fileName) { + // hugegraph-studio.sh -Dstudio.home="$STUDIO_HOME" + final String homeDir = System.getProperty("studio.home"); + config = new HugeConfig(String.format("%s/conf/%s", + homeDir, fileName)); + } + + public int getHttpPort() { + return this.config.get(StudioServerOptions.STUDIO_SERVER_HTTP_PORT); + } + + public String getHttpBindAddress() { + return this.config.get(StudioServerOptions.STUDIO_SERVER_HTTP_BIND_ADDRESS); + } + + public String getServerBasePath() { + return this.config.get(StudioServerOptions.STUDIO_SERVER_BASE_PATH); + } + + public String getServerUIDirectory() { + return this.config.get(StudioServerOptions.STUDIO_SERVER_UI_DIR); + } + + public String getServerWarDirectory() { + return this.config.get(StudioServerOptions.STUDIO_SERVER_WAR_DIR); + } +} diff --git a/studio-dist/src/main/java/com/baidu/hugegraph/studio/config/StudioServerOptions.java b/studio-dist/src/main/java/com/baidu/hugegraph/studio/config/StudioServerOptions.java new file mode 100644 index 00000000..1ca8cae6 --- /dev/null +++ b/studio-dist/src/main/java/com/baidu/hugegraph/studio/config/StudioServerOptions.java @@ -0,0 +1,106 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.studio.config; + +import static com.baidu.hugegraph.config.OptionChecker.disallowEmpty; +import static com.baidu.hugegraph.config.OptionChecker.positiveInt; + +import org.apache.commons.lang3.StringUtils; + +import com.baidu.hugegraph.config.ConfigOption; +import com.baidu.hugegraph.config.OptionHolder; + +/** + * Options for StudioConfiguration. + */ +public class StudioServerOptions extends OptionHolder { + + private StudioServerOptions() { + super(); + } + + private static volatile StudioServerOptions instance; + + /** + * Single Instance for StudioOptions. + * + * @return the studio options + */ + public static StudioServerOptions instance() { + if (instance == null) { + synchronized (StudioServerOptions.class) { + if (instance == null) { + instance = new StudioServerOptions(); + instance.registerOptions(); + } + } + } + return instance; + } + + // hugegraph-studio.sh -Dstudio.home="$STUDIO_HOME" + public static final ConfigOption STUDIO_SERVER_BASE_PATH = + new ConfigOption<>( + "studio.server.base_path", + "The base path of HugeGraphStudio", + disallowEmpty(), + StringUtils.isNotBlank(System.getProperty("studio.home")) ? + System.getProperty("studio.home") : + System.getProperty("user.dir") + ); + + public static final ConfigOption STUDIO_SERVER_UI_DIR = + new ConfigOption<>( + "studio.server.ui", + "The ui directory of HugeGraphStudio", + disallowEmpty(), + "ui" + ); + + public static final ConfigOption STUDIO_SERVER_WAR_DIR = + new ConfigOption<>( + "studio.server.api.war", + "The war directory of HugeGraphStudio", + disallowEmpty(), + "war/studio-api.war" + ); + + /** + * The constant STUDIO_SERVER_HTTP_BIND_ADDRESS. + */ + public static final ConfigOption STUDIO_SERVER_HTTP_BIND_ADDRESS = + new ConfigOption<>( + "studio.server.host", + "The http bind address of HugeGraphStudio", + disallowEmpty(), + "localhost" + ); + + /** + * The constant STUDIO_SERVER_HTTP_PORT. + */ + public static final ConfigOption STUDIO_SERVER_HTTP_PORT = + new ConfigOption<>( + "studio.server.port", + "The http port of HugeGraphStudio", + positiveInt(), + 8088 + ); +}