This is an automated email from the ASF dual-hosted git repository.

colegreer pushed a commit to branch TINKERPOP-3107
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 80a6ca8834352128705d4ef607d8e66e4e859d05
Author: Cole Greer <[email protected]>
AuthorDate: Mon Apr 13 09:50:30 2026 -0700

    initial PoC
---
 gremlin-server/conf/gremlin-server-classic.yaml    |   9 +-
 .../conf/gremlin-server-modern-readonly.yaml       |  11 +-
 gremlin-server/conf/gremlin-server-modern.yaml     |  10 +-
 .../conf/gremlin-server-rest-modern.yaml           |   9 +-
 .../conf/gremlin-server-rest-secure.yaml           |   8 --
 gremlin-server/conf/gremlin-server-secure.yaml     |   8 --
 .../conf/gremlin-server-transaction.yaml           |   7 --
 gremlin-server/conf/gremlin-server.yaml            |   7 --
 .../tinkerpop/gremlin/server/GremlinServer.java    |   4 +-
 .../apache/tinkerpop/gremlin/server/Settings.java  |  58 ++++++++++
 .../gremlin/server/util/LifeCycleHook.java         |  29 +++++
 .../gremlin/server/util/ServerGremlinExecutor.java | 125 +++++++++++++++++++--
 .../server/util/TinkerFactoryDataLoader.java       |  99 ++++++++++++++++
 13 files changed, 316 insertions(+), 68 deletions(-)

diff --git a/gremlin-server/conf/gremlin-server-classic.yaml 
b/gremlin-server/conf/gremlin-server-classic.yaml
index 37b51f5380..b421c3f276 100644
--- a/gremlin-server/conf/gremlin-server-classic.yaml
+++ b/gremlin-server/conf/gremlin-server-classic.yaml
@@ -20,13 +20,8 @@ port: 8182
 evaluationTimeout: 30000
 graphs: {
   graph: conf/tinkergraph-empty.properties}
-scriptEngines: {
-  gremlin-lang: {},
-  gremlin-groovy: {
-    plugins: { 
org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
-               
org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
-               org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: 
{classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
-               org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: 
{files: [scripts/generate-classic.groovy]}}}}
+lifecycleHooks:
+  - { className: 
org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: 
{graph: graph, dataset: classic}}
 serializers:
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { 
ioRegistries: 
[org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }}      
      # application/json
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1 }          
                                                                                
                 # application/vnd.graphbinary-v1.0
diff --git a/gremlin-server/conf/gremlin-server-modern-readonly.yaml 
b/gremlin-server/conf/gremlin-server-modern-readonly.yaml
index 3c6ad637b7..975caed215 100644
--- a/gremlin-server/conf/gremlin-server-modern-readonly.yaml
+++ b/gremlin-server/conf/gremlin-server-modern-readonly.yaml
@@ -20,13 +20,10 @@ port: 8182
 evaluationTimeout: 30000
 graphs: {
   graph: conf/tinkergraph-empty.properties}
-scriptEngines: {
-  gremlin-lang: {},
-  gremlin-groovy: {
-    plugins: { 
org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
-               
org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
-               org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: 
{classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
-               org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: 
{files: [scripts/generate-modern-readonly.groovy]}}}}
+traversalSources: {
+  g: {graph: graph, query: "g.withStrategies(ReadOnlyStrategy)"}}
+lifecycleHooks:
+  - { className: 
org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: 
{graph: graph, dataset: classic}}
 serializers:
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { 
ioRegistries: 
[org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }}      
      # application/json
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1 }          
                                                                                
                 # application/vnd.graphbinary-v1.0
diff --git a/gremlin-server/conf/gremlin-server-modern.yaml 
b/gremlin-server/conf/gremlin-server-modern.yaml
index 4551023d80..4a1fe9803e 100644
--- a/gremlin-server/conf/gremlin-server-modern.yaml
+++ b/gremlin-server/conf/gremlin-server-modern.yaml
@@ -21,14 +21,8 @@ evaluationTimeout: 30000
 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer
 graphs: {
   graph: conf/tinkergraph-empty.properties}
