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

io.bdeploy.api.product.v1.ProductManifestBuilder Maven / Gradle / Ivy

Go to download

Public API including dependencies, ready to be used for integrations and plugins.

There is a newer version: 7.3.6
Show newest version
package io.bdeploy.api.product.v1;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.jar.Attributes;
import java.util.jar.JarInputStream;

import io.bdeploy.api.plugin.v1.Plugin;
import io.bdeploy.api.product.v1.impl.ScopedManifestKey;
import io.bdeploy.bhive.BHive;
import io.bdeploy.bhive.BHiveTransactions.Transaction;
import io.bdeploy.bhive.model.Manifest;
import io.bdeploy.bhive.model.ObjectId;
import io.bdeploy.bhive.model.Tree;
import io.bdeploy.bhive.model.Tree.EntryType;
import io.bdeploy.bhive.objects.view.TreeView;
import io.bdeploy.bhive.objects.view.scanner.TreeVisitor;
import io.bdeploy.bhive.op.ImportFileOperation;
import io.bdeploy.bhive.op.ImportObjectOperation;
import io.bdeploy.bhive.op.ImportOperation;
import io.bdeploy.bhive.op.ImportTreeOperation;
import io.bdeploy.bhive.op.InsertArtificialTreeOperation;
import io.bdeploy.bhive.op.InsertManifestOperation;
import io.bdeploy.bhive.op.InsertManifestRefOperation;
import io.bdeploy.bhive.op.ManifestExistsOperation;
import io.bdeploy.bhive.op.ObjectLoadOperation;
import io.bdeploy.bhive.op.ScanOperation;
import io.bdeploy.bhive.util.StorageHelper;
import io.bdeploy.common.util.OsHelper.OperatingSystem;
import io.bdeploy.common.util.PathHelper;
import io.bdeploy.common.util.RuntimeAssert;

/**
 * Builder to create new product manifests.
 */
public class ProductManifestBuilder {

    public static final String PRODUCT_LABEL = "X-Product";
    public static final String PRODUCT_DESC = "product.json";
    public static final String CONFIG_ENTRY = "config";
    public static final String PLUGINS_ENTRY = "plugins";
    public static final String TEMPLATES_ENTRY = "templates";
    public static final String APP_TEMPLATES_ENTRY = "appTemplates";
    public static final String PARAM_TEMPLATES_ENTRY = "paramTemplates";
    public static final String VARIABLE_TEMPLATES_ENTRY = "variableTemplates";

    private final Map applications = new TreeMap<>();
    private final Map labels = new TreeMap<>();
    private final ProductDescriptor desc;
    private Path configTemplates;
    private Path pluginFolder;
    private final List instanceTemplates = new ArrayList<>();
    private final List appTemplates = new ArrayList<>();
    private final List paramTemplates = new ArrayList<>();
    private final List varTemplates = new ArrayList<>();

    public ProductManifestBuilder(ProductDescriptor desc) {
        this.desc = desc;
    }

    public synchronized ProductManifestBuilder add(Manifest.Key application) {
        applications.put(application.directoryFriendlyName(), application);
        return this;
    }

    public synchronized ProductManifestBuilder setConfigTemplates(Path templates) {
        configTemplates = templates;
        return this;
    }

    public synchronized ProductManifestBuilder setPluginFolder(Path plugins) {
        pluginFolder = plugins;
        return this;
    }

    public synchronized ProductManifestBuilder addLabel(String key, String value) {
        labels.put(key, value);
        return this;
    }

    public synchronized ProductManifestBuilder addInstanceTemplate(Path tmplPath) {
        instanceTemplates.add(tmplPath);
        return this;
    }

    public synchronized ProductManifestBuilder addApplicationTemplate(Path tmplPath) {
        appTemplates.add(tmplPath);
        return this;
    }

    public synchronized ProductManifestBuilder addParameterTemplate(Path tmplPath) {
        paramTemplates.add(tmplPath);
        return this;
    }

    public synchronized ProductManifestBuilder addInstanceVariableTemplate(Path tmplPath) {
        varTemplates.add(tmplPath);
        return this;
    }

    public synchronized void insert(BHive hive, Manifest.Key manifest, String productName) {
        try (Transaction t = hive.getTransactions().begin()) {
            doInsertLocked(hive, manifest, productName);
        }
    }

