From 07dd65484922ca7328edf5b6027cfd0c04830157 Mon Sep 17 00:00:00 2001 From: Gert Vanthienen Date: Tue, 29 May 2012 12:41:09 +0200 Subject: [PATCH] Adding a convenience BundleActivator implementation to bootstrap Akka from an OSGi bundle --- .../akka/osgi/ActorSystemActivator.scala | 78 +++++++++++++++++++ .../akka/osgi/ActorSystemActivatorTest.scala | 75 ++++++++++++++++++ project/AkkaBuild.scala | 16 ++++ 3 files changed, 169 insertions(+) create mode 100644 akka-osgi/src/main/scala/akka/osgi/ActorSystemActivator.scala create mode 100644 akka-osgi/src/test/scala/akka/osgi/ActorSystemActivatorTest.scala 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..d63404334a --- /dev/null +++ b/akka-osgi/src/main/scala/akka/osgi/ActorSystemActivator.scala @@ -0,0 +1,78 @@ +package akka.osgi + +import com.typesafe.config.{ Config, ConfigFactory } +import akka.actor.ActorSystem +import org.osgi.framework.{ BundleContext, BundleActivator } +import java.util.Properties + +/** + * Abstract {@link BundleActivator} implementation to bootstrap and configure an {@link ActorSystem} in an + * OSGi environment. + */ +abstract class ActorSystemActivator extends BundleActivator { + + var system: ActorSystem = null + + /** + * Implement this method to add your own actors to the ActorSystem + * + * @param system the ActorSystem that was created by the activator + */ + def configure(system: ActorSystem) + + /** + * Sets up a new ActorSystem and registers it in the OSGi Service Registry + * + * @param context the BundleContext + */ + def start(context: BundleContext) { + system = createActorSystem(context) + configure(system) + + val properties = new Properties(); + properties.put("name", getActorSystemName(context)) + context.registerService(classOf[ActorSystem].getName, system, properties) + } + + /** + * Shuts down the ActorSystem when the bundle is stopped. + * + * @param context the BundleContext + */ + def stop(context: BundleContext) { + if (system != null) { + system.shutdown() + system.shutdown() + system = null + } + } + + /** + * Strategy method to create the ActorSystem. + */ + def createActorSystem(context: BundleContext) = + ActorSystem(getActorSystemName(context), getActorSystemConfig(context), getClass.getClassLoader) + + + /** + * Strategy method to create the Config for the ActorSystem, ensuring that the default/reference configuration is + * loaded from the akka-actor bundle. + */ + def getActorSystemConfig(context: BundleContext): Config = { + val reference = ConfigFactory.defaultReference(classOf[ActorSystem].getClassLoader) + ConfigFactory.load(getClass.getClassLoader).withFallback(reference) + } + + /** + * Strategy method to determine the ActorSystem name - override this method to define the ActorSytem name yourself. + * + * The default implementation will use 'bundle--ActorSystem' where matches the bundle id for the containing bundle. + * + * @param context the BundleContext + * @return the ActorSystem name + */ + def getActorSystemName(context: BundleContext): String = { + "bundle-%s-ActorSystem".format(context.getBundle().getBundleId) + } + +} 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..ffcc3cc0e7 --- /dev/null +++ b/akka-osgi/src/test/scala/akka/osgi/ActorSystemActivatorTest.scala @@ -0,0 +1,75 @@ +package akka.osgi + +import java.util.{ ServiceLoader, HashMap } +import de.kalpatec.pojosr.framework.launch.{ ClasspathScanner, PojoServiceRegistryFactory } +import org.scalatest.FlatSpec +import org.osgi.framework.BundleContext +import akka.actor.{ Actor, Props, ActorSystem } +import akka.pattern.ask +import akka.dispatch.Await +import akka.util.duration._ +import akka.util.Timeout + +/** + * Test cases for {@link ActorSystemActivator} + */ +class ActorSystemActivatorTest extends FlatSpec { + + abstract class TestMessage + + case object Ping extends TestMessage + case object Pong extends TestMessage + + class PongActor extends Actor { + def receive = { + case Ping ⇒ + sender ! Pong + } + } + + lazy val context: BundleContext = { + val config = new HashMap[String, AnyRef](); + config.put(PojoServiceRegistryFactory.BUNDLE_DESCRIPTORS, new ClasspathScanner().scanForBundles()); + + val loader = ServiceLoader.load(classOf[PojoServiceRegistryFactory]); + + val registry = loader.iterator().next().newPojoServiceRegistry(config); + registry.getBundleContext + } + + val activator = new ActorSystemActivator { + def configure(system: ActorSystem) { + system.actorOf(Props(new PongActor), name = "pong") + } + } + + "ActorSystemActivator" should "start and register the ActorSystem on start" in { + + activator.start(context) + + val reference = context.getServiceReference(classOf[ActorSystem].getName) + assert(reference != null) + + val system = context.getService(reference).asInstanceOf[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) + } + + it should "stop the ActorSystem on bundle stop" in { + val reference = context.getServiceReference(classOf[ActorSystem].getName) + assert(reference != null) + + val system = context.getService(reference).asInstanceOf[ActorSystem] + assert(!system.isTerminated) + + activator.stop(context) + + system.awaitTermination() + assert(system.isTerminated) + } + +} diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index dbe9fbae9e..f0e6446879 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -209,6 +209,15 @@ 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 akkaSbtPlugin = Project( id = "akka-sbt-plugin", base = file("akka-sbt-plugin"), @@ -419,6 +428,8 @@ object Dependencies { val camel = Seq(camelCore, Test.scalatest, Test.junit, Test.mockito) + val osgi = Seq(osgiCore, Test.pojosr, Test.scalatest, Test.junit) + val tutorials = Seq(Test.scalatest, Test.junit) val docs = Seq(Test.scalatest, Test.junit, Test.specs2) @@ -434,6 +445,7 @@ object Dependency { val Camel = "2.8.0" val Logback = "0.9.28" val Netty = "3.3.0.Final" + val OSGi = "4.2.0" val Protobuf = "2.4.1" val ScalaStm = "0.5" val Scalatest = "1.6.1" @@ -444,6 +456,7 @@ object Dependency { 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 @@ -463,6 +476,7 @@ object Dependency { 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.8" % "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 @@ -487,6 +501,8 @@ object OSGi { val mailboxesCommon = exports(Seq("akka.actor.mailbox.*")) + val osgi = exports(Seq("akka.osgi.*")) + val remote = exports(Seq("akka.remote.*", "akka.routing.*", "akka.serialization.*")) val slf4j = exports(Seq("akka.event.slf4j.*"))