All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.deephaven.server.jetty.JsPluginsZipFilesystem Maven / Gradle / Ivy

//
// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending
//
package io.deephaven.server.jetty;

import io.deephaven.configuration.CacheDir;
import io.deephaven.plugin.js.JsPlugin;
import io.deephaven.server.plugin.js.JsPluginManifest;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static io.deephaven.server.jetty.Json.OBJECT_MAPPER;
import static io.deephaven.server.plugin.js.JsPluginManifest.MANIFEST_JSON;

class JsPluginsZipFilesystem {
    private static final String ZIP_ROOT = "/";

    /**
     * Creates a new js plugins instance with a temporary zip filesystem.
     *
     * @return the js plugins
     * @throws IOException if an I/O exception occurs
     */
    public static JsPluginsZipFilesystem create() throws IOException {
        final Path tempDir =
                Files.createTempDirectory(CacheDir.get(), "." + JsPluginsZipFilesystem.class.getSimpleName());
        tempDir.toFile().deleteOnExit();
        final Path fsZip = tempDir.resolve("deephaven-js-plugins.zip");
        fsZip.toFile().deleteOnExit();
        final URI uri = URI.create(String.format("jar:%s!/", fsZip.toUri()));
        final JsPluginsZipFilesystem jsPlugins = new JsPluginsZipFilesystem(uri);
        jsPlugins.init();
        return jsPlugins;
    }

    private final URI filesystem;
    private final List plugins;

    private JsPluginsZipFilesystem(URI filesystem) {
        this.filesystem = Objects.requireNonNull(filesystem);
        this.plugins = new ArrayList<>();
    }

    public URI filesystem() {
        return filesystem;
    }

    public synchronized void add(JsPlugin plugin) throws IOException {
        checkExisting(plugin.name());
        // TODO(deephaven-core#3005): js-plugins checksum-based caching
        // Note: FileSystem#close is necessary to write out contents for ZipFileSystem
        try (final FileSystem fs = FileSystems.newFileSystem(filesystem, Map.of())) {
            final Path manifestRoot = manifestRoot(fs);
            final Path dstPath = manifestRoot.resolve(plugin.name());
            // This is using internal knowledge that paths() must be PathsInternal and extends PathsMatcher.
            final PathMatcher pathMatcher = (PathMatcher) plugin.paths();
            // If listing and traversing the contents of development directories (and skipping the copy) becomes
            // too expensive, we can add logic here wrt PathsInternal/PathsPrefix to specify a dirMatcher. Or,
            // properly route directly from the filesystem via Jetty.
            CopyHelper.copyRecursive(plugin.path(), dstPath, pathMatcher);
            plugins.add(plugin);
            writeManifest(fs);
        }
    }

    private void checkExisting(String name) {
        for (JsPlugin existing : plugins) {
            if (name.equals(existing.name())) {
                // TODO(deephaven-core#3048): Improve JS plugin support around plugins with conflicting names
                throw new IllegalArgumentException(String.format(
                        "js plugin with name '%s' already exists. See https://github.com/deephaven/deephaven-core/issues/3048",
                        existing.name()));
            }
        }
    }

    private synchronized void init() throws IOException {
        // Note: FileSystem#close is necessary to write out contents for ZipFileSystem
        try (final FileSystem fs = FileSystems.newFileSystem(filesystem, Map.of("create", "true"))) {
            writeManifest(fs);
        }
    }

    private void writeManifest(FileSystem fs) throws IOException {
        final Path manifestJson = manifestRoot(fs).resolve(MANIFEST_JSON);
        final Path manifestJsonTmp = manifestJson.resolveSibling(manifestJson.getFileName().toString() + ".tmp");
        // jackson impl does buffering internally
        try (final OutputStream out = Files.newOutputStream(manifestJsonTmp)) {
            OBJECT_MAPPER.writeValue(out, manifest());
            out.flush();
        }
        Files.move(manifestJsonTmp, manifestJson,
                StandardCopyOption.REPLACE_EXISTING,
                StandardCopyOption.COPY_ATTRIBUTES,
                StandardCopyOption.ATOMIC_MOVE);
    }

    private JsPluginManifest manifest() {
        return JsPluginManifest.from(plugins);
    }

    private static Path manifestRoot(FileSystem fs) {
        return fs.getPath(ZIP_ROOT);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy