blob: cf47f230eb9249a7159207f868b42b1ae02da852 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.brooklyn.core.mgmt.ha;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.annotation.Nullable;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.rebind.ChangeListener;
import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.api.typereg.ManagedBundle;
import org.apache.brooklyn.api.typereg.RegisteredType;
import org.apache.brooklyn.core.BrooklynVersion;
import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
import org.apache.brooklyn.core.catalog.internal.CatalogInitialization;
import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult.ResultCode;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.core.typereg.*;
import org.apache.brooklyn.core.typereg.BundleUpgradeParser.CatalogUpgrades;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.ResourceUtils;
import org.apache.brooklyn.util.core.osgi.BundleMaker;
import org.apache.brooklyn.util.core.osgi.Osgis;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.exceptions.ReferenceWithError;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.osgi.VersionedName;
import org.apache.brooklyn.util.stream.InputStreamSource.InputStreamSourceFromFile;
import org.apache.brooklyn.util.stream.Streams;
import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.text.VersionComparator;
import org.apache.commons.lang3.tuple.Pair;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Beta
public class BrooklynBomOsgiArchiveInstaller {
private static final Logger log = LoggerFactory.getLogger(BrooklynBomOsgiArchiveInstaller.class);
final private OsgiManager osgiManager;
private ManagedBundle suppliedKnownBundleMetadata;
private InputStream zipIn;
private boolean start = true;
private boolean loadCatalogBom = true;
private String format = null;
private String bomText = null;
private boolean force = false;
private boolean deferredStart = false;
private boolean validateTypes = true;
public static class FileWithTempInfo<T extends File> {
public FileWithTempInfo(T f, boolean isTemp) { this.f = f; this.isTemp = isTemp; }
private T f;
public boolean isTemp;
public void deleteIfTemp() {
if (isTemp && getFile() !=null) {
getFile().delete();
f = null;
}
}
public T getFile() {
return f;
}
}
private FileWithTempInfo<File> zipFile;
private boolean isBringingExistingOsgiInstalledBundleUnderBrooklynManagement = false;
private Manifest discoveredManifest;
private VersionedName discoveredBomVersionedName;
OsgiBundleInstallationResult result;
private ManagedBundle inferredMetadata;
private final boolean inputStreamSupplied;
public BrooklynBomOsgiArchiveInstaller(OsgiManager osgiManager, ManagedBundle knownBundleMetadata, InputStream zipIn) {
this.osgiManager = osgiManager;
this.suppliedKnownBundleMetadata = knownBundleMetadata;
this.zipIn = zipIn;
inputStreamSupplied = zipIn!=null;
}
public void setStart(boolean start) {
this.start = start;
}
public void setLoadCatalogBom(boolean loadCatalogBom) {
this.loadCatalogBom = loadCatalogBom;
}
/** If BOM is generated by a bundle resolver we need to know the generated BOM, and the format for subsequent bundle reload */
public void setCatalogBomText(String format, String bomText) {
this.format = format;
this.bomText = bomText;
}
public void setForce(boolean force) {
this.force = force;
}
public void setDeferredStart(boolean deferredStart) {
this.deferredStart = deferredStart;
}
public void setValidateTypes(boolean validateTypes) {
this.validateTypes = validateTypes;
}
private ManagementContextInternal mgmt() {
return (ManagementContextInternal) osgiManager.getManagementContext();
}
private synchronized void init() {
if (result!=null) {
if (zipFile!=null || zipIn==null) return;
throw new IllegalStateException("This installer instance has already been used and the input stream discarded");
}
result = new OsgiBundleInstallationResult();
inferredMetadata = suppliedKnownBundleMetadata==null ? new BasicManagedBundle() : suppliedKnownBundleMetadata;
}
private synchronized void makeLocalZipFileFromInputStreamOrUrl() {
PrepareInstallResult pr = prepareInstall(mgmt(), suppliedKnownBundleMetadata, inferredMetadata, () -> zipIn, force, result);
if (pr.isBringingExistingOsgiInstalledBundleUnderBrooklynManagement) {
isBringingExistingOsgiInstalledBundleUnderBrooklynManagement = true;
}
if (pr.existingBundle!=null) {
result.bundle = pr.existingBundle;
}
if (pr.zipFile!=null) {
zipFile = pr.zipFile;
}
}
public static class PrepareInstallResult {
/** set if there is a bundle matching the given metadata */
public Bundle existingBundle;
/** result object ready for returning to a user */
public OsgiBundleInstallationResult resultObject = null;
/** set if already installed to OSGi but not to Brooklyn */
public boolean isBringingExistingOsgiInstalledBundleUnderBrooklynManagement = false;
/** set if we anticipate a caller may want to read from a ZIP file;
* this is for the case where no input stream was supplied but
* OSGi bundle co-ordinates were supplied and can be used to find a ZIP */
public FileWithTempInfo<File> zipFile;
}
public static PrepareInstallResult prepareInstall(ManagementContext mgmt, ManagedBundle suppliedKnownBundleMetadata, ManagedBundle optionalAdditionalInferredMetadata,
Supplier<InputStream> zipInS, boolean force, @Nullable OsgiBundleInstallationResult resultObject) {
InputStream zipIn = zipInS == null ? null : zipInS.get();
PrepareInstallResult prepareInstallResult = new PrepareInstallResult();
prepareInstallResult.resultObject = resultObject;
try {
Maybe<Bundle> existingOsgiInstalledBundle = Maybe.absent();
Maybe<ManagedBundle> existingBrooklynInstalledBundle = Maybe.absent();
OsgiManager osgiManager = ((ManagementContextInternal) mgmt).getOsgiManager().get();
if (suppliedKnownBundleMetadata != null) {
// Before even trying to find or download the bundle, check if it is supposed to be forcibly replaced.
// If so, return the replacement (if any).
if (optionalAdditionalInferredMetadata==null) {
optionalAdditionalInferredMetadata = suppliedKnownBundleMetadata;
}
if (suppliedKnownBundleMetadata.isNameResolved()) {
Maybe<VersionedName> forcedReplacementBundle = CatalogUpgrades.tryGetBundleForcedReplaced(mgmt, suppliedKnownBundleMetadata.getVersionedName());
if (forcedReplacementBundle.isPresent()) {
if (prepareInstallResult.resultObject==null) {
prepareInstallResult.resultObject = new OsgiBundleInstallationResult();
}
setResultForciblyRemovedResult(osgiManager, suppliedKnownBundleMetadata.getVersionedName(), optionalAdditionalInferredMetadata,
forcedReplacementBundle, prepareInstallResult.resultObject);
return prepareInstallResult;
}
} else if (suppliedKnownBundleMetadata.getUrl() != null && suppliedKnownBundleMetadata.getUrl().toLowerCase().startsWith("mvn:")) {
// This inference is not guaranteed to get the right answer - you can put whatever
// you want in the MANIFEST.MF. Also, the maven-bundle-plugin does some surprising
// transforms, but we take a simpler approach here.
// If folk want it to work for such edge-cases, they should include the
// name:version explicitly in the `brooklyn.libraries list`.
Optional<VersionedName> inferredName = inferBundleNameFromMvnUrl(suppliedKnownBundleMetadata.getUrl());
if (inferredName.isPresent()) {
Maybe<VersionedName> forcedReplacementBundle = CatalogUpgrades.tryGetBundleForcedReplaced(mgmt, inferredName.get());
if (forcedReplacementBundle.isPresent()) {
if (prepareInstallResult.resultObject==null) {
prepareInstallResult.resultObject = new OsgiBundleInstallationResult();
}
setResultForciblyRemovedResult(osgiManager, inferredName.get(), optionalAdditionalInferredMetadata,
forcedReplacementBundle, prepareInstallResult.resultObject);
return prepareInstallResult;
}
}
}
if (zipIn == null) {
// if no input stream (zipIn), look for a URL and/or a matching bundle
if (!suppliedKnownBundleMetadata.isNameResolved()) {
existingBrooklynInstalledBundle = Maybe.ofDisallowingNull(osgiManager.getManagedBundleFromUrl(suppliedKnownBundleMetadata.getUrl()));
if (existingBrooklynInstalledBundle.isPresent()) {
// user supplied just a URL (eg brooklyn.libraries), but we recognise it,
// so don't try to reload it, just record the info we know about it to retrieve the bundle
((BasicManagedBundle) suppliedKnownBundleMetadata).setSymbolicName(existingBrooklynInstalledBundle.get().getSymbolicName());
((BasicManagedBundle) suppliedKnownBundleMetadata).setVersion(existingBrooklynInstalledBundle.get().getSuppliedVersionString());
}
}
if (existingOsgiInstalledBundle.isAbsent() && suppliedKnownBundleMetadata.getOsgiUniqueUrl() != null) {
existingOsgiInstalledBundle = Osgis.bundleFinder(osgiManager.getFramework()).requiringFromUrl(suppliedKnownBundleMetadata.getOsgiUniqueUrl()).find();
}
if (existingOsgiInstalledBundle.isAbsent() && suppliedKnownBundleMetadata.getUrl() != null) {
existingOsgiInstalledBundle = Osgis.bundleFinder(osgiManager.getFramework()).requiringFromUrl(suppliedKnownBundleMetadata.getUrl()).find();
}
if (existingOsgiInstalledBundle.isAbsent() && suppliedKnownBundleMetadata.isNameResolved()) {
existingOsgiInstalledBundle = Osgis.bundleFinder(osgiManager.getFramework()).symbolicName(suppliedKnownBundleMetadata.getSymbolicName()).version(suppliedKnownBundleMetadata.getSuppliedVersionString()).find();
}
if (existingOsgiInstalledBundle.isPresent()) {
if (existingBrooklynInstalledBundle.isAbsent()) {
// try to find as brooklyn bundle based on knowledge of OSGi bundle
existingBrooklynInstalledBundle = Maybe.ofDisallowingNull(osgiManager.getManagedBundle(new VersionedName(existingOsgiInstalledBundle.get())));
}
if (suppliedKnownBundleMetadata.getUrl() == null) {
// installer did not supply a usable URL, just coords
// but bundle is installed at least to OSGi
if (existingBrooklynInstalledBundle.isPresent()) {
log.debug("Detected bundle " + suppliedKnownBundleMetadata + " installed to Brooklyn already; no URL or stream supplied, so re-using existing installation");
// if bundle is brooklyn-managed simply say "already installed"
// prepareInstallResult.existingBundleMetadata = existingBrooklynInstalledBundle.get();
if (prepareInstallResult.resultObject==null) {
prepareInstallResult.resultObject = new OsgiBundleInstallationResult();
}
prepareInstallResult.resultObject.metadata = existingBrooklynInstalledBundle.get();
prepareInstallResult.resultObject.setIgnoringAlreadyInstalled();
} else {
// if bundle is not brooklyn-managed we want to make it be so
// and for that we need to find a URL.
// the getLocation() _might_ be usable, or might be totally opaque;
// in tests we rely on the block below (see system:file:) and things
// being explicitly set, but in live and rebind deployments the URL
// in practice with karaf how we package it is of the form mvn:...
// which _does_ work in this block, so we will be able to do most
// things which rely on taking osgi-installed bundles into brooklyn mgmt
// (and if not don't think it's a big deal, we just uninstall and reinstall
// sometimes or fail with a reasonable error message)
String candidateUrl = existingOsgiInstalledBundle.get().getLocation();
log.debug("Detected bundle " + suppliedKnownBundleMetadata + " installed to OSGi but not Brooklyn; trying to find a URL to get bundle binary, candidate " + candidateUrl);
if (Strings.isBlank(candidateUrl)) {
throw new IllegalArgumentException("No input stream available and no URL could be found: no way to promote " + suppliedKnownBundleMetadata + " from " + existingOsgiInstalledBundle.get() + " to Brooklyn management");
}
try {
// do this in special try block, not below, so we can give a better error
// (the user won't understand the URL)
zipIn = ResourceUtils.create(mgmt).getResourceFromUrl(candidateUrl);
prepareInstallResult.isBringingExistingOsgiInstalledBundleUnderBrooklynManagement = true;
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
throw new IllegalArgumentException("Could not find binary for already installed OSGi bundle " + existingOsgiInstalledBundle.get() + " (location " + candidateUrl + ") when trying to promote " + suppliedKnownBundleMetadata + " to Brooklyn management", e);
}
}
}
} else if (suppliedKnownBundleMetadata.getUrl() == null) {
// not installed anywhere and no URL
throw new IllegalArgumentException("No input stream available and no URL could be found: no way to install " + suppliedKnownBundleMetadata);
}
if (zipIn != null) {
// found input stream for existing osgi bundle
} else if (existingBrooklynInstalledBundle.isAbsent() || force) {
// reload
String url = suppliedKnownBundleMetadata.getUrl();
if (url == null) {
throw new IllegalStateException("should have found a stream or inferred a URL or already installed");
}
if (BrooklynVersion.isDevelopmentEnvironment() && url.startsWith("system:file:")) {
// in live dists the url is usually mvn: but in dev/test karaf will prefix it with system;
// leave the url alone so we correctly dedupe when considering whether to update, but create a zip file
// so that things work consistently in dev/test (in particular ClassLoaderUtilsTest passes).
// pretty sure we have to do this, even if not replacing the osgi bundle, because we need to
// get a handle on the zip file (although we could skip if not doing persistence - but that feels even worse than this!)
try {
url = Strings.removeFromStart(url, "system:");
File zipTemp = new BundleMaker(ResourceUtils.create(mgmt)).createJarFromClasspathDir(url);
zipIn = new FileInputStream(zipTemp);
} catch (FileNotFoundException e) {
throw Exceptions.propagate(e);
}
} else {
zipIn = ResourceUtils.create(mgmt).getResourceFromUrl(url, suppliedKnownBundleMetadata.getUrlCredential());
}
} else {
// already installed, not forced, just say already installed
// (even if snapshot as this is a reference by URL, not uploaded content)
// prepareInstallResult.existingBundleMetadata = existingBrooklynInstalledBundle.get();
if (prepareInstallResult.resultObject==null) {
prepareInstallResult.resultObject = new OsgiBundleInstallationResult();
}
prepareInstallResult.resultObject.metadata = existingBrooklynInstalledBundle.get();
prepareInstallResult.resultObject.setIgnoringAlreadyInstalled();
}
}
}
prepareInstallResult.existingBundle = existingOsgiInstalledBundle.orNull();
if (zipIn != null) {
if (zipInS instanceof InputStreamSourceFromFile) {
prepareInstallResult.zipFile = new FileWithTempInfo<File>( ((InputStreamSourceFromFile) zipInS).getFile(), false );
} else {
prepareInstallResult.zipFile = new FileWithTempInfo<File>( Os.newTempFile("brooklyn-bundle-transient-" + suppliedKnownBundleMetadata, "zip"), true );
try {
FileOutputStream fos = new FileOutputStream(prepareInstallResult.zipFile.getFile());
Streams.copyClose(zipIn, fos);
try (ZipFile zf = new ZipFile(prepareInstallResult.zipFile.getFile())) {
// validate it is a valid ZIP, otherwise errors are more obscure later.
// can happen esp if user supplies a file://path/to/folder/ as the URL.openStream returns a list of that folder (!)
// the error thrown by the below is useful enough, and caller will wrap with suppliedKnownBundleMetadata details
zf.entries();
}
} catch (Exception e) {
try {
prepareInstallResult.zipFile.deleteIfTemp();
} catch (Exception e2) {
Exceptions.propagateIfFatal(e2);
log.warn("Error deleting ZIP file but ignoring because handling error "+e+": "+e2);
}
throw Exceptions.propagate(e);
}
}
}
} finally {
Streams.closeQuietly(zipIn);
zipIn = null;
}
return prepareInstallResult;
}
private void discoverManifestFromCatalogBom(boolean isCatalogBomRequired) {
discoveredManifest = new BundleMaker(mgmt()).getManifest(zipFile.getFile());
if (Strings.isNonBlank(bomText)) {
discoveredBomVersionedName = BasicBrooklynCatalog.getVersionedName(BasicBrooklynCatalog.getCatalogMetadata(bomText), false );
return;
}
ZipFile zf = null;
try {
try {
zf = new ZipFile(zipFile.getFile());
} catch (IOException e) {
throw new IllegalArgumentException("Invalid ZIP/JAR archive: "+e);
}
ZipEntry bom = zf.getEntry(BasicBrooklynCatalog.CATALOG_BOM);
if (bom==null) {
bom = zf.getEntry("/"+BasicBrooklynCatalog.CATALOG_BOM);
}
if (bom==null) {
if (isCatalogBomRequired) {
throw new IllegalArgumentException("Archive must contain a catalog.bom file in the root");
} else {
return;
}
}
try {
bomText = Streams.readFullyString(zf.getInputStream(bom));
} catch (IOException e) {
throw new IllegalArgumentException("Error reading catalog.bom from ZIP/JAR archive: "+e);
}
discoveredBomVersionedName = BasicBrooklynCatalog.getVersionedName( BasicBrooklynCatalog.getCatalogMetadata(bomText), false );
} finally {
Streams.closeQuietly(zf);
}
}
private void updateManifestFromAllSourceInformation() {
if (discoveredBomVersionedName !=null) {
matchSetOrFail("catalog.bom in archive", discoveredBomVersionedName.getSymbolicName(), discoveredBomVersionedName.getVersionString());
}
boolean manifestNeedsUpdating = false;
if (discoveredManifest==null) {
discoveredManifest = new Manifest();
manifestNeedsUpdating = true;
}
if (!matchSetOrFail("MANIFEST.MF in archive", discoveredManifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME),
discoveredManifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION))) {
manifestNeedsUpdating = true;
discoveredManifest.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, inferredMetadata.getSymbolicName());
discoveredManifest.getMainAttributes().putValue(Constants.BUNDLE_VERSION, inferredMetadata.getOsgiVersionString());
}
if (Strings.isBlank(inferredMetadata.getSymbolicName())) {
throw new IllegalArgumentException("Missing bundle symbolic name in BOM or MANIFEST");
}
if (Strings.isBlank(inferredMetadata.getSuppliedVersionString())) {
throw new IllegalArgumentException("Missing bundle version in BOM or MANIFEST");
}
if (discoveredManifest.getMainAttributes().getValue(Attributes.Name.MANIFEST_VERSION)==null) {
discoveredManifest.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), BasicBrooklynCatalog.OSGI_MANIFEST_VERSION_VALUE);
manifestNeedsUpdating = true;
}
if (discoveredManifest.getMainAttributes().getValue(Constants.BUNDLE_MANIFESTVERSION)==null ||
(""+discoveredManifest.getMainAttributes().getValue(Constants.BUNDLE_MANIFESTVERSION)).startsWith("1")) {
// manifest version must be 2 otherwise cannot install to osgi
discoveredManifest.getMainAttributes().putValue(Constants.BUNDLE_MANIFESTVERSION, "2");
manifestNeedsUpdating = true;
}
if (manifestNeedsUpdating) {
File zf2 = new BundleMaker(mgmt()).copyAddingManifest(zipFile.getFile(), discoveredManifest);
zipFile.deleteIfTemp();
zipFile = new FileWithTempInfo<>(zf2, zipFile.isTemp);
}
}
private synchronized void close() {
if (zipFile!=null) {
zipFile.deleteIfTemp();
zipFile = null;
}
}
/**
* Installs a bundle, taking from ZIP input stream if supplied, falling back to URL in the {@link ManagedBundle} metadata supplied.
* It will take metadata from any of: a MANIFEST.MF in the ZIP; a catalog.bom in the ZIP; the {@link ManagedBundle} metadata supplied.
* If metadata is supplied in multiple such places, it must match.
* Appropriate metadata will be added to the ZIP and installation attempted.
* <p>
* If a matching bundle is already installed, the installation will stop with a {@link ResultCode#IGNORING_BUNDLE_AREADY_INSTALLED}
* unless the bundle is a snapshot or "force" is specified.
* In the latter two cases, if there is an installed matching bundle, that bundle will be updated with the input stream here,
* with any catalog items from the old bundle removed and those from this bundle installed.
* <p>
* Default behaviour is {@link #setLoadCatalogBom(boolean)} true and {@link #setForce(boolean)} false.
* <p>
* The return value is extensive but should be self-evident, and will include a list of any registered types (catalog items) installed.
*/
public ReferenceWithError<OsgiBundleInstallationResult> install() {
boolean startedInstallation = false;
try {
init();
makeLocalZipFileFromInputStreamOrUrl();
if (result.code!=null) return ReferenceWithError.newInstanceWithoutError(result);
discoverManifestFromCatalogBom(false);
if (result.code!=null) return ReferenceWithError.newInstanceWithoutError(result);
updateManifestFromAllSourceInformation();
if (result.code!=null) return ReferenceWithError.newInstanceWithoutError(result);
assert inferredMetadata.isNameResolved() : "Should have resolved "+inferredMetadata;
assert inferredMetadata instanceof BasicManagedBundle : "Only BasicManagedBundles supported";
((BasicManagedBundle)inferredMetadata).setChecksum(getChecksum(new ZipFile(zipFile.getFile())));
((BasicManagedBundle)inferredMetadata).setFormat(format);
final boolean updating;
result.metadata = osgiManager.getManagedBundle(inferredMetadata.getVersionedName());
if (result.getMetadata()!=null) {
// already have a managed bundle - check if this is using a new/different URL
if (suppliedKnownBundleMetadata!=null && suppliedKnownBundleMetadata.getUrl()!=null) {
String knownIdForThisUrl = osgiManager.managedBundlesRecord.getManagedBundleIdFromUrl(suppliedKnownBundleMetadata.getUrl());
if (knownIdForThisUrl==null) {
// it's a new URL, but a bundle we already know about
log.warn("Request to install from "+suppliedKnownBundleMetadata.getUrl()+" which is not recognized but "+
"appears to match "+result.getMetadata()+"; now associating with the latter");
osgiManager.managedBundlesRecord.setManagedBundleUrl(suppliedKnownBundleMetadata.getUrl(), result.getMetadata().getId());
} else if (!knownIdForThisUrl.equals(result.getMetadata().getId())) {
log.warn("Request to install from "+suppliedKnownBundleMetadata.getUrl()+" which is associated to "+knownIdForThisUrl+" but "+
"appears to match "+result.getMetadata()+"; now associating with the latter");
osgiManager.managedBundlesRecord.setManagedBundleUrl(suppliedKnownBundleMetadata.getUrl(), result.getMetadata().getId());
}
}
log.debug("Request to install "+inferredMetadata.getVersionedName()+" (checksum "+inferredMetadata.getChecksum()+", OSGi URL "+inferredMetadata.getOsgiUniqueUrl()+") in the presence of "+result.getMetadata().getVersionedName()+" (checksum "+result.getMetadata().getChecksum()+", OSGi URL "+result.getMetadata().getOsgiUniqueUrl()+")");
result.bundle = osgiManager.getFramework().getBundleContext().getBundle(result.getMetadata().getOsgiUniqueUrl());
// Check if exactly this bundle is already installed
if (result.bundle != null && checksumsMatch(result.getMetadata(), inferredMetadata)) {
// e.g. repeatedly installing the same bundle
log.trace("Bundle "+inferredMetadata+" matches already installed managed bundle "+result.getMetadata()
+"; install is no-op");
result.setIgnoringAlreadyInstalled();
return ReferenceWithError.newInstanceWithoutError(result);
}
List<Bundle> matchingVsnBundles = findBundlesBySymbolicNameAndVersion(osgiManager, inferredMetadata);
List<Bundle> sameContentBundles = matchingVsnBundles.stream().filter(b -> isBundleSameOsgiUrlOrSameContents(b, inferredMetadata, zipFile.getFile())).collect(Collectors.toList());
if (!sameContentBundles.isEmpty()) {
// e.g. happens if pre-installed bundle is brought under management, and then add it again via a mvn-style url.
// We wouldn't know the checksum from the pre-installed bundle, the osgi locations might be different,
// but the contents are the same
log.trace("Bundle "+inferredMetadata+" matches metadata of managed bundle "+result.getMetadata()
+" (but not OSGi bundle location "+result.getMetadata().getOsgiUniqueUrl()+"), "
+ "and identified as equivalent to installed OSGi bundle; ; install is no-op");
result.setIgnoringAlreadyInstalled();
result.bundle = sameContentBundles.iterator().next();
return ReferenceWithError.newInstanceWithoutError(result);
}
if (canUpdate()) {
if (result.bundle == null && !matchingVsnBundles.isEmpty()) {
// if we are updating a snapshot bundle or forcing, and somehow we did not manage to preserve the original OSGi location
log.info("Updating existing brooklyn-managed bundle "+result+" with "+inferredMetadata+" with different OSGi location and different contents");
result.bundle = matchingVsnBundles.iterator().next();
}
if (result.getBundle() == null) {
log.warn("Brooklyn thought it was already managing bundle "+result.getMetadata().getVersionedName()
+" but it's not installed to framework at location "+result.getMetadata().getOsgiUniqueUrl()+"; reinstalling it");
updating = false;
} else {
log.trace("Updating existing brooklyn-managed bundle "+result);
updating = true;
}
} else {
if (matchingVsnBundles.size() > 0 && (result.getMetadata().getChecksum()==null || inferredMetadata.getChecksum()==null)) {
// e.g. Checksum would be missing if we brought under management a pre-installed bundle with an unusable url.
log.info("Missing bundle checksum data for "+result+"; assuming bundle matches existing brooklyn-managed bundle (not re-installing)");
result.setIgnoringAlreadyInstalled();
return ReferenceWithError.newInstanceWithoutError(result);
} else if (result.bundle != null || matchingVsnBundles.size() > 0) {
throw new IllegalArgumentException("Bundle "+result.getMetadata().getVersionedName()+" already installed; "
+ "cannot install a different bundle with the same non-snapshot version");
} else {
throw new IllegalArgumentException("Bundle "+result.getMetadata().getVersionedName()+" already a brooklyn-managed bundle, but not found in OSGi framework; "
+ "will not re-install without use of 'force'");
}
}
// if proceeding with install, use the new metadata but the old id and osgi url
// (the osgi url must match because we use "getBundle(...)" to update it)
result.metadata = BasicManagedBundle.copyFirstWithCoordsOfSecond(inferredMetadata, result.metadata);
} else {
// No such Brooklyn-managed bundle.
// Check if likely-looking bundle already installed to OSGi subsystem, but brooklyn not aware of it.
// This will often happen on a karaf restart where bundle was cached by karaf, so we need to allow it;
// can also happen if brooklyn.libraries references an existing bundle.
//
// If we're not certain that the bundle is identical
// First check if this bundle is forcibly removed (and optionally upgraded).
// If so, don't install it - return the replacement, if any.
Maybe<VersionedName> forcedReplacementBundle = CatalogUpgrades.tryGetBundleForcedReplaced(mgmt(), inferredMetadata.getVersionedName());
if (forcedReplacementBundle.isPresent()) {
setResultForciblyRemovedResult(osgiManager, inferredMetadata.getVersionedName(), inferredMetadata, forcedReplacementBundle, result);
return ReferenceWithError.newInstanceWithoutError(result);
}
result.metadata = inferredMetadata;
// search for already-installed bundles.
List<Bundle> existingBundles = findBundlesBySymbolicNameAndVersion(osgiManager, inferredMetadata);
Maybe<Bundle> existingEquivalentBundle = tryFindSameOsgiUrlOrSameContentsBundle(existingBundles, inferredMetadata, zipFile.getFile());
if (existingEquivalentBundle.isPresent()) {
// Identical bundle (by osgi location or binary content) already installed; just bring that under management.
// This will often happen on a karaf restart: bundles from persisted state match those cached by karaf,
isBringingExistingOsgiInstalledBundleUnderBrooklynManagement = true;
result.bundle = existingEquivalentBundle.get();
} else if (existingBundles.size() > 0) {
Bundle existingBundle = existingBundles.get(0);
if (force) {
if (isBringingExistingOsgiInstalledBundleUnderBrooklynManagement) {
log.debug("Request to install "+inferredMetadata+" was forced, so forcing reinstallation "
+ "of existing OSGi installed (but not Brooklyn-managed) bundle "+existingBundle);
isBringingExistingOsgiInstalledBundleUnderBrooklynManagement = false;
}
}
if (isBringingExistingOsgiInstalledBundleUnderBrooklynManagement) {
// We were explicitly asked to bring an existing OSGi bundle under management;
// no equivalence check required
result.bundle = existingBundle;
} else {
// Uninstall and re-install the bundle.
// This is a good idea for brooklyn managed bundles that were in the karaf cache (when we can't
// determine that they are definitely identical).
// It's less good for pre-installed bundles, but if the user has said to deploy it or has
// referenced it in `brooklyn.libraries` then we'll go for it anyway! Let's hope they didn't
// reference `org.apache.brooklyn.core` or some such.
//
// We are this extreme because we want rebind to always work! If a user did a `force` install
// of a bundle, then we want to do the same on rebind (rather than risk failing).
//
// Instead of uninstall, we could update the bundle.
// Note however either way we won't be able to rollback if there is a failure
log.debug("Brooklyn install of "+result.getMetadata().getVersionedName()+" detected already loaded in OSGi; uninstalling that to reinstall as Brooklyn-managed");
existingBundle.uninstall();
result.bundle = null;
}
}
updating = false;
}
startedInstallation = true;
try (InputStream fin = new FileInputStream(zipFile.getFile())) {
if (!updating) {
if (isBringingExistingOsgiInstalledBundleUnderBrooklynManagement) {
assert result.getBundle()!=null;
log.debug("Brooklyn install of "+result.getMetadata().getVersionedName()+" detected already loaded "+result.getBundle()+" in OSGi can be re-used, skipping OSGi install");
} else {
assert result.getBundle()==null;
log.debug("Installing bundle "+result.getMetadata().getVersionedName()+", using OSGi location "+result.getMetadata().getOsgiUniqueUrl());
result.bundle = osgiManager.getFramework().getBundleContext().installBundle(result.getMetadata().getOsgiUniqueUrl(), fin);
}
} else {
result.bundle.update(fin);
}
}
osgiManager.checkCorrectlyInstalled(result.getMetadata(), result.bundle);
final File oldZipFile;
final ManagedBundle oldManagedBundle;
if (!updating) {
oldZipFile = null;
oldManagedBundle = null;
osgiManager.managedBundlesRecord.addManagedBundle(result, zipFile.getFile());
result.code = OsgiBundleInstallationResult.ResultCode.INSTALLED_NEW_BUNDLE;
result.message = "Installed Brooklyn catalog bundle "+result.getMetadata().getVersionedName()+" with ID "+result.getMetadata().getId()+" ["+result.bundle.getBundleId()+"]";
} else {
Pair<File, ManagedBundle> olds = osgiManager.managedBundlesRecord.updateManagedBundleFileAndMetadata(result, zipFile.getFile());
oldZipFile = olds.getLeft();
oldManagedBundle = olds.getRight();
result.code = OsgiBundleInstallationResult.ResultCode.UPDATED_EXISTING_BUNDLE;
result.message = "Updated Brooklyn catalog bundle "+result.getMetadata().getVersionedName()+" as existing ID "+result.getMetadata().getId()+" ["+result.bundle.getBundleId()+"]";
}
log.debug(result.message + " (partial): OSGi bundle installed, with bundle start and Brooklyn management to follow");
// can now delete and close (copy has been made and is available from OsgiManager)
zipFile.deleteIfTemp();
zipFile = null;
// setting the above before the code below means if there is a problem starting or loading catalog items
// a user has to remove then add again, or forcibly reinstall;
// that seems fine and probably better than allowing bundles to start and catalog items to be installed
// when brooklyn isn't aware it is supposed to be managing it
// starting here flags wiring issues earlier
// but may break some things running from the IDE
// eg if it doesn't have OSGi deps, or if it doesn't have camp parser,
// or if caller is installing multiple things that depend on each other
// eg rebind code, brooklyn.libraries list -- deferred start allows caller to
// determine whether not to start or to start all after things are installed
Runnable startRunnable = new Runnable() {
private void rollbackBundle() {
if (updating) {
if (oldZipFile==null) {
throw new IllegalStateException("Did not have old ZIP file to install");
}
log.debug("Rolling back bundle "+result.getVersionedName()+" to state "+oldManagedBundle+" from "+oldZipFile);
try {
File zipFileNow = osgiManager.managedBundlesRecord.rollbackManagedBundleFileAndMetadata(result, oldZipFile, oldManagedBundle);
result.bundle.update(new FileInputStream(Preconditions.checkNotNull(zipFileNow, "Couldn't find contents of old version of bundle")));
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
log.error("Error rolling back following failed install of updated "+result.getVersionedName()+"; "
+ "installation will likely be corrupted and correct version should be manually installed.", e);
}
if (!isExcludedFromPersistence(oldManagedBundle)) {
((BasicManagedBundle)oldManagedBundle).setPersistenceNeeded(true);
mgmt().getRebindManager().getChangeListener().onChanged(oldManagedBundle);
}
} else {
if (isBringingExistingOsgiInstalledBundleUnderBrooklynManagement) {
log.debug("Uninstalling bundle "+result.getVersionedName()+" from Brooklyn management only (rollback needed but it was already installed to OSGi)");
} else {
log.debug("Uninstalling bundle "+result.getVersionedName()+" (roll back of failed fresh install, no previous version to revert to)");
}
osgiManager.uninstallUploadedBundle(result.getMetadata(), false, isBringingExistingOsgiInstalledBundleUnderBrooklynManagement);
if (!isExcludedFromPersistence(result.getMetadata())) {
((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true);
mgmt().getRebindManager().getChangeListener().onUnmanaged(result.getMetadata());
}
}
}
public void run() {
if (start) {
try {
log.debug("Starting bundle "+result.getVersionedName());
if (!isExcludedFromPersistence(result.getMetadata()) && !Boolean.TRUE.equals(result.rebinding)) {
((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true);
if (updating) {
mgmt().getRebindManager().getChangeListener().onChanged(result.getMetadata());
} else {
mgmt().getRebindManager().getChangeListener().onManaged(result.getMetadata());
}
}
result.bundle.start();
} catch (BundleException e) {
log.warn("Error starting bundle "+result.getVersionedName()+", uninstalling, restoring any old bundle, then re-throwing error: "+e);
try {
rollbackBundle();
} catch (Throwable t) {
Exceptions.propagateIfFatal(t);
log.warn("Error rolling back "+result.getVersionedName()+" after bundle start problem; server may be in inconsistent state (swallowing this error and propagating installation error): "+Exceptions.collapseText(t), t);
throw Exceptions.propagate(new BundleException("Failure installing and rolling back; server may be in inconsistent state regarding bundle "+result.getVersionedName()+". "
+ "Rollback failure ("+Exceptions.collapseText(t)+") detailed in log. Installation error is: "+Exceptions.collapseText(e), e));
}
throw Exceptions.propagate(e);
}
}
if (loadCatalogBom) {
Iterable<RegisteredType> itemsFromOldBundle = null;
Map<RegisteredType, RegisteredType> itemsReplacedHere = null;
try {
if (updating) {
itemsFromOldBundle = osgiManager.uninstallCatalogItemsFromBundle( result.getVersionedName() );
// (ideally removal and addition would be atomic)
}
itemsReplacedHere = MutableMap.of();
osgiManager.loadBrooklynBundleWithCatalogBom(result.bundle, bomText, force, validateTypes, itemsReplacedHere);
Iterable<RegisteredType> items = mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(result.getMetadata()));
log.debug("Adding items from bundle "+result.getVersionedName()+": "+items);
for (RegisteredType ci: items) {
result.addType(ci);
}
} catch (Exception e) {
// unable to install new items; rollback bundles
// and reload replaced items
if (CatalogInitialization.isRebindReadOnlyShuttingDown(osgiManager.mgmt)) {
// very likely when RO mode interrupted - ignore
throw Exceptions.propagate(e);
}
log.warn("Error adding Brooklyn items from bundle "+result.getVersionedName()+", uninstalling, restoring any old bundle and items, then re-throwing error: "+Exceptions.collapseText(e));
try {
rollbackBundle();
} catch (Throwable t) {
Exceptions.propagateIfFatal(t);
log.warn("Error rolling back "+result.getVersionedName()+" after catalog install problem; server may be in inconsistent state (swallowing this error and propagating installation error): "+Exceptions.collapseText(t), t);
throw Exceptions.propagate(new BundleException("Failure loading catalog items, and also failed rolling back; server may be in inconsistent state regarding bundle "+result.getVersionedName()+". "
+ "Rollback failure ("+Exceptions.collapseText(t)+") detailed in log. Installation error is: "+Exceptions.collapseText(e), e));
}
if (itemsFromOldBundle!=null) {
// add back all itemsFromOldBundle (when replacing a bundle)
for (RegisteredType oldItem: itemsFromOldBundle) {
if (log.isTraceEnabled()) {
log.trace("RESTORING replaced bundle item "+oldItem+"\n"+ RegisteredTypes.getImplementationDataStringForSpec(oldItem));
}
((BasicBrooklynTypeRegistry)mgmt().getTypeRegistry()).addToLocalUnpersistedTypeRegistry(oldItem, true);
}
}
if (itemsReplacedHere!=null) {
// and restore any items from other bundles (eg wrappers) that were replaced
MutableList<RegisteredType> replaced = MutableList.copyOf(itemsReplacedHere.values());
// in reverse order so if other bundle adds multiple we end up with the real original
Collections.reverse(replaced);
for (RegisteredType oldItem: replaced) {
if (oldItem!=null) {
if (log.isTraceEnabled()) {
log.trace("RESTORING replaced external item "+oldItem+"\n"+RegisteredTypes.getImplementationDataStringForSpec(oldItem));
}
((BasicBrooklynTypeRegistry)mgmt().getTypeRegistry()).addToLocalUnpersistedTypeRegistry(oldItem, true);
}
}
}
throw Exceptions.propagate(e);
}
}
}
};
if (deferredStart) {
result.deferredStart = startRunnable;
log.debug(result.message+" (Brooklyn load deferred)");
} else {
int startBundleCount = mgmt().getOsgiManager().map(o -> o.getManagedBundles().size()).or(0);
startRunnable.run();
int newBundleCount = mgmt().getOsgiManager().map(o -> o.getManagedBundles().size()).or(0) - startBundleCount;
if (!result.typesInstalled.isEmpty() && newBundleCount<=1) {
// show fewer info messages, only for 'interesting' and non-deferred installations
// (rebind is deferred, as are tests, but REST is not)
final int LIST_EXPLICITLY_MAX = 5;
final int LIST_EXPLICITLY_IF_MAX_EXCEEDED = 1;
final int listExplicitlyHereCount = result.typesInstalled.size() > LIST_EXPLICITLY_MAX ? LIST_EXPLICITLY_IF_MAX_EXCEEDED : LIST_EXPLICITLY_MAX;
Iterable<String> firstN = Iterables.transform(MutableList.copyOf(Iterables.limit(result.typesInstalled, listExplicitlyHereCount)),
input -> input.getVersionedName().toString());
log.info(result.message+" (completed), items: "+firstN+
(result.typesInstalled.size() > listExplicitlyHereCount ? " (and others, "+result.typesInstalled.size()+" total)" : "") );
if (log.isDebugEnabled() && result.typesInstalled.size()>listExplicitlyHereCount) {
log.debug(result.message+", all items: "+result.typesInstalled);
}
} else {
log.info(result.message+" (completed), "+result.typesInstalled.size()+" item"+(result.typesInstalled.size()!=1 ? "s" : "")+" "+
(newBundleCount > 1 ? " plus "+(newBundleCount-1)+" other bundle"+(newBundleCount>2 ? "s" :"")+" detected" : "")+
", "+Iterables.size(mgmt().getTypeRegistry().getAll())+" total in type registry");
}
}
return ReferenceWithError.newInstanceWithoutError(result);
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
result.code = startedInstallation ? OsgiBundleInstallationResult.ResultCode.ERROR_LAUNCHING_BUNDLE : OsgiBundleInstallationResult.ResultCode.ERROR_PREPARING_BUNDLE;
result.message = "Bundle "+inferredMetadata+" failed "+
(startedInstallation ? "installation" : "preparation") + ": " + Exceptions.collapseText(e);
return ReferenceWithError.newInstanceThrowingError(result, new IllegalStateException(result.message, e));
} finally {
close();
}
}
@VisibleForTesting
static Optional<VersionedName> inferBundleNameFromMvnUrl(String url) {
// Assumes format 'mvn:<groupId>/<artifactId>/<version>'
// e.g. "mvn:io.brooklyn.etcd/brooklyn-etcd/2.7.0"
assert url.startsWith("mvn:") : "url="+url;
String[] parts = url.substring(4).split("/");
if (parts.length != 3) return Optional.absent();
if (parts[0].trim().isEmpty() || parts[1].trim().isEmpty() || parts[2].trim().isEmpty()) return Optional.absent();
return Optional.of(new VersionedName(parts[0]+"."+parts[1], parts[2]));
}
private static void setResultForciblyRemovedResult(OsgiManager osgiManager, VersionedName desiredBundle, ManagedBundle inferredMetadata,
Maybe<VersionedName> replacementBundle, OsgiBundleInstallationResult result) {
if (replacementBundle.isPresentAndNonNull()) {
result.metadata = osgiManager.getManagedBundle(replacementBundle.get());
if (result.getMetadata() != null) {
result.bundle = osgiManager.getFramework().getBundleContext().getBundle(result.getMetadata().getOsgiUniqueUrl());
log.debug("Bundle "+inferredMetadata+" forcibly replaced by bundle "+result.getMetadata() +"; install is no-op");
result.setIgnoringForciblyRemoved(inferredMetadata.getVersionedName(), replacementBundle);
return;
} else {
// try installing
java.util.Optional<Bundle> replacer = Arrays.stream(osgiManager.getFramework().getBundleContext().getBundles()).filter(b -> replacementBundle.get().equals(b.getSymbolicName(), b.getVersion())).findAny();
if (replacer.isPresent()) {
log.info("Bundle "+inferredMetadata+" forcibly replaced by bundle "+replacementBundle.get()+" which was not yet installed; installing it now");
try {
replacer.get().start();
} catch (Exception e) {
log.warn("Failed to install "+replacer.get()+" which declares itself as a replacement for "+inferredMetadata+" (rethrowing): "+e);
Exceptions.propagateAnnotated("Failed to install "+replacer.get()+" which declares itself as a replacement for "+inferredMetadata, e);
}
result.metadata = osgiManager.getManagedBundle(replacementBundle.get());
if (result.getMetadata() != null) {
result.bundle = osgiManager.getFramework().getBundleContext().getBundle(result.getMetadata().getOsgiUniqueUrl());
log.debug("Bundle " + inferredMetadata + " forcibly replaced by bundle " + result.getMetadata() + " which has been installed instead");
result.setIgnoringForciblyRemoved(inferredMetadata.getVersionedName(), replacementBundle);
return;
} else {
log.warn("Failed to find "+replacer.get()+" which declares itself as a replacement for "+inferredMetadata+", after starting it (throwing); bundle status is: "+replacer.get().getState());
throw new IllegalArgumentException("Bundle "+inferredMetadata+" forcibly replaced by bundle "
+replacementBundle.get()+" started, but still replacement not found");
}
} else {
throw new IllegalArgumentException("Bundle "+inferredMetadata+" forcibly replaced by bundle "
+replacementBundle.get()+", but replacement not found");
}
}
} else {
log.debug("Bundle "+inferredMetadata+" forcibly removed, but no upgrade bundle supplied"
+ "; install is no-op");
result.setIgnoringForciblyRemoved(inferredMetadata.getVersionedName(), Maybe.absent());
return;
}
}
@VisibleForTesting
boolean isExcludedFromPersistence(ManagedBundle managedBundle) {
return osgiManager.isExcludedFromPersistence(managedBundle);
}
private static List<Bundle> findBundlesBySymbolicNameAndVersion(OsgiManager osgiManager, ManagedBundle desired) {
return Osgis.bundleFinder(osgiManager.getFramework())
.symbolicName(desired.getSymbolicName())
.version(desired.getOsgiVersionString())
.findAll();
}
private static boolean checksumsMatch(ManagedBundle actual, ManagedBundle desired) {
return actual.getChecksum() != null && Objects.equal(actual.getChecksum(), desired.getChecksum());
}
private static Maybe<Bundle> tryFindSameOsgiUrlOrSameContentsBundle(Iterable<? extends Bundle> bundles, ManagedBundle desired, File zipFile) {
for (Bundle bundle : bundles) {
if (isBundleSameOsgiUrlOrSameContents(bundle, desired, zipFile)) {
return Maybe.of(bundle);
}
}
return Maybe.absent();
}
private static boolean isBundleSameOsgiUrlOrSameContents(Bundle bundle, ManagedBundle desired, File zipFile) {
// Would be nice to also use `desired.getChecksum()`, but not clear if we can get
// MD5 checksum from an installed OSGi bundle.
if (Objects.equal(bundle.getLocation(), desired.getUrl())) {
// installation request was for identical location, so assume we are simply bringing under mgmt
log.debug("Request to install "+desired+" from same location "+bundle.getLocation()+
" as existing OSGi installed (but not Brooklyn-managed) bundle "+bundle+", so skipping reinstall");
return true;
} else {
// different locations, but see if we can compare input stream contents
// (prevents needless uninstall/reinstall of already installed bundles)
try {
if (Streams.compare(new FileInputStream(zipFile), new URL(bundle.getLocation()).openStream())) {
log.debug("Request to install "+desired+" has same contents"+
" as existing OSGi installed (but not Brooklyn-managed) bundle "+bundle+", so skipping reinstall");
return true;
} else {
log.debug("Request to install "+desired+" has different contents"+
" as existing OSGi installed (but not Brooklyn-managed) bundle "+bundle+", so will do reinstall (if no other equivalents found)");
return false;
}
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
// probably an invalid URL on installed bundle; that's allowed
log.debug("Request to install "+desired+" could not compare contents"+
" with existing OSGi installed (but not Brooklyn-managed) bundle "+bundle+", so will do reinstall if not other equivalents found (error "+e+" loading from "+bundle.getLocation()+")");
return false;
}
}
}
private static String getChecksum(ZipFile zf) {
// checksum should ignore time/date stamps on files - just look at entries and contents. also ignore order.
// (tests fail without time/date is one reason, but really if a person rebuilds a ZIP that is the same
// files we should treat it as identical)
try {
Map<String,String> entriesToChecksum = MutableMap.of();
for (ZipEntry ze: Collections.list(zf.entries())) {
entriesToChecksum.put(ze.getName(), Streams.getMd5Checksum(zf.getInputStream(ze)));
}
return Streams.getMd5Checksum(Streams.newInputStreamWithContents(new TreeMap<>(entriesToChecksum).toString()));
} catch (Exception e) {
throw Exceptions.propagate(e);
}
}
private boolean canUpdate() {
// only update if forced, or it's a snapshot for which a byte stream is supplied
// (IE don't update a snapshot verison every time its URL is referenced in a 'libraries' section)
return force || (VersionComparator.isSnapshot(inferredMetadata.getSuppliedVersionString()) && inputStreamSupplied);
}
/** true if the supplied name and version are complete; updates if the known data is incomplete;
* throws if there is a mismatch; false if the supplied data is incomplete */
private boolean matchSetOrFail(String source, String name, String version) {
boolean suppliedIsComplete = true;
if (Strings.isBlank(name)) {
suppliedIsComplete = false;
} else if (Strings.isBlank(inferredMetadata.getSymbolicName())) {
((BasicManagedBundle)inferredMetadata).setSymbolicName(name);
} else if (!Objects.equal(inferredMetadata.getSymbolicName(), name)){
// if bundle name is inferred from ID, it is allowed to be overridden by manifest, but with a warning, for legacy and non-bundle BOM compatibility.
// if bundle name is set using 'bundle' it must match (previous behaviour).
boolean inferredMetadataCanBeOverridden = false;
try {
Map<?, ?> catalogMetadata = BasicBrooklynCatalog.getCatalogMetadata(bomText);
if (!catalogMetadata.containsKey("bundle") && catalogMetadata.containsKey("id")) {
inferredMetadataCanBeOverridden = true;
log.warn("Installing bundle '" + inferredMetadata + "' from " + source + ", even though 'id' in its catalog BOM is different ('" + name + "', v '"+version+"'); strongly recommended that the BOM 'id' match the bundle symbolic name");
}
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
}
if (!inferredMetadataCanBeOverridden) {
throw new IllegalArgumentException("Symbolic name mismatch '" + name + "' from " + source + " (expected '" + inferredMetadata.getSymbolicName() + "')");
}
}
if (Strings.isBlank(version)) {
suppliedIsComplete = false;
} else if (Strings.isBlank(inferredMetadata.getSuppliedVersionString())) {
((BasicManagedBundle)inferredMetadata).setVersion(version);
} else if (!BrooklynVersionSyntax.equalAsOsgiVersions(inferredMetadata.getSuppliedVersionString(), version)) {
boolean inferredMetadataCanBeOverridden = false;
try {
if (BasicBrooklynCatalog.NO_VERSION.equals(version)) {
inferredMetadataCanBeOverridden = true;
} else {
Map<?, ?> catalogMetadata = BasicBrooklynCatalog.getCatalogMetadata(bomText);
if (!catalogMetadata.containsKey("bundle") && catalogMetadata.containsKey("id")) {
inferredMetadataCanBeOverridden = true;
log.warn("Installing bundle '" + inferredMetadata + "' from " + source + ", even though 'version' in its catalog BOM is different ('" + name + "', v '" + version + "'); strongly recommended that the BOM 'version' match the bundle version");
}
}
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
}
if (!inferredMetadataCanBeOverridden) {
throw new IllegalArgumentException("Bundle version mismatch '" + version + "' from " + source + " (expected '" + inferredMetadata.getSuppliedVersionString() + "')");
}
}
return suppliedIsComplete;
}
}