io.bdeploy.api.product.v1.ProductManifestBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of api Show documentation
Show all versions of api Show documentation
Public API including dependencies, ready to be used for integrations and plugins.
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