diff --git a/.gitignore b/.gitignore index b92e37e421..6280a4415b 100755 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,7 @@ src_managed storage tags target +target-sbt tm*.lck tm*.log tm.out diff --git a/akka-docs/rst/additional/osgi.rst b/akka-docs/rst/additional/osgi.rst index a87590f341..63b903f35e 100644 --- a/akka-docs/rst/additional/osgi.rst +++ b/akka-docs/rst/additional/osgi.rst @@ -1,14 +1,6 @@ Akka in OSGi ============ -In an OSGi environment the ``akka-osgi`` bundle replaces ``akka-actor`` artifact. It includes all classes from ``akka-actor`` and merged ``reference.conf`` files from all akka modules. The dependency is:: - - - com.typesafe.akka - akka-osgi_@binVersion@ - @version@ - - Configuring the OSGi Framework ------------------------------ @@ -19,26 +11,20 @@ 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 bootstrap Akka inside an OSGi environment, you can use the ``akka.osgi.ActorSystemActivator`` class to conveniently set up the ActorSystem. .. includecode:: code/osgi/Activator.scala#Activator -The ``AkkaSystemActivator`` class is included in the ``akka-osgi`` artifact. -Blueprint ---------- +The ``ActorSystemActivator`` creates the actor system with a class loader that finds resources +(``reference.conf`` files) and classes from the application bundle and all transitive dependencies. -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 - -The blueprint is included in the ``akka-osgi-aries`` artifact:: +The ``ActorSystemActivator`` class is included in the ``akka-osgi`` artifact. com.typesafe.akka - akka-osgi-aries_@binVersion@ + akka-osgi_@binVersion@ @version@ diff --git a/akka-docs/rst/project/migration-guide-2.2.x-2.3.x.rst b/akka-docs/rst/project/migration-guide-2.2.x-2.3.x.rst index 47b2d8f98f..1fee3fc72b 100644 --- a/akka-docs/rst/project/migration-guide-2.2.x-2.3.x.rst +++ b/akka-docs/rst/project/migration-guide-2.2.x-2.3.x.rst @@ -245,4 +245,9 @@ ReliableProxy Constructor Changed The constructor of ``ReliableProxy`` in ``akka-contrib`` has been changed to take an ``ActorPath`` instead of an ``ActorRef``. Also it takes new parameters to support reconnection. Use the new props factory methods, ``ReliableProxy.props``. + +Akka OSGi Aries Blueprint is Removed +==================================== + +``akka-osgi-aries`` has been removed. Similar can be implemented outside of Akka if needed. \ No newline at end of file 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 deleted file mode 100644 index 99492bedf2..0000000000 --- a/akka-osgi-aries/src/main/resources/OSGI-INF/blueprint/akka-namespacehandler.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - 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 deleted file mode 100644 index d7d0f77a2c..0000000000 --- a/akka-osgi-aries/src/main/resources/akka/osgi/aries/blueprint/akka.xsd +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - 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 deleted file mode 100644 index c09dfe6984..0000000000 --- a/akka-osgi-aries/src/main/scala/akka/osgi/aries/blueprint/BlueprintActorSystemFactory.scala +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (C) 2009-2013 Typesafe Inc. - */ -package akka.osgi.aries.blueprint - -import org.osgi.framework.BundleContext -import akka.osgi.OsgiActorSystemFactory -import akka.actor.ActorSystem -import com.typesafe.config.{ 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, fallbackClassLoader: Option[ClassLoader]) extends OsgiActorSystemFactory(context, fallbackClassLoader) { - - def this(context: BundleContext, name: String) = this(context, name, Some(OsgiActorSystemFactory.akkaActorClassLoader)) - - var config: Option[String] = None - - lazy val system: ActorSystem = super.createActorSystem(if (name == null || name.isEmpty) None else Some(name)) - - def setConfig(config: String): Unit = this.config = Some(config) - - def create(): ActorSystem = system - - def destroy(): Unit = system.shutdown() - - /** - * 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 = - 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 deleted file mode 100644 index 2109a1e01b..0000000000 --- a/akka-osgi-aries/src/main/scala/akka/osgi/aries/blueprint/NamespaceHandler.scala +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright (C) 2009-2013 Typesafe Inc. - */ -package akka.osgi.aries.blueprint - -import org.osgi.framework.BundleContext -import org.osgi.service.blueprint.container.ComponentDefinitionException -import org.osgi.service.blueprint.reflect.{ BeanMetadata, ComponentMetadata } -import org.apache.aries.blueprint.ParserContext -import org.apache.aries.blueprint.mutable.MutableBeanMetadata -import org.apache.aries.blueprint.reflect.{ ValueMetadataImpl, RefMetadataImpl, BeanArgumentImpl } -import org.w3c.dom.{ Element, Node } -import java.util.concurrent.atomic.AtomicInteger - -import akka.actor.ActorSystem -import scala.annotation.tailrec -import java.net.URL - -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" -} - -/** - * 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._ - - protected val idCounter = new AtomicInteger(0) - - override def getSchemaLocation(namespace: String): URL = getClass().getResource("akka.xsd") - - override def getManagedClasses = java.util.Collections.singleton(classOf[BlueprintActorSystemFactory]) - - override def parse(element: Element, context: ParserContext) = - if (element.getLocalName == ACTORSYSTEM_ELEMENT_NAME) parseActorSystem(element, context) - else throw new ComponentDefinitionException("Unexpected element for Akka namespace: %s".format(element)) - - override 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)) - - val nodelist = element.getChildNodes - (0 until nodelist.getLength) collect { - case idx if nodelist.item(idx).getNodeType == Node.ELEMENT_NODE ⇒ nodelist.item(idx).asInstanceOf[Element] - } foreach { - case child if child.getLocalName == CONFIG_ELEMENT_NAME ⇒ parseConfig(child, context, factory) - case child ⇒ throw new ComponentDefinitionException("Unexpected child element %s found in %s".format(child, element)) - } - - createActorSystemBean(context, element, factory) - } - - /* - * Parse - */ - protected def parseConfig(node: Element, context: ParserContext, factory: MutableBeanMetadata) = - factory.addProperty(CONFIG_ELEMENT_NAME, new ValueMetadataImpl(node.getTextContent)) - - @tailrec protected final def findAvailableId(context: ParserContext): String = - ".akka-" + idCounter.incrementAndGet() match { - case id if context.getComponentDefinitionRegistry.containsComponentDefinition(id) ⇒ findAvailableId(context) - case available ⇒ available - } - - /* - * Create the bean definition for the ActorSystem - */ - protected def createActorSystemBean(context: ParserContext, element: Element, factory: MutableBeanMetadata): MutableBeanMetadata = { - val system = context.createMetadata(classOf[MutableBeanMetadata]) - val id = if (element.hasAttribute(ID_ATTRIBUTE)) element.getAttribute(ID_ATTRIBUTE) else findAvailableId(context) - - system.setId(id) - system.setFactoryComponent(factory) - - system.setFactoryMethod(FACTORY_METHOD_NAME) - system.setRuntimeClass(classOf[ActorSystem]) - system - } - - /* - * Create the bean definition for the BlueprintActorSystemFactory - */ - protected 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 - } -} \ No newline at end of file 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 deleted file mode 100644 index ce9f48c551..0000000000 --- a/akka-osgi-aries/src/test/resources/akka/osgi/aries/blueprint/config.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - 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 deleted file mode 100644 index 6fd21db5ef..0000000000 --- a/akka-osgi-aries/src/test/resources/akka/osgi/aries/blueprint/injection.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - 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 deleted file mode 100644 index 2ac6552f80..0000000000 --- a/akka-osgi-aries/src/test/resources/akka/osgi/aries/blueprint/simple.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - 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 deleted file mode 100644 index a7c9f5c08a..0000000000 --- a/akka-osgi-aries/src/test/scala/akka/osgi/aries/blueprint/ActorSystemAwareBean.scala +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (C) 2009-2013 Typesafe Inc. - */ -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 deleted file mode 100644 index 100cea4db1..0000000000 --- a/akka-osgi-aries/src/test/scala/akka/osgi/aries/blueprint/NamespaceHandlerTest.scala +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright (C) 2009-2013 Typesafe Inc. - */ -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 - -/** - * 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 Matchers with PojoSRTestSupport { - - import NamespaceHandlerTest._ - - val testBundles = buildTestBundles(List( - AKKA_OSGI_BLUEPRINT, - bundle(TEST_BUNDLE_NAME).withBlueprintFile(getClass.getResource("simple.xml")))) - - "simple.xml" must { - "set up ActorSystem when bundle starts" in { - filterErrors() { - serviceForType[ActorSystem] should not be (null) - } - } - - "stop the ActorSystem when bundle stops" in { - filterErrors() { - val system = serviceForType[ActorSystem] - system.isTerminated should be(false) - - bundleForName(TEST_BUNDLE_NAME).stop() - - system.awaitTermination() - system.isTerminated should be(true) - } - } - } - -} - -class ConfigNamespaceHandlerTest extends WordSpec with Matchers with PojoSRTestSupport { - - import NamespaceHandlerTest._ - - val testBundles = buildTestBundles(List( - AKKA_OSGI_BLUEPRINT, - bundle(TEST_BUNDLE_NAME).withBlueprintFile(getClass.getResource("config.xml")))) - - "config.xml" must { - "set up ActorSystem when bundle starts" in { - filterErrors() { - val system = serviceForType[ActorSystem] - system should not be (null) - system.settings.config.getString("some.config.key") should be("value") - } - } - - "stop the ActorSystem when bundle stops" in { - filterErrors() { - val system = serviceForType[ActorSystem] - system.isTerminated should be(false) - - bundleForName(TEST_BUNDLE_NAME).stop() - - system.awaitTermination() - system.isTerminated should be(true) - } - } - } - -} - -class DependencyInjectionNamespaceHandlerTest extends WordSpec with Matchers with PojoSRTestSupport { - - import NamespaceHandlerTest._ - - val testBundles = buildTestBundles(List( - AKKA_OSGI_BLUEPRINT, - bundle(TEST_BUNDLE_NAME).withBlueprintFile(getClass.getResource("injection.xml")))) - - "injection.xml" must { - - "set up bean containing ActorSystem" in { - filterErrors() { - val bean = serviceForType[ActorSystemAwareBean] - bean should not be (null) - bean.system should not be (null) - } - } - } - -} diff --git a/akka-osgi/src/main/scala/akka/osgi/ActorSystemActivator.scala b/akka-osgi/src/main/scala/akka/osgi/ActorSystemActivator.scala index 37fa8293e2..7c0b94b9cf 100644 --- a/akka-osgi/src/main/scala/akka/osgi/ActorSystemActivator.scala +++ b/akka-osgi/src/main/scala/akka/osgi/ActorSystemActivator.scala @@ -21,7 +21,7 @@ import com.typesafe.config.{ ConfigFactory, Config } abstract class ActorSystemActivator extends BundleActivator { private var system: Option[ActorSystem] = None - private var registration: Option[ServiceRegistration] = 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 @@ -72,7 +72,7 @@ abstract class ActorSystemActivator extends BundleActivator { /** * Convenience method to find a service by its reference. */ - def serviceForReference[T](context: BundleContext, reference: ServiceReference): T = + def serviceForReference[T](context: BundleContext, reference: ServiceReference[_]): T = context.getService(reference).asInstanceOf[T] /** diff --git a/akka-osgi/src/main/scala/akka/osgi/BundleDelegatingClassLoader.scala b/akka-osgi/src/main/scala/akka/osgi/BundleDelegatingClassLoader.scala new file mode 100644 index 0000000000..762dc15945 --- /dev/null +++ b/akka-osgi/src/main/scala/akka/osgi/BundleDelegatingClassLoader.scala @@ -0,0 +1,95 @@ +/** + * Copyright (C) 2009-2013 Typesafe Inc. + */ +package akka.osgi + +import language.existentials +import java.net.URL +import java.util.Enumeration +import org.osgi.framework.{ BundleContext, Bundle } +import scala.util.Try +import scala.io.Source +import org.osgi.framework.wiring.{ BundleRevision, BundleWire, BundleWiring } +import scala.collection.JavaConverters._ +import scala.util.Success +import scala.util.Failure +import scala.annotation.tailrec + +/* + * Companion object to create bundle delegating ClassLoader instances + */ +object BundleDelegatingClassLoader { + + /* + * Create a bundle delegating ClassLoader for the bundle context's bundle + */ + def apply(context: BundleContext): BundleDelegatingClassLoader = new BundleDelegatingClassLoader(context.getBundle, null) + + def apply(context: BundleContext, fallBackCLassLoader: Option[ClassLoader]): BundleDelegatingClassLoader = + new BundleDelegatingClassLoader(context.getBundle, fallBackCLassLoader.orNull) +} + +/* + * A bundle delegating ClassLoader implementation - this will try to load classes and resources from the bundle + * and the bundles transitive dependencies. If there's a ClassLoader specified, that will be used as a fallback. + */ +class BundleDelegatingClassLoader(bundle: Bundle, fallBackClassLoader: ClassLoader) extends ClassLoader(fallBackClassLoader) { + + private val bundles = findTransitiveBundles(bundle).toList + + override def findClass(name: String): Class[_] = { + @tailrec def find(remaining: List[Bundle]): Class[_] = { + if (remaining.isEmpty) throw new ClassNotFoundException(name) + else Try { remaining.head.loadClass(name) } match { + case Success(cls) ⇒ cls + case Failure(_) ⇒ find(remaining.tail) + } + } + find(bundles) + } + + override def findResource(name: String): URL = { + @tailrec def find(remaining: List[Bundle]): URL = { + if (remaining.isEmpty) getParent.getResource(name) + else Option { remaining.head.getResource(name) } match { + case Some(r) ⇒ r + case None ⇒ find(remaining.tail) + } + } + find(bundles) + } + + override def findResources(name: String): Enumeration[URL] = { + val resources = bundles.flatMap { + bundle ⇒ Option(bundle.getResources(name)).map { _.asScala.toList }.getOrElse(Nil) + } + java.util.Collections.enumeration(resources.asJava) + } + + private def findTransitiveBundles(bundle: Bundle): Set[Bundle] = { + @tailrec def process(processed: Set[Bundle], remaining: Set[Bundle]): Set[Bundle] = { + if (remaining.isEmpty) { + processed + } else { + val (b, rest) = (remaining.head, remaining.tail) + if (processed contains b) { + process(processed, rest) + } else { + val wiring = b.adapt(classOf[BundleWiring]) + val direct: Set[Bundle] = + if (wiring == null) Set.empty + else { + val requiredWires: List[BundleWire] = + wiring.getRequiredWires(BundleRevision.PACKAGE_NAMESPACE).asScala.toList + requiredWires.flatMap { + wire ⇒ Option(wire.getProviderWiring) map { _.getBundle } + }.toSet + } + process(processed + b, rest ++ (direct -- processed)) + } + } + } + process(Set.empty, Set(bundle)) + } +} + diff --git a/akka-osgi/src/main/scala/akka/osgi/OsgiActorSystemFactory.scala b/akka-osgi/src/main/scala/akka/osgi/OsgiActorSystemFactory.scala index 7e434671b2..7210583d69 100644 --- a/akka-osgi/src/main/scala/akka/osgi/OsgiActorSystemFactory.scala +++ b/akka-osgi/src/main/scala/akka/osgi/OsgiActorSystemFactory.scala @@ -3,7 +3,6 @@ */ package akka.osgi -import impl.BundleDelegatingClassLoader import akka.actor.ActorSystem import com.typesafe.config.{ ConfigFactory, Config } import org.osgi.framework.BundleContext @@ -18,7 +17,7 @@ class OsgiActorSystemFactory(val context: BundleContext, val fallbackClassLoader /* * Classloader that delegates to the bundle for which the factory is creating an ActorSystem */ - private val classloader = new BundleDelegatingClassLoader(context.getBundle, fallbackClassLoader) + private val classloader = BundleDelegatingClassLoader(context, fallbackClassLoader) /** * Creates the [[akka.actor.ActorSystem]], using the name specified @@ -55,7 +54,7 @@ object OsgiActorSystemFactory { /** * Class loader of akka-actor bundle. */ - def akkaActorClassLoader = classOf[ActorSystem].getClassLoader + def akkaActorClassLoader = classOf[ActorSystemActivator].getClassLoader /* * Create an [[OsgiActorSystemFactory]] instance to set up Akka in an OSGi environment diff --git a/akka-osgi/src/main/scala/akka/osgi/impl/BundleDelegatingClassLoader.scala b/akka-osgi/src/main/scala/akka/osgi/impl/BundleDelegatingClassLoader.scala deleted file mode 100644 index 512cd68509..0000000000 --- a/akka-osgi/src/main/scala/akka/osgi/impl/BundleDelegatingClassLoader.scala +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (C) 2009-2013 Typesafe Inc. - */ -package akka.osgi.impl - -import language.existentials - -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 apply(context: BundleContext): BundleDelegatingClassLoader = 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 = - bundle.getResource(name) match { - case null if classLoader.isDefined ⇒ classLoader.get.getResource(name) - case result ⇒ result - } - - @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 { - try findClass(name) catch { case _: ClassNotFoundException if classLoader.isDefined ⇒ classLoader.get.loadClass(name) } // First fall back to secondary loader - } catch { - case cnfe: ClassNotFoundException ⇒ - throw new ClassNotFoundException("%s from bundle %s (%s)".format(name, bundle.getBundleId, bundle.getSymbolicName), cnfe) // IF we have no secondary loader or that failed as well, wrap and rethrow - } - - if (resolve) - resolveClass(clazz) - - clazz - } - - override val toString: String = "BundleDelegatingClassLoader(%s)".format(bundle.getBundleId) -} - diff --git a/akka-osgi/src/test/scala/akka/osgi/ActorSystemActivatorTest.scala b/akka-osgi/src/test/scala/akka/osgi/ActorSystemActivatorTest.scala index 2730b8f418..b8e3f2b497 100644 --- a/akka-osgi/src/test/scala/akka/osgi/ActorSystemActivatorTest.scala +++ b/akka-osgi/src/test/scala/akka/osgi/ActorSystemActivatorTest.scala @@ -41,7 +41,7 @@ class PingPongActorSystemActivatorTest extends WordSpec with Matchers with PojoS "start and register the ActorSystem when bundle starts" in { filterErrors() { val system = serviceForType[ActorSystem] - val actor = system.actorFor("/user/pong") + val actor = system.actorSelection("/user/pong") implicit val timeout = Timeout(5 seconds) Await.result(actor ? Ping, timeout.duration) should be(Pong) diff --git a/akka-osgi/src/test/scala/akka/osgi/PojoSRTestSupport.scala b/akka-osgi/src/test/scala/akka/osgi/PojoSRTestSupport.scala index 08871d6164..9d89e5b490 100644 --- a/akka-osgi/src/test/scala/akka/osgi/PojoSRTestSupport.scala +++ b/akka-osgi/src/test/scala/akka/osgi/PojoSRTestSupport.scala @@ -70,18 +70,18 @@ trait PojoSRTestSupport extends Suite with BeforeAndAfterAll { def serviceForType[T](implicit t: ClassTag[T]): T = context.getService(awaitReference(t.runtimeClass)).asInstanceOf[T] - def awaitReference(serviceType: Class[_]): ServiceReference = awaitReference(serviceType, SleepyTime) + def awaitReference[T](serviceType: Class[T]): ServiceReference[T] = awaitReference(serviceType, SleepyTime) - def awaitReference(serviceType: Class[_], wait: FiniteDuration): ServiceReference = { + def awaitReference[T](serviceType: Class[T], wait: FiniteDuration): ServiceReference[T] = { - @tailrec def poll(step: Duration, deadline: Deadline): ServiceReference = context.getServiceReference(serviceType.getName) match { + @tailrec def poll(step: Duration, deadline: Deadline): ServiceReference[T] = context.getServiceReference(serviceType.getName) match { case null ⇒ if (deadline.isOverdue()) fail("Gave up waiting for service of type %s".format(serviceType)) else { Thread.sleep((step min deadline.timeLeft max Duration.Zero).toMillis) poll(step, deadline) } - case some ⇒ some + case some ⇒ some.asInstanceOf[ServiceReference[T]] } poll(wait, Deadline.now + MaxWaitDuration) diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/api/pom.xml b/akka-samples/akka-sample-osgi-dining-hakkers/api/pom.xml index 0d34113d53..931640172d 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/api/pom.xml +++ b/akka-samples/akka-sample-osgi-dining-hakkers/api/pom.xml @@ -5,7 +5,7 @@ com.typesafe.akka.akka-sample-osgi-dining-hakkers project - 2.2.0-SNAPSHOT + 2.3-SNAPSHOT 4.0.0 diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/api/src/main/scala/akka/sample/osgi/api/DiningHakkersMessages.scala b/akka-samples/akka-sample-osgi-dining-hakkers/api/src/main/scala/akka/sample/osgi/api/DiningHakkersMessages.scala index 2fb5625f42..d21d99d867 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/api/src/main/scala/akka/sample/osgi/api/DiningHakkersMessages.scala +++ b/akka-samples/akka-sample-osgi-dining-hakkers/api/src/main/scala/akka/sample/osgi/api/DiningHakkersMessages.scala @@ -8,7 +8,7 @@ import akka.actor.ActorRef /* * Define our messages, they basically speak for themselves */ -sealed trait DiningHakkerMessage +sealed trait DiningHakkerMessage extends Serializable case class Busy(chopstick: ActorRef) extends DiningHakkerMessage @@ -18,10 +18,20 @@ case class Take(hakker: ActorRef) extends DiningHakkerMessage case class Taken(chopstick: ActorRef) extends DiningHakkerMessage -object Eat extends DiningHakkerMessage +case object Eat extends DiningHakkerMessage -object Think extends DiningHakkerMessage +case object Think extends DiningHakkerMessage -object Identify extends DiningHakkerMessage +case object Identify extends DiningHakkerMessage case class Identification(name: String, busyWith: String) extends DiningHakkerMessage + +case object SubscribeToHakkerStateChanges extends DiningHakkerMessage + +case class HakkerStateChange(hakkerName: String, from: String, to: String) + +case class TrackHakker(hakker: ActorRef) extends DiningHakkerMessage + +case class GetEatingCount(hakkerName: String) extends DiningHakkerMessage + +case class EatingCount(hakkerName: String, count: Int) extends DiningHakkerMessage diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/api/src/main/scala/akka/sample/osgi/api/DiningHakkersService.scala b/akka-samples/akka-sample-osgi-dining-hakkers/api/src/main/scala/akka/sample/osgi/api/DiningHakkersService.scala index 2b61699f66..9e6e5c070b 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/api/src/main/scala/akka/sample/osgi/api/DiningHakkersService.scala +++ b/akka-samples/akka-sample-osgi-dining-hakkers/api/src/main/scala/akka/sample/osgi/api/DiningHakkersService.scala @@ -19,4 +19,5 @@ import akka.actor.ActorRef trait DiningHakkersService { def getHakker(name: String, chairNumber: Int): ActorRef + def getTracker(): ActorRef } diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/assembly-dist/pom.xml b/akka-samples/akka-sample-osgi-dining-hakkers/assembly-dist/pom.xml index 846db0fde1..7494df3892 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/assembly-dist/pom.xml +++ b/akka-samples/akka-sample-osgi-dining-hakkers/assembly-dist/pom.xml @@ -7,7 +7,7 @@ com.typesafe.akka.akka-sample-osgi-dining-hakkers project - 2.2.0-SNAPSHOT + 2.3-SNAPSHOT akka-sample-osgi-dining-hakkers-dist diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/assembly-features/pom.xml b/akka-samples/akka-sample-osgi-dining-hakkers/assembly-features/pom.xml index 617fac5bb5..b383fb098d 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/assembly-features/pom.xml +++ b/akka-samples/akka-sample-osgi-dining-hakkers/assembly-features/pom.xml @@ -7,7 +7,7 @@ com.typesafe.akka.akka-sample-osgi-dining-hakkers project - 2.2.0-SNAPSHOT + 2.3-SNAPSHOT akka-sample-osgi-dining-hakkers diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/assembly-features/src/main/resources/features.xml b/akka-samples/akka-sample-osgi-dining-hakkers/assembly-features/src/main/resources/features.xml index 66606d2534..5a0f151a3e 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/assembly-features/src/main/resources/features.xml +++ b/akka-samples/akka-sample-osgi-dining-hakkers/assembly-features/src/main/resources/features.xml @@ -7,9 +7,10 @@ mvn:org.scala-lang/scala-library/${scala.version} + mvn:org.scala-lang/scala-reflect/${scala.version} - + mvn:com.typesafe.akka.akka-sample-osgi-dining-hakkers/uncommons/1.2.2 @@ -22,24 +23,32 @@ mvn:io.netty/netty/${netty.version} + + wrap:mvn:org.iq80.leveldb/leveldb/${leveldb.version} + wrap:mvn:org.fusesource.leveldbjni/leveldbjni-all/${leveldbjni.version} + + - scala mvn:com.typesafe/config/${typesafe.config.version} - scala - netty - uncommons-maths - protobuf - typesafe-config - mvn:com.typesafe.akka/akka-cluster_${scala.dep.version}/${akka.version} - mvn:com.typesafe.akka/akka-remote_${scala.dep.version}/${akka.version} + scala + netty + uncommons-maths + protobuf + typesafe-config + leveldb + mvn:com.typesafe.akka/akka-actor_${scala.dep.version}/${akka.version} mvn:com.typesafe.akka/akka-osgi_${scala.dep.version}/${akka.version} + mvn:com.typesafe.akka/akka-remote_${scala.dep.version}/${akka.version} + mvn:com.typesafe.akka/akka-cluster_${scala.dep.version}/${akka.version} + mvn:com.typesafe.akka/akka-persistence-experimental_${scala.dep.version}/${akka.version} + mvn:com.typesafe.akka/akka-slf4j_${scala.dep.version}/${akka.version} - akka + akka mvn:com.typesafe.akka.akka-sample-osgi-dining-hakkers/api/${project.version} mvn:com.typesafe.akka.akka-sample-osgi-dining-hakkers/core/${project.version} mvn:com.typesafe.akka.akka-sample-osgi-dining-hakkers/command/${project.version} diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/command/pom.xml b/akka-samples/akka-sample-osgi-dining-hakkers/command/pom.xml index 913c9ffb8c..cd3c190436 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/command/pom.xml +++ b/akka-samples/akka-sample-osgi-dining-hakkers/command/pom.xml @@ -5,7 +5,7 @@ com.typesafe.akka.akka-sample-osgi-dining-hakkers project - 2.2.0-SNAPSHOT + 2.3-SNAPSHOT 4.0.0 diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/command/src/main/scala/akka/sample/osgi/command/Activator.scala b/akka-samples/akka-sample-osgi-dining-hakkers/command/src/main/scala/akka/sample/osgi/command/Activator.scala index 1b6e7c9887..253ed0b5b3 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/command/src/main/scala/akka/sample/osgi/command/Activator.scala +++ b/akka-samples/akka-sample-osgi-dining-hakkers/command/src/main/scala/akka/sample/osgi/command/Activator.scala @@ -32,7 +32,7 @@ class Activator extends BundleActivator { } def startHakker(service: DiningHakkersService, name: String) { - hakker = Some(service.getHakker(name, (math.floor(math.random * 5)).toInt)) + hakker = Some(service.getHakker(name, 4)) } def stop(context: BundleContext) { diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/core/pom.xml b/akka-samples/akka-sample-osgi-dining-hakkers/core/pom.xml index 14a9c1c2fc..00c91b96cd 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/core/pom.xml +++ b/akka-samples/akka-sample-osgi-dining-hakkers/core/pom.xml @@ -5,7 +5,7 @@ com.typesafe.akka.akka-sample-osgi-dining-hakkers project - 2.2.0-SNAPSHOT + 2.3-SNAPSHOT 4.0.0 @@ -30,6 +30,14 @@ com.typesafe.akka akka-remote_${scala.dep.version} + + com.typesafe.akka + akka-slf4j_${scala.dep.version} + + + com.typesafe.akka + akka-persistence-experimental_${scala.dep.version} + com.typesafe config @@ -39,14 +47,6 @@ api - - - io.netty @@ -61,9 +61,12 @@ akka-cluster_${scala.dep.version} - org.fusesource - sigar - 1.6.4 + org.iq80.leveldb + leveldb + + + org.fusesource.leveldbjni + leveldbjni-all diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/resources/application.conf b/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/resources/application.conf index 1db639fc76..1466994073 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/resources/application.conf +++ b/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/resources/application.conf @@ -1,7 +1,19 @@ akka { + loggers = ["akka.event.slf4j.Slf4jLogger", "akka.event.Logging$DefaultLogger"] + actor { provider = "akka.cluster.ClusterActorRefProvider" + + serialize-messages = on + + serializers { + dining = "akka.sample.osgi.serialization.DiningHakkerSerializer" + } + + serialization-bindings { + "akka.sample.osgi.api.DiningHakkerMessage" = dining + } } remote { @@ -13,7 +25,7 @@ akka { cluster { seed-nodes = ["akka.tcp://akka-osgi-sample@localhost:4242"] - auto-down = on + auto-down-unreachable-after = 20 s } } diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/activation/Activator.scala b/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/activation/Activator.scala index 4fc009eba7..f0b1b7a347 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/activation/Activator.scala +++ b/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/activation/Activator.scala @@ -22,13 +22,12 @@ import akka.sample.osgi.service.DiningHakkersServiceImpl import akka.sample.osgi.api.DiningHakkersService import akka.event.{ LogSource, Logging } import org.osgi.framework.{ ServiceRegistration, BundleContext } -import scala.collection.mutable.ListBuffer class Activator extends ActorSystemActivator { import Activator._ - var diningHakkerService: Option[ServiceRegistration] = None + var diningHakkerService: Option[ServiceRegistration[_]] = None def configure(context: BundleContext, system: ActorSystem) { val log = Logging(system, this) diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/internal/Hakker.scala b/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/internal/Hakker.scala index 3f961cc4a8..0b19b8cd3d 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/internal/Hakker.scala +++ b/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/internal/Hakker.scala @@ -4,12 +4,15 @@ package akka.sample.osgi.internal import language.postfixOps +import scala.concurrent.duration._ +import akka.actor.Terminated import akka.cluster.Cluster import akka.cluster.ClusterEvent.{ CurrentClusterState, LeaderChanged } import akka.event.Logging -import scala.concurrent.duration._ import akka.sample.osgi.api._ import akka.actor.{ RootActorPath, Address, ActorRef, Actor } +import akka.sample.osgi.api.SubscribeToHakkerStateChanges +import akka.sample.osgi.api.HakkerStateChange //Akka adaptation of //http://www.dalnefre.com/wp/2010/08/dining-philosophers-in-humus/ @@ -68,11 +71,14 @@ class Hakker(name: String, chair: Int) extends Actor { cluster.unsubscribe(self) } + var subscribers = Set.empty[ActorRef] + //When a hakker is thinking it can become hungry //and try to pick up its chopsticks and eat def thinking(left: ActorRef, right: ActorRef): Receive = { case Eat => - become(hungry(left, right) orElse (clusterEvents)) + pubStateChange("thinking", "hungry") + become(hungry(left, right) orElse (managementEvents)) left ! Take(self) right ! Take(self) case Identify => identify("Thinking") @@ -84,11 +90,14 @@ class Hakker(name: String, chair: Int) extends Actor { //it starts to wait for the response of the other grab def hungry(left: ActorRef, right: ActorRef): Receive = { case Taken(`left`) => - become(waiting_for(left, right, false) orElse (clusterEvents)) + pubStateChange("hungry", "waiting") + become(waiting_for(left, right, false) orElse (managementEvents)) case Taken(`right`) => - become(waiting_for(left, right, true) orElse (clusterEvents)) + pubStateChange("hungry", "waiting") + become(waiting_for(left, right, true) orElse (managementEvents)) case Busy(chopstick) => - become(denied_a_chopstick(left, right) orElse (clusterEvents)) + pubStateChange("hungry", "denied_a_chopstick") + become(denied_a_chopstick(left, right) orElse (managementEvents)) case Identify => identify("Hungry") } @@ -98,14 +107,17 @@ class Hakker(name: String, chair: Int) extends Actor { def waiting_for(left: ActorRef, right: ActorRef, waitingForLeft: Boolean): Receive = { case Taken(`left`) if waitingForLeft => log.info("%s has picked up %s and %s and starts to eat".format(name, left.path.name, right.path.name)) - become(eating(left, right) orElse (clusterEvents)) + pubStateChange("waiting", "eating") + become(eating(left, right) orElse (managementEvents)) system.scheduler.scheduleOnce(5 seconds, self, Think) case Taken(`right`) if !waitingForLeft => log.info("%s has picked up %s and %s and starts to eat".format(name, left.path.name, right.path.name)) - become(eating(left, right) orElse (clusterEvents)) + pubStateChange("waiting", "eating") + become(eating(left, right) orElse (managementEvents)) system.scheduler.scheduleOnce(5 seconds, self, Think) case Busy(chopstick) => - become(thinking(left, right) orElse (clusterEvents)) + pubStateChange("waiting", "thinking") + become(thinking(left, right) orElse (managementEvents)) if (waitingForLeft) { right ! Put(self) } else { @@ -120,11 +132,13 @@ class Hakker(name: String, chair: Int) extends Actor { //Then go back and think and try to grab the chopsticks again def denied_a_chopstick(left: ActorRef, right: ActorRef): Receive = { case Taken(chopstick) => - become(thinking(left, right) orElse (clusterEvents)) + pubStateChange("denied_a_chopstick", "thinking") + become(thinking(left, right) orElse (managementEvents)) chopstick ! Put(self) self ! Eat case Busy(chopstick) => - become(thinking(left, right) orElse (clusterEvents)) + pubStateChange("denied_a_chopstick", "thinking") + become(thinking(left, right) orElse (managementEvents)) self ! Eat case Identify => identify("Denied a Chopstick") } @@ -133,7 +147,8 @@ class Hakker(name: String, chair: Int) extends Actor { //then he puts down his chopsticks and starts to think def eating(left: ActorRef, right: ActorRef): Receive = { case Think => - become(thinking(left, right) orElse (clusterEvents)) + pubStateChange("eating", "thinking") + become(thinking(left, right) orElse (managementEvents)) left ! Put(self) right ! Put(self) log.info("%s puts down his chopsticks and starts to think".format(name)) @@ -143,25 +158,42 @@ class Hakker(name: String, chair: Int) extends Actor { def waitForChopsticks: Receive = { case (left: ActorRef, right: ActorRef) => - become(thinking(left, right) orElse (clusterEvents)) + pubStateChange("waiting", "thinking") + become(thinking(left, right) orElse managementEvents) system.scheduler.scheduleOnce(5 seconds, self, Eat) + case Identify => identify("Waiting") } - def clusterEvents: Receive = { + def managementEvents: Receive = { case state: CurrentClusterState => state.leader foreach updateTable case LeaderChanged(Some(leaderAddress)) => updateTable(leaderAddress) + case SubscribeToHakkerStateChanges => + subscribers += sender + context watch sender + case Terminated(subscriber) => + subscribers -= subscriber } - def identify(busyWith: String) { + def initializing: Receive = { + case Identify => identify("Initializing") + } + + def identify(busyWith: String): Unit = { sender() ! Identification(name, busyWith) } - def updateTable(leaderAdress: Address) { - become(waitForChopsticks) - context.actorFor(RootActorPath(leaderAdress) / "user" / "table") ! chair + def updateTable(leaderAdress: Address): Unit = { + pubStateChange("-", "waiting") + become(waitForChopsticks orElse managementEvents) + context.actorSelection(RootActorPath(leaderAdress) / "user" / "table") ! chair } //All hakkers start in a non-eating state - def receive = clusterEvents + def receive = initializing orElse managementEvents + + def pubStateChange(from: String, to: String): Unit = { + val chg = HakkerStateChange(name, from, to) + subscribers foreach { _ ! chg } + } } diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/internal/HakkerTracker.scala b/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/internal/HakkerTracker.scala new file mode 100644 index 0000000000..205b273409 --- /dev/null +++ b/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/internal/HakkerTracker.scala @@ -0,0 +1,58 @@ +package akka.sample.osgi.internal + +import akka.persistence.EventsourcedProcessor +import akka.actor.ActorRef +import akka.sample.osgi.api.HakkerStateChange +import akka.sample.osgi.api.SubscribeToHakkerStateChanges +import akka.sample.osgi.api.EatingCount +import akka.sample.osgi.api.GetEatingCount +import akka.sample.osgi.api.TrackHakker + +object HakkerTracker { + sealed trait DomainEvent + case class StartedEating(name: String) extends DomainEvent + case class StoppedEating(name: String) extends DomainEvent + + object State { + val empty: State = new State(Map.empty) + } + case class State private (eatingCounts: Map[String, Int]) { + def updated(event: DomainEvent): State = event match { + case StartedEating(name) => + val c = eatingCounts.getOrElse(name, 0) + 1 + copy(eatingCounts = eatingCounts + (name -> c)) + case StoppedEating(name) => + this + } + } +} + +class HakkerTracker extends EventsourcedProcessor { + import HakkerTracker._ + + var state = State.empty + + override def receiveRecover: Receive = { + case evt: DomainEvent => + state = state.updated(evt) + } + + override def receiveCommand: Receive = { + case TrackHakker(hakker) => + hakker ! SubscribeToHakkerStateChanges + + case HakkerStateChange(name, _, "eating") => + persist(StartedEating(name)) { evt => + state = state.updated(evt) + } + + case HakkerStateChange(name, "eating", _) => + persist(StoppedEating(name)) { evt => + state = state.updated(evt) + } + + case GetEatingCount(name) => + sender ! EatingCount(name, 17) + } + +} \ No newline at end of file diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/serialization/DiningHakkerSerializer.scala b/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/serialization/DiningHakkerSerializer.scala new file mode 100644 index 0000000000..37b23d85b6 --- /dev/null +++ b/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/serialization/DiningHakkerSerializer.scala @@ -0,0 +1,25 @@ +package akka.sample.osgi.serialization + +import akka.serialization.Serializer +import akka.actor.ExtendedActorSystem +import akka.serialization.Serialization +import akka.serialization.SerializationExtension + +class DiningHakkerSerializer(val system: ExtendedActorSystem) extends Serializer { + + override def includeManifest: Boolean = true + + override def identifier = 98765 + + lazy val javaSerializer = SerializationExtension(system).findSerializerFor(classOf[java.io.Serializable]) + + def toBinary(obj: AnyRef): Array[Byte] = { + javaSerializer.toBinary(obj) + } + + def fromBinary(bytes: Array[Byte], + clazz: Option[Class[_]]): AnyRef = { + javaSerializer.fromBinary(bytes, clazz) + } + +} diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/service/DiningHakkersServiceImpl.scala b/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/service/DiningHakkersServiceImpl.scala index b597de8bdc..64aab41f25 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/service/DiningHakkersServiceImpl.scala +++ b/akka-samples/akka-sample-osgi-dining-hakkers/core/src/main/scala/akka/sample/osgi/service/DiningHakkersServiceImpl.scala @@ -17,10 +17,14 @@ package akka.sample.osgi.service import akka.sample.osgi.api.DiningHakkersService import akka.actor.{ Props, ActorSystem } +import akka.actor.ActorRef import akka.sample.osgi.internal.Hakker +import akka.sample.osgi.internal.HakkerTracker class DiningHakkersServiceImpl(system: ActorSystem) extends DiningHakkersService { - def getHakker(name: String, chairNumber: Int) = { + def getHakker(name: String, chairNumber: Int): ActorRef = system.actorOf(Props(classOf[Hakker], name, chairNumber)) - } + + def getTracker(): ActorRef = + system.actorOf(Props[HakkerTracker], "tracker") } diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/integration-test/pom.xml b/akka-samples/akka-sample-osgi-dining-hakkers/integration-test/pom.xml index a4b1e4a9f9..843fb49df7 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/integration-test/pom.xml +++ b/akka-samples/akka-sample-osgi-dining-hakkers/integration-test/pom.xml @@ -5,7 +5,7 @@ com.typesafe.akka.akka-sample-osgi-dining-hakkers project - 2.2.0-SNAPSHOT + 2.3-SNAPSHOT 4.0.0 @@ -34,92 +34,10 @@ org.scalatest scalatest_${scala.dep.version} - - - org.scala-lang - scala-actors - ${scala.version} - junit junit - - - - org.ops4j.pax.exam - pax-exam - ${paxexam.version} - test - - - org.ops4j.pax.exam - pax-exam-container-remote - ${paxexam.version} - test - - - org.ops4j.pax.exam - pax-exam-invoker-junit - ${paxexam.version} - test - - - org.ops4j.pax.exam - pax-exam-container-rbc - ${paxexam.version} - test - - - org.ops4j.pax.exam - pax-exam-container-rbc-client - ${paxexam.version} - test - - - org.ops4j.pax.exam - pax-exam-extender-service - ${paxexam.version} - test - - - org.ops4j.pax.exam - pax-exam-inject - ${paxexam.version} - test - - - org.ops4j.pax.swissbox - pax-swissbox-core - ${paxswissbox.version} - test - - - org.ops4j.pax.swissbox - pax-swissbox-extender - ${paxswissbox.version} - test - - - org.ops4j.pax.swissbox - pax-swissbox-lifecycle - ${paxswissbox.version} - test - - - org.ops4j.pax.swissbox - pax-swissbox-framework - ${paxswissbox.version} - test - - @@ -147,7 +65,6 @@ 30000 ${karaf.version} ${project.version} - ${scala.dep.version} diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/integration-test/src/test/scala/akka/sample/osgi/test/HakkerStatusTest.scala b/akka-samples/akka-sample-osgi-dining-hakkers/integration-test/src/test/scala/akka/sample/osgi/test/HakkerStatusTest.scala index f7eedc6869..c8d04ef5df 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/integration-test/src/test/scala/akka/sample/osgi/test/HakkerStatusTest.scala +++ b/akka-samples/akka-sample-osgi-dining-hakkers/integration-test/src/test/scala/akka/sample/osgi/test/HakkerStatusTest.scala @@ -1,19 +1,19 @@ package akka.sample.osgi.test -import akka.actor._ -import akka.sample.osgi.api.{DiningHakkersService, Identify, Identification} +import akka.actor.{ Identify => _, _ } +import akka.sample.osgi.api._ import akka.sample.osgi.test.TestOptions._ -import org.junit.runner.RunWith -import org.junit.{Before, Test} -import org.ops4j.pax.exam.{Option => PaxOption} -import org.ops4j.pax.exam.junit.{Configuration, JUnit4TestRunner} -import org.ops4j.pax.exam.util.Filter -import org.scalatest.junit.JUnitSuite -import org.scalatest.junit.ShouldMatchersForJUnit -import javax.inject.Inject -import java.util.concurrent.{TimeUnit, SynchronousQueue} import akka.testkit.TestProbe -import scala.Some +import javax.inject.Inject +import org.junit.runner.RunWith +import org.junit.{ Before, Test } +import org.ops4j.pax.exam.junit.{ Configuration, JUnit4TestRunner } +import org.ops4j.pax.exam.util.Filter +import org.ops4j.pax.exam.{ Option => PaxOption } +import org.scalatest.junit.{AssertionsForJUnit, JUnitSuite} +import org.scalatest.Matchers +import scala.concurrent.duration._ +import org.apache.karaf.tooling.exam.options.LogLevelOption /** * This is a ScalaTest based integration test. Pax-Exam, which is responsible for loading the test class into @@ -30,7 +30,7 @@ import scala.Some * TODO attempt to use the Akka test probe */ @RunWith(classOf[JUnit4TestRunner]) -class HakkerStatusTest extends JUnitSuite with MatchersForJUnit { +class HakkerStatusTest extends JUnitSuite with Matchers with AssertionsForJUnit { @Inject @Filter(timeout = 30000) var actorSystem: ActorSystem = _ @@ -43,66 +43,52 @@ class HakkerStatusTest extends JUnitSuite with MatchersForJUnit { @Configuration def config: Array[PaxOption] = Array[PaxOption]( karafOptionsWithTestBundles(), - featureDiningHakkers() - //,debugOptions() - ) + featureDiningHakkers() //, debugOptions(level = LogLevelOption.LogLevel.DEBUG) + ) // Junit @Before and @After can be used as well -/* @Before + @Before def setupAkkaTestkit() { testProbe = new TestProbe(actorSystem) - }*/ - + } @Test def verifyObtainingAHakkerViaTheTheDiningHakkersService() { val name = "TestHakker" - val hakker = Some(service.getHakker(name, (math.floor(math.random * 5)).toInt)) + val hakker = Option(service.getHakker(name, 2)) .getOrElse(throw new IllegalStateException("No Hakker was created via DiningHakkerService")) - hakker should not be (null) + // takes some time for the first message to get through + testProbe.within(10.seconds) { + testProbe.send(hakker, Identify) + val Identification(fromHakker, busyWith) = testProbe.expectMsgType[Identification] -/* TODO Getting some weird config error with TestProbe, is it a TestProbe inside OSGi issue? - Exception in thread "RMI TCP Connection(idle)" java.lang.NullPointerException - at com.typesafe.config.impl.SerializedConfigValue.writeOrigin(SerializedConfigValue.java:202) - at com.typesafe.config.impl.ConfigImplUtil.writeOrigin(ConfigImplUtil.java:224) - at com.typesafe.config.ConfigException.writeObject(ConfigException.java:58) - at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) - -java.io.EOFException - at java.io.ObjectInputStream$BlockDataInputStream.readUnsignedByte(ObjectInputStream.java:2747) - at java.io.ObjectInputStream.readUnsignedByte(ObjectInputStream.java:924) - at com.typesafe.config.impl.SerializedConfigValue.readCode(SerializedConfigValue.java:412) - at com.typesafe.config.impl.SerializedConfigValue.readOrigin(SerializedConfigValue.java:218) - ... - testProbe.send(hakker, Identify) - val identification = testProbe.expectMsgClass(classOf[Identification]) - val (fromHakker, busyWith) = (identification.name, identification.busyWith) - */ - - // create an "Interrogator" actor that receives a Hakker's identification - // this is to work around the TestProbe Exception above - val response = new SynchronousQueue[(String, String)]() - - hakker.tell(Identify, actorSystem.actorOf(Props(classOf[HakkerStatusTest.Interrogator], response), "Interrogator")) - val (fromHakker, busyWith) = response.poll(5, TimeUnit.SECONDS) - - println("---------------> %s is busy with %s.".format(fromHakker, busyWith)) - fromHakker should be ("TestHakker") - busyWith should not be (null) + println("---------------> %s is busy with %s.".format(fromHakker, busyWith)) + fromHakker should be("TestHakker") + busyWith should not be (null) + } } -} + @Test + def verifyHakkerTracker() { -object HakkerStatusTest { - class Interrogator(queue: SynchronousQueue[(String, String)]) extends Actor { - def receive = { - case msg: Identification => { - queue.put((msg.name, msg.busyWith)) + val name = "TestHakker" + val hakker = service.getHakker(name, 3) + val tracker = service.getTracker() + tracker ! TrackHakker(hakker) + testProbe.within(10.seconds) { + testProbe.awaitAssert { + testProbe.within(1.second) { + tracker.tell(GetEatingCount(name), testProbe.ref) + val reply = testProbe.expectMsgType[EatingCount] + reply.hakkerName should be(name) + reply.count should be > (0) + } } } } + } diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/integration-test/src/test/scala/akka/sample/osgi/test/TestOptions.scala b/akka-samples/akka-sample-osgi-dining-hakkers/integration-test/src/test/scala/akka/sample/osgi/test/TestOptions.scala index 13741a6108..063c53489f 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/integration-test/src/test/scala/akka/sample/osgi/test/TestOptions.scala +++ b/akka-samples/akka-sample-osgi-dining-hakkers/integration-test/src/test/scala/akka/sample/osgi/test/TestOptions.scala @@ -2,7 +2,7 @@ package akka.sample.osgi.test import org.ops4j.pax.exam.CoreOptions._ import org.ops4j.pax.exam.options.DefaultCompositeOption -import org.ops4j.pax.exam.{Option => PaxOption} +import org.ops4j.pax.exam.{ Option => PaxOption } import org.apache.karaf.tooling.exam.options.LogLevelOption import org.apache.karaf.tooling.exam.options.KarafDistributionOption._ import java.io.File @@ -20,34 +20,26 @@ object TestOptions { .karafVersion(System.getProperty("karaf.version")).name("Apache Karaf").useDeployFolder(useDeployFolder) new DefaultCompositeOption(if (extractInTargetFolder) kdc.unpackDirectory(new File("target/paxexam/unpack/")) else kdc, - editConfigurationFilePut("etc/config.properties", "karaf.framework", "equinox") - ) + editConfigurationFilePut("etc/config.properties", "karaf.framework", "equinox")) } def testBundles(): PaxOption = { new DefaultCompositeOption( mavenBundle("com.typesafe.akka", "akka-testkit_%s".format(scalaDepVersion)).versionAsInProject, mavenBundle("org.scalatest", "scalatest_%s".format(scalaDepVersion)).versionAsInProject, - // This is needed for the scalatest osgi bundle, it has a non-optional import on a scala-actors package - mavenBundle("org.scala-lang", "scala-actors").versionAsInProject, - junitBundles - ) + junitBundles) } - def debugOptions(level: LogLevelOption.LogLevel = LogLevelOption.LogLevel.INFO, debugPort: Int= 5005): PaxOption = { - new DefaultCompositeOption( - logLevel(level), - debugConfiguration(String.valueOf(debugPort), true), - configureConsole().startLocalConsole(), - configureConsole().startRemoteShell() - ) + def debugOptions(level: LogLevelOption.LogLevel = LogLevelOption.LogLevel.INFO, debugPort: Option[Int] = None): PaxOption = { + val options: List[PaxOption] = List(logLevel(level), configureConsole().startLocalConsole(), configureConsole().startRemoteShell()) ++ + debugPort.toList.map(p => debugConfiguration(String.valueOf(p), true)) + new DefaultCompositeOption(options: _*) } def karafOptionsWithTestBundles(useDeployFolder: Boolean = false, extractInTargetFolder: Boolean = true): PaxOption = { new DefaultCompositeOption( karafOptions(useDeployFolder, extractInTargetFolder), - testBundles() - ) + testBundles()) } def featureDiningHakkers(): PaxOption = { @@ -56,7 +48,7 @@ object TestOptions { def akkaFeature(feature: String): PaxOption = { scanFeatures(maven.groupId("com.typesafe.akka.akka-sample-osgi-dining-hakkers") - .artifactId("akka-sample-osgi-dining-hakkers") .`type`("xml").classifier("features") + .artifactId("akka-sample-osgi-dining-hakkers").`type`("xml").classifier("features") .version(System.getProperty("project.version")), feature) } diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/karaf.sh b/akka-samples/akka-sample-osgi-dining-hakkers/karaf.sh index 502d9cdbb3..96bbc4c524 100755 --- a/akka-samples/akka-sample-osgi-dining-hakkers/karaf.sh +++ b/akka-samples/akka-sample-osgi-dining-hakkers/karaf.sh @@ -1,16 +1,18 @@ #!/bin/bash projdir=$(cd $(dirname $0); pwd) -version=2.2.0-SNAPSHOT +version=2.3-SNAPSHOT # This directory is specified in the build as the root of the tar # Use tar --strip-components=1 to ignore the root -outputdir="$projdir/akka-osgi-sample-$version" +outputdir="$projdir/target/akka-sample-osgi-dining-hakkers-$version" + +mkdir $projdir/target if [[ -d "$outputdir" ]]; then echo Deleting existing $outputdir... rm -fr "$outputdir" fi echo Extracting configured container into $outputdir... -tar -C $projdir -zxf assembly-dist/target/assembly-dist-$version.tar.gz +tar -C $projdir/target -zxf assembly-dist/target/akka-sample-osgi-dining-hakkers-dist-$version.tar.gz echo Extract complete, please run $outputdir/bin/karaf diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/osgi-run.sh b/akka-samples/akka-sample-osgi-dining-hakkers/osgi-run.sh deleted file mode 100755 index 6423e4a835..0000000000 --- a/akka-samples/akka-sample-osgi-dining-hakkers/osgi-run.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -./apache-karaf-2.3.0/bin/karaf \ No newline at end of file diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/pom.xml b/akka-samples/akka-sample-osgi-dining-hakkers/pom.xml index 9eaa381160..737f817da6 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/pom.xml +++ b/akka-samples/akka-sample-osgi-dining-hakkers/pom.xml @@ -5,22 +5,25 @@ 4.0.0 com.typesafe.akka.akka-sample-osgi-dining-hakkers project - 2.2.0-SNAPSHOT + 2.3-SNAPSHOT UTF-8 2.3-SNAPSHOT - 2.3.0 + 2.3.1 ${karaf.version} - 3.6.2.Final - 4.2.0 + 3.8.0.Final + 4.3.1 2.6.0 1.6.0 2.5.0 2.10.2 2.10 - 1.9.1 - 1.0.2 + 2.0 + 1.2.0 + 0.5 + 1.7 + 1.2.2 @@ -86,6 +89,16 @@ akka-remote_${scala.dep.version} ${akka.version} + + com.typesafe.akka + akka-slf4j_${scala.dep.version} + ${akka.version} + + + com.typesafe.akka + akka-persistence-experimental_${scala.dep.version} + ${akka.version} + com.typesafe config @@ -101,6 +114,16 @@ netty ${netty.version} + + org.iq80.leveldb + leveldb + ${leveldb.version} + + + org.fusesource.leveldbjni + leveldbjni-all + ${leveldbjni.version} + org.apache.karaf.tooling.exam @@ -129,7 +152,7 @@ junit junit - 4.9 + 4.10 test @@ -161,10 +184,6 @@ oss-sonatype-releases https://oss.sonatype.org/content/repositories/releases - - typesafe-snapshots - http://repo.typesafe.com/typesafe/snapshots/ - diff --git a/akka-samples/akka-sample-osgi-dining-hakkers/uncommons/pom.xml b/akka-samples/akka-sample-osgi-dining-hakkers/uncommons/pom.xml index 6d00a830e7..a4d1a0cf65 100644 --- a/akka-samples/akka-sample-osgi-dining-hakkers/uncommons/pom.xml +++ b/akka-samples/akka-sample-osgi-dining-hakkers/uncommons/pom.xml @@ -5,7 +5,7 @@ com.typesafe.akka.akka-sample-osgi-dining-hakkers project - 2.2.0-SNAPSHOT + 2.3-SNAPSHOT 4.0.0 diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index a06f44d4e0..1efcf6c2fa 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -57,7 +57,7 @@ object AkkaBuild extends Build { javacOptions in JavaDoc := Seq(), artifactName in packageDoc in JavaDoc := ((sv, mod, art) => "" + mod.name + "_" + sv.binary + "-" + mod.revision + "-javadoc.jar"), packageDoc in Compile <<= packageDoc in JavaDoc, - Dist.distExclude := Seq(actorTests.id, docs.id, samples.id, osgi.id, osgiAries.id), + Dist.distExclude := Seq(actorTests.id, docs.id, samples.id, osgi.id), // generate online version of docs sphinxInputs in Sphinx <<= sphinxInputs in Sphinx in LocalProject(docs.id) map { inputs => inputs.copy(tags = inputs.tags :+ "online") }, // don't regenerate the pdf, just reuse the akka-docs version @@ -75,7 +75,7 @@ object AkkaBuild extends Build { validatePullRequest <<= (Unidoc.unidoc, SphinxSupport.generate in Sphinx in docs) map { (_, _) => } ), aggregate = Seq(actor, testkit, actorTests, dataflow, remote, remoteTests, camel, cluster, slf4j, agent, transactor, - persistence, mailboxes, zeroMQ, kernel, osgi, osgiAries, docs, contrib, samples, multiNodeTestkit) + persistence, mailboxes, zeroMQ, kernel, osgi, docs, contrib, samples, multiNodeTestkit) ) lazy val akkaScalaNightly = Project( @@ -83,7 +83,7 @@ object AkkaBuild extends Build { base = file("akka-scala-nightly"), // remove dependencies that we have to build ourselves (Scala STM, ZeroMQ Scala Bindings) aggregate = Seq(actor, testkit, actorTests, dataflow, remote, remoteTests, camel, cluster, slf4j, - persistence, mailboxes, kernel, osgi, osgiAries, contrib, samples, multiNodeTestkit) + persistence, mailboxes, kernel, osgi, contrib, samples, multiNodeTestkit) ) // this detached pseudo-project is used for running the tests against a different Scala version than the one used for compilation @@ -145,7 +145,7 @@ object AkkaBuild extends Build { lazy val actor = Project( id = "akka-actor", base = file("akka-actor"), - settings = defaultSettings ++ formatSettings ++ scaladocSettings ++ javadocSettings ++ Seq( + settings = defaultSettings ++ formatSettings ++ scaladocSettings ++ javadocSettings ++ OSGi.actor ++ Seq( // to fix scaladoc generation fullClasspath in doc in Compile <<= fullClasspath in Compile, libraryDependencies ++= Dependencies.actor, @@ -352,77 +352,12 @@ object AkkaBuild extends Build { ) ) - - val ActorMakeOsgiConfiguration = TaskKey[Seq[File]]("actor-make-osgi-configuration", "Copy reference.conf from akka modules for akka-osgi") - val ActorOsgiConfigurationReference = TaskKey[Seq[(File, String)]]("actor-osgi-configuration-reference", "The list of all configuration files to be bundled in an osgi bundle, as well as project name.") - - import Project.Initialize - /** This method uses a bit of advanced sbt initailizers to grab the normalized names and resource directories - * from a set of projects, and then use this to create a mapping of (reference.conf to project name). - */ - def ActorOsgiConfigurationReferenceAction(projects: Seq[Project]): Initialize[Task[Seq[(File, String)]]] = { - val directories: Initialize[Seq[File]] = projects.map(resourceDirectory in Compile in _).join - val names: Initialize[Seq[String]] = projects.map(normalizedName in _).join - directories zip names map { case (dirs, ns) => - for { - (dir, project) <- dirs zip ns - val conf = dir / "reference.conf" - if conf.exists - } yield conf -> project - } - } - - /** This method is repsonsible for genreating a new typeasafe config reference.conf file for OSGi. - * it copies all the files in the `includes` parameter, using the associated project name. Then - * it generates a new resource.conf file which includes these files. - * - * @param target The location where we write the new files - * @param includes A sequnece of (, ) pairs. - */ - def makeOsgiConfigurationFiles(includes: Seq[(File, String)], target: File, streams: TaskStreams): Seq[File] = { - // First we copy all the files to their destination - val toCopy = - for { - (file, project) <- includes - val toFile = target / (project + ".conf") - } yield file -> toFile - IO.copy(toCopy) - val copiedResourceFileLocations = toCopy.map(_._2) - streams.log.debug("Copied OSGi resources: " + copiedResourceFileLocations.mkString("\n\t", "\n\t", "\n")) - // Now we generate the new including conf file - val newConf = target / "resource.conf" - val confIncludes = - for { - (file, project) <- includes - } yield "include \""+ project +".conf\"" - val writer = new PrintWriter(newConf) - try writer.write(confIncludes mkString "\n") - finally writer.close() - streams.log.info("Copied OSGi resources.") - newConf +: copiedResourceFileLocations - } - lazy val osgi = Project( id = "akka-osgi", base = file("akka-osgi"), dependencies = Seq(actor), settings = defaultSettings ++ formatSettings ++ scaladocSettings ++ javadocSettings ++ OSGi.osgi ++ Seq( libraryDependencies ++= Dependencies.osgi, - cleanFiles <+= baseDirectory { base => base / "src/main/resources" } , - ActorOsgiConfigurationReference <<= ActorOsgiConfigurationReferenceAction(projects.filter(p => !p.id.contains("test") && !p.id.contains("sample"))), - ActorMakeOsgiConfiguration <<= (ActorOsgiConfigurationReference, resourceManaged in Compile, streams) map makeOsgiConfigurationFiles, - resourceGenerators in Compile <+= ActorMakeOsgiConfiguration, - parallelExecution in Test := false, - reportBinaryIssues := () // disable bin comp check - ) - ) - - lazy val osgiAries = Project( - id = "akka-osgi-aries", - base = file("akka-osgi-aries"), - dependencies = Seq(osgi % "compile;test->test"), - settings = defaultSettings ++ formatSettings ++ scaladocSettings ++ javadocSettings ++ OSGi.osgiAries ++ Seq( - libraryDependencies ++= Dependencies.osgiAries, parallelExecution in Test := false, reportBinaryIssues := () // disable bin comp check ) @@ -553,61 +488,73 @@ object AkkaBuild extends Build { lazy val osgiDiningHakkersSample = Project(id = "akka-sample-osgi-dining-hakkers", base = file("akka-samples/akka-sample-osgi-dining-hakkers"), - settings = parentSettings + settings = parentSettings ++ osgiSampleSettings ) aggregate(osgiDiningHakkersSampleApi, osgiDiningHakkersSampleCommand, osgiDiningHakkersSampleCore, osgiDiningHakkersSampleIntegrationTest, uncommons) lazy val osgiDiningHakkersSampleApi = Project(id = "akka-sample-osgi-dining-hakkers-api", base = file("akka-samples/akka-sample-osgi-dining-hakkers/api"), - settings = sampleSettings ++ OSGi.osgiDiningHakkersSampleApi + settings = sampleSettings ++ osgiSampleSettings ++ OSGi.osgiDiningHakkersSampleApi )dependsOn(actor) lazy val osgiDiningHakkersSampleCommand = Project(id = "akka-sample-osgi-dining-hakkers-command", base = file("akka-samples/akka-sample-osgi-dining-hakkers/command"), - settings = sampleSettings ++ OSGi.osgiDiningHakkersSampleCommand ++ Seq( - libraryDependencies ++= Dependencies.osgiDiningHakkerSampleCommand + settings = sampleSettings ++ osgiSampleSettings ++ OSGi.osgiDiningHakkersSampleCommand ++ Seq( + libraryDependencies ++= Dependencies.osgiDiningHakkersSampleCommand ) ) dependsOn (osgiDiningHakkersSampleApi, actor) lazy val osgiDiningHakkersSampleCore = Project(id = "akka-sample-osgi-dining-hakkers-core", base = file("akka-samples/akka-sample-osgi-dining-hakkers/core"), - settings = sampleSettings ++ OSGi.osgiDiningHakkersSampleCore ++ Seq( - libraryDependencies ++= Dependencies.osgiDiningHakkerSampleCore + settings = sampleSettings ++ osgiSampleSettings ++ OSGi.osgiDiningHakkersSampleCore ++ Seq( + libraryDependencies ++= Dependencies.osgiDiningHakkersSampleCore ) - ) dependsOn (osgiDiningHakkersSampleApi, actor, remote, cluster, osgi) + ) dependsOn (osgiDiningHakkersSampleApi, actor, remote, cluster, persistence, osgi) + + lazy val osgiDiningHakkersSampleTest = Project(id = "akka-sample-osgi-dining-hakkers-test", + base = file("akka-samples/akka-sample-osgi-dining-hakkers/integration-test"), + settings = sampleSettings ++ osgiSampleSettings ++ OSGi.osgiDiningHakkersSampleCore ++ Seq( + libraryDependencies ++= Dependencies.osgiDiningHakkersSampleTest + ) + ) dependsOn (osgiDiningHakkersSampleCommand, osgiDiningHakkersSampleCore, testkit ) //TODO to remove it as soon as the uncommons gets OSGified, see ticket #2990 lazy val uncommons = Project(id = "akka-sample-osgi-dining-hakkers-uncommons", base = file("akka-samples/akka-sample-osgi-dining-hakkers/uncommons"), - settings = sampleSettings ++ OSGi.osgiDiningHakkersSampleUncommons ++ Seq( + settings = sampleSettings ++ osgiSampleSettings ++ OSGi.osgiDiningHakkersSampleUncommons ++ Seq( libraryDependencies ++= Dependencies.uncommons, version := "1.2.0" ) ) def executeMvnCommands(failureMessage: String, commands: String*) = { - if ({List("sh", "-c", commands.mkString("cd akka-samples/akka-sample-osgi-dining-hakkers; mvn -U ", " ", "")) !} != 0) + if ({List("sh", "-c", commands.mkString("cd akka-samples/akka-sample-osgi-dining-hakkers; mvn ", " ", "")) !} != 0) throw new Exception(failureMessage) } lazy val osgiDiningHakkersSampleIntegrationTest = Project(id = "akka-sample-osgi-dining-hakkers-integration", base = file("akka-samples/akka-sample-osgi-dining-hakkers-integration"), - settings = sampleSettings ++ ( - if (System.getProperty("akka.osgi.sample.test", "false").toBoolean) Seq( + settings = sampleSettings ++ osgiSampleSettings ++ ( + if (System.getProperty("akka.osgi.sample.test", "true").toBoolean) Seq( test in Test ~= { x => { executeMvnCommands("Osgi sample Dining hakkers test failed", "clean", "install") - }}) + }}, + // force publication of artifacts to local maven repo + compile in Compile <<= + (publishM2 in actor, publishM2 in testkit, publishM2 in remote, publishM2 in cluster, publishM2 in osgi, + publishM2 in slf4j, publishM2 in persistence, compile in Compile) map + ((_, _, _, _, _, _, _, c) => c)) else Seq.empty ) ) dependsOn(osgiDiningHakkersSampleApi, osgiDiningHakkersSampleCommand, osgiDiningHakkersSampleCore, uncommons) - + lazy val osgiSampleSettings: Seq[Setting[_]] = Seq(target := baseDirectory.value / "target-sbt") lazy val docs = Project( id = "akka-docs", base = file("akka-docs"), dependencies = Seq(actor, testkit % "test->test", - remote % "compile;test->test", cluster, slf4j, agent, zeroMQ, camel, osgi, osgiAries, + remote % "compile;test->test", cluster, slf4j, agent, zeroMQ, camel, osgi, persistence % "compile;test->test"), settings = defaultSettings ++ docFormatSettings ++ site.settings ++ site.sphinxSupport() ++ site.publishSite ++ sphinxPreprocessing ++ cpsPlugin ++ Seq( sourceDirectory in Sphinx <<= baseDirectory / "rst", @@ -826,7 +773,16 @@ object AkkaBuild extends Build { // don't save test output to a file testListeners in (Test, test) := Seq(TestLogger(streams.value.log, {_ => streams.value.log }, logBuffered.value)) - ) + ) ++ (System.getProperty("akka.build.M2Dir") match { + case null => Seq.empty + case path => + // Maven resolver settings + Seq( + otherResolvers := + Resolver.file("user-publish-m2-local", new File(path)) :: publishTo.value.toList, + publishM2Configuration := Classpaths.publishConfig(packagedArtifacts.value, None, resolverName = "user-publish-m2-local", checksums = checksums.in(publishM2).value, logging = ivyLoggingLevel.value) + ) + }) val validatePullRequest = TaskKey[Unit]("validate-pull-request", "Additional tasks for pull request validation") @@ -929,7 +885,7 @@ object AkkaBuild extends Build { case (false, _) => Seq.empty }) - lazy val scaladocDiagramsEnabled = System.getProperty("akka.scaladoc.diagrams", "true").toBoolean + lazy val scaladocDiagramsEnabled = System.getProperty("akka.scaladoc.diagrams", "false").toBoolean lazy val scaladocAutoAPI = System.getProperty("akka.scaladoc.autoapi", "true").toBoolean lazy val scaladocOptions = List("-implicits") ::: (if (scaladocDiagramsEnabled) List("-diagrams") else Nil) @@ -1010,13 +966,6 @@ object AkkaBuild extends Build { } } - def copyFile(source: String, sink: String){ - val src = new java.io.File(source) - val dest = new java.io.File(sink) - new java.io.FileOutputStream(dest) getChannel() transferFrom( - new java.io.FileInputStream(src) getChannel, 0, Long.MaxValue ) - } - // OSGi settings object OSGi { @@ -1028,8 +977,15 @@ object AkkaBuild extends Build { packagedArtifact in (Compile, packageBin) <<= (artifact in (Compile, packageBin), OsgiKeys.bundle).identityMap ) - //akka-actor is wrapped into akka-osgi to simplify OSGi deployement. - + val actor = osgiSettings ++ Seq( + OsgiKeys.exportPackage := Seq("akka*"), + OsgiKeys.privatePackage := Seq("akka.osgi.impl"), + //akka-actor packages are not imported, as contained in the CP + OsgiKeys.importPackage := (osgiOptionalImports map optionalResolution) ++ Seq("!sun.misc", scalaImport(), configImport(), "*"), + // dynamicImportPackage needed for loading classes defined in configuration + OsgiKeys.dynamicImportPackage := Seq("*") + ) + val agent = exports(Seq("akka.agent.*")) val camel = exports(Seq("akka.camel.*")) @@ -1040,12 +996,7 @@ object AkkaBuild extends Build { val mailboxesCommon = exports(Seq("akka.actor.mailbox.*"), imports = Seq(protobufImport())) - val osgi = osgiSettings ++ Seq( - OsgiKeys.exportPackage := Seq("akka*"), //exporting akka packages enforces bnd to aggregate akka-actor packages in the bundle - OsgiKeys.privatePackage := Seq("akka.osgi.impl"), - //akka-actor packages are not imported, as contained in the CP - OsgiKeys.importPackage := (osgiOptionalImports map optionalResolution) ++ Seq("!sun.misc", scalaImport(),configImport(), "*") - ) + val osgi = exports(Seq("akka.osgi.*")) val osgiDiningHakkersSampleApi = exports(Seq("akka.sample.osgi.api")) @@ -1055,8 +1006,6 @@ object AkkaBuild extends Build { val osgiDiningHakkersSampleUncommons = exports(Seq("org.uncommons.maths.random")) ++ Seq(OsgiKeys.privatePackage := Seq("org.uncommons.maths.binary", "org.uncommons.maths", "org.uncommons.maths.number")) - val osgiAries = exports() ++ Seq(OsgiKeys.privatePackage := Seq("akka.osgi.aries.*")) - val remote = exports(Seq("akka.remote.*"), imports = Seq(protobufImport())) val slf4j = exports(Seq("akka.event.slf4j.*")) @@ -1071,25 +1020,11 @@ object AkkaBuild extends Build { val zeroMQ = exports(Seq("akka.zeromq.*"), imports = Seq(protobufImport()) ) - val osgiOptionalImports = Seq("akka.remote", - "akka.remote.transport.netty", - "akka.remote.security.provider", - "akka.remote.netty", - "akka.remote.routing", - "akka.remote.transport", - "akka.remote.serialization", - "akka.cluster", - "akka.cluster.routing", - "akka.cluster.protobuf", - "akka.transactor", - "akka.agent", - "akka.dataflow", - "akka.actor.mailbox", - "akka.camel.internal", - "akka.camel.javaapi", - "akka.camel", - "akka.camel.internal.component", - "akka.zeromq", + val osgiOptionalImports = Seq( + // needed because testkit is normally not used in the application bundle, + // but it should still be included as transitive dependency and used by BundleDelegatingClassLoader + // to be able to find refererence.conf + "akka.testkit", "com.google.protobuf") def exports(packages: Seq[String] = Seq(), imports: Seq[String] = Nil) = osgiSettings ++ Seq( @@ -1098,7 +1033,7 @@ object AkkaBuild extends Build { ) def defaultImports = Seq("!sun.misc", akkaImport(), configImport(), scalaImport(), "*") def akkaImport(packageName: String = "akka.*") = "%s;version=\"[2.3,2.4)\"".format(packageName) - def configImport(packageName: String = "com.typesafe.config.*") = "%s;version=\"[0.4.1,1.1.0)\"".format(packageName) + def configImport(packageName: String = "com.typesafe.config.*") = "%s;version=\"[1.2.0,1.3.0)\"".format(packageName) def protobufImport(packageName: String = "com.google.protobuf.*") = "%s;version=\"[2.5.0,2.6.0)\"".format(packageName) def scalaImport(packageName: String = "scala.*") = "%s;version=\"[2.10,2.11)\"".format(packageName) def optionalResolution(packageName: String) = "%s;resolution:=optional".format(packageName) @@ -1121,21 +1056,30 @@ object Dependencies { object Compile { import Versions._ + // Several dependencies are mirrored in the OSGi Dining Hackers maven project + // They need to be changed in this file as well: + // akka-samples/akka-sample-osgi-dining-hakkers/pom.xml + // Compile val camelCore = "org.apache.camel" % "camel-core" % "2.10.3" exclude("org.slf4j", "slf4j-api") // ApacheV2 val config = "com.typesafe" % "config" % "1.2.0" // ApacheV2 + // mirrored in OSGi sample val netty = "io.netty" % "netty" % "3.8.0.Final" // ApacheV2 + // mirrored in OSGi sample val protobuf = "com.google.protobuf" % "protobuf-java" % "2.5.0" // New BSD val scalaStm = "org.scala-stm" %% "scala-stm" % scalaStmVersion // Modified BSD (Scala) val slf4jApi = "org.slf4j" % "slf4j-api" % "1.7.5" // MIT val zeroMQClient = "org.zeromq" %% "zeromq-scala-binding" % scalaZeroMQVersion // ApacheV2 + // mirrored in OSGi sample val uncommonsMath = "org.uncommons.maths" % "uncommons-maths" % "1.2.2a" exclude("jfree", "jcommon") exclude("jfree", "jfreechart") // ApacheV2 - val ariesBlueprint = "org.apache.aries.blueprint" % "org.apache.aries.blueprint" % "1.1.0" // ApacheV2 - val osgiCore = "org.osgi" % "org.osgi.core" % "4.2.0" // ApacheV2 - val osgiCompendium= "org.osgi" % "org.osgi.compendium" % "4.2.0" // ApacheV2 + // mirrored in OSGi sample + val osgiCore = "org.osgi" % "org.osgi.core" % "4.3.1" // ApacheV2 + val osgiCompendium= "org.osgi" % "org.osgi.compendium" % "4.3.1" // ApacheV2 + // mirrored in OSGi sample val levelDB = "org.iq80.leveldb" % "leveldb" % "0.5" // ApacheV2 + // mirrored in OSGi sample val levelDBNative = "org.fusesource.leveldbjni" % "leveldbjni-all" % "1.7" // New BSD // Camel Sample @@ -1157,13 +1101,18 @@ object Dependencies { val logback = "ch.qos.logback" % "logback-classic" % "1.0.13" % "test" // EPL 1.0 / LGPL 2.1 val mockito = "org.mockito" % "mockito-all" % "1.8.1" % "test" // MIT // changing the scalatest dependency must be reflected in akka-docs/rst/dev/multi-jvm-testing.rst + // mirrored in OSGi sample val scalatest = "org.scalatest" %% "scalatest" % scalaTestVersion % "test" // ApacheV2 val scalacheck = "org.scalacheck" %% "scalacheck" % scalaCheckVersion % "test" // New BSD - val ariesProxy = "org.apache.aries.proxy" % "org.apache.aries.proxy.impl" % "1.0.1" % "test" // ApacheV2 - val pojosr = "com.googlecode.pojosr" % "de.kalpatec.pojosr.framework" % "0.1.4" % "test" // ApacheV2 + val pojosr = "com.googlecode.pojosr" % "de.kalpatec.pojosr.framework" % "0.2.1" % "test" // ApacheV2 val tinybundles = "org.ops4j.pax.tinybundles" % "tinybundles" % "1.0.0" % "test" // ApacheV2 val log4j = "log4j" % "log4j" % "1.2.14" % "test" // ApacheV2 val junitIntf = "com.novocode" % "junit-interface" % "0.8" % "test" // MIT + // dining hakkers integration test using pax-exam + // mirrored in OSGi sample + val karafExam = "org.apache.karaf.tooling.exam" % "org.apache.karaf.tooling.exam.container" % "2.3.1" % "test" // ApacheV2 + // mirrored in OSGi sample + val paxExam = "org.ops4j.pax.exam" % "pax-exam-junit4" % "2.6.0" % "test" // ApacheV2 } } @@ -1201,14 +1150,14 @@ object Dependencies { val osgi = Seq(osgiCore, osgiCompendium, Test.logback, Test.commonsIo, Test.pojosr, Test.tinybundles, Test.scalatest, Test.junit) - val osgiDiningHakkerSampleCore = Seq(config, osgiCore, osgiCompendium) + val osgiDiningHakkersSampleCore = Seq(config, osgiCore, osgiCompendium) - val osgiDiningHakkerSampleCommand = Seq(osgiCore, osgiCompendium) + val osgiDiningHakkersSampleCommand = Seq(osgiCore, osgiCompendium) + + val osgiDiningHakkersSampleTest = Seq(osgiCore, osgiCompendium, Test.karafExam, Test.paxExam, Test.junit, Test.scalatest) val uncommons = Seq(uncommonsMath) - val osgiAries = Seq(osgiCore, osgiCompendium, ariesBlueprint, Test.ariesProxy) - val docs = Seq(Test.scalatest, Test.junit, Test.junitIntf) val zeroMQ = Seq(protobuf, zeroMQClient, Test.scalatest, Test.junit) diff --git a/project/Unidoc.scala b/project/Unidoc.scala index 31fb756b3e..0ff23d1585 100644 --- a/project/Unidoc.scala +++ b/project/Unidoc.scala @@ -11,15 +11,14 @@ object Unidoc { lazy val GenJavaDocEnabled = Option(sys.props("akka.genjavadoc.enabled")) filter (_.toLowerCase == "true") map (_ => true) getOrElse false lazy val javadocSettings = - inConfig(JavaDoc)(Defaults.configSettings) ++ Seq( - packageDoc in Compile <<= packageDoc in JavaDoc, - sources in JavaDoc <<= (target, compile in Compile, sources in Compile) map ((t, c, s) => - if (GenJavaDocEnabled) (t / "java" ** "*.java").get ++ s.filter(_.getName.endsWith(".java")) - else throw new RuntimeException("cannot build java docs without -Dakka.genjavadoc.enabled=true") - ), - javacOptions in JavaDoc := Seq(), - artifactName in packageDoc in JavaDoc := ((sv, mod, art) => "" + mod.name + "_" + sv.binary + "-" + mod.revision + "-javadoc.jar") - ) ++ (if (GenJavaDocEnabled) Seq( + inConfig(JavaDoc)(Defaults.configSettings) ++ + (if (GenJavaDocEnabled) Seq( + packageDoc in Compile <<= packageDoc in JavaDoc, + sources in JavaDoc <<= (target, compile in Compile, sources in Compile) map ((t, c, s) => + (t / "java" ** "*.java").get ++ s.filter(_.getName.endsWith(".java")) + ), + javacOptions in JavaDoc := Seq(), + artifactName in packageDoc in JavaDoc := ((sv, mod, art) => "" + mod.name + "_" + sv.binary + "-" + mod.revision + "-javadoc.jar"), libraryDependencies += Dependencies.Compile.genjavadoc, scalacOptions <+= target map (t => "-P:genjavadoc:out=" + (t / "java")) ) else Nil)