diff --git a/engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceClassLoader.java b/engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceClassLoader.java new file mode 100644 index 00000000000..fb8784892c5 --- /dev/null +++ b/engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceClassLoader.java @@ -0,0 +1,254 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.engine.util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; + +/** + * A custom ClassLoader that fetches source files from remote clients via registered RemoteFileSourceProvider instances. + * This is designed to support Groovy script imports where the source files are provided by remote clients. + * + *

+ * When a resource is requested (e.g., for a Groovy import), this class loader: + *

    + *
  1. Checks registered providers to see if they can source the resource
  2. + *
  3. Returns a custom URL with protocol "remotefile://" if a provider can handle it
  4. + *
  5. When that URL is opened, fetches the resource bytes from the provider
  6. + *
+ */ +public class RemoteFileSourceClassLoader extends ClassLoader { + private static final long RESOURCE_TIMEOUT_SECONDS = 5; + + private static volatile RemoteFileSourceClassLoader instance; + private final CopyOnWriteArrayList providers = new CopyOnWriteArrayList<>(); + + + /** + * Constructs a new RemoteFileSourceClassLoader with the specified parent class loader. + * + * @param parent the parent class loader for delegation + */ + private RemoteFileSourceClassLoader(ClassLoader parent) { + super(parent); + } + + /** + * Initializes the singleton RemoteFileSourceClassLoader instance with the specified parent class loader. + * + *

+ * This method must be called exactly once before any calls to {@link #getInstance()}. The method is synchronized to + * prevent race conditions when multiple threads attempt initialization. + * + * @param parent the parent class loader for delegation + * @return the newly created singleton instance + * @throws IllegalStateException if the instance has already been initialized + */ + public static synchronized RemoteFileSourceClassLoader initialize(ClassLoader parent) { + if (instance != null) { + throw new IllegalStateException("RemoteFileSourceClassLoader is already initialized"); + } + + instance = new RemoteFileSourceClassLoader(parent); + return instance; + } + + /** + * Returns the singleton instance of the RemoteFileSourceClassLoader. + * + *

+ * This method requires that {@link #initialize(ClassLoader)} has been called first. + * + * @return the singleton instance + * @throws IllegalStateException if the instance has not yet been initialized via {@link #initialize(ClassLoader)} + */ + public static RemoteFileSourceClassLoader getInstance() { + if (instance == null) { + throw new IllegalStateException("RemoteFileSourceClassLoader is not yet initialized"); + } + return instance; + } + + /** + * Registers a new provider that can source remote resources. + * + * @param provider the provider to register + */ + public void registerProvider(RemoteFileSourceProvider provider) { + providers.add(provider); + } + + /** + * Unregisters a previously registered provider. + * + * @param provider the provider to unregister + */ + public void unregisterProvider(RemoteFileSourceProvider provider) { + providers.remove(provider); + } + + /** + * Returns whether there are any active providers with non-empty resource paths configured. This indicates that + * remote sources are actually configured, not just that the execution context is set. + * + * @return true if any provider is active and has resource paths configured, false otherwise + */ + public boolean hasConfiguredRemoteSources() { + for (RemoteFileSourceProvider candidate : providers) { + if (candidate.isActive() && candidate.hasConfiguredResources()) { + return true; + } + } + return false; + } + + /** + * Returns whether the current execution context is dirty, indicating that remote sources have changed and the cache + * should be cleared. This method is used by GroovyDeephavenSession to determine when to refresh the class cache. + * + * @return true if there is a dirty execution context, false otherwise + */ + public boolean isDirty() { + for (RemoteFileSourceProvider candidate : providers) { + if (candidate.isActive() && candidate.isDirty()) { + return true; + } + } + return false; + } + + /** + * Gets the resource with the specified name by checking registered providers. + * + *

+ * This method iterates through all registered providers to see if any can source the requested resource. If a + * provider can handle the resource, a custom URL with protocol "remotefile://" is returned. If no provider can + * handle the resource, the request is delegated to the parent class loader. + * + * @param name the resource name + * @return a URL for reading the resource, or null if the resource could not be found + */ + @Override + public URL getResource(String name) { + RemoteFileSourceProvider provider = null; + for (RemoteFileSourceProvider candidate : providers) { + if (candidate.isActive() && candidate.canSourceResource(name)) { + provider = candidate; + break; + } + } + + if (provider != null) { + try { + return new URL(null, "remotefile://" + name, new RemoteFileURLStreamHandler(provider, name)); + } catch (MalformedURLException e) { + // Fall through to parent if URL creation fails + } + } + + return super.getResource(name); + } + + /** + * URLStreamHandler that delegates to a RemoteFileSourceProvider to fetch resource bytes. + */ + private static class RemoteFileURLStreamHandler extends URLStreamHandler { + private final RemoteFileSourceProvider provider; + private final String resourceName; + + /** + * Constructs a new RemoteFileURLStreamHandler for the specified provider and resource. + * + * @param provider the provider that will source the resource + * @param resourceName the name of the resource to fetch + */ + RemoteFileURLStreamHandler(RemoteFileSourceProvider provider, String resourceName) { + this.provider = provider; + this.resourceName = resourceName; + } + + /** + * Opens a connection to the resource referenced by this URL. + * + * @param url the URL to open a connection to + * @return a URLConnection to the specified URL + */ + @Override + protected URLConnection openConnection(URL url) { + return new RemoteFileURLConnection(url, provider, resourceName); + } + } + + /** + * URLConnection that fetches resource bytes from a RemoteFileSourceProvider. + */ + private static class RemoteFileURLConnection extends URLConnection { + private final RemoteFileSourceProvider provider; + private final String resourceName; + private byte[] content; + + /** + * Constructs a new RemoteFileURLConnection for the specified URL, provider, and resource. + * + * @param url the URL to connect to + * @param provider the provider that will source the resource + * @param resourceName the name of the resource to fetch + */ + RemoteFileURLConnection(URL url, RemoteFileSourceProvider provider, String resourceName) { + super(url); + this.provider = provider; + this.resourceName = resourceName; + } + + /** + * Opens a connection to the resource by requesting it from the provider. + * + *

+ * This method fetches the resource bytes from the provider with a timeout of {@value #RESOURCE_TIMEOUT_SECONDS} + * seconds. If already connected, this method does nothing. + * + * @throws IOException if the connection fails or times out + */ + @Override + public void connect() throws IOException { + if (!connected) { + try { + content = provider.requestResource(resourceName) + .orTimeout(RESOURCE_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .get(); + connected = true; + } catch (Exception e) { + throw new IOException("Failed to fetch remote resource: " + resourceName, e); + } + } + } + + /** + * Returns an input stream that reads from this connection's resource. + * + *

+ * This method calls {@link #connect()} to ensure the connection is established and resource bytes are fetched + * from the provider. The method then verifies that content has been successfully downloaded before creating the + * input stream. + * + * @return an input stream that reads from the fetched resource bytes + * @throws IOException if the connection or content download fails or if the resource has no content + */ + @Override + public InputStream getInputStream() throws IOException { + connect(); + if (content == null || content.length == 0) { + throw new IOException("No content for resource: " + resourceName); + } + return new ByteArrayInputStream(content); + } + } +} diff --git a/engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceProvider.java b/engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceProvider.java new file mode 100644 index 00000000000..dae0ee077f9 --- /dev/null +++ b/engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceProvider.java @@ -0,0 +1,52 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.engine.util; + +import java.util.concurrent.CompletableFuture; + +/** + * Interface for providing remote resources to the ClassLoader. Plugins can implement this interface and register with + * RemoteFileSourceClassLoader to provide resources from remote sources. + */ +public interface RemoteFileSourceProvider { + /** + * Check if this provider can source the given resource. + * + * @param resourceName the name of the resource to check (e.g., "com/example/MyClass.groovy") + * @return true if this provider can handle the resource, false otherwise + */ + boolean canSourceResource(String resourceName); + + /** + * Check if this provider is currently active and should be used for resource requests. + * + * @return true if this provider is active, false otherwise + */ + boolean isActive(); + + /** + * Check if this provider has any resource paths configured. A provider can be active (execution context set) but + * have no resource paths configured. + * + * @return true if this provider has resource paths configured, false otherwise + */ + boolean hasConfiguredResources(); + + /** + * Check if this provider's execution context is dirty, indicating that remote sources have changed and the cache + * should be cleared. + * + * @return true if this provider is active and dirty, false otherwise + */ + boolean isDirty(); + + /** + * Request a resource from the remote source. + * + * @param resourceName the name of the resource to fetch (e.g., "com/example/MyClass.groovy") + * @return a CompletableFuture containing the resource bytes, or null if not found + */ + CompletableFuture requestResource(String resourceName); +} + diff --git a/engine/context/src/main/java/io/deephaven/engine/context/ExecutionContext.java b/engine/context/src/main/java/io/deephaven/engine/context/ExecutionContext.java index 71b55a68cda..d4622acff56 100644 --- a/engine/context/src/main/java/io/deephaven/engine/context/ExecutionContext.java +++ b/engine/context/src/main/java/io/deephaven/engine/context/ExecutionContext.java @@ -205,6 +205,21 @@ public ExecutionContext withClassLoader(ClassLoader classLoader) { operationInitializer, classLoader); } + /** + * Returns, or creates, an execution context with the given value for {@code queryCompiler} and existing values for + * the other members. + * + * @param queryCompiler the query compiler to use instead + * @return the execution context + */ + public ExecutionContext withQueryCompiler(QueryCompiler queryCompiler) { + if (queryCompiler == this.queryCompiler) { + return this; + } + return new ExecutionContext(isSystemic, authContext, queryLibrary, queryScope, queryCompiler, updateGraph, + operationInitializer); + } + /** * Execute runnable within this execution context. diff --git a/engine/table/src/main/java/io/deephaven/engine/util/AbstractScriptSession.java b/engine/table/src/main/java/io/deephaven/engine/util/AbstractScriptSession.java index eee2d650535..9701bbec46f 100644 --- a/engine/table/src/main/java/io/deephaven/engine/util/AbstractScriptSession.java +++ b/engine/table/src/main/java/io/deephaven/engine/util/AbstractScriptSession.java @@ -69,7 +69,8 @@ private static void ensureDirectory(final Path directory) { protected final File classCacheDirectory; private final ScriptSessionQueryScope queryScope; - protected final ExecutionContext executionContext; + // DH-20578: Not final so it can be updated with fresh QueryCompiler when remote sources are detected + protected ExecutionContext executionContext; private S lastSnapshot; @@ -95,9 +96,26 @@ protected AbstractScriptSession( this.classCacheDirectory = classCacheDirectory; queryScope = new ScriptSessionQueryScope(); + + executionContext = createExecutionContext(updateGraph, operationInitializer, parentClassLoader); + } + + /** + * DH-20578: Creates an ExecutionContext with a QueryCompiler based on the provided ClassLoader. This allows + * updating the ExecutionContext when fresh ClassLoaders are needed (e.g., for remote file sources). + * + * @param updateGraph the update graph for the context + * @param operationInitializer the operation initializer for the context + * @param parentClassLoader the ClassLoader to use for creating the QueryCompiler + * @return a new ExecutionContext with a QueryCompiler based on the provided ClassLoader + */ + protected ExecutionContext createExecutionContext( + final UpdateGraph updateGraph, + final OperationInitializer operationInitializer, + final ClassLoader parentClassLoader) { final QueryCompiler compilerContext = QueryCompilerImpl.create(classCacheDirectory, parentClassLoader); - executionContext = ExecutionContext.newBuilder() + return ExecutionContext.newBuilder() .markSystemic() .newQueryLibrary() .setQueryScope(queryScope) diff --git a/engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java b/engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java index 6bb8c5d262d..89f5efe59b5 100644 --- a/engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java +++ b/engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java @@ -96,7 +96,8 @@ public class GroovyDeephavenSession extends AbstractScriptSession mapping = new ConcurrentHashMap<>(); @Override @@ -131,6 +132,9 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE /** Contains imports to be applied to .groovy files loaded from the classpath */ private final ImportCustomizer loadedGroovyScriptImports; + private final CompilerConfiguration scriptConfig; + private final CompilerConfiguration consoleConfig; + private final Set dynamicClasses; private final Map bindingBackingMap; @@ -162,7 +166,9 @@ public SafeCloseable setScriptPrefix(final String newPrefix) { } } - private final DeephavenGroovyShell groovyShell; + private DeephavenGroovyShell groovyShell; + + private boolean previousEvalHadRemoteSources = false; public static GroovyDeephavenSession of( final UpdateGraph updateGraph, @@ -206,8 +212,8 @@ public static GroovyDeephavenSession of( return new GroovyDeephavenSession(updateGraph, operationInitializer, objectTypeLookup, changeListener, - runScripts, classCacheDirectory, consoleImports, loadedGroovyScriptImports, bindingBackingMap, - groovyShell); + runScripts, classCacheDirectory, consoleImports, loadedGroovyScriptImports, scriptConfig, + consoleConfig, bindingBackingMap, groovyShell); } private GroovyDeephavenSession( @@ -219,6 +225,8 @@ private GroovyDeephavenSession( final File classCacheDirectory, final ImportCustomizer consoleImports, final ImportCustomizer loadedGroovyScriptImports, + final CompilerConfiguration scriptConfig, + final CompilerConfiguration consoleConfig, final Map bindingBackingMap, final DeephavenGroovyShell groovyShell) throws IOException { @@ -229,6 +237,8 @@ private GroovyDeephavenSession( this.consoleImports = consoleImports; this.loadedGroovyScriptImports = loadedGroovyScriptImports; + this.scriptConfig = scriptConfig; + this.consoleConfig = consoleConfig; this.dynamicClasses = new HashSet<>(); this.bindingBackingMap = bindingBackingMap; @@ -332,6 +342,31 @@ protected T getVariable(String name) { } } + /** + * Clears cached .class files and refreshes the GroovyClassLoader and associated shell. This ensures that classes + * will be recompiled from source on the next execution. + * + * @throws IllegalStateException if the cache directory does not exist + */ + private void refreshClassLoader() { + // If no cache directory exists, this is an error - we shouldn't be trying to refresh without a cache + if (classCacheDirectory == null || !classCacheDirectory.exists()) { + throw new IllegalStateException("Cannot refresh classloader: cache directory does not exist"); + } + + deleteCachedClassFiles(classCacheDirectory); + + // Create fresh classloader - reusing the existing configurations + GroovyClassLoader scriptClassLoader = new GroovyClassLoader(STATIC_LOADER, scriptConfig); + + // Update the execution context with a fresh QueryCompiler + final QueryCompiler freshCompiler = QueryCompilerImpl.create(classCacheDirectory, scriptClassLoader); + executionContext = executionContext.withQueryCompiler(freshCompiler); + + // Permanently replace groovyShell with fresh shell + groovyShell = new DeephavenGroovyShell(scriptClassLoader, groovyShell.getContext(), consoleConfig); + } + @Override protected void evaluate(String command, String scriptName) { grepScriptImports(removeComments(command)); @@ -343,8 +378,26 @@ protected void evaluate(String command, String scriptName) { final String currentScriptName = scriptName == null ? DEFAULT_SCRIPT_PREFIX : scriptName.replaceAll("[^0-9A-Za-z_]", "_").replaceAll("(^[0-9])", "_$1"); - try (final SafeCloseable ignored = groovyShell.setScriptPrefix(currentScriptName)) { + final RemoteFileSourceClassLoader remoteLoader = RemoteFileSourceClassLoader.getInstance(); + final boolean hasRemoteSources = remoteLoader.hasConfiguredRemoteSources(); + final boolean isDirty = remoteLoader.isDirty(); + + // Clear the cache in two cases: + // 1. is_dirty flag is set - remote sources have changed + // 2. Previous eval had remote sources but current does not - catches edge case where script is run + // without providing execution context at all, and we need to clear from previous remote source scenario + if (isDirty || (previousEvalHadRemoteSources && !hasRemoteSources)) { + log.debug("Clearing class cache. isDirty: " + isDirty + ", previousEvalHadRemoteSources: " + + previousEvalHadRemoteSources + ", hasRemoteSources: " + hasRemoteSources); + refreshClassLoader(); + } + + // Update state tracker for next execution + previousEvalHadRemoteSources = hasRemoteSources; + + // Execute the script + try (final SafeCloseable ignored = groovyShell.setScriptPrefix(currentScriptName)) { updateClassloader(lastCommand); try { ExecutionContext.getContext().getUpdateGraph().exclusiveLock() @@ -358,6 +411,7 @@ protected void evaluate(String command, String scriptName) { } } + private RuntimeException wrapAndRewriteStackTrace(String scriptName, String currentScriptName, Exception e, String lastCommand, String commandPrefix) { final Exception en = maybeRewriteStackTrace(scriptName, currentScriptName, e, lastCommand, commandPrefix); @@ -688,7 +742,7 @@ && isAnInteger(aClass.getName().substring(SCRIPT_PREFIX.length()))) { // only increment QueryLibrary version if some dynamic class overrides an existing class if (!dynamicClasses.add(entry.getKey()) && !notifiedQueryLibrary) { notifiedQueryLibrary = true; - executionContext.getQueryLibrary().updateVersionString(); + getExecutionContext().getQueryLibrary().updateVersionString(); } try { @@ -716,6 +770,36 @@ private static boolean isAnInteger(final String s) { } } + /** + * Recursively delete all .class files from the cache directory to force recompilation. This is necessary because + * GroovyClassLoader can reload .class files from disk even after clearCache(). + * + * @param directory the directory to recursively search for .class files + */ + private void deleteCachedClassFiles(@NotNull File directory) { + if (!directory.exists() || !directory.isDirectory()) { + return; + } + + File[] files = directory.listFiles(); + if (files == null) { + return; + } + + log.debug().append("Deleting ").append(files.length).append(" files from class cache directory: ") + .append(directory.getAbsolutePath()); + + for (File file : files) { + if (file.isDirectory()) { + deleteCachedClassFiles(file); + } else if (file.getName().endsWith(".class")) { + if (!file.delete()) { + log.warn("Failed to delete cached class file: " + file.getAbsolutePath()); + } + } + } + } + @Override protected GroovySnapshot emptySnapshot() { return new GroovySnapshot(Collections.emptyMap()); diff --git a/go/internal/proto/remotefilesource/remotefilesource.pb.go b/go/internal/proto/remotefilesource/remotefilesource.pb.go new file mode 100644 index 00000000000..619507922bf --- /dev/null +++ b/go/internal/proto/remotefilesource/remotefilesource.pb.go @@ -0,0 +1,731 @@ +// +// Copyright (c) 2016-2025 Deephaven Data Labs and Patent Pending + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v6.33.2 +// source: deephaven_core/proto/remotefilesource.proto + +package remotefilesource + +import ( + ticket "github.com/deephaven/deephaven-core/go/internal/proto/ticket" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Server → Client: Requests sent from server to client via MessageStream +type RemoteFileSourceServerMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Unique identifier for this request, used to correlate the response + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Types that are assignable to Request: + // *RemoteFileSourceServerMessage_MetaRequest + // *RemoteFileSourceServerMessage_SetExecutionContextResponse + Request isRemoteFileSourceServerMessage_Request `protobuf_oneof:"request"` +} + +func (x *RemoteFileSourceServerMessage) Reset() { + *x = RemoteFileSourceServerMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoteFileSourceServerMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoteFileSourceServerMessage) ProtoMessage() {} + +func (x *RemoteFileSourceServerMessage) ProtoReflect() protoreflect.Message { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoteFileSourceServerMessage.ProtoReflect.Descriptor instead. +func (*RemoteFileSourceServerMessage) Descriptor() ([]byte, []int) { + return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{0} +} + +func (x *RemoteFileSourceServerMessage) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (m *RemoteFileSourceServerMessage) GetRequest() isRemoteFileSourceServerMessage_Request { + if m != nil { + return m.Request + } + return nil +} + +func (x *RemoteFileSourceServerMessage) GetMetaRequest() *RemoteFileSourceMetaRequest { + if x, ok := x.GetRequest().(*RemoteFileSourceServerMessage_MetaRequest); ok { + return x.MetaRequest + } + return nil +} + +func (x *RemoteFileSourceServerMessage) GetSetExecutionContextResponse() *SetExecutionContextResponse { + if x, ok := x.GetRequest().(*RemoteFileSourceServerMessage_SetExecutionContextResponse); ok { + return x.SetExecutionContextResponse + } + return nil +} + +type isRemoteFileSourceServerMessage_Request interface { + isRemoteFileSourceServerMessage_Request() +} + +type RemoteFileSourceServerMessage_MetaRequest struct { + // Request source data/resource from the client + MetaRequest *RemoteFileSourceMetaRequest `protobuf:"bytes,2,opt,name=meta_request,json=metaRequest,proto3,oneof"` +} + +type RemoteFileSourceServerMessage_SetExecutionContextResponse struct { + // Acknowledgment that execution context was set + SetExecutionContextResponse *SetExecutionContextResponse `protobuf:"bytes,3,opt,name=set_execution_context_response,json=setExecutionContextResponse,proto3,oneof"` +} + +func (*RemoteFileSourceServerMessage_MetaRequest) isRemoteFileSourceServerMessage_Request() {} + +func (*RemoteFileSourceServerMessage_SetExecutionContextResponse) isRemoteFileSourceServerMessage_Request() { +} + +// Client → Server: Requests/responses sent from client to server via MessageStream +type RemoteFileSourceClientMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The request_id from the ServerRequest this is responding to (if applicable) + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Types that are assignable to Request: + // *RemoteFileSourceClientMessage_MetaResponse + // *RemoteFileSourceClientMessage_SetExecutionContext + Request isRemoteFileSourceClientMessage_Request `protobuf_oneof:"request"` +} + +func (x *RemoteFileSourceClientMessage) Reset() { + *x = RemoteFileSourceClientMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoteFileSourceClientMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoteFileSourceClientMessage) ProtoMessage() {} + +func (x *RemoteFileSourceClientMessage) ProtoReflect() protoreflect.Message { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoteFileSourceClientMessage.ProtoReflect.Descriptor instead. +func (*RemoteFileSourceClientMessage) Descriptor() ([]byte, []int) { + return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{1} +} + +func (x *RemoteFileSourceClientMessage) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (m *RemoteFileSourceClientMessage) GetRequest() isRemoteFileSourceClientMessage_Request { + if m != nil { + return m.Request + } + return nil +} + +func (x *RemoteFileSourceClientMessage) GetMetaResponse() *RemoteFileSourceMetaResponse { + if x, ok := x.GetRequest().(*RemoteFileSourceClientMessage_MetaResponse); ok { + return x.MetaResponse + } + return nil +} + +func (x *RemoteFileSourceClientMessage) GetSetExecutionContext() *SetExecutionContextRequest { + if x, ok := x.GetRequest().(*RemoteFileSourceClientMessage_SetExecutionContext); ok { + return x.SetExecutionContext + } + return nil +} + +type isRemoteFileSourceClientMessage_Request interface { + isRemoteFileSourceClientMessage_Request() +} + +type RemoteFileSourceClientMessage_MetaResponse struct { + // Response to a resource request + MetaResponse *RemoteFileSourceMetaResponse `protobuf:"bytes,2,opt,name=meta_response,json=metaResponse,proto3,oneof"` +} + +type RemoteFileSourceClientMessage_SetExecutionContext struct { + // Set the execution context for script execution + SetExecutionContext *SetExecutionContextRequest `protobuf:"bytes,3,opt,name=set_execution_context,json=setExecutionContext,proto3,oneof"` +} + +func (*RemoteFileSourceClientMessage_MetaResponse) isRemoteFileSourceClientMessage_Request() {} + +func (*RemoteFileSourceClientMessage_SetExecutionContext) isRemoteFileSourceClientMessage_Request() {} + +// Request source data/resource from the client +type RemoteFileSourceMetaRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The name/path of the resource being requested (e.g., "com/example/MyClass.java") + ResourceName string `protobuf:"bytes,1,opt,name=resource_name,json=resourceName,proto3" json:"resource_name,omitempty"` +} + +func (x *RemoteFileSourceMetaRequest) Reset() { + *x = RemoteFileSourceMetaRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoteFileSourceMetaRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoteFileSourceMetaRequest) ProtoMessage() {} + +func (x *RemoteFileSourceMetaRequest) ProtoReflect() protoreflect.Message { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoteFileSourceMetaRequest.ProtoReflect.Descriptor instead. +func (*RemoteFileSourceMetaRequest) Descriptor() ([]byte, []int) { + return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{2} +} + +func (x *RemoteFileSourceMetaRequest) GetResourceName() string { + if x != nil { + return x.ResourceName + } + return "" +} + +// Response to a resource request +type RemoteFileSourceMetaResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The content of the resource, or empty if not found + Content []byte `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"` + // Indicates whether the resource was found + Found bool `protobuf:"varint,2,opt,name=found,proto3" json:"found,omitempty"` + // Error message if the resource could not be retrieved + Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *RemoteFileSourceMetaResponse) Reset() { + *x = RemoteFileSourceMetaResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoteFileSourceMetaResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoteFileSourceMetaResponse) ProtoMessage() {} + +func (x *RemoteFileSourceMetaResponse) ProtoReflect() protoreflect.Message { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoteFileSourceMetaResponse.ProtoReflect.Descriptor instead. +func (*RemoteFileSourceMetaResponse) Descriptor() ([]byte, []int) { + return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{3} +} + +func (x *RemoteFileSourceMetaResponse) GetContent() []byte { + if x != nil { + return x.Content + } + return nil +} + +func (x *RemoteFileSourceMetaResponse) GetFound() bool { + if x != nil { + return x.Found + } + return false +} + +func (x *RemoteFileSourceMetaResponse) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +// Request to set the execution context for script execution +type SetExecutionContextRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Resource paths that should be resolved from the remote source + // (e.g., ["com/example/Test.groovy", "org/mycompany/Utils.groovy"]) + ResourcePaths []string `protobuf:"bytes,1,rep,name=resource_paths,json=resourcePaths,proto3" json:"resource_paths,omitempty"` + // Indicates whether remote sources have changed and cache should be cleared + IsDirty bool `protobuf:"varint,2,opt,name=is_dirty,json=isDirty,proto3" json:"is_dirty,omitempty"` +} + +func (x *SetExecutionContextRequest) Reset() { + *x = SetExecutionContextRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetExecutionContextRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetExecutionContextRequest) ProtoMessage() {} + +func (x *SetExecutionContextRequest) ProtoReflect() protoreflect.Message { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetExecutionContextRequest.ProtoReflect.Descriptor instead. +func (*SetExecutionContextRequest) Descriptor() ([]byte, []int) { + return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{4} +} + +func (x *SetExecutionContextRequest) GetResourcePaths() []string { + if x != nil { + return x.ResourcePaths + } + return nil +} + +func (x *SetExecutionContextRequest) GetIsDirty() bool { + if x != nil { + return x.IsDirty + } + return false +} + +// Response acknowledging execution context was set +type SetExecutionContextResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Whether the operation was successful + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *SetExecutionContextResponse) Reset() { + *x = SetExecutionContextResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetExecutionContextResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetExecutionContextResponse) ProtoMessage() {} + +func (x *SetExecutionContextResponse) ProtoReflect() protoreflect.Message { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetExecutionContextResponse.ProtoReflect.Descriptor instead. +func (*SetExecutionContextResponse) Descriptor() ([]byte, []int) { + return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{5} +} + +func (x *SetExecutionContextResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +// Fetch the remote file source plugin into the specified ticket (Flight command, not MessageStream) +type RemoteFileSourcePluginFetchRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ResultId *ticket.Ticket `protobuf:"bytes,1,opt,name=result_id,json=resultId,proto3" json:"result_id,omitempty"` + // The plugin name to create the PluginMarker for. + PluginName string `protobuf:"bytes,2,opt,name=plugin_name,json=pluginName,proto3" json:"plugin_name,omitempty"` +} + +func (x *RemoteFileSourcePluginFetchRequest) Reset() { + *x = RemoteFileSourcePluginFetchRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoteFileSourcePluginFetchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoteFileSourcePluginFetchRequest) ProtoMessage() {} + +func (x *RemoteFileSourcePluginFetchRequest) ProtoReflect() protoreflect.Message { + mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoteFileSourcePluginFetchRequest.ProtoReflect.Descriptor instead. +func (*RemoteFileSourcePluginFetchRequest) Descriptor() ([]byte, []int) { + return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{6} +} + +func (x *RemoteFileSourcePluginFetchRequest) GetResultId() *ticket.Ticket { + if x != nil { + return x.ResultId + } + return nil +} + +func (x *RemoteFileSourcePluginFetchRequest) GetPluginName() string { + if x != nil { + return x.PluginName + } + return "" +} + +var File_deephaven_core_proto_remotefilesource_proto protoreflect.FileDescriptor + +var file_deephaven_core_proto_remotefilesource_proto_rawDesc = []byte{ + 0x0a, 0x2b, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x66, 0x69, 0x6c, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x69, + 0x6f, 0x2e, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x1a, 0x21, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0xb6, 0x02, 0x0a, 0x1d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, 0x69, + 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x49, 0x64, 0x12, 0x63, 0x0a, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x5f, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x69, 0x6f, 0x2e, + 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x62, 0x61, 0x63, 0x6b, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, + 0x65, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6d, 0x65, + 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x85, 0x01, 0x0a, 0x1e, 0x73, 0x65, + 0x74, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65, + 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x48, 0x00, 0x52, 0x1b, 0x73, 0x65, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x42, 0x09, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa6, 0x02, 0x0a, + 0x1d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x66, 0x0a, + 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, + 0x76, 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, + 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, 0x15, 0x73, 0x65, 0x74, 0x5f, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, + 0x76, 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x13, 0x73, 0x65, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x42, 0x0a, 0x1b, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, + 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x64, 0x0a, 0x1c, 0x52, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, + 0x5e, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, + 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, + 0x61, 0x74, 0x68, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x64, 0x69, 0x72, 0x74, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x44, 0x69, 0x72, 0x74, 0x79, 0x22, + 0x37, 0x0a, 0x1b, 0x53, 0x65, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x8d, 0x01, 0x0a, 0x22, 0x52, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x46, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65, + 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x08, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x4c, 0x48, 0x01, 0x50, 0x01, 0x5a, 0x46, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x65, 0x70, 0x68, + 0x61, 0x76, 0x65, 0x6e, 0x2f, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x2d, 0x63, + 0x6f, 0x72, 0x65, 0x2f, 0x67, 0x6f, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_deephaven_core_proto_remotefilesource_proto_rawDescOnce sync.Once + file_deephaven_core_proto_remotefilesource_proto_rawDescData = file_deephaven_core_proto_remotefilesource_proto_rawDesc +) + +func file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP() []byte { + file_deephaven_core_proto_remotefilesource_proto_rawDescOnce.Do(func() { + file_deephaven_core_proto_remotefilesource_proto_rawDescData = protoimpl.X.CompressGZIP(file_deephaven_core_proto_remotefilesource_proto_rawDescData) + }) + return file_deephaven_core_proto_remotefilesource_proto_rawDescData +} + +var file_deephaven_core_proto_remotefilesource_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_deephaven_core_proto_remotefilesource_proto_goTypes = []interface{}{ + (*RemoteFileSourceServerMessage)(nil), // 0: io.deephaven.proto.backplane.grpc.RemoteFileSourceServerMessage + (*RemoteFileSourceClientMessage)(nil), // 1: io.deephaven.proto.backplane.grpc.RemoteFileSourceClientMessage + (*RemoteFileSourceMetaRequest)(nil), // 2: io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaRequest + (*RemoteFileSourceMetaResponse)(nil), // 3: io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaResponse + (*SetExecutionContextRequest)(nil), // 4: io.deephaven.proto.backplane.grpc.SetExecutionContextRequest + (*SetExecutionContextResponse)(nil), // 5: io.deephaven.proto.backplane.grpc.SetExecutionContextResponse + (*RemoteFileSourcePluginFetchRequest)(nil), // 6: io.deephaven.proto.backplane.grpc.RemoteFileSourcePluginFetchRequest + (*ticket.Ticket)(nil), // 7: io.deephaven.proto.backplane.grpc.Ticket +} +var file_deephaven_core_proto_remotefilesource_proto_depIdxs = []int32{ + 2, // 0: io.deephaven.proto.backplane.grpc.RemoteFileSourceServerMessage.meta_request:type_name -> io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaRequest + 5, // 1: io.deephaven.proto.backplane.grpc.RemoteFileSourceServerMessage.set_execution_context_response:type_name -> io.deephaven.proto.backplane.grpc.SetExecutionContextResponse + 3, // 2: io.deephaven.proto.backplane.grpc.RemoteFileSourceClientMessage.meta_response:type_name -> io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaResponse + 4, // 3: io.deephaven.proto.backplane.grpc.RemoteFileSourceClientMessage.set_execution_context:type_name -> io.deephaven.proto.backplane.grpc.SetExecutionContextRequest + 7, // 4: io.deephaven.proto.backplane.grpc.RemoteFileSourcePluginFetchRequest.result_id:type_name -> io.deephaven.proto.backplane.grpc.Ticket + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_deephaven_core_proto_remotefilesource_proto_init() } +func file_deephaven_core_proto_remotefilesource_proto_init() { + if File_deephaven_core_proto_remotefilesource_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_deephaven_core_proto_remotefilesource_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoteFileSourceServerMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_deephaven_core_proto_remotefilesource_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoteFileSourceClientMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_deephaven_core_proto_remotefilesource_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoteFileSourceMetaRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_deephaven_core_proto_remotefilesource_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoteFileSourceMetaResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_deephaven_core_proto_remotefilesource_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetExecutionContextRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_deephaven_core_proto_remotefilesource_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetExecutionContextResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_deephaven_core_proto_remotefilesource_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoteFileSourcePluginFetchRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_deephaven_core_proto_remotefilesource_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*RemoteFileSourceServerMessage_MetaRequest)(nil), + (*RemoteFileSourceServerMessage_SetExecutionContextResponse)(nil), + } + file_deephaven_core_proto_remotefilesource_proto_msgTypes[1].OneofWrappers = []interface{}{ + (*RemoteFileSourceClientMessage_MetaResponse)(nil), + (*RemoteFileSourceClientMessage_SetExecutionContext)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_deephaven_core_proto_remotefilesource_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_deephaven_core_proto_remotefilesource_proto_goTypes, + DependencyIndexes: file_deephaven_core_proto_remotefilesource_proto_depIdxs, + MessageInfos: file_deephaven_core_proto_remotefilesource_proto_msgTypes, + }.Build() + File_deephaven_core_proto_remotefilesource_proto = out.File + file_deephaven_core_proto_remotefilesource_proto_rawDesc = nil + file_deephaven_core_proto_remotefilesource_proto_goTypes = nil + file_deephaven_core_proto_remotefilesource_proto_depIdxs = nil +} diff --git a/plugin/remotefilesource/build.gradle b/plugin/remotefilesource/build.gradle new file mode 100644 index 00000000000..61e914c8f88 --- /dev/null +++ b/plugin/remotefilesource/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'io.deephaven.project.register' +} + +dependencies { + implementation project(':plugin') + implementation project(':server') + implementation project(':proto:proto-backplane-grpc') + implementation project(':proto:proto-backplane-grpc-flight') + + compileOnly libs.autoservice + compileOnly libs.jetbrains.annotations + annotationProcessor libs.autoservice.compiler +} \ No newline at end of file diff --git a/plugin/remotefilesource/gradle.properties b/plugin/remotefilesource/gradle.properties new file mode 100644 index 00000000000..c186bbfdde1 --- /dev/null +++ b/plugin/remotefilesource/gradle.properties @@ -0,0 +1 @@ +io.deephaven.project.ProjectType=JAVA_PUBLIC diff --git a/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceCommandResolver.java b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceCommandResolver.java new file mode 100644 index 00000000000..5c8bef481ff --- /dev/null +++ b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceCommandResolver.java @@ -0,0 +1,266 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.remotefilesource; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.rpc.Code; +import io.deephaven.UncheckedDeephavenException; +import io.deephaven.base.verify.Assert; +import io.deephaven.internal.log.LoggerFactory; +import io.deephaven.io.logger.Logger; +import io.deephaven.plugin.type.PluginMarker; +import io.deephaven.proto.backplane.grpc.RemoteFileSourcePluginFetchRequest; +import io.deephaven.proto.backplane.grpc.Ticket; +import io.deephaven.proto.util.Exceptions; +import io.deephaven.server.session.CommandResolver; +import io.deephaven.server.session.SessionState; +import io.deephaven.server.session.TicketRouter; +import io.deephaven.server.session.WantsTicketRouter; + +import io.grpc.StatusRuntimeException; +import org.apache.arrow.flight.impl.Flight; +import org.jetbrains.annotations.Nullable; + +import java.nio.ByteBuffer; +import java.util.function.Consumer; + +public class RemoteFileSourceCommandResolver implements CommandResolver, WantsTicketRouter { + private static final Logger log = LoggerFactory.getLogger(RemoteFileSourceCommandResolver.class); + + private static final String FETCH_PLUGIN_TYPE_URL = + "type.googleapis.com/" + RemoteFileSourcePluginFetchRequest.getDescriptor().getFullName(); + + /** + * Parses a RemoteFileSourcePluginFetchRequest from the given Any command. + * + * @param command the Any command containing the fetch request + * @return the parsed RemoteFileSourcePluginFetchRequest + * @throws IllegalArgumentException if the command type URL doesn't match the expected fetch plugin type + * @throws UncheckedDeephavenException if the command cannot be parsed as a RemoteFileSourcePluginFetchRequest + */ + private static RemoteFileSourcePluginFetchRequest parseFetchRequest(final Any command) { + if (!FETCH_PLUGIN_TYPE_URL.equals(command.getTypeUrl())) { + throw new IllegalArgumentException("Not a valid remotefilesource command: " + command.getTypeUrl()); + } + + try { + return RemoteFileSourcePluginFetchRequest.parseFrom(command.getValue()); + } catch (InvalidProtocolBufferException e) { + throw new UncheckedDeephavenException("Could not parse RemoteFileSourcePluginFetchRequest", e); + } + } + + /** + * Attempts to parse ByteString data as a protobuf Any message. Returns null if parsing fails rather than throwing + * an exception, allowing callers to handle invalid data gracefully. + * + * @param data the ByteString data to parse + * @return the parsed Any message, or null if parsing fails + */ + private static Any parseAsAnyOrNull(final ByteString data) { + try { + return Any.parseFrom(data); + } catch (final InvalidProtocolBufferException e) { + return null; + } + } + + /** + * Exports the PluginMarker singleton based on the fetch request. The marker object is exported to the session using + * the result ticket specified in the request, and flight info is returned containing the endpoint for accessing it. + * + *

+ * Note: This exports a PluginMarker for the specified plugin name. Plugin-specific routing is handled by + * TypedTicket.type in the ConnectRequest phase, which is validated against the plugin's name() method. + * + * @param session the session state for the current request + * @param descriptor the flight descriptor containing the command + * @param request the parsed RemoteFileSourcePluginFetchRequest containing the result ticket + * @return a FlightInfo export object containing the plugin endpoint information + * @throws StatusRuntimeException if the request doesn't contain a valid result ID ticket or plugin name + */ + private static SessionState.ExportObject fetchPlugin(@Nullable final SessionState session, + final Flight.FlightDescriptor descriptor, + final RemoteFileSourcePluginFetchRequest request) { + final Ticket resultTicket = request.getResultId(); + final boolean hasResultId = !resultTicket.getTicket().isEmpty(); + if (!hasResultId) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "RemoteFileSourcePluginFetchRequest must contain a valid result_id"); + } + + final String pluginName = request.getPluginName(); + if (pluginName.isEmpty()) { + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "RemoteFileSourcePluginFetchRequest must contain a valid plugin_name"); + } + + // Export a plugin-specific PluginMarker. Plugins using PluginMarker should check + // marker.getPluginName() in isType() to prevent conflicts with markers for other plugins. + session.newExport(resultTicket, "RemoteFileSourcePluginFetchRequest.resultTicket") + .submit(() -> PluginMarker.forPluginName(pluginName)); + + final Flight.FlightInfo flightInfo = Flight.FlightInfo.newBuilder() + .setFlightDescriptor(descriptor) + .addEndpoint(Flight.FlightEndpoint.newBuilder() + .setTicket(Flight.Ticket.newBuilder() + .setTicket( + resultTicket.getTicket())) + .build()) + .setTotalRecords(-1) + .setTotalBytes(-1) + .build(); + + return SessionState.wrapAsExport(flightInfo); + } + + /** + * Resolves a flight descriptor to flight info for remote file source commands. Handles + * RemoteFileSourcePluginFetchRequest commands by parsing the descriptor and delegating to the appropriate handler + * method. + * + * @param session the session state for the current request + * @param descriptor the flight descriptor containing the command + * @param logId the log identifier for tracking + * @return a FlightInfo export object for the requested command + * @throws StatusRuntimeException if session is null (UNAUTHENTICATED), the command cannot be parsed, or the command + * type URL is not recognized + */ + @Override + public SessionState.ExportObject flightInfoFor(@Nullable final SessionState session, + final Flight.FlightDescriptor descriptor, + final String logId) { + if (session == null) { + throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, + "Could not resolve '" + logId + "': no session available"); + } + + final Any request = parseAsAnyOrNull(descriptor.getCmd()); + if (request == null) { + log.error().append("Could not parse remotefilesource command.").endl(); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, + "Could not parse remotefilesource command Any."); + } + + if (!FETCH_PLUGIN_TYPE_URL.equals(request.getTypeUrl())) { + log.error().append("Invalid remotefilesource command typeUrl: " + request.getTypeUrl()).endl(); + throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Invalid typeUrl: " + request.getTypeUrl()); + } + + return fetchPlugin(session, descriptor, parseFetchRequest(request)); + } + + /** + * Visits all flight info that this resolver exposes. + * + *

+ * Not implemented: This resolver does not expose any flight info via list flights. + */ + @Override + public void forAllFlightInfo(@Nullable final SessionState session, final Consumer visitor) { + // nothing to do + } + + /** + * Returns a log-friendly name for the given ticket. + * + * @throws UnsupportedOperationException always, as this resolver does not support ticket-based routing + */ + @Override + public String getLogNameFor(final ByteBuffer ticket, final String logId) { + // no ticket-based routing + throw new UnsupportedOperationException(); + } + + /** + * Determines if this resolver is responsible for handling the given command descriptor. This resolver handles + * commands with type URL matching RemoteFileSourcePluginFetchRequest. + * + * @param descriptor the flight descriptor containing the command + * @return true if this resolver handles the command, false otherwise + */ + @Override + public boolean handlesCommand(final Flight.FlightDescriptor descriptor) { + Assert.eq(descriptor.getType(), "descriptor.getType()", Flight.FlightDescriptor.DescriptorType.CMD, "CMD"); + + final Any command = parseAsAnyOrNull(descriptor.getCmd()); + if (command == null) { + return false; + } + + return FETCH_PLUGIN_TYPE_URL.equals(command.getTypeUrl()); + } + + /** + * Publishes an export to the session using a ticket. + * + * @throws UnsupportedOperationException always, as this resolver does not support publishing + */ + @Override + public SessionState.ExportBuilder publish(final SessionState session, + final ByteBuffer ticket, + final String logId, + @Nullable final Runnable onPublish) { + throw new UnsupportedOperationException(); + } + + /** + * Publishes an export to the session using a flight descriptor. + * + * @throws UnsupportedOperationException always, as this resolver does not support publishing + */ + @Override + public SessionState.ExportBuilder publish(final SessionState session, + final Flight.FlightDescriptor descriptor, final String logId, + @Nullable final Runnable onPublish) { + throw new UnsupportedOperationException(); + } + + /** + * Resolves a flight descriptor to an export object. + * + * @throws UnsupportedOperationException always, use flightInfoFor() instead + */ + @Override + public SessionState.ExportObject resolve(@Nullable final SessionState session, + final Flight.FlightDescriptor descriptor, + final String logId) { + throw new UnsupportedOperationException(); + } + + /** + * Resolves a ticket to an export object. + * + * @throws UnsupportedOperationException always, as this resolver does not support ticket-based routing + */ + @Override + public SessionState.ExportObject resolve(@Nullable final SessionState session, + final ByteBuffer ticket, + final String logId) { + throw new UnsupportedOperationException(); + } + + /** + * Sets the ticket router for this resolver. + * + *

+ * Not implemented: This resolver does not need access to the ticket router. + */ + @Override + public void setTicketRouter(TicketRouter ticketRouter) { + // not needed + } + + /** + * Returns the ticket route byte for this resolver. This resolver does not use ticket-based routing, so returns 0. + * + * @return 0, indicating no ticket routing + */ + @Override + public byte ticketRoute() { + return 0; + } +} diff --git a/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceMessageStream.java b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceMessageStream.java new file mode 100644 index 00000000000..a61b89834d2 --- /dev/null +++ b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceMessageStream.java @@ -0,0 +1,404 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.remotefilesource; + +import com.google.protobuf.InvalidProtocolBufferException; +import io.deephaven.engine.util.RemoteFileSourceClassLoader; +import io.deephaven.engine.util.RemoteFileSourceProvider; +import io.deephaven.internal.log.LoggerFactory; +import io.deephaven.io.logger.Logger; +import io.deephaven.plugin.type.ObjectCommunicationException; +import io.deephaven.plugin.type.ObjectType; +import io.deephaven.proto.backplane.grpc.RemoteFileSourceClientMessage; +import io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaRequest; +import io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaResponse; +import io.deephaven.proto.backplane.grpc.RemoteFileSourceServerMessage; +import io.deephaven.proto.backplane.grpc.SetExecutionContextRequest; +import io.deephaven.proto.backplane.grpc.SetExecutionContextResponse; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Message stream implementation for RemoteFileSource bidirectional communication. Each instance represents a file + * source provider for one client connection and implements RemoteFileSourceProvider so it can be registered with the + * RemoteFileSourceClassLoader. Only one MessageStream can be "active" at a time (determined by the execution context). + * The RemoteFileSourceClassLoader checks isActive() on each registered provider to find the active one. + */ +public class RemoteFileSourceMessageStream implements ObjectType.MessageStream, RemoteFileSourceProvider { + private static final Logger log = LoggerFactory.getLogger(RemoteFileSourceMessageStream.class); + + /** + * The current execution context containing the active message stream and configuration. Null when no execution + * context is active. Used by this class's isActive() and canSourceResource() methods to determine if this provider + * should handle resource requests from RemoteFileSourceClassLoader. + */ + private static volatile RemoteFileSourceExecutionContext executionContext; + + + private final ObjectType.MessageStream connection; + private final Map> pendingRequests = new ConcurrentHashMap<>(); + + /** + * Creates a new RemoteFileSourceMessageStream for the given connection. Automatically registers this instance as a + * provider with the RemoteFileSourceClassLoader. + * + * @param connection the message stream connection to the client + * @throws ObjectCommunicationException if the initial message cannot be sent to the client + */ + public RemoteFileSourceMessageStream(final ObjectType.MessageStream connection) + throws ObjectCommunicationException { + this.connection = connection; + // Send initial empty message to client as required by the ObjectType contract + connection.onData(ByteBuffer.allocate(0)); + // Register this instance as a provider with the RemoteFileSourceClassLoader + registerWithClassLoader(); + } + + /** + * Determines if this provider can source the specified resource. Only returns true if this message stream is + * active, the resource is a .groovy file, and the resource path matches one of the configured resource paths. + * + * @param resourceName the name of the resource to check + * @return true if this provider can source the resource, false otherwise + */ + @Override + public boolean canSourceResource(String resourceName) { + if (!isActive()) { + return false; + } + + // Only handle .groovy source files, not compiled .class files + if (!resourceName.endsWith(".groovy")) { + return false; + } + + for (String contextResourcePath : executionContext.getResourcePaths()) { + if (resourceName.equals(contextResourcePath)) { + log.debug().append("Can source: ").append(resourceName).endl(); + return true; + } + } + + return false; + } + + /** + * Requests a resource from the remote client. Sends a request to the client and returns a future that will be + * completed when the client responds. Only services requests if this message stream is active. + * + * @param resourceName the name of the resource to request + * @return a CompletableFuture that will contain the resource bytes when available, or null if inactive + */ + @Override + public CompletableFuture requestResource(String resourceName) { + // Only service requests if this instance is active + if (!isActive()) { + log.warn().append("Request for resource ").append(resourceName) + .append(" on inactive message stream").endl(); + return CompletableFuture.failedFuture(new IllegalStateException("Inactive message stream")); + } + + log.info().append("Requesting resource: ").append(resourceName).endl(); + + String requestId = UUID.randomUUID().toString(); + CompletableFuture future = new CompletableFuture<>(); + pendingRequests.put(requestId, future); + + try { + // Build RemoteFileSourceMetaRequest proto + RemoteFileSourceMetaRequest metaRequest = + RemoteFileSourceMetaRequest.newBuilder() + .setResourceName(resourceName) + .build(); + + // Wrap in RemoteFileSourceServerMessage (server→client) + RemoteFileSourceServerMessage message = + RemoteFileSourceServerMessage.newBuilder() + .setRequestId(requestId) + .setMetaRequest(metaRequest) + .build(); + + ByteBuffer buffer = ByteBuffer.wrap(message.toByteArray()); + + log.info().append("Sending resource request for: ").append(resourceName) + .append(" with requestId: ").append(requestId).endl(); + + connection.onData(buffer); + } catch (ObjectCommunicationException e) { + future.completeExceptionally(e); + pendingRequests.remove(requestId); + } + + return future; + } + + /** + * Checks if this message stream is currently active. A message stream is active when the execution context is set + * and this instance is the active stream. + * + * @return true if this message stream is active, false otherwise + */ + @Override + public boolean isActive() { + RemoteFileSourceExecutionContext context = executionContext; + return context != null && context.getActiveMessageStream() == this; + } + + /** + * Checks if this provider has any resource paths configured. + * + * @return true if this provider is active and has non-empty resource paths, false otherwise + */ + @Override + public boolean hasConfiguredResources() { + return isActive() && !executionContext.getResourcePaths().isEmpty(); + } + + /** + * Checks if this provider's execution context is dirty. + * + * @return true if this provider is active and the execution context is dirty, false otherwise + */ + @Override + public boolean isDirty() { + return isActive() && executionContext.isDirty(); + } + + /** + * Sets the execution context with the active message stream and resource paths. + * + *

+ * This static method establishes which message stream instance should be considered "active" for resource requests, + * and which resource paths should be resolved from that remote source. Only one execution context can be active at + * a time across all instances. + * + *

+ * In multi-client scenarios (Community Core), this ensures that only the message stream for the currently executing + * script is active, preventing resource requests from being serviced by the wrong client connection. + * + *

+ * Typical Usage: Called at the beginning of script execution to establish which .groovy files should be + * sourced from the remote client rather than the local classpath. + * + * @param messageStream the message stream to set as active (must not be null) + * @param resourcePaths list of resource paths (e.g., "package/MyScript.groovy") to resolve from remote source + * @param isDirty whether remote sources have changed and cache should be cleared + * @throws IllegalArgumentException if messageStream is null + */ + public static void setExecutionContext(RemoteFileSourceMessageStream messageStream, List resourcePaths, + boolean isDirty) { + if (messageStream == null) { + throw new IllegalArgumentException("messageStream must not be null"); + } + + executionContext = new RemoteFileSourceExecutionContext(messageStream, resourcePaths, isDirty); + log.info().append("Set execution context with ") + .append(executionContext.getResourcePaths().size()).append(" resource paths") + .append(", isDirty: ").append(isDirty).endl(); + } + + /** + * Clears the execution context. + */ + public static void clearExecutionContext() { + if (executionContext != null) { + executionContext = null; + log.info().append("Cleared execution context").endl(); + } + } + + + /** + * Handles incoming data from the client. Parses RemoteFileSourceClientMessage messages and processes meta responses + * or execution context updates from the client. + * + * @param payload the message payload containing the protobuf data + * @param references optional references (not used) + * @throws ObjectCommunicationException if the message cannot be parsed + */ + @Override + public void onData(ByteBuffer payload, Object... references) throws ObjectCommunicationException { + try { + byte[] bytes = new byte[payload.remaining()]; + payload.get(bytes); + RemoteFileSourceClientMessage message = RemoteFileSourceClientMessage.parseFrom(bytes); + + if (message.hasMetaResponse()) { + handleMetaResponse(message.getRequestId(), message.getMetaResponse()); + } else if (message.hasSetExecutionContext()) { + handleSetExecutionContext(message.getRequestId(), message.getSetExecutionContext()); + } else { + log.error().append("Received unknown message type from client").endl(); + throw new ObjectCommunicationException("Received unknown message type from client"); + } + } catch (InvalidProtocolBufferException e) { + log.error().append("Failed to parse RemoteFileSourceClientMessage: ").append(e).endl(); + throw new ObjectCommunicationException("Failed to parse message", e); + } + } + + /** + * Handles a meta response from the client containing requested resource content. + * + * @param requestId the request ID + * @param response the meta response from the client + */ + private void handleMetaResponse(String requestId, RemoteFileSourceMetaResponse response) { + CompletableFuture future = pendingRequests.remove(requestId); + if (future == null) { + log.warn().append("Received response for unknown requestId: ").append(requestId).endl(); + return; + } + + byte[] content = response.getContent().toByteArray(); + + log.info().append("Received resource response for requestId: ").append(requestId) + .append(", found: ").append(response.getFound()) + .append(", content length: ").append(content.length).endl(); + + if (!response.getError().isEmpty()) { + log.warn().append("Error in response: ").append(response.getError()).endl(); + } + + future.complete(content); + } + + /** + * Handles a request from the client to set the execution context. + * + * @param requestId the request ID + * @param setExecutionContext the SetExecutionContextRequest containing resource paths and isDirty flag + */ + private void handleSetExecutionContext(String requestId, SetExecutionContextRequest setExecutionContext) { + boolean isDirty = setExecutionContext.getIsDirty(); + List resourcePaths = setExecutionContext.getResourcePathsList(); + + setExecutionContext(this, resourcePaths, isDirty); + log.info().append("Client set execution context for this message stream with ") + .append(resourcePaths.size()).append(" resource paths") + .append(", isDirty: ").append(isDirty).endl(); + + sendExecutionContextAcknowledgment(requestId); + } + + /** + * Sends an acknowledgment to the client that the execution context was successfully set. + * + * @param requestId the request ID to acknowledge + */ + private void sendExecutionContextAcknowledgment(String requestId) { + SetExecutionContextResponse response = SetExecutionContextResponse.newBuilder() + .setSuccess(true) + .build(); + + RemoteFileSourceServerMessage serverRequest = RemoteFileSourceServerMessage.newBuilder() + .setRequestId(requestId) + .setSetExecutionContextResponse(response) + .build(); + + try { + connection.onData(ByteBuffer.wrap(serverRequest.toByteArray())); + } catch (ObjectCommunicationException e) { + log.error().append("Failed to send execution context acknowledgment: ").append(e).endl(); + } + } + + /** + * Handles cleanup when the message stream is closed. Unregisters this provider from the + * RemoteFileSourceClassLoader, clears the execution context if this was active, and cancels all pending resource + * requests. + */ + @Override + public void onClose() { + // Unregister this provider from the RemoteFileSourceClassLoader + unregisterFromClassLoader(); + + // Clear execution context if this was the active stream + if (isActive()) { + clearExecutionContext(); + } + + // Cancel all pending requests + pendingRequests.values().forEach(future -> future.cancel(true)); + pendingRequests.clear(); + } + + /** + * Register this message stream instance as a provider with the RemoteFileSourceClassLoader. + */ + private void registerWithClassLoader() { + RemoteFileSourceClassLoader classLoader = RemoteFileSourceClassLoader.getInstance(); + classLoader.registerProvider(this); + log.info().append("Registered RemoteFileSourceMessageStream provider with RemoteFileSourceClassLoader").endl(); + } + + /** + * Unregister this message stream instance from the RemoteFileSourceClassLoader. + */ + private void unregisterFromClassLoader() { + RemoteFileSourceClassLoader classLoader = RemoteFileSourceClassLoader.getInstance(); + classLoader.unregisterProvider(this); + log.info().append("Unregistered RemoteFileSourceMessageStream provider from RemoteFileSourceClassLoader") + .endl(); + } + + + /** + * Encapsulates the execution context for remote file source operations. This includes the currently active message + * stream and the resource paths that should be resolved from the remote source. This class is immutable - a new + * instance is created each time the context changes. + */ + public static class RemoteFileSourceExecutionContext { + private final RemoteFileSourceMessageStream activeMessageStream; + private final List resourcePaths; + private final boolean isDirty; + + /** + * Creates a new execution context. + * + * @param activeMessageStream the active message stream + * @param resourcePaths list of resource paths to resolve from remote source + * @param isDirty whether remote sources have changed and cache should be cleared + */ + public RemoteFileSourceExecutionContext(RemoteFileSourceMessageStream activeMessageStream, + List resourcePaths, boolean isDirty) { + this.activeMessageStream = activeMessageStream; + this.resourcePaths = resourcePaths; + this.isDirty = isDirty; + } + + /** + * Gets the currently active message stream. + * + * @return the active message stream + */ + public RemoteFileSourceMessageStream getActiveMessageStream() { + return activeMessageStream; + } + + /** + * Gets the resource paths that should be resolved from the remote source. + * + * @return the list of resource paths + */ + public List getResourcePaths() { + return resourcePaths; + } + + /** + * Gets whether remote sources have changed and cache should be cleared. + * + * @return true if dirty, false otherwise + */ + public boolean isDirty() { + return isDirty; + } + } +} + diff --git a/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourcePlugin.java b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourcePlugin.java new file mode 100644 index 00000000000..a106d0c1daf --- /dev/null +++ b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourcePlugin.java @@ -0,0 +1,54 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.remotefilesource; + +import com.google.auto.service.AutoService; +import io.deephaven.plugin.type.ObjectType; +import io.deephaven.plugin.type.ObjectTypeBase; +import io.deephaven.plugin.type.ObjectCommunicationException; +import io.deephaven.plugin.type.PluginMarker; + +import java.nio.ByteBuffer; + +/** + * ObjectType plugin for remote file sources. This plugin is registered via @AutoService and handles creation of + * RemoteFileSourceMessageStream connections for bidirectional communication with clients. + *

+ * This plugin recognizes PluginMarker objects whose pluginName matches this plugin's name. When a connection is + * established, a RemoteFileSourceMessageStream is created to handle bidirectional message passing between client and + * server. + *

+ * Each RemoteFileSourceMessageStream instance registers itself as a provider with the RemoteFileSourceClassLoader when + * created and unregisters when closed. The RemoteFileSourceClassLoader uses isActive() to determine which registered + * provider should handle resource requests. + */ +@AutoService(ObjectType.class) +public class RemoteFileSourcePlugin extends ObjectTypeBase { + + @Override + public String name() { + return "DeephavenRemoteFileSourcePlugin"; + } + + @Override + public boolean isType(Object object) { + if (object instanceof PluginMarker) { + PluginMarker marker = (PluginMarker) object; + return name().equals(marker.getPluginName()); + } + return false; + } + + @Override + public MessageStream compatibleClientConnection(Object object, MessageStream connection) + throws ObjectCommunicationException { + if (!isType(object)) { + throw new ObjectCommunicationException("Expected RemoteFileSource marker object, got " + object.getClass()); + } + + // Return a new bidirectional message stream for this connection + return new RemoteFileSourceMessageStream(connection); + } +} + diff --git a/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceTicketResolverFactoryService.java b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceTicketResolverFactoryService.java new file mode 100644 index 00000000000..adbc1ab34ae --- /dev/null +++ b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceTicketResolverFactoryService.java @@ -0,0 +1,21 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.remotefilesource; + +import com.google.auto.service.AutoService; +import io.deephaven.server.runner.TicketResolversFromServiceLoader; +import io.deephaven.server.session.TicketResolver; + +/** + * Factory service for creating RemoteFileSourceCommandResolver instances. This service is registered via @AutoService + * and provides ticket resolver functionality for handling remote file source plugin commands through the Deephaven + * server infrastructure. + */ +@AutoService(TicketResolversFromServiceLoader.Factory.class) +public class RemoteFileSourceTicketResolverFactoryService implements TicketResolversFromServiceLoader.Factory { + @Override + public TicketResolver create(final TicketResolversFromServiceLoader.TicketResolverOptions options) { + return new RemoteFileSourceCommandResolver(); + } +} diff --git a/plugin/src/main/java/io/deephaven/plugin/type/PluginMarker.java b/plugin/src/main/java/io/deephaven/plugin/type/PluginMarker.java new file mode 100644 index 00000000000..02ebd4f3589 --- /dev/null +++ b/plugin/src/main/java/io/deephaven/plugin/type/PluginMarker.java @@ -0,0 +1,61 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.plugin.type; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A generic marker object for plugin exports that can be used by multiple plugin types. + *

+ * IMPORTANT: The pluginName field is required because ObjectTypeLookup.findObjectType() returns the FIRST plugin where + * isType() returns true. Without plugin-specific identification in isType(), multiple plugins using PluginMarker would + * conflict, and whichever is registered first would intercept all PluginMarker instances. + *

+ * This class maintains a single instance per pluginName - multiple calls to {@link #forPluginName(String)} with the + * same name will return the same instance. + */ +public class PluginMarker { + private static final Map INSTANCES = new ConcurrentHashMap<>(); + + private final String pluginName; + + /** + * Private constructor - use forPluginName() to get instances. + * + * @param pluginName the plugin name identifier (should match the plugin's name() method) + */ + private PluginMarker(String pluginName) { + this.pluginName = pluginName; + } + + /** + * Gets the PluginMarker instance for the specified plugin name, creating it if necessary. + * + * @param pluginName the plugin name identifier (should match the plugin's name() method) + * @return the PluginMarker instance for this plugin name + * @throws IllegalArgumentException if pluginName is null or empty + */ + public static PluginMarker forPluginName(String pluginName) { + if (pluginName == null || pluginName.isEmpty()) { + throw new IllegalArgumentException("pluginName cannot be null or empty"); + } + return INSTANCES.computeIfAbsent(pluginName, PluginMarker::new); + } + + /** + * Gets the plugin name this marker is intended for. This should match the ObjectType.name() of the target plugin. + * + * @return the plugin name identifier + */ + public String getPluginName() { + return pluginName; + } + + @Override + public String toString() { + return "PluginMarker{pluginName='" + pluginName + "'}"; + } +} + diff --git a/proto/proto-backplane-grpc/Dockerfile b/proto/proto-backplane-grpc/Dockerfile index a95d0465655..0b142c717a0 100644 --- a/proto/proto-backplane-grpc/Dockerfile +++ b/proto/proto-backplane-grpc/Dockerfile @@ -29,6 +29,7 @@ RUN set -eux; \ /includes/deephaven_core/proto/application.proto \ /includes/deephaven_core/proto/inputtable.proto \ /includes/deephaven_core/proto/partitionedtable.proto \ + /includes/deephaven_core/proto/remotefilesource.proto \ /includes/deephaven_core/proto/config.proto \ /includes/deephaven_core/proto/hierarchicaltable.proto \ /includes/deephaven_core/proto/storage.proto; \ @@ -48,6 +49,7 @@ RUN set -eux; \ /includes/deephaven_core/proto/application.proto \ /includes/deephaven_core/proto/inputtable.proto \ /includes/deephaven_core/proto/partitionedtable.proto \ + /includes/deephaven_core/proto/remotefilesource.proto \ /includes/deephaven_core/proto/config.proto \ /includes/deephaven_core/proto/hierarchicaltable.proto \ /includes/deephaven_core/proto/storage.proto; \ @@ -64,6 +66,7 @@ RUN set -eux; \ /includes/deephaven_core/proto/application.proto \ /includes/deephaven_core/proto/inputtable.proto \ /includes/deephaven_core/proto/partitionedtable.proto \ + /includes/deephaven_core/proto/remotefilesource.proto \ /includes/deephaven_core/proto/config.proto \ /includes/deephaven_core/proto/hierarchicaltable.proto \ /includes/deephaven_core/proto/storage.proto; \ @@ -83,6 +86,7 @@ RUN set -eux; \ /includes/deephaven_core/proto/application.proto \ /includes/deephaven_core/proto/inputtable.proto \ /includes/deephaven_core/proto/partitionedtable.proto \ + /includes/deephaven_core/proto/remotefilesource.proto \ /includes/deephaven_core/proto/config.proto \ /includes/deephaven_core/proto/hierarchicaltable.proto \ /includes/deephaven_core/proto/storage.proto; \ @@ -151,6 +155,12 @@ RUN set -eux; \ --doc_opt=html,partitionedtable.html \ -I/includes \ /includes/deephaven_core/proto/partitionedtable.proto; \ + /opt/protoc/bin/protoc \ + --plugin=protoc-gen-doc=/usr/local/bin/protoc-gen-doc \ + --doc_out=generated/proto-doc/multi-html \ + --doc_opt=html,remotefilesource.html \ + -I/includes \ + /includes/deephaven_core/proto/remotefilesource.proto; \ /opt/protoc/bin/protoc \ --plugin=protoc-gen-doc=/usr/local/bin/protoc-gen-doc \ --doc_out=generated/proto-doc/multi-html \ @@ -218,6 +228,12 @@ RUN set -eux; \ --doc_opt=markdown,partitionedtable.md \ -I/includes \ /includes/deephaven_core/proto/partitionedtable.proto; \ + /opt/protoc/bin/protoc \ + --plugin=protoc-gen-doc=/usr/local/bin/protoc-gen-doc \ + --doc_out=generated/proto-doc/multi-md \ + --doc_opt=markdown,remotefilesource.md \ + -I/includes \ + /includes/deephaven_core/proto/remotefilesource.proto; \ /opt/protoc/bin/protoc \ --plugin=protoc-gen-doc=/usr/local/bin/protoc-gen-doc \ --doc_out=generated/proto-doc/multi-md \ diff --git a/proto/proto-backplane-grpc/src/main/proto/deephaven_core/proto/remotefilesource.proto b/proto/proto-backplane-grpc/src/main/proto/deephaven_core/proto/remotefilesource.proto new file mode 100644 index 00000000000..7560ba56648 --- /dev/null +++ b/proto/proto-backplane-grpc/src/main/proto/deephaven_core/proto/remotefilesource.proto @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016-2025 Deephaven Data Labs and Patent Pending + */ +syntax = "proto3"; + +package io.deephaven.proto.backplane.grpc; + +option java_multiple_files = true; +option optimize_for = SPEED; +option go_package = "github.com/deephaven/deephaven-core/go/internal/proto/remotefilesource"; + +import "deephaven_core/proto/ticket.proto"; + +// Server → Client: Requests sent from server to client via MessageStream +message RemoteFileSourceServerMessage { + // Unique identifier for this request, used to correlate the response + string request_id = 1; + + oneof request { + // Request source data/resource from the client + RemoteFileSourceMetaRequest meta_request = 2; + + // Acknowledgment that execution context was set + SetExecutionContextResponse set_execution_context_response = 3; + } +} + +// Client → Server: Requests/responses sent from client to server via MessageStream +message RemoteFileSourceClientMessage { + // The request_id from the ServerRequest this is responding to (if applicable) + string request_id = 1; + + oneof request { + // Response to a resource request + RemoteFileSourceMetaResponse meta_response = 2; + + // Set the execution context for script execution + SetExecutionContextRequest set_execution_context = 3; + } +} + +// Request source data/resource from the client +message RemoteFileSourceMetaRequest { + // The name/path of the resource being requested (e.g., "com/example/MyClass.java") + string resource_name = 1; +} + +// Response to a resource request +message RemoteFileSourceMetaResponse { + // The content of the resource, or empty if not found + bytes content = 1; + + // Indicates whether the resource was found + bool found = 2; + + // Error message if the resource could not be retrieved + string error = 3; +} + +// Request to set the execution context for script execution +message SetExecutionContextRequest { + // Resource paths that should be resolved from the remote source + // (e.g., ["com/example/Test.groovy", "org/mycompany/Utils.groovy"]) + repeated string resource_paths = 1; + + // Indicates whether remote sources have changed and cache should be cleared + bool is_dirty = 2; +} + +// Response acknowledging execution context was set +message SetExecutionContextResponse { + // Whether the operation was successful + bool success = 1; +} + +// Fetch the remote file source plugin into the specified ticket (Flight command, not MessageStream) +message RemoteFileSourcePluginFetchRequest { + io.deephaven.proto.backplane.grpc.Ticket result_id = 1; + + // The plugin name to create the PluginMarker for. + string plugin_name = 2; +} \ No newline at end of file diff --git a/proto/raw-js-openapi/src/index.js b/proto/raw-js-openapi/src/index.js index 6d7b2e69ff5..9176663e2b6 100644 --- a/proto/raw-js-openapi/src/index.js +++ b/proto/raw-js-openapi/src/index.js @@ -6,6 +6,7 @@ var application_pb = require("deephaven_core/proto/application_pb"); var inputtable_pb = require("deephaven_core/proto/inputtable_pb"); var object_pb = require("deephaven_core/proto/object_pb"); var partitionedtable_pb = require("deephaven_core/proto/partitionedtable_pb"); +var remotefilesource_pb = require("deephaven_core/proto/remotefilesource_pb"); var storage_pb = require("deephaven_core/proto/storage_pb"); var config_pb = require("deephaven_core/proto/config_pb"); var hierarchicaltable_pb = require("deephaven_core/proto/hierarchicaltable_pb"); @@ -46,6 +47,7 @@ var io = { deephaven_core: { object_pb_service, partitionedtable_pb, partitionedtable_pb_service, + remotefilesource_pb, storage_pb, storage_pb_service, config_pb, diff --git a/proto/raw-js-openapi/src/shim/remotefilesource_pb.js b/proto/raw-js-openapi/src/shim/remotefilesource_pb.js new file mode 100644 index 00000000000..01f90ee66e7 --- /dev/null +++ b/proto/raw-js-openapi/src/shim/remotefilesource_pb.js @@ -0,0 +1,2 @@ +Object.assign(exports, require('real/remotefilesource_pb').io.deephaven.proto.backplane.grpc) + diff --git a/proto/raw-js-openapi/webpack.config.js b/proto/raw-js-openapi/webpack.config.js index f0e47f9cbee..8d64a5ac4bd 100644 --- a/proto/raw-js-openapi/webpack.config.js +++ b/proto/raw-js-openapi/webpack.config.js @@ -3,7 +3,7 @@ const path = require('path'); // Workaround for broken codegen from protoc-gen-js using import_style=commonjs_strict, both in // the grpc-web protoc-gen-ts plugin, and in protoc-gen-js itself: const aliases = {}; -for (const proto of ['application', 'config', 'console', 'hierarchicaltable', 'inputtable', 'object', 'partitionedtable', 'session', 'storage', 'table', 'ticket']) { +for (const proto of ['application', 'config', 'console', 'hierarchicaltable', 'inputtable', 'object', 'partitionedtable', 'remotefilesource', 'session', 'storage', 'table', 'ticket']) { // Allows a reference to the real proto files, to be made from the shim aliases[`real/${proto}_pb`] = `${__dirname}/build/js-src/deephaven_core/proto/${proto}_pb`; diff --git a/py/client/deephaven_core/proto/remotefilesource_pb2.py b/py/client/deephaven_core/proto/remotefilesource_pb2.py new file mode 100644 index 00000000000..3aa205c62e3 --- /dev/null +++ b/py/client/deephaven_core/proto/remotefilesource_pb2.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: deephaven_core/proto/remotefilesource.proto +# Protobuf Python Version: 6.31.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 31, + 1, + '', + 'deephaven_core/proto/remotefilesource.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from deephaven_core.proto import ticket_pb2 as deephaven__core_dot_proto_dot_ticket__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n+deephaven_core/proto/remotefilesource.proto\x12!io.deephaven.proto.backplane.grpc\x1a!deephaven_core/proto/ticket.proto\"\x80\x02\n\x1dRemoteFileSourceServerMessage\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12V\n\x0cmeta_request\x18\x02 \x01(\x0b\x32>.io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaRequestH\x00\x12h\n\x1eset_execution_context_response\x18\x03 \x01(\x0b\x32>.io.deephaven.proto.backplane.grpc.SetExecutionContextResponseH\x00\x42\t\n\x07request\"\xf8\x01\n\x1dRemoteFileSourceClientMessage\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12X\n\rmeta_response\x18\x02 \x01(\x0b\x32?.io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaResponseH\x00\x12^\n\x15set_execution_context\x18\x03 \x01(\x0b\x32=.io.deephaven.proto.backplane.grpc.SetExecutionContextRequestH\x00\x42\t\n\x07request\"4\n\x1bRemoteFileSourceMetaRequest\x12\x15\n\rresource_name\x18\x01 \x01(\t\"M\n\x1cRemoteFileSourceMetaResponse\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\x0c\x12\r\n\x05\x66ound\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"F\n\x1aSetExecutionContextRequest\x12\x16\n\x0eresource_paths\x18\x01 \x03(\t\x12\x10\n\x08is_dirty\x18\x02 \x01(\x08\".\n\x1bSetExecutionContextResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"w\n\"RemoteFileSourcePluginFetchRequest\x12<\n\tresult_id\x18\x01 \x01(\x0b\x32).io.deephaven.proto.backplane.grpc.Ticket\x12\x13\n\x0bplugin_name\x18\x02 \x01(\tBLH\x01P\x01ZFgithub.com/deephaven/deephaven-core/go/internal/proto/remotefilesourceb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'deephaven_core.proto.remotefilesource_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'H\001P\001ZFgithub.com/deephaven/deephaven-core/go/internal/proto/remotefilesource' + _globals['_REMOTEFILESOURCESERVERMESSAGE']._serialized_start=118 + _globals['_REMOTEFILESOURCESERVERMESSAGE']._serialized_end=374 + _globals['_REMOTEFILESOURCECLIENTMESSAGE']._serialized_start=377 + _globals['_REMOTEFILESOURCECLIENTMESSAGE']._serialized_end=625 + _globals['_REMOTEFILESOURCEMETAREQUEST']._serialized_start=627 + _globals['_REMOTEFILESOURCEMETAREQUEST']._serialized_end=679 + _globals['_REMOTEFILESOURCEMETARESPONSE']._serialized_start=681 + _globals['_REMOTEFILESOURCEMETARESPONSE']._serialized_end=758 + _globals['_SETEXECUTIONCONTEXTREQUEST']._serialized_start=760 + _globals['_SETEXECUTIONCONTEXTREQUEST']._serialized_end=830 + _globals['_SETEXECUTIONCONTEXTRESPONSE']._serialized_start=832 + _globals['_SETEXECUTIONCONTEXTRESPONSE']._serialized_end=878 + _globals['_REMOTEFILESOURCEPLUGINFETCHREQUEST']._serialized_start=880 + _globals['_REMOTEFILESOURCEPLUGINFETCHREQUEST']._serialized_end=999 +# @@protoc_insertion_point(module_scope) diff --git a/py/client/deephaven_core/proto/remotefilesource_pb2.pyi b/py/client/deephaven_core/proto/remotefilesource_pb2.pyi new file mode 100644 index 00000000000..08689044308 --- /dev/null +++ b/py/client/deephaven_core/proto/remotefilesource_pb2.pyi @@ -0,0 +1,211 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file + +Copyright (c) 2016-2025 Deephaven Data Labs and Patent Pending +""" + +import builtins +import collections.abc +import deephaven_core.proto.ticket_pb2 +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.message +import sys +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class RemoteFileSourceServerMessage(google.protobuf.message.Message): + """Server → Client: Requests sent from server to client via MessageStream""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + REQUEST_ID_FIELD_NUMBER: builtins.int + META_REQUEST_FIELD_NUMBER: builtins.int + SET_EXECUTION_CONTEXT_RESPONSE_FIELD_NUMBER: builtins.int + request_id: builtins.str + """Unique identifier for this request, used to correlate the response""" + @property + def meta_request(self) -> Global___RemoteFileSourceMetaRequest: + """Request source data/resource from the client""" + + @property + def set_execution_context_response(self) -> Global___SetExecutionContextResponse: + """Acknowledgment that execution context was set""" + + def __init__( + self, + *, + request_id: builtins.str = ..., + meta_request: Global___RemoteFileSourceMetaRequest | None = ..., + set_execution_context_response: Global___SetExecutionContextResponse | None = ..., + ) -> None: ... + _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["meta_request", b"meta_request", "request", b"request", "set_execution_context_response", b"set_execution_context_response"] + def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... + _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["meta_request", b"meta_request", "request", b"request", "request_id", b"request_id", "set_execution_context_response", b"set_execution_context_response"] + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + _WhichOneofReturnType_request: typing_extensions.TypeAlias = typing.Literal["meta_request", "set_execution_context_response"] + _WhichOneofArgType_request: typing_extensions.TypeAlias = typing.Literal["request", b"request"] + def WhichOneof(self, oneof_group: _WhichOneofArgType_request) -> _WhichOneofReturnType_request | None: ... + +Global___RemoteFileSourceServerMessage: typing_extensions.TypeAlias = RemoteFileSourceServerMessage + +@typing.final +class RemoteFileSourceClientMessage(google.protobuf.message.Message): + """Client → Server: Requests/responses sent from client to server via MessageStream""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + REQUEST_ID_FIELD_NUMBER: builtins.int + META_RESPONSE_FIELD_NUMBER: builtins.int + SET_EXECUTION_CONTEXT_FIELD_NUMBER: builtins.int + request_id: builtins.str + """The request_id from the ServerRequest this is responding to (if applicable)""" + @property + def meta_response(self) -> Global___RemoteFileSourceMetaResponse: + """Response to a resource request""" + + @property + def set_execution_context(self) -> Global___SetExecutionContextRequest: + """Set the execution context for script execution""" + + def __init__( + self, + *, + request_id: builtins.str = ..., + meta_response: Global___RemoteFileSourceMetaResponse | None = ..., + set_execution_context: Global___SetExecutionContextRequest | None = ..., + ) -> None: ... + _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["meta_response", b"meta_response", "request", b"request", "set_execution_context", b"set_execution_context"] + def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... + _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["meta_response", b"meta_response", "request", b"request", "request_id", b"request_id", "set_execution_context", b"set_execution_context"] + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + _WhichOneofReturnType_request: typing_extensions.TypeAlias = typing.Literal["meta_response", "set_execution_context"] + _WhichOneofArgType_request: typing_extensions.TypeAlias = typing.Literal["request", b"request"] + def WhichOneof(self, oneof_group: _WhichOneofArgType_request) -> _WhichOneofReturnType_request | None: ... + +Global___RemoteFileSourceClientMessage: typing_extensions.TypeAlias = RemoteFileSourceClientMessage + +@typing.final +class RemoteFileSourceMetaRequest(google.protobuf.message.Message): + """Request source data/resource from the client""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + RESOURCE_NAME_FIELD_NUMBER: builtins.int + resource_name: builtins.str + """The name/path of the resource being requested (e.g., "com/example/MyClass.java")""" + def __init__( + self, + *, + resource_name: builtins.str = ..., + ) -> None: ... + _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["resource_name", b"resource_name"] + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___RemoteFileSourceMetaRequest: typing_extensions.TypeAlias = RemoteFileSourceMetaRequest + +@typing.final +class RemoteFileSourceMetaResponse(google.protobuf.message.Message): + """Response to a resource request""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + CONTENT_FIELD_NUMBER: builtins.int + FOUND_FIELD_NUMBER: builtins.int + ERROR_FIELD_NUMBER: builtins.int + content: builtins.bytes + """The content of the resource, or empty if not found""" + found: builtins.bool + """Indicates whether the resource was found""" + error: builtins.str + """Error message if the resource could not be retrieved""" + def __init__( + self, + *, + content: builtins.bytes = ..., + found: builtins.bool = ..., + error: builtins.str = ..., + ) -> None: ... + _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["content", b"content", "error", b"error", "found", b"found"] + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___RemoteFileSourceMetaResponse: typing_extensions.TypeAlias = RemoteFileSourceMetaResponse + +@typing.final +class SetExecutionContextRequest(google.protobuf.message.Message): + """Request to set the execution context for script execution""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + RESOURCE_PATHS_FIELD_NUMBER: builtins.int + IS_DIRTY_FIELD_NUMBER: builtins.int + is_dirty: builtins.bool + """Indicates whether remote sources have changed and cache should be cleared""" + @property + def resource_paths(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + """Resource paths that should be resolved from the remote source + (e.g., ["com/example/Test.groovy", "org/mycompany/Utils.groovy"]) + """ + + def __init__( + self, + *, + resource_paths: collections.abc.Iterable[builtins.str] | None = ..., + is_dirty: builtins.bool = ..., + ) -> None: ... + _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["is_dirty", b"is_dirty", "resource_paths", b"resource_paths"] + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___SetExecutionContextRequest: typing_extensions.TypeAlias = SetExecutionContextRequest + +@typing.final +class SetExecutionContextResponse(google.protobuf.message.Message): + """Response acknowledging execution context was set""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + SUCCESS_FIELD_NUMBER: builtins.int + success: builtins.bool + """Whether the operation was successful""" + def __init__( + self, + *, + success: builtins.bool = ..., + ) -> None: ... + _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["success", b"success"] + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___SetExecutionContextResponse: typing_extensions.TypeAlias = SetExecutionContextResponse + +@typing.final +class RemoteFileSourcePluginFetchRequest(google.protobuf.message.Message): + """Fetch the remote file source plugin into the specified ticket (Flight command, not MessageStream)""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + RESULT_ID_FIELD_NUMBER: builtins.int + PLUGIN_NAME_FIELD_NUMBER: builtins.int + plugin_name: builtins.str + """The plugin name to create the PluginMarker for.""" + @property + def result_id(self) -> deephaven_core.proto.ticket_pb2.Ticket: ... + def __init__( + self, + *, + result_id: deephaven_core.proto.ticket_pb2.Ticket | None = ..., + plugin_name: builtins.str = ..., + ) -> None: ... + _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["result_id", b"result_id"] + def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... + _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["plugin_name", b"plugin_name", "result_id", b"result_id"] + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___RemoteFileSourcePluginFetchRequest: typing_extensions.TypeAlias = RemoteFileSourcePluginFetchRequest diff --git a/py/client/deephaven_core/proto/remotefilesource_pb2_grpc.py b/py/client/deephaven_core/proto/remotefilesource_pb2_grpc.py new file mode 100644 index 00000000000..a66295078b8 --- /dev/null +++ b/py/client/deephaven_core/proto/remotefilesource_pb2_grpc.py @@ -0,0 +1,24 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.76.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in deephaven_core/proto/remotefilesource_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) diff --git a/server/build.gradle b/server/build.gradle index f59344a3cdf..564093a1f3b 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -93,6 +93,7 @@ dependencies { runtimeOnly project(':plugin-figure') runtimeOnly project(':plugin-partitionedtable') runtimeOnly project(':plugin-hierarchicaltable') + runtimeOnly project(':plugin-remotefilesource') implementation project(':plugin-gc-app') api platform(libs.grpc.bom) diff --git a/server/jetty-app-11/build.gradle b/server/jetty-app-11/build.gradle index e075c08a8fa..09baafd32a8 100644 --- a/server/jetty-app-11/build.gradle +++ b/server/jetty-app-11/build.gradle @@ -16,6 +16,7 @@ dependencies { implementation libs.dagger annotationProcessor libs.dagger.compiler + runtimeOnly project(':plugin-remotefilesource') runtimeOnly project(':log-to-slf4j') runtimeOnly project(':logback-print-stream-globals') runtimeOnly project(':logback-logbuffer') diff --git a/server/jetty-app/build.gradle b/server/jetty-app/build.gradle index 50c7406be2b..ec03184a602 100644 --- a/server/jetty-app/build.gradle +++ b/server/jetty-app/build.gradle @@ -16,6 +16,7 @@ dependencies { implementation libs.dagger annotationProcessor libs.dagger.compiler + runtimeOnly project(':plugin-remotefilesource') runtimeOnly project(':log-to-slf4j') runtimeOnly project(':logback-print-stream-globals') runtimeOnly project(':logback-logbuffer') diff --git a/settings.gradle b/settings.gradle index ea57fc5cf51..c99ad5cdec4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -318,6 +318,9 @@ project(':plugin-figure').projectDir = file('plugin/figure') include(':plugin-partitionedtable') project(':plugin-partitionedtable').projectDir = file('plugin/partitionedtable') +include(':plugin-remotefilesource') +project(':plugin-remotefilesource').projectDir = file('plugin/remotefilesource') + include(':plugin-hierarchicaltable') project(':plugin-hierarchicaltable').projectDir = file('plugin/hierarchicaltable') diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/CoreClient.java b/web/client-api/src/main/java/io/deephaven/web/client/api/CoreClient.java index 0abbdd30efe..1eb0247b1f8 100644 --- a/web/client-api/src/main/java/io/deephaven/web/client/api/CoreClient.java +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/CoreClient.java @@ -15,6 +15,7 @@ import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.config_pb_service.ConfigServiceClient; import io.deephaven.javascript.proto.dhinternal.jspb.Map; import io.deephaven.web.client.api.event.HasEventHandling; +import io.deephaven.web.client.api.remotefilesource.JsRemoteFileSourceService; import io.deephaven.web.client.api.storage.JsStorageService; import io.deephaven.web.client.fu.JsLog; import io.deephaven.web.client.fu.LazyPromise; @@ -46,6 +47,7 @@ public class CoreClient extends HasEventHandling { LOGIN_TYPE_ANONYMOUS = "anonymous"; private final IdeConnection ideConnection; + private Promise remoteFileSourceServicePromise; public CoreClient(String serverUrl, @TsTypeRef(ConnectOptions.class) @JsOptional Object connectOptions) { ideConnection = new IdeConnection(serverUrl, connectOptions); @@ -141,6 +143,14 @@ public Promise onConnected(@JsOptional Double timeoutInMillis) { return ideConnection.onConnected(); } + public Promise getRemoteFileSourceService() { + if (remoteFileSourceServicePromise == null) { + remoteFileSourceServicePromise = JsRemoteFileSourceService.fetchPlugin(ideConnection.connection.get()); + } + + return remoteFileSourceServicePromise; + } + public Promise getServerConfigValues() { return getConfigs( c -> ideConnection.connection.get().configServiceClient().getConfigurationConstants( diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/JsProtobufUtils.java b/web/client-api/src/main/java/io/deephaven/web/client/api/JsProtobufUtils.java new file mode 100644 index 00000000000..6ee6ad92c75 --- /dev/null +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/JsProtobufUtils.java @@ -0,0 +1,183 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.web.client.api; + +import elemental2.core.Uint8Array; +import elemental2.dom.TextEncoder; + +/** + * Utility methods for working with protobuf messages in the client-side JavaScript environment. + */ +public class JsProtobufUtils { + + // Varint encoding constants + private static final int VARINT_CONTINUATION_BIT = 0x80; // 10000000 - indicates more bytes follow + private static final int VARINT_DATA_MASK = 0x7F; // 01111111 - extracts 7 bits of data + private static final int VARINT_BYTE_THRESHOLD = 128; // Values >= 128 require multiple bytes + + private JsProtobufUtils() { + // Utility class, no instantiation + } + + /** + * Wraps a protobuf message in a google.protobuf.Any message. + *

+ * The google.protobuf.Any message has two fields: + *

+ *

+ * This method manually encodes the Any message in protobuf binary format since the client-side JavaScript protobuf + * library doesn't provide Any.pack() like the server-side Java library does. + * + * @param typeUrl the type URL for the message (e.g., "type.googleapis.com/package.MessageName") + * @param messageBytes the serialized protobuf message bytes + * @return the serialized Any message containing the wrapped message + */ + public static Uint8Array wrapInAny(String typeUrl, Uint8Array messageBytes) { + // Protobuf tag constants for google.protobuf.Any message fields + // Tag format: (field_number << 3) | wire_type + // wire_type=2 means length-delimited (for strings/bytes) + final int TYPE_URL_TAG = 10; // (1 << 3) | 2 = field 1, wire type 2 + final int VALUE_TAG = 18; // (2 << 3) | 2 = field 2, wire type 2 + + // Encode the type_url string to UTF-8 bytes + TextEncoder textEncoder = new TextEncoder(); + Uint8Array typeUrlBytes = textEncoder.encode(typeUrl); + + // Calculate sizes for protobuf binary encoding + int typeUrlFieldSize = calculateFieldSize(TYPE_URL_TAG, typeUrlBytes.length); + int valueFieldSize = calculateFieldSize(VALUE_TAG, messageBytes.length); + + // Allocate buffer for the complete Any message + int totalSize = typeUrlFieldSize + valueFieldSize; + Uint8Array result = new Uint8Array(totalSize); + int pos = 0; + + // Write field 1 (type_url) in protobuf binary format + pos = writeField(result, pos, TYPE_URL_TAG, typeUrlBytes); + + // Write field 2 (value) in protobuf binary format + writeField(result, pos, VALUE_TAG, messageBytes); + + return result; + } + + /** + * Calculates the total size needed for a protobuf length-delimited field. + *

+ * A length-delimited field consists of: + *

+ * + * @param tag the protobuf field tag (field number << 3 | wire type) + * @param dataLength the length of the data in bytes + * @return the total number of bytes needed for this field + */ + private static int calculateFieldSize(int tag, int dataLength) { + return sizeOfVarint(tag) + sizeOfVarint(dataLength) + dataLength; + } + + /** + * Calculates how many bytes a varint encoding will require for the given value. + *

+ * Protobuf uses varint encoding where each byte stores 7 bits of data (the 8th bit is a continuation flag). This + * means: + *

+ * + * @param value the integer value to encode + * @return the number of bytes required to encode the value as a varint + */ + private static int sizeOfVarint(int value) { + if (value < 0) + return 10; // Negative numbers use sign extension, always 10 bytes + if (value < VARINT_BYTE_THRESHOLD) // 2^7 = 128 + return 1; + if (value < 16384) // 2^14 + return 2; + if (value < 2097152) // 2^21 + return 3; + if (value < 268435456) // 2^28 + return 4; + return 5; // Max unsigned 32-bit int requires 5 bytes + } + + /** + * Writes a complete protobuf length-delimited field to the buffer. + *

+ * A length-delimited field consists of: + *

+ * + * @param buffer the buffer to write to + * @param pos the starting position in the buffer + * @param tag the protobuf field tag + * @param data the data bytes to write + * @return the new position after writing the complete field + */ + private static int writeField(Uint8Array buffer, int pos, int tag, Uint8Array data) { + // Write tag and length + pos = writeVarint(buffer, pos, tag); + pos = writeVarint(buffer, pos, data.length); + // Write data bytes + for (int i = 0; i < data.length; i++) { + buffer.setAt(pos++, data.getAt(i)); + } + return pos; + } + + /** + * Writes a value to the buffer as a protobuf varint (variable-length integer). + *

+ * Varint encoding works by: + *

    + *
  1. Taking the lowest 7 bits of the value
  2. + *
  3. Setting the 8th bit to 1 if more bytes follow (continuation flag)
  4. + *
  5. Writing the byte to the buffer
  6. + *
  7. Shifting the value right by 7 bits
  8. + *
  9. Repeating until the value is less than 128
  10. + *
  11. Writing the final byte without the continuation flag (8th bit = 0)
  12. + *
+ *

+ * Example: encoding 300 + *

+ * + * @param buffer the buffer to write to + * @param pos the starting position in the buffer + * @param value the value to encode + * @return the new position after writing + */ + private static int writeVarint(Uint8Array buffer, int pos, int value) { + while (value >= VARINT_BYTE_THRESHOLD) { + // Extract lowest 7 bits and set continuation flag (8th bit = 1) + buffer.setAt(pos++, (double) ((value & VARINT_DATA_MASK) | VARINT_CONTINUATION_BIT)); + // Shift right by 7 to process next chunk + value >>>= 7; // Unsigned right shift to handle large positive values + } + // Write final byte (no continuation flag, 8th bit = 0) + buffer.setAt(pos++, (double) value); + return pos; + } +} + diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/JsRemoteFileSourceService.java b/web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/JsRemoteFileSourceService.java new file mode 100644 index 00000000000..ec51fb748ed --- /dev/null +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/JsRemoteFileSourceService.java @@ -0,0 +1,366 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.web.client.api.remotefilesource; + +import com.vertispan.tsdefs.annotations.TsInterface; +import com.vertispan.tsdefs.annotations.TsName; +import elemental2.core.Uint8Array; +import elemental2.dom.TextEncoder; +import elemental2.promise.Promise; +import io.deephaven.javascript.proto.dhinternal.arrow.flight.protocol.flight_pb.FlightDescriptor; +import io.deephaven.javascript.proto.dhinternal.arrow.flight.protocol.flight_pb.FlightInfo; +import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceClientMessage; +import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceMetaRequest; +import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceMetaResponse; +import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourcePluginFetchRequest; +import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceServerMessage; +import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.SetExecutionContextRequest; +import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.SetExecutionContextResponse; +import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.ticket_pb.Ticket; +import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.ticket_pb.TypedTicket; +import io.deephaven.web.client.api.Callbacks; +import io.deephaven.web.client.api.JsProtobufUtils; +import io.deephaven.web.client.api.event.Event; +import io.deephaven.web.client.api.event.EventFn; +import io.deephaven.web.client.api.WorkerConnection; +import io.deephaven.web.client.api.event.HasEventHandling; +import io.deephaven.web.client.api.widget.JsWidget; +import io.deephaven.web.shared.fu.RemoverFn; +import io.deephaven.web.client.api.widget.WidgetMessageDetails; +import io.deephaven.web.client.fu.LazyPromise; +import jsinterop.annotations.JsIgnore; +import jsinterop.annotations.JsMethod; +import jsinterop.annotations.JsNullable; +import jsinterop.annotations.JsOptional; +import jsinterop.annotations.JsProperty; +import jsinterop.annotations.JsType; +import jsinterop.base.Js; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + + +/** + * JavaScript client for the RemoteFileSource service. Provides bidirectional communication with the server-side + * RemoteFileSourcePlugin via a message stream. + *

+ * Events: + *

+ */ +@JsType(namespace = "dh.remotefilesource", name = "RemoteFileSourceService") +public class JsRemoteFileSourceService extends HasEventHandling { + /** Event name for resource request events from the server */ + @JsProperty(namespace = "dh.remotefilesource.RemoteFileSourceService") + public static final String EVENT_REQUEST_SOURCE = "requestsource"; + + // Plugin name must match RemoteFileSourcePlugin.name() on the server + private static final String PLUGIN_NAME = "DeephavenRemoteFileSourcePlugin"; + + // Timeout for setExecutionContext requests (in milliseconds) + private static final int SET_EXECUTION_CONTEXT_TIMEOUT_MS = 30000; // 30 seconds + + private final JsWidget widget; + + // Track pending setExecutionContext requests + private final Map> pendingSetExecutionContextRequests = new HashMap<>(); + private int requestIdCounter = 0; + + private JsRemoteFileSourceService(JsWidget widget) { + this.widget = widget; + } + + /** + * Overrides addEventListener to enforce that EVENT_REQUEST_SOURCE can only have one listener. + * + * @param name the name of the event to listen for + * @param callback a function to call when the event occurs + * @return Returns a cleanup function. + * @param the type of the data that the event will provide + */ + @Override + public RemoverFn addEventListener(String name, EventFn callback) { + if (EVENT_REQUEST_SOURCE.equals(name) && hasListeners(EVENT_REQUEST_SOURCE)) { + throw new IllegalStateException( + "EVENT_REQUEST_SOURCE already has a listener. Only one listener is allowed for this event."); + } + return super.addEventListener(name, callback); + } + + /** + * Fetches the FlightInfo for the plugin fetch command. + * + * @param connection the worker connection to use + * @return a promise that resolves to the FlightInfo for the plugin fetch + */ + private static Promise fetchPluginFlightInfo(WorkerConnection connection) { + // Create a new export ticket for the result + Ticket resultTicket = connection.getTickets().newExportTicket(); + + // Create the fetch request + RemoteFileSourcePluginFetchRequest fetchRequest = new RemoteFileSourcePluginFetchRequest(); + fetchRequest.setResultId(resultTicket); + fetchRequest.setPluginName(PLUGIN_NAME); + + // Serialize the request to bytes + Uint8Array innerRequestBytes = fetchRequest.serializeBinary(); + + // Wrap in google.protobuf.Any with the proper typeUrl + Uint8Array anyWrappedBytes = JsProtobufUtils.wrapInAny( + "type.googleapis.com/io.deephaven.proto.backplane.grpc.RemoteFileSourcePluginFetchRequest", + innerRequestBytes); + + // Create a FlightDescriptor with the command + FlightDescriptor descriptor = new FlightDescriptor(); + descriptor.setType(FlightDescriptor.DescriptorType.getCMD()); + descriptor.setCmd(anyWrappedBytes); + + // Send the getFlightInfo request + return Callbacks.grpcUnaryPromise( + c -> connection.flightServiceClient().getFlightInfo(descriptor, connection.metadata(), c::apply)); + } + + /** + * Fetches a RemoteFileSource plugin instance from the server and establishes a message stream connection. + * + * @param connection the worker connection to use for communication + * @return a promise that resolves to a RemoteFileSourceService instance with an active message stream + */ + @JsIgnore + public static Promise fetchPlugin(WorkerConnection connection) { + return fetchPluginFlightInfo(connection) + .then(flightInfo -> { + // The first endpoint contains the ticket for the plugin instance. + // This is the standard Flight pattern: we passed resultTicket in the request, + // the server exported the service to that ticket, and returned a FlightInfo + // with an endpoint containing that same ticket for us to use. + if (flightInfo.getEndpointList().length > 0) { + // Get the Arrow Flight ticket from the endpoint + io.deephaven.javascript.proto.dhinternal.arrow.flight.protocol.flight_pb.Ticket flightTicket = + flightInfo.getEndpointList().getAt(0).getTicket(); + + // Convert the Arrow Flight ticket to a Deephaven ticket + Ticket dhTicket = new Ticket(); + dhTicket.setTicket(flightTicket.getTicket_asU8()); + + // Create a TypedTicket for the plugin instance + // The type must match RemoteFileSourcePlugin.name() + TypedTicket typedTicket = new TypedTicket(); + typedTicket.setTicket(dhTicket); + typedTicket.setType(PLUGIN_NAME); + + JsWidget widget = new JsWidget(connection, typedTicket); + + JsRemoteFileSourceService service = new JsRemoteFileSourceService(widget); + return service.connect(); + } else { + return Promise.reject("No endpoints returned from " + PLUGIN_NAME + " plugin fetch"); + } + }); + } + + /** + * Establishes the message stream connection to the server-side plugin instance. + * + * @return a promise that resolves to this service instance when the connection is established + */ + private Promise connect() { + widget.addEventListener(JsWidget.EVENT_MESSAGE, this::handleMessage); + return widget.refetch().then(w -> Promise.resolve(this)); + } + + /** + * Handles incoming messages from the server. + * + * @param event the message event from the server + */ + private void handleMessage(Event event) { + Uint8Array payload = event.getDetail().getDataAsU8(); + + RemoteFileSourceServerMessage message; + try { + message = RemoteFileSourceServerMessage.deserializeBinary(payload); + } catch (Exception e) { + // Failed to parse as proto + throw new IllegalStateException("Received unparseable message from server", e); + } + + // Route the parsed message to the appropriate handler + if (message.hasMetaRequest()) { + handleMetaRequest(message); + } else if (message.hasSetExecutionContextResponse()) { + handleSetExecutionContextResponse(message); + } else { + throw new IllegalStateException("Received unknown message type from server"); + } + } + + /** + * Handles a meta request (resource request) from the server. + * + * @param message the server request message + */ + private void handleMetaRequest(RemoteFileSourceServerMessage message) { + if (!hasListeners(EVENT_REQUEST_SOURCE)) { + throw new IllegalStateException( + "Received resource request from server but no listener is registered for EVENT_REQUEST_SOURCE. " + + "A listener must be registered to handle resource requests."); + } + RemoteFileSourceMetaRequest request = message.getMetaRequest(); + fireEvent(EVENT_REQUEST_SOURCE, new ResourceRequestEvent(message.getRequestId(), request)); + } + + /** + * Handles a set execution context response from the server. + * + * @param message the server request message + */ + private void handleSetExecutionContextResponse(RemoteFileSourceServerMessage message) { + String requestId = message.getRequestId(); + LazyPromise promise = pendingSetExecutionContextRequests.remove(requestId); + if (promise != null) { + SetExecutionContextResponse response = message.getSetExecutionContextResponse(); + promise.succeed(response.getSuccess()); + } + } + + + /** + * Sets the execution context on the server to identify this message stream as active for script execution. + * + * @param isDirty whether the execution context is dirty (has pending changes) + * @param resourcePaths array of resource paths to resolve from remote source (e.g., ["com/example/Test.groovy", + * "org/mycompany/Utils.groovy"]), or null/empty for no specific resources + * @return a promise that resolves to true if the server successfully set the execution context, false otherwise + */ + @JsMethod + public Promise setExecutionContext(boolean isDirty, @JsOptional String[] resourcePaths) { + // Generate a unique request ID + String requestId = "setExecutionContext-" + (requestIdCounter++); + + // Create a lazy promise that will be resolved when we get the response + LazyPromise promise = new LazyPromise<>(); + pendingSetExecutionContextRequests.put(requestId, promise); + + // Send the request + RemoteFileSourceClientMessage clientRequest = getSetExecutionContextRequest(isDirty, resourcePaths, requestId); + sendClientRequest(clientRequest); + + // Return a promise with built-in timeout + return promise.asPromise(SET_EXECUTION_CONTEXT_TIMEOUT_MS); + } + + /** + * Helper method to build a RemoteFileSourceClientMessage for setting execution context. + * + * @param isDirty whether the execution context is dirty (has pending changes) + * @param resourcePaths array of resource paths to resolve + * @param requestId unique request ID + * @return the constructed RemoteFileSourceClientMessage + */ + private static @NotNull RemoteFileSourceClientMessage getSetExecutionContextRequest(boolean isDirty, + String[] resourcePaths, String requestId) { + SetExecutionContextRequest setContextRequest = new SetExecutionContextRequest(); + setContextRequest.setIsDirty(isDirty); + + if (resourcePaths != null) { + setContextRequest.setResourcePathsList(resourcePaths); + } + + RemoteFileSourceClientMessage clientRequest = new RemoteFileSourceClientMessage(); + clientRequest.setRequestId(requestId); + clientRequest.setSetExecutionContext(setContextRequest); + return clientRequest; + } + + /** + * Helper method to send a RemoteFileSourceClientMessage to the server. + * + * @param clientRequest the client request to send + */ + private void sendClientRequest(RemoteFileSourceClientMessage clientRequest) { + // Serialize the protobuf message to bytes + Uint8Array messageBytes = clientRequest.serializeBinary(); + + // Uint8Array is an ArrayBufferView, which is one of the MessageUnion types + // The unchecked cast is safe because MessageUnion accepts String | ArrayBuffer | ArrayBufferView + widget.sendMessage(Js.uncheckedCast(messageBytes), null); + } + + /** + * Closes the message stream connection to the server. + */ + public void close() { + widget.close(); + } + + /** + * Event details for a resource request from the server. Wraps the proto RemoteFileSourceMetaRequest and provides a + * respond() method. + */ + @TsInterface + @TsName(namespace = "dh.remotefilesource") + public class ResourceRequestEvent { + private final String requestId; + private final RemoteFileSourceMetaRequest protoRequest; + + public ResourceRequestEvent(String requestId, RemoteFileSourceMetaRequest protoRequest) { + this.requestId = requestId; + this.protoRequest = protoRequest; + } + + /** + * @return the name/path of the requested resource + */ + @JsProperty + public String getResourceName() { + return protoRequest.getResourceName(); + } + + /** + * Responds to this resource request with the given content. + * + * @param content the resource content (String | Uint8Array | null): + *
    + *
  • String - will be UTF-8 encoded before sending to server
  • + *
  • Uint8Array - sent as-is to server
  • + *
  • null - indicates resource was not found
  • + *
+ */ + @JsMethod + public void respond(@JsNullable ResourceContentUnion content) { + // Build RemoteFileSourceMetaResponse proto + RemoteFileSourceMetaResponse response = new RemoteFileSourceMetaResponse(); + + if (content == null) { + // Resource not found + response.setFound(false); + response.setContent(new Uint8Array(0)); + } else { + response.setFound(true); + + // Convert content to bytes using union type methods + if (content.isString()) { + TextEncoder textEncoder = new TextEncoder(); + response.setContent(textEncoder.encode(content.asString())); + } else if (content.isUint8Array()) { + response.setContent(content.asUint8Array()); + } else { + throw new IllegalArgumentException("Content must be a String, Uint8Array, or null"); + } + } + + // Wrap in RemoteFileSourceClientMessage (client→server) + RemoteFileSourceClientMessage clientRequest = new RemoteFileSourceClientMessage(); + clientRequest.setRequestId(requestId); + clientRequest.setMetaResponse(response); + + sendClientRequest(clientRequest); + } + } +} diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/ResourceContentUnion.java b/web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/ResourceContentUnion.java new file mode 100644 index 00000000000..8728839edd8 --- /dev/null +++ b/web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/ResourceContentUnion.java @@ -0,0 +1,49 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.web.client.api.remotefilesource; + +import com.vertispan.tsdefs.annotations.TsUnion; +import com.vertispan.tsdefs.annotations.TsUnionMember; +import elemental2.core.Uint8Array; +import jsinterop.annotations.JsOverlay; +import jsinterop.annotations.JsPackage; +import jsinterop.annotations.JsType; +import jsinterop.base.Js; + +/** + * Union type for resource content that can be either a String or Uint8Array. + */ +@TsUnion +@JsType(name = "?", namespace = JsPackage.GLOBAL, isNative = true) +public interface ResourceContentUnion { + @JsOverlay + static ResourceContentUnion of(Object o) { + return Js.cast(o); + } + + @JsOverlay + default boolean isString() { + // Cast to (Object) since Java only "knows" that `this` is `ResourceContentUnion` type which cannot have a + // subclass that is also a String. + return (Object) this instanceof String; + } + + @JsOverlay + default boolean isUint8Array() { + return this instanceof Uint8Array; + } + + @TsUnionMember + @JsOverlay + default String asString() { + return Js.cast(this); + } + + @TsUnionMember + @JsOverlay + default Uint8Array asUint8Array() { + return (Uint8Array) this; + } +} + diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceClientMessage.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceClientMessage.java new file mode 100644 index 00000000000..fa1377115b8 --- /dev/null +++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceClientMessage.java @@ -0,0 +1,308 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb; + +import elemental2.core.JsArray; +import elemental2.core.Uint8Array; +import jsinterop.annotations.JsOverlay; +import jsinterop.annotations.JsPackage; +import jsinterop.annotations.JsProperty; +import jsinterop.annotations.JsType; +import jsinterop.base.Js; +import jsinterop.base.JsPropertyMap; + +@JsType( + isNative = true, + name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceClientMessage", + namespace = JsPackage.GLOBAL) +public class RemoteFileSourceClientMessage { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface MetaResponseFieldType { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface GetContentUnionType { + @JsOverlay + static RemoteFileSourceClientMessage.ToObjectReturnType.MetaResponseFieldType.GetContentUnionType of( + Object o) { + return Js.cast(o); + } + + @JsOverlay + default String asString() { + return Js.asString(this); + } + + @JsOverlay + default Uint8Array asUint8Array() { + return Js.cast(this); + } + + @JsOverlay + default boolean isString() { + return (Object) this instanceof String; + } + + @JsOverlay + default boolean isUint8Array() { + return (Object) this instanceof Uint8Array; + } + } + + @JsOverlay + static RemoteFileSourceClientMessage.ToObjectReturnType.MetaResponseFieldType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + RemoteFileSourceClientMessage.ToObjectReturnType.MetaResponseFieldType.GetContentUnionType getContent(); + + @JsProperty + String getError(); + + @JsProperty + boolean isFound(); + + @JsProperty + void setContent( + RemoteFileSourceClientMessage.ToObjectReturnType.MetaResponseFieldType.GetContentUnionType content); + + @JsOverlay + default void setContent(String content) { + setContent( + Js.uncheckedCast( + content)); + } + + @JsOverlay + default void setContent(Uint8Array content) { + setContent( + Js.uncheckedCast( + content)); + } + + @JsProperty + void setError(String error); + + @JsProperty + void setFound(boolean found); + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface SetExecutionContextFieldType { + @JsOverlay + static RemoteFileSourceClientMessage.ToObjectReturnType.SetExecutionContextFieldType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + JsArray getResourcePathsList(); + + @JsProperty + boolean isIsDirty(); + + @JsProperty + void setIsDirty(boolean isDirty); + + @JsProperty + void setResourcePathsList(JsArray resourcePathsList); + + @JsOverlay + default void setResourcePathsList(String[] resourcePathsList) { + setResourcePathsList(Js.>uncheckedCast(resourcePathsList)); + } + } + + @JsOverlay + static RemoteFileSourceClientMessage.ToObjectReturnType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + RemoteFileSourceClientMessage.ToObjectReturnType.MetaResponseFieldType getMetaResponse(); + + @JsProperty + String getRequestId(); + + @JsProperty + RemoteFileSourceClientMessage.ToObjectReturnType.SetExecutionContextFieldType getSetExecutionContext(); + + @JsProperty + void setMetaResponse( + RemoteFileSourceClientMessage.ToObjectReturnType.MetaResponseFieldType metaResponse); + + @JsProperty + void setRequestId(String requestId); + + @JsProperty + void setSetExecutionContext( + RemoteFileSourceClientMessage.ToObjectReturnType.SetExecutionContextFieldType setExecutionContext); + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType0 { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface MetaResponseFieldType { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface GetContentUnionType { + @JsOverlay + static RemoteFileSourceClientMessage.ToObjectReturnType0.MetaResponseFieldType.GetContentUnionType of( + Object o) { + return Js.cast(o); + } + + @JsOverlay + default String asString() { + return Js.asString(this); + } + + @JsOverlay + default Uint8Array asUint8Array() { + return Js.cast(this); + } + + @JsOverlay + default boolean isString() { + return (Object) this instanceof String; + } + + @JsOverlay + default boolean isUint8Array() { + return (Object) this instanceof Uint8Array; + } + } + + @JsOverlay + static RemoteFileSourceClientMessage.ToObjectReturnType0.MetaResponseFieldType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + RemoteFileSourceClientMessage.ToObjectReturnType0.MetaResponseFieldType.GetContentUnionType getContent(); + + @JsProperty + String getError(); + + @JsProperty + boolean isFound(); + + @JsProperty + void setContent( + RemoteFileSourceClientMessage.ToObjectReturnType0.MetaResponseFieldType.GetContentUnionType content); + + @JsOverlay + default void setContent(String content) { + setContent( + Js.uncheckedCast( + content)); + } + + @JsOverlay + default void setContent(Uint8Array content) { + setContent( + Js.uncheckedCast( + content)); + } + + @JsProperty + void setError(String error); + + @JsProperty + void setFound(boolean found); + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface SetExecutionContextFieldType { + @JsOverlay + static RemoteFileSourceClientMessage.ToObjectReturnType0.SetExecutionContextFieldType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + JsArray getResourcePathsList(); + + @JsProperty + boolean isIsDirty(); + + @JsProperty + void setIsDirty(boolean isDirty); + + @JsProperty + void setResourcePathsList(JsArray resourcePathsList); + + @JsOverlay + default void setResourcePathsList(String[] resourcePathsList) { + setResourcePathsList(Js.>uncheckedCast(resourcePathsList)); + } + } + + @JsOverlay + static RemoteFileSourceClientMessage.ToObjectReturnType0 create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + RemoteFileSourceClientMessage.ToObjectReturnType0.MetaResponseFieldType getMetaResponse(); + + @JsProperty + String getRequestId(); + + @JsProperty + RemoteFileSourceClientMessage.ToObjectReturnType0.SetExecutionContextFieldType getSetExecutionContext(); + + @JsProperty + void setMetaResponse( + RemoteFileSourceClientMessage.ToObjectReturnType0.MetaResponseFieldType metaResponse); + + @JsProperty + void setRequestId(String requestId); + + @JsProperty + void setSetExecutionContext( + RemoteFileSourceClientMessage.ToObjectReturnType0.SetExecutionContextFieldType setExecutionContext); + } + + public static native RemoteFileSourceClientMessage deserializeBinary(Uint8Array bytes); + + public static native RemoteFileSourceClientMessage deserializeBinaryFromReader( + RemoteFileSourceClientMessage message, Object reader); + + public static native void serializeBinaryToWriter( + RemoteFileSourceClientMessage message, Object writer); + + public static native RemoteFileSourceClientMessage.ToObjectReturnType toObject( + boolean includeInstance, RemoteFileSourceClientMessage msg); + + public native void clearMetaResponse(); + + public native void clearSetExecutionContext(); + + public native RemoteFileSourceMetaResponse getMetaResponse(); + + public native int getRequestCase(); + + public native String getRequestId(); + + public native SetExecutionContextRequest getSetExecutionContext(); + + public native boolean hasMetaResponse(); + + public native boolean hasSetExecutionContext(); + + public native Uint8Array serializeBinary(); + + public native void setMetaResponse(); + + public native void setMetaResponse(RemoteFileSourceMetaResponse value); + + public native void setRequestId(String value); + + public native void setSetExecutionContext(); + + public native void setSetExecutionContext(SetExecutionContextRequest value); + + public native RemoteFileSourceClientMessage.ToObjectReturnType0 toObject(); + + public native RemoteFileSourceClientMessage.ToObjectReturnType0 toObject(boolean includeInstance); +} diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaRequest.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaRequest.java new file mode 100644 index 00000000000..88b401232a6 --- /dev/null +++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaRequest.java @@ -0,0 +1,67 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb; + +import elemental2.core.Uint8Array; +import jsinterop.annotations.JsOverlay; +import jsinterop.annotations.JsPackage; +import jsinterop.annotations.JsProperty; +import jsinterop.annotations.JsType; +import jsinterop.base.Js; +import jsinterop.base.JsPropertyMap; + +@JsType( + isNative = true, + name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceMetaRequest", + namespace = JsPackage.GLOBAL) +public class RemoteFileSourceMetaRequest { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType { + @JsOverlay + static RemoteFileSourceMetaRequest.ToObjectReturnType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + String getResourceName(); + + @JsProperty + void setResourceName(String resourceName); + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType0 { + @JsOverlay + static RemoteFileSourceMetaRequest.ToObjectReturnType0 create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + String getResourceName(); + + @JsProperty + void setResourceName(String resourceName); + } + + public static native RemoteFileSourceMetaRequest deserializeBinary(Uint8Array bytes); + + public static native RemoteFileSourceMetaRequest deserializeBinaryFromReader( + RemoteFileSourceMetaRequest message, Object reader); + + public static native void serializeBinaryToWriter( + RemoteFileSourceMetaRequest message, Object writer); + + public static native RemoteFileSourceMetaRequest.ToObjectReturnType toObject( + boolean includeInstance, RemoteFileSourceMetaRequest msg); + + public native String getResourceName(); + + public native Uint8Array serializeBinary(); + + public native void setResourceName(String value); + + public native RemoteFileSourceMetaRequest.ToObjectReturnType0 toObject(); + + public native RemoteFileSourceMetaRequest.ToObjectReturnType0 toObject(boolean includeInstance); +} diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaResponse.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaResponse.java new file mode 100644 index 00000000000..057fd695300 --- /dev/null +++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaResponse.java @@ -0,0 +1,253 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb; + +import elemental2.core.Uint8Array; +import jsinterop.annotations.JsOverlay; +import jsinterop.annotations.JsPackage; +import jsinterop.annotations.JsProperty; +import jsinterop.annotations.JsType; +import jsinterop.base.Js; +import jsinterop.base.JsPropertyMap; + +@JsType( + isNative = true, + name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceMetaResponse", + namespace = JsPackage.GLOBAL) +public class RemoteFileSourceMetaResponse { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface GetContentUnionType { + @JsOverlay + static RemoteFileSourceMetaResponse.GetContentUnionType of(Object o) { + return Js.cast(o); + } + + @JsOverlay + default String asString() { + return Js.asString(this); + } + + @JsOverlay + default Uint8Array asUint8Array() { + return Js.cast(this); + } + + @JsOverlay + default boolean isString() { + return (Object) this instanceof String; + } + + @JsOverlay + default boolean isUint8Array() { + return (Object) this instanceof Uint8Array; + } + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface SetContentValueUnionType { + @JsOverlay + static RemoteFileSourceMetaResponse.SetContentValueUnionType of(Object o) { + return Js.cast(o); + } + + @JsOverlay + default String asString() { + return Js.asString(this); + } + + @JsOverlay + default Uint8Array asUint8Array() { + return Js.cast(this); + } + + @JsOverlay + default boolean isString() { + return (Object) this instanceof String; + } + + @JsOverlay + default boolean isUint8Array() { + return (Object) this instanceof Uint8Array; + } + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface GetContentUnionType { + @JsOverlay + static RemoteFileSourceMetaResponse.ToObjectReturnType.GetContentUnionType of(Object o) { + return Js.cast(o); + } + + @JsOverlay + default String asString() { + return Js.asString(this); + } + + @JsOverlay + default Uint8Array asUint8Array() { + return Js.cast(this); + } + + @JsOverlay + default boolean isString() { + return (Object) this instanceof String; + } + + @JsOverlay + default boolean isUint8Array() { + return (Object) this instanceof Uint8Array; + } + } + + @JsOverlay + static RemoteFileSourceMetaResponse.ToObjectReturnType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + RemoteFileSourceMetaResponse.ToObjectReturnType.GetContentUnionType getContent(); + + @JsProperty + String getError(); + + @JsProperty + boolean isFound(); + + @JsProperty + void setContent(RemoteFileSourceMetaResponse.ToObjectReturnType.GetContentUnionType content); + + @JsOverlay + default void setContent(String content) { + setContent( + Js.uncheckedCast( + content)); + } + + @JsOverlay + default void setContent(Uint8Array content) { + setContent( + Js.uncheckedCast( + content)); + } + + @JsProperty + void setError(String error); + + @JsProperty + void setFound(boolean found); + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType0 { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface GetContentUnionType { + @JsOverlay + static RemoteFileSourceMetaResponse.ToObjectReturnType0.GetContentUnionType of(Object o) { + return Js.cast(o); + } + + @JsOverlay + default String asString() { + return Js.asString(this); + } + + @JsOverlay + default Uint8Array asUint8Array() { + return Js.cast(this); + } + + @JsOverlay + default boolean isString() { + return (Object) this instanceof String; + } + + @JsOverlay + default boolean isUint8Array() { + return (Object) this instanceof Uint8Array; + } + } + + @JsOverlay + static RemoteFileSourceMetaResponse.ToObjectReturnType0 create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + RemoteFileSourceMetaResponse.ToObjectReturnType0.GetContentUnionType getContent(); + + @JsProperty + String getError(); + + @JsProperty + boolean isFound(); + + @JsProperty + void setContent(RemoteFileSourceMetaResponse.ToObjectReturnType0.GetContentUnionType content); + + @JsOverlay + default void setContent(String content) { + setContent( + Js.uncheckedCast( + content)); + } + + @JsOverlay + default void setContent(Uint8Array content) { + setContent( + Js.uncheckedCast( + content)); + } + + @JsProperty + void setError(String error); + + @JsProperty + void setFound(boolean found); + } + + public static native RemoteFileSourceMetaResponse deserializeBinary(Uint8Array bytes); + + public static native RemoteFileSourceMetaResponse deserializeBinaryFromReader( + RemoteFileSourceMetaResponse message, Object reader); + + public static native void serializeBinaryToWriter( + RemoteFileSourceMetaResponse message, Object writer); + + public static native RemoteFileSourceMetaResponse.ToObjectReturnType toObject( + boolean includeInstance, RemoteFileSourceMetaResponse msg); + + public native RemoteFileSourceMetaResponse.GetContentUnionType getContent(); + + public native String getContent_asB64(); + + public native Uint8Array getContent_asU8(); + + public native String getError(); + + public native boolean getFound(); + + public native Uint8Array serializeBinary(); + + public native void setContent(RemoteFileSourceMetaResponse.SetContentValueUnionType value); + + @JsOverlay + public final void setContent(String value) { + setContent(Js.uncheckedCast(value)); + } + + @JsOverlay + public final void setContent(Uint8Array value) { + setContent(Js.uncheckedCast(value)); + } + + public native void setError(String value); + + public native void setFound(boolean value); + + public native RemoteFileSourceMetaResponse.ToObjectReturnType0 toObject(); + + public native RemoteFileSourceMetaResponse.ToObjectReturnType0 toObject(boolean includeInstance); +} diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourcePluginFetchRequest.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourcePluginFetchRequest.java new file mode 100644 index 00000000000..afc60285df6 --- /dev/null +++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourcePluginFetchRequest.java @@ -0,0 +1,209 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb; + +import elemental2.core.Uint8Array; +import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.ticket_pb.Ticket; +import jsinterop.annotations.JsOverlay; +import jsinterop.annotations.JsPackage; +import jsinterop.annotations.JsProperty; +import jsinterop.annotations.JsType; +import jsinterop.base.Js; +import jsinterop.base.JsPropertyMap; + +@JsType( + isNative = true, + name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourcePluginFetchRequest", + namespace = JsPackage.GLOBAL) +public class RemoteFileSourcePluginFetchRequest { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ResultIdFieldType { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface GetTicketUnionType { + @JsOverlay + static RemoteFileSourcePluginFetchRequest.ToObjectReturnType.ResultIdFieldType.GetTicketUnionType of( + Object o) { + return Js.cast(o); + } + + @JsOverlay + default String asString() { + return Js.asString(this); + } + + @JsOverlay + default Uint8Array asUint8Array() { + return Js.cast(this); + } + + @JsOverlay + default boolean isString() { + return (Object) this instanceof String; + } + + @JsOverlay + default boolean isUint8Array() { + return (Object) this instanceof Uint8Array; + } + } + + @JsOverlay + static RemoteFileSourcePluginFetchRequest.ToObjectReturnType.ResultIdFieldType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + RemoteFileSourcePluginFetchRequest.ToObjectReturnType.ResultIdFieldType.GetTicketUnionType getTicket(); + + @JsProperty + void setTicket( + RemoteFileSourcePluginFetchRequest.ToObjectReturnType.ResultIdFieldType.GetTicketUnionType ticket); + + @JsOverlay + default void setTicket(String ticket) { + setTicket( + Js.uncheckedCast( + ticket)); + } + + @JsOverlay + default void setTicket(Uint8Array ticket) { + setTicket( + Js.uncheckedCast( + ticket)); + } + } + + @JsOverlay + static RemoteFileSourcePluginFetchRequest.ToObjectReturnType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + String getPluginName(); + + @JsProperty + RemoteFileSourcePluginFetchRequest.ToObjectReturnType.ResultIdFieldType getResultId(); + + @JsProperty + void setPluginName(String pluginName); + + @JsProperty + void setResultId( + RemoteFileSourcePluginFetchRequest.ToObjectReturnType.ResultIdFieldType resultId); + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType0 { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ResultIdFieldType { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface GetTicketUnionType { + @JsOverlay + static RemoteFileSourcePluginFetchRequest.ToObjectReturnType0.ResultIdFieldType.GetTicketUnionType of( + Object o) { + return Js.cast(o); + } + + @JsOverlay + default String asString() { + return Js.asString(this); + } + + @JsOverlay + default Uint8Array asUint8Array() { + return Js.cast(this); + } + + @JsOverlay + default boolean isString() { + return (Object) this instanceof String; + } + + @JsOverlay + default boolean isUint8Array() { + return (Object) this instanceof Uint8Array; + } + } + + @JsOverlay + static RemoteFileSourcePluginFetchRequest.ToObjectReturnType0.ResultIdFieldType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + RemoteFileSourcePluginFetchRequest.ToObjectReturnType0.ResultIdFieldType.GetTicketUnionType getTicket(); + + @JsProperty + void setTicket( + RemoteFileSourcePluginFetchRequest.ToObjectReturnType0.ResultIdFieldType.GetTicketUnionType ticket); + + @JsOverlay + default void setTicket(String ticket) { + setTicket( + Js.uncheckedCast( + ticket)); + } + + @JsOverlay + default void setTicket(Uint8Array ticket) { + setTicket( + Js.uncheckedCast( + ticket)); + } + } + + @JsOverlay + static RemoteFileSourcePluginFetchRequest.ToObjectReturnType0 create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + String getPluginName(); + + @JsProperty + RemoteFileSourcePluginFetchRequest.ToObjectReturnType0.ResultIdFieldType getResultId(); + + @JsProperty + void setPluginName(String pluginName); + + @JsProperty + void setResultId( + RemoteFileSourcePluginFetchRequest.ToObjectReturnType0.ResultIdFieldType resultId); + } + + public static native RemoteFileSourcePluginFetchRequest deserializeBinary(Uint8Array bytes); + + public static native RemoteFileSourcePluginFetchRequest deserializeBinaryFromReader( + RemoteFileSourcePluginFetchRequest message, Object reader); + + public static native void serializeBinaryToWriter( + RemoteFileSourcePluginFetchRequest message, Object writer); + + public static native RemoteFileSourcePluginFetchRequest.ToObjectReturnType toObject( + boolean includeInstance, RemoteFileSourcePluginFetchRequest msg); + + public native void clearResultId(); + + public native String getPluginName(); + + public native Ticket getResultId(); + + public native boolean hasResultId(); + + public native Uint8Array serializeBinary(); + + public native void setPluginName(String value); + + public native void setResultId(); + + public native void setResultId(Ticket value); + + public native RemoteFileSourcePluginFetchRequest.ToObjectReturnType0 toObject(); + + public native RemoteFileSourcePluginFetchRequest.ToObjectReturnType0 toObject( + boolean includeInstance); +} diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceServerMessage.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceServerMessage.java new file mode 100644 index 00000000000..9069c393d6d --- /dev/null +++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceServerMessage.java @@ -0,0 +1,173 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb; + +import elemental2.core.Uint8Array; +import jsinterop.annotations.JsOverlay; +import jsinterop.annotations.JsPackage; +import jsinterop.annotations.JsProperty; +import jsinterop.annotations.JsType; +import jsinterop.base.Js; +import jsinterop.base.JsPropertyMap; + +@JsType( + isNative = true, + name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceServerMessage", + namespace = JsPackage.GLOBAL) +public class RemoteFileSourceServerMessage { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface MetaRequestFieldType { + @JsOverlay + static RemoteFileSourceServerMessage.ToObjectReturnType.MetaRequestFieldType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + String getResourceName(); + + @JsProperty + void setResourceName(String resourceName); + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface SetExecutionContextResponseFieldType { + @JsOverlay + static RemoteFileSourceServerMessage.ToObjectReturnType.SetExecutionContextResponseFieldType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + boolean isSuccess(); + + @JsProperty + void setSuccess(boolean success); + } + + @JsOverlay + static RemoteFileSourceServerMessage.ToObjectReturnType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + RemoteFileSourceServerMessage.ToObjectReturnType.MetaRequestFieldType getMetaRequest(); + + @JsProperty + String getRequestId(); + + @JsProperty + RemoteFileSourceServerMessage.ToObjectReturnType.SetExecutionContextResponseFieldType getSetExecutionContextResponse(); + + @JsProperty + void setMetaRequest( + RemoteFileSourceServerMessage.ToObjectReturnType.MetaRequestFieldType metaRequest); + + @JsProperty + void setRequestId(String requestId); + + @JsProperty + void setSetExecutionContextResponse( + RemoteFileSourceServerMessage.ToObjectReturnType.SetExecutionContextResponseFieldType setExecutionContextResponse); + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType0 { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface MetaRequestFieldType { + @JsOverlay + static RemoteFileSourceServerMessage.ToObjectReturnType0.MetaRequestFieldType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + String getResourceName(); + + @JsProperty + void setResourceName(String resourceName); + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface SetExecutionContextResponseFieldType { + @JsOverlay + static RemoteFileSourceServerMessage.ToObjectReturnType0.SetExecutionContextResponseFieldType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + boolean isSuccess(); + + @JsProperty + void setSuccess(boolean success); + } + + @JsOverlay + static RemoteFileSourceServerMessage.ToObjectReturnType0 create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + RemoteFileSourceServerMessage.ToObjectReturnType0.MetaRequestFieldType getMetaRequest(); + + @JsProperty + String getRequestId(); + + @JsProperty + RemoteFileSourceServerMessage.ToObjectReturnType0.SetExecutionContextResponseFieldType getSetExecutionContextResponse(); + + @JsProperty + void setMetaRequest( + RemoteFileSourceServerMessage.ToObjectReturnType0.MetaRequestFieldType metaRequest); + + @JsProperty + void setRequestId(String requestId); + + @JsProperty + void setSetExecutionContextResponse( + RemoteFileSourceServerMessage.ToObjectReturnType0.SetExecutionContextResponseFieldType setExecutionContextResponse); + } + + public static native RemoteFileSourceServerMessage deserializeBinary(Uint8Array bytes); + + public static native RemoteFileSourceServerMessage deserializeBinaryFromReader( + RemoteFileSourceServerMessage message, Object reader); + + public static native void serializeBinaryToWriter( + RemoteFileSourceServerMessage message, Object writer); + + public static native RemoteFileSourceServerMessage.ToObjectReturnType toObject( + boolean includeInstance, RemoteFileSourceServerMessage msg); + + public native void clearMetaRequest(); + + public native void clearSetExecutionContextResponse(); + + public native RemoteFileSourceMetaRequest getMetaRequest(); + + public native int getRequestCase(); + + public native String getRequestId(); + + public native SetExecutionContextResponse getSetExecutionContextResponse(); + + public native boolean hasMetaRequest(); + + public native boolean hasSetExecutionContextResponse(); + + public native Uint8Array serializeBinary(); + + public native void setMetaRequest(); + + public native void setMetaRequest(RemoteFileSourceMetaRequest value); + + public native void setRequestId(String value); + + public native void setSetExecutionContextResponse(); + + public native void setSetExecutionContextResponse(SetExecutionContextResponse value); + + public native RemoteFileSourceServerMessage.ToObjectReturnType0 toObject(); + + public native RemoteFileSourceServerMessage.ToObjectReturnType0 toObject(boolean includeInstance); +} diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextRequest.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextRequest.java new file mode 100644 index 00000000000..728323a187a --- /dev/null +++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextRequest.java @@ -0,0 +1,105 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb; + +import elemental2.core.JsArray; +import elemental2.core.Uint8Array; +import jsinterop.annotations.JsOverlay; +import jsinterop.annotations.JsPackage; +import jsinterop.annotations.JsProperty; +import jsinterop.annotations.JsType; +import jsinterop.base.Js; +import jsinterop.base.JsPropertyMap; + +@JsType( + isNative = true, + name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.SetExecutionContextRequest", + namespace = JsPackage.GLOBAL) +public class SetExecutionContextRequest { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType { + @JsOverlay + static SetExecutionContextRequest.ToObjectReturnType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + JsArray getResourcePathsList(); + + @JsProperty + boolean isIsDirty(); + + @JsProperty + void setIsDirty(boolean isDirty); + + @JsProperty + void setResourcePathsList(JsArray resourcePathsList); + + @JsOverlay + default void setResourcePathsList(String[] resourcePathsList) { + setResourcePathsList(Js.>uncheckedCast(resourcePathsList)); + } + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType0 { + @JsOverlay + static SetExecutionContextRequest.ToObjectReturnType0 create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + JsArray getResourcePathsList(); + + @JsProperty + boolean isIsDirty(); + + @JsProperty + void setIsDirty(boolean isDirty); + + @JsProperty + void setResourcePathsList(JsArray resourcePathsList); + + @JsOverlay + default void setResourcePathsList(String[] resourcePathsList) { + setResourcePathsList(Js.>uncheckedCast(resourcePathsList)); + } + } + + public static native SetExecutionContextRequest deserializeBinary(Uint8Array bytes); + + public static native SetExecutionContextRequest deserializeBinaryFromReader( + SetExecutionContextRequest message, Object reader); + + public static native void serializeBinaryToWriter( + SetExecutionContextRequest message, Object writer); + + public static native SetExecutionContextRequest.ToObjectReturnType toObject( + boolean includeInstance, SetExecutionContextRequest msg); + + public native String addResourcePaths(String value, double index); + + public native String addResourcePaths(String value); + + public native void clearResourcePathsList(); + + public native boolean getIsDirty(); + + public native JsArray getResourcePathsList(); + + public native Uint8Array serializeBinary(); + + public native void setIsDirty(boolean value); + + public native void setResourcePathsList(JsArray value); + + @JsOverlay + public final void setResourcePathsList(String[] value) { + setResourcePathsList(Js.>uncheckedCast(value)); + } + + public native SetExecutionContextRequest.ToObjectReturnType0 toObject(); + + public native SetExecutionContextRequest.ToObjectReturnType0 toObject(boolean includeInstance); +} diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextResponse.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextResponse.java new file mode 100644 index 00000000000..6f9b83938c7 --- /dev/null +++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextResponse.java @@ -0,0 +1,67 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb; + +import elemental2.core.Uint8Array; +import jsinterop.annotations.JsOverlay; +import jsinterop.annotations.JsPackage; +import jsinterop.annotations.JsProperty; +import jsinterop.annotations.JsType; +import jsinterop.base.Js; +import jsinterop.base.JsPropertyMap; + +@JsType( + isNative = true, + name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.SetExecutionContextResponse", + namespace = JsPackage.GLOBAL) +public class SetExecutionContextResponse { + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType { + @JsOverlay + static SetExecutionContextResponse.ToObjectReturnType create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + boolean isSuccess(); + + @JsProperty + void setSuccess(boolean success); + } + + @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL) + public interface ToObjectReturnType0 { + @JsOverlay + static SetExecutionContextResponse.ToObjectReturnType0 create() { + return Js.uncheckedCast(JsPropertyMap.of()); + } + + @JsProperty + boolean isSuccess(); + + @JsProperty + void setSuccess(boolean success); + } + + public static native SetExecutionContextResponse deserializeBinary(Uint8Array bytes); + + public static native SetExecutionContextResponse deserializeBinaryFromReader( + SetExecutionContextResponse message, Object reader); + + public static native void serializeBinaryToWriter( + SetExecutionContextResponse message, Object writer); + + public static native SetExecutionContextResponse.ToObjectReturnType toObject( + boolean includeInstance, SetExecutionContextResponse msg); + + public native boolean getSuccess(); + + public native Uint8Array serializeBinary(); + + public native void setSuccess(boolean value); + + public native SetExecutionContextResponse.ToObjectReturnType0 toObject(); + + public native SetExecutionContextResponse.ToObjectReturnType0 toObject(boolean includeInstance); +} diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceclientmessage/RequestCase.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceclientmessage/RequestCase.java new file mode 100644 index 00000000000..402849f4f93 --- /dev/null +++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceclientmessage/RequestCase.java @@ -0,0 +1,15 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.remotefilesourceclientmessage; + +import jsinterop.annotations.JsPackage; +import jsinterop.annotations.JsType; + +@JsType( + isNative = true, + name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceClientMessage.RequestCase", + namespace = JsPackage.GLOBAL) +public class RequestCase { + public static int META_RESPONSE, REQUEST_NOT_SET, SET_EXECUTION_CONTEXT; +} diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceservermessage/RequestCase.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceservermessage/RequestCase.java new file mode 100644 index 00000000000..5d3752b8728 --- /dev/null +++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceservermessage/RequestCase.java @@ -0,0 +1,15 @@ +// +// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending +// +package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.remotefilesourceservermessage; + +import jsinterop.annotations.JsPackage; +import jsinterop.annotations.JsType; + +@JsType( + isNative = true, + name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceServerMessage.RequestCase", + namespace = JsPackage.GLOBAL) +public class RequestCase { + public static int META_REQUEST, REQUEST_NOT_SET, SET_EXECUTION_CONTEXT_RESPONSE; +}