[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) {
