diff --git a/docs/configs/janusgraph-cfg.md b/docs/configs/janusgraph-cfg.md
index e8d07a7043..1a01983873 100644
--- a/docs/configs/janusgraph-cfg.md
+++ b/docs/configs/janusgraph-cfg.md
@@ -384,6 +384,29 @@ Schema related configuration options
| schema.default | Configures the DefaultSchemaMaker to be used by this graph. Either one of the following shorthands can be used:
- `default` (a blueprints compatible schema maker with MULTI edge labels and SINGLE property keys),
- `tp3` (same as default, but has LIST property keys),
- `none` (automatic schema creation is disabled)
- `ignore-prop` (same as none, but simply ignore unknown properties rather than throw exceptions)
- or to the full package and classname of a custom/third-party implementing the interface `org.janusgraph.core.schema.DefaultSchemaMaker` | String | default | MASKABLE |
| schema.logging | Controls whether logging is enabled for schema makers. This only takes effect if you set `schema.default` to `default` or `ignore-prop`. For `default` schema maker, warning messages will be logged before schema types are created automatically. For `ignore-prop` schema maker, warning messages will be logged before unknown properties are ignored. | Boolean | false | MASKABLE |
+### schema.init
+Configuration options to configure schema initialization options.
+
+
+| Name | Description | Datatype | Default Value | Mutability |
+| ---- | ---- | ---- | ---- | ---- |
+| schema.init.schema-drop-before-startup | Drops the whole schema before JanusGraph schema initialization. Notice schema is dropped regardless of chosen initialization strategy (it will be dropped even if `none` schema-init-strategy is selected). | Boolean | false | LOCAL |
+| schema.init.schema-init-strategy | Selects the strategy for schema initialization before JanusGraph is started. The full class path which implements `SchemaInitStrategy` interface and has no any constructor parameters must be provided. Also, the following shortcuts exist:
- `none` - Skip any schema initialization.
- `json` - Use provided JSON file for schema initialization.
| String | none | LOCAL |
+
+### schema.init.json
+Options for JSON schema initialization strategy.
+
+
+| Name | Description | Datatype | Default Value | Mutability |
+| ---- | ---- | ---- | ---- | ---- |
+| schema.init.json.await-index-status-timeout | Timeout for awaiting index status operation defined in milliseconds. If the status await timeouts an exception will be thrown during schema initialization process. | Long | 180000 | LOCAL |
+| schema.init.json.file | File path to JSON formated schema definition. | String | (no default value) | LOCAL |
+| schema.init.json.force-close-other-instances | Force closes other JanusGraph instances before schema initialization regardless if they are active or not. This is a dangerous operation. This option exists to help people initialize schema who struggle with zombie JanusGraph instances. It's not recommended to be used unless you know what you are doing. Instead of this parameter, it's recommended to check `graph.unique-instance-id` and `graph.replace-instance-if-exists` options to not create zombie instances in the cluster. | Boolean | false | LOCAL |
+| schema.init.json.indices-activation | Indices activation type:
- `reindex_and_enable_updated_only` - Reindex process will be triggered for any updated index. After this all updated indexes will be enabled.
- `reindex_and_enable_non_enabled` - Reindex process will be triggered for any index which is not enabled. After this all indexes will be enabled.
- `skip_activation` - Skip reindex process for any updated indexes.
- `force_enable_updated_only` - Force enable all updated indexes without running any reindex process (previous data may not be available for such indices).
- `force_enable_non_enabled` - Force enable all indexes (including previously created indexes) without running any reindex process (previous data may not be available for such indices).
| String | reindex_and_enable_non_enabled | LOCAL |
+| schema.init.json.skip-elements | Skip creation of VertexLabel, EdgeLabel, and PropertyKey. | Boolean | false | LOCAL |
+| schema.init.json.skip-indices | Skip creation of indices. | Boolean | false | LOCAL |
+| schema.init.json.string | JSON formated string of schema definition. This option takes precedence if both `file` and `string` are used. | String | (no default value) | LOCAL |
+
### storage
Configuration options for the storage backend. Some options are applicable only for certain backends.
diff --git a/janusgraph-backend-testutils/src/main/java/org/janusgraph/graphdb/JanusGraphBaseTest.java b/janusgraph-backend-testutils/src/main/java/org/janusgraph/graphdb/JanusGraphBaseTest.java
index efff716143..a69af6d185 100644
--- a/janusgraph-backend-testutils/src/main/java/org/janusgraph/graphdb/JanusGraphBaseTest.java
+++ b/janusgraph-backend-testutils/src/main/java/org/janusgraph/graphdb/JanusGraphBaseTest.java
@@ -19,6 +19,7 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import org.apache.commons.configuration2.MapConfiguration;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Element;
import org.apache.tinkerpop.gremlin.structure.T;
@@ -207,7 +208,7 @@ public static Map validateConfigOptions(Object... setti
return options;
}
- public void clopen(Object... settings) {
+ public void setupConfig(Object... settings){
config = getConfiguration();
if (mgmt!=null && mgmt.isOpen()) mgmt.rollback();
if (null != tx && tx.isOpen()) tx.commit();
@@ -226,6 +227,10 @@ public void clopen(Object... settings) {
if (janusGraphManagement!=null) janusGraphManagement.commit();
modifiableConfiguration.close();
}
+ }
+
+ public void clopen(Object... settings) {
+ setupConfig(settings);
if (null != graph && null != graph.tx() && graph.tx().isOpen())
graph.tx().commit();
if (null != graph && graph.isOpen())
@@ -234,6 +239,29 @@ public void clopen(Object... settings) {
open(config);
}
+ public void clopenNoDelimiterUsage(Map extraConfigs, boolean clearGraph) {
+ this.config = getConfiguration();
+ if (null != graph && null != graph.tx() && graph.tx().isOpen())
+ graph.tx().commit();
+ if (null != graph && graph.isOpen())
+ graph.close();
+ if(clearGraph) {
+ try {
+ clearGraph(config);
+ } catch (BackendException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ readConfig = new BasicConfiguration(GraphDatabaseConfiguration.ROOT_NS, config, BasicConfiguration.Restriction.NONE);
+ Map finalConfig = new HashMap<>();
+ readConfig.getAll().forEach((pathIdentifier, value) -> finalConfig.put(pathIdentifier.element.toStringWithoutRoot(), value));
+ finalConfig.putAll(extraConfigs);
+
+ graph = (StandardJanusGraph) JanusGraphFactory.open(new MapConfiguration(finalConfig));
+ features = graph.getConfiguration().getStoreFeatures();
+ tx = graph.newTransaction();
+ mgmt = graph.openManagement();
+ }
public static TestConfigOption option(ConfigOption option, String... umbrella) {
return new TestConfigOption(option,umbrella);
diff --git a/janusgraph-backend-testutils/src/main/java/org/janusgraph/graphdb/JanusGraphTest.java b/janusgraph-backend-testutils/src/main/java/org/janusgraph/graphdb/JanusGraphTest.java
index 717451ab1f..61ae312403 100644
--- a/janusgraph-backend-testutils/src/main/java/org/janusgraph/graphdb/JanusGraphTest.java
+++ b/janusgraph-backend-testutils/src/main/java/org/janusgraph/graphdb/JanusGraphTest.java
@@ -22,6 +22,7 @@
import com.google.common.collect.Sets;
import io.github.artsok.RepeatedIfExceptionsTest;
import org.apache.commons.configuration2.MapConfiguration;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.NotImplementedException;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
@@ -70,6 +71,7 @@
import org.janusgraph.core.schema.ConsistencyModifier;
import org.janusgraph.core.schema.DisableDefaultSchemaMaker;
import org.janusgraph.core.schema.IgnorePropertySchemaMaker;
+import org.janusgraph.core.schema.IndicesActivationType;
import org.janusgraph.core.schema.JanusGraphDefaultSchemaMaker;
import org.janusgraph.core.schema.JanusGraphIndex;
import org.janusgraph.core.schema.JanusGraphManagement;
@@ -77,6 +79,7 @@
import org.janusgraph.core.schema.Mapping;
import org.janusgraph.core.schema.RelationTypeIndex;
import org.janusgraph.core.schema.SchemaAction;
+import org.janusgraph.core.schema.SchemaInitType;
import org.janusgraph.core.schema.SchemaStatus;
import org.janusgraph.core.util.ManagementUtil;
import org.janusgraph.diskstorage.Backend;
@@ -169,6 +172,9 @@
import org.slf4j.LoggerFactory;
import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
@@ -234,6 +240,10 @@
import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.REPEAT_STEP_BATCH_MODE;
import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.REPLACE_INSTANCE_IF_EXISTS;
import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCHEMA_CONSTRAINTS;
+import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCHEMA_DROP_BEFORE_INIT;
+import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE;
+import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCHEMA_INIT_JSON_STR;
+import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCHEMA_INIT_STRATEGY;
import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCRIPT_EVAL_ENABLED;
import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCRIPT_EVAL_ENGINE;
import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.STORAGE_BACKEND;
@@ -4521,6 +4531,29 @@ private boolean isSortedByID(VertexList vl) {
return true;
}
+ @Test
+ public void testSimpleJsonSchemaImportFromProperty() throws IOException, BackendException {
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ InputStream resourceStream = loader.getResourceAsStream("jsonSimpleSchemaExample.json");
+ String jsonSchemaExample = IOUtils.toString(resourceStream, StandardCharsets.UTF_8.name());
+
+ Map extraConfig = new HashMap<>();
+ extraConfig.put(SCHEMA_INIT_STRATEGY.toStringWithoutRoot(), SchemaInitType.JSON.getConfigName());
+ extraConfig.put(SCHEMA_DROP_BEFORE_INIT.toStringWithoutRoot(), true);
+ extraConfig.put(SCHEMA_INIT_JSON_STR.toStringWithoutRoot(), jsonSchemaExample);
+ extraConfig.put(SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE.toStringWithoutRoot(), IndicesActivationType.REINDEX_AND_ENABLE_UPDATED_ONLY.getConfigName());
+ clopenNoDelimiterUsage(extraConfig, true);
+
+ assertEquals(Long.class, mgmt.getPropertyKey("time").dataType());
+ assertEquals(Double.class, mgmt.getPropertyKey("doubleProp").dataType());
+ assertEquals(Cardinality.LIST, mgmt.getPropertyKey("longPropCardinalityList").cardinality());
+ assertEquals("organization", mgmt.getVertexLabel("organization").name());
+ assertEquals(Multiplicity.SIMPLE, mgmt.getEdgeLabel("connects").multiplicity());
+ assertTrue(mgmt.getEdgeLabel("connects").isDirected());
+ assertEquals(Multiplicity.MULTI, mgmt.getEdgeLabel("viewed").multiplicity());
+ assertTrue(mgmt.getEdgeLabel("viewed").isUnidirected());
+ }
+
@Test
public void testEdgesExceedCacheSize() {
// Add a vertex with as many edges as the tx-cache-size. (20000 by default)
diff --git a/janusgraph-backend-testutils/src/main/resources/jsonSimpleSchemaExample.json b/janusgraph-backend-testutils/src/main/resources/jsonSimpleSchemaExample.json
new file mode 100644
index 0000000000..08fac5a325
--- /dev/null
+++ b/janusgraph-backend-testutils/src/main/resources/jsonSimpleSchemaExample.json
@@ -0,0 +1,48 @@
+{
+ "vertexLabels": [
+ {
+ "label": "organization"
+ },
+ {
+ "label": "user"
+ }
+ ],
+
+ "edgeLabels": [
+ {
+ "label": "connects",
+ "multiplicity": "SIMPLE",
+ "unidirected": false
+ },
+ {
+ "label": "viewed",
+ "multiplicity": "MULTI",
+ "unidirected": true
+ }
+ ],
+
+ "propertyKeys": [
+ {
+ "key": "time",
+ "className": "java.lang.Long"
+ },
+ {
+ "key": "doubleProp",
+ "className": "java.lang.Double"
+ },
+ {
+ "key": "integerProp",
+ "className": "java.lang.Integer"
+ },
+ {
+ "key": "longPropCardinalityList",
+ "className": "java.lang.Long",
+ "cardinality": "LIST"
+ },
+ {
+ "key": "name",
+ "className": "java.lang.String",
+ "cardinality": "SINGLE"
+ }
+ ]
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/JanusGraphFactory.java b/janusgraph-core/src/main/java/org/janusgraph/core/JanusGraphFactory.java
index 39545dbc38..f4be5a62f8 100644
--- a/janusgraph-core/src/main/java/org/janusgraph/core/JanusGraphFactory.java
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/JanusGraphFactory.java
@@ -23,6 +23,7 @@
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.janusgraph.core.log.LogProcessorFramework;
import org.janusgraph.core.log.TransactionRecovery;
+import org.janusgraph.core.schema.SchemaInitializationManager;
import org.janusgraph.diskstorage.Backend;
import org.janusgraph.diskstorage.BackendException;
import org.janusgraph.diskstorage.StandardStoreManager;
@@ -162,7 +163,7 @@ public static JanusGraph open(ReadConfiguration configuration, String backupName
final JanusGraphManager jgm = JanusGraphManagerUtility.getInstance();
if (null != graphName) {
Preconditions.checkNotNull(jgm, JANUS_GRAPH_MANAGER_EXPECTED_STATE_MSG);
- return (JanusGraph) jgm.openGraph(graphName, gName -> new StandardJanusGraph(new GraphDatabaseConfigurationBuilder().build(configuration)));
+ return (JanusGraph) jgm.openGraph(graphName, gName -> defineSchemaAndStart(configuration));
} else {
if (jgm != null) {
log.warn("You should supply \"graph.graphname\" in your .properties file configuration if you are opening " +
@@ -173,10 +174,15 @@ public static JanusGraph open(ReadConfiguration configuration, String backupName
"\"graph.graphname\" so these graphs should be accessed dynamically by supplying a .properties file here " +
"or by using the ConfiguredGraphFactory.");
}
- return new StandardJanusGraph(new GraphDatabaseConfigurationBuilder().build(configuration));
+ return defineSchemaAndStart(configuration);
}
}
+ private static JanusGraph defineSchemaAndStart(ReadConfiguration configuration) {
+ GraphDatabaseConfiguration graphDatabaseConfiguration = new GraphDatabaseConfigurationBuilder().build(configuration);
+ return SchemaInitializationManager.initializeSchemaAndStart(graphDatabaseConfiguration);
+ }
+
/**
* Return a Set of graph names stored in the {@link JanusGraphManager}
*
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/IndicesActivationType.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/IndicesActivationType.java
new file mode 100644
index 0000000000..86a2d7768a
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/IndicesActivationType.java
@@ -0,0 +1,36 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema;
+
+import org.janusgraph.graphdb.configuration.ConfigName;
+
+public enum IndicesActivationType implements ConfigName {
+ SKIP_ACTIVATION("skip_activation"),
+ REINDEX_AND_ENABLE_UPDATED_ONLY("reindex_and_enable_updated_only"),
+ REINDEX_AND_ENABLE_NON_ENABLED("reindex_and_enable_non_enabled"),
+ FORCE_ENABLE_UPDATED_ONLY("force_enable_updated_only"),
+ FORCE_ENABLE_NON_ENABLED("force_enable_non_enabled");
+
+ private final String configurationOptionName;
+
+ IndicesActivationType(String configurationOptionName){
+ this.configurationOptionName = configurationOptionName;
+ }
+
+ @Override
+ public String getConfigName() {
+ return configurationOptionName;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/JsonSchemaInitStrategy.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/JsonSchemaInitStrategy.java
new file mode 100644
index 0000000000..fc6c7851f6
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/JsonSchemaInitStrategy.java
@@ -0,0 +1,265 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema;
+
+import org.apache.commons.lang3.StringUtils;
+import org.janusgraph.core.JanusGraph;
+import org.janusgraph.core.Namifiable;
+import org.janusgraph.core.PropertyKey;
+import org.janusgraph.core.RelationType;
+import org.janusgraph.core.schema.json.creator.GeneralJsonSchemaCreator;
+import org.janusgraph.core.schema.json.creator.JsonSchemaCreationContext;
+import org.janusgraph.core.schema.json.creator.SchemaCreationException;
+import org.janusgraph.core.schema.json.definition.JsonSchemaDefinition;
+import org.janusgraph.core.schema.json.definition.index.AbstractJsonGraphCentricIndexDefinition;
+import org.janusgraph.core.schema.json.definition.index.AbstractJsonIndexDefinition;
+import org.janusgraph.core.schema.json.definition.index.AbstractJsonVertexCentricIndexDefinition;
+import org.janusgraph.core.schema.json.definition.index.JsonVertexCentricEdgeIndexDefinition;
+import org.janusgraph.core.schema.json.definition.index.JsonVertexCentricPropertyIndexDefinition;
+import org.janusgraph.core.util.JsonUtil;
+import org.janusgraph.core.util.ManagementUtil;
+import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration;
+import org.janusgraph.graphdb.database.StandardJanusGraph;
+import org.janusgraph.graphdb.database.management.ManagementSystem;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class JsonSchemaInitStrategy implements SchemaInitStrategy{
+
+ private static final Logger LOG = LoggerFactory.getLogger(JsonSchemaInitStrategy.class);
+
+ @Override
+ public JanusGraph initializeSchemaAndStart(GraphDatabaseConfiguration graphDatabaseConfiguration) {
+
+ JanusGraph graph = new StandardJanusGraph(graphDatabaseConfiguration);
+
+ try{
+ if(StringUtils.isNotEmpty(graphDatabaseConfiguration.getSchemaInitJsonString())) {
+
+ initializeSchemaFromString(graph, graphDatabaseConfiguration.getSchemaInitJsonString());
+
+ } else if(StringUtils.isNotEmpty(graphDatabaseConfiguration.getSchemaInitJsonFile())){
+
+ initializeSchemaFromFile(graph, graphDatabaseConfiguration.getSchemaInitJsonFile());
+
+ } else {
+ throw new SchemaCreationException("When JSON schema initialization is enabled it's necessary to provide either JSON file or string for schema initialization.");
+ }
+ } catch (Throwable throwable){
+ graph.close();
+ if(throwable instanceof SchemaCreationException){
+ throw (SchemaCreationException)throwable;
+ }
+ throw new SchemaCreationException(throwable);
+ }
+
+ return graph;
+ }
+
+ public static void initializeSchemaFromFile(JanusGraph graph, String jsonSchemaFilePath) {
+ GraphDatabaseConfiguration graphDatabaseConfiguration = ((StandardJanusGraph) graph).getConfiguration();
+ IndicesActivationType indicesActivationType = graphDatabaseConfiguration.getSchemaInitJsonIndicesActivationType();
+ boolean createSchemaElements = !graphDatabaseConfiguration.getSchemaInitJsonSkipElements();
+ boolean createSchemaIndices = !graphDatabaseConfiguration.getSchemaInitJsonSkipIndices();
+ long indexStatusTimeout = graphDatabaseConfiguration.getSchemaInitJsonIndexStatusAwaitTimeout();
+
+ initializeSchemaFromFile(graph, createSchemaElements, createSchemaIndices, indicesActivationType, true,
+ graphDatabaseConfiguration.getSchemaInitJsonForceCloseOtherInstances(), indexStatusTimeout, jsonSchemaFilePath);
+ }
+
+ public static void initializeSchemaFromString(JanusGraph graph, String jsonSchemaString) {
+ GraphDatabaseConfiguration graphDatabaseConfiguration = ((StandardJanusGraph) graph).getConfiguration();
+ IndicesActivationType indicesActivationType = graphDatabaseConfiguration.getSchemaInitJsonIndicesActivationType();
+ boolean createSchemaElements = !graphDatabaseConfiguration.getSchemaInitJsonSkipElements();
+ boolean createSchemaIndices = !graphDatabaseConfiguration.getSchemaInitJsonSkipIndices();
+ long indexStatusTimeout = graphDatabaseConfiguration.getSchemaInitJsonIndexStatusAwaitTimeout();
+
+ initializeSchemaFromString(graph, createSchemaElements, createSchemaIndices, indicesActivationType, true,
+ graphDatabaseConfiguration.getSchemaInitJsonForceCloseOtherInstances(), indexStatusTimeout, jsonSchemaString);
+ }
+
+ public static void initializeSchemaFromFile(JanusGraph graph, boolean createSchemaElements, boolean createSchemaIndices,
+ IndicesActivationType indicesActivationType,
+ boolean forceRollBackActiveTransactions, boolean forceCloseOtherInstances,
+ long indexStatusTimeout, String jsonSchemaFilePath) {
+
+ JsonSchemaDefinition generalDefinition;
+ try {
+ generalDefinition = JsonUtil.jsonFilePathToObject(jsonSchemaFilePath, JsonSchemaDefinition.class);
+ } catch (IOException e) {
+ throw new SchemaCreationException(e);
+ }
+
+ initializeSchema(graph, createSchemaElements, createSchemaIndices, indicesActivationType,
+ forceRollBackActiveTransactions, forceCloseOtherInstances, indexStatusTimeout, generalDefinition);
+ }
+
+ public static void initializeSchemaFromString(JanusGraph graph, boolean createSchemaElements, boolean createSchemaIndices,
+ IndicesActivationType indicesActivationType,
+ boolean forceRollBackActiveTransactions, boolean forceCloseOtherInstances,
+ long indexStatusTimeout, String jsonSchemaString) {
+
+ JsonSchemaDefinition generalDefinition;
+ try {
+ generalDefinition = JsonUtil.jsonStringToObject(jsonSchemaString, JsonSchemaDefinition.class);
+ } catch (IOException e) {
+ throw new SchemaCreationException(e);
+ }
+
+ initializeSchema(graph, createSchemaElements, createSchemaIndices, indicesActivationType,
+ forceRollBackActiveTransactions, forceCloseOtherInstances, indexStatusTimeout, generalDefinition);
+ }
+
+ public static void initializeSchema(JanusGraph graph, boolean createSchemaElements, boolean createSchemaIndices,
+ IndicesActivationType indicesActivationType,
+ boolean forceRollBackActiveTransactions, boolean forceCloseOtherInstances,
+ long indexStatusTimeout, JsonSchemaDefinition generalDefinition) {
+
+ LOG.info("Starting schema initialization.");
+
+ if(forceCloseOtherInstances){
+ ManagementUtil.forceCloseOtherInstances(graph);
+ }
+
+ if(forceRollBackActiveTransactions){
+ ManagementUtil.forceRollbackAllTransactions(graph);
+ }
+
+ final ManagementSystem schemaCreationMgmt = (ManagementSystem) graph.openManagement();
+ JsonSchemaCreationContext schemaCreationContext = new JsonSchemaCreationContext(
+ schemaCreationMgmt,
+ createSchemaElements,
+ createSchemaIndices);
+
+ GeneralJsonSchemaCreator generalJsonSchemaCreator = new GeneralJsonSchemaCreator();
+
+ // initialize schema
+ generalJsonSchemaCreator.create(generalDefinition, schemaCreationContext);
+
+ List updatedButNonEnabledIndicesNames = schemaCreationContext.getCreatedOrUpdatedIndices().stream()
+ .filter(indexDefinition -> !isIndexEnabled(indexDefinition, schemaCreationMgmt))
+ .map(AbstractJsonIndexDefinition::getName)
+ .collect(Collectors.toList());
+
+ schemaCreationMgmt.commit();
+
+ // process indices activation (re-index and enable)
+ processIndicesActivation(graph, updatedButNonEnabledIndicesNames, indicesActivationType, indexStatusTimeout);
+
+ LOG.info("Schema initialization completed.");
+ }
+
+ private static void processIndicesActivation(JanusGraph graph, List updatedButNonEnabledIndicesNames,
+ IndicesActivationType indicesActivationType, long indexStatusTimeout){
+
+ if(IndicesActivationType.SKIP_ACTIVATION.equals(indicesActivationType)){
+ return;
+ }
+
+ ManagementSystem schemaFetchMgmt = (ManagementSystem) graph.openManagement();
+
+ List nonEnabledGraphIndices = schemaFetchMgmt.getGraphIndices(SchemaStatus.ENABLED);
+ List nonEnabledRelationTypeIndices = schemaFetchMgmt.getVertexCentricIndices(SchemaStatus.ENABLED);
+
+ if(IndicesActivationType.REINDEX_AND_ENABLE_UPDATED_ONLY.equals(indicesActivationType) || IndicesActivationType.FORCE_ENABLE_UPDATED_ONLY.equals(indicesActivationType)){
+ List nonEnabledGraphIndicesCopy = nonEnabledGraphIndices;
+ List nonEnabledRelationTypeIndicesCopy = nonEnabledRelationTypeIndices;
+ nonEnabledGraphIndices = new ArrayList<>(nonEnabledGraphIndices.size());
+ nonEnabledRelationTypeIndices = new ArrayList<>(nonEnabledRelationTypeIndices.size());
+ for (JanusGraphIndex index : nonEnabledGraphIndicesCopy) {
+ if(updatedButNonEnabledIndicesNames.contains(index.name())){
+ nonEnabledGraphIndices.add(index);
+ }
+ }
+ for (RelationTypeIndex index : nonEnabledRelationTypeIndicesCopy) {
+ if(updatedButNonEnabledIndicesNames.contains(index.name())){
+ nonEnabledRelationTypeIndices.add(index);
+ }
+ }
+ }
+
+ List nonEnabledGraphIndexNames = nonEnabledGraphIndices.stream().map(JanusGraphIndex::name).collect(Collectors.toList());
+ Map nonEnabledRelationTypeIndexNamesAndTypes = nonEnabledRelationTypeIndices.stream().collect(Collectors.toMap(Namifiable::name, o -> o.getType().name()));
+
+ schemaFetchMgmt.rollback();
+
+ List nonEnabledIndexNames = Stream.concat(nonEnabledGraphIndexNames.stream(), nonEnabledRelationTypeIndexNamesAndTypes.keySet().stream()).collect(Collectors.toList());
+
+ if(!nonEnabledIndexNames.isEmpty()){
+
+ String nonEnabledIndexNamesJoined = String.join(", ", nonEnabledIndexNames);
+ if(!nonEnabledIndexNames.isEmpty() && LOG.isInfoEnabled()){
+ LOG.info("Found {} non-enabled indices. Awaiting for their status updates. Indexes [{}].",
+ nonEnabledIndexNames.size(), nonEnabledIndexNamesJoined);
+ }
+
+ if(IndicesActivationType.REINDEX_AND_ENABLE_UPDATED_ONLY.equals(indicesActivationType) || IndicesActivationType.REINDEX_AND_ENABLE_NON_ENABLED.equals(indicesActivationType)){
+
+ ManagementUtil.reindexAndEnableIndices(graph, nonEnabledGraphIndexNames, nonEnabledRelationTypeIndexNamesAndTypes, indexStatusTimeout);
+
+ } else if(IndicesActivationType.FORCE_ENABLE_UPDATED_ONLY.equals(indicesActivationType) || IndicesActivationType.FORCE_ENABLE_NON_ENABLED.equals(indicesActivationType)){
+
+ ManagementUtil.forceEnableIndices(graph, nonEnabledGraphIndexNames, nonEnabledRelationTypeIndexNamesAndTypes, indexStatusTimeout);
+
+ } else {
+ throw new IllegalStateException("Unknown indicesActivationType: " + indicesActivationType);
+ }
+ }
+ }
+
+ private static boolean isIndexEnabled(AbstractJsonIndexDefinition createdIndexesDefinition, JanusGraphManagement graphManagement){
+ return isIndexHasStatus(createdIndexesDefinition, graphManagement, SchemaStatus.ENABLED);
+ }
+
+ private static boolean isIndexHasStatus(AbstractJsonIndexDefinition createdIndexesDefinition, JanusGraphManagement graphManagement, SchemaStatus schemaStatus){
+ if(createdIndexesDefinition instanceof AbstractJsonVertexCentricIndexDefinition){
+
+ AbstractJsonVertexCentricIndexDefinition vertexCentricIndex =
+ (AbstractJsonVertexCentricIndexDefinition) createdIndexesDefinition;
+ String indexedElement;
+ if(vertexCentricIndex instanceof JsonVertexCentricEdgeIndexDefinition){
+ indexedElement = ((JsonVertexCentricEdgeIndexDefinition) vertexCentricIndex).getIndexedEdgeLabel();
+ } else if (vertexCentricIndex instanceof JsonVertexCentricPropertyIndexDefinition){
+ indexedElement = ((JsonVertexCentricPropertyIndexDefinition) vertexCentricIndex).getIndexedPropertyKey();
+ } else {
+ throw new SchemaCreationException("Unknown index type definition: "+createdIndexesDefinition.getClass().getName());
+ }
+ RelationType relationType = graphManagement.getRelationType(indexedElement);
+ RelationTypeIndex relationIndex = graphManagement.getRelationIndex(relationType, vertexCentricIndex.getName());
+ return schemaStatus.equals(relationIndex.getIndexStatus());
+
+ } else if (createdIndexesDefinition instanceof AbstractJsonGraphCentricIndexDefinition) {
+
+ JanusGraphIndex janusGraphIndex = graphManagement.getGraphIndex(createdIndexesDefinition.getName());
+ for(PropertyKey propertyKey : janusGraphIndex.getFieldKeys()){
+ if(!schemaStatus.equals(janusGraphIndex.getIndexStatus(propertyKey))){
+ return false;
+ }
+ }
+ return true;
+
+ } else {
+ throw new SchemaCreationException("Unknown index type definition: "+createdIndexesDefinition.getClass().getName());
+ }
+ }
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/NoneSchemaInitStrategy.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/NoneSchemaInitStrategy.java
new file mode 100644
index 0000000000..773029356c
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/NoneSchemaInitStrategy.java
@@ -0,0 +1,29 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema;
+
+import org.janusgraph.core.JanusGraph;
+import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration;
+import org.janusgraph.graphdb.database.StandardJanusGraph;
+
+/**
+ * Skips any schema initialization on startup
+ */
+public class NoneSchemaInitStrategy implements SchemaInitStrategy{
+ @Override
+ public JanusGraph initializeSchemaAndStart(GraphDatabaseConfiguration graphDatabaseConfiguration) {
+ return new StandardJanusGraph(graphDatabaseConfiguration);
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/SchemaInitStrategy.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/SchemaInitStrategy.java
new file mode 100644
index 0000000000..6059716bca
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/SchemaInitStrategy.java
@@ -0,0 +1,24 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema;
+
+import org.janusgraph.core.JanusGraph;
+import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration;
+
+public interface SchemaInitStrategy {
+
+ JanusGraph initializeSchemaAndStart(GraphDatabaseConfiguration graphDatabaseConfiguration);
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/SchemaInitType.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/SchemaInitType.java
new file mode 100644
index 0000000000..29341176e0
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/SchemaInitType.java
@@ -0,0 +1,33 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema;
+
+import org.janusgraph.graphdb.configuration.ConfigName;
+
+public enum SchemaInitType implements ConfigName {
+ NONE("none"), // skips any schema initialization during startup
+ JSON("json"); // initializes schema using provided JSON file
+
+ private final String configurationOptionName;
+
+ SchemaInitType(String configurationOptionName){
+ this.configurationOptionName = configurationOptionName;
+ }
+
+ @Override
+ public String getConfigName() {
+ return configurationOptionName;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/SchemaInitializationManager.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/SchemaInitializationManager.java
new file mode 100644
index 0000000000..9d150a8ceb
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/SchemaInitializationManager.java
@@ -0,0 +1,78 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema;
+
+import org.janusgraph.core.JanusGraph;
+import org.janusgraph.core.JanusGraphFactory;
+import org.janusgraph.core.schema.json.creator.SchemaCreationException;
+import org.janusgraph.diskstorage.BackendException;
+import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration;
+import org.janusgraph.graphdb.database.StandardJanusGraph;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SchemaInitializationManager {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SchemaInitializationManager.class);
+
+ public static final Map SCHEMA_INIT_STRATEGIES;
+
+ static {
+ SCHEMA_INIT_STRATEGIES = new HashMap<>(1);
+ SCHEMA_INIT_STRATEGIES.put(SchemaInitType.NONE.getConfigName(), new NoneSchemaInitStrategy());
+ SCHEMA_INIT_STRATEGIES.put(SchemaInitType.JSON.getConfigName(), new JsonSchemaInitStrategy());
+ }
+
+ public static JanusGraph initializeSchemaAndStart(GraphDatabaseConfiguration graphDatabaseConfiguration){
+
+ // drop schema before init is selected
+ if(graphDatabaseConfiguration.isDropSchemaBeforeInit()){
+ try {
+ LOG.info("Dropping current JanusGraph schema and data.");
+ JanusGraphFactory.drop(new StandardJanusGraph(graphDatabaseConfiguration));
+ LOG.info("Current JanusGraph schema and data were removed.");
+ } catch (BackendException e) {
+ throw new SchemaCreationException(e);
+ }
+ }
+
+ // get schema initialization strategy
+ String schemaInitStrategyPath = graphDatabaseConfiguration.getSchemaInitStrategy();
+ SchemaInitStrategy schemaInitStrategy = SCHEMA_INIT_STRATEGIES.get(schemaInitStrategyPath);
+ if(schemaInitStrategy == null){
+ try {
+ Class schemaInitStrategyClass = Class.forName(schemaInitStrategyPath);
+ schemaInitStrategy = (SchemaInitStrategy) schemaInitStrategyClass.getDeclaredConstructor().newInstance();
+ } catch (ClassNotFoundException | InvocationTargetException | InstantiationException |
+ IllegalAccessException | NoSuchMethodException e) {
+ throw new SchemaCreationException(e);
+ }
+ }
+
+ // initialize schema
+ JanusGraph graph = schemaInitStrategy.initializeSchemaAndStart(graphDatabaseConfiguration);
+
+ if(graph == null){
+ graph = new StandardJanusGraph(graphDatabaseConfiguration);
+ }
+
+ return graph;
+ }
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/GeneralJsonSchemaCreator.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/GeneralJsonSchemaCreator.java
new file mode 100644
index 0000000000..812727e002
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/GeneralJsonSchemaCreator.java
@@ -0,0 +1,113 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.janusgraph.core.schema.json.creator.index.JsonCompositeIndexCreator;
+import org.janusgraph.core.schema.json.creator.index.JsonMixedIndexCreator;
+import org.janusgraph.core.schema.json.creator.index.JsonVertexCentricEdgeIndexCreator;
+import org.janusgraph.core.schema.json.creator.index.JsonVertexCentricPropertyIndexCreator;
+import org.janusgraph.core.schema.json.definition.JsonEdgeLabelDefinition;
+import org.janusgraph.core.schema.json.definition.JsonPropertyKeyDefinition;
+import org.janusgraph.core.schema.json.definition.JsonSchemaDefinition;
+import org.janusgraph.core.schema.json.definition.JsonVertexLabelDefinition;
+import org.janusgraph.core.schema.json.definition.index.JsonCompositeIndexDefinition;
+import org.janusgraph.core.schema.json.definition.index.JsonMixedIndexDefinition;
+import org.janusgraph.core.schema.json.definition.index.JsonVertexCentricEdgeIndexDefinition;
+import org.janusgraph.core.schema.json.definition.index.JsonVertexCentricPropertyIndexDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GeneralJsonSchemaCreator implements JsonSchemaCreator{
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(GeneralJsonSchemaCreator.class);
+
+ private final JsonPropertySchemaCreator propertySchemaCreator = new JsonPropertySchemaCreator();
+ private final JsonVertexSchemaCreator vertexSchemaCreator = new JsonVertexSchemaCreator();
+ private final JsonEdgeSchemaCreator edgeSchemaCreator = new JsonEdgeSchemaCreator();
+ private final JsonCompositeIndexCreator compositeIndexCreator = new JsonCompositeIndexCreator();
+ private final JsonMixedIndexCreator mixedIndexCreator = new JsonMixedIndexCreator();
+ private final JsonVertexCentricEdgeIndexCreator vertexCentricEdgeIndexCreator = new JsonVertexCentricEdgeIndexCreator();
+ private final JsonVertexCentricPropertyIndexCreator vertexCentricPropertyIndexCreator = new JsonVertexCentricPropertyIndexCreator();
+
+ @Override
+ public boolean create(JsonSchemaDefinition generalDefinition, JsonSchemaCreationContext context) {
+
+ LOGGER.info("Starting general schema initialization.");
+
+ boolean wasUpdated = false;
+
+ if(context.isCreateSchemaElements()){
+ // Create all properties
+ if(CollectionUtils.isNotEmpty(generalDefinition.getPropertyKeys())){
+ for(JsonPropertyKeyDefinition definition : generalDefinition.getPropertyKeys()){
+ wasUpdated |= propertySchemaCreator.create(definition, context);
+ }
+ }
+
+ // Create all vertices
+ if(CollectionUtils.isNotEmpty(generalDefinition.getVertexLabels())){
+ for(JsonVertexLabelDefinition definition : generalDefinition.getVertexLabels()){
+ wasUpdated |= vertexSchemaCreator.create(definition, context);
+ }
+ }
+
+ // Create all edges
+ if(CollectionUtils.isNotEmpty(generalDefinition.getEdgeLabels())){
+ for(JsonEdgeLabelDefinition definition : generalDefinition.getEdgeLabels()){
+ wasUpdated |= edgeSchemaCreator.create(definition, context);
+ }
+ }
+ }
+
+ if(context.isCreateIndices()){
+ // Create all composite indices
+ if(CollectionUtils.isNotEmpty(generalDefinition.getCompositeIndexes())){
+ for(JsonCompositeIndexDefinition definition : generalDefinition.getCompositeIndexes()){
+ wasUpdated |= compositeIndexCreator.create(definition, context);
+ }
+ }
+
+ // Create all mixed indices
+ if(CollectionUtils.isNotEmpty(generalDefinition.getMixedIndexes())){
+ for(JsonMixedIndexDefinition definition : generalDefinition.getMixedIndexes()){
+ wasUpdated |= mixedIndexCreator.create(definition, context);
+ }
+ }
+
+ // Create all vertex-centric edge indices
+ if(CollectionUtils.isNotEmpty(generalDefinition.getVertexCentricEdgeIndexes())){
+ for(JsonVertexCentricEdgeIndexDefinition definition : generalDefinition.getVertexCentricEdgeIndexes()){
+ wasUpdated |= vertexCentricEdgeIndexCreator.create(definition, context);
+ }
+ }
+
+ // Create all vertex-centric property indices
+ if(CollectionUtils.isNotEmpty(generalDefinition.getVertexCentricPropertyIndexes())){
+ for(JsonVertexCentricPropertyIndexDefinition definition : generalDefinition.getVertexCentricPropertyIndexes()){
+ wasUpdated |= vertexCentricPropertyIndexCreator.create(definition, context);
+ }
+ }
+ }
+
+ if(wasUpdated){
+ LOGGER.info("Schema initialization complete.");
+ } else {
+ LOGGER.info("There was no any changes during schema initialization. Schema initialization is skipped.");
+ }
+
+ return wasUpdated;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonEdgeSchemaCreator.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonEdgeSchemaCreator.java
new file mode 100644
index 0000000000..9c32ea3fe1
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonEdgeSchemaCreator.java
@@ -0,0 +1,76 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator;
+
+import org.janusgraph.core.EdgeLabel;
+import org.janusgraph.core.schema.EdgeLabelMaker;
+import org.janusgraph.core.schema.JanusGraphManagement;
+import org.janusgraph.core.schema.json.definition.JsonEdgeLabelDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+
+public class JsonEdgeSchemaCreator implements JsonSchemaCreator {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(JsonEdgeSchemaCreator.class);
+
+ @Override
+ public boolean create(JsonEdgeLabelDefinition definition, JsonSchemaCreationContext context) {
+
+ JanusGraphManagement graphManagement = context.getGraphManagement();
+
+ if(isEdgeExists(graphManagement, definition)) {
+ LOGGER.info("Creation of the edge {} was skipped because it is already exists.", definition.getLabel());
+ return false;
+ }
+
+ EdgeLabel edgeLabel = createEdge(graphManagement, definition);
+ if(definition.getTtl() != null){
+ graphManagement.setTTL(edgeLabel, Duration.ofMillis(definition.getTtl()));
+ }
+ if(definition.getConsistency() != null){
+ graphManagement.setConsistency(edgeLabel, definition.getConsistency());
+ }
+
+ LOGGER.info("Edge {} was created", definition.getLabel());
+
+ return true;
+
+ }
+
+ private EdgeLabel createEdge(JanusGraphManagement graphManagement,
+ JsonEdgeLabelDefinition definition) {
+
+ EdgeLabelMaker edgeLabelMaker = graphManagement.makeEdgeLabel(definition.getLabel());
+
+ if(Boolean.TRUE.equals(definition.getUnidirected())) {
+ edgeLabelMaker.unidirected();
+ } else {
+ edgeLabelMaker.directed();
+ }
+
+ if(definition.getMultiplicity() != null){
+ edgeLabelMaker.multiplicity(definition.getMultiplicity());
+ }
+
+ return edgeLabelMaker.make();
+ }
+
+ private boolean isEdgeExists(JanusGraphManagement graphManagement,
+ JsonEdgeLabelDefinition definition) {
+ return graphManagement.containsEdgeLabel(definition.getLabel());
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonPropertySchemaCreator.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonPropertySchemaCreator.java
new file mode 100644
index 0000000000..435f2d8354
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonPropertySchemaCreator.java
@@ -0,0 +1,78 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator;
+
+import org.janusgraph.core.PropertyKey;
+import org.janusgraph.core.schema.JanusGraphManagement;
+import org.janusgraph.core.schema.PropertyKeyMaker;
+import org.janusgraph.core.schema.json.definition.JsonPropertyKeyDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+
+public class JsonPropertySchemaCreator implements JsonSchemaCreator {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(JsonPropertySchemaCreator.class);
+
+ @Override
+ public boolean create(JsonPropertyKeyDefinition definition, JsonSchemaCreationContext context) {
+
+ JanusGraphManagement graphManagement = context.getGraphManagement();
+
+ if(isPropertyExists(graphManagement, definition)) {
+ LOGGER.info("Creation of the property {} was skipped because it is already exists.", definition.getKey());
+ return false;
+ }
+
+ PropertyKey propertyKey = createProperty(graphManagement, definition);
+ if(definition.getTtl() != null){
+ graphManagement.setTTL(propertyKey, Duration.ofMillis(definition.getTtl()));
+ }
+ if(definition.getConsistency() != null){
+ graphManagement.setConsistency(propertyKey, definition.getConsistency());
+ }
+
+ LOGGER.info("Property {} was created", definition.getKey());
+
+ return true;
+ }
+
+ private PropertyKey createProperty(JanusGraphManagement graphManagement,
+ JsonPropertyKeyDefinition definition) {
+
+ Class propertyTypeClass;
+ try{
+ propertyTypeClass = Class.forName(definition.getClassName());
+ } catch (Exception e){
+ throw new SchemaCreationException(e);
+ }
+
+ PropertyKeyMaker propertyKeyMaker = graphManagement.makePropertyKey(definition.getKey())
+ .dataType(propertyTypeClass);
+
+ if(definition.getCardinality() != null){
+ propertyKeyMaker.cardinality(definition.getCardinality());
+ }
+
+ return propertyKeyMaker.make();
+ }
+
+ private boolean isPropertyExists(JanusGraphManagement graphManagement,
+ JsonPropertyKeyDefinition definition) {
+ return graphManagement.containsPropertyKey(definition.getKey());
+ }
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonSchemaCreationContext.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonSchemaCreationContext.java
new file mode 100644
index 0000000000..30f3699b94
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonSchemaCreationContext.java
@@ -0,0 +1,55 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator;
+
+import org.janusgraph.core.schema.JanusGraphManagement;
+import org.janusgraph.core.schema.json.definition.index.AbstractJsonIndexDefinition;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class JsonSchemaCreationContext {
+
+ private JanusGraphManagement graphManagement;
+ private final boolean createSchemaElements;
+ private final boolean createIndices;
+ private final Set createdOrUpdatedIndices = new HashSet<>();
+
+ public JsonSchemaCreationContext(JanusGraphManagement graphManagement, boolean createSchemaElements, boolean createIndices) {
+ this.graphManagement=graphManagement;
+ this.createSchemaElements = createSchemaElements;
+ this.createIndices = createIndices;
+ }
+
+ public void setGraphManagement(JanusGraphManagement graphManagement) {
+ this.graphManagement = graphManagement;
+ }
+
+ public JanusGraphManagement getGraphManagement() {
+ return graphManagement;
+ }
+
+ public boolean isCreateSchemaElements() {
+ return createSchemaElements;
+ }
+
+ public boolean isCreateIndices() {
+ return createIndices;
+ }
+
+ public Set getCreatedOrUpdatedIndices() {
+ return createdOrUpdatedIndices;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonSchemaCreator.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonSchemaCreator.java
new file mode 100644
index 0000000000..09e43996ce
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonSchemaCreator.java
@@ -0,0 +1,21 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator;
+
+public interface JsonSchemaCreator {
+
+ boolean create(T definition, JsonSchemaCreationContext context);
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonVertexSchemaCreator.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonVertexSchemaCreator.java
new file mode 100644
index 0000000000..f684130118
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/JsonVertexSchemaCreator.java
@@ -0,0 +1,71 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator;
+
+import org.janusgraph.core.VertexLabel;
+import org.janusgraph.core.schema.JanusGraphManagement;
+import org.janusgraph.core.schema.VertexLabelMaker;
+import org.janusgraph.core.schema.json.definition.JsonVertexLabelDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+
+public class JsonVertexSchemaCreator implements JsonSchemaCreator {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(JsonVertexSchemaCreator.class);
+
+ @Override
+ public boolean create(JsonVertexLabelDefinition definition, JsonSchemaCreationContext context) {
+
+ JanusGraphManagement graphManagement = context.getGraphManagement();
+
+ if(isVertexExists(graphManagement, definition)){
+ LOGGER.info("Creation of the vertex {} was skipped because it is already exists.", definition.getLabel());
+ return false;
+ }
+
+ VertexLabel vertexLabel = createVertex(graphManagement, definition);
+ if(definition.getTtl() != null){
+ graphManagement.setTTL(vertexLabel, Duration.ofMillis(definition.getTtl()));
+ }
+
+ LOGGER.info("Vertex {} was created", definition.getLabel());
+
+ return true;
+ }
+
+ private VertexLabel createVertex(JanusGraphManagement graphManagement,
+ JsonVertexLabelDefinition definition) {
+
+ VertexLabelMaker vertexLabelMaker = graphManagement.makeVertexLabel(definition.getLabel());
+
+ if(Boolean.TRUE.equals(definition.getPartition())) {
+ vertexLabelMaker.partition();
+ }
+
+ if(Boolean.TRUE.equals(definition.getStaticVertex())) {
+ vertexLabelMaker.setStatic();
+ }
+
+ return vertexLabelMaker.make();
+ }
+
+ private boolean isVertexExists(JanusGraphManagement graphManagement,
+ JsonVertexLabelDefinition definition) {
+ return graphManagement.containsVertexLabel(definition.getLabel());
+ }
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/SchemaCreationException.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/SchemaCreationException.java
new file mode 100644
index 0000000000..fd681969a6
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/SchemaCreationException.java
@@ -0,0 +1,27 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator;
+
+public class SchemaCreationException extends RuntimeException {
+
+ public SchemaCreationException(Throwable throwable) {
+ super(throwable);
+ }
+
+ public SchemaCreationException(String msg) {
+ super(msg);
+ }
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/AbstractJsonGraphCentricIndexCreator.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/AbstractJsonGraphCentricIndexCreator.java
new file mode 100644
index 0000000000..79afc707b7
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/AbstractJsonGraphCentricIndexCreator.java
@@ -0,0 +1,101 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator.index;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Element;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.janusgraph.core.schema.Index;
+import org.janusgraph.core.schema.JanusGraphManagement;
+import org.janusgraph.core.schema.JanusGraphSchemaType;
+import org.janusgraph.core.schema.Parameter;
+import org.janusgraph.core.schema.json.creator.JsonSchemaCreationContext;
+import org.janusgraph.core.schema.json.creator.SchemaCreationException;
+import org.janusgraph.core.schema.json.definition.index.AbstractJsonGraphCentricIndexDefinition;
+import org.janusgraph.core.schema.json.parser.JsonParameterDefinitionParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractJsonGraphCentricIndexCreator
+ extends AbstractJsonIndexCreator {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractJsonGraphCentricIndexCreator.class);
+ private final JsonParameterDefinitionParser jsonParameterDefinitionParser = new JsonParameterDefinitionParser();
+
+ @Override
+ protected Index buildIndex(T definition, JsonSchemaCreationContext context) {
+
+ JanusGraphManagement graphManagement = context.getGraphManagement();
+
+ Class extends Element> typeClass = toClass(definition.getTypeClass());
+ JanusGraphManagement.IndexBuilder indexBuilder = graphManagement.buildIndex(definition.getName(), typeClass);
+ addKeys(graphManagement, indexBuilder, definition);
+ addIndexOnly(graphManagement, indexBuilder, definition, typeClass);
+
+ return buildSpecificIndex(graphManagement, indexBuilder, definition);
+ }
+
+ @Override
+ protected boolean containsIndex(T definition, JsonSchemaCreationContext context) {
+ return context.getGraphManagement().containsGraphIndex(definition.getName());
+ }
+
+ protected void addKeys(JanusGraphManagement graphManagement,
+ JanusGraphManagement.IndexBuilder builder,
+ T definition) {
+ if(CollectionUtils.isEmpty(definition.getKeys())){
+ return;
+ }
+ definition.getKeys().forEach(entry -> {
+ if (CollectionUtils.isEmpty(entry.getParameters())) {
+ builder.addKey(graphManagement.getPropertyKey(entry.getPropertyKey()));
+ } else {
+ Parameter[] parameters = entry.getParameters().stream()
+ .map(jsonParameterDefinitionParser::parse).toArray(Parameter[]::new);
+ builder.addKey(graphManagement.getPropertyKey(entry.getPropertyKey()), parameters);
+ }
+ });
+ }
+
+ protected void addIndexOnly(JanusGraphManagement graphManagement,
+ JanusGraphManagement.IndexBuilder indexBuilder,
+ T definition, Class extends Element> typeClass) {
+ if(StringUtils.isNotEmpty(definition.getIndexOnly())){
+ JanusGraphSchemaType schemaLabel;
+ if(Vertex.class.isAssignableFrom(typeClass)){
+ schemaLabel = graphManagement.getVertexLabel(definition.getIndexOnly());
+ } else if(Edge.class.isAssignableFrom(typeClass)){
+ schemaLabel = graphManagement.getEdgeLabel(definition.getIndexOnly());
+ } else {
+ throw new SchemaCreationException("No implementation for type "+typeClass.getName());
+ }
+ indexBuilder.indexOnly(schemaLabel);
+ }
+ }
+
+ private Class extends Element> toClass(String type){
+ try{
+ return (Class extends Element>) Class.forName(type);
+ } catch (Exception e){
+ LOGGER.error("Class [{}] is not a child of {}", type, Element.class.getName());
+ throw new SchemaCreationException(e);
+ }
+ }
+
+ protected abstract Index buildSpecificIndex(JanusGraphManagement graphManagement, JanusGraphManagement.IndexBuilder indexBuilder, T definition);
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/AbstractJsonIndexCreator.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/AbstractJsonIndexCreator.java
new file mode 100644
index 0000000000..3a9a42d8b5
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/AbstractJsonIndexCreator.java
@@ -0,0 +1,48 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator.index;
+
+import org.janusgraph.core.schema.Index;
+import org.janusgraph.core.schema.json.creator.JsonSchemaCreationContext;
+import org.janusgraph.core.schema.json.creator.JsonSchemaCreator;
+import org.janusgraph.core.schema.json.definition.index.AbstractJsonIndexDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractJsonIndexCreator implements JsonSchemaCreator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractJsonIndexCreator.class);
+
+ @Override
+ public boolean create(T definition, JsonSchemaCreationContext context) {
+
+ if (containsIndex(definition, context)) {
+ LOG.info("Index with name [{}] was skipped because it already exists.", definition.getName());
+ return false;
+ }
+
+ Index index = buildIndex(definition, context);
+ LOG.info("Index {} was created", index.name());
+
+ context.getCreatedOrUpdatedIndices().add(definition);
+
+ return true;
+ }
+
+ protected abstract boolean containsIndex(T definition, JsonSchemaCreationContext context);
+
+ protected abstract Index buildIndex(T definition, JsonSchemaCreationContext context);
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/AbstractJsonVertexCentricIndexCreator.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/AbstractJsonVertexCentricIndexCreator.java
new file mode 100644
index 0000000000..f80dc2af32
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/AbstractJsonVertexCentricIndexCreator.java
@@ -0,0 +1,47 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator.index;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.janusgraph.core.PropertyKey;
+import org.janusgraph.core.RelationType;
+import org.janusgraph.core.schema.JanusGraphManagement;
+import org.janusgraph.core.schema.json.creator.JsonSchemaCreationContext;
+import org.janusgraph.core.schema.json.definition.index.AbstractJsonVertexCentricIndexDefinition;
+
+public abstract class AbstractJsonVertexCentricIndexCreator
+ extends AbstractJsonIndexCreator {
+
+ private static final PropertyKey[] EMPTY_PROPERTY_KEYS = new PropertyKey[0];
+
+ @Override
+ protected boolean containsIndex(T definition, JsonSchemaCreationContext context) {
+ JanusGraphManagement graphManagement = context.getGraphManagement();
+ RelationType relationType = graphManagement.getRelationType(getIndexedElementName(definition));
+ return graphManagement.containsRelationIndex(relationType, definition.getName());
+ }
+
+ protected PropertyKey[] toPropertyKeys(T definition, JsonSchemaCreationContext context){
+ if(CollectionUtils.isEmpty(definition.getPropertyKeys())){
+ return EMPTY_PROPERTY_KEYS;
+ }
+ return definition.getPropertyKeys().stream()
+ .map(propertyKey -> context.getGraphManagement().getPropertyKey(propertyKey))
+ .toArray(PropertyKey[]::new);
+ }
+
+ protected abstract String getIndexedElementName(T definition);
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/JsonCompositeIndexCreator.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/JsonCompositeIndexCreator.java
new file mode 100644
index 0000000000..8f4c405d74
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/JsonCompositeIndexCreator.java
@@ -0,0 +1,39 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator.index;
+
+import org.janusgraph.core.schema.Index;
+import org.janusgraph.core.schema.JanusGraphIndex;
+import org.janusgraph.core.schema.JanusGraphManagement;
+import org.janusgraph.core.schema.json.definition.index.JsonCompositeIndexDefinition;
+
+public class JsonCompositeIndexCreator extends AbstractJsonGraphCentricIndexCreator {
+
+ @Override
+ protected Index buildSpecificIndex(JanusGraphManagement graphManagement, JanusGraphManagement.IndexBuilder indexBuilder,
+ JsonCompositeIndexDefinition definition){
+ if(Boolean.TRUE.equals(definition.getUnique())){
+ indexBuilder.unique();
+ }
+
+ JanusGraphIndex index = indexBuilder.buildCompositeIndex();
+
+ if(definition.getConsistency() != null){
+ graphManagement.setConsistency(index, definition.getConsistency());
+ }
+
+ return index;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/JsonMixedIndexCreator.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/JsonMixedIndexCreator.java
new file mode 100644
index 0000000000..20a7f78442
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/JsonMixedIndexCreator.java
@@ -0,0 +1,30 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator.index;
+
+import org.janusgraph.core.schema.Index;
+import org.janusgraph.core.schema.JanusGraphManagement;
+import org.janusgraph.core.schema.json.definition.index.JsonMixedIndexDefinition;
+
+public class JsonMixedIndexCreator
+ extends AbstractJsonGraphCentricIndexCreator {
+
+ @Override
+ protected Index buildSpecificIndex(JanusGraphManagement graphManagement, JanusGraphManagement.IndexBuilder indexBuilder,
+ JsonMixedIndexDefinition definition){
+
+ return indexBuilder.buildMixedIndex(definition.getIndexBackend());
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/JsonVertexCentricEdgeIndexCreator.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/JsonVertexCentricEdgeIndexCreator.java
new file mode 100644
index 0000000000..d6ab225f57
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/JsonVertexCentricEdgeIndexCreator.java
@@ -0,0 +1,54 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator.index;
+
+import org.janusgraph.core.EdgeLabel;
+import org.janusgraph.core.PropertyKey;
+import org.janusgraph.core.schema.Index;
+import org.janusgraph.core.schema.JanusGraphManagement;
+import org.janusgraph.core.schema.json.creator.JsonSchemaCreationContext;
+import org.janusgraph.core.schema.json.definition.index.JsonVertexCentricEdgeIndexDefinition;
+
+public class JsonVertexCentricEdgeIndexCreator extends AbstractJsonVertexCentricIndexCreator {
+
+ @Override
+ protected Index buildIndex(JsonVertexCentricEdgeIndexDefinition definition, JsonSchemaCreationContext context){
+ JanusGraphManagement graphManagement = context.getGraphManagement();
+ EdgeLabel edgeLabel = graphManagement.getEdgeLabel(definition.getIndexedEdgeLabel());
+ PropertyKey[] propertyKeys = toPropertyKeys(definition, context);
+
+ if (definition.getOrder() == null) {
+ return graphManagement.buildEdgeIndex(
+ edgeLabel,
+ definition.getName(),
+ definition.getDirection(),
+ propertyKeys
+ );
+ } else {
+ return graphManagement.buildEdgeIndex(
+ edgeLabel,
+ definition.getName(),
+ definition.getDirection(),
+ definition.getOrder(),
+ propertyKeys
+ );
+ }
+ }
+
+ @Override
+ protected String getIndexedElementName(JsonVertexCentricEdgeIndexDefinition definition) {
+ return definition.getIndexedEdgeLabel();
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/JsonVertexCentricPropertyIndexCreator.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/JsonVertexCentricPropertyIndexCreator.java
new file mode 100644
index 0000000000..6dc9f44b7e
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/creator/index/JsonVertexCentricPropertyIndexCreator.java
@@ -0,0 +1,51 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.creator.index;
+
+import org.janusgraph.core.PropertyKey;
+import org.janusgraph.core.schema.Index;
+import org.janusgraph.core.schema.JanusGraphManagement;
+import org.janusgraph.core.schema.json.creator.JsonSchemaCreationContext;
+import org.janusgraph.core.schema.json.definition.index.JsonVertexCentricPropertyIndexDefinition;
+
+public class JsonVertexCentricPropertyIndexCreator extends AbstractJsonVertexCentricIndexCreator {
+
+ @Override
+ protected Index buildIndex(JsonVertexCentricPropertyIndexDefinition definition, JsonSchemaCreationContext context){
+ JanusGraphManagement graphManagement = context.getGraphManagement();
+ PropertyKey propertyKey = graphManagement.getPropertyKey(definition.getIndexedPropertyKey());
+ PropertyKey[] propertyKeys = toPropertyKeys(definition, context);
+
+ if (definition.getOrder() == null) {
+ return graphManagement.buildPropertyIndex(
+ propertyKey,
+ definition.getName(),
+ propertyKeys
+ );
+ } else {
+ return graphManagement.buildPropertyIndex(
+ propertyKey,
+ definition.getName(),
+ definition.getOrder(),
+ propertyKeys
+ );
+ }
+ }
+
+ @Override
+ protected String getIndexedElementName(JsonVertexCentricPropertyIndexDefinition definition) {
+ return definition.getIndexedPropertyKey();
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonEdgeLabelDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonEdgeLabelDefinition.java
new file mode 100644
index 0000000000..29de21d720
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonEdgeLabelDefinition.java
@@ -0,0 +1,71 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition;
+
+import org.janusgraph.core.Multiplicity;
+import org.janusgraph.core.schema.ConsistencyModifier;
+
+public class JsonEdgeLabelDefinition {
+
+ private String label;
+
+ private Multiplicity multiplicity;
+
+ private Boolean unidirected;
+
+ private Long ttl;
+
+ private ConsistencyModifier consistency;
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public Multiplicity getMultiplicity() {
+ return multiplicity;
+ }
+
+ public void setMultiplicity(Multiplicity multiplicity) {
+ this.multiplicity = multiplicity;
+ }
+
+ public Boolean getUnidirected() {
+ return unidirected;
+ }
+
+ public void setUnidirected(Boolean unidirected) {
+ this.unidirected = unidirected;
+ }
+
+ public Long getTtl() {
+ return ttl;
+ }
+
+ public void setTtl(Long ttl) {
+ this.ttl = ttl;
+ }
+
+ public ConsistencyModifier getConsistency() {
+ return consistency;
+ }
+
+ public void setConsistency(ConsistencyModifier consistency) {
+ this.consistency = consistency;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonParameterDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonParameterDefinition.java
new file mode 100644
index 0000000000..37095b091b
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonParameterDefinition.java
@@ -0,0 +1,48 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition;
+
+public class JsonParameterDefinition {
+
+ private String key;
+
+ private String value;
+
+ private String parser;
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getParser() {
+ return parser;
+ }
+
+ public void setParser(String parser) {
+ this.parser = parser;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonPropertyKeyDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonPropertyKeyDefinition.java
new file mode 100644
index 0000000000..3abfb90656
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonPropertyKeyDefinition.java
@@ -0,0 +1,71 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition;
+
+import org.janusgraph.core.Cardinality;
+import org.janusgraph.core.schema.ConsistencyModifier;
+
+public class JsonPropertyKeyDefinition {
+
+ private String key;
+
+ private String className;
+
+ private Cardinality cardinality;
+
+ private Long ttl;
+
+ private ConsistencyModifier consistency;
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public void setClassName(String className) {
+ this.className = className;
+ }
+
+ public Cardinality getCardinality() {
+ return cardinality;
+ }
+
+ public void setCardinality(Cardinality cardinality) {
+ this.cardinality = cardinality;
+ }
+
+ public Long getTtl() {
+ return ttl;
+ }
+
+ public void setTtl(Long ttl) {
+ this.ttl = ttl;
+ }
+
+ public ConsistencyModifier getConsistency() {
+ return consistency;
+ }
+
+ public void setConsistency(ConsistencyModifier consistency) {
+ this.consistency = consistency;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonSchemaDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonSchemaDefinition.java
new file mode 100644
index 0000000000..08ea7f0dcc
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonSchemaDefinition.java
@@ -0,0 +1,89 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition;
+
+import org.janusgraph.core.schema.json.definition.index.JsonCompositeIndexDefinition;
+import org.janusgraph.core.schema.json.definition.index.JsonMixedIndexDefinition;
+import org.janusgraph.core.schema.json.definition.index.JsonVertexCentricEdgeIndexDefinition;
+import org.janusgraph.core.schema.json.definition.index.JsonVertexCentricPropertyIndexDefinition;
+
+import java.util.List;
+
+public class JsonSchemaDefinition {
+
+ private List vertexLabels;
+ private List edgeLabels;
+ private List propertyKeys;
+ private List compositeIndexes;
+ private List vertexCentricEdgeIndexes;
+ private List vertexCentricPropertyIndexes;
+ private List mixedIndexes;
+
+ public List getVertexLabels() {
+ return vertexLabels;
+ }
+
+ public void setVertexLabels(List vertexLabels) {
+ this.vertexLabels = vertexLabels;
+ }
+
+ public List getEdgeLabels() {
+ return edgeLabels;
+ }
+
+ public void setEdgeLabels(List edgeLabels) {
+ this.edgeLabels = edgeLabels;
+ }
+
+ public List getPropertyKeys() {
+ return propertyKeys;
+ }
+
+ public void setPropertyKeys(List propertyKeys) {
+ this.propertyKeys = propertyKeys;
+ }
+
+ public List getCompositeIndexes() {
+ return compositeIndexes;
+ }
+
+ public void setCompositeIndexes(List compositeIndexes) {
+ this.compositeIndexes = compositeIndexes;
+ }
+
+ public List getVertexCentricEdgeIndexes() {
+ return vertexCentricEdgeIndexes;
+ }
+
+ public void setVertexCentricEdgeIndexes(List vertexCentricEdgeIndexes) {
+ this.vertexCentricEdgeIndexes = vertexCentricEdgeIndexes;
+ }
+
+ public List getVertexCentricPropertyIndexes() {
+ return vertexCentricPropertyIndexes;
+ }
+
+ public void setVertexCentricPropertyIndexes(List vertexCentricPropertyIndexes) {
+ this.vertexCentricPropertyIndexes = vertexCentricPropertyIndexes;
+ }
+
+ public List getMixedIndexes() {
+ return mixedIndexes;
+ }
+
+ public void setMixedIndexes(List mixedIndexes) {
+ this.mixedIndexes = mixedIndexes;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonVertexLabelDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonVertexLabelDefinition.java
new file mode 100644
index 0000000000..7b434f46f5
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/JsonVertexLabelDefinition.java
@@ -0,0 +1,58 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition;
+
+public class JsonVertexLabelDefinition {
+
+ private String label;
+
+ private Boolean staticVertex;
+
+ private Boolean partition;
+
+ private Long ttl;
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public Boolean getStaticVertex() {
+ return staticVertex;
+ }
+
+ public void setStaticVertex(Boolean staticVertex) {
+ this.staticVertex = staticVertex;
+ }
+
+ public Boolean getPartition() {
+ return partition;
+ }
+
+ public void setPartition(Boolean partition) {
+ this.partition = partition;
+ }
+
+ public Long getTtl() {
+ return ttl;
+ }
+
+ public void setTtl(Long ttl) {
+ this.ttl = ttl;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/AbstractJsonGraphCentricIndexDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/AbstractJsonGraphCentricIndexDefinition.java
new file mode 100644
index 0000000000..6748ff5d34
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/AbstractJsonGraphCentricIndexDefinition.java
@@ -0,0 +1,48 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition.index;
+
+import java.util.List;
+
+public abstract class AbstractJsonGraphCentricIndexDefinition extends AbstractJsonIndexDefinition {
+
+ private String indexOnly;
+ private String typeClass;
+ private List keys;
+
+ public String getIndexOnly() {
+ return indexOnly;
+ }
+
+ public void setIndexOnly(String indexOnly) {
+ this.indexOnly = indexOnly;
+ }
+
+ public String getTypeClass() {
+ return typeClass;
+ }
+
+ public void setTypeClass(String typeClass) {
+ this.typeClass = typeClass;
+ }
+
+ public List getKeys() {
+ return keys;
+ }
+
+ public void setKeys(List keys) {
+ this.keys = keys;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/AbstractJsonIndexDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/AbstractJsonIndexDefinition.java
new file mode 100644
index 0000000000..8b1bc99833
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/AbstractJsonIndexDefinition.java
@@ -0,0 +1,28 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition.index;
+
+public abstract class AbstractJsonIndexDefinition {
+
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/AbstractJsonVertexCentricIndexDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/AbstractJsonVertexCentricIndexDefinition.java
new file mode 100644
index 0000000000..f95412ad40
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/AbstractJsonVertexCentricIndexDefinition.java
@@ -0,0 +1,41 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition.index;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Order;
+
+import java.util.List;
+
+public abstract class AbstractJsonVertexCentricIndexDefinition extends AbstractJsonIndexDefinition {
+
+ private List propertyKeys;
+ private Order order;
+
+ public List getPropertyKeys() {
+ return propertyKeys;
+ }
+
+ public void setPropertyKeys(List propertyKeys) {
+ this.propertyKeys = propertyKeys;
+ }
+
+ public Order getOrder() {
+ return order;
+ }
+
+ public void setOrder(Order order) {
+ this.order = order;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonCompositeIndexDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonCompositeIndexDefinition.java
new file mode 100644
index 0000000000..2c0960a487
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonCompositeIndexDefinition.java
@@ -0,0 +1,40 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition.index;
+
+import org.janusgraph.core.schema.ConsistencyModifier;
+
+public class JsonCompositeIndexDefinition extends AbstractJsonGraphCentricIndexDefinition {
+
+ private ConsistencyModifier consistency;
+
+ private Boolean unique;
+
+ public ConsistencyModifier getConsistency() {
+ return consistency;
+ }
+
+ public void setConsistency(ConsistencyModifier consistency) {
+ this.consistency = consistency;
+ }
+
+ public Boolean getUnique() {
+ return unique;
+ }
+
+ public void setUnique(Boolean unique) {
+ this.unique = unique;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonIndexedPropertyKeyDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonIndexedPropertyKeyDefinition.java
new file mode 100644
index 0000000000..5036bbfc88
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonIndexedPropertyKeyDefinition.java
@@ -0,0 +1,42 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition.index;
+
+import org.janusgraph.core.schema.json.definition.JsonParameterDefinition;
+
+import java.util.List;
+
+public class JsonIndexedPropertyKeyDefinition {
+
+ private String propertyKey;
+
+ private List parameters;
+
+ public String getPropertyKey() {
+ return propertyKey;
+ }
+
+ public void setPropertyKey(String propertyKey) {
+ this.propertyKey = propertyKey;
+ }
+
+ public List getParameters() {
+ return parameters;
+ }
+
+ public void setParameters(List parameters) {
+ this.parameters = parameters;
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonMixedIndexDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonMixedIndexDefinition.java
new file mode 100644
index 0000000000..dc2fe1e140
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonMixedIndexDefinition.java
@@ -0,0 +1,29 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition.index;
+
+public class JsonMixedIndexDefinition extends AbstractJsonGraphCentricIndexDefinition {
+
+ private String indexBackend;
+
+ public String getIndexBackend() {
+ return indexBackend;
+ }
+
+ public void setIndexBackend(String indexBackend) {
+ this.indexBackend = indexBackend;
+ }
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonVertexCentricEdgeIndexDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonVertexCentricEdgeIndexDefinition.java
new file mode 100644
index 0000000000..9843075165
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonVertexCentricEdgeIndexDefinition.java
@@ -0,0 +1,41 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition.index;
+
+import org.apache.tinkerpop.gremlin.structure.Direction;
+
+public class JsonVertexCentricEdgeIndexDefinition extends AbstractJsonVertexCentricIndexDefinition {
+
+ private String indexedEdgeLabel;
+
+ private Direction direction;
+
+ public String getIndexedEdgeLabel() {
+ return indexedEdgeLabel;
+ }
+
+ public void setIndexedEdgeLabel(String indexedEdgeLabel) {
+ this.indexedEdgeLabel = indexedEdgeLabel;
+ }
+
+ public Direction getDirection() {
+ return direction;
+ }
+
+ public void setDirection(Direction direction) {
+ this.direction = direction;
+ }
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonVertexCentricPropertyIndexDefinition.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonVertexCentricPropertyIndexDefinition.java
new file mode 100644
index 0000000000..ad268bc335
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/definition/index/JsonVertexCentricPropertyIndexDefinition.java
@@ -0,0 +1,29 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.definition.index;
+
+public class JsonVertexCentricPropertyIndexDefinition extends AbstractJsonVertexCentricIndexDefinition {
+
+ private String indexedPropertyKey;
+
+ public String getIndexedPropertyKey() {
+ return indexedPropertyKey;
+ }
+
+ public void setIndexedPropertyKey(String indexedPropertyKey) {
+ this.indexedPropertyKey = indexedPropertyKey;
+ }
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/parser/EnumJsonParameterParser.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/parser/EnumJsonParameterParser.java
new file mode 100644
index 0000000000..5f7b9459b3
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/parser/EnumJsonParameterParser.java
@@ -0,0 +1,50 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.parser;
+
+import org.apache.commons.lang.UnhandledException;
+import org.janusgraph.core.schema.Parameter;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class EnumJsonParameterParser implements JsonParameterParser {
+
+ @Override
+ public Parameter parse(String key, String value) {
+ return Parameter.of(key, toEnum(value));
+ }
+
+ private Object toEnum(String value){
+
+ int indexOfValue = value.lastIndexOf(".");
+
+ if(indexOfValue == -1){
+ throw new AssertionError(value+" isn't a valid enum value. Please use full path (package + class + value) to enum value. " +
+ "Example: org.janusgraph.core.schema.Mapping.TEXT");
+ }
+
+ Class enumClass;
+ Method method;
+
+ try {
+ enumClass = Class.forName(value.substring(0,indexOfValue));
+ method = enumClass.getMethod("valueOf", String.class);
+ return method.invoke(null, value.substring(indexOfValue+1));
+ } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ throw new UnhandledException(e);
+ }
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/parser/JsonParameterDefinitionParser.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/parser/JsonParameterDefinitionParser.java
new file mode 100644
index 0000000000..0f93b15957
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/parser/JsonParameterDefinitionParser.java
@@ -0,0 +1,61 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.parser;
+
+import org.janusgraph.core.schema.Parameter;
+import org.janusgraph.core.schema.json.creator.SchemaCreationException;
+import org.janusgraph.core.schema.json.definition.JsonParameterDefinition;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class JsonParameterDefinitionParser {
+
+ public static final String STRING_PARAMETER_PARSER_NAME = "string";
+ public static final String ENUM_PARAMETER_PARSER_NAME = "enum";
+
+ public final Map jsonParameterParsers;
+ {
+ jsonParameterParsers = new HashMap<>(2);
+ jsonParameterParsers.put(STRING_PARAMETER_PARSER_NAME, new StringJsonParameterParser());
+ jsonParameterParsers.put(ENUM_PARAMETER_PARSER_NAME, new EnumJsonParameterParser());
+ }
+
+ public Parameter parse(JsonParameterDefinition definition){
+
+ JsonParameterParser parameterParser = jsonParameterParsers.get(definition.getParser());
+
+ if(parameterParser == null){
+
+ try {
+ Class parserClass = Class.forName(definition.getParser());
+ Object instance = parserClass.newInstance();
+
+ if (!(instance instanceof JsonParameterParser)){
+ throw new SchemaCreationException("Class "+definition.getParser()+" does not implement JsonParameterParser");
+ }
+
+ parameterParser = (JsonParameterParser) instance;
+
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
+ throw new SchemaCreationException(e);
+ }
+
+ jsonParameterParsers.put(definition.getParser(), parameterParser);
+ }
+
+ return parameterParser.parse(definition.getKey(), definition.getValue());
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/parser/JsonParameterParser.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/parser/JsonParameterParser.java
new file mode 100644
index 0000000000..a5ff71d9a6
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/parser/JsonParameterParser.java
@@ -0,0 +1,23 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.parser;
+
+import org.janusgraph.core.schema.Parameter;
+
+public interface JsonParameterParser {
+
+ Parameter parse(String key, String value);
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/parser/StringJsonParameterParser.java b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/parser/StringJsonParameterParser.java
new file mode 100644
index 0000000000..5dbc10e4c3
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/schema/json/parser/StringJsonParameterParser.java
@@ -0,0 +1,24 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.schema.json.parser;
+
+import org.janusgraph.core.schema.Parameter;
+
+public class StringJsonParameterParser implements JsonParameterParser {
+ @Override
+ public Parameter parse(String key, String value) {
+ return Parameter.of(key, value);
+ }
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/util/JsonUtil.java b/janusgraph-core/src/main/java/org/janusgraph/core/util/JsonUtil.java
new file mode 100644
index 0000000000..b3963d2f39
--- /dev/null
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/util/JsonUtil.java
@@ -0,0 +1,50 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.core.util;
+
+import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+
+public class JsonUtil {
+
+ public static T jsonResourcePathToObject(String resourcePath, Class parsedClass) throws IOException {
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ InputStream resourceStream = loader.getResourceAsStream(resourcePath);
+ return jsonToObject(new InputStreamReader(resourceStream), parsedClass);
+ }
+
+ // FileNotFoundException
+ public static T jsonFilePathToObject(String filePath, Class parsedClass) throws IOException {
+ return jsonToObject(new FileReader(filePath), parsedClass);
+ }
+
+ public static T jsonStringToObject(String json, Class parsedClass) throws IOException {
+ return jsonToObject(new StringReader(json), parsedClass);
+ }
+
+ // JsonProcessingException
+ public static T jsonToObject(Reader reader, Class parsedClass) throws IOException {
+// Gson gson = new Gson();
+// return gson.fromJson(gson.newJsonReader(reader), parsedClass);
+ return new ObjectMapper().readValue(reader, parsedClass);
+ }
+
+}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/util/ManagementUtil.java b/janusgraph-core/src/main/java/org/janusgraph/core/util/ManagementUtil.java
index f8b3fbc64e..4b2f89230b 100644
--- a/janusgraph-core/src/main/java/org/janusgraph/core/util/ManagementUtil.java
+++ b/janusgraph-core/src/main/java/org/janusgraph/core/util/ManagementUtil.java
@@ -18,23 +18,36 @@
import org.apache.commons.lang3.StringUtils;
import org.janusgraph.core.JanusGraph;
import org.janusgraph.core.JanusGraphException;
+import org.janusgraph.core.JanusGraphTransaction;
import org.janusgraph.core.PropertyKey;
import org.janusgraph.core.schema.Index;
import org.janusgraph.core.schema.JanusGraphIndex;
import org.janusgraph.core.schema.JanusGraphManagement;
import org.janusgraph.core.schema.RelationTypeIndex;
+import org.janusgraph.core.schema.SchemaAction;
+import org.janusgraph.core.schema.SchemaStatus;
import org.janusgraph.diskstorage.util.time.TimestampProvider;
import org.janusgraph.graphdb.database.StandardJanusGraph;
+import org.janusgraph.graphdb.database.management.GraphIndexStatusReport;
+import org.janusgraph.graphdb.database.management.ManagementSystem;
+import org.janusgraph.graphdb.database.management.RelationIndexStatusReport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
+import java.util.List;
+import java.util.Map;
/**
* @author Matthias Broecheler (me@matthiasb.com)
*/
public class ManagementUtil {
+ private static final Logger LOG = LoggerFactory.getLogger(ManagementUtil.class);
+
/**
* This method blocks and waits until the provided index has been updated across the entire JanusGraph cluster
* and reached a stable state.
@@ -96,4 +109,188 @@ private static void awaitIndexUpdate(JanusGraph g, String indexName, String rela
"wait periods this is most likely caused by a failed/incorrectly shut down JanusGraph instance or a lingering transaction.");
}
+ /**
+ * Force rollback all transactions which are opened on the graph.
+ * @param graph - graph on which to rollback all current transactions
+ */
+ public static void forceRollbackAllTransactions(JanusGraph graph){
+ if(graph instanceof StandardJanusGraph){
+ for(JanusGraphTransaction janusGraphTransaction : ((StandardJanusGraph) graph).getOpenTransactions()){
+ janusGraphTransaction.rollback();
+ }
+ }
+ }
+
+ /**
+ * Force closes all instances except current instance. This is a dangerous operation as it will close any other active instances.
+ * @param graph - graph instance to keep (this instance will not be closed).
+ */
+ public static void forceCloseOtherInstances(JanusGraph graph){
+ String currentInstanceId = ((StandardJanusGraph) graph).getConfiguration().getUniqueGraphId();
+ String currentInstanceIdWithSuffix = currentInstanceId + ManagementSystem.CURRENT_INSTANCE_SUFFIX;
+ JanusGraphManagement mgmt = graph.openManagement();
+ mgmt.getOpenInstances().forEach(instanceId -> {
+ if(!currentInstanceId.equals(instanceId) && !currentInstanceIdWithSuffix.equals(instanceId)){
+ mgmt.forceCloseInstance(instanceId);
+ }
+ });
+ mgmt.commit();
+ }
+
+ public static void reindexAndEnableIndices(JanusGraph graph, List nonEnabledGraphIndexNames, Map nonEnabledRelationTypeIndexNamesAndTypes, long indexStatusTimeout){
+ nonEnabledGraphIndexNames.forEach(indexName -> awaitGraphIndexStatus(graph, indexName, indexStatusTimeout));
+ nonEnabledRelationTypeIndexNamesAndTypes.forEach((indexName, indexedElementName) -> awaitVertexCentricIndexStatus(graph, indexName, indexedElementName, indexStatusTimeout));
+
+ nonEnabledGraphIndexNames.forEach(indexName -> {
+ try {
+ LOG.info("Start re-indexing graph index {}", indexName);
+
+ ManagementSystem schemaUpdateMgmt = (ManagementSystem) graph.openManagement();
+ Index index = schemaUpdateMgmt.getGraphIndex(indexName);
+ schemaUpdateMgmt.updateIndex(index, SchemaAction.REINDEX).get();
+ schemaUpdateMgmt.commit();
+
+ LOG.info("Finished re-indexing graph index {}", indexName);
+ } catch (Exception e) {
+ LOG.error("Couldn't execute re-index for the graph index [{}]", indexName, e);
+ throw new RuntimeException(e);
+ }
+ });
+
+ nonEnabledRelationTypeIndexNamesAndTypes.forEach((indexName, indexedElementName) -> {
+ try {
+ LOG.info("Start re-indexing vertex-centric index {}", indexName);
+
+ ManagementSystem schemaUpdateMgmt = (ManagementSystem) graph.openManagement();
+ Index index = schemaUpdateMgmt.getRelationIndex(schemaUpdateMgmt.getRelationType(indexedElementName), indexName);
+ schemaUpdateMgmt.updateIndex(index, SchemaAction.REINDEX).get();
+ schemaUpdateMgmt.commit();
+
+ LOG.info("Finished re-indexing vertex-centric index {}", indexName);
+ } catch (Exception e) {
+ LOG.error("Couldn't execute re-index for the vertex-centric index [{}]", indexName, e);
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ public static void forceEnableIndices(JanusGraph graph, List nonEnabledGraphIndexNames, Map nonEnabledRelationTypeIndexNamesAndTypes, long indexStatusTimeout){
+ nonEnabledGraphIndexNames.forEach(indexName -> {
+ try{
+ awaitGraphIndexStatus(graph, indexName, indexStatusTimeout);
+ } catch (Exception e){
+ LOG.warn("Await for status update of the graph index {} finished with exception", indexName, e);
+ }
+ });
+ nonEnabledRelationTypeIndexNamesAndTypes.forEach((indexName, indexedElementName) -> {
+ try{
+ awaitVertexCentricIndexStatus(graph, indexName, indexedElementName, indexStatusTimeout);
+ } catch (Exception e){
+ LOG.warn("Await for status update of the vertex-centric index {} finished with exception", indexName, e);
+ }
+ });
+
+ nonEnabledGraphIndexNames.forEach(indexName -> {
+ try {
+ LOG.info("Start force-enabling graph index {}", indexName);
+
+ ManagementSystem schemaUpdateMgmt = (ManagementSystem) graph.openManagement();
+ Index index = schemaUpdateMgmt.getGraphIndex(indexName);
+ schemaUpdateMgmt.updateIndex(index, SchemaAction.ENABLE_INDEX).get();
+ schemaUpdateMgmt.commit();
+
+ LOG.info("Finished force-enabling graph index {}", indexName);
+ } catch (Exception e) {
+ LOG.error("Couldn't execute force-enable for the graph index [{}]", indexName, e);
+ throw new RuntimeException(e);
+ }
+ });
+
+ nonEnabledRelationTypeIndexNamesAndTypes.forEach((indexName, indexedElementName) -> {
+ try {
+ LOG.info("Start force-enabling vertex-centric index {}", indexName);
+
+ ManagementSystem schemaUpdateMgmt = (ManagementSystem) graph.openManagement();
+ Index index = schemaUpdateMgmt.getRelationIndex(schemaUpdateMgmt.getRelationType(indexedElementName), indexName);
+ schemaUpdateMgmt.updateIndex(index, SchemaAction.ENABLE_INDEX).get();
+ schemaUpdateMgmt.commit();
+
+ LOG.info("Finished force-enabling vertex-centric index {}", indexName);
+ } catch (Exception e) {
+ LOG.error("Couldn't execute force-enable for the vertex-centric index [{}]", indexName, e);
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ public static void awaitVertexCentricIndexStatus(JanusGraph janusGraph, String indexName, String indexedElement, long indexStatusTimeout) {
+
+ try {
+
+ RelationIndexStatusReport relationIndexStatusReport =
+ ManagementSystem.awaitRelationIndexStatus(
+ janusGraph,
+ indexName,
+ indexedElement
+ ).timeout(indexStatusTimeout, ChronoUnit.MILLIS).call();
+
+ if (!relationIndexStatusReport.getSucceeded()){
+ LOG.error("Await wasn't successful for index [{}]. Actual status [{}]. Time elapsed [{}]. Target statuses [{}]",
+ indexName,
+ relationIndexStatusReport.getActualStatus().toString(),
+ relationIndexStatusReport.getElapsed().toString(),
+ StringUtils.join(relationIndexStatusReport.getTargetStatuses())
+ );
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void awaitGraphIndexStatus(JanusGraph janusGraph, String indexName, long indexStatusTimeout) {
+
+ try {
+ GraphIndexStatusReport graphIndexStatusReport =
+ ManagementSystem.awaitGraphIndexStatus(
+ janusGraph,
+ indexName
+ ).timeout(indexStatusTimeout, ChronoUnit.MILLIS).call();
+
+ if (!graphIndexStatusReport.getSucceeded()){
+ LOG.warn("Await wasn't successful for index [{}]. Covered keys [{}]. Not covered keys [{}]. Time elapsed [{}]. Target statuses [{}]",
+ indexName,
+ StringUtils.join(graphIndexStatusReport.getConvergedKeys()),
+ StringUtils.join(graphIndexStatusReport.getNotConvergedKeys()),
+ graphIndexStatusReport.getElapsed().toString(),
+ StringUtils.join(graphIndexStatusReport.getTargetStatuses())
+ );
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static boolean isIndexHasStatus(Index index, SchemaStatus status){
+ if(index instanceof JanusGraphIndex){
+ return isGraphIndexHasStatus((JanusGraphIndex) index, status);
+ }
+ if(index instanceof RelationTypeIndex){
+ return isRelationIndexHasStatus((RelationTypeIndex) index, status);
+ }
+ throw new IllegalStateException("Unexpected index type: " + index.getClass() + ", indexName: " + index.name());
+ }
+
+ public static boolean isGraphIndexHasStatus(JanusGraphIndex graphIndex, SchemaStatus status){
+ for(PropertyKey propertyKey : graphIndex.getFieldKeys()){
+ if(!status.equals(graphIndex.getIndexStatus(propertyKey))){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isRelationIndexHasStatus(RelationTypeIndex relationTypeIndex, SchemaStatus status){
+ return status.equals(relationTypeIndex.getIndexStatus());
+ }
+
}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/configuration/GraphDatabaseConfiguration.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/configuration/GraphDatabaseConfiguration.java
index 10bf05bb3d..f94f2f5410 100644
--- a/janusgraph-core/src/main/java/org/janusgraph/graphdb/configuration/GraphDatabaseConfiguration.java
+++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/configuration/GraphDatabaseConfiguration.java
@@ -28,7 +28,10 @@
import org.janusgraph.core.schema.DisableDefaultSchemaMaker;
import org.janusgraph.core.schema.IgnorePropertySchemaMaker;
import org.janusgraph.core.schema.JanusGraphDefaultSchemaMaker;
+import org.janusgraph.core.schema.SchemaInitStrategy;
+import org.janusgraph.core.schema.SchemaInitType;
import org.janusgraph.core.schema.Tp3DefaultSchemaMaker;
+import org.janusgraph.core.schema.IndicesActivationType;
import org.janusgraph.diskstorage.Backend;
import org.janusgraph.diskstorage.StandardIndexProvider;
import org.janusgraph.diskstorage.StandardStoreManager;
@@ -80,6 +83,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.management.MBeanServerFactory;
@@ -437,6 +441,71 @@ public boolean apply(@Nullable String s) {
"as described in the config option 'schema.default'. If 'schema.constraints' is set to 'false' which is the default, then no schema constraints are applied.",
ConfigOption.Type.GLOBAL_OFFLINE, false);
+ public static final ConfigNamespace SCHEMA_INIT = new ConfigNamespace(SCHEMA_NS,"init",
+ "Configuration options to configure schema initialization options.");
+
+ public static final ConfigOption SCHEMA_INIT_STRATEGY = new ConfigOption<>(SCHEMA_INIT,"schema-init-strategy",
+ String.format("Selects the strategy for schema initialization before JanusGraph is started. The full class path which " +
+ "implements `%s` interface and has no any constructor parameters must be provided. Also, the following shortcuts exist:
" +
+ "- `%s` - Skip any schema initialization.
" +
+ "- `%s` - Use provided JSON file for schema initialization.
",
+ SchemaInitStrategy.class.getSimpleName(),
+ SchemaInitType.NONE.getConfigName(),
+ SchemaInitType.JSON.getConfigName()),
+ ConfigOption.Type.LOCAL, SchemaInitType.NONE.getConfigName());
+
+ public static final ConfigOption SCHEMA_DROP_BEFORE_INIT = new ConfigOption<>(SCHEMA_INIT,"schema-drop-before-startup",
+ "Drops the whole schema before JanusGraph schema initialization. Notice schema is dropped regardless " +
+ "of chosen initialization strategy (it will be dropped even if `none` schema-init-strategy is selected).",
+ ConfigOption.Type.LOCAL, false);
+
+ public static final ConfigNamespace SCHEMA_INIT_JSON = new ConfigNamespace(SCHEMA_INIT,"json",
+ "Options for JSON schema initialization strategy.");
+
+ public static final ConfigOption SCHEMA_INIT_JSON_FILE = new ConfigOption<>(SCHEMA_INIT_JSON,"file",
+ "File path to JSON formated schema definition.", ConfigOption.Type.LOCAL, String.class);
+
+ public static final ConfigOption SCHEMA_INIT_JSON_STR = new ConfigOption<>(SCHEMA_INIT_JSON,"string",
+ "JSON formated string of schema definition. This option takes precedence if both `file` and `string` are used.",
+ ConfigOption.Type.LOCAL, String.class);
+
+ public static final ConfigOption SCHEMA_INIT_JSON_SKIP_ELEMENTS = new ConfigOption<>(SCHEMA_INIT_JSON,"skip-elements",
+ "Skip creation of VertexLabel, EdgeLabel, and PropertyKey.",
+ ConfigOption.Type.LOCAL, false);
+
+ public static final ConfigOption SCHEMA_INIT_JSON_SKIP_INDICES = new ConfigOption<>(SCHEMA_INIT_JSON,"skip-indices",
+ "Skip creation of indices.",
+ ConfigOption.Type.LOCAL, false);
+
+ public static final ConfigOption SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE = new ConfigOption<>(SCHEMA_INIT_JSON,"indices-activation",
+ String.format("Indices activation type:
" +
+ "- `%s` - Reindex process will be triggered for any updated index. After this all updated indexes will be enabled.
" +
+ "- `%s` - Reindex process will be triggered for any index which is not enabled. After this all indexes will be enabled.
" +
+ "- `%s` - Skip reindex process for any updated indexes.
" +
+ "- `%s` - Force enable all updated indexes without running any reindex process (previous data may not be available for such indices).
" +
+ "- `%s` - Force enable all indexes (including previously created indexes) without running any reindex process (previous data may not be available for such indices).
",
+ IndicesActivationType.REINDEX_AND_ENABLE_UPDATED_ONLY.getConfigName(),
+ IndicesActivationType.REINDEX_AND_ENABLE_NON_ENABLED.getConfigName(),
+ IndicesActivationType.SKIP_ACTIVATION.getConfigName(),
+ IndicesActivationType.FORCE_ENABLE_UPDATED_ONLY.getConfigName(),
+ IndicesActivationType.FORCE_ENABLE_NON_ENABLED.getConfigName()
+ ),
+ ConfigOption.Type.LOCAL, IndicesActivationType.REINDEX_AND_ENABLE_NON_ENABLED.getConfigName());
+
+ public static final ConfigOption SCHEMA_INIT_JSON_FORCE_CLOSE_OTHER_INSTANCES = new ConfigOption<>(SCHEMA_INIT_JSON,"force-close-other-instances",
+ String.format("Force closes other JanusGraph instances before schema initialization regardless if they are active or not. " +
+ "This is a dangerous operation. This option exists to help people initialize schema who struggle with zombie JanusGraph instances. " +
+ "It's not recommended to be used unless you know what you are doing. Instead of this parameter, " +
+ "it's recommended to check `%s` and `%s` options to not create zombie instances in the cluster.",
+ UNIQUE_INSTANCE_ID.toStringWithoutRoot(),
+ REPLACE_INSTANCE_IF_EXISTS.toStringWithoutRoot()
+ ),
+ ConfigOption.Type.LOCAL, false);
+
+ public static final ConfigOption SCHEMA_INIT_JSON_AWAIT_INDEX_STATUS_TIMEOUT = new ConfigOption<>(SCHEMA_INIT_JSON,"await-index-status-timeout",
+ "Timeout for awaiting index status operation defined in milliseconds. If the status await timeouts an exception will be thrown during schema initialization process.",
+ ConfigOption.Type.LOCAL, TimeUnit.MINUTES.toMillis(3));
+
// ################ CACHE #######################
// ################################################
@@ -1371,6 +1440,15 @@ public boolean apply(@Nullable String s) {
private MultiQueryHasStepStrategyMode hasStepStrategyMode;
private MultiQueryPropertiesStrategyMode propertiesStrategyMode;
private MultiQueryLabelStepStrategyMode labelStepStrategyMode;
+ private String schemaInitStrategy;
+ private boolean dropSchemaBeforeInit;
+ private String schemaInitJsonFile;
+ private String schemaInitJsonString;
+ private boolean schemaInitJsonSkipElements;
+ private boolean schemaInitJsonSkipIndices;
+ private IndicesActivationType schemaInitJsonIndicesActivationType;
+ private boolean schemaInitJsonForceCloseOtherInstances;
+ private long schemaInitJsonIndexStatusAwaitTimeout;
private StoreFeatures storeFeatures = null;
@@ -1585,6 +1663,42 @@ public org.apache.commons.configuration2.Configuration getConfigurationAtOpen()
return ReadConfigurationConverter.getInstance().convertToBaseConfiguration(configurationAtOpen);
}
+ public String getSchemaInitStrategy(){
+ return schemaInitStrategy;
+ }
+
+ public boolean isDropSchemaBeforeInit(){
+ return dropSchemaBeforeInit;
+ }
+
+ public String getSchemaInitJsonFile(){
+ return schemaInitJsonFile;
+ }
+
+ public String getSchemaInitJsonString(){
+ return schemaInitJsonString;
+ }
+
+ public boolean getSchemaInitJsonSkipElements(){
+ return schemaInitJsonSkipElements;
+ }
+
+ public boolean getSchemaInitJsonSkipIndices(){
+ return schemaInitJsonSkipIndices;
+ }
+
+ public IndicesActivationType getSchemaInitJsonIndicesActivationType(){
+ return schemaInitJsonIndicesActivationType;
+ }
+
+ public boolean getSchemaInitJsonForceCloseOtherInstances(){
+ return schemaInitJsonForceCloseOtherInstances;
+ }
+
+ public long getSchemaInitJsonIndexStatusAwaitTimeout(){
+ return schemaInitJsonIndexStatusAwaitTimeout;
+ }
+
private void preLoadConfiguration() {
readOnly = configuration.get(STORAGE_READONLY);
evalScript = configuration.get(SCRIPT_EVAL_ENABLED);
@@ -1646,6 +1760,16 @@ private void preLoadConfiguration() {
unknownIndexKeyName = configuration.get(IGNORE_UNKNOWN_INDEX_FIELD) ? UNKNOWN_FIELD_NAME : null;
+ schemaInitStrategy = configuration.get(SCHEMA_INIT_STRATEGY);
+ dropSchemaBeforeInit = configuration.get(SCHEMA_DROP_BEFORE_INIT);
+ schemaInitJsonFile = configuration.has(SCHEMA_INIT_JSON_FILE) ? configuration.get(SCHEMA_INIT_JSON_FILE) : null;
+ schemaInitJsonString = configuration.has(SCHEMA_INIT_JSON_STR) ? configuration.get(SCHEMA_INIT_JSON_STR) : null;
+ schemaInitJsonSkipElements = configuration.get(SCHEMA_INIT_JSON_SKIP_ELEMENTS);
+ schemaInitJsonSkipIndices = configuration.get(SCHEMA_INIT_JSON_SKIP_INDICES);
+ schemaInitJsonIndicesActivationType = selectExactConfig(SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE, IndicesActivationType.values());
+ schemaInitJsonForceCloseOtherInstances = configuration.get(SCHEMA_INIT_JSON_FORCE_CLOSE_OTHER_INSTANCES);
+ schemaInitJsonIndexStatusAwaitTimeout = configuration.get(SCHEMA_INIT_JSON_AWAIT_INDEX_STATUS_TIMEOUT);
+
configureMetrics();
}
diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/database/management/ManagementSystem.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/database/management/ManagementSystem.java
index 0e43eb9e39..801749a2da 100644
--- a/janusgraph-core/src/main/java/org/janusgraph/graphdb/database/management/ManagementSystem.java
+++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/database/management/ManagementSystem.java
@@ -585,6 +585,39 @@ private String printIndexes(boolean calledDirectly) {
return sb.toString();
}
+ public List getGraphIndices(SchemaStatus withoutStatusFilter){
+ List indices = filter(getGraphIndexes(Vertex.class), withoutStatusFilter);
+ indices.addAll(filter(getGraphIndexes(Edge.class), withoutStatusFilter));
+ return indices;
+ }
+
+ private List filter(Iterable graphIndices, SchemaStatus withoutStatusFilter){
+ List indicesWithStatus = new ArrayList<>();
+ for(JanusGraphIndex graphIndex : graphIndices){
+ for(PropertyKey propertyKey : graphIndex.getFieldKeys()){
+ if(!withoutStatusFilter.equals(graphIndex.getIndexStatus(propertyKey))){
+ indicesWithStatus.add(graphIndex);
+ break;
+ }
+ }
+ }
+ return indicesWithStatus;
+ }
+
+ public List getVertexCentricIndices(SchemaStatus withoutStatusFilter){
+ Iterable relationTypes = getRelationTypes(RelationType.class);
+ LinkedList relationIndexes = new LinkedList<>();
+ for (RelationType rt :relationTypes) {
+ Iterable rti = getRelationIndexes(rt);
+ rti.forEach(relationTypeIndex -> {
+ if(!withoutStatusFilter.equals(relationTypeIndex.getIndexStatus())){
+ relationIndexes.add(relationTypeIndex);
+ }
+ });
+ }
+ return relationIndexes;
+ }
+
private String iterateIndexes(String pattern, Iterable indexes) {
StringBuilder sb = new StringBuilder();
for (JanusGraphIndex index: indexes) {
diff --git a/janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/CQLElasticsearchTest.java b/janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/CQLElasticsearchTest.java
index f38c2ae867..ae595d0969 100644
--- a/janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/CQLElasticsearchTest.java
+++ b/janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/CQLElasticsearchTest.java
@@ -14,12 +14,45 @@
package org.janusgraph.diskstorage.es;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.janusgraph.JanusGraphCassandraContainer;
+import org.janusgraph.core.Cardinality;
+import org.janusgraph.core.JanusGraph;
+import org.janusgraph.core.Multiplicity;
+import org.janusgraph.core.schema.IndicesActivationType;
+import org.janusgraph.core.schema.JanusGraphManagement;
+import org.janusgraph.core.schema.SchemaInitType;
+import org.janusgraph.core.schema.SchemaStatus;
+import org.janusgraph.core.util.ManagementUtil;
import org.janusgraph.diskstorage.configuration.ModifiableConfiguration;
import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.Callable;
+
+import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCHEMA_DROP_BEFORE_INIT;
+import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCHEMA_INIT_JSON_FILE;
+import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCHEMA_INIT_JSON_FORCE_CLOSE_OTHER_INSTANCES;
+import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE;
+import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCHEMA_INIT_JSON_SKIP_ELEMENTS;
+import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCHEMA_INIT_JSON_SKIP_INDICES;
+import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.SCHEMA_INIT_STRATEGY;
+import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.UNIQUE_INSTANCE_ID;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
@Testcontainers
public class CQLElasticsearchTest extends ElasticsearchJanusGraphIndexTest {
@@ -34,4 +67,264 @@ public ModifiableConfiguration getStorageConfiguration() {
@Override
@Disabled("CQL seems to not clear storage correctly")
public void testClearStorage() {}
+
+ @Test
+ public void testJsonFreshSchemaImport() {
+ clopen(
+ option(SCHEMA_INIT_STRATEGY), SchemaInitType.JSON.getConfigName(),
+ option(SCHEMA_DROP_BEFORE_INIT), true,
+ option(SCHEMA_INIT_JSON_FILE), createJsonFileAndReturnPath(),
+ option(SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE), IndicesActivationType.REINDEX_AND_ENABLE_UPDATED_ONLY.getConfigName()
+ );
+
+ assertSchemaElements();
+ assertIndices(SchemaStatus.ENABLED);
+ }
+
+ @Test
+ public void testJsonGradualSchemaImport() {
+ clopen(
+ option(SCHEMA_INIT_STRATEGY), SchemaInitType.JSON.getConfigName(),
+ option(SCHEMA_DROP_BEFORE_INIT), true,
+ option(SCHEMA_INIT_JSON_FILE), createJsonFileAndReturnPath(),
+ option(SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE), IndicesActivationType.REINDEX_AND_ENABLE_NON_ENABLED.getConfigName(),
+ option(SCHEMA_INIT_JSON_SKIP_ELEMENTS), false,
+ option(SCHEMA_INIT_JSON_SKIP_INDICES), true
+ );
+
+ createData();
+ assertDataFetch(false, true);
+
+ clopen(
+ option(SCHEMA_INIT_STRATEGY), SchemaInitType.JSON.getConfigName(),
+ option(SCHEMA_DROP_BEFORE_INIT), false,
+ option(SCHEMA_INIT_JSON_FILE), createJsonFileAndReturnPath(),
+ option(SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE), IndicesActivationType.REINDEX_AND_ENABLE_UPDATED_ONLY.getConfigName(),
+ option(SCHEMA_INIT_JSON_SKIP_ELEMENTS), true,
+ option(SCHEMA_INIT_JSON_SKIP_INDICES), false
+ );
+
+ assertSchemaElements();
+ assertIndices(SchemaStatus.ENABLED);
+ assertDataFetch(true, true);
+ }
+
+ @Test
+ public void testJsonForceSchemaImport() {
+ clopen(
+ option(SCHEMA_INIT_STRATEGY), SchemaInitType.JSON.getConfigName(),
+ option(SCHEMA_DROP_BEFORE_INIT), true,
+ option(SCHEMA_INIT_JSON_FILE), createJsonFileAndReturnPath(),
+ option(SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE), IndicesActivationType.FORCE_ENABLE_NON_ENABLED.getConfigName(),
+ option(SCHEMA_INIT_JSON_SKIP_ELEMENTS), false,
+ option(SCHEMA_INIT_JSON_SKIP_INDICES), true
+ );
+
+ createData();
+ assertDataFetch(false, true);
+
+ clopen(
+ option(SCHEMA_INIT_STRATEGY), SchemaInitType.JSON.getConfigName(),
+ option(SCHEMA_DROP_BEFORE_INIT), false,
+ option(SCHEMA_INIT_JSON_FILE), createJsonFileAndReturnPath(),
+ option(SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE), IndicesActivationType.FORCE_ENABLE_UPDATED_ONLY.getConfigName(),
+ option(SCHEMA_INIT_JSON_SKIP_ELEMENTS), true,
+ option(SCHEMA_INIT_JSON_SKIP_INDICES), false
+ );
+
+ assertSchemaElements();
+ assertIndices(SchemaStatus.ENABLED);
+ assertDataFetch(true, false);
+ }
+
+ @Test
+ public void testJsonSchemaImportSkipIndicesActivation() {
+ clopen(
+ option(SCHEMA_INIT_STRATEGY), SchemaInitType.JSON.getConfigName(),
+ option(SCHEMA_DROP_BEFORE_INIT), true,
+ option(SCHEMA_INIT_JSON_FILE), createJsonFileAndReturnPath(),
+ option(SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE), IndicesActivationType.FORCE_ENABLE_NON_ENABLED.getConfigName(),
+ option(SCHEMA_INIT_JSON_SKIP_ELEMENTS), false,
+ option(SCHEMA_INIT_JSON_SKIP_INDICES), true
+ );
+
+ createData();
+ assertDataFetch(false, true);
+
+ clopen(
+ option(SCHEMA_INIT_STRATEGY), SchemaInitType.JSON.getConfigName(),
+ option(SCHEMA_DROP_BEFORE_INIT), false,
+ option(SCHEMA_INIT_JSON_FILE), createJsonFileAndReturnPath(),
+ option(SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE), IndicesActivationType.SKIP_ACTIVATION.getConfigName(),
+ option(SCHEMA_INIT_JSON_SKIP_ELEMENTS), true,
+ option(SCHEMA_INIT_JSON_SKIP_INDICES), false
+ );
+
+ assertSchemaElements();
+ assertIndices(SchemaStatus.INSTALLED);
+ assertDataFetch(false, true);
+ }
+
+ @Test
+ public void testJsonGradualSchemaImportForceClosingOtherInstances() {
+ clopen(
+ option(SCHEMA_INIT_STRATEGY), SchemaInitType.JSON.getConfigName(),
+ option(SCHEMA_DROP_BEFORE_INIT), true,
+ option(SCHEMA_INIT_JSON_FILE), createJsonFileAndReturnPath(),
+ option(SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE), IndicesActivationType.REINDEX_AND_ENABLE_NON_ENABLED.getConfigName(),
+ option(SCHEMA_INIT_JSON_SKIP_ELEMENTS), false,
+ option(SCHEMA_INIT_JSON_SKIP_INDICES), true,
+ option(UNIQUE_INSTANCE_ID), "graph1"
+
+ );
+
+ createData();
+ assertDataFetch(false, true);
+
+ setupConfig(option(SCHEMA_INIT_STRATEGY), SchemaInitType.JSON.getConfigName(),
+ option(SCHEMA_DROP_BEFORE_INIT), false,
+ option(SCHEMA_INIT_JSON_FILE), createJsonFileAndReturnPath(),
+ option(SCHEMA_INIT_JSON_INDICES_ACTIVATION_TYPE), IndicesActivationType.REINDEX_AND_ENABLE_UPDATED_ONLY.getConfigName(),
+ option(SCHEMA_INIT_JSON_SKIP_ELEMENTS), true,
+ option(SCHEMA_INIT_JSON_SKIP_INDICES), false,
+ option(SCHEMA_INIT_JSON_FORCE_CLOSE_OTHER_INSTANCES), true,
+ option(UNIQUE_INSTANCE_ID), "graph2"
+ );
+
+ JanusGraph prevGraph = graph;
+ JanusGraphManagement prevMgmt = mgmt;
+ if(!prevMgmt.isOpen()){
+ prevMgmt = graph.openManagement();
+ }
+ open(config);
+
+ assertSchemaElements();
+ assertIndices(SchemaStatus.ENABLED);
+ assertDataFetch(true, true);
+
+ if(prevMgmt.isOpen()){
+ prevMgmt.rollback();
+ }
+ if(prevGraph.isOpen()){
+ prevGraph.close();
+ }
+ }
+
+ @Test
+ public void testCustomSchemaInitStrategy() {
+ assertFalse(CustomTestSchemaInitStrategy.initialized);
+ clopen(
+ option(SCHEMA_INIT_STRATEGY), CustomTestSchemaInitStrategy.class.getName()
+ );
+ assertTrue(CustomTestSchemaInitStrategy.initialized);
+ }
+
+ private void assertDataFetch(boolean indicesUsed, boolean indexedDataShouldBeFound){
+ GraphTraversalSource g = graph.traversal();
+
+ try {
+ Callable> query = () -> g.V().hasLabel("organization").has("name", "test_org1");
+ assertEquals(indicesUsed, query.call().profile().next().toString().contains("index="));
+ assertEquals(indexedDataShouldBeFound, query.call().hasNext());
+
+ query = () -> g.V().hasLabel("organization").has("name", "test_org2");
+ assertEquals(indicesUsed, query.call().profile().next().toString().contains("index="));
+ assertEquals(indexedDataShouldBeFound, query.call().hasNext());
+
+ query = () -> g.V().hasLabel("device").has("name", "test_org3");
+ assertEquals(indicesUsed, query.call().profile().next().toString().contains("index="));
+ assertEquals(indexedDataShouldBeFound, query.call().hasNext());
+
+ if(indexedDataShouldBeFound){
+ Vertex vertex = g.V().hasLabel("organization").has("name", "test_org1").next();
+ query = () -> g.V(vertex).properties("longPropCardinalityList").has("time", P.lt(300));
+ assertEquals(indicesUsed, query.call().profile().next().toString().contains("longPropCardinalityListMetaPropertyVertexCentricIndexForTime"));
+ assertTrue(query.call().hasNext());
+ }
+
+ query = () -> g.E().hasLabel("connects").has("name", "connectsEdge1");
+ assertEquals(indicesUsed, query.call().profile().next().toString().contains("index="));
+ assertEquals(indexedDataShouldBeFound, query.call().hasNext());
+
+ query = () -> g.E().hasLabel("viewed").has("name", "connectsEdge4");
+ // No indices were defined for such query. Thus, it should always be `false`.
+ assertFalse(query.call().profile().next().toString().contains("index="));
+ assertTrue(query.call().profile().next().toString().contains("fullscan=true"));
+ assertTrue(query.call().hasNext());
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ g.tx().rollback();
+ }
+ }
+
+ private void assertSchemaElements(){
+ assertEquals(Long.class, mgmt.getPropertyKey("time").dataType());
+ assertEquals(Double.class, mgmt.getPropertyKey("doubleProp").dataType());
+ assertEquals(Cardinality.LIST, mgmt.getPropertyKey("longPropCardinalityList").cardinality());
+
+ assertEquals("organization", mgmt.getVertexLabel("organization").name());
+
+ assertEquals(Multiplicity.SIMPLE, mgmt.getEdgeLabel("connects").multiplicity());
+ assertTrue(mgmt.getEdgeLabel("connects").isDirected());
+ assertEquals(Multiplicity.MULTI, mgmt.getEdgeLabel("viewed").multiplicity());
+ assertTrue(mgmt.getEdgeLabel("viewed").isUnidirected());
+ }
+
+ private void assertIndices(SchemaStatus status){
+ assertTrue(ManagementUtil.isGraphIndexHasStatus(mgmt.getGraphIndex("nameCompositeIndex"), status));
+ assertTrue(ManagementUtil.isGraphIndexHasStatus(mgmt.getGraphIndex("timeForOrganizationsOnlyCompositeIndex"), status));
+ assertTrue(ManagementUtil.isGraphIndexHasStatus(mgmt.getGraphIndex("connectsOnlyEdgeCompositeIndex"), status));
+ assertTrue(ManagementUtil.isGraphIndexHasStatus(mgmt.getGraphIndex("uniqueCompositeIndexWithLocking"), status));
+ assertTrue(ManagementUtil.isGraphIndexHasStatus(mgmt.getGraphIndex("multiKeysCompositeIndex"), status));
+ assertTrue(ManagementUtil.isGraphIndexHasStatus(mgmt.getGraphIndex("nameMixedIndex"), status));
+
+ assertTrue(ManagementUtil.isRelationIndexHasStatus(mgmt.getRelationIndex(mgmt.getRelationType("connects"), "connectsTimeVertexCentricIndex"), status));
+ assertTrue(ManagementUtil.isRelationIndexHasStatus(mgmt.getRelationIndex(mgmt.getRelationType("viewed"), "vertexCentricUnidirectedEdgeIndexWithTwoProps"), status));
+ assertTrue(ManagementUtil.isRelationIndexHasStatus(mgmt.getRelationIndex(mgmt.getRelationType("longPropCardinalityList"), "longPropCardinalityListMetaPropertyVertexCentricIndexForTime"), status));
+ }
+
+ private String createJsonFileAndReturnPath() {
+ try{
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ InputStream resourceStream = loader.getResourceAsStream("jsonSchemaExample.json");
+ String jsonSchemaExample = IOUtils.toString(resourceStream, StandardCharsets.UTF_8);
+ File file = File.createTempFile( "janusgraph", "_testJsonSchemaImport.json");
+ file.deleteOnExit();
+ FileUtils.writeStringToFile(file, jsonSchemaExample, StandardCharsets.UTF_8);
+ return file.getAbsolutePath();
+ } catch (IOException e){
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void createData(){
+ GraphTraversalSource g = graph.traversal();
+
+ Vertex vertex1 = g.addV("organization").property("time", 12345L)
+ .property("name", "test_org1")
+ .property("longPropCardinalityList", 123L, "time", 321L)
+ .property("longPropCardinalityList", 123L, "time", 213L)
+ .property("longPropCardinalityList", 123L, "time", 231L).next();
+
+ Vertex vertex2 = g.addV("organization").property("time", 54321L)
+ .property("name", "test_org2")
+ .property("longPropCardinalityList", 123L, "time", 321L)
+ .property("longPropCardinalityList", 123L, "time", 213L)
+ .property("longPropCardinalityList", 123L, "time", 231L).next();
+
+ Vertex vertex3 = g.addV("device").property("time", 1L)
+ .property("name", "test_org3")
+ .property("longPropCardinalityList", 123L, "time", 321L)
+ .property("longPropCardinalityList", 123L, "time", 213L)
+ .property("longPropCardinalityList", 123L, "time", 231L).next();
+
+ g.addE("connects").from(vertex1).to(vertex2).property("name", "connectsEdge1").property("time", 123L).next();
+ g.addE("connects").from(vertex1).to(vertex3).property("name", "connectsEdge2").property("time", 124L).next();
+ g.addE("connects").from(vertex3).to(vertex2).property("name", "connectsEdge3").property("time", 125L).next();
+ g.addE("viewed").from(vertex2).to(vertex3).property("name", "connectsEdge4").property("time", 100L).next();
+
+ g.tx().commit();
+ }
}
diff --git a/janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/CustomJsonStringParameterParser.java b/janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/CustomJsonStringParameterParser.java
new file mode 100644
index 0000000000..f67313b5a7
--- /dev/null
+++ b/janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/CustomJsonStringParameterParser.java
@@ -0,0 +1,27 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.diskstorage.es;
+
+import org.janusgraph.core.schema.Parameter;
+import org.janusgraph.core.schema.json.parser.JsonParameterParser;
+
+public class CustomJsonStringParameterParser implements JsonParameterParser {
+
+ @Override
+ public Parameter parse(String key, String value) {
+ return Parameter.of(key, value);
+ }
+
+}
diff --git a/janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/CustomTestSchemaInitStrategy.java b/janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/CustomTestSchemaInitStrategy.java
new file mode 100644
index 0000000000..68d0a7cc42
--- /dev/null
+++ b/janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/CustomTestSchemaInitStrategy.java
@@ -0,0 +1,30 @@
+// Copyright 2024 JanusGraph Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.janusgraph.diskstorage.es;
+
+import org.janusgraph.core.JanusGraph;
+import org.janusgraph.core.schema.SchemaInitStrategy;
+import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration;
+
+public class CustomTestSchemaInitStrategy implements SchemaInitStrategy {
+
+ public static volatile boolean initialized = false;
+
+ @Override
+ public JanusGraph initializeSchemaAndStart(GraphDatabaseConfiguration graphDatabaseConfiguration) {
+ initialized = true;
+ return null;
+ }
+}
diff --git a/janusgraph-es/src/test/resources/jsonSchemaExample.json b/janusgraph-es/src/test/resources/jsonSchemaExample.json
new file mode 100644
index 0000000000..5d49e5294b
--- /dev/null
+++ b/janusgraph-es/src/test/resources/jsonSchemaExample.json
@@ -0,0 +1,232 @@
+{
+ "vertexLabels": [
+ {
+ "label": "organization"
+ },
+ {
+ "label": "device",
+ "staticVertex": false,
+ "partition": false
+ },
+ {
+ "label": "unmodifiableVertex",
+ "staticVertex": true
+ },
+ {
+ "label": "temporaryVertexForTwoHours",
+ "staticVertex": true,
+ "ttl": 7200000
+ }
+ ],
+ "edgeLabels": [
+ {
+ "label": "connects",
+ "multiplicity": "SIMPLE",
+ "unidirected": false
+ },
+ {
+ "label": "viewed",
+ "multiplicity": "MULTI",
+ "unidirected": true
+ },
+ {
+ "label": "temporaryEdgeForOneHour",
+ "ttl": 3600000
+ },
+ {
+ "label": "edgeWhichUsesLocksInEventualConsistentDBs",
+ "consistency": "LOCK"
+ },
+ {
+ "label": "edgeWhichUsesForkingInEventualConsistentDBs",
+ "consistency": "FORK"
+ }
+ ],
+ "propertyKeys": [
+ {
+ "key": "time",
+ "className": "java.lang.Long"
+ },
+ {
+ "key": "doubleProp",
+ "className": "java.lang.Double"
+ },
+ {
+ "key": "integerProp",
+ "className": "java.lang.Integer"
+ },
+ {
+ "key": "longPropCardinalityList",
+ "className": "java.lang.Long",
+ "cardinality": "LIST"
+ },
+ {
+ "key": "name",
+ "className": "java.lang.String",
+ "cardinality": "SINGLE"
+ },
+ {
+ "key": "geoshape",
+ "className": "org.janusgraph.core.attribute.Geoshape"
+ },
+ {
+ "key": "type",
+ "className": "java.lang.String",
+ "cardinality": "SET"
+ },
+ {
+ "key": "propertyWhichUsesLocksInEventualConsistentDBs",
+ "className": "java.lang.Long",
+ "cardinality": "SET",
+ "consistency": "LOCK"
+ },
+ {
+ "key": "temporaryPropertyForOneHour",
+ "className": "java.lang.Long",
+ "ttl": 3600000
+ },
+ {
+ "key": "anotherName",
+ "className": "java.lang.String"
+ }
+ ],
+ "compositeIndexes": [
+ {
+ "name": "nameCompositeIndex",
+ "typeClass": "org.apache.tinkerpop.gremlin.structure.Vertex",
+ "keys": [
+ {
+ "propertyKey": "name"
+ }
+ ]
+ },
+ {
+ "name": "timeForOrganizationsOnlyCompositeIndex",
+ "indexOnly": "organization",
+ "typeClass": "org.apache.tinkerpop.gremlin.structure.Vertex",
+ "keys": [
+ {
+ "propertyKey": "time"
+ }
+ ]
+ },
+ {
+ "name": "connectsOnlyEdgeCompositeIndex",
+ "indexOnly": "connects",
+ "typeClass": "org.apache.tinkerpop.gremlin.structure.Edge",
+ "keys": [
+ {
+ "propertyKey": "name"
+ }
+ ]
+ },
+ {
+ "name": "uniqueCompositeIndexWithLocking",
+ "indexOnly": "unmodifiableVertex",
+ "typeClass": "org.apache.tinkerpop.gremlin.structure.Vertex",
+ "unique": true,
+ "consistency": "LOCK",
+ "keys": [
+ {
+ "propertyKey": "integerProp"
+ }
+ ]
+ },
+ {
+ "name": "multiKeysCompositeIndex",
+ "typeClass": "org.apache.tinkerpop.gremlin.structure.Vertex",
+ "unique": true,
+ "consistency": "LOCK",
+ "keys": [
+ {
+ "propertyKey": "doubleProp"
+ },
+ {
+ "propertyKey": "integerProp"
+ }
+ ]
+ }
+ ],
+ "vertexCentricEdgeIndexes": [
+ {
+ "name": "connectsTimeVertexCentricIndex",
+ "indexedEdgeLabel": "connects",
+ "direction": "BOTH",
+ "propertyKeys": [
+ "time"
+ ],
+ "order": "asc"
+ },
+ {
+ "name": "vertexCentricUnidirectedEdgeIndexWithTwoProps",
+ "indexedEdgeLabel": "viewed",
+ "direction": "OUT",
+ "propertyKeys": [
+ "time",
+ "integerProp"
+ ],
+ "order": "asc"
+ }
+ ],
+ "vertexCentricPropertyIndexes": [
+ {
+ "name": "longPropCardinalityListMetaPropertyVertexCentricIndexForTime",
+ "indexedPropertyKey": "longPropCardinalityList",
+ "propertyKeys": [
+ "time"
+ ],
+ "order": "desc"
+ }
+ ],
+ "mixedIndexes": [
+ {
+ "name": "nameMixedIndex",
+ "indexOnly": "organization",
+ "typeClass": "org.apache.tinkerpop.gremlin.structure.Vertex",
+ "indexBackend": "search",
+ "keys": [
+ {
+ "propertyKey": "name",
+ "parameters": [
+ {
+ "key": "string-analyzer",
+ "value": "standard",
+ "parser": "string"
+ },
+ {
+ "key": "mapping",
+ "value": "org.janusgraph.core.schema.Mapping.STRING",
+ "parser": "enum"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "typeAndAnotherNameMixedIndex",
+ "indexOnly": "unmodifiableVertex",
+ "typeClass": "org.apache.tinkerpop.gremlin.structure.Vertex",
+ "indexBackend": "search",
+ "keys": [
+ {
+ "propertyKey": "type"
+ },
+ {
+ "propertyKey": "anotherName",
+ "parameters": [
+ {
+ "key": "string-analyzer",
+ "value": "standard",
+ "parser": "org.janusgraph.diskstorage.es.CustomJsonStringParameterParser"
+ },
+ {
+ "key": "mapping",
+ "value": "org.janusgraph.core.schema.Mapping.STRING",
+ "parser": "enum"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file