diff --git a/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java b/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java new file mode 100644 index 0000000000..edddc56a95 --- /dev/null +++ b/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.actor; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class JavaExtension { + + static class TestExtension implements Extension { + private ActorSystemImpl system; + public static ExtensionKey key = new ExtensionKey() {}; + + public ExtensionKey init(ActorSystemImpl system) { + this.system = system; + return key; + } + + public ActorSystemImpl getSystem() { + return system; + } + } + + private ActorSystem system = ActorSystem.create("JavaExtension", ActorSystemSpec.javaconfig()); + + @Test + public void mustBeAccessible() { + final ActorSystemImpl s = system.extension(TestExtension.key).getSystem(); + assertSame(s, system); + } + +} diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorSystemSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorSystemSpec.scala new file mode 100644 index 0000000000..ebd98809e6 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorSystemSpec.scala @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.actor + +import akka.testkit._ +import akka.config.Configuration +import org.scalatest.junit.JUnitSuite + +class JavaExtensionSpec extends JavaExtension with JUnitSuite + +object ActorSystemSpec { + + // do not remove, needed for JavaExtension test + val javaconfig = Configuration("akka.extensions" -> Seq("akka.actor.JavaExtension$TestExtension")) + + case class TestExtension extends Extension[TestExtension] { + var system: ActorSystemImpl = _ + + def init(system: ActorSystemImpl): ExtensionKey[TestExtension] = { + this.system = system + TestExtension + } + } + + object TestExtension extends ExtensionKey[TestExtension] + +} + +class ActorSystemSpec extends AkkaSpec(Configuration("akka.extensions" -> Seq("akka.actor.ActorSystemSpec$TestExtension"))) { + import ActorSystemSpec._ + + "An ActorSystem" must { + + "support extensions" in { + system.extension(TestExtension).system must be === system + } + + } + +} \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index b4416ae5fb..77aee25380 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -16,6 +16,7 @@ import akka.serialization.Serialization import akka.remote.RemoteAddress import org.jboss.netty.akka.util.HashedWheelTimer import java.util.concurrent.{ Executors, TimeUnit } +import java.util.concurrent.ConcurrentHashMap object ActorSystem { @@ -163,9 +164,12 @@ abstract class ActorSystem extends ActorRefFactory with TypedActorFactory { def registerOnTermination(code: ⇒ Unit) def registerOnTermination(code: Runnable) def stop() + + def registerExtension(ext: Extension[_ <: AnyRef]) + def extension[T <: AnyRef](key: ExtensionKey[T]): T } -class ActorSystemImpl(val name: String, config: Configuration) extends ActorSystem { +class ActorSystemImpl private[actor] (val name: String, config: Configuration) extends ActorSystem { import ActorSystem._ @@ -258,6 +262,7 @@ class ActorSystemImpl(val name: String, config: Configuration) extends ActorSyst // this starts the reaper actor and the user-configured logging subscribers, which are also actors eventStream.start(this) eventStream.startDefaultLoggers(this) + loadExtensions() this } @@ -273,4 +278,28 @@ class ActorSystemImpl(val name: String, config: Configuration) extends ActorSyst terminationFuture onComplete (_ ⇒ dispatcher.shutdown()) } + private val extensions = new ConcurrentHashMap[ExtensionKey[_], Extension[_]] + + def registerExtension(ext: Extension[_ <: AnyRef]) { + val key = ext.init(this) + extensions.put(key, ext) match { + case null ⇒ + case old ⇒ log.warning("replacing extension {}:{} with {}", key, old, ext) + } + } + + def extension[T <: AnyRef](key: ExtensionKey[T]): T = extensions.get(key) match { + case null ⇒ throw new NullPointerException("trying to get non-registered extension " + key) + case x ⇒ x.asInstanceOf[T] + } + + private def loadExtensions() { + config.getList("akka.extensions") foreach { fqcn ⇒ + import ReflectiveAccess._ + createInstance[Extension[_ <: AnyRef]](fqcn, noParams, noArgs) match { + case Left(ex) ⇒ log.error(ex, "Exception trying to load extension " + fqcn) + case Right(ext) ⇒ if (ext.isInstanceOf[Extension[_]]) registerExtension(ext) else log.error("Class {} is not an Extension", fqcn) + } + } + } } diff --git a/akka-actor/src/main/scala/akka/actor/Deployer.scala b/akka-actor/src/main/scala/akka/actor/Deployer.scala index 7a2ad320b6..82936a2d9d 100644 --- a/akka-actor/src/main/scala/akka/actor/Deployer.scala +++ b/akka-actor/src/main/scala/akka/actor/Deployer.scala @@ -98,7 +98,7 @@ class Deployer(val settings: ActorSystem.Settings, val eventStream: EventStream, * Lookup deployment in 'akka.conf' configuration file. */ private[akka] def lookupInConfig(path: String, configuration: Configuration = settings.config): Option[Deploy] = { - import akka.util.ReflectiveAccess.{ createInstance, emptyArguments, emptyParams, getClassFor } + import akka.util.ReflectiveAccess.getClassFor // -------------------------------- // akka.actor.deployment. diff --git a/akka-actor/src/main/scala/akka/actor/Extension.scala b/akka-actor/src/main/scala/akka/actor/Extension.scala new file mode 100644 index 0000000000..55da369a5d --- /dev/null +++ b/akka-actor/src/main/scala/akka/actor/Extension.scala @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.actor + +/** + * The basic ActorSystem covers all that is needed for locally running actors, + * using futures and so on. In addition, more features can hook into it and + * thus become visible to actors et al by registering themselves as extensions. + * This is accomplished by providing an extension—which is an object + * implementing this trait—to `ActorSystem.registerExtension(...)` or by + * specifying the corresponding option in the configuration passed to + * ActorSystem, which will then instantiate (without arguments) each FQCN and + * register the result. + * + * The extension itself can be created in any way desired and has full access + * to the ActorSystem implementation. + * + * Scala example: + * + * {{{ + * class MyExtension extends Extension[MyExtension] { + * def init(system: ActorSystemImpl): ExtensionKey[MyExtension] = { + * ... // initialize here + * MyExtension + * } + * } + * object MyExtension extends ExtensionKey[MyExtension] + * }}} + * + * Java exmple: + * + * {{{ + * static class MyExtension implements Extension { + * public static ExtensionKey key = new ExtensionKey() {}; + * + * public ExtensionKey init(ActorSystemImpl system) { + * ... // initialize here + * return key; + * } + * } + * }}} + */ +trait Extension[T <: AnyRef] { + /** + * This method is called by the ActorSystem upon registering this extension. + * The key returned is used for looking up extensions, hence it must be a + * suitable hash key and available to all clients of the extension. This is + * best achieved by storing it in a static field (Java) or as/in an object + * (Scala). + */ + def init(system: ActorSystemImpl): ExtensionKey[T] +} + +/** + * Marker trait identifying a registered [[akka.actor.Extension]]. + */ +trait ExtensionKey[T <: AnyRef] diff --git a/akka-actor/src/main/scala/akka/serialization/Serialization.scala b/akka-actor/src/main/scala/akka/serialization/Serialization.scala index 9973b11a17..90a819fa52 100644 --- a/akka-actor/src/main/scala/akka/serialization/Serialization.scala +++ b/akka-actor/src/main/scala/akka/serialization/Serialization.scala @@ -46,7 +46,7 @@ class Serialization(val system: ActorSystemImpl) { * Tries to load the specified Serializer by the FQN */ def serializerOf(serializerFQN: String): Either[Exception, Serializer] = - ReflectiveAccess.createInstance(serializerFQN, ReflectiveAccess.emptyParams, ReflectiveAccess.emptyArguments) + ReflectiveAccess.createInstance(serializerFQN, ReflectiveAccess.noParams, ReflectiveAccess.noArgs) private def serializerForBestMatchClass(cl: Class[_]): Either[Exception, Serializer] = { if (bindings.isEmpty) diff --git a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala index d331190de6..e2f667527f 100644 --- a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala +++ b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala @@ -16,11 +16,8 @@ import akka.actor.ActorSystem object ReflectiveAccess { val loader = getClass.getClassLoader - val emptyParams: Array[Class[_]] = Array() - val emptyArguments: Array[AnyRef] = Array() - - val noParams = Array[Class[_]]() - val noArgs = Array[AnyRef]() + val noParams: Array[Class[_]] = Array() + val noArgs: Array[AnyRef] = Array() def createInstance[T](clazz: Class[_], params: Array[Class[_]],