public ReferenceWithError install()

in core/src/main/java/org/apache/brooklyn/core/mgmt/ha/BrooklynBomOsgiArchiveInstaller.java [482:852]


    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 (!isBlacklistedForPersistence(result.getMetadata())) {
                            ((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true);
                            mgmt().getRebindManager().getChangeListener().onChanged(result.getMetadata());
                        }
                    } 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 (!isBlacklistedForPersistence(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 (!isBlacklistedForPersistence(result.getMetadata())) {
                                ((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 {
                startRunnable.run();
                if (!result.typesInstalled.isEmpty()) {
                    // show fewer info messages, only for 'interesting' and non-deferred installations
                    // (rebind is deferred, as are tests, but REST is not)
                    final int MAX_TO_LIST_EXPLICITLY = 5;
                    Iterable<String> firstN = Iterables.transform(MutableList.copyOf(Iterables.limit(result.typesInstalled, MAX_TO_LIST_EXPLICITLY)),
                            input -> input.getVersionedName().toString());
                    log.info(result.message+", items: "+firstN+
                        (result.typesInstalled.size() > MAX_TO_LIST_EXPLICITLY ? " (and others, "+result.typesInstalled.size()+" total)" : "") );
                    if (log.isDebugEnabled() && result.typesInstalled.size()>MAX_TO_LIST_EXPLICITLY) {
                        log.debug(result.message+", all items: "+result.typesInstalled);
                    }
                } else {
                    log.debug(result.message+" (complete): bundle started and now managed by Brooklyn, though no catalog items found (may have installed other bundles though)");
                }
            }

            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();
        }
    }