FELIX-1914: Add a command to enable/disable dynamic imports to troubleshoot classloading issues
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@887748 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/shell/dev/pom.xml b/karaf/shell/dev/pom.xml
index ce90495..82edb94 100644
--- a/karaf/shell/dev/pom.xml
+++ b/karaf/shell/dev/pom.xml
@@ -40,6 +40,11 @@
</dependency>
<dependency>
+ <groupId>org.ops4j.pax.url</groupId>
+ <artifactId>pax-url-wrap</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.osgi.core</artifactId>
<scope>provided</scope>
diff --git a/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/AbstractBundleCommand.java b/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/AbstractBundleCommand.java
new file mode 100644
index 0000000..bc4f7b8
--- /dev/null
+++ b/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/AbstractBundleCommand.java
@@ -0,0 +1,93 @@
+/*
+ * 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.felix.karaf.shell.dev;
+
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.karaf.shell.console.OsgiCommandSupport;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+/**
+ * Base class for a dev: command that takes a bundle id as an argument
+ *
+ * It also provides convient access to the PackageAdmin service
+ */
+public abstract class AbstractBundleCommand extends OsgiCommandSupport {
+
+ @Argument(index = 0, name = "id", description = "The bundle ID", required = true)
+ Long id;
+
+ private PackageAdmin admin;
+
+ @Override
+ protected Object doExecute() throws Exception {
+ // Get package admin service.
+ ServiceReference ref = getBundleContext().getServiceReference(PackageAdmin.class.getName());
+ if (ref == null) {
+ System.out.println("PackageAdmin service is unavailable.");
+ return null;
+ }
+
+ // using the getService call ensures that the reference will be released at the end
+ admin = getService(PackageAdmin.class, ref);
+
+ Bundle bundle = getBundleContext().getBundle(id);
+ if (bundle == null) {
+ System.err.println("Bundle ID" + id + " is invalid");
+ return null;
+ }
+
+ doExecute(bundle);
+
+ return null;
+ }
+
+ protected abstract void doExecute(Bundle bundle) throws Exception;
+
+ /*
+ * Get the list of bundles from which the given bundle imports packages
+ */
+ protected Map<String, Bundle> getWiredBundles(Bundle bundle) {
+ // the set of bundles from which the bundle imports packages
+ Map<String, Bundle> exporters = new HashMap<String, Bundle>();
+
+ for (ExportedPackage pkg : getPackageAdmin().getExportedPackages((Bundle) null)) {
+ Bundle[] bundles = pkg.getImportingBundles();
+ if (bundles != null) {
+ for (Bundle importingBundle : bundles) {
+ if (bundle.equals(importingBundle)
+ && !(pkg.getExportingBundle().getBundleId() == 0)
+ && !(pkg.getExportingBundle().equals(bundle))) {
+ exporters.put(pkg.getName(), pkg.getExportingBundle());
+ }
+ }
+ }
+ }
+ return exporters;
+ }
+
+ protected PackageAdmin getPackageAdmin() {
+ return admin;
+ }
+}
diff --git a/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/DynamicImport.java b/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/DynamicImport.java
new file mode 100644
index 0000000..228db5f
--- /dev/null
+++ b/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/DynamicImport.java
@@ -0,0 +1,125 @@
+/*
+ * 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.felix.karaf.shell.dev;
+
+import java.io.IOException;
+import static java.lang.String.format;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.gogo.commands.Command;
+import org.apache.felix.karaf.shell.console.OsgiCommandSupport;
+import org.ops4j.pax.url.wrap.Handler;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+/**
+ * Command for enabling/disabling debug logging on a bundle and calculating the difference in
+ * wired imports.
+ */
+@Command(scope = "dev", name = "dynamic-import",
+ description = "Enable/disable dynamic-import for a given bundle")
+public class DynamicImport extends AbstractBundleCommand {
+
+ private static final Log LOG = LogFactory.getLog(DynamicImport.class);
+
+ /**
+ * The header key where we store the active wires when we enable DynamicImport=*
+ */
+ protected static final String ORIGINAL_WIRES = "Original-Wires";
+
+ @Override
+ protected void doExecute(Bundle bundle) throws Exception {
+ if (bundle.getHeaders().get(ORIGINAL_WIRES) == null) {
+ enableDynamicImports(bundle);
+ } else {
+ disableDynamicImports(bundle);
+ }
+ }
+
+ /*
+ * Enable DynamicImport=* on the bundle
+ */
+ private void enableDynamicImports(Bundle bundle) throws IOException, BundleException {
+ System.out.printf("Enabling dynamic imports on bundle %s%n", bundle);
+
+ String location =
+ String.format("wrap:%s$" +
+ "Bundle-UpdateLocation=%s&" +
+ "DynamicImport-Package=*&" +
+ "%s=%s&" +
+ "overwrite=merge",
+ bundle.getLocation(),
+ bundle.getLocation(),
+ ORIGINAL_WIRES,
+ explode(getWiredBundles(bundle).keySet()));
+ LOG.debug(format("Updating %s with URL %s", bundle, location));
+
+ URL url = new URL(location);
+ bundle.update(url.openStream());
+ getPackageAdmin().refreshPackages(new Bundle[] {bundle});
+ }
+
+ /*
+ * Disable DynamicImport=* on the bundle
+ *
+ * At this time, we will also calculate the difference in package wiring for the bundle compared to
+ * when we enabled the DynamicImport
+ */
+ private void disableDynamicImports(Bundle bundle) throws BundleException {
+ System.out.printf("Disabling dynamic imports on bundle %s%n", bundle);
+
+ Set<String> current = getWiredBundles(bundle).keySet();
+ for (String original : bundle.getHeaders().get(ORIGINAL_WIRES).toString().split(",")) {
+ current.remove(original);
+ }
+
+ if (current.isEmpty()) {
+ System.out.println("(no additional packages have been wired since dynamic import was enabled)");
+ } else {
+ System.out.printf("%nAdditional packages wired since dynamic import was enabled:%n");
+ for (String pkg : current) {
+ System.out.printf("- %s%n", pkg);
+ }
+ }
+
+ bundle.update();
+ }
+
+ /*
+ * Explode a set of string values in to a ,-delimited string
+ */
+ private String explode(Set<String> set) {
+ StringBuffer result = new StringBuffer();
+ Iterator<String> it = set.iterator();
+ while (it.hasNext()) {
+ result.append(it.next());
+ if (it.hasNext()) {
+ result.append(",");
+ }
+ }
+ return result.toString();
+ }
+}
diff --git a/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/ShowBundleTree.java b/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/ShowBundleTree.java
index feee325..17d0382 100644
--- a/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/ShowBundleTree.java
+++ b/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/ShowBundleTree.java
@@ -17,6 +17,7 @@
package org.apache.felix.karaf.shell.dev;
import static java.lang.String.format;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -41,46 +42,22 @@
* Command for showing the full tree of bundles that have been used to resolve
* a given bundle.
*/
-@Command(scope = "dev", name = "bundle-tree",
+@Command(scope = "dev", name = "show-tree",
description = "Show the tree of bundles based on the wiring information")
-public class ShowBundleTree extends OsgiCommandSupport {
+public class ShowBundleTree extends AbstractBundleCommand {
private static final Logger LOGGER = LoggerFactory.getLogger(ShowBundleTree.class);
- @Argument(index = 0, name = "id", description = "The ID of the bundle to check", required = true)
- Long id;
-
- // the package admin service reference
- private PackageAdmin admin;
-
// a cache of all exported packages
private ExportedPackage[] allExportedPackages;
@Override
- protected Object doExecute() throws Exception {
- // Get package admin service.
- ServiceReference ref = getBundleContext().getServiceReference(PackageAdmin.class.getName());
- if (ref == null) {
- System.out.println("PackageAdmin service is unavailable.");
- return null;
- }
-
- // using the getService call ensures that the reference will be released at the end
- admin = getService(PackageAdmin.class, ref);
-
-
- Bundle bundle = getBundleContext().getBundle(id);
- if (bundle == null) {
- System.err.println("Bundle ID" + id + " is invalid");
- return null;
- }
-
+ protected void doExecute(Bundle bundle) throws Exception {
// let's do the real work here
printHeader(bundle);
Tree<Bundle> tree = createTree(bundle);
printTree(tree);
printDuplicatePackages(tree);
- return null;
}
/*
@@ -98,19 +75,26 @@
*/
private void printTree(Tree<Bundle> tree) {
System.out.printf("%n");
- tree.write(System.out);
+ tree.write(System.out, new Tree.Converter<Bundle>() {
+
+ public String toString(Node<Bundle> node) {
+ return String.format("%s [%s]",
+ node.getValue().getSymbolicName(),
+ node.getValue().getBundleId());
+ }
+ });
}
/*
* Check for bundles in the tree exporting the same package
- * (possible cause for 'Unresolved constraint...' on a uses-conflict
+ * as a possible cause for 'Unresolved constraint...' on a uses-conflict
*/
private void printDuplicatePackages(Tree<Bundle> tree) {
Set<Bundle> bundles = tree.flatten();
Map<String, Set<Bundle>> exports = new HashMap<String, Set<Bundle>>();
for (Bundle bundle : bundles) {
- ExportedPackage[] packages = admin.getExportedPackages(bundle);
+ ExportedPackage[] packages = getPackageAdmin().getExportedPackages(bundle);
if (packages != null) {
for (ExportedPackage p : packages) {
if (exports.get(p.getName()) == null) {
@@ -142,7 +126,7 @@
createNode(tree, trail);
} else {
for (Import i : Import.parse(String.valueOf(bundle.getHeaders().get("Import-Package")))) {
- for (ExportedPackage ep : admin.getExportedPackages(i.getPackage())) {
+ for (ExportedPackage ep : getPackageAdmin().getExportedPackages(i.getPackage())) {
if (ep.getVersion().compareTo(i.getVersion()) >= 0) {
if (!bundle.equals(ep.getExportingBundle())) {
Node child = tree.addChild(ep.getExportingBundle());
@@ -161,7 +145,7 @@
*/
private void createNode(Node<Bundle> node, Set<Bundle> trail) {
Bundle bundle = node.getValue();
- Set<Bundle> exporters = getWiredBundles(bundle);
+ Collection<Bundle> exporters = getWiredBundles(bundle).values();
for (Bundle exporter : exporters) {
if (trail.contains(exporter)) {
@@ -175,36 +159,4 @@
}
}
}
-
- /*
- * Get the list of bundles from which the given bundle imports packages
- */
- private Set<Bundle> getWiredBundles(Bundle bundle) {
- // the set of bundles from which the bundle imports packages
- Set<Bundle> exporters = new LinkedHashSet<Bundle>();
-
- for (ExportedPackage pkg : getAllExportedPackages()) {
- Bundle[] bundles = pkg.getImportingBundles();
- if (bundles != null) {
- for (Bundle importingBundle : bundles) {
- if (bundle.equals(importingBundle)
- && !(pkg.getExportingBundle().getBundleId() == 0)
- && !(pkg.getExportingBundle().equals(bundle))) {
- exporters.add(pkg.getExportingBundle());
- }
- }
- }
- }
- return exporters;
- }
-
- /*
- * Get the full list of package exports from PackageAdmin
- */
- private ExportedPackage[] getAllExportedPackages() {
- if (allExportedPackages == null) {
- allExportedPackages = admin.getExportedPackages((Bundle) null);
- }
- return allExportedPackages;
- }
}
diff --git a/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/util/Node.java b/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/util/Node.java
index 450828d..1d509fa 100644
--- a/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/util/Node.java
+++ b/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/util/Node.java
@@ -99,13 +99,13 @@
* array is <code>true</code>, there should be a | to connect to the next
* sibling.
*/
- protected void write(PrintWriter writer, boolean... indents) {
+ protected void write(PrintWriter writer, Tree.Converter<T> converter, boolean... indents) {
for (boolean indent : indents) {
writer.printf("%-3s", indent ? "|" : "");
}
- writer.printf("+- %s%n", value);
+ writer.printf("+- %s%n", converter.toString(this));
for (Node<T> child : getChildren()) {
- child.write(writer, concat(indents, hasNextSibling()));
+ child.write(writer, converter, concat(indents, hasNextSibling()));
}
}
diff --git a/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/util/Tree.java b/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/util/Tree.java
index ae61eed5..a625780 100644
--- a/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/util/Tree.java
+++ b/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/util/Tree.java
@@ -45,7 +45,8 @@
}
/**
- * Write the tree to a PrintStream
+ * Write the tree to a PrintStream, using the default toString() method to output the node values
+ *
* @param stream
*/
public void write(PrintStream stream) {
@@ -53,14 +54,50 @@
}
/**
- * Write the tree to a PrintWriter
+ * Write the tree to a PrintStream, using the provided converter to output the node values
+ *
+ * @param stream
+ * @param converter
+ */
+ public void write(PrintStream stream, Converter<T> converter) {
+ write(new PrintWriter(stream), converter);
+ }
+
+ /**
+ * Write the tree to a PrintWriter, using the default toString() method to output the node values
+ *
* @param writer
*/
public void write(PrintWriter writer) {
- writer.printf("%s%n", getValue());
+ write(writer, new Converter() {
+ public String toString(Node node) {
+ return node.getValue().toString();
+ }
+ });
+ }
+
+ /**
+ * Write the tree to a PrintWriter, using the provided converter to output the node values
+ *
+ * @param writer
+ * @param converter
+ */
+ public void write(PrintWriter writer, Converter<T> converter) {
+ writer.printf("%s%n", converter.toString(this));
for (Node<T> child : getChildren()) {
- child.write(writer);
+ child.write(writer, converter);
}
writer.flush();
}
+
+ /**
+ * Interface to convert node values to string
+ *
+ * @param <T> the object type for the node value
+ */
+ public static interface Converter<T> {
+
+ public String toString(Node<T> node);
+
+ }
}
diff --git a/karaf/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml b/karaf/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml
index 2dd7d28..d3fab7d 100644
--- a/karaf/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml
+++ b/karaf/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml
@@ -26,6 +26,9 @@
<command name="dev/framework">
<action class="org.apache.felix.karaf.shell.dev.FrameworkDebug" />
</command>
+ <command name="dev/dynamic-import">
+ <action class="org.apache.felix.karaf.shell.dev.DynamicImport" />
+ </command>
</command-bundle>
</blueprint>
diff --git a/karaf/shell/dev/src/test/java/org/apache/felix/karaf/shell/dev/util/TreeTest.java b/karaf/shell/dev/src/test/java/org/apache/felix/karaf/shell/dev/util/TreeTest.java
index 2132687..d05b519 100644
--- a/karaf/shell/dev/src/test/java/org/apache/felix/karaf/shell/dev/util/TreeTest.java
+++ b/karaf/shell/dev/src/test/java/org/apache/felix/karaf/shell/dev/util/TreeTest.java
@@ -46,6 +46,24 @@
}
@Test
+ public void writeTreeWithOneChildAndNodeConverter() throws IOException {
+ Tree<String> tree = new Tree<String>("root");
+ tree.addChild("child");
+
+ StringWriter writer = new StringWriter();
+ tree.write(new PrintWriter(writer), new Tree.Converter<String>() {
+ public String toString(Node<String> node) {
+ return "my " + node.getValue();
+ }
+ });
+
+ BufferedReader reader = new BufferedReader(new StringReader(writer.getBuffer().toString()));
+
+ assertEquals("my root" , reader.readLine());
+ assertEquals("+- my child" , reader.readLine());
+ }
+
+ @Test
public void writeTreeWithChildAndGrandChild() throws IOException {
Tree<String> tree = new Tree<String>("root");
Node<String> node = tree.addChild("child");