diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /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.java | 186 |
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(); + } +} |