    private void doInsertLocked(BHive hive, Manifest.Key manifest, String productName) {
        Tree.Builder tree = new Tree.Builder();

        // add application references
        applications.forEach((k, v) -> tree.add(new Tree.Key(k, Tree.EntryType.MANIFEST),
                hive.execute(new InsertManifestRefOperation().setManifest(v))));

        // add product descriptor
        ObjectId descId = hive.execute(new ImportObjectOperation().setData(StorageHelper.toRawBytes(desc)));
        tree.add(new Tree.Key(PRODUCT_DESC, Tree.EntryType.BLOB), descId);

        // create config file tree
        if (configTemplates != null) {
            ObjectId configId = hive.execute(new ImportTreeOperation().setSkipEmpty(true).setSourcePath(configTemplates));
            tree.add(new Tree.Key(CONFIG_ENTRY, Tree.EntryType.TREE), configId);
        }

        // import product-bound plugins
        if (pluginFolder != null) {
            ObjectId pluginId = hive.execute(new ImportTreeOperation().setSkipEmpty(true).setSourcePath(pluginFolder));
            tree.add(new Tree.Key(PLUGINS_ENTRY, Tree.EntryType.TREE), pluginId);

            TreeView tv = hive.execute(new ScanOperation().setTree(pluginId));
            tv.visit(new TreeVisitor.Builder().onBlob(b -> {
                if (b.getName().toLowerCase().endsWith(".jar")) {
                    try (JarInputStream jis = new JarInputStream(
                            hive.execute(new ObjectLoadOperation().setObject(b.getElementId())))) {
                        java.util.jar.Manifest pluginMf = jis.getManifest();
                        if (pluginMf == null) {
                            throw new IllegalStateException("The plugin is not a valid JAR file: " + b.getName());
                        }

                        Attributes mainAttributes = pluginMf.getMainAttributes();
                        String mainClass = mainAttributes.getValue(Plugin.PLUGIN_CLASS_HEADER);
                        String name = mainAttributes.getValue(Plugin.PLUGIN_NAME_HEADER);

                        if (mainClass == null || name == null) {
                            throw new IllegalStateException("The plugin must define the '" + Plugin.PLUGIN_CLASS_HEADER
                                    + "' and '" + Plugin.PLUGIN_NAME_HEADER + "' headers: " + b.getName());
                        }
                    } catch (IOException e) {
                        throw new IllegalStateException("The plugin cannot be read: " + b.getName(), e);
                    }
                }
            }).build());
        }

        // import instance templates
        Tree.Builder templTree = new Tree.Builder();
        for (Path p : instanceTemplates) {
            ObjectId id = hive.execute(new ImportFileOperation().setFile(p));
            templTree.add(new Tree.Key(id.toString() + ".yaml", EntryType.BLOB), id);
        }
        tree.add(new Tree.Key(TEMPLATES_ENTRY, EntryType.TREE),
                hive.execute(new InsertArtificialTreeOperation().setTree(templTree)));

        // import application templates
        Tree.Builder appTemplTree = new Tree.Builder();
        for (Path p : appTemplates) {
            ObjectId id = hive.execute(new ImportFileOperation().setFile(p));
            appTemplTree.add(new Tree.Key(id.toString() + ".yaml", EntryType.BLOB), id);
        }
        tree.add(new Tree.Key(APP_TEMPLATES_ENTRY, EntryType.TREE),
                hive.execute(new InsertArtificialTreeOperation().setTree(appTemplTree)));

        // import parameter templates
        Tree.Builder paramTemplTree = new Tree.Builder();
        for (Path p : paramTemplates) {
            ObjectId id = hive.execute(new ImportFileOperation().setFile(p));
            paramTemplTree.add(new Tree.Key(id.toString() + ".yaml", EntryType.BLOB), id);
        }
        tree.add(new Tree.Key(PARAM_TEMPLATES_ENTRY, EntryType.TREE),
                hive.execute(new InsertArtificialTreeOperation().setTree(paramTemplTree)));

        // import variable templates
        Tree.Builder varTemplTree = new Tree.Builder();
        for (Path p : varTemplates) {
            ObjectId id = hive.execute(new ImportFileOperation().setFile(p));
            varTemplTree.add(new Tree.Key(id.toString() + ".yaml", EntryType.BLOB), id);
        }
        tree.add(new Tree.Key(VARIABLE_TEMPLATES_ENTRY, EntryType.TREE),
                hive.execute(new InsertArtificialTreeOperation().setTree(varTemplTree)));

        Manifest.Builder m = new Manifest.Builder(manifest);
        labels.forEach(m::addLabel);
        m.addLabel(PRODUCT_LABEL, productName).setRoot(hive.execute(new InsertArtificialTreeOperation().setTree(tree)));
        hive.execute(new InsertManifestOperation().addManifest(m.build(hive)));
    }

