[KARAF-4973] Refactoring of features extension
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
index f0be0e8..19e2769 100644
--- 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
@@ -16,35 +16,25 @@
*/
package org.apache.karaf.features.extension;
-import org.osgi.framework.*;
-import org.osgi.framework.hooks.resolver.ResolverHook;
+import java.util.Arrays;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.SynchronousBundleListener;
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 org.osgi.framework.wiring.FrameworkWiring;
-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 {
-
+public class Activator implements BundleActivator, SynchronousBundleListener {
private static final String WIRING_PATH = "wiring";
-
- private final Map<Long, Map<String, String>> wiring = new HashMap<>();
- private BundleContext bundleContext;
+ private StoredWiringResolver resolver;
+ private BundleContext context;
@Override
public void start(BundleContext context) throws Exception {
- this.bundleContext = context;
- load();
+ this.context = context;
+ resolver = new StoredWiringResolver(context.getDataFile(WIRING_PATH).toPath());
context.addBundleListener(this);
}
@@ -53,163 +43,23 @@
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 bundleChanged(BundleEvent event) {
+ if (event.getBundle().getBundleId() == 0 && event.getType() == BundleEvent.STARTED) {
+ resolveAll();
+ } else if (event.getType() == BundleEvent.RESOLVED) {
+ resolver.update(event.getBundle());
+ } else if (event.getType() == BundleEvent.UNRESOLVED) {
+ resolver.delete(event.getBundle());
+ }
+ }
- @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().getBundle().getBundleId() != sourceId) {
- 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;
- }
+ private void resolveAll() {
+ ServiceRegistration<ResolverHookFactory> registration = context.registerService(ResolverHookFactory.class, (triggers) -> resolver, null);
+ try {
+ context.getBundle().adapt(FrameworkWiring.class).resolveBundles(Arrays.asList(context.getBundles()));
+ } finally {
+ registration.unregister();
+ }
+ }
}
diff --git a/features/extension/src/main/java/org/apache/karaf/features/extension/BundleWires.java b/features/extension/src/main/java/org/apache/karaf/features/extension/BundleWires.java
new file mode 100644
index 0000000..48b289a
--- /dev/null
+++ b/features/extension/src/main/java/org/apache/karaf/features/extension/BundleWires.java
@@ -0,0 +1,140 @@
+/*
+ * 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 static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.nio.file.StandardOpenOption.WRITE;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.namespace.HostNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Requirement;
+
+class BundleWires {
+ long bundleId;
+ Map<String, String> wiring;
+
+ BundleWires(Bundle bundle) {
+ this.bundleId = bundle.getBundleId();
+ this.wiring = new HashMap<>();
+ Map<String, String> bw = new HashMap<>();
+ for (BundleWire wire : bundle.adapt(BundleWiring.class).getRequiredWires(null)) {
+ bw.put(getRequirementId(wire.getRequirement()), getCapabilityId(wire.getCapability()));
+ }
+ }
+
+ BundleWires(BufferedReader reader) throws IOException {
+ 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;
+ }
+ }
+ }
+
+ void save(Path path) {
+ try {
+ Files.createDirectories(path);
+ Path file = path.resolve(Long.toString(this.bundleId));
+ Files.createDirectories(file.getParent());
+ try (BufferedWriter fw = Files.newBufferedWriter(file, TRUNCATE_EXISTING, WRITE, CREATE)) {
+ for (Map.Entry<String, String> wire : wiring.entrySet()) {
+ fw.append(wire.getKey()).append('\n');
+ fw.append(wire.getValue()).append('\n');
+ }
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ void delete(Path path) {
+ try {
+ Files.createDirectories(path);
+ Path file = path.resolve(Long.toString(this.bundleId));
+ Files.deleteIfExists(file);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ long getFragmentHost() {
+ return wiring.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);
+ }
+
+ void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) {
+ String cap = wiring.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().getBundle().getBundleId() != this.bundleId) {
+ candIter.remove();
+ }
+ }
+ }
+
+ 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();
+ }
+}
diff --git a/features/extension/src/main/java/org/apache/karaf/features/extension/StoredWiringResolver.java b/features/extension/src/main/java/org/apache/karaf/features/extension/StoredWiringResolver.java
new file mode 100644
index 0000000..3ebd8b4
--- /dev/null
+++ b/features/extension/src/main/java/org/apache/karaf/features/extension/StoredWiringResolver.java
@@ -0,0 +1,112 @@
+/*
+ * 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 java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.hooks.resolver.ResolverHook;
+import org.osgi.framework.namespace.HostNamespace;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+
+class StoredWiringResolver implements ResolverHook {
+ private final Map<Long, BundleWires> wiring = new HashMap<>();
+ private Path path;
+
+ StoredWiringResolver(Path path) {
+ this.path = path;
+ load();
+ }
+
+ void load() {
+ try {
+ Files.createDirectories(path);
+ Files.list(path).forEach(p -> {
+ String name = p.getFileName().toString();
+ if (name.matches("[0-9]+")) {
+ try (BufferedReader reader = Files.newBufferedReader(p)) {
+ long id = Long.parseLong(name);
+ wiring.put(id, new BundleWires(reader));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ });
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @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 = getBundleId(requirement);
+ wiring.get(sourceId).filterMatches(requirement, candidates);
+ }
+
+ @Override
+ public void end() {
+ }
+
+ private long getBundleId(BundleRequirement requirement) {
+ long sourceId = requirement.getRevision().getBundle().getBundleId();
+ if (isFragment(requirement.getRevision())
+ && !requirement.getNamespace().equals(HostNamespace.HOST_NAMESPACE)) {
+ sourceId = wiring.get(sourceId).getFragmentHost();
+ }
+ return sourceId;
+ }
+
+ 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;
+ }
+
+ synchronized void update(Bundle bundle) {
+ BundleWires bw = new BundleWires(bundle);
+ bw.save(path);
+ wiring.put(bundle.getBundleId(), bw);
+ }
+
+ synchronized void delete(Bundle bundle) {
+ wiring.get(bundle.getBundleId()).delete(path);
+ }
+}