diff --git a/akka-docs/additional/code/osgi/Activator.scala b/akka-docs/additional/code/osgi/Activator.scala
new file mode 100644
index 0000000000..34e83fcf77
--- /dev/null
+++ b/akka-docs/additional/code/osgi/Activator.scala
@@ -0,0 +1,19 @@
+import akka.actor.{ Props, ActorSystem }
+import akka.osgi.ActorSystemActivator
+import org.apache.servicemix.examples.akka.Listener
+import org.apache.servicemix.examples.akka.Master
+
+//#Activator
+class Activator extends ActorSystemActivator("PiSystem") {
+
+ def configure(context: BundleContext, system: ActorSystem) {
+ // optionally register the ActorSystem in the OSGi Service Registry
+ registerService(context, system)
+
+ val listener = system.actorOf(Props[Listener], name = "listener")
+ val master = system.actorOf(Props(new Master(4, 10000, 10000, listener)), name = "master")
+ master ! Calculate
+ }
+
+}
+//#Activator
\ No newline at end of file
diff --git a/akka-docs/additional/code/osgi/blueprint.xml b/akka-docs/additional/code/osgi/blueprint.xml
new file mode 100644
index 0000000000..8fcedb990c
--- /dev/null
+++ b/akka-docs/additional/code/osgi/blueprint.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+ some.config {
+ key=value
+ }
+
+
+
diff --git a/akka-docs/additional/osgi.rst b/akka-docs/additional/osgi.rst
index aea554ef9c..3bedc8c7dd 100644
--- a/akka-docs/additional/osgi.rst
+++ b/akka-docs/additional/osgi.rst
@@ -8,3 +8,20 @@ To use Akka in an OSGi environment, the ``org.osgi.framework.bootdelegation``
property must be set to always delegate the ``sun.misc`` package to the boot classloader
instead of resolving it through the normal OSGi class space.
+
+Activator
+---------
+
+To bootstrap Akka inside an OSGi environment, you can use the akka.osgi.AkkaSystemActivator class
+to conveniently set up the ActorSystem.
+
+.. includecode:: code/osgi/Activator.scala#Activator
+
+
+Blueprint
+---------
+
+For the Apache Aries Blueprint implementation, there's also a namespace handler available. The namespace URI
+is http://akka.io/xmlns/blueprint/v1.0.0 and it can be used to set up an ActorSystem.
+
+.. includecode:: code/osgi/blueprint.xml
diff --git a/akka-osgi-aries/src/main/resources/OSGI-INF/blueprint/akka-namespacehandler.xml b/akka-osgi-aries/src/main/resources/OSGI-INF/blueprint/akka-namespacehandler.xml
new file mode 100644
index 0000000000..99492bedf2
--- /dev/null
+++ b/akka-osgi-aries/src/main/resources/OSGI-INF/blueprint/akka-namespacehandler.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+ http://akka.io/xmlns/blueprint/v1.0.0
+
+
+
+
+
+
+
diff --git a/akka-osgi-aries/src/main/resources/akka/osgi/aries/blueprint/akka.xsd b/akka-osgi-aries/src/main/resources/akka/osgi/aries/blueprint/akka.xsd
new file mode 100644
index 0000000000..d7d0f77a2c
--- /dev/null
+++ b/akka-osgi-aries/src/main/resources/akka/osgi/aries/blueprint/akka.xsd
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+ Defines the configuration elements for setting up Akka with Blueprint
+
+
+
+
+
+
+
+ Defines an Akka ActorSystem
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Defines an Akka ActorSystem configuration
+
+
+
+
+
diff --git a/akka-osgi-aries/src/main/scala/akka/osgi/aries/blueprint/BlueprintActorSystemFactory.scala b/akka-osgi-aries/src/main/scala/akka/osgi/aries/blueprint/BlueprintActorSystemFactory.scala
new file mode 100644
index 0000000000..40c9d7367b
--- /dev/null
+++ b/akka-osgi-aries/src/main/scala/akka/osgi/aries/blueprint/BlueprintActorSystemFactory.scala
@@ -0,0 +1,44 @@
+package akka.osgi.aries.blueprint
+
+import org.osgi.framework.BundleContext
+import akka.osgi.OsgiActorSystemFactory
+import com.typesafe.config.ConfigFactory
+
+/**
+ * A set of helper/factory classes to build a Akka system using Blueprint. This class is only meant to be used by
+ * the [[akka.osgi.aries.blueprint.NamespaceHandler]] class, you should not use this class directly.
+ *
+ * If you're looking for a way to set up Akka using Blueprint without the namespace handler, you should use
+ * [[akka.osgi.OsgiActorSystemFactory]] instead.
+ */
+class BlueprintActorSystemFactory(context: BundleContext, name: String) extends OsgiActorSystemFactory(context) {
+
+ var config: Option[String] = None
+
+ lazy val system = super.createActorSystem(stringToOption(name))
+
+ def setConfig(config: String) = { this.config = Some(config) }
+
+ def create = system
+
+ def destroy = system.shutdown()
+
+ def stringToOption(original: String) = if (original == null || original.isEmpty) {
+ None
+ } else {
+ Some(original)
+ }
+
+ /**
+ * Strategy method to create the Config for the ActorSystem, ensuring that the default/reference configuration is
+ * loaded from the akka-actor bundle.
+ */
+ override def actorSystemConfig(context: BundleContext) = {
+ config match {
+ case Some(value) ⇒ ConfigFactory.parseString(value).withFallback(super.actorSystemConfig(context))
+ case None ⇒ super.actorSystemConfig(context)
+ }
+
+ }
+}
+
diff --git a/akka-osgi-aries/src/main/scala/akka/osgi/aries/blueprint/NamespaceHandler.scala b/akka-osgi-aries/src/main/scala/akka/osgi/aries/blueprint/NamespaceHandler.scala
new file mode 100644
index 0000000000..0570a027b6
--- /dev/null
+++ b/akka-osgi-aries/src/main/scala/akka/osgi/aries/blueprint/NamespaceHandler.scala
@@ -0,0 +1,148 @@
+package akka.osgi.aries.blueprint
+
+import org.apache.aries.blueprint.ParserContext
+import org.osgi.service.blueprint.container.ComponentDefinitionException
+import org.apache.aries.blueprint.mutable.MutableBeanMetadata
+
+import collection.JavaConversions.setAsJavaSet
+import org.osgi.framework.BundleContext
+import org.apache.aries.blueprint.reflect.{ ValueMetadataImpl, RefMetadataImpl, BeanArgumentImpl }
+import org.w3c.dom.{ Element, Node }
+import org.osgi.service.blueprint.reflect.{ BeanMetadata, ComponentMetadata }
+import akka.actor.ActorSystem
+import java.util.concurrent.atomic.AtomicInteger
+
+import ParserHelper.childElements
+
+/**
+ * Aries Blueprint namespace handler implementation. This namespace handler will allow users of Apache Aries' Blueprint
+ * implementation to define their Akka [[akka.actor.ActorSystem]] using a syntax like this:
+ *
+ * {{{
+ *
+ *
+ *
+ *
+ *
+ * some.config {
+ * key=value
+ * }
+ *
+ *
+ *
+ *
+ * }}}
+ *
+ * Users of other IoC frameworks in an OSGi environment should use [[akka.osgi.OsgiActorSystemFactory]] instead.
+ */
+class NamespaceHandler extends org.apache.aries.blueprint.NamespaceHandler {
+
+ import NamespaceHandler._
+
+ val idCounter = new AtomicInteger(0)
+
+ def getSchemaLocation(namespace: String) = getClass().getResource("akka.xsd")
+
+ def getManagedClasses = setAsJavaSet(Set(classOf[BlueprintActorSystemFactory]))
+
+ def parse(element: Element, context: ParserContext) = element.getLocalName match {
+ case ACTORSYSTEM_ELEMENT_NAME ⇒ parseActorSystem(element, context)
+ case _ ⇒ throw new ComponentDefinitionException("Unexpected element for Akka namespace: %s".format(element))
+ }
+
+ def decorate(node: Node, component: ComponentMetadata, context: ParserContext) =
+ throw new ComponentDefinitionException("Bad xml syntax: node decoration is not supported")
+
+ /*
+ * Parse
+ */
+ def parseActorSystem(element: Element, context: ParserContext) = {
+ val factory = createFactoryBean(context, element.getAttribute(NAME_ATTRIBUTE))
+
+ for (child ← childElements(element)) {
+ child.getLocalName match {
+ case CONFIG_ELEMENT_NAME ⇒ parseConfig(child, context, factory)
+ case _ ⇒ throw new ComponentDefinitionException("Unexpected child element %s found in %s".format(child, element))
+ }
+ }
+
+ createActorSystemBean(context, element, factory)
+ }
+
+ /*
+ * Parse
+ */
+ def parseConfig(node: Element, context: ParserContext, factory: MutableBeanMetadata) = {
+ factory.addProperty("config", new ValueMetadataImpl(node.getTextContent))
+ }
+
+ /*
+ * Create the bean definition for the ActorSystem
+ */
+ def createActorSystemBean(context: ParserContext, element: Element, factory: MutableBeanMetadata): MutableBeanMetadata = {
+ val system = context.createMetadata(classOf[MutableBeanMetadata])
+ system.setId(getId(context, element))
+ system.setFactoryComponent(factory)
+
+ system.setFactoryMethod(FACTORY_METHOD_NAME)
+ system.setRuntimeClass(classOf[ActorSystem])
+ system
+ }
+
+ /*
+ * Create the bean definition for the BlueprintActorSystemFactory
+ */
+ def createFactoryBean(context: ParserContext, name: String): MutableBeanMetadata = {
+ val factory = context.createMetadata(classOf[MutableBeanMetadata])
+ factory.setId(findAvailableId(context))
+ factory.setScope(BeanMetadata.SCOPE_SINGLETON)
+ factory.setProcessor(true)
+ factory.setRuntimeClass(classOf[BlueprintActorSystemFactory])
+
+ factory.setDestroyMethod(DESTROY_METHOD_NAME)
+
+ factory.addArgument(new BeanArgumentImpl(new RefMetadataImpl(BUNDLE_CONTEXT_REFID), classOf[BundleContext].getName, -1))
+ factory.addArgument(new BeanArgumentImpl(new ValueMetadataImpl(name), classOf[String].getName, -1))
+ factory.setProcessor(true)
+ context.getComponentDefinitionRegistry.registerComponentDefinition(factory)
+ factory
+ }
+
+ /*
+ * Get the assigned id or generate a suitable id
+ */
+ def getId(context: ParserContext, element: Element) = {
+ if (element.hasAttribute(ID_ATTRIBUTE)) {
+ element.getAttribute(ID_ATTRIBUTE)
+ } else {
+ findAvailableId(context)
+ }
+ }
+
+ /*
+ * Find the next available component id
+ */
+ def findAvailableId(context: ParserContext): String = {
+ val id = ".akka-" + idCounter.incrementAndGet()
+ if (context.getComponentDefinitionRegistry.containsComponentDefinition(id)) {
+ // id already exists, let's try the next one
+ findAvailableId(context)
+ } else id
+ }
+}
+
+object NamespaceHandler {
+
+ private val ID_ATTRIBUTE = "id"
+ private val NAME_ATTRIBUTE = "name"
+
+ private val BUNDLE_CONTEXT_REFID = "blueprintBundleContext"
+
+ private val ACTORSYSTEM_ELEMENT_NAME = "actor-system"
+ private val CONFIG_ELEMENT_NAME = "config"
+
+ private val DESTROY_METHOD_NAME = "destroy"
+ private val FACTORY_METHOD_NAME = "create"
+
+}
diff --git a/akka-osgi-aries/src/main/scala/akka/osgi/aries/blueprint/ParserHelper.scala b/akka-osgi-aries/src/main/scala/akka/osgi/aries/blueprint/ParserHelper.scala
new file mode 100644
index 0000000000..585037db09
--- /dev/null
+++ b/akka-osgi-aries/src/main/scala/akka/osgi/aries/blueprint/ParserHelper.scala
@@ -0,0 +1,17 @@
+package akka.osgi.aries.blueprint
+
+import org.w3c.dom.{ Node, Element }
+
+/**
+ * Helper class to deal with the W3C DOM types
+ */
+object ParserHelper {
+
+ def childElements(element: Element): Seq[Element] =
+ children(element).filter(_.getNodeType == Node.ELEMENT_NODE).asInstanceOf[Seq[Element]]
+
+ private[this] def children(element: Element): Seq[Node] = {
+ val nodelist = element.getChildNodes
+ for (index ← 0 until nodelist.getLength) yield nodelist.item(index)
+ }
+}
diff --git a/akka-osgi-aries/src/test/resources/akka/osgi/aries/blueprint/config.xml b/akka-osgi-aries/src/test/resources/akka/osgi/aries/blueprint/config.xml
new file mode 100644
index 0000000000..ce9f48c551
--- /dev/null
+++ b/akka-osgi-aries/src/test/resources/akka/osgi/aries/blueprint/config.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ some.config {
+ key=value
+ }
+
+
+
+
diff --git a/akka-osgi-aries/src/test/resources/akka/osgi/aries/blueprint/injection.xml b/akka-osgi-aries/src/test/resources/akka/osgi/aries/blueprint/injection.xml
new file mode 100644
index 0000000000..6fd21db5ef
--- /dev/null
+++ b/akka-osgi-aries/src/test/resources/akka/osgi/aries/blueprint/injection.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/akka-osgi-aries/src/test/resources/akka/osgi/aries/blueprint/simple.xml b/akka-osgi-aries/src/test/resources/akka/osgi/aries/blueprint/simple.xml
new file mode 100644
index 0000000000..2ac6552f80
--- /dev/null
+++ b/akka-osgi-aries/src/test/resources/akka/osgi/aries/blueprint/simple.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/akka-osgi-aries/src/test/scala/akka/osgi/aries/blueprint/ActorSystemAwareBean.scala b/akka-osgi-aries/src/test/scala/akka/osgi/aries/blueprint/ActorSystemAwareBean.scala
new file mode 100644
index 0000000000..6e4bac39dd
--- /dev/null
+++ b/akka-osgi-aries/src/test/scala/akka/osgi/aries/blueprint/ActorSystemAwareBean.scala
@@ -0,0 +1,11 @@
+package akka.osgi.aries.blueprint
+
+import akka.actor.ActorSystem
+
+/**
+ * Just a simple POJO that can contain an actor system.
+ * Used for testing dependency injection with Blueprint
+ */
+class ActorSystemAwareBean(val system: ActorSystem) {
+
+}
diff --git a/akka-osgi-aries/src/test/scala/akka/osgi/aries/blueprint/NamespaceHandlerTest.scala b/akka-osgi-aries/src/test/scala/akka/osgi/aries/blueprint/NamespaceHandlerTest.scala
new file mode 100644
index 0000000000..4f4eb647e0
--- /dev/null
+++ b/akka-osgi-aries/src/test/scala/akka/osgi/aries/blueprint/NamespaceHandlerTest.scala
@@ -0,0 +1,101 @@
+package akka.osgi.aries.blueprint
+
+import org.scalatest.WordSpec
+import akka.actor.ActorSystem
+import de.kalpatec.pojosr.framework.launch.BundleDescriptor
+import akka.osgi.PojoSRTestSupport
+import akka.osgi.PojoSRTestSupport.bundle
+import org.scalatest.matchers.MustMatchers
+
+/**
+ * Test cases for {@link ActorSystemActivator}
+ */
+object NamespaceHandlerTest {
+
+ /*
+ * Bundle-SymbolicName to easily find our test bundle
+ */
+ val TEST_BUNDLE_NAME = "akka.osgi.test.aries.namespace"
+
+ /*
+ * Bundle descriptor representing the akka-osgi bundle itself
+ */
+ val AKKA_OSGI_BLUEPRINT =
+ bundle("akka-osgi").withBlueprintFile(getClass.getResource("/OSGI-INF/blueprint/akka-namespacehandler.xml"))
+
+}
+
+class SimpleNamespaceHandlerTest extends WordSpec with MustMatchers with PojoSRTestSupport {
+
+ import NamespaceHandlerTest._
+
+ val testBundles: Seq[BundleDescriptor] = buildTestBundles(Seq(
+ AKKA_OSGI_BLUEPRINT,
+ bundle(TEST_BUNDLE_NAME).withBlueprintFile(getClass.getResource("simple.xml"))))
+
+ "simple.xml" must {
+ "set up ActorSystem when bundle starts" in {
+ val system = serviceForType[ActorSystem]
+ assert(system != null)
+ }
+
+ "stop the ActorSystem when bundle stops" in {
+ val system = serviceForType[ActorSystem]
+ assert(!system.isTerminated)
+
+ bundleForName(TEST_BUNDLE_NAME).stop()
+
+ system.awaitTermination()
+ assert(system.isTerminated)
+ }
+ }
+
+}
+
+class ConfigNamespaceHandlerTest extends WordSpec with MustMatchers with PojoSRTestSupport {
+
+ import NamespaceHandlerTest._
+
+ val testBundles: Seq[BundleDescriptor] = buildTestBundles(Seq(
+ AKKA_OSGI_BLUEPRINT,
+ bundle(TEST_BUNDLE_NAME).withBlueprintFile(getClass.getResource("config.xml"))))
+
+ "config.xml" must {
+ "set up ActorSystem when bundle starts" in {
+ val system = serviceForType[ActorSystem]
+ assert(system != null)
+
+ assert(system.settings.config.getString("some.config.key") == "value")
+ }
+
+ "stop the ActorSystem when bundle stops" in {
+ val system = serviceForType[ActorSystem]
+ assert(!system.isTerminated)
+
+ bundleForName(TEST_BUNDLE_NAME).stop()
+
+ system.awaitTermination()
+ assert(system.isTerminated)
+ }
+ }
+
+}
+
+class DependencyInjectionNamespaceHandlerTest extends WordSpec with MustMatchers with PojoSRTestSupport {
+
+ import NamespaceHandlerTest._
+
+ val testBundles: Seq[BundleDescriptor] = buildTestBundles(Seq(
+ AKKA_OSGI_BLUEPRINT,
+ bundle(TEST_BUNDLE_NAME).withBlueprintFile(getClass.getResource("injection.xml"))))
+
+ "injection.xml" must {
+
+ "set up bean containing ActorSystem" in {
+ val bean = serviceForType[ActorSystemAwareBean]
+ assert(bean != null)
+ assert(bean.system != null)
+ }
+ }
+
+}
diff --git a/akka-osgi/src/main/scala/akka/osgi/ActorSystemActivator.scala b/akka-osgi/src/main/scala/akka/osgi/ActorSystemActivator.scala
new file mode 100644
index 0000000000..e279247dbc
--- /dev/null
+++ b/akka-osgi/src/main/scala/akka/osgi/ActorSystemActivator.scala
@@ -0,0 +1,74 @@
+package akka.osgi
+
+import akka.actor.ActorSystem
+import java.util.{ Dictionary, Properties }
+import org.osgi.framework.{ ServiceRegistration, BundleContext, BundleActivator }
+
+/**
+ * Abstract bundle activator implementation to bootstrap and configure an actor system in an
+ * OSGi environment. It also provides a convenience method to register the actor system in
+ * the OSGi Service Registry for sharing it with other OSGi bundles.
+ *
+ * This convenience activator is mainly useful for setting up a single [[akka.actor.ActorSystem]] instance and sharing that
+ * with other bundles in the OSGi Framework. If you want to set up multiple systems in the same bundle context, look at
+ * the [[akka.osgi.OsgiActorSystemFactory]] instead.
+ */
+abstract class ActorSystemActivator extends BundleActivator {
+
+ private var system: Option[ActorSystem] = None
+ private var registration: Option[ServiceRegistration] = None
+
+ /**
+ * Implement this method to add your own actors to the ActorSystem. If you want to share the actor
+ * system with other bundles, call the `registerService(BundleContext, ActorSystem)` method from within
+ * this method.
+ *
+ * @param context the bundle context
+ * @param system the ActorSystem that was created by the activator
+ */
+ def configure(context: BundleContext, system: ActorSystem): Unit
+
+ /**
+ * Sets up a new ActorSystem
+ *
+ * @param context the BundleContext
+ */
+ def start(context: BundleContext): Unit = {
+ system = Some(OsgiActorSystemFactory(context).createActorSystem(Option(getActorSystemName(context))))
+ system foreach (configure(context, _))
+ }
+
+ /**
+ * Shuts down the ActorSystem when the bundle is stopped and, if necessary, unregisters a service registration.
+ *
+ * @param context the BundleContext
+ */
+ def stop(context: BundleContext): Unit = {
+ registration foreach (_.unregister())
+ system foreach (_.shutdown())
+ }
+
+ /**
+ * Register the actor system in the OSGi service registry. The activator itself will ensure that this service
+ * is unregistered again when the bundle is being stopped.
+ *
+ * @param context the bundle context
+ * @param system the actor system
+ */
+ def registerService(context: BundleContext, system: ActorSystem): Unit = {
+ val properties = new Properties()
+ properties.put("name", system.name)
+ registration = Some(context.registerService(classOf[ActorSystem].getName, system,
+ properties.asInstanceOf[Dictionary[String, Any]]))
+ }
+
+ /**
+ * By default, the [[akka.actor.ActorSystem]] name will be set to `bundle--ActorSystem`. Override this
+ * method to define another name for your [[akka.actor.ActorSystem]] instance.
+ *
+ * @param context the bundle context
+ * @return the actor system name
+ */
+ def getActorSystemName(context: BundleContext): String = null
+
+}
diff --git a/akka-osgi/src/main/scala/akka/osgi/OsgiActorSystemFactory.scala b/akka-osgi/src/main/scala/akka/osgi/OsgiActorSystemFactory.scala
new file mode 100644
index 0000000000..ae36406a60
--- /dev/null
+++ b/akka-osgi/src/main/scala/akka/osgi/OsgiActorSystemFactory.scala
@@ -0,0 +1,57 @@
+package akka.osgi
+
+import impl.BundleDelegatingClassLoader
+import akka.actor.ActorSystem
+import com.typesafe.config.{ ConfigFactory, Config }
+import org.osgi.framework.BundleContext
+
+/**
+ * Factory class to create ActorSystem implementations in an OSGi environment. This mainly involves dealing with
+ * bundle classloaders appropriately to ensure that configuration files and classes get loaded properly
+ */
+class OsgiActorSystemFactory(val context: BundleContext) {
+
+ /*
+ * Classloader that delegates to the bundle for which the factory is creating an ActorSystem
+ */
+ private val classloader = BundleDelegatingClassLoader.createFor(context)
+
+ /**
+ * Creates the [[akka.actor.ActorSystem]], using the name specified
+ */
+ def createActorSystem(name: String): ActorSystem = createActorSystem(Option(name))
+
+ /**
+ * Creates the [[akka.actor.ActorSystem]], using the name specified.
+ *
+ * A default name (`bundle--ActorSystem`) is assigned when you pass along [[scala.None]] instead.
+ */
+ def createActorSystem(name: Option[String]): ActorSystem =
+ ActorSystem(actorSystemName(name), actorSystemConfig(context), classloader)
+
+ /**
+ * Strategy method to create the Config for the ActorSystem, ensuring that the default/reference configuration is
+ * loaded from the akka-actor bundle.
+ */
+ def actorSystemConfig(context: BundleContext): Config = {
+ val reference = ConfigFactory.defaultReference(classOf[ActorSystem].getClassLoader)
+ ConfigFactory.load(classloader).withFallback(reference)
+ }
+
+ /**
+ * Determine the name for the [[akka.actor.ActorSystem]]
+ * Returns a default value of `bundle--ActorSystem` is no name is being specified
+ */
+ def actorSystemName(name: Option[String]): String =
+ name.getOrElse("bundle-%s-ActorSystem".format(context.getBundle().getBundleId))
+
+}
+
+object OsgiActorSystemFactory {
+
+ /*
+ * Create an [[OsgiActorSystemFactory]] instance to set up Akka in an OSGi environment
+ */
+ def apply(context: BundleContext): OsgiActorSystemFactory = new OsgiActorSystemFactory(context)
+
+}
diff --git a/akka-osgi/src/main/scala/akka/osgi/impl/BundleDelegatingClassLoader.scala b/akka-osgi/src/main/scala/akka/osgi/impl/BundleDelegatingClassLoader.scala
new file mode 100644
index 0000000000..08dee0344e
--- /dev/null
+++ b/akka-osgi/src/main/scala/akka/osgi/impl/BundleDelegatingClassLoader.scala
@@ -0,0 +1,72 @@
+package akka.osgi.impl
+
+import java.net.URL
+import java.util.Enumeration
+
+import org.osgi.framework.{ BundleContext, Bundle }
+
+/*
+ * Companion object to create bundle delegating classloader instances
+ */
+object BundleDelegatingClassLoader {
+
+ /*
+ * Create a bundle delegating classloader for the bundle context's bundle
+ */
+ def createFor(context: BundleContext) = new BundleDelegatingClassLoader(context.getBundle)
+
+}
+
+/*
+ * A bundle delegating classloader implemenation - this will try to load classes and resources from the bundle
+ * specified first and if there's a classloader specified, that will be used as a fallback
+ */
+class BundleDelegatingClassLoader(bundle: Bundle, classLoader: Option[ClassLoader]) extends ClassLoader {
+
+ def this(bundle: Bundle) = this(bundle, None)
+
+ protected override def findClass(name: String): Class[_] = bundle.loadClass(name)
+
+ protected override def findResource(name: String): URL = {
+ val resource = bundle.getResource(name)
+ classLoader match {
+ case Some(loader) if resource == null ⇒ loader.getResource(name)
+ case _ ⇒ resource
+ }
+ }
+
+ @SuppressWarnings(Array("unchecked", "rawtypes"))
+ protected override def findResources(name: String): Enumeration[URL] =
+ bundle.getResources(name).asInstanceOf[Enumeration[URL]]
+
+ protected override def loadClass(name: String, resolve: Boolean): Class[_] = {
+ val clazz = try {
+ findClass(name)
+ } catch {
+ case cnfe: ClassNotFoundException ⇒ {
+ classLoader match {
+ case Some(loader) ⇒ loadClass(name, loader)
+ case None ⇒ rethrowClassNotFoundException(name, cnfe)
+ }
+ }
+ }
+ if (resolve) {
+ resolveClass(clazz)
+ }
+ clazz
+ }
+
+ private def loadClass(name: String, classLoader: ClassLoader) =
+ try {
+ classLoader.loadClass(name)
+ } catch {
+ case cnfe: ClassNotFoundException ⇒ rethrowClassNotFoundException(name, cnfe)
+ }
+
+ private def rethrowClassNotFoundException(name: String, cnfe: ClassNotFoundException): Nothing =
+ throw new ClassNotFoundException(name + " from bundle " + bundle.getBundleId + " (" + bundle.getSymbolicName + ")", cnfe)
+
+ override def toString: String = String.format("BundleDelegatingClassLoader(%s)", bundle)
+
+}
+
diff --git a/akka-osgi/src/test/resources/logback-test.xml b/akka-osgi/src/test/resources/logback-test.xml
new file mode 100644
index 0000000000..9c441a6fb6
--- /dev/null
+++ b/akka-osgi/src/test/resources/logback-test.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ %date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n
+
+
+
+
+ target/akka-osgi.log
+ true
+
+ %date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n
+
+
+
+
+
+
+
+
diff --git a/akka-osgi/src/test/scala/akka/osgi/ActorSystemActivatorTest.scala b/akka-osgi/src/test/scala/akka/osgi/ActorSystemActivatorTest.scala
new file mode 100644
index 0000000000..37002975e4
--- /dev/null
+++ b/akka-osgi/src/test/scala/akka/osgi/ActorSystemActivatorTest.scala
@@ -0,0 +1,74 @@
+package akka.osgi
+
+import org.scalatest.WordSpec
+import akka.actor.ActorSystem
+import akka.pattern.ask
+import akka.dispatch.Await
+import akka.util.duration._
+import akka.util.Timeout
+import de.kalpatec.pojosr.framework.launch.BundleDescriptor
+import test.{ RuntimeNameActorSystemActivator, TestActivators, PingPongActorSystemActivator }
+import test.PingPong._
+import PojoSRTestSupport.bundle
+import org.scalatest.matchers.MustMatchers
+
+/**
+ * Test cases for [[akka.osgi.ActorSystemActivator]] in 2 different scenarios:
+ * - no name configured for [[akka.actor.ActorSystem]]
+ * - runtime name configuration
+ */
+object ActorSystemActivatorTest {
+
+ val TEST_BUNDLE_NAME = "akka.osgi.test.activator"
+
+}
+
+class PingPongActorSystemActivatorTest extends WordSpec with MustMatchers with PojoSRTestSupport {
+
+ import ActorSystemActivatorTest._
+
+ val testBundles: Seq[BundleDescriptor] = buildTestBundles(Seq(
+ bundle(TEST_BUNDLE_NAME).withActivator(classOf[PingPongActorSystemActivator])))
+
+ "PingPongActorSystemActivator" must {
+
+ "start and register the ActorSystem when bundle starts" in {
+ val system = serviceForType[ActorSystem]
+ val actor = system.actorFor("/user/pong")
+
+ implicit val timeout = Timeout(5 seconds)
+ val future = actor ? Ping
+ val result = Await.result(future, timeout.duration)
+ assert(result != null)
+ }
+
+ "stop the ActorSystem when bundle stops" in {
+ val system = serviceForType[ActorSystem]
+ assert(!system.isTerminated)
+
+ bundleForName(TEST_BUNDLE_NAME).stop()
+
+ system.awaitTermination()
+ assert(system.isTerminated)
+ }
+ }
+
+}
+
+class RuntimeNameActorSystemActivatorTest extends WordSpec with MustMatchers with PojoSRTestSupport {
+
+ import ActorSystemActivatorTest._
+
+ val testBundles: Seq[BundleDescriptor] = buildTestBundles(Seq(
+ bundle(TEST_BUNDLE_NAME).withActivator(classOf[RuntimeNameActorSystemActivator])))
+
+ "RuntimeNameActorSystemActivator" must {
+
+ "register an ActorSystem and add the bundle id to the system name" in {
+ val system = serviceForType[ActorSystem]
+ val bundle = bundleForName(TEST_BUNDLE_NAME)
+ system.name must equal(TestActivators.ACTOR_SYSTEM_NAME_PATTERN.format(bundle.getBundleId))
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/akka-osgi/src/test/scala/akka/osgi/PojoSRTestSupport.scala b/akka-osgi/src/test/scala/akka/osgi/PojoSRTestSupport.scala
new file mode 100644
index 0000000000..3ba9068907
--- /dev/null
+++ b/akka-osgi/src/test/scala/akka/osgi/PojoSRTestSupport.scala
@@ -0,0 +1,160 @@
+package akka.osgi
+
+import de.kalpatec.pojosr.framework.launch.{ BundleDescriptor, PojoServiceRegistryFactory, ClasspathScanner }
+
+import scala.collection.JavaConversions.seqAsJavaList
+import scala.collection.JavaConversions.collectionAsScalaIterable
+import org.apache.commons.io.IOUtils.copy
+
+import org.osgi.framework._
+import java.net.URL
+
+import java.util.jar.JarInputStream
+import java.io.{ FileInputStream, FileOutputStream, File }
+import java.util.{ Date, ServiceLoader, HashMap }
+import org.scalatest.{ BeforeAndAfterAll, Suite }
+
+/**
+ * Trait that provides support for building akka-osgi tests using PojoSR
+ */
+trait PojoSRTestSupport extends Suite with BeforeAndAfterAll {
+
+ val MAX_WAIT_TIME = 12800
+ val START_WAIT_TIME = 50
+
+ /**
+ * All bundles being found on the test classpath are automatically installed and started in the PojoSR runtime.
+ * Implement this to define the extra bundles that should be available for testing.
+ */
+ val testBundles: Seq[BundleDescriptor]
+
+ lazy val context: BundleContext = {
+ val config = new HashMap[String, AnyRef]()
+ System.setProperty("org.osgi.framework.storage", "target/akka-osgi/" + System.currentTimeMillis)
+
+ val bundles = new ClasspathScanner().scanForBundles()
+ bundles.addAll(testBundles)
+ config.put(PojoServiceRegistryFactory.BUNDLE_DESCRIPTORS, bundles)
+
+ val loader: ServiceLoader[PojoServiceRegistryFactory] = ServiceLoader.load(classOf[PojoServiceRegistryFactory])
+
+ val registry = loader.iterator.next.newPojoServiceRegistry(config)
+ registry.getBundleContext
+ }
+
+ // Ensure bundles get stopped at the end of the test to release resources and stop threads
+ override protected def afterAll() = context.getBundles.foreach(_.stop)
+
+ /**
+ * Convenience method to find a bundle by symbolic name
+ */
+ def bundleForName(name: String) = context.getBundles.find(_.getSymbolicName == name) match {
+ case Some(bundle) ⇒ bundle
+ case None ⇒ fail("Unable to find bundle with symbolic name %s".format(name))
+ }
+
+ /**
+ * Convenience method to find a service by interface. If the service is not already available in the OSGi Service
+ * Registry, this method will wait for a few seconds for the service to appear.
+ */
+ def serviceForType[T](implicit manifest: Manifest[T]): T = {
+ val reference = awaitReference(manifest.erasure)
+ context.getService(reference).asInstanceOf[T]
+ }
+
+ def awaitReference(serviceType: Class[_]): ServiceReference = awaitReference(serviceType, START_WAIT_TIME)
+
+ def awaitReference(serviceType: Class[_], wait: Long): ServiceReference = {
+ val option = Option(context.getServiceReference(serviceType.getName))
+ Thread.sleep(wait)
+ option match {
+ case Some(reference) ⇒ reference
+ case None if (wait > MAX_WAIT_TIME) ⇒ fail("Gave up waiting for service of type %s".format(serviceType))
+ case None ⇒ awaitReference(serviceType, wait * 2)
+ }
+ }
+
+ protected def buildTestBundles(builders: Seq[BundleDescriptorBuilder]): Seq[BundleDescriptor] = builders map (_.build)
+}
+
+object PojoSRTestSupport {
+
+ /**
+ * Convenience method to define additional test bundles
+ */
+ def bundle(name: String) = new BundleDescriptorBuilder(name)
+
+}
+
+/**
+ * Helper class to make it easier to define test bundles
+ */
+class BundleDescriptorBuilder(name: String) {
+
+ import org.ops4j.pax.tinybundles.core.TinyBundles
+
+ val tinybundle = TinyBundles.bundle.set(Constants.BUNDLE_SYMBOLICNAME, name)
+
+ /**
+ * Add a Blueprint XML file to our test bundle
+ */
+ def withBlueprintFile(name: String, contents: URL): BundleDescriptorBuilder =
+ returnBuilder(tinybundle.add("OSGI-INF/blueprint/%s".format(name), contents))
+
+ /**
+ * Add a Blueprint XML file to our test bundle
+ */
+ def withBlueprintFile(contents: URL): BundleDescriptorBuilder = withBlueprintFile(filename(contents), contents)
+
+ /**
+ * Add a Bundle activator to our test bundle
+ */
+ def withActivator(activator: Class[_ <: BundleActivator]): BundleDescriptorBuilder =
+ returnBuilder(tinybundle.set(Constants.BUNDLE_ACTIVATOR, activator.getName))
+
+ private def returnBuilder(block: ⇒ Unit) = {
+ block
+ this
+ }
+
+ /**
+ * Build the actual PojoSR BundleDescriptor instance
+ */
+ def build: BundleDescriptor = {
+ val file: File = tinybundleToJarFile(name)
+
+ new BundleDescriptor(
+ getClass().getClassLoader(),
+ new URL("jar:" + file.toURI().toString() + "!/"),
+ extractHeaders(file))
+ }
+
+ def extractHeaders(file: File): HashMap[String, String] = {
+ val headers = new HashMap[String, String]()
+
+ val jis = new JarInputStream(new FileInputStream(file))
+ try {
+ for (entry ← jis.getManifest().getMainAttributes().entrySet()) {
+ headers.put(entry.getKey().toString(), entry.getValue().toString())
+ }
+ } finally {
+ jis.close()
+ }
+
+ headers
+ }
+
+ def tinybundleToJarFile(name: String): File = {
+ val file = new File("target/%s-%tQ.jar".format(name, new Date()))
+ val fos = new FileOutputStream(file)
+ try {
+ copy(tinybundle.build(), fos)
+ } finally {
+ fos.close()
+ }
+ file
+ }
+
+ private[this] def filename(url: URL) = url.getFile.split("/").last
+}
+
diff --git a/akka-osgi/src/test/scala/akka/osgi/test/PingPong.scala b/akka-osgi/src/test/scala/akka/osgi/test/PingPong.scala
new file mode 100644
index 0000000000..6a7409c667
--- /dev/null
+++ b/akka-osgi/src/test/scala/akka/osgi/test/PingPong.scala
@@ -0,0 +1,22 @@
+package akka.osgi.test
+
+import akka.actor.Actor
+
+/**
+ * Simple ping-pong actor, used for testing
+ */
+object PingPong {
+
+ abstract class TestMessage
+
+ case object Ping extends TestMessage
+ case object Pong extends TestMessage
+
+ class PongActor extends Actor {
+ def receive = {
+ case Ping ⇒
+ sender ! Pong
+ }
+ }
+
+}
diff --git a/akka-osgi/src/test/scala/akka/osgi/test/TestActivators.scala b/akka-osgi/src/test/scala/akka/osgi/test/TestActivators.scala
new file mode 100644
index 0000000000..54369d88ca
--- /dev/null
+++ b/akka-osgi/src/test/scala/akka/osgi/test/TestActivators.scala
@@ -0,0 +1,39 @@
+package akka.osgi.test
+
+import akka.osgi.ActorSystemActivator
+import akka.actor.{ Props, ActorSystem }
+import PingPong._
+import org.osgi.framework.BundleContext
+
+/**
+ * A set of [[akka.osgi.ActorSystemActivator]]s for testing purposes
+ */
+object TestActivators {
+
+ val ACTOR_SYSTEM_NAME_PATTERN = "actor-system-for-bundle-%s"
+
+}
+
+/**
+ * Simple ActorSystemActivator that starts the sample ping-pong application
+ */
+class PingPongActorSystemActivator extends ActorSystemActivator {
+
+ def configure(context: BundleContext, system: ActorSystem) {
+ system.actorOf(Props(new PongActor), name = "pong")
+ registerService(context, system)
+ }
+
+}
+
+/**
+ * [[akka.osgi.ActorSystemActivator]] implementation that determines [[akka.actor.ActorSystem]] name at runtime
+ */
+class RuntimeNameActorSystemActivator extends ActorSystemActivator {
+
+ def configure(context: BundleContext, system: ActorSystem) = registerService(context, system);
+
+ override def getActorSystemName(context: BundleContext) =
+ TestActivators.ACTOR_SYSTEM_NAME_PATTERN.format(context.getBundle.getBundleId)
+
+}
\ No newline at end of file
diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala
index 3a670062ad..30c1db8996 100644
--- a/project/AkkaBuild.scala
+++ b/project/AkkaBuild.scala
@@ -217,6 +217,24 @@ object AkkaBuild extends Build {
)
)
+ lazy val osgi = Project(
+ id = "akka-osgi",
+ base = file("akka-osgi"),
+ dependencies = Seq(actor),
+ settings = defaultSettings ++ OSGi.osgi ++ Seq(
+ libraryDependencies ++= Dependencies.osgi
+ )
+ )
+
+ lazy val osgiAries = Project(
+ id = "akka-osgi-aries",
+ base = file("akka-osgi-aries"),
+ dependencies = Seq(osgi % "compile;test->test"),
+ settings = defaultSettings ++ OSGi.osgiAries ++ Seq(
+ libraryDependencies ++= Dependencies.osgiAries
+ )
+ )
+
lazy val akkaSbtPlugin = Project(
id = "akka-sbt-plugin",
base = file("akka-sbt-plugin"),
@@ -469,6 +487,10 @@ object Dependencies {
val camel = Seq(camelCore, Test.scalatest, Test.junit, Test.mockito)
+ val osgi = Seq(osgiCore,Test.logback, Test.commonsIo, Test.pojosr, Test.tinybundles, Test.scalatest, Test.junit)
+
+ val osgiAries = Seq(osgiCore, ariesBlueprint, Test.ariesProxy)
+
val tutorials = Seq(Test.scalatest, Test.junit)
val docs = Seq(Test.scalatest, Test.junit, Test.specs2)
@@ -484,6 +506,7 @@ object Dependency {
val Camel = "2.8.0"
val Logback = "1.0.4"
val Netty = "3.5.1.Final"
+ val OSGi = "4.2.0"
val Protobuf = "2.4.1"
val ScalaStm = "0.5"
val Scalatest = "1.6.1"
@@ -492,9 +515,11 @@ object Dependency {
}
// Compile
+ val ariesBlueprint = "org.apache.aries.blueprint" % "org.apache.aries.blueprint" % "0.3.2" // ApacheV2
val config = "com.typesafe" % "config" % "0.4.1" // ApacheV2
val camelCore = "org.apache.camel" % "camel-core" % V.Camel // ApacheV2
val netty = "io.netty" % "netty" % V.Netty // ApacheV2
+ val osgiCore = "org.osgi" % "org.osgi.core" % V.OSGi // ApacheV2
val protobuf = "com.google.protobuf" % "protobuf-java" % V.Protobuf // New BSD
val scalaStm = "org.scala-tools" % "scala-stm_2.9.1" % V.ScalaStm // Modified BSD (Scala)
val slf4jApi = "org.slf4j" % "slf4j-api" % V.Slf4j // MIT
@@ -504,14 +529,18 @@ object Dependency {
// Test
object Test {
+ val ariesProxy = "org.apache.aries.proxy" % "org.apache.aries.proxy.impl" % "0.3" % "test" // ApacheV2
val commonsMath = "org.apache.commons" % "commons-math" % "2.1" % "test" // ApacheV2
- val commonsIo = "commons-io" % "commons-io" % "2.0.1" % "test"// ApacheV2
+ val commonsIo = "commons-io" % "commons-io" % "2.0.1" % "test"// ApacheV2
val junit = "junit" % "junit" % "4.5" % "test" // Common Public License 1.0
val logback = "ch.qos.logback" % "logback-classic" % V.Logback % "test" // EPL 1.0 / LGPL 2.1
val mockito = "org.mockito" % "mockito-all" % "1.8.1" % "test" // MIT
+ val pojosr = "com.googlecode.pojosr" % "de.kalpatec.pojosr.framework" % "0.1.4" % "test" // ApacheV2
val scalatest = "org.scalatest" % "scalatest_2.9.1" % V.Scalatest % "test" // ApacheV2
val scalacheck = "org.scala-tools.testing" % "scalacheck_2.9.1" % "1.9" % "test" // New BSD
val specs2 = "org.specs2" % "specs2_2.9.1" % "1.9" % "test" // Modified BSD / ApacheV2
+ val tinybundles = "org.ops4j.pax.tinybundles" % "tinybundles" % "1.0.0" % "test" // ApacheV2
+ val log4j = "log4j" % "log4j" % "1.2.14" % "test" // ApacheV2
}
}
@@ -531,6 +560,14 @@ object OSGi {
val mailboxesCommon = exports(Seq("akka.actor.mailbox.*"))
+ val osgi = exports(Seq("akka.osgi")) ++ Seq(
+ OsgiKeys.privatePackage := Seq("akka.osgi.impl")
+ )
+
+ val osgiAries = exports() ++ Seq(
+ OsgiKeys.privatePackage := Seq("akka.osgi.aries.*")
+ )
+
val remote = exports(Seq("akka.remote.*", "akka.routing.*", "akka.serialization.*"))
val slf4j = exports(Seq("akka.event.slf4j.*"))
@@ -539,11 +576,12 @@ object OSGi {
val zeroMQ = exports(Seq("akka.zeromq.*"))
- def exports(packages: Seq[String]) = osgiSettings ++ Seq(
- OsgiKeys.importPackage := Seq("!sun.misc", akkaImport(), configImport(), scalaImport(), "*"),
+ def exports(packages: Seq[String] = Seq()) = osgiSettings ++ Seq(
+ OsgiKeys.importPackage := defaultImports,
OsgiKeys.exportPackage := packages
)
+ def defaultImports = Seq("!sun.misc", akkaImport(), configImport(), scalaImport(), "*")
def akkaImport(packageName: String = "akka.*") = "%s;version=\"[2.1,2.2)\"".format(packageName)
def configImport(packageName: String = "com.typesafe.config.*") = "%s;version=\"[0.4.1,0.5)\"".format(packageName)
def scalaImport(packageName: String = "scala.*") = "%s;version=\"[2.9.2,2.10)\"".format(packageName)