[KARAF-4739] Rebooting Karaf can cause some bundles to not resolve anymore

Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/d937f50c
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/d937f50c
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/d937f50c

Branch: refs/heads/master
Commit: d937f50c51fe06bd58b915fda17e276ba1a63c41
Parents: 8d9dd28
Author: Guillaume Nodet <[email protected]>
Authored: Mon Oct 3 15:37:51 2016 +0200
Committer: Guillaume Nodet <[email protected]>
Committed: Wed Oct 5 08:10:44 2016 +0200

----------------------------------------------------------------------
 assemblies/features/framework/pom.xml           |   4 +
 .../framework/src/main/feature/feature.xml      |   2 +
 .../standard/src/main/feature/feature.xml       |   3 +-
 .../features/internal/service/Deployer.java     |   2 +-
 .../internal/service/FeaturesServiceImpl.java   |  28 +--
 features/extension/pom.xml                      |  78 +++++++
 .../karaf/features/extension/Activator.java     | 215 +++++++++++++++++++
 features/pom.xml                                |   1 +
 .../apache/karaf/itests/KarafTestSupport.java   |   1 +
 .../main/java/org/apache/karaf/main/Main.java   |   4 +-
 pom.xml                                         |   5 +
 .../apache/karaf/profile/assembly/Builder.java  |   3 +-
 12 files changed, 318 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/d937f50c/assemblies/features/framework/pom.xml
----------------------------------------------------------------------
diff --git a/assemblies/features/framework/pom.xml 
b/assemblies/features/framework/pom.xml
index 30236c6..b4806a6 100644
--- a/assemblies/features/framework/pom.xml
+++ b/assemblies/features/framework/pom.xml
@@ -161,6 +161,10 @@
 
         <dependency>
             <groupId>org.apache.karaf.features</groupId>
+            <artifactId>org.apache.karaf.features.extension</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.features</groupId>
             <artifactId>org.apache.karaf.features.core</artifactId>
         </dependency>
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/d937f50c/assemblies/features/framework/src/main/feature/feature.xml
----------------------------------------------------------------------
diff --git a/assemblies/features/framework/src/main/feature/feature.xml 
b/assemblies/features/framework/src/main/feature/feature.xml
index 6deab64..de7947b 100644
--- a/assemblies/features/framework/src/main/feature/feature.xml
+++ b/assemblies/features/framework/src/main/feature/feature.xml
@@ -22,6 +22,8 @@
               including the correct start-level for the generation of the 
startup.propertie file -->
 
     <feature version="${project.version}" description="Karaf core feature" 
name="framework" hidden="true">
+        <!-- persistent wiring extension -->
+        <bundle start="true" 
start-level="1">mvn:org.apache.karaf.features/org.apache.karaf.features.extension/${project.version}</bundle>
         <!-- mvn: and wrap: url handlers -->
         <bundle start="true" 
start-level="5">mvn:org.ops4j.pax.url/pax-url-aether/${pax.url.version}</bundle>
         <!-- logging -->

http://git-wip-us.apache.org/repos/asf/karaf/blob/d937f50c/assemblies/features/standard/src/main/feature/feature.xml
----------------------------------------------------------------------
diff --git a/assemblies/features/standard/src/main/feature/feature.xml 
b/assemblies/features/standard/src/main/feature/feature.xml
index bd11a25..26588c9 100644
--- a/assemblies/features/standard/src/main/feature/feature.xml
+++ b/assemblies/features/standard/src/main/feature/feature.xml
@@ -70,7 +70,8 @@
     </feature>
 
     <feature name="feature" description="Features Support" 
version="${project.version}">
-        <bundle 
start-level="30">mvn:org.apache.karaf.features/org.apache.karaf.features.core/${project.version}</bundle>
+        <bundle 
start-level="1">mvn:org.apache.karaf.features/org.apache.karaf.features.extension/${project.version}</bundle>
+        <bundle 
start-level="15">mvn:org.apache.karaf.features/org.apache.karaf.features.core/${project.version}</bundle>
         <conditional>
             <condition>shell</condition>
             <config name="org.apache.karaf.command.acl.feature">

http://git-wip-us.apache.org/repos/asf/karaf/blob/d937f50c/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
index 9be3bc6..4877fe9 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
@@ -1003,7 +1003,7 @@ public class Deployer {
                         oldFragments.add(wire.getRequirer());
                     }
                 }
