summaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java
Publish
Diffstat (limited to 'container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java')
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java186
1 files changed, 186 insertions, 0 deletions
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java b/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java
new file mode 100644
index 00000000000..682abcc53ac
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java
@@ -0,0 +1,186 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.core.config;
+
+import com.yahoo.collections.PredicateSplit;
+import com.yahoo.config.FileReference;
+import com.yahoo.container.Container;
+import com.yahoo.filedistribution.fileacquirer.FileAcquirer;
+import com.yahoo.osgi.Osgi;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.wiring.BundleRevision;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import static com.yahoo.collections.PredicateSplit.partition;
+import static com.yahoo.container.core.BundleLoaderProperties.DISK_BUNDLE_PREFIX;
+
+/**
+ * Manages the set of installed 3rd-party component bundles.
+ *
+ * @author tonytv
+ */
+public class BundleLoader {
+
+ private final List<Bundle> initialBundles;
+
+ private final Map<FileReference, List<Bundle>> reference2Bundles = new LinkedHashMap<>();
+
+ private final Logger log = Logger.getLogger(BundleLoader.class.getName());
+ private final Osgi osgi;
+
+ public BundleLoader(Osgi osgi) {
+ this.osgi = osgi;
+ initialBundles = Arrays.asList(osgi.getBundles());
+ }
+
+ private List<Bundle> obtainBundles(FileReference reference, FileAcquirer fileAcquirer)
+ throws InterruptedException {
+ File file = fileAcquirer.waitFor(reference, 15, TimeUnit.MINUTES);
+ return osgi.install(file.getAbsolutePath());
+ }
+
+ /**
+ * @return the number of bundles installed by this call.
+ */
+ private int install(List<FileReference> references) {
+ Set<FileReference> bundlesToInstall = new HashSet<>(references);
+ bundlesToInstall.removeAll(reference2Bundles.keySet());
+
+ PredicateSplit<FileReference> bundlesToInstall_isDisk = partition(bundlesToInstall, BundleLoader::isDiskBundle);
+ installBundlesFromDisk(bundlesToInstall_isDisk.trueValues);
+ installBundlesFromFileDistribution(bundlesToInstall_isDisk.falseValues);
+
+ startBundles();
+ return bundlesToInstall.size();
+ }
+
+ private static boolean isDiskBundle(FileReference fileReference) {
+ return fileReference.value().startsWith(DISK_BUNDLE_PREFIX);
+ }
+
+ private void installBundlesFromDisk(List<FileReference> bundlesToInstall) {
+ for (FileReference reference : bundlesToInstall) {
+ try {
+ installBundleFromDisk(reference);
+ }
+ catch(Exception e) {
+ throw new RuntimeException("Could not install bundle '" + reference + "'", e);
+ }
+ }
+ }
+
+ private void installBundlesFromFileDistribution(List<FileReference> bundlesToInstall) {
+ if (!bundlesToInstall.isEmpty()) {
+ FileAcquirer fileAcquirer = Container.get().getFileAcquirer();
+ boolean hasFileDistribution = (fileAcquirer != null);
+ if (hasFileDistribution) {
+ installWithFileDistribution(bundlesToInstall, fileAcquirer);
+ } else {
+ log.warning("Can't retrieve bundles since file distribution is disabled.");
+ }
+ }
+ }
+
+ private void installBundleFromDisk(FileReference reference) {
+ assert(reference.value().startsWith(DISK_BUNDLE_PREFIX));
+ String referenceFileName = reference.value().substring(DISK_BUNDLE_PREFIX.length());
+ log.info("Installing bundle from disk with reference '" + reference.value() + "'");
+
+ File file = new File(referenceFileName);
+ if (!file.exists()) {
+ throw new IllegalArgumentException("Reference '" + reference.value() + "' not found on disk.");
+ }
+
+ List<Bundle> bundles = osgi.install(file.getAbsolutePath());
+ reference2Bundles.put(reference, bundles);
+ }
+
+ private void installWithFileDistribution(List<FileReference> bundlesToInstall, FileAcquirer fileAcquirer) {
+ for (FileReference reference : bundlesToInstall) {
+ try {
+ log.info("Installing bundle with reference '" + reference.value() + "'");
+ List<Bundle> bundles = obtainBundles(reference, fileAcquirer);
+ reference2Bundles.put(reference, bundles);
+ }
+ catch(Exception e) {
+ throw new RuntimeException("Could not install bundle '" + reference + "'", e);
+ }
+ }
+ }
+
+ //all bundles must have been started first to ensure correct package resolution.
+ private void startBundles() {
+ for (List<Bundle> bundles : reference2Bundles.values()) {
+ for (Bundle bundle : bundles) {
+ try {
+ if (!isFragment(bundle))
+ bundle.start();
+ } catch(Exception e) {
+ throw new RuntimeException("Could not start bundle '" + bundle.getSymbolicName() + "'", e);
+ }
+ }
+ }
+ }
+
+ // The OSGi APIs are just getting worse...
+ private boolean isFragment(Bundle bundle) {
+ BundleRevision bundleRevision = bundle.adapt(BundleRevision.class);
+ if (bundleRevision == null)
+ throw new NullPointerException("Null bundle revision means that bundle has probably been uninstalled: " +
+ bundle.getSymbolicName() + ":" + bundle.getVersion());
+ return (bundleRevision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0;
+ }
+
+ /**
+ * Returns the number of uninstalled bundles
+ */
+ private int retainOnly(List<FileReference> newReferences) {
+ Set<Bundle> bundlesToRemove = new HashSet<>(Arrays.asList(osgi.getBundles()));
+
+ for (FileReference fileReferenceToKeep: newReferences) {
+ if (reference2Bundles.containsKey(fileReferenceToKeep))
+ bundlesToRemove.removeAll(reference2Bundles.get(fileReferenceToKeep));
+ }
+
+ bundlesToRemove.removeAll(initialBundles);
+ for (Bundle bundle : bundlesToRemove) {
+ log.info("Removing bundle '" + bundle.toString() + "'");
+ osgi.uninstall(bundle);
+ }
+
+ Set<FileReference> fileReferencesToRemove = new HashSet<>(reference2Bundles.keySet());
+ fileReferencesToRemove.removeAll(newReferences);
+
+ for (FileReference fileReferenceToRemove : fileReferencesToRemove) {
+ reference2Bundles.remove(fileReferenceToRemove);
+ }
+ return bundlesToRemove.size();
+ }
+
+ public synchronized int use(List<FileReference> bundles) {
+ int removedBundles = retainOnly(bundles);
+ int installedBundles = install(bundles);
+ startBundles();
+
+ log.info(removedBundles + " bundles were removed, and " + installedBundles + " bundles were installed.");
+ log.info(installedBundlesMessage());
+ return removedBundles + installedBundles;
+ }
+
+ private String installedBundlesMessage() {
+ StringBuilder sb = new StringBuilder("Installed bundles: {" );
+ for (Bundle b : osgi.getBundles())
+ sb.append("[" + b.getBundleId() + "]" + b.getSymbolicName() + ", ");
+ sb.setLength(sb.length() - 2);
+ sb.append("}");
+ return sb.toString();
+ }
+}