This is an automated email from the ASF dual-hosted git repository.

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git


The following commit(s) were added to refs/heads/master by this push:
     new 931a6adf38 fixing osgi loading issue where osgi lets resources come 
from other bundles than oneself
931a6adf38 is described below

commit 931a6adf38881ab7cb1d2e7065c7f09f3dcf0b48
Author: Alex Heneveld <[email protected]>
AuthorDate: Tue Dec 13 21:55:05 2022 +0000

    fixing osgi loading issue where osgi lets resources come from other bundles 
than oneself
    
    if package imports include its own packages and there are eg bundle v 1-A 
and 1-B
    
    always prefer local resources. (might not do that for classes, but the main 
issue was during bom processing.)
    
    also be lenient if such a problem does occur, but there are no types 
affected, warn rather than throw.
---
 .../catalog/internal/BasicBrooklynCatalog.java     | 14 ++--
 .../catalog/internal/CatalogInitialization.java    |  7 +-
 .../apache/brooklyn/core/mgmt/ha/OsgiManager.java  | 15 +++-
 .../BrooklynMementoPersisterToObjectStore.java     | 37 ++++++++--
 .../brooklyn/core/mgmt/rebind/RebindIteration.java | 11 +++
 .../jackson/BrooklynJacksonSerializationUtils.java |  3 +-
 .../brooklyn/core/typereg/BundleUpgradeParser.java | 80 +++++++++++++---------
 .../core/typereg/BundleUpgradeParserTest.java      | 46 +++++++++++++
 .../brooklyn/util/core/ClassLoaderUtilsTest.java   |  9 ++-
 .../brooklyn/rest/resources/ServerResource.java    |  8 ++-
 10 files changed, 175 insertions(+), 55 deletions(-)

diff --git 
a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
 
b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
index f28f819b64..4be8e65385 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
@@ -681,7 +681,6 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
 
         // brooklyn.libraries we treat specially, to append the list, with the 
child's list preferred in classloading order
         // `libraries` is supported in some places as a legacy syntax; it 
should always be `brooklyn.libraries` for new apps
-        // TODO in 0.8.0 require brooklyn.libraries, don't allow "libraries" 
on its own
         List<?> librariesAddedHereNames = 
MutableList.copyOf(getFirstAs(itemMetadataWithoutItemDef, List.class, 
"brooklyn.libraries", "libraries").orNull());
         Collection<CatalogBundle> librariesAddedHereBundles = 
CatalogItemDtoAbstract.parseLibraries(librariesAddedHereNames);
         
