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
+ );
+}