You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2014/07/04 11:50:47 UTC
[05/45] git commit: osgi wip
osgi wip
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/79ebcc33
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/79ebcc33
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/79ebcc33
Branch: refs/heads/master
Commit: 79ebcc3382cdd3e8725967aa2c139696ce1f2294
Parents: 1809b36
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Jun 10 20:27:57 2014 -0700
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Tue Jul 1 13:37:36 2014 +0100
----------------------------------------------------------------------
core/pom.xml | 6 +
.../brooklyn/config/BrooklynServerConfig.java | 3 +
.../brooklyn/management/ha/OsgiManager.java | 48 +++++++
.../internal/LocalManagementContext.java | 17 +++
.../internal/ManagementContextInternal.java | 5 +
.../NonDeploymentManagementContext.java | 6 +
.../src/main/java/brooklyn/util/osgi/Osgis.java | 139 +++++++++++++++++++
.../management/osgi/OsgiStandaloneTest.java | 119 ++++++++++++++++
.../entity/LocalManagementContextForTests.java | 9 +-
.../osgi/brooklyn-osgi-test-a_0.1.0.jar | Bin 0 -> 2055 bytes
pom.xml | 1 +
11 files changed, 351 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/79ebcc33/core/pom.xml
----------------------------------------------------------------------
diff --git a/core/pom.xml b/core/pom.xml
index 1056ad1..f80aa24 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -85,6 +85,12 @@
</dependency>
<dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>${felix.framework.version}</version>
+ </dependency>
+
+ <dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>${reflections.version}</version>
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/79ebcc33/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynServerConfig.java b/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
index 6154ba6..f564176 100644
--- a/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
+++ b/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
@@ -131,4 +131,7 @@ public class BrooklynServerConfig {
"or empty for no URL (use default scanner)",
new File(Os.fromHome(".brooklyn/catalog.xml")).toURI().toString());
+ public static final ConfigKey<Boolean> USE_OSGI = ConfigKeys.newBooleanConfigKey("brooklyn.osgi.enabled",
+ "Whether OSGi is enabled, defaulting to true", true);
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/79ebcc33/core/src/main/java/brooklyn/management/ha/OsgiManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/OsgiManager.java b/core/src/main/java/brooklyn/management/ha/OsgiManager.java
new file mode 100644
index 0000000..07400ec
--- /dev/null
+++ b/core/src/main/java/brooklyn/management/ha/OsgiManager.java
@@ -0,0 +1,48 @@
+package brooklyn.management.ha;
+
+import java.io.File;
+
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+
+import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.ConfigKey;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.os.Os;
+import brooklyn.util.osgi.Osgis;
+
+public class OsgiManager {
+
+ public static final ConfigKey<Boolean> USE_OSGI = BrooklynServerConfig.USE_OSGI;
+
+ /* see Osgis for info on starting framework etc */
+
+ protected Framework framework;
+ protected File osgiTempDir;
+
+ public void start() {
+ try {
+ // TODO any extra startup args?
+ // TODO dir to come from brooklyn properties;
+ // note dir must be different for each if starting multiple instances
+ osgiTempDir = Os.newTempDir("brooklyn-osgi-cache");
+ framework = Osgis.newFrameworkStarted(osgiTempDir.getAbsolutePath(), false, MutableMap.of());
+
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
+ }
+ }
+
+ public void stop() {
+ try {
+ if (framework!=null)
+ framework.stop();
+ } catch (BundleException e) {
+ throw Exceptions.propagate(e);
+ }
+ osgiTempDir = Os.deleteRecursively(osgiTempDir).asNullOrThrowing();
+ framework = null;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/79ebcc33/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java b/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
index c84a200..0d3deb9 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
@@ -34,6 +34,7 @@ import brooklyn.management.ExecutionManager;
import brooklyn.management.ManagementContext;
import brooklyn.management.SubscriptionManager;
import brooklyn.management.Task;
+import brooklyn.management.ha.OsgiManager;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.task.BasicExecutionContext;
import brooklyn.util.task.BasicExecutionManager;
@@ -99,6 +100,7 @@ public class LocalManagementContext extends AbstractManagementContext {
private final LocalLocationManager locationManager;
private final LocalAccessManager accessManager;
private final LocalUsageManager usageManager;
+ private OsgiManager osgiManager;
public final Throwable constructionStackTrace = new Throwable("for construction stacktrace").fillInStackTrace();
@@ -160,6 +162,11 @@ public class LocalManagementContext extends AbstractManagementContext {
this.accessManager = new LocalAccessManager();
this.usageManager = new LocalUsageManager(this);
+ if (configMap.getConfig(OsgiManager.USE_OSGI)) {
+ this.osgiManager = new OsgiManager();
+ osgiManager.start();
+ }
+
INSTANCES.add(this);
log.debug("Created management context "+this);
}
@@ -246,6 +253,12 @@ public class LocalManagementContext extends AbstractManagementContext {
if (!isRunning()) throw new IllegalStateException("Management context no longer running");
return usageManager;
}
+
+ @Override
+ public synchronized OsgiManager getOsgiManager() {
+ if (!isRunning()) throw new IllegalStateException("Management context no longer running");
+ return osgiManager;
+ }
@Override
public synchronized AccessController getAccessController() {
@@ -277,6 +290,10 @@ public class LocalManagementContext extends AbstractManagementContext {
public void terminate() {
INSTANCES.remove(this);
super.terminate();
+ if (osgiManager!=null) {
+ osgiManager.stop();
+ osgiManager = null;
+ }
if (execution != null) execution.shutdownNow();
if (gc != null) gc.shutdownNow();
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/79ebcc33/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java b/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java
index 2565b40..fb50641 100644
--- a/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java
+++ b/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java
@@ -18,6 +18,7 @@ import brooklyn.internal.storage.BrooklynStorage;
import brooklyn.location.Location;
import brooklyn.management.ManagementContext;
import brooklyn.management.Task;
+import brooklyn.management.ha.OsgiManager;
import brooklyn.util.task.TaskTags;
public interface ManagementContextInternal extends ManagementContext {
@@ -57,6 +58,10 @@ public interface ManagementContextInternal extends ManagementContext {
AccessManager getAccessManager();
UsageManager getUsageManager();
+
+ /** returns OSGi manager, if available; may be null if OSGi not supported, e.g. in test contexts
+ * (but major contexts will support this) */
+ OsgiManager getOsgiManager();
InternalEntityFactory getEntityFactory();
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/79ebcc33/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index 85b7029..61826ef 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -44,6 +44,7 @@ import brooklyn.management.ha.HighAvailabilityMode;
import brooklyn.management.ha.ManagementNodeState;
import brooklyn.management.ha.ManagementPlaneSyncRecord;
import brooklyn.management.ha.ManagementPlaneSyncRecordPersister;
+import brooklyn.management.ha.OsgiManager;
import brooklyn.mementos.BrooklynMementoPersister;
import brooklyn.util.guava.Maybe;
import brooklyn.util.time.Duration;
@@ -175,6 +176,11 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
public UsageManager getUsageManager() {
return usageManager;
}
+
+ @Override
+ public OsgiManager getOsgiManager() {
+ return null;
+ }
@Override
public AccessController getAccessController() {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/79ebcc33/core/src/main/java/brooklyn/util/osgi/Osgis.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/osgi/Osgis.java b/core/src/main/java/brooklyn/util/osgi/Osgis.java
new file mode 100644
index 0000000..2cfc5a7
--- /dev/null
+++ b/core/src/main/java/brooklyn/util/osgi/Osgis.java
@@ -0,0 +1,139 @@
+package brooklyn.util.osgi;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.framework.FrameworkFactory;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.framework.launch.Framework;
+
+import brooklyn.util.ResourceUtils;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+/**
+ * utilities for working with osgi.
+ * osgi support is in early days (June 2014) so this class is beta, subject to change,
+ * particularly in how framework is started and bundles installed.
+ *
+ * @since 0.7.0 */
+@Beta
+public class Osgis {
+
+ public static List<Bundle> getBundlesByName(Framework framework, String symbolicName, Predicate<Version> versionMatcher) {
+ List<Bundle> result = MutableList.of();
+ for (Bundle b: framework.getBundleContext().getBundles()) {
+ if (symbolicName.equals(b.getSymbolicName())) {
+ if (versionMatcher.apply(b.getVersion())) {
+ result.add(b);
+ }
+ }
+ }
+ return result;
+ }
+
+ public static List<Bundle> getBundlesByName(Framework framework, String symbolicName) {
+ return getBundlesByName(framework, symbolicName, Predicates.<Version>alwaysTrue());
+ }
+
+ public static Maybe<Bundle> getBundle(Framework framework, String symbolicNameOptionallyWithVersion) {
+ String[] parts = symbolicNameOptionallyWithVersion.split(":");
+ List<Bundle> matches;
+ if (parts.length==2) {
+ return getBundle(framework, symbolicNameOptionallyWithVersion);
+ } else if (parts.length==1) {
+ matches = getBundlesByName(framework, symbolicNameOptionallyWithVersion);
+ if (matches.isEmpty()) return Maybe.absent("no bundles matching "+symbolicNameOptionallyWithVersion);
+ return Maybe.of(matches.iterator().next());
+ } else {
+ throw new IllegalArgumentException("Cannot parse symbolic-name:version string '"+symbolicNameOptionallyWithVersion+"'");
+ }
+ }
+
+ public static Maybe<Bundle> getBundle(Framework framework, String symbolicName, String version) {
+ return getBundle(framework, symbolicName, Version.parseVersion(version));
+ }
+
+ public static Maybe<Bundle> getBundle(Framework framework, String symbolicName, Version version) {
+ List<Bundle> matches = getBundlesByName(framework, symbolicName, Predicates.<Version>equalTo(version));
+ if (matches.isEmpty()) return Maybe.absent("no bundles matching "+symbolicName+":"+version);
+ return Maybe.of(matches.iterator().next());
+ }
+
+ // -------- creating
+
+ /*
+ * loading framework factory and starting framework based on:
+ * http://felix.apache.org/documentation/subprojects/apache-felix-framework/apache-felix-framework-launching-and-embedding.html :
+ */
+
+ public static FrameworkFactory newFrameworkFactory() {
+ URL url = Osgis.class.getClassLoader().getResource(
+ "META-INF/services/org.osgi.framework.launch.FrameworkFactory");
+ if (url != null) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
+ try {
+ for (String s = br.readLine(); s != null; s = br.readLine()) {
+ s = s.trim();
+ // load the first non-empty, non-commented line
+ if ((s.length() > 0) && (s.charAt(0) != '#')) {
+ return (FrameworkFactory) Class.forName(s).newInstance();
+ }
+ }
+ } finally {
+ if (br != null) br.close();
+ }
+ } catch (Exception e) {
+ // class creation exceptions are not interesting to caller...
+ throw Exceptions.propagate(e);
+ }
+ }
+ throw new IllegalStateException("Could not find framework factory.");
+ }
+
+ public static Framework newFrameworkStarted(String felixCacheDir, boolean clean, Map<?,?> extraStartupConfig) {
+ Map<Object,Object> cfg = MutableMap.copyOf(extraStartupConfig);
+ if (clean) cfg.put(Constants.FRAMEWORK_STORAGE_CLEAN, "onFirstInit");
+ if (felixCacheDir!=null) cfg.put(Constants.FRAMEWORK_STORAGE, felixCacheDir);
+ FrameworkFactory factory = newFrameworkFactory();
+
+ Framework framework = factory.newFramework(cfg);
+ try {
+ framework.init();
+ // nothing needs auto-loading, currently (and this needs a new dependency)
+// AutoProcessor.process(configProps, m_fwk.getBundleContext());
+ framework.start();
+ } catch (Exception e) {
+ // framework bundle start exceptions are not interesting to caller...
+ throw Exceptions.propagate(e);
+ }
+ return framework;
+ }
+
+ /** install a bundle from the given URL, doing a check if already installed, and
+ * using the {@link ResourceUtils} loader for this project (brooklyn core) */
+ public static Bundle install(Framework framework, String url) throws BundleException {
+ Bundle bundle = framework.getBundleContext().getBundle(url);
+ if (bundle!=null) return bundle;
+
+ // use our URL resolution so we get classpath items
+ InputStream stream = ResourceUtils.create(Osgis.class).getResourceFromUrl(url);
+ return framework.getBundleContext().installBundle(url, stream);
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/79ebcc33/core/src/test/java/brooklyn/management/osgi/OsgiStandaloneTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/osgi/OsgiStandaloneTest.java b/core/src/test/java/brooklyn/management/osgi/OsgiStandaloneTest.java
new file mode 100644
index 0000000..7ffaf2d
--- /dev/null
+++ b/core/src/test/java/brooklyn/management/osgi/OsgiStandaloneTest.java
@@ -0,0 +1,119 @@
+package brooklyn.management.osgi;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.io.FileUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.util.os.Os;
+import brooklyn.util.osgi.Osgis;
+
+/** tests some assumptions about OSGi behaviour, in standalone mode (not part of brooklyn).
+ *
+ * relies on the following bundles, which exist in the classpath (and contain their sources):
+ *
+ * <li>brooklyn-osgi-test-a_0.1.0 -
+ * defines TestA which has a "times" method and a static multiplier field;
+ * we set the multiplier to determine when we are sharing versions and when not
+ *
+ * */
+public class OsgiStandaloneTest {
+
+ public static final String BROOKLYN_OSGI_TEST_A_0_1_0_URL = "classpath:///brooklyn/osgi/brooklyn-osgi-test-a_0.1.0.jar";
+
+ protected Framework framework = null;
+ private File storageTempDir;
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ storageTempDir = Os.newTempDir("osgi-standalone");
+ framework = Osgis.newFrameworkStarted(storageTempDir.getAbsolutePath(), true, null);
+ }
+
+ @AfterMethod
+ public void tearDown() throws BundleException, IOException {
+ if (framework!=null) {
+ framework.stop();
+ framework = null;
+ }
+ if (storageTempDir!=null) {
+ FileUtils.deleteDirectory(storageTempDir);
+ storageTempDir = null;
+ }
+ }
+
+ protected Bundle install(String url) throws BundleException {
+ try {
+ return Osgis.install(framework, url);
+ } catch (Exception e) {
+ throw new IllegalStateException("test resources not available; may be an IDE issue, so try a mvn rebuild of this project", e);
+ }
+ }
+
+ @Test
+ public void testInstallBundle() throws Exception {
+ Bundle bundle = install(BROOKLYN_OSGI_TEST_A_0_1_0_URL);
+ checkMath(bundle, 3, 6);
+ }
+
+ @Test
+ public void testAMultiplier() throws Exception {
+ Bundle bundle = install(BROOKLYN_OSGI_TEST_A_0_1_0_URL);
+ checkMath(bundle, 3, 6);
+ setAMultiplier(bundle, 5);
+ checkMath(bundle, 3, 15);
+ }
+
+ /** run two multiplier tests to ensure that irrespective of order the tests run in,
+ * on a fresh install the multiplier is reset */
+ @Test
+ public void testANOtherMultiple() throws Exception {
+ Bundle bundle = install(BROOKLYN_OSGI_TEST_A_0_1_0_URL);
+ checkMath(bundle, 3, 6);
+ setAMultiplier(bundle, 14);
+ checkMath(bundle, 3, 42);
+ }
+
+ @Test
+ public void testGetBundle() throws Exception {
+ Bundle bundle = install(BROOKLYN_OSGI_TEST_A_0_1_0_URL);
+ setAMultiplier(bundle, 3);
+
+ // can look it up based on the same location string (no other "location identifier" reference string seems to work here, however)
+ Bundle bundle2 = install(BROOKLYN_OSGI_TEST_A_0_1_0_URL);
+ checkMath(bundle2, 3, 9);
+ }
+
+ @Test
+ public void testUninstallAndReinstallBundle() throws Exception {
+ Bundle bundle = install(BROOKLYN_OSGI_TEST_A_0_1_0_URL);
+ checkMath(bundle, 3, 6);
+ setAMultiplier(bundle, 3);
+ checkMath(bundle, 3, 9);
+ bundle.uninstall();
+
+ Bundle bundle2 = install(BROOKLYN_OSGI_TEST_A_0_1_0_URL);
+ checkMath(bundle2, 3, 6);
+ }
+
+ protected void checkMath(Bundle bundle, int input, int output) throws Exception {
+ Assert.assertNotNull(bundle);
+ Class<?> aClass = bundle.loadClass("brooklyn.test.osgi.TestA");
+ Object aInst = aClass.newInstance();
+ Object result = aClass.getMethod("times", int.class).invoke(aInst, input);
+ Assert.assertEquals(result, output);
+ }
+ protected void setAMultiplier(Bundle bundle, int newMultiplier) throws Exception {
+ Assert.assertNotNull(bundle);
+ Class<?> aClass = bundle.loadClass("brooklyn.test.osgi.TestA");
+ aClass.getField("multiplier").set(null, newMultiplier);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/79ebcc33/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java b/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java
index 2b2a884..3631ff4 100644
--- a/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java
+++ b/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java
@@ -2,7 +2,6 @@ package brooklyn.test.entity;
import brooklyn.config.BrooklynProperties;
import brooklyn.config.BrooklynServerConfig;
-import brooklyn.management.internal.AbstractManagementContext;
import brooklyn.management.internal.LocalManagementContext;
/** management context which forces an empty catalog to prevent scanning / interacting with local filesystem.
@@ -21,7 +20,13 @@ public class LocalManagementContextForTests extends LocalManagementContext {
public static BrooklynProperties setEmptyCatalogAsDefault(BrooklynProperties brooklynProperties) {
if (brooklynProperties==null) return null;
- brooklynProperties.putIfAbsent(AbstractManagementContext.BROOKLYN_CATALOG_URL, "classpath://brooklyn-catalog-empty.xml");
+ brooklynProperties.putIfAbsent(BrooklynServerConfig.BROOKLYN_CATALOG_URL, "classpath://brooklyn-catalog-empty.xml");
+ return brooklynProperties;
+ }
+
+ public static BrooklynProperties disableOsgi(BrooklynProperties brooklynProperties) {
+ if (brooklynProperties==null) return null;
+ brooklynProperties.putIfAbsent(BrooklynServerConfig.USE_OSGI, false);
return brooklynProperties;
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/79ebcc33/core/src/test/resources/brooklyn/osgi/brooklyn-osgi-test-a_0.1.0.jar
----------------------------------------------------------------------
diff --git a/core/src/test/resources/brooklyn/osgi/brooklyn-osgi-test-a_0.1.0.jar b/core/src/test/resources/brooklyn/osgi/brooklyn-osgi-test-a_0.1.0.jar
new file mode 100644
index 0000000..b4c777c
Binary files /dev/null and b/core/src/test/resources/brooklyn/osgi/brooklyn-osgi-test-a_0.1.0.jar differ
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/79ebcc33/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index f95beca..70a128b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -99,6 +99,7 @@
<mx4j.version>3.0.1</mx4j.version>
<bouncycastle.version>1.49</bouncycastle.version>
<sshj.version>0.8.1</sshj.version>
+ <felix.framework.version>4.4.0</felix.framework.version>
<reflections.version>0.9.9-RC1</reflections.version>
<jetty.version>8.1.4.v20120524</jetty.version>
<airline.version>0.6</airline.version>