@@ -745,6 +744,12 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
 
         String id = getFirstAs(catalogMetadata, String.class, "id").orNull();
         String version = getFirstAs(catalogMetadata, String.class, 
"version").orNull();
+        if (log.isTraceEnabled()) log.trace("Installing "+id+":"+version);
+        if (parentMetadata.containsKey("version") && 
!Objects.equal(parentMetadata.get("version"), version))
+            log.warn("Bundle "+containingBundle+" declares version "+version+" 
for items overriding broader version "+parentMetadata.get("version"));
+        else if (!parentMetadata.containsKey("version") && 
containingBundle!=null && version!=null && !Objects.equal(new 
VersionedName("x", version).getOsgiVersionString(), 
containingBundle.getVersionedName().getOsgiVersionString()))
+            log.warn("Bundle "+containingBundle+" declares items at different 
version "+version);
+
         String symbolicName = getFirstAs(catalogMetadata, String.class, 
"symbolicName").orNull();
         String displayName = getFirstAs(catalogMetadata, String.class, 
"displayName").orNull();
         String name = getFirstAs(catalogMetadata, String.class, 
"name").orNull();
@@ -1154,9 +1159,9 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
     }
 
     private void collectUrlReferencedCatalogItems(String url, ManagedBundle 
containingBundle, List<CatalogItemDtoAbstract<?, ?>> resultLegacyFormat, 
Map<RegisteredType, RegisteredType> resultNewFormat, boolean requireValidation, 
Map<Object, Object> parentMeta, int depth, boolean force, Boolean throwOnError) 
{
+        log.debug("Catalog load, loading referenced BOM at "+url+" as part of 
"+(containingBundle==null ? "non-bundled load" : 
containingBundle.getVersionedName())+" ("+(resultNewFormat!=null ? 
resultNewFormat.size() : resultLegacyFormat!=null ? resultLegacyFormat.size() : 
"(unknown)")+" items before load)");
         BrooklynClassLoadingContext loader = getClassLoadingContext("catalog 
url reference loader", parentMeta, null);
         String yaml;
-        log.debug("Catalog load, loading referenced BOM at "+url+" as part of 
"+(containingBundle==null ? "non-bundled load" : 
containingBundle.getVersionedName())+" ("+(resultNewFormat!=null ? 
resultNewFormat.size() : resultLegacyFormat!=null ? resultLegacyFormat.size() : 
"(unknown)")+" items before load)");
         if (url.startsWith("http")) {
             // give greater visibility to these
             log.info("Loading external referenced BOM at "+url+" as part of 
"+(containingBundle==null ? "non-bundled load" : 
containingBundle.getVersionedName()));
@@ -1168,6 +1173,7 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
             throw new IllegalStateException("Remote catalog url " + url + " in 
"+(containingBundle==null ? "non-bundled load" : 
containingBundle.getVersionedName())+" can't be fetched.", e);
         }
         try {
+            if (log.isTraceEnabled()) log.trace("Loaded yaml for 
"+containingBundle+" from "+url+":\n"+yaml);
             collectCatalogItemsFromCatalogBomRoot("BOM expected at "+url, 
yaml, containingBundle, resultLegacyFormat, resultNewFormat, requireValidation, 
parentMeta, depth, force, throwOnError);
         } catch (Exception e) {
             Exceptions.propagateAnnotated("Error loading "+url+" as part of 
"+(containingBundle==null ? "non-bundled load" : 
containingBundle.getVersionedName()), e);
@@ -1393,7 +1399,7 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
                 }
 
                 // couldn't resolve it with the plan transformers; retry with 
legacy "spec" transformers.
-                // TODO this legacy path is still needed where an entity is 
declared with nice abbreviated 'type: xxx' syntax, not the full-camp 'services: 
[ { type: xxx } ]' syntax.
+                // TODO this legacy path is still needed where an entity is 
declared with nice abbreviated 'type: ...' syntax, not the full-camp 'services: 
[ { type: ... } ]' syntax.
                 // would be nice to move that logic internally to CAMP and see 
if we can remove this altogether.
                 // (see 
