io.deephaven.server.plugin.js.JsPluginNpmPackageRegistration Maven / Gradle / Ivy
//
// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending
//
package io.deephaven.server.plugin.js;
import dagger.Binds;
import dagger.multibindings.IntoSet;
import io.deephaven.configuration.Configuration;
import io.deephaven.plugin.Registration;
import io.deephaven.plugin.js.JsPlugin;
import javax.inject.Inject;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import static io.deephaven.server.plugin.js.JsPluginManifestRegistration.JS_PLUGIN_RESOURCE_BASE;
import static io.deephaven.server.plugin.js.JsPluginModule.DEEPHAVEN_JS_PLUGINS_PREFIX;
/**
* Registers the {@link JsPlugin JS plugins} sourced from the NPM package roots as specified via the configuration
* properties that start with {@value io.deephaven.server.plugin.js.JsPluginModule#DEEPHAVEN_JS_PLUGINS_PREFIX}. This
* configuration is meant for development-oriented use-cases and is done on a "best-effort" basis.
*
*
* The configuration value of the above property corresponds to the {@link JsPlugin#path()} directory. A
* {@value PACKAGE_JSON} must exist in this directory (as specified via
* package-json). The {@value NAME} json value
* corresponds to {@link JsPlugin#name()}, the {@value VERSION} json value corresponds to {@link JsPlugin#version()},
* and the {@value MAIN} json value corresponds to {@link JsPlugin#main()}. Furthermore, the top-level directory of the
* {@value MAIN} json value will be used to set {@link JsPlugin#paths()} using
* {@link io.deephaven.plugin.js.Paths#ofPrefixes(Path)}; for example, a {@value MAIN} of "build/index.js" will limit
* the resources to the "build/" directory; a {@value MAIN} of "dist/bundle/index.js" will limit the resources to the
* "dist/" directory.
*/
public final class JsPluginNpmPackageRegistration implements Registration {
public static final String PACKAGE_JSON = "package.json";
public static final String NAME = "name";
public static final String VERSION = "version";
public static final String MAIN = "main";
// TODO(deephaven-core#4817): JS Plugins development is slow
// We may wish to make parsing NPM package.json easier with a bespoke "deephaven" field, or try to exactly match
// the semantics of the existing "files" field. This may not be necessary if the top-level directory of "main" works
// well enough, or if we have a non-copying jetty route-based impl.
/**
* Binds {@link JsPluginNpmPackageRegistration} into the set of {@link Registration}.
*/
@dagger.Module
public interface Module {
@Binds
@IntoSet
Registration bindsRegistration(JsPluginNpmPackageRegistration registration);
}
@Inject
JsPluginNpmPackageRegistration() {}
@Override
public void registerInto(Callback callback) {
// deephaven.jsPlugins. (package root)
final Configuration config = Configuration.getInstance();
final Set parts = partsThatStartWith(DEEPHAVEN_JS_PLUGINS_PREFIX, config);
for (String part : parts) {
final String propertyName = DEEPHAVEN_JS_PLUGINS_PREFIX + part;
if (JS_PLUGIN_RESOURCE_BASE.equals(propertyName)) {
// handled by jsPluginsResourceBase
continue;
}
final String packageRoot = config.getStringWithDefault(propertyName, null);
if (packageRoot == null) {
continue;
}
URI uri = URI.create(packageRoot);
if (uri.getScheme() == null) {
uri = URI.create("file:" + packageRoot);
}
final FileSystem fileSystem = getOrCreateFileSystem(uri);
final JsPlugin plugin;
try {
plugin = JsPluginFromNpmPackage.of(fileSystem.provider().getPath(uri));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
callback.register(plugin);
}
}
private static FileSystem getOrCreateFileSystem(URI uri) {
if ("file".equalsIgnoreCase(uri.getScheme())) {
return FileSystems.getDefault();
}
try {
return FileSystems.getFileSystem(uri);
} catch (FileSystemNotFoundException e) {
// ignore
}
try {
return FileSystems.newFileSystem(uri, Map.of());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static Set partsThatStartWith(String prefix, Configuration configuration) {
final Set parts = new HashSet<>();
final Iterator