Skip to content

Commit

Permalink
Add package.json js-plugins support (#4389)
Browse files Browse the repository at this point in the history
In support of #4405
  • Loading branch information
devinrsmith authored Oct 5, 2023
1 parent acebb6f commit 87ee5ec
Show file tree
Hide file tree
Showing 24 changed files with 708 additions and 61 deletions.
2 changes: 2 additions & 0 deletions plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ plugins {
}

description = 'The Deephaven plugin interface'

Classpaths.inheritImmutables(project)
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
import io.deephaven.plugin.js.JsPlugin;
import io.deephaven.plugin.type.ObjectType;

import java.util.ServiceLoader;
Expand All @@ -15,7 +16,7 @@

/**
* Provides the set of {@link Registration} from {@link ServiceLoader#load(Class)} against the classes
* {@link Registration}, {@link Plugin}, and {@link ObjectType}.
* {@link Registration}, {@link Plugin}, {@link ObjectType}, and {@link JsPlugin}.
*/
@Module
public interface PluginModule {
Expand All @@ -37,4 +38,10 @@ static Set<Registration> providesServiceLoaderPlugins() {
static Set<Registration> providesServiceLoaderObjectTypes() {
return ServiceLoader.load(ObjectType.class).stream().map(Provider::get).collect(Collectors.toSet());
}

@Provides
@ElementsIntoSet
static Set<Registration> providesServiceLoaderJsPlugin() {
return ServiceLoader.load(JsPlugin.class).stream().map(Provider::get).collect(Collectors.toSet());
}
}
3 changes: 3 additions & 0 deletions plugin/src/main/java/io/deephaven/plugin/Plugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
package io.deephaven.plugin;

import io.deephaven.plugin.js.JsPlugin;
import io.deephaven.plugin.type.ObjectType;

/**
Expand All @@ -24,5 +25,7 @@ public interface Plugin extends Registration {

interface Visitor<T> {
T visit(ObjectType objectType);

T visit(JsPlugin jsPlugin);
}
}
18 changes: 18 additions & 0 deletions plugin/src/main/java/io/deephaven/plugin/js/JsPlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending
*/
package io.deephaven.plugin.js;

import io.deephaven.plugin.Plugin;

/**
* A js plugin is a {@link Plugin} that allows adding javascript code under the server's URL path "js-plugins/". See
* <a href="https://github.com/deephaven/deephaven-plugins#js-plugins">deephaven-plugins#js-plugins</a> for more details
* about the underlying construction for js plugins.
*
* @see JsPluginPackagePath
* @see JsPluginManifestPath
*/
public interface JsPlugin extends Plugin {

}
15 changes: 15 additions & 0 deletions plugin/src/main/java/io/deephaven/plugin/js/JsPluginBase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending
*/
package io.deephaven.plugin.js;

import io.deephaven.plugin.Plugin;
import io.deephaven.plugin.PluginBase;

public abstract class JsPluginBase extends PluginBase implements JsPlugin {

@Override
public final <T, V extends Plugin.Visitor<T>> T walk(V visitor) {
return visitor.visit(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending
*/
package io.deephaven.plugin.js;

import io.deephaven.annotations.SimpleStyle;
import org.immutables.value.Value.Immutable;
import org.immutables.value.Value.Parameter;

import java.nio.file.Path;

/**
* A manifest-based js plugin sourced from a {@value MANIFEST_JSON} file.
*/
@Immutable
@SimpleStyle
public abstract class JsPluginManifestPath extends JsPluginBase {

public static final String MANIFEST_JSON = "manifest.json";

/**
* Creates a manifest-based js plugin from {@code manifestRoot}.
*
* @param manifestRoot the manifest root directory path
* @return the manifest-based js plugin
*/
public static JsPluginManifestPath of(Path manifestRoot) {
return ImmutableJsPluginManifestPath.of(manifestRoot);
}

/**
* The manifest root path directory path.
*
* @return the manifest root directory path
*/
@Parameter
public abstract Path path();

/**
* The {@value MANIFEST_JSON} file path, relative to {@link #path()}. Equivalent to
* {@code path().resolve(MANIFEST_JSON)}.
*
* @return the manifest json file path
*/
public final Path manifestJson() {
return path().resolve(MANIFEST_JSON);
}

/**
* Equivalent to {@code JsPluginPackagePath.of(path().resolve(name))}.
*
* @param name the package name
* @return the package path
*/
public final JsPluginPackagePath packagePath(String name) {
return JsPluginPackagePath.of(path().resolve(name));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending
*/
package io.deephaven.plugin.js;

import io.deephaven.annotations.SimpleStyle;
import org.immutables.value.Value.Immutable;
import org.immutables.value.Value.Parameter;

import java.nio.file.Path;

/**
* A package-based js plugin sourced from a {@value PACKAGE_JSON} file.
*/
@Immutable
@SimpleStyle
public abstract class JsPluginPackagePath extends JsPluginBase {
public static final String PACKAGE_JSON = "package.json";

/**
* Creates a package-based js plugin from {@code packageRoot}.
*
* @param packageRoot the package root directory path
* @return the package-based js plugin
*/
public static JsPluginPackagePath of(Path packageRoot) {
return ImmutableJsPluginPackagePath.of(packageRoot);
}

/**
* The package root directory path.
*
* @return the package root directory path
*/
@Parameter
public abstract Path path();

/**
* The {@value PACKAGE_JSON} file path. Equivalent to {@code path().resolve(PACKAGE_JSON)}.
*
* @return the package json file path
*/
public final Path packageJson() {
return path().resolve(PACKAGE_JSON);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending
*/
package io.deephaven.server.jetty;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Objects;

class CopyHelper {
static void copyRecursive(Path src, Path dst) throws IOException {
Files.createDirectories(dst.getParent());
Files.walkFileTree(src, new CopyRecursiveVisitor(src, dst));
}

private static class CopyRecursiveVisitor extends SimpleFileVisitor<Path> {
private final Path src;
private final Path dst;

public CopyRecursiveVisitor(Path src, Path dst) {
this.src = Objects.requireNonNull(src);
this.dst = Objects.requireNonNull(dst);
}

@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
// Note: toString() necessary for src/dst that don't share the same root FS
Files.copy(dir, dst.resolve(src.relativize(dir).toString()), StandardCopyOption.COPY_ATTRIBUTES);
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
// Note: toString() necessary for src/dst that don't share the same root FS
Files.copy(file, dst.resolve(src.relativize(file).toString()), StandardCopyOption.COPY_ATTRIBUTES);
return FileVisitResult.CONTINUE;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
package io.deephaven.server.jetty;

import io.deephaven.plugin.js.JsPlugin;
import io.deephaven.server.browserstreaming.BrowserStreamInterceptor;
import io.deephaven.server.runner.GrpcServer;
import io.deephaven.ssl.config.CiphersIntermediate;
Expand All @@ -12,10 +13,10 @@
import io.deephaven.ssl.config.impl.KickstartUtils;
import io.grpc.InternalStatus;
import io.grpc.internal.GrpcUtil;
import io.grpc.servlet.jakarta.web.GrpcWebFilter;
import io.grpc.servlet.web.websocket.GrpcWebsocket;
import io.grpc.servlet.web.websocket.MultiplexedWebSocketServerStream;
import io.grpc.servlet.web.websocket.WebSocketServerStream;
import io.grpc.servlet.jakarta.web.GrpcWebFilter;
import jakarta.servlet.DispatcherType;
import jakarta.websocket.Endpoint;
import jakarta.websocket.server.ServerEndpointConfig;
Expand All @@ -36,7 +37,6 @@
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HandlerContainer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
Expand All @@ -48,6 +48,7 @@
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.component.Graceful;
Expand All @@ -59,8 +60,10 @@
import org.eclipse.jetty.websocket.jakarta.server.internal.JakartaWebSocketServerContainer;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -70,15 +73,19 @@
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static io.grpc.servlet.web.websocket.MultiplexedWebSocketServerStream.GRPC_WEBSOCKETS_MULTIPLEX_PROTOCOL;
import static io.grpc.servlet.web.websocket.WebSocketServerStream.GRPC_WEBSOCKETS_PROTOCOL;
import static org.eclipse.jetty.servlet.ServletContextHandler.NO_SESSIONS;

@Singleton
public class JettyBackedGrpcServer implements GrpcServer {
private static final String JS_PLUGINS_PATH_SPEC = "/" + JsPlugins.JS_PLUGINS + "/*";

private final Server jetty;
private final JsPlugins jsPlugins;
private final boolean websocketsEnabled;

@Inject
Expand All @@ -87,6 +94,11 @@ public JettyBackedGrpcServer(
final GrpcFilter filter) {
jetty = new Server();
jetty.addConnector(createConnector(jetty, config));
try {
jsPlugins = JsPlugins.create();
} catch (IOException e) {
throw new UncheckedIOException(e);
}

final WebAppContext context =
new WebAppContext(null, "/", null, null, null, new ErrorPageErrorHandler(), NO_SESSIONS);
Expand Down Expand Up @@ -170,6 +182,9 @@ public JettyBackedGrpcServer(
// Wire up the provided grpc filter
context.addFilter(new FilterHolder(filter), "/*", EnumSet.noneOf(DispatcherType.class));

// Wire up /js-plugins/*
context.addServlet(servletHolder("js-plugins", jsPlugins.filesystem()), JS_PLUGINS_PATH_SPEC);

// Set up websockets for grpc-web - depending on configuration, we can register both in case we encounter a
// client using "vanilla"
// grpc-websocket, that can't multiplex all streams on a single socket
Expand Down Expand Up @@ -207,8 +222,7 @@ public <T> T getEndpointInstance(Class<T> endpointClass) {

// Note: handler order matters due to pathSpec order
HandlerCollection handlers = new HandlerCollection();
// Set up /js-plugins/*
JsPlugins.maybeAdd(handlers::addHandler);

// Set up /*
handlers.addHandler(context);

Expand All @@ -230,6 +244,10 @@ public <T> T getEndpointInstance(Class<T> endpointClass) {
jetty.setHandler(handler);
}

public Consumer<JsPlugin> jsPluginConsumer() {
return jsPlugins;
}

@Override
public void start() throws IOException {
try {
Expand Down Expand Up @@ -366,4 +384,16 @@ public void onClosed(Connection connection) {

return serverConnector;
}

private static ServletHolder servletHolder(String name, URI filesystemUri) {
final ServletHolder jsPlugins = new ServletHolder(name, DefaultServlet.class);
// Note, the URI needs explicitly be parseable as a directory URL ending in "!/", a requirement of the jetty
// resource creation implementation, see
// org.eclipse.jetty.util.resource.Resource.newResource(java.lang.String, boolean)
jsPlugins.setInitParameter("resourceBase", filesystemUri.toString());
jsPlugins.setInitParameter("pathInfoOnly", "true");
jsPlugins.setInitParameter("dirAllowed", "false");
jsPlugins.setAsyncSupported(true);
return jsPlugins;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import io.deephaven.plugin.js.JsPlugin;
import io.deephaven.server.config.ServerConfig;
import io.deephaven.server.plugin.PluginsModule;
import io.deephaven.server.runner.GrpcServer;
import io.grpc.BindableService;
import io.grpc.ServerInterceptor;
Expand All @@ -18,6 +20,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.function.Consumer;

import static io.grpc.internal.GrpcUtil.getThreadFactory;

Expand Down Expand Up @@ -63,4 +66,10 @@ static ServletAdapter provideGrpcServletAdapter(

return serverBuilder.buildServletAdapter();
}

@Provides
@Named(PluginsModule.JS_PLUGIN_CONSUMER_NAME)
static Consumer<JsPlugin> providesJsPluginConsumer(JettyBackedGrpcServer server) {
return server.jsPluginConsumer();
}
}
Loading

0 comments on commit 87ee5ec

Please sign in to comment.