org.apache.brooklyn.camp.brooklyn.spi.creation.CampResolver.createEntitySpecFromServicesBlock
 )
                 if (format == null) {
@@ -1567,7 +1573,7 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
             String candidateYamlWithKeyAdded = null;
             boolean legacyModeForOriginalBlueprint;
             if (optionalKeyForModifyingYaml!=null) {
-                /* often when added to a catalog we simply say "type: xxx" for 
the definition;
+                /* often when added to a catalog we simply say "type: ..." for 
the definition;
                  * the services: parent key at root (or brooklyn.policies, 
etc) needed by the camp parser
                  * are implicit, and added here */
                 if (item.containsKey(optionalKeyForModifyingYaml)) {
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
 
b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
index 14f95d2bab..99597d5737 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
@@ -292,8 +292,7 @@ public class CatalogInitialization implements 
ManagementContextInjectable {
         } else {
             final OsgiManager osgiManager = maybesOsgiManager.get();
             final BundleContext bundleContext = 
osgiManager.getFramework().getBundleContext();
-            final CatalogUpgrades catalogUpgrades =
-                    catalogUpgradeScanner.scan(osgiManager, bundleContext, 
rebindLogger);
+            final CatalogUpgrades catalogUpgrades = 
catalogUpgradeScanner.scan(osgiManager, bundleContext, rebindLogger);
             CatalogUpgrades.storeInManagementContext(catalogUpgrades, 
managementContext);
         }
 
@@ -563,8 +562,8 @@ public class CatalogInitialization implements 
ManagementContextInjectable {
             }
         });
         bundlesToRemove.forEach(b -> {
-            ManagedBundle mb = 
getManagementContext().getOsgiManager().get().getManagedBundle(b.getVersionedName());
-            if (b.getBundle().getState() >= Bundle.INSTALLED && 
b.getBundle().getState() < Bundle.STARTING) {
+            log.debug("Considering to uninstall: "+b+" / 
"+getManagementContext().getOsgiManager().get().getManagedBundle(b.getVersionedName()));
+            if (b.getBundle()!=null && b.getBundle().getState() >= 
Bundle.INSTALLED && b.getBundle().getState() < Bundle.STARTING) {
                 // we installed it, catalog did not start it, so let's 
uninstall it
                 OsgiBundleInstallationResult result = 
getManagementContext().getOsgiManager().get().uninstallUploadedBundle(b.getMetadata());
                 log.debug("Result of uninstalling "+b+" due to catalog upgrade 
metadata instructions: "+result);
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java
index edd4349396..7482dd88a3 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java
@@ -807,7 +807,12 @@ public class OsgiManager {
             try {
                 Maybe<Bundle> bundle = findBundle(osgiBundle);
                 if (bundle.isPresent()) {
-                    URL result = bundle.get().getResource(name);
+                    URL result;
+                    if (!name.endsWith(".class")) {
+                        result = bundle.get().getEntry(name);  // see comments 
at getResources
+                        if (result != null) return result;
+                    }
+                    result = bundle.get().getResource(name);
                     if (result!=null) return result;
                 }
             } catch (Exception e) {
@@ -821,13 +826,19 @@ public class OsgiManager {
      * @return URL's to all resources matching the given name (using {@link 
Bundle#getResources(String)} in the referenced osgi bundles.
      */
     public Iterable<URL> getResources(String name, Iterable<? extends 
OsgiBundleWithUrl> osgiBundles) {
-        Set<URL> resources = Sets.newLinkedHashSet();
+        MutableSet<URL> resources = MutableSet.of();
         for (OsgiBundleWithUrl catalogBundle : osgiBundles) {
             try {
                 Maybe<Bundle> bundle = findBundle(catalogBundle);
                 if (bundle.isPresent()) {
+                    boolean isClass = name.endsWith(".class");
+                    if (!isClass) 
resources.putIfNotNull(bundle.get().getEntry(name));  // files in the same 
bundle are not always preferred
+                    // (observed with a bundle at 1.0.0-2022 being installed 
when 1.0.0-SNAPSHOT is installed, the former includes its packages in 
import-package,
+                    // with the result that it returns the contents of the 
1.0.0-SNAPSHOT bundle instead of its own contents, which is not what we want);
+                    // see also in BundleUpgradeParser
                     Enumeration<URL> result = bundle.get().getResources(name);
                     resources.addAll(Collections.list(result));
+                    if (isClass) 
resources.putIfNotNull(bundle.get().getEntry(name));  //class resources, for 
consistency and to match loadClass, we prefer osgi behaviour
                 }
             } catch (Exception e) {
                 Exceptions.propagateIfFatal(e);
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterToObjectStore.java
 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterToObjectStore.java
index 7593b1082a..82fd7c5cda 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterToObjectStore.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterToObjectStore.java
@@ -33,6 +33,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.stream.Collectors;
 
 import javax.annotation.Nullable;
 import javax.xml.xpath.XPathConstants;
@@ -364,19 +365,34 @@ public class BrooklynMementoPersisterToObjectStore 
implements BrooklynMementoPer
 
                 } else {
                     String xmlId = (String) 
XmlUtil.xpathHandlingIllegalChars(contents, "/" + type.toCamelCase() + "/id");
+                    String xmlUrl = getXmlValue(contents, "/" + 
type.toCamelCase() + "/url").orNull();
+                    String xmlJavaType = getXmlValue(contents, "/" + 
type.toCamelCase() + "/type").orNull();
+                    String summary = MutableList.<String>of(contentsSubpath, 
type.toCamelCase()).appendIfNotNull(xmlId).appendIfNotNull(xmlUrl).appendIfNotNull(xmlJavaType).stream().collect(Collectors.joining("
 / "));
+
                     String safeXmlId = Strings.makeValidFilename(xmlId);
-                    if (!Objects.equal(id, safeXmlId))
-                        LOG.warn("ID mismatch on " + type.toCamelCase() + ", " 
+ id + " from path, " + safeXmlId + " from xml");
+                    if (!Objects.equal(id, safeXmlId)) {
+                        LOG.warn("ID mismatch on " + summary + ": xml id is " 
+ safeXmlId);
+                    }
 
+                    boolean include;
                     if (type == BrooklynObjectType.MANAGED_BUNDLE) {
-                        // TODO could R/W to cache space directly, rather than 
memory copy then extra file copy
+                        // could R/W to cache space directly, rather than 
memory copy then extra file copy
                         byte[] jarData = readBytes(contentsSubpath + ".jar");
-                        if (jarData == null) {
-                            throw new IllegalStateException("No bundle data 
for " + contentsSubpath);
+                        include = (jarData != null);
+                        if (!include) {
+                            LOG.warn("No JAR data for "+summary+"; assuming 
deprecated and not meant to be installed, so ignoring");
+                            //throw new IllegalStateException("No bundle data 
for " + contentsSubpath+".jar; contents:\n"+contents);
+                        } else {
+                            LOG.debug("Including bundle "+summary+", with jar 
data size "+jarData.length);
+                            builder.bundleJar(id, ByteSource.wrap(jarData));
                         }
-                        builder.bundleJar(id, ByteSource.wrap(jarData));
+                    } else {
+                        LOG.debug("Including item "+summary);
+                        include = true;
+                    }
+                    if (include) {
+                        builder.put(type, xmlId, contents);
                     }
-                    builder.put(type, xmlId, contents);
                 }
             }
         };
@@ -399,6 +415,13 @@ public class BrooklynMementoPersisterToObjectStore 
implements BrooklynMementoPer
         return result;
     }
 
+    private Maybe<String> getXmlValue(String xml, String path) {
+        try {
+            return Maybe.ofDisallowingNull((String) 
XmlUtil.xpathHandlingIllegalChars(xml, path));
+        } catch (Exception e) { Exceptions.propagateIfFatal(e); /* otherwise 
ignore if no url */ }
+        return Maybe.absent();
+    }
+
     private static class XPathHelper {
         private String contents;
         private String prefix;
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java
index 03d8018707..a7675af57d 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java
@@ -360,6 +360,14 @@ public abstract class RebindIteration {
                 }
             });
         }
+
+        @Override
+        public String toString() {
+            return "InstallableManagedBundleImpl{" +
+                    "memento=" + memento +
+                    ", managedBundle=" + managedBundle +
+                    '}';
+        }
     }
 
     protected void installBundlesAndRebuildCatalog() {
@@ -1137,6 +1145,9 @@ public abstract class RebindIteration {
                             logRebindingWarn("Needed rebind catalog to resolve 
search path entry " + searchItemId + " (now " + fixedSearchItemId + ") for " + 
bType.getSimpleName().toLowerCase() + " " + contextSuchAsId +
                                     ", persistence should remove this in 
future but future versions will not support this and definitions should be 
fixed");
                         } else {
+                            // could do some magic if there is a peer with the 
same version use it, might be handy for snapshots especially
+                            // (snapshots upgrading bundle version "*" drop 
the qualifier so don't replace peers;
+                            // but as soon as you have a larger datestamp, it 
will replace the previous ones)
                             logRebindingWarn("Could not find search path entry 
" + searchItemId + " for " + bType.getSimpleName().toLowerCase() + " " + 
contextSuchAsId + ", ignoring");
                         }
                     }
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BrooklynJacksonSerializationUtils.java
 
b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BrooklynJacksonSerializationUtils.java
index ad5f0efe97..240e914b64 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BrooklynJacksonSerializationUtils.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BrooklynJacksonSerializationUtils.java
@@ -252,7 +252,8 @@ public class BrooklynJacksonSerializationUtils {
                             Object type = rm.get("type");
                             if (type instanceof String && 
((String)type).startsWith("org.apache.brooklyn.camp.brooklyn.spi.dsl.")) {
                                 // should always have a brooklyn:literal on 
outermost type, so that even with json pass through (used for steps etc) dsl 
gets preserved across convertDeeply
-                                log.warn("Failed to deserialize "+result+" as 
DSL; will treat as map");
+                                // apart from nested DSL expressions; and unf 
there is no easy way to tell here, so we just log debug either way
+                                log.debug("Data "+result+" looks like DSL but 
has no literal entry; probably it is a nested DSL expression, or otherwise it 
may be left as a map after deserialization");
                             }
                         }
                     }
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java 
b/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
index 89a77258e3..ea685c0704 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
@@ -18,26 +18,19 @@
  */
 package org.apache.brooklyn.core.typereg;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Dictionary;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.stream.Collectors;
-
-import javax.annotation.Nonnull;
-
+import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Supplier;
+import com.google.common.collect.*;
 import org.apache.brooklyn.api.catalog.CatalogItem;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.core.mgmt.ha.OsgiManager;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.core.osgi.Osgis;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.osgi.VersionedName;
@@ -50,16 +43,11 @@ import org.osgi.framework.VersionRange;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.annotations.Beta;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.base.Objects;
-import com.google.common.base.Supplier;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Multimap;
+import javax.annotation.Nonnull;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * Internal class for parsing bundle manifests to extract their upgrade 
instructions.
@@ -551,16 +539,16 @@ public class BundleUpgradeParser {
         // TODO Add support for the other options described in the proposal:
         //   
https://docs.google.com/document/d/1Lm47Kx-cXPLe8BO34-qrL3ZMPosuUHJILYVQUswEH6Y/edit#
         //   section "Bundle Upgrade Metadata"
-        
+
         Dictionary<String, String> headers = bundle.getHeaders();
         String upgradesForBundlesHeader = 
headers.get(MANIFEST_HEADER_UPGRADE_FOR_BUNDLES);
-        Multimap<VersionedName,VersionRangedName> upgradesForBundles = 
parseUpgradeForBundlesHeader(upgradesForBundlesHeader, bundle);
+        Multimap<VersionedName, VersionRangedName> upgradesForBundles = 
parseUpgradeForBundlesHeader(upgradesForBundlesHeader, bundle);
         return CatalogUpgrades.builder()
                 
.removedLegacyItems(parseForceRemoveLegacyItemsHeader(headers.get(MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS),
 bundle, typeSupplier))
                 
.removedBundles(parseForceRemoveBundlesHeader(headers.get(MANIFEST_HEADER_FORCE_REMOVE_BUNDLES),
 bundle))
                 .upgradeBundles(upgradesForBundles)
-                
.upgradeTypes(parseUpgradeForTypesHeader(headers.get(MANIFEST_HEADER_UPGRADE_FOR_TYPES),
 bundle, typeSupplier, 
-                    upgradesForBundlesHeader==null ? null : 
upgradesForBundles))
+                
.upgradeTypes(parseUpgradeForTypesHeader(headers.get(MANIFEST_HEADER_UPGRADE_FOR_TYPES),
 bundle, typeSupplier,
+                        upgradesForBundlesHeader == null ? null : 
upgradesForBundles))
                 .build();
     }
 
@@ -582,6 +570,7 @@ public class BundleUpgradeParser {
             // default target is this bundle
             (i) -> { return new VersionedName(bundle); });
     }
+
     private static Multimap<VersionedName, VersionRangedName> 
parseUpgradeForTypesHeader(String input, Bundle bundle, Supplier<? extends 
Iterable<? extends RegisteredType>> typeSupplier, Multimap<VersionedName, 
VersionRangedName> upgradesForBundles) {
         List<String> sourceVersions = null;
         if (upgradesForBundles!=null) {
@@ -609,8 +598,35 @@ public class BundleUpgradeParser {
             (i) -> { 
                 VersionedName targetTypeAtBundleVersion = new 
VersionedName(i.getSymbolicName(), bundle.getVersion());
                 if 
(!typeSupplierNames.contains(VersionedName.toOsgiVersionedName(targetTypeAtBundleVersion)))
 {
-                    throw new IllegalStateException("Bundle manifest for 
"+bundle+" declares it upgrades "+i+" "
-                        + "but does not declare an explicit target and does 
not contain inferred target "+targetTypeAtBundleVersion);
+                    String error = "Bundle manifest for " + bundle + " 
declares it upgrades " + i + " "
+                            + "but does not declare an explicit target and 
does not contain inferred target " + targetTypeAtBundleVersion;
+
+                    ManagementContext mgmt = Osgis.getManagementContext();
+                    if (mgmt!=null) {
+                        List<RegisteredType> matches = 
MutableList.copyOf(mgmt.getTypeRegistry().getMatching(rt -> 
Objects.equal(i.getSymbolicName(), rt.getSymbolicName()) && 
i.getOsgiVersionRange().includes(rt.getVersionedName().getOsgiVersion())));
+                        if (matches.isEmpty()) {
+                            /* can happen if a bundle imports its own packages 
and references a path/to/bom which is available in the bundle it imports, eg v 
1.0.0-A and 1.0.0-B;
+                             * curiously osgi will sometimes read the resource 
from the imported bundle (see OsgiManager.getResources)
+                             */
+                            log.debug(error+"; however there are no such types 
to be upgrades so ignoring");
+                            return null;
+                        }
+                        CatalogUpgrades previousUpgrades = 
CatalogUpgrades.getFromManagementContext(mgmt);
+                        if (!previousUpgrades.getUpgradesForBundle(new 
VersionedName(bundle)).isEmpty()) {
+                            log.debug(error+"; however this bundle has 
applicable upgrade instructions so ignoring");
+                            return null;
+                        }
+                        matches = matches.stream().filter(rt -> 
previousUpgrades.getUpgradesForType(rt.getVersionedName()).isEmpty()).collect(Collectors.toList());
+                        if (matches.isEmpty()) {
+                            log.debug(error+"; however all such types have 
other upgrade instructions so ignoring");
+                            return null;
+                        }
+
+                        log.debug(error+"; types we cannot upgrade: 
"+matches+"; types available here: "+typeSupplierNames);
+                    } else {
+                        log.debug(error + "; details: mgmt context 
unavailable, osgi target '" + 
VersionedName.toOsgiVersionedName(targetTypeAtBundleVersion) + "', supplier 
names: " + typeSupplierNames);
+                    }
+                    throw new IllegalStateException(error);
                 }
                 return targetTypeAtBundleVersion; 
             });
@@ -766,7 +782,9 @@ public class BundleUpgradeParser {
                     } else {
                         throw new IllegalArgumentException("Wildcard entry key 
must be of the form \"*\" or \"*:range\"");
                     }
-                    result.put(target, source);
+                    if (target!=null) {
+                        result.put(target, source);
+                    }
                 }
             }
         }
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
index 1b437b397b..91c2226f7e 100644
--- 
a/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
+++ 
b/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
@@ -89,6 +89,52 @@ public class BundleUpgradeParserTest {
         assertFalse(from1.includes(Version.valueOf("0.0.0.SNAPSHOT")));
         assertFalse(from1.includes(Version.valueOf("0.1.0.SNAPSHOT")));
     }
+
+    @Test
+    public void testVersionRangesWithTimestampsAndSnapshots() throws Exception 
{
+        VersionRange from0lessThan1 = 
VersionRangedName.fromString("foo:[0,1.0.0.2022)", false).getOsgiVersionRange();
+        assertTrue(from0lessThan1.includes(Version.valueOf("0.1.0.SNAPSHOT")));
+        assertTrue(from0lessThan1.includes(Version.valueOf("1.0.0")));
+        assertTrue(from0lessThan1.includes(Version.valueOf("1.0.0.2021")));
+        
assertTrue(from0lessThan1.includes(Version.valueOf(BrooklynVersionSyntax.toValidOsgiVersion("1.0.0-2021"))));
+        assertFalse(from0lessThan1.includes(Version.valueOf("1.0.0.2023")));
+        
assertFalse(from0lessThan1.includes(Version.valueOf(BrooklynVersionSyntax.toValidOsgiVersion("1.0.0-2023"))));
+
+        assertFalse(from0lessThan1.includes(Version.valueOf("1.0.0.2022")));
+        
assertFalse(from0lessThan1.includes(Version.valueOf(BrooklynVersionSyntax.toValidOsgiVersion("1.0.0-2022"))));
+
+        
assertFalse(from0lessThan1.includes(Version.valueOf("1.0.0.SNAPSHOT")));
+        
assertFalse(from0lessThan1.includes(Version.valueOf(BrooklynVersionSyntax.toValidOsgiVersion("1.0.0-SNAPSHOT"))));
+
+        assertFalse(from0lessThan1.includes(Version.valueOf("1.0.1.2023")));
+        
assertFalse(from0lessThan1.includes(Version.valueOf("1.0.1.SNAPSHOT")));
+
+        // note however when given * for a snapshot we strip the snapshot 
qualifier, and so only match up to 1.0.0)
+        from0lessThan1 = 
VersionRangedName.fromString("foo:[0,1.0.0.SNAPSHOT)", 
false).getOsgiVersionRange();
+        assertTrue(from0lessThan1.includes(Version.valueOf("0.1.0.SNAPSHOT")));
+        assertTrue(from0lessThan1.includes(Version.valueOf("1.0.0")));
+        assertTrue(from0lessThan1.includes(Version.valueOf("1.0.0.2023")));
+        
assertFalse(from0lessThan1.includes(Version.valueOf("1.0.0.SNAPSHOT")));
+        assertFalse(from0lessThan1.includes(Version.valueOf("1.0.1.2023")));
+        
assertFalse(from0lessThan1.includes(Version.valueOf("1.0.1.SNAPSHOT")));
+
+        from0lessThan1 = VersionRangedName.fromString("foo:[0,1.0.0)", 
false).getOsgiVersionRange();
+        assertTrue(from0lessThan1.includes(Version.valueOf("0.1.0.SNAPSHOT")));
+
+        assertFalse(from0lessThan1.includes(Version.valueOf("1.0.0")));
+        assertFalse(from0lessThan1.includes(Version.valueOf("1.0.0.2023")));
+        
assertFalse(from0lessThan1.includes(Version.valueOf("1.0.0.SNAPSHOT")));
+        assertFalse(from0lessThan1.includes(Version.valueOf("1.0.1.2023")));
+        
assertFalse(from0lessThan1.includes(Version.valueOf("1.0.1.SNAPSHOT")));
+
+        VersionRange from1lessThan101 = 
VersionRangedName.fromString("foo:(1.0.0,1.0.1)", false).getOsgiVersionRange();
+        
assertFalse(from1lessThan101.includes(Version.valueOf("0.1.0.SNAPSHOT")));
+        assertFalse(from1lessThan101.includes(Version.valueOf("1.0.0")));
+        assertTrue(from1lessThan101.includes(Version.valueOf("1.0.0.2023")));
+        
assertTrue(from1lessThan101.includes(Version.valueOf("1.0.0.SNAPSHOT")));
+        assertFalse(from1lessThan101.includes(Version.valueOf("1.0.1")));
+        assertFalse(from1lessThan101.includes(Version.valueOf("1.0.1.2023")));
+    }
     
     @Test
     public void testParseSingleQuotedVal() throws Exception {
diff --git 
a/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java 
b/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java
index db90666722..b868c46764 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java
@@ -144,7 +144,7 @@ public class ClassLoaderUtilsTest {
 
         BundledName resource = new BundledName(classname).toResource();
         BundledName bn = new BundledName(resource.bundle, resource.version, 
"/" + resource.name);
-        Asserts.assertSize(cluEntity.getResources(bn.toString()), 1);
+        Asserts.assertSize(cluEntity.getResources(bn.toString()), 2);  // 
should no longer mask the other available option
     }
 
 
@@ -354,12 +354,15 @@ public class ClassLoaderUtilsTest {
         String bundledResource = resource.toString();
         URL resourceUrl = cl.getResource(resource.name);
         assertEquals(clu.getResource(bundledResource), resourceUrl);
-        assertEquals(clu.getResources(bundledResource), 
ImmutableList.of(resourceUrl), "Loading with "+clu);
+        Iterable<URL> allMatches = clu.getResources(bundledResource);
+        assertEquals(allMatches.iterator().next(), resourceUrl, "Loading with 
"+clu);
+        // may have others, eg if the bundle provides it via multiple sources
 
         BundledName rootResource = new BundledName(resource.bundle, 
resource.version, "/" + resource.name);
         String rootBundledResource = rootResource.toString();
         assertEquals(clu.getResource(rootBundledResource), resourceUrl);
-        assertEquals(clu.getResources(rootBundledResource), 
ImmutableList.of(resourceUrl));
+        allMatches = clu.getResources(rootBundledResource);
+        assertEquals(allMatches.iterator().next(), resourceUrl);
     }
 
     private void assertLoadFails(String bundledClassName, ClassLoaderUtils... 
clua) {
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java
index 7614e2c157..00c3cf029f 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java
@@ -590,6 +590,8 @@ public class ServerResource extends 
AbstractBrooklynRestResource implements Serv
         File tempZipFile = null;
         String unzippedPath = null;
         try {
+            log.debug("Importing persisted state via REST API, starting");
+
             // create a temporary archive where persistence data supplied is 
written to
             tempZipFile = File.createTempFile("persistence-import",null);
             Files.write(tempZipFile.toPath(), persistenceData, 
StandardOpenOption.TRUNCATE_EXISTING);
@@ -623,15 +625,15 @@ public class ServerResource extends 
AbstractBrooklynRestResource implements Serv
                 ManagedBundleMemento bundleMemento = 
mementoManifest.getBundle(bundleJar.getKey());
                 ManagedBundle b = 
RebindIteration.newManagedBundle(bundleMemento);
                 bundles.put(b.getVersionedName(), new 
RebindIteration.InstallableManagedBundleImpl(bundleMemento, b));
-                log.debug("Installing "+bundleMemento+" for "+b+" as part of 
persisted state import");
+                log.debug("Importing persisted state, preparing bundle "+b+" 
from "+bundleMemento);
             }
             CatalogInitialization.PersistedCatalogState persistedCatalogState 
= new CatalogInitialization.PersistedCatalogState(bundles, 
Collections.emptySet());
             CatalogInitialization.RebindLogger rebindLogger = new 
CatalogInitialization.RebindLogger() {
                 @Override public void debug(String message, Object... args) {
-                    log.debug(message, args);
+                    log.debug("Importing persisted state: "+message, args);
                 }
                 @Override public void info(String message, Object... args) {
-                    log.warn(message, args);
+                    log.warn("Importing persisted state: "+message, args);
                 }
             };
             
mgmtInternal().getCatalogInitialization().installPersistedBundles(persistedCatalogState,
 null, ((RebindManagerImpl)mgmt().getRebindManager()).newExceptionHandler(), 
rebindLogger);


Reply via email to