-                if (!oldFragments.equals(newFragments.get(bundle))) {
+                if (!oldFragments.containsAll(newFragments.get(bundle))) {
                     toRefresh.put(bundle, "Attached fragments changed: " + new 
ArrayList<>(newFragments.get(bundle)));
                     break;
                 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/d937f50c/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
index 81b1241..2a32c46 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
@@ -203,30 +203,10 @@ public class FeaturesServiceImpl implements 
FeaturesService, Deployer.DeployCall
                                long scheduleDelay,
                                int scheduleMaxRun,
                                String blacklisted) {
-        this.bundle = bundle;
-        this.bundleContext = bundleContext;
-        this.systemBundleContext = systemBundleContext;
-        this.storage = storage;
-        this.featureFinder = featureFinder;
-        this.eventAdminListener = eventAdminListener;
-        this.configurationAdmin = configurationAdmin;
-        this.resolver = resolver;
-        this.configInstaller = configurationAdmin != null ? new 
FeatureConfigInstaller(configurationAdmin, 
FeaturesService.DEFAULT_CONFIG_CFG_STORE) : null;
-        this.digraph = digraph;
-        this.overrides = overrides;
-        this.featureResolutionRange = featureResolutionRange;
-        this.bundleUpdateRange = bundleUpdateRange;
-        this.updateSnaphots = updateSnaphots;
-        this.serviceRequirements = serviceRequirements;
-        this.globalRepository = globalRepository;
-        this.downloadThreads = downloadThreads > 0 ? downloadThreads : 1;
-        this.scheduleDelay = scheduleDelay;
-        this.scheduleMaxRun = scheduleMaxRun;
-        this.blacklisted = blacklisted;
-        this.configCfgStore = FeaturesService.DEFAULT_CONFIG_CFG_STORE;
-        this.executor = Executors.newSingleThreadExecutor();
-        loadState();
-        checkResolve();
+        this(bundle, bundleContext,systemBundleContext, storage, 
featureFinder, eventAdminListener, configurationAdmin,
+                resolver, digraph, overrides, featureResolutionRange, 
bundleUpdateRange, updateSnaphots,
+                serviceRequirements, globalRepository, downloadThreads, 
scheduleDelay, scheduleMaxRun, blacklisted,
+                FeaturesService.DEFAULT_CONFIG_CFG_STORE);
     }
 
     public FeaturesServiceImpl(Bundle bundle,

http://git-wip-us.apache.org/repos/asf/karaf/blob/d937f50c/features/extension/pom.xml
----------------------------------------------------------------------
diff --git a/features/extension/pom.xml b/features/extension/pom.xml
new file mode 100644
index 0000000..5ed4629
--- /dev/null
+++ b/features/extension/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+
+    <!--
+
+        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.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf.features</groupId>
+        <artifactId>features</artifactId>
+        <version>4.1.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.karaf.features.extension</artifactId>
+    <packaging>bundle</packaging>
+    <name>Apache Karaf :: Features :: Extension</name>
+    <description>This bundle is an extension bundle for the 
framework.</description>
+
+    <properties>
+        
<appendedResourcesDirectory>${basedir}/../../etc/appended-resources</appendedResourcesDirectory>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Fragment-Host>system.bundle; 
extension:=framework</Fragment-Host>
+                        
<ExtensionBundle-Activator>org.apache.karaf.features.extension.Activator</ExtensionBundle-Activator>
+                        <Import-Package>!*</Import-Package>
+                        
<Private-Package>org.apache.karaf.features.extension</Private-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

http://git-wip-us.apache.org/repos/asf/karaf/blob/d937f50c/features/extension/src/main/java/org/apache/karaf/features/extension/Activator.java
----------------------------------------------------------------------
diff --git 
a/features/extension/src/main/java/org/apache/karaf/features/extension/Activator.java
 
b/features/extension/src/main/java/org/apache/karaf/features/extension/Activator.java
new file mode 100644
index 0000000..4c0385e
--- /dev/null
+++ 
b/features/extension/src/main/java/org/apache/karaf/features/extension/Activator.java
@@ -0,0 +1,215 @@
+/*
+ * 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.karaf.features.extension;
+
+import org.osgi.framework.*;
+import org.osgi.framework.hooks.resolver.ResolverHook;
+import org.osgi.framework.hooks.resolver.ResolverHookFactory;
+import org.osgi.framework.namespace.HostNamespace;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.wiring.*;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class Activator implements BundleActivator, ResolverHook, 
SynchronousBundleListener, ResolverHookFactory {
+
+    private static final String WIRING_PATH = "wiring";
+
+    private final Map<Long, Map<String, String>> wiring = new HashMap<>();
+    private BundleContext bundleContext;
+
+    @Override
+    public void start(BundleContext context) throws Exception {
+        this.bundleContext = context;
+        load();
+        context.addBundleListener(this);
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        context.removeBundleListener(this);
+    }
+
+    @Override
+    public void bundleChanged(BundleEvent event) {
+        if (event.getBundle().getBundleId() == 0 && event.getType() == 
BundleEvent.STARTED) {
+            ServiceRegistration<ResolverHookFactory> registration = 
bundleContext.registerService(ResolverHookFactory.class, this, null);
+            try {
+                List<Bundle> bundles = wiring.keySet().stream()
+                        .map(id -> bundleContext.getBundle(id))
+                        .collect(Collectors.toList());
+                bundleContext.getBundle().adapt(FrameworkWiring.class)
+                        .resolveBundles(bundles);
+            } finally {
+                registration.unregister();
+            }
+        } else if (event.getType() == BundleEvent.RESOLVED || event.getType() 
== BundleEvent.UNRESOLVED) {
+            synchronized (wiring) {
+                long id = event.getBundle().getBundleId();
+                if (event.getType() == BundleEvent.RESOLVED) {
+                    Map<String, String> bw = new HashMap<>();
+                    for (BundleWire wire : 
event.getBundle().adapt(BundleWiring.class).getRequiredWires(null)) {
+                        bw.put(getRequirementId(wire.getRequirement()), 
getCapabilityId(wire.getCapability()));
+                    }
+                    wiring.put(id, bw);
+                    saveWiring(id, bw);
+                } else {
+                    wiring.remove(id);
+                    saveWiring(id, null);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void filterResolvable(Collection<BundleRevision> candidates) {
+    }
+
+    @Override
+    public void filterSingletonCollisions(BundleCapability singleton, 
Collection<BundleCapability> collisionCandidates) {
+    }
+
+    @Override
+    public void filterMatches(BundleRequirement requirement, 
Collection<BundleCapability> candidates) {
+        long sourceId = requirement.getRevision().getBundle().getBundleId();
+        if (isFragment(requirement.getRevision())
+                && 
!requirement.getNamespace().equals(HostNamespace.HOST_NAMESPACE)) {
+            sourceId = wiring.get(sourceId).entrySet().stream()
+                    .filter(e -> 
e.getKey().startsWith(HostNamespace.HOST_NAMESPACE))
+                    .map(Map.Entry::getValue)
+                    .mapToLong(s -> {
+                        int idx = s.indexOf(';');
+                        if (idx > 0) {
+                            s = s.substring(0, idx);
+                        }
+                        return Long.parseLong(s.trim());
+                    })
+                    .findFirst()
+                    .orElse(-1);
+        }
+        Map<String, String> bw = wiring.get(sourceId);
+        String cap = bw.get(getRequirementId(requirement));
+        for (Iterator<BundleCapability> candIter = candidates.iterator(); 
candIter.hasNext();) {
+            BundleCapability cand = candIter.next();
+            if (cap != null && !cap.equals(getCapabilityId(cand))
+                    || cap == null && cand.getRevision() != 
requirement.getRevision()) {
+                candIter.remove();
+            }
+        }
+    }
+
+    @Override
+    public void end() {
+    }
+
+    @Override
+    public ResolverHook begin(Collection<BundleRevision> triggers) {
+        return this;
+    }
+
+    private void load() {
+        try {
+            Path dir = bundleContext.getDataFile(WIRING_PATH).toPath();
+            Files.createDirectories(dir);
+            Files.list(dir).forEach(p -> {
+                String name = p.getFileName().toString();
+                if (name.matches("[0-9]+")) {
+                    try (BufferedReader reader = Files.newBufferedReader(p)) {
+                        long id = Long.parseLong(name);
+                        Map<String, String> map = new HashMap<>();
+                        while (true) {
+                            String key = reader.readLine();
+                            String val = reader.readLine();
+                            if (key != null && val != null) {
+                                map.put(key, val);
+                            } else {
+                                break;
+                            }
+                        }
+                        wiring.put(id, map);
+                    } catch (IOException e) {
+                        throw new UncheckedIOException(e);
+                    }
+                }
+            });
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private void saveWiring(long id, Map<String, String> wiring) {
+        try {
+            Path dir = bundleContext.getDataFile(WIRING_PATH).toPath();
+            Files.createDirectories(dir);
+            Path file = dir.resolve(Long.toString(id));
+            if (wiring != null) {
+                Files.createDirectories(file.getParent());
+                try (BufferedWriter fw = Files.newBufferedWriter(file,
+                        StandardOpenOption.TRUNCATE_EXISTING,
+                        StandardOpenOption.WRITE,
+                        StandardOpenOption.CREATE)) {
+                    for (Map.Entry<String, String> wire : wiring.entrySet()) {
+                        fw.append(wire.getKey()).append('\n');
+                        fw.append(wire.getValue()).append('\n');
+                    }
+                }
+            } else {
+                Files.deleteIfExists(file);
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private String getRequirementId(Requirement requirement) {
+        String filter = 
requirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
+        if (filter != null) {
+            return requirement.getNamespace() + "; " + filter;
+        } else {
+            return requirement.getNamespace();
+        }
+    }
+
+    private String getCapabilityId(BundleCapability capability) {
+        StringBuilder sb = new StringBuilder(64);
+        sb.append(capability.getRevision().getBundle().getBundleId());
+        Object v = capability.getAttributes().get(Constants.VERSION_ATTRIBUTE);
+        if (v != null) {
+            sb.append("; version=").append(v.toString());
+        }
+        return sb.toString();
+    }
+
+    private static boolean isFragment(Resource resource) {
+        for (Capability cap : resource.getCapabilities(null)) {
+            if 
(IdentityNamespace.IDENTITY_NAMESPACE.equals(cap.getNamespace())) {
+                return IdentityNamespace.TYPE_FRAGMENT.equals(
+                        
cap.getAttributes().get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE));
+            }
+        }
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/d937f50c/features/pom.xml
----------------------------------------------------------------------
diff --git a/features/pom.xml b/features/pom.xml
index 1f2674b..0d86ad2 100644
--- a/features/pom.xml
+++ b/features/pom.xml
@@ -34,6 +34,7 @@
     <name>Apache Karaf :: Features</name>
 
     <modules>
+        <module>extension</module>
         <module>core</module>
         <module>command</module>
     </modules>

http://git-wip-us.apache.org/repos/asf/karaf/blob/d937f50c/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java 
b/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
index c61cdac..85eb928 100644
--- a/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
+++ b/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
@@ -197,6 +197,7 @@ public class KarafTestSupport {
             keepRuntimeFolder(),
             logLevel(LogLevel.INFO),
             replaceConfigurationFile("etc/org.ops4j.pax.logging.cfg", 
getConfigFile("/etc/org.ops4j.pax.logging.cfg")),
+            editConfigurationFilePut("etc/org.apache.karaf.features.cfg", 
"updateSnapshots", "none"),
             editConfigurationFilePut("etc/org.ops4j.pax.web.cfg", 
"org.osgi.service.http.port", httpPort),
             editConfigurationFilePut("etc/org.apache.karaf.management.cfg", 
"rmiRegistryPort", rmiRegistryPort),
             editConfigurationFilePut("etc/org.apache.karaf.management.cfg", 
"rmiServerPort", rmiServerPort),

http://git-wip-us.apache.org/repos/asf/karaf/blob/d937f50c/main/src/main/java/org/apache/karaf/main/Main.java
----------------------------------------------------------------------
diff --git a/main/src/main/java/org/apache/karaf/main/Main.java 
b/main/src/main/java/org/apache/karaf/main/Main.java
index 31cd758..581334d 100644
--- a/main/src/main/java/org/apache/karaf/main/Main.java
+++ b/main/src/main/java/org/apache/karaf/main/Main.java
@@ -658,7 +658,9 @@ public class Main {
                 }
                 FrameworkEvent event = framework.waitForStop(step);
                 if (event.getType() != FrameworkEvent.WAIT_TIMEDOUT) {
-                    activatorManager.stopKarafActivators();
+                    if (activatorManager != null) {
+                        activatorManager.stopKarafActivators();
+                    }
                     return true;
                 }
             }

http://git-wip-us.apache.org/repos/asf/karaf/blob/d937f50c/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 15a2739..e4f6b7c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -600,6 +600,11 @@
 
             <dependency>
                 <groupId>org.apache.karaf.features</groupId>
+                <artifactId>org.apache.karaf.features.extension</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.karaf.features</groupId>
                 <artifactId>org.apache.karaf.features.core</artifactId>
                 <version>${project.version}</version>
             </dependency>

http://git-wip-us.apache.org/repos/asf/karaf/blob/d937f50c/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
----------------------------------------------------------------------
diff --git 
a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java 
b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
index e3678d2..f629845 100644
--- a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
@@ -39,6 +39,7 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.UUID;
 import java.util.concurrent.Executors;
@@ -1044,7 +1045,7 @@ public class Builder {
         Properties startup = new Properties();
         startup.setHeader(Collections.singletonList("# Bundles to be started 
on startup, with startlevel"));
         Map<Integer, Set<String>> invertedStartupBundles = 
MapUtils.invert(bundles);
-        for (Map.Entry<Integer, Set<String>> entry : 
invertedStartupBundles.entrySet()) {
+        for (Map.Entry<Integer, Set<String>> entry : new 
TreeMap<>(invertedStartupBundles).entrySet()) {
             String startLevel = Integer.toString(entry.getKey());
             for (String location : new TreeSet<>(entry.getValue())) {
                 if (useReferenceUrls) {

Reply via email to