-scriptEngines: {
-  gremlin-lang: {},
-  gremlin-groovy: {
-    plugins: { 
org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
-               
org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
-               
org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin: 
{enableThreadInterrupt: true},
-               org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: 
{classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
-               org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: 
{files: [scripts/generate-modern.groovy]}}}}
+lifecycleHooks:
+  - { className: 
org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: 
{graph: graph, dataset: modern}}
 serializers:
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4, config: { 
ioRegistries: 
[org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }}      
      # application/json
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4}           
                                                                                
                 # application/vnd.graphbinary-v4.0
diff --git a/gremlin-server/conf/gremlin-server-rest-modern.yaml 
b/gremlin-server/conf/gremlin-server-rest-modern.yaml
index 905a9ca002..3b66b58177 100644
--- a/gremlin-server/conf/gremlin-server-rest-modern.yaml
+++ b/gremlin-server/conf/gremlin-server-rest-modern.yaml
@@ -21,13 +21,8 @@ evaluationTimeout: 30000
 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer
 graphs: {
   graph: conf/tinkergraph-empty.properties}
-scriptEngines: {
-  gremlin-lang: {},
-  gremlin-groovy: {
-    plugins: { 
org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
-               
org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
-               org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: 
{classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
-               org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: 
{files: [scripts/generate-modern.groovy]}}}}
+lifecycleHooks:
+  - { className: 
org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: 
{graph: graph, dataset: modern}}
 serializers:
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV3, 
config: { ioRegistries: 
[org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }}     
# application/vnd.gremlin-v3.0+json;types=false
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { 
ioRegistries: 
[org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }}      
      # application/vnd.gremlin-v3.0+json
diff --git a/gremlin-server/conf/gremlin-server-rest-secure.yaml 
b/gremlin-server/conf/gremlin-server-rest-secure.yaml
index 8a62795351..642033b31d 100644
--- a/gremlin-server/conf/gremlin-server-rest-secure.yaml
+++ b/gremlin-server/conf/gremlin-server-rest-secure.yaml
@@ -29,14 +29,6 @@ evaluationTimeout: 30000
 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer
 graphs: {
   graph: conf/tinkergraph-empty.properties}
-scriptEngines: {
-  gremlin-lang: {},
-  gremlin-groovy: {
-    plugins: { 
org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
-               
org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
-               org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: 
{classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
-               
org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin: 
{enableThreadInterrupt: true, timedInterrupt: 10000, compilation: 
COMPILE_STATIC, extensions: 
org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.SimpleSandboxExtension},
-               org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: 
{files: [scripts/empty-sample-secure.groovy]}}}}
 serializers:
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { 
ioRegistries: 
[org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }}      
      # application/json
 metrics: {
diff --git a/gremlin-server/conf/gremlin-server-secure.yaml 
b/gremlin-server/conf/gremlin-server-secure.yaml
index ddc57c0d85..644b275f70 100644
--- a/gremlin-server/conf/gremlin-server-secure.yaml
+++ b/gremlin-server/conf/gremlin-server-secure.yaml
@@ -29,14 +29,6 @@ evaluationTimeout: 30000
 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer
 graphs: {
   graph: conf/tinkergraph-empty.properties}
-scriptEngines: {
-  gremlin-lang: {},
-  gremlin-groovy: {
-    plugins: { 
org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
-               
org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
-               
org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin: 
{enableThreadInterrupt: true, timedInterrupt: 10000, compilation: 
COMPILE_STATIC, extensions: 
org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.SimpleSandboxExtension},
-               org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: 
{classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
-               org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: 
{files: [scripts/empty-sample-secure.groovy]}}}}
 serializers:
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { 
ioRegistries: 
[org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }}      
      # application/json
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1 }          
                                                                                
                 # application/vnd.graphbinary-v1.0
diff --git a/gremlin-server/conf/gremlin-server-transaction.yaml 
b/gremlin-server/conf/gremlin-server-transaction.yaml
index e4cc804ac2..6b61f40a25 100644
--- a/gremlin-server/conf/gremlin-server-transaction.yaml
+++ b/gremlin-server/conf/gremlin-server-transaction.yaml
@@ -21,13 +21,6 @@ evaluationTimeout: 30000
 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer
 graphs: {
   graph: conf/tinkertransactiongraph-empty.properties}
-scriptEngines: {
-  gremlin-lang: {},
-  gremlin-groovy: {
-    plugins: { 
org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
-               
org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
-               org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: 
{classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
-               org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: 
{files: [scripts/empty-sample.groovy]}}}}
 serializers:
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { 
ioRegistries: 
[org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }}      
      # application/json
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1 }          
                                                                                
                 # application/vnd.graphbinary-v1.0
diff --git a/gremlin-server/conf/gremlin-server.yaml 
b/gremlin-server/conf/gremlin-server.yaml
index 286f7f18ea..163d807849 100644
--- a/gremlin-server/conf/gremlin-server.yaml
+++ b/gremlin-server/conf/gremlin-server.yaml
@@ -21,13 +21,6 @@ evaluationTimeout: 30000
 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer
 graphs: {
   graph: conf/tinkergraph-empty.properties}
-scriptEngines: {
-  gremlin-lang: {},
-  gremlin-groovy: {
-    plugins: { 
org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
-               
org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
-               org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: 
{classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
-               org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: 
{files: [scripts/empty-sample.groovy]}}}}
 serializers:
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4, config: { 
ioRegistries: 
[org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }}      
      # application/json
   - { className: 
org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4 }          
                                                                                
                 # application/vnd.graphbinary-v1.0
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/GremlinServer.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/GremlinServer.java
index 16d8a2d045..a0b68e5e3a 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/GremlinServer.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/GremlinServer.java
@@ -159,7 +159,7 @@ public class GremlinServer {
             serverGremlinExecutor.getHooks().forEach(hook -> {
                 logger.info("Executing start up {}", 
LifeCycleHook.class.getSimpleName());
                 try {
-                    hook.onStartUp(new LifeCycleHook.Context(logger));
+                    hook.onStartUp(new LifeCycleHook.Context(logger, 
serverGremlinExecutor.getGraphManager()));
                 } catch (UnsupportedOperationException uoe) {
                     // if the user doesn't implement onStartUp the 
scriptengine will throw
                     // this exception.  it can safely be ignored.
@@ -269,7 +269,7 @@ public class GremlinServer {
                 serverGremlinExecutor.getHooks().forEach(hook -> {
                     logger.info("Executing shutdown {}", 
LifeCycleHook.class.getSimpleName());
                     try {
-                        hook.onShutDown(new LifeCycleHook.Context(logger));
+                        hook.onShutDown(new LifeCycleHook.Context(logger, 
serverGremlinExecutor.getGraphManager()));
                     } catch (UnsupportedOperationException | 
UndeclaredThrowableException uoe) {
                         // if the user doesn't implement onShutDown the 
scriptengine will throw
                         // this exception.  it can safely be ignored.
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
index 3489bd511e..5805c649f1 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
@@ -244,6 +244,18 @@ public class Settings {
      */
     public Map<String, ScriptEngineSettings> scriptEngines;
 
+    /**
+     * {@link Map} of {@link TraversalSource} configurations keyed by the 
binding name. Each entry specifies a
+     * graph reference and optionally a Gremlin query to configure strategies.
+     */
+    public Map<String, TraversalSourceSettings> traversalSources = new 
LinkedHashMap<>();
+
+    /**
+     * List of {@link LifeCycleHook} implementations to instantiate via 
reflection and execute during server
+     * initialization and shutdown.
+     */
+    public List<LifeCycleHookSettings> lifecycleHooks = new ArrayList<>();
+
     /**
      * List of {@link MessageSerializer} to configure. If no serializers are 
specified then default serializers for
      * the most current versions of "application/json" and 
"application/vnd.gremlin-v1.0+gryo" are applied.
@@ -300,6 +312,8 @@ public class Settings {
         settingsDescription.addPropertyParameters("graphs", String.class, 
String.class);
         settingsDescription.addPropertyParameters("scriptEngines", 
String.class, ScriptEngineSettings.class);
         settingsDescription.addPropertyParameters("serializers", 
SerializerSettings.class);
+        settingsDescription.addPropertyParameters("traversalSources", 
String.class, TraversalSourceSettings.class);
+        settingsDescription.addPropertyParameters("lifecycleHooks", 
LifeCycleHookSettings.class);
         constructor.addTypeDescription(settingsDescription);
 
         final TypeDescription serializerSettingsDescription = new 
TypeDescription(SerializerSettings.class);
@@ -314,6 +328,13 @@ public class Settings {
         scriptEngineSettingsDescription.addPropertyParameters("plugins", 
String.class, Object.class);
         constructor.addTypeDescription(scriptEngineSettingsDescription);
 
+        final TypeDescription traversalSourceSettingsDescription = new 
TypeDescription(TraversalSourceSettings.class);
+        constructor.addTypeDescription(traversalSourceSettingsDescription);
+
+        final TypeDescription lifeCycleHookSettingsDescription = new 
TypeDescription(LifeCycleHookSettings.class);
+        lifeCycleHookSettingsDescription.addPropertyParameters("config", 
String.class, Object.class);
+        constructor.addTypeDescription(lifeCycleHookSettingsDescription);
+
         final TypeDescription sslSettings = new 
TypeDescription(SslSettings.class);
         constructor.addTypeDescription(sslSettings);
 
@@ -393,6 +414,43 @@ public class Settings {
         public Map<String,Map<String,Object>> plugins = new LinkedHashMap<>();
     }
 
+    /**
+     * Settings for a declarative {@link TraversalSource} entry in the server 
YAML.
+     */
+    public static class TraversalSourceSettings {
+        /**
+         * The name of the graph (as defined in {@code graphs}) to create the 
traversal source from.
+         */
+        public String graph;
+
+        /**
+         * An optional Gremlin query evaluated with a base traversal source 
bound as {@code g}.
+         * The result of the query becomes the final {@link TraversalSource}.
+         */
+        public String query = null;
+
+        /**
+         * The script engine language to use for evaluating {@link #query}. If 
not specified, resolution
+         * falls back to the single configured engine or {@code gremlin-lang}.
+         */
+        public String language = null;
+    }
+
+    /**
+     * Settings for a Java-based {@link LifeCycleHook} configured in the 
server YAML.
+     */
+    public static class LifeCycleHookSettings {
+        /**
+         * The fully qualified class name of the {@link LifeCycleHook} 
implementation.
+         */
+        public String className;
+
+        /**
+         * Optional configuration passed to {@link 
LifeCycleHook#init(java.util.Map)}.
+         */
+        public Map<String, Object> config = null;
+    }
+
     /**
      * Settings for the {@link MessageSerializer} implementations.
      */
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/LifeCycleHook.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/LifeCycleHook.java
index 95884d8d7f..80696686b8 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/LifeCycleHook.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/LifeCycleHook.java
@@ -18,8 +18,11 @@
  */
 package org.apache.tinkerpop.gremlin.server.util;
 
+import org.apache.tinkerpop.gremlin.server.GraphManager;
 import org.slf4j.Logger;
 
+import java.util.Map;
+
 /**
  * Provides a method in which users can hook into the startup and shutdown 
lifecycle of Gremlin Server.  Creating
  * an instance of this interface in a Gremlin Server initialization script 
enables the ability to get a callback
@@ -29,6 +32,15 @@ import org.slf4j.Logger;
  */
 public interface LifeCycleHook {
 
+    /**
+     * Called once after instantiation to pass configuration from the {@code 
lifecycleHooks} YAML section.
+     * Implementations that require configuration should override this method. 
 The default implementation
+     * is a no-op so that existing hooks are not forced to implement it.
+     *
+     * @param config the key/value pairs from the {@code config} block in the 
YAML entry
+     */
+    public default void init(final Map<String, Object> config) {}
+
     /**
      * Called when the server starts up.  The graph collection will have been 
initialized at this point
      * and all initialization scripts will have been executed when this 
callback is called.
@@ -45,13 +57,30 @@ public interface LifeCycleHook {
      */
     public static class Context {
         private final Logger logger;
+        private final GraphManager graphManager;
 
+        /**
+         * @deprecated As of release 4.0.0, replaced by {@link 
#Context(Logger, GraphManager)}.
+         */
+        @Deprecated
         public Context(final Logger logger) {
+            this(logger, null);
+        }
+
+        public Context(final Logger logger, final GraphManager graphManager) {
             this.logger = logger;
+            this.graphManager = graphManager;
         }
 
         public Logger getLogger() {
             return logger;
         }
+
+        /**
+         * Gets the {@link GraphManager} which provides access to all 
configured graphs and traversal sources.
+         */
+        public GraphManager getGraphManager() {
+            return graphManager;
+        }
     }
 }
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java
index dce5fb931a..f3970b7a92 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java
@@ -36,9 +36,12 @@ import org.slf4j.LoggerFactory;
 
 import javax.script.SimpleBindings;
 import java.lang.reflect.Constructor;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentHashMap;
@@ -186,20 +189,110 @@ public class ServerGremlinExecutor {
                 .forEach(kv -> this.graphManager.putGraph(kv.getKey(), (Graph) 
kv.getValue()));
 
         // script engine init may have constructed the TraversalSource 
bindings - store them in Graphs object
-        
gremlinExecutor.getScriptEngineManager().getBindings().entrySet().stream()
-                .filter(kv -> kv.getValue() instanceof TraversalSource)
-                .forEach(kv -> {
-                    logger.info("A {} is now bound to [{}] with {}", 
kv.getValue().getClass().getSimpleName(), kv.getKey(), kv.getValue());
-                    this.graphManager.putTraversalSource(kv.getKey(), 
(TraversalSource) kv.getValue());
-                });
+        final boolean hasScriptTraversalSources = 
gremlinExecutor.getScriptEngineManager().getBindings().entrySet().stream()
+                .anyMatch(kv -> kv.getValue() instanceof TraversalSource);
+        if (hasScriptTraversalSources) {
+            logger.warn("TraversalSource instances were created via script 
engine initialization scripts. " +
+                        "This approach is deprecated - use the 
'traversalSources' YAML configuration instead.");
+            
gremlinExecutor.getScriptEngineManager().getBindings().entrySet().stream()
+                    .filter(kv -> kv.getValue() instanceof TraversalSource)
+                    .forEach(kv -> {
+                        logger.info("A {} is now bound to [{}] with {}", 
kv.getValue().getClass().getSimpleName(), kv.getKey(), kv.getValue());
+                        this.graphManager.putTraversalSource(kv.getKey(), 
(TraversalSource) kv.getValue());
+                    });
+        }
 
         // determine if the initialization scripts introduced LifeCycleHook 
objects - if so we need to gather them
         // up for execution
-        hooks = 
gremlinExecutor.getScriptEngineManager().getBindings().entrySet().stream()
+        final boolean hasScriptHooks = 
gremlinExecutor.getScriptEngineManager().getBindings().entrySet().stream()
+                .anyMatch(kv -> kv.getValue() instanceof LifeCycleHook);
+        if (hasScriptHooks) {
+            logger.warn("LifeCycleHook instances were created via script 
engine initialization scripts. " +
+                        "This approach is deprecated - use the 
'lifecycleHooks' YAML configuration instead.");
+        }
+        final List<LifeCycleHook> scriptHooks = 
gremlinExecutor.getScriptEngineManager().getBindings().entrySet().stream()
                 .filter(kv -> kv.getValue() instanceof LifeCycleHook)
                 .map(kv -> (LifeCycleHook) kv.getValue())
                 .collect(Collectors.toList());
 
+        // process declarative traversalSources from YAML config - track which 
graphs have explicit entries
+        final Set<String> graphsWithExplicitTraversalSources = new HashSet<>();
+        if (settings.traversalSources != null && 
!settings.traversalSources.isEmpty()) {
+            settings.traversalSources.forEach((tsName, tsSettings) -> {
+                final Graph graph = 
this.graphManager.getGraph(tsSettings.graph);
+                if (null == graph) {
+                    logger.warn("Could not create TraversalSource [{}] - graph 
[{}] not found in graphs configuration",
+                                tsName, tsSettings.graph);
+                    return;
+                }
+
+                graphsWithExplicitTraversalSources.add(tsSettings.graph);
+
+                if (tsSettings.query != null && !tsSettings.query.isEmpty()) {
+                    // resolve which script engine to use for the query
+                    final String language = 
resolveLanguage(tsSettings.language);
+                    try {
+                        // bind a base traversal source as 'g' for the query 
to operate on
+                        final SimpleBindings bindings = new SimpleBindings();
+                        bindings.put("g", graph.traversal());
+                        final GremlinExecutor.LifeCycle lifeCycle = 
GremlinExecutor.LifeCycle.build()
+                                .evaluationTimeoutOverride(0L).create();
+                        final TraversalSource ts = (TraversalSource) 
gremlinExecutor.eval(
+                                tsSettings.query, language, bindings, 
lifeCycle).join();
+                        this.graphManager.putTraversalSource(tsName, ts);
+                        logger.info("A {} is now bound to [{}] via query", 
ts.getClass().getSimpleName(), tsName);
+                    } catch (Exception ex) {
+                        logger.warn(String.format("Could not create 
TraversalSource [%s] from query - %s",
+                                    tsName, ex.getMessage()), ex);
+                    }
+                } else {
+                    final TraversalSource ts = graph.traversal();
+                    this.graphManager.putTraversalSource(tsName, ts);
+                    logger.info("A {} is now bound to [{}]", 
ts.getClass().getSimpleName(), tsName);
+                }
+            });
+        }
+
+        // auto-create TraversalSources for graphs that don't have explicit 
traversalSources entries and
+        // were not already bound by script engine initialization
+        for (final String graphName : this.graphManager.getGraphNames()) {
+            if (graphsWithExplicitTraversalSources.contains(graphName))
+                continue;
+
+            final String tsName = graphName.equals("graph") ? "g" : "g_" + 
graphName;
+            if (this.graphManager.getTraversalSource(tsName) != null)
+                continue;
+
+            final Graph graph = this.graphManager.getGraph(graphName);
+            final TraversalSource ts = graph.traversal();
+            this.graphManager.putTraversalSource(tsName, ts);
+            logger.info("A {} is now auto-bound to [{}] for graph [{}]",
+                        ts.getClass().getSimpleName(), tsName, graphName);
+        }
+
+        // instantiate Java-based lifecycle hooks from YAML config
+        final List<LifeCycleHook> yamlHooks = new ArrayList<>();
+        if (settings.lifecycleHooks != null) {
+            for (final Settings.LifeCycleHookSettings hookSettings : 
settings.lifecycleHooks) {
+                try {
+                    final Class<?> clazz = 
Class.forName(hookSettings.className);
+                    final LifeCycleHook hook = (LifeCycleHook) 
clazz.getDeclaredConstructor().newInstance();
+                    if (hookSettings.config != null) {
+                        hook.init(hookSettings.config);
+                    }
+                    yamlHooks.add(hook);
+                    logger.info("Instantiated LifeCycleHook: {}", 
hookSettings.className);
+                } catch (Exception ex) {
+                    logger.error(String.format("Could not instantiate 
LifeCycleHook %s", hookSettings.className), ex);
+                }
+            }
+        }
+
+        // combine YAML-configured hooks with any script-produced hooks (YAML 
hooks execute first)
+        final List<LifeCycleHook> allHooks = new ArrayList<>(yamlHooks);
+        allHooks.addAll(scriptHooks);
+        hooks = allHooks;
+
         transactionManager = new TransactionManager(
                 scheduledExecutorService,
                 graphManager,
@@ -214,6 +307,24 @@ public class ServerGremlinExecutor {
         MetricManager.INSTANCE.registerGremlinScriptEngineMetrics(engine, 
engineName, "sessionless", "class-cache");
     }
 
+    /**
+     * Resolves the script engine language to use for evaluating a traversal 
source query. If an explicit language
+     * is provided, it is used directly. Otherwise, if only one non-{@code 
gremlin-lang} script engine is configured,
+     * that engine is used. Falls back to {@code gremlin-lang}.
+     */
+    private String resolveLanguage(final String explicitLanguage) {
+        if (explicitLanguage != null && !explicitLanguage.isEmpty())
+            return explicitLanguage;
+
+        final List<String> nonLangEngines = 
settings.scriptEngines.keySet().stream()
+                .filter(name -> !name.equals("gremlin-lang"))
+                .collect(Collectors.toList());
+        if (nonLangEngines.size() == 1)
+            return nonLangEngines.get(0);
+
+        return "gremlin-lang";
+    }
+
     public void addHostOption(final String key, final Object value) {
         hostOptions.put(key, value);
     }
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoader.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoader.java
new file mode 100644
index 0000000000..5cc87f8835
--- /dev/null
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoader.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.server.util;
+
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory;
+
+import java.util.Map;
+
+/**
+ * A {@link LifeCycleHook} that loads TinkerFactory sample datasets into a 
graph during server startup.
+ * Replaces the Groovy init scripts (e.g. generate-modern.groovy) for default 
server configurations.
+ *
+ * <p>Configuration parameters (via {@code config} in YAML):
+ * <ul>
+ *   <li>{@code graph} — name of the graph (as defined in {@code graphs:} 
config) to load data into</li>
+ *   <li>{@code dataset} — which dataset to load: modern, classic, crew, 
grateful, sink</li>
+ * </ul>
+ *
+ * <p>Example YAML usage:
+ * <pre>
+ * lifecycleHooks:
+ *   - className: 
org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader
+ *     config: {graph: graph, dataset: modern}
+ * </pre>
+ */
+public class TinkerFactoryDataLoader implements LifeCycleHook {
+
+    private String graphName;
+    private String dataset;
+
+    @Override
+    public void init(final Map<String, Object> config) {
+        graphName = (String) config.get("graph");
+        dataset = (String) config.get("dataset");
+        if (graphName == null || dataset == null) {
+            throw new IllegalArgumentException("TinkerFactoryDataLoader 
requires 'graph' and 'dataset' config parameters");
+        }
+    }
+
+    @Override
+    public void onStartUp(final Context c) {
+        final Graph graph = c.getGraphManager().getGraph(graphName);
+        if (null == graph) {
+            c.getLogger().warn("TinkerFactoryDataLoader could not find graph 
[{}]", graphName);
+            return;
+        }
+        if (!(graph instanceof AbstractTinkerGraph)) {
+            c.getLogger().warn("TinkerFactoryDataLoader requires an 
AbstractTinkerGraph but [{}] is {}",
+                               graphName, graph.getClass().getName());
+            return;
+        }
+
+        final AbstractTinkerGraph tinkerGraph = (AbstractTinkerGraph) graph;
+        switch (dataset) {
+            case "modern":
+                TinkerFactory.generateModern(tinkerGraph);
+                break;
+            case "classic":
+                TinkerFactory.generateClassic(tinkerGraph);
+                break;
+            case "crew":
+                TinkerFactory.generateTheCrew(tinkerGraph);
+                break;
+            case "grateful":
+                TinkerFactory.generateGratefulDead(tinkerGraph);
+                break;
+            case "sink":
+                TinkerFactory.generateKitchenSink(tinkerGraph);
+                break;
+            default:
+                c.getLogger().warn("TinkerFactoryDataLoader unknown dataset 
[{}]", dataset);
+                return;
+        }
+        c.getLogger().info("TinkerFactoryDataLoader loaded [{}] dataset into 
graph [{}]", dataset, graphName);
+    }
+
+    @Override
+    public void onShutDown(final Context c) {
+        // nothing to do on shutdown
+    }
+}


Reply via email to