    /**
     * @param descriptorPath a relative or absolute path to a directory or a product-info.yaml file.
     * @param hive the target hive to import to.
     * @param fetcher a {@link DependencyFetcher} capable of assuring that external dependencies are present.
     * @param parallel whether it is allowed to spawn threads, or the import must happen on the calling thread.
     */
    public static Manifest.Key importFromDescriptor(Path descriptorPath, BHive hive, DependencyFetcher fetcher,
            boolean parallel) {
        // 1. read product desc yaml.
        descriptorPath = getDescriptorPath(descriptorPath);
        descriptorPath = descriptorPath.toAbsolutePath();
        ProductDescriptor prod = readProductDescriptor(descriptorPath);

        // 2. read version descriptor.
        if (prod.versionFile == null || prod.versionFile.isEmpty()) {
            throw new IllegalStateException(
                    "product descriptor does not reference a product version descriptor, which is required.");
        }
        // path is relative to product descriptor, or absolute.
        Path vDesc = descriptorPath.getParent().resolve(prod.versionFile);
        ProductVersionDescriptor versions = readProductVersionDescriptor(descriptorPath, vDesc);

        // 3. validate product and version info.
        RuntimeAssert.assertNotNull(versions.version, "no version defined in " + vDesc);
        RuntimeAssert.assertNotNull(prod.name, "no name defined in " + descriptorPath);
        RuntimeAssert.assertNotNull(prod.product, "no name defined in " + descriptorPath);

        // make sure that all applications are there in the version descriptor.
        Map> toImport = new TreeMap<>();
        matchApplicationsWithDescriptor(prod, vDesc, versions, toImport);

        // 4. prepare product meta-data and builder to be filled.
        String baseName = prod.product + '/';
        Manifest.Key prodKey = new Manifest.Key(baseName + "product", versions.version);
        ProductManifestBuilder builder = new ProductManifestBuilder(prod);

        // 4a. check if product is already present
        if (Boolean.TRUE.equals(hive.execute(new ManifestExistsOperation().setManifest(prodKey)))) {
            throw new IllegalStateException("Product " + prodKey + " is already present.");
        }

        // 5. find and import all applications to import.
        Path impBasePath = descriptorPath.getParent();
        importApplications(hive, fetcher, versions, toImport, baseName, builder, impBasePath, parallel);

        // 6. additional labels
        versions.labels.forEach(builder::addLabel);

        // 7. configuration templates
        if (prod.configTemplates != null) {
            Path cfgDir = descriptorPath.getParent().resolve(prod.configTemplates);
            if (!Files.isDirectory(cfgDir)) {
                throw new IllegalStateException("Configuration template directory not found: " + cfgDir);
            }
            builder.setConfigTemplates(cfgDir);
        }

        // 8. product-bound plugins
        if (prod.pluginFolder != null) {
            Path pluginPath = descriptorPath.getParent().resolve(prod.pluginFolder);
            if (!Files.isDirectory(pluginPath)) {
                throw new IllegalStateException("Plugin directory not found: " + pluginPath);
            }
            builder.setPluginFolder(pluginPath);
        }

        // 9. instance templates
        if (prod.instanceTemplates != null && !prod.instanceTemplates.isEmpty()) {
            for (String tmpl : prod.instanceTemplates) {
                Path tmplPath = descriptorPath.getParent().resolve(tmpl);
                if (!Files.isRegularFile(tmplPath)) {
                    throw new IllegalStateException("Instance Template descriptor not found: " + tmplPath);
                }
                builder.addInstanceTemplate(tmplPath);
            }
        }

        // 10. application templates
        if (prod.applicationTemplates != null && !prod.applicationTemplates.isEmpty()) {
            for (String tmpl : prod.applicationTemplates) {
                Path tmplPath = descriptorPath.getParent().resolve(tmpl);
                if (!Files.isRegularFile(tmplPath)) {
                    throw new IllegalStateException("Application Template descriptor not found: " + tmplPath);
                }
                builder.addApplicationTemplate(tmplPath);
            }
        }

        // 11. parameter templates
        if (prod.parameterTemplates != null && !prod.parameterTemplates.isEmpty()) {
            for (String tmpl : prod.parameterTemplates) {
                Path tmplPath = descriptorPath.getParent().resolve(tmpl);
                if (!Files.isRegularFile(tmplPath)) {
                    throw new IllegalStateException("Parameter Template descriptor not found: " + tmplPath);
                }
                builder.addParameterTemplate(tmplPath);
            }
        }

        // 12. instance variable templates
        if (prod.instanceVariableTemplates != null && !prod.instanceVariableTemplates.isEmpty()) {
            for (String tmpl : prod.instanceVariableTemplates) {
                Path tmplPath = descriptorPath.getParent().resolve(tmpl);
                if (!Files.isRegularFile(tmplPath)) {
                    throw new IllegalStateException("Parameter Template descriptor not found: " + tmplPath);
                }
                builder.addInstanceVariableTemplate(tmplPath);
            }
        }

        // 13. generate product
        builder.insert(hive, prodKey, prod.product);

        return prodKey;
    }

    public static Path getDescriptorPath(Path descriptorPath) {
        if (Files.isDirectory(descriptorPath)) {
            descriptorPath = descriptorPath.resolve("product-info.yaml");
        }
        return descriptorPath;
    }

    private static void matchApplicationsWithDescriptor(ProductDescriptor prod, Path vDesc, ProductVersionDescriptor versions,
            Map> toImport) {
        for (String appName : prod.applications) {
            Map map = versions.appInfo.get(appName);
            if (map == null || map.isEmpty()) {
                throw new IllegalStateException("Cannot find build information for " + appName + " in " + vDesc);
            }

            // sanity check - the product manifest will be called 'product'
            RuntimeAssert.assertFalse("product".equals(appName), "application may not be named 'product'");

            toImport.put(appName, map);
        }
    }

    private static void importApplications(BHive hive, DependencyFetcher fetcher, ProductVersionDescriptor versions,
            Map> toImport, String baseName, ProductManifestBuilder builder, Path impBasePath,
            boolean parallel) {
        List> tasks = doGatherImportTasks(hive, fetcher, versions, toImport, baseName, builder,
                impBasePath);

        try {
            if (parallel) {
                for (Future f : ForkJoinPool.commonPool().invokeAll(tasks)) {
                    f.get();
                }
            } else {
                for (Callable c : tasks) {
                    c.call();
                }
            }
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
            throw new IllegalStateException("Failed to import", ie);
        } catch (Exception e) {
            throw new IllegalStateException("Failed to import", e);
        }
    }

    /**
     * Find all required tasks for the import
     */
    private static List> doGatherImportTasks(BHive hive, DependencyFetcher fetcher,
            ProductVersionDescriptor versions, Map> toImport, String baseName,
            ProductManifestBuilder builder, Path impBasePath) {
        List> tasks = new ArrayList<>();
        for (Map.Entry> entry : toImport.entrySet()) {
            for (Map.Entry relApp : entry.getValue().entrySet()) {
                Path appPath = impBasePath.resolve(relApp.getValue());
                if (Files.isDirectory(appPath)) {
                    appPath = appPath.resolve(ApplicationDescriptorApi.FILE_NAME);
                }

                if (!PathHelper.exists(appPath)) {
                    throw new IllegalStateException("Cannot find " + appPath + " while importing " + entry.getKey());
                }

                Path finalAppPath = appPath;

                tasks.add(() -> importDependenciesAndApplication(hive, fetcher, versions, baseName, builder, entry.getKey(),
                        relApp.getKey(), finalAppPath));
            }
        }
        return tasks;
    }

    private static ApplicationDescriptorApi importDependenciesAndApplication(BHive hive, DependencyFetcher fetcher,
            ProductVersionDescriptor versions, String baseName, ProductManifestBuilder builder, String appName,
            OperatingSystem os, Path appPath) {
        // read and resolve dependencies.
        ApplicationDescriptorApi appDesc;
        try (InputStream is = Files.newInputStream(appPath)) {
            appDesc = StorageHelper.fromYamlStream(is, ApplicationDescriptorApi.class);
        } catch (IOException e) {
            throw new IllegalStateException("Cannot read " + appPath);
        }

        RuntimeAssert.assertTrue(appDesc.supportedOperatingSystems.contains(os),
                "Application " + appName + " does not support operating system " + os);

        fetcher.fetch(hive, appDesc.runtimeDependencies, os).forEach(builder::add);

        try (Transaction t = hive.getTransactions().begin()) {
            builder.add(hive.execute(new ImportOperation().setSourcePath(appPath.getParent())
                    .setManifest(new ScopedManifestKey(baseName + appName, os, versions.version).getKey())));
        }

        return appDesc;
    }

    public static ProductVersionDescriptor readProductVersionDescriptor(Path impDesc, Path vDesc) {
        if (!PathHelper.exists(vDesc)) {
            throw new IllegalStateException("Cannot find version descriptor at " + vDesc);
        }
        ProductVersionDescriptor versions;
        try (InputStream is = Files.newInputStream(vDesc)) {
            versions = StorageHelper.fromYamlStream(is, ProductVersionDescriptor.class);
        } catch (IOException e) {
            throw new IllegalStateException("Cannot read " + impDesc, e);
        }
        return versions;
    }

    public static ProductDescriptor readProductDescriptor(Path impDesc) {
        if (!PathHelper.exists(impDesc)) {
            throw new IllegalArgumentException("Product descriptor does not exist: " + impDesc);
        }
        ProductDescriptor prod;
        try (InputStream is = Files.newInputStream(impDesc)) {
            prod = StorageHelper.fromYamlStream(is, ProductDescriptor.class);
        } catch (IOException e) {
            throw new IllegalStateException("Cannot read " + impDesc, e);
        }
        return prod;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy