diff --git a/akka-active-object-test/src/test/java/se/scalablesolutions/akka/api/InMemStateful.java b/akka-active-object-test/src/test/java/se/scalablesolutions/akka/api/InMemStateful.java index afe2f2e232..411fc1d420 100644 --- a/akka-active-object-test/src/test/java/se/scalablesolutions/akka/api/InMemStateful.java +++ b/akka-active-object-test/src/test/java/se/scalablesolutions/akka/api/InMemStateful.java @@ -10,14 +10,14 @@ import se.scalablesolutions.akka.stm.*; public class InMemStateful { private TransactionalMap mapState; private TransactionalVector vectorState; - private TransactionalRef refState; + private Ref refState; private boolean isInitialized = false; public void init() { if (!isInitialized) { - mapState = TransactionalState.newMap(); - vectorState = TransactionalState.newVector(); - refState = TransactionalState.newRef(); + mapState = new TransactionalMap(); + vectorState = new TransactionalVector(); + refState = new Ref(); isInitialized = true; } } diff --git a/akka-active-object-test/src/test/java/se/scalablesolutions/akka/api/InMemStatefulNested.java b/akka-active-object-test/src/test/java/se/scalablesolutions/akka/api/InMemStatefulNested.java index 932dc2c162..424e2c03e0 100644 --- a/akka-active-object-test/src/test/java/se/scalablesolutions/akka/api/InMemStatefulNested.java +++ b/akka-active-object-test/src/test/java/se/scalablesolutions/akka/api/InMemStatefulNested.java @@ -8,14 +8,14 @@ import se.scalablesolutions.akka.stm.*; public class InMemStatefulNested { private TransactionalMap mapState; private TransactionalVector vectorState; - private TransactionalRef refState; + private Ref refState; private boolean isInitialized = false; public void init() { if (!isInitialized) { - mapState = TransactionalState.newMap(); - vectorState = TransactionalState.newVector(); - refState = TransactionalState.newRef(); + mapState = new TransactionalMap(); + vectorState = new TransactionalVector(); + refState = new Ref(); isInitialized = true; } } diff --git a/akka-camel/src/main/scala/CamelService.scala b/akka-camel/src/main/scala/CamelService.scala index 0e7fac4c9f..f689e6fe44 100644 --- a/akka-camel/src/main/scala/CamelService.scala +++ b/akka-camel/src/main/scala/CamelService.scala @@ -66,7 +66,10 @@ trait CamelService extends Bootable with Logging { * * @see onLoad */ - def load = onLoad + def load: CamelService = { + onLoad + this + } /** * Stops the CamelService. diff --git a/akka-camel/src/main/scala/Producer.scala b/akka-camel/src/main/scala/Producer.scala index 7aca16f956..9a68b8d57f 100644 --- a/akka-camel/src/main/scala/Producer.scala +++ b/akka-camel/src/main/scala/Producer.scala @@ -133,6 +133,11 @@ trait Producer { this: Actor => } } + /** + * Default implementation of Actor.receive + */ + protected def receive = produce + /** * Creates a new in-only Exchange. */ diff --git a/akka-camel/src/test/scala/CamelServiceFeatureTest.scala b/akka-camel/src/test/scala/CamelServiceFeatureTest.scala index fd57d83457..771ed83af3 100644 --- a/akka-camel/src/test/scala/CamelServiceFeatureTest.scala +++ b/akka-camel/src/test/scala/CamelServiceFeatureTest.scala @@ -26,7 +26,7 @@ class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with Gi // count expectations in the next step (needed for testing only). service.consumerPublisher.start // set expectations on publish count - val latch = service.consumerPublisher.!![CountDownLatch](SetExpectedMessageCount(1)).get + val latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get // start the CamelService service.load // await publication of first test consumer @@ -43,7 +43,7 @@ class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with Gi scenario("access registered consumer actors via Camel direct-endpoints") { given("two consumer actors registered before and after CamelService startup") - val latch = service.consumerPublisher.!![CountDownLatch](SetExpectedMessageCount(1)).get + val latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get actorOf(new TestConsumer("direct:publish-test-2")).start assert(latch.await(5000, TimeUnit.MILLISECONDS)) @@ -64,12 +64,12 @@ class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with Gi given("a consumer actor that has been stopped") assert(CamelContextManager.context.hasEndpoint(endpointUri) eq null) - var latch = service.consumerPublisher.!![CountDownLatch](SetExpectedMessageCount(1)).get + var latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get val consumer = actorOf(new TestConsumer(endpointUri)).start assert(latch.await(5000, TimeUnit.MILLISECONDS)) assert(CamelContextManager.context.hasEndpoint(endpointUri) ne null) - latch = service.consumerPublisher.!![CountDownLatch](SetExpectedMessageCount(1)).get + latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get consumer.stop assert(latch.await(5000, TimeUnit.MILLISECONDS)) // endpoint is still there but the route has been stopped @@ -103,7 +103,7 @@ class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with Gi scenario("access active object methods via Camel direct-endpoints") { given("an active object registered after CamelService startup") - val latch = service.consumerPublisher.!![CountDownLatch](SetExpectedMessageCount(3)).get + val latch = (service.consumerPublisher !! SetExpectedMessageCount(3)).as[CountDownLatch].get ActiveObject.newInstance(classOf[PojoBase]) assert(latch.await(5000, TimeUnit.MILLISECONDS)) diff --git a/akka-camel/src/test/scala/ProducerFeatureTest.scala b/akka-camel/src/test/scala/ProducerFeatureTest.scala index 6f1f39abeb..96d1b9eeef 100644 --- a/akka-camel/src/test/scala/ProducerFeatureTest.scala +++ b/akka-camel/src/test/scala/ProducerFeatureTest.scala @@ -11,7 +11,6 @@ import se.scalablesolutions.akka.actor.Actor._ object ProducerFeatureTest { class TestProducer(uri: String) extends Actor with Producer { def endpointUri = uri - def receive = produce } } @@ -69,7 +68,7 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before when("a fail message is sent to the producer") val message = Message("fail", Map(Message.MessageExchangeId -> "123")) - val result = producer.!![Failure](message) + val result = (producer !! message).as[Failure] then("the expected failure message should be returned including a correlation identifier") val expectedFailureText = result.get.cause.getMessage @@ -85,7 +84,7 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before when("a fail message is sent to the producer") val message = Message("fail", Map(Message.MessageExchangeId -> "123")) - val result = producer.!![Failure](message) + val result = (producer !! message).as[Failure] then("the expected failure message should be returned including a correlation identifier") val expectedFailureText = result.get.cause.getMessage diff --git a/akka-camel/src/test/scala/PublishRequestorTest.scala b/akka-camel/src/test/scala/PublishRequestorTest.scala index f3c9a899b2..7729e6eec6 100644 --- a/akka-camel/src/test/scala/PublishRequestorTest.scala +++ b/akka-camel/src/test/scala/PublishRequestorTest.scala @@ -34,7 +34,7 @@ class PublishRequestorTest extends JUnitSuite { @Test def shouldReceiveConsumerMethodRegisteredEvent = { val obj = ActiveObject.newInstance(classOf[PojoSingle]) val init = AspectInit(classOf[PojoSingle], null, None, 1000) - val latch = publisher.!![CountDownLatch](SetExpectedTestMessageCount(1)).get + val latch = (publisher !! SetExpectedTestMessageCount(1)).as[CountDownLatch].get requestor ! AspectInitRegistered(obj, init) assert(latch.await(5000, TimeUnit.MILLISECONDS)) val event = (publisher !! GetRetainedMessage).get.asInstanceOf[ConsumerMethodRegistered] @@ -45,7 +45,7 @@ class PublishRequestorTest extends JUnitSuite { } @Test def shouldReceiveConsumerRegisteredEvent = { - val latch = publisher.!![CountDownLatch](SetExpectedTestMessageCount(1)).get + val latch = (publisher !! SetExpectedTestMessageCount(1)).as[CountDownLatch].get requestor ! ActorRegistered(consumer) assert(latch.await(5000, TimeUnit.MILLISECONDS)) assert((publisher !! GetRetainedMessage) === @@ -53,7 +53,7 @@ class PublishRequestorTest extends JUnitSuite { } @Test def shouldReceiveConsumerUnregisteredEvent = { - val latch = publisher.!![CountDownLatch](SetExpectedTestMessageCount(1)).get + val latch = (publisher !! SetExpectedTestMessageCount(1)).as[CountDownLatch].get requestor ! ActorUnregistered(consumer) assert(latch.await(5000, TimeUnit.MILLISECONDS)) assert((publisher !! GetRetainedMessage) === diff --git a/akka-camel/src/test/scala/RemoteConsumerTest.scala b/akka-camel/src/test/scala/RemoteConsumerTest.scala index e1a7842e0d..4e2aa59b24 100644 --- a/akka-camel/src/test/scala/RemoteConsumerTest.scala +++ b/akka-camel/src/test/scala/RemoteConsumerTest.scala @@ -45,7 +45,7 @@ class RemoteConsumerTest extends FeatureSpec with BeforeAndAfterAll with GivenWh val consumer = actorOf[RemoteConsumer].start when("remote consumer publication is triggered") - val latch = service.consumerPublisher.!![CountDownLatch](SetExpectedMessageCount(1)).get + val latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get consumer !! "init" assert(latch.await(5000, TimeUnit.MILLISECONDS)) diff --git a/akka-camel/src/test/scala/component/ActorComponentFeatureTest.scala b/akka-camel/src/test/scala/component/ActorComponentFeatureTest.scala index f73a2fcd3e..b7fd607f28 100644 --- a/akka-camel/src/test/scala/component/ActorComponentFeatureTest.scala +++ b/akka-camel/src/test/scala/component/ActorComponentFeatureTest.scala @@ -26,7 +26,7 @@ class ActorComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with scenario("one-way communication using actor id") { val actor = actorOf[Tester1].start - val latch = actor.!![CountDownLatch](SetExpectedMessageCount(1)).get + val latch = (actor !! SetExpectedMessageCount(1)).as[CountDownLatch].get template.sendBody("actor:%s" format actor.id, "Martin") assert(latch.await(5000, TimeUnit.MILLISECONDS)) val reply = (actor !! GetRetainedMessage).get.asInstanceOf[Message] @@ -35,7 +35,7 @@ class ActorComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with scenario("one-way communication using actor uuid") { val actor = actorOf[Tester1].start - val latch = actor.!![CountDownLatch](SetExpectedMessageCount(1)).get + val latch = (actor !! SetExpectedMessageCount(1)).as[CountDownLatch].get template.sendBody("actor:uuid:%s" format actor.uuid, "Martin") assert(latch.await(5000, TimeUnit.MILLISECONDS)) val reply = (actor !! GetRetainedMessage).get.asInstanceOf[Message] diff --git a/akka-camel/src/test/scala/component/ActorProducerTest.scala b/akka-camel/src/test/scala/component/ActorProducerTest.scala index 419784681b..6840bf1c79 100644 --- a/akka-camel/src/test/scala/component/ActorProducerTest.scala +++ b/akka-camel/src/test/scala/component/ActorProducerTest.scala @@ -19,7 +19,7 @@ class ActorProducerTest extends JUnitSuite with BeforeAndAfterAll { @Test def shouldSendMessageToActor = { val actor = actorOf[Tester1].start - val latch = actor.!![CountDownLatch](SetExpectedMessageCount(1)).get + val latch = (actor !! SetExpectedMessageCount(1)).as[CountDownLatch].get val endpoint = mockEndpoint("actor:uuid:%s" format actor.uuid) val exchange = endpoint.createExchange(ExchangePattern.InOnly) exchange.getIn.setBody("Martin") diff --git a/akka-core/src/main/scala/actor/ActiveObject.scala b/akka-core/src/main/scala/actor/ActiveObject.scala index 41a87fa6ee..421381ccf7 100644 --- a/akka-core/src/main/scala/actor/ActiveObject.scala +++ b/akka-core/src/main/scala/actor/ActiveObject.scala @@ -4,6 +4,7 @@ package se.scalablesolutions.akka.actor +import Actor._ import se.scalablesolutions.akka.config.FaultHandlingStrategy import se.scalablesolutions.akka.remote.protocol.RemoteProtocol.RemoteRequestProtocol import se.scalablesolutions.akka.remote.{RemoteProtocolBuilder, RemoteClient, RemoteRequestProtocolIdFactory} @@ -548,7 +549,7 @@ private[akka] sealed class ActiveObjectAspect { actorRef ! Invocation(joinPoint, true, true, sender, senderFuture) null.asInstanceOf[AnyRef] } else { - val result = actorRef !! (Invocation(joinPoint, false, isOneWay, sender, senderFuture), timeout) + val result = (actorRef !! (Invocation(joinPoint, false, isOneWay, sender, senderFuture), timeout)).as[AnyRef] if (result.isDefined) result.get else throw new IllegalStateException("No result defined for invocation [" + joinPoint + "]") } diff --git a/akka-core/src/main/scala/actor/Actor.scala b/akka-core/src/main/scala/actor/Actor.scala index 693fbaad26..06b00f4e24 100644 --- a/akka-core/src/main/scala/actor/Actor.scala +++ b/akka-core/src/main/scala/actor/Actor.scala @@ -7,8 +7,9 @@ package se.scalablesolutions.akka.actor import se.scalablesolutions.akka.dispatch._ import se.scalablesolutions.akka.config.Config._ import se.scalablesolutions.akka.config.ScalaConfig._ -import se.scalablesolutions.akka.util.Logging import se.scalablesolutions.akka.serialization.Serializer +import se.scalablesolutions.akka.util.Helpers.{ narrow, narrowSilently } +import se.scalablesolutions.akka.util.Logging import com.google.protobuf.Message @@ -311,8 +312,13 @@ object Actor extends Logging { case Spawn => body; self.stop } }).start ! Spawn - } + + /** + * Implicitly converts the given Option[Any] to a AnyOptionAsTypedOption which offers the method as[T] + * to convert an Option[Any] to an Option[T]. + */ + implicit def toAnyOptionAsTypedOption(anyOption: Option[Any]) = new AnyOptionAsTypedOption(anyOption) } /** @@ -528,3 +534,18 @@ trait Actor extends Logging { case Kill => throw new ActorKilledException("Actor [" + toString + "] was killed by a Kill message") } } + +private[actor] class AnyOptionAsTypedOption(anyOption: Option[Any]) { + + /** + * Convenience helper to cast the given Option of Any to an Option of the given type. Will throw a ClassCastException + * if the actual type is not assignable from the given one. + */ + def as[T]: Option[T] = narrow[T](anyOption) + + /** + * Convenience helper to cast the given Option of Any to an Option of the given type. Will swallow a possible + * ClassCastException and return None in that case. + */ + def asSilently[T: Manifest]: Option[T] = narrowSilently[T](anyOption) +} diff --git a/akka-core/src/main/scala/actor/ActorRef.scala b/akka-core/src/main/scala/actor/ActorRef.scala index d7ca9a369b..ed0de1072a 100644 --- a/akka-core/src/main/scala/actor/ActorRef.scala +++ b/akka-core/src/main/scala/actor/ActorRef.scala @@ -8,7 +8,7 @@ import se.scalablesolutions.akka.dispatch._ import se.scalablesolutions.akka.config.Config.config import se.scalablesolutions.akka.config.{AllForOneStrategy, OneForOneStrategy, FaultHandlingStrategy} import se.scalablesolutions.akka.config.ScalaConfig._ -import se.scalablesolutions.akka.stm.Transaction.Global._ +import se.scalablesolutions.akka.stm.global._ import se.scalablesolutions.akka.stm.TransactionManagement._ import se.scalablesolutions.akka.stm.TransactionManagement import se.scalablesolutions.akka.remote.protocol.RemoteProtocol._ @@ -290,7 +290,17 @@ trait ActorRef extends TransactionManagement { */ @volatile protected[akka] var isTransactor = false - /**v + /** + * Configuration for TransactionFactory. User overridable. + */ + protected[akka] var _transactionConfig: TransactionConfig = DefaultGlobalTransactionConfig + + /** + * TransactionFactory to be used for atomic when isTransactor. Configuration is overridable. + */ + private[akka] var _transactionFactory: Option[TransactionFactory] = None + + /** * This lock ensures thread safety in the dispatching: only one message can * be dispatched at once on the actor. */ @@ -391,9 +401,9 @@ trait ActorRef extends TransactionManagement { * If you are sending messages using !! then you have to use self.reply(..) * to send a reply message to the original sender. If not then the sender will block until the timeout expires. */ - def !![T](message: Any, timeout: Long = this.timeout)(implicit sender: Option[ActorRef] = None): Option[T] = { + def !!(message: Any, timeout: Long = this.timeout)(implicit sender: Option[ActorRef] = None): Option[Any] = { if (isRunning) { - val future = postMessageToMailboxAndCreateFutureResultWithTimeout[T](message, timeout, sender, None) + val future = postMessageToMailboxAndCreateFutureResultWithTimeout[Any](message, timeout, sender, None) val isActiveObject = message.isInstanceOf[Invocation] if (isActiveObject && message.asInstanceOf[Invocation].isVoid) { future.asInstanceOf[CompletableFuture[Option[_]]].completeWithResult(None) @@ -502,13 +512,19 @@ trait ActorRef extends TransactionManagement { /** * Invoking 'makeTransactionRequired' means that the actor will **start** a new transaction if non exists. * However, it will always participate in an existing transaction. - * If transactionality want to be completely turned off then do it by invoking: - *
-   *  TransactionManagement.disableTransactions
-   * 
*/ def makeTransactionRequired: Unit + /** + * Sets the transaction configuration for this actor. Needs to be invoked before the actor is started. + */ + def transactionConfig_=(config: TransactionConfig): Unit + + /** + * Get the transaction configuration for this actor. + */ + def transactionConfig: TransactionConfig + /** * Returns the home address and port for this actor. */ @@ -877,10 +893,6 @@ sealed class LocalActorRef private[akka]( /** * Invoking 'makeTransactionRequired' means that the actor will **start** a new transaction if non exists. * However, it will always participate in an existing transaction. - * If transactionality want to be completely turned off then do it by invoking: - *
-   *  TransactionManagement.disableTransactions
-   * 
*/ def makeTransactionRequired = guard.withGuard { if (!isRunning || isBeingRestarted) isTransactor = true @@ -888,6 +900,20 @@ sealed class LocalActorRef private[akka]( "Can not make actor transaction required after it has been started") } + /** + * Sets the transaction configuration for this actor. Needs to be invoked before the actor is started. + */ + def transactionConfig_=(config: TransactionConfig) = guard.withGuard { + if (!isRunning || isBeingRestarted) _transactionConfig = config + else throw new ActorInitializationException( + "Cannot set transaction configuration for actor after it has been started") + } + + /** + * Get the transaction configuration for this actor. + */ + def transactionConfig: TransactionConfig = guard.withGuard { _transactionConfig } + /** * Set the contact address for this actor. This is used for replying to messages * sent asynchronously when no reply channel exists. @@ -909,6 +935,9 @@ sealed class LocalActorRef private[akka]( if (!isRunning) { dispatcher.register(this) dispatcher.start + if (isTransactor) { + _transactionFactory = Some(TransactionFactory(_transactionConfig, id)) + } _isRunning = true if (!isInInitialization) initializeActorInstance else runActorInitialization = true @@ -922,6 +951,7 @@ sealed class LocalActorRef private[akka]( def stop = guard.withGuard { if (isRunning) { dispatcher.unregister(this) + _transactionFactory = None _isRunning = false _isShutDown = true actor.shutdown @@ -1178,7 +1208,7 @@ sealed class LocalActorRef private[akka]( /** * Callback for the dispatcher. This is the ingle entry point to the user Actor implementation. */ - protected[akka] def invoke(messageHandle: MessageInvocation): Unit = actor.synchronized { + protected[akka] def invoke(messageHandle: MessageInvocation): Unit = actor.synchronized { if (isShutdown) { Actor.log.warning("Actor [%s] is shut down, ignoring message [%s]", toString, messageHandle) return @@ -1186,8 +1216,7 @@ sealed class LocalActorRef private[akka]( sender = messageHandle.sender senderFuture = messageHandle.senderFuture try { - if (TransactionManagement.isTransactionalityEnabled) transactionalDispatch(messageHandle) - else dispatch(messageHandle) + dispatch(messageHandle) } catch { case e => Actor.log.error(e, "Could not invoke actor [%s]", this) @@ -1196,23 +1225,6 @@ sealed class LocalActorRef private[akka]( } private def dispatch[T](messageHandle: MessageInvocation) = { - val message = messageHandle.message //serializeMessage(messageHandle.message) - setTransactionSet(messageHandle.transactionSet) - try { - actor.base(message) - } catch { - case e => - _isBeingRestarted = true - Actor.log.error(e, "Could not invoke actor [%s]", toString) - // FIXME to fix supervisor restart of remote actor for oneway calls, inject a supervisor proxy that can send notification back to client - if (_supervisor.isDefined) _supervisor.get ! Exit(this, e) - senderFuture.foreach(_.completeWithException(this, e)) - } finally { - clearTransaction - } - } - - private def transactionalDispatch[T](messageHandle: MessageInvocation) = { val message = messageHandle.message //serializeMessage(messageHandle.message) var topLevelTransaction = false val txSet: Option[CountDownCommitBarrier] = @@ -1230,7 +1242,8 @@ sealed class LocalActorRef private[akka]( try { if (isTransactor) { - atomic { + val txFactory = _transactionFactory.getOrElse(DefaultGlobalTransactionFactory) + atomic(txFactory) { actor.base(message) setTransactionSet(txSet) // restore transaction set to allow atomic block to do commit } @@ -1470,6 +1483,8 @@ private[akka] case class RemoteActorRef private[akka] ( def dispatcher_=(md: MessageDispatcher): Unit = unsupported def dispatcher: MessageDispatcher = unsupported def makeTransactionRequired: Unit = unsupported + def transactionConfig_=(config: TransactionConfig): Unit = unsupported + def transactionConfig: TransactionConfig = unsupported def makeRemote(hostname: String, port: Int): Unit = unsupported def makeRemote(address: InetSocketAddress): Unit = unsupported def homeAddress_=(address: InetSocketAddress): Unit = unsupported diff --git a/akka-core/src/main/scala/stm/DataFlowVariable.scala b/akka-core/src/main/scala/dataflow/DataFlowVariable.scala similarity index 97% rename from akka-core/src/main/scala/stm/DataFlowVariable.scala rename to akka-core/src/main/scala/dataflow/DataFlowVariable.scala index 752c71cead..baf3e33f6e 100644 --- a/akka-core/src/main/scala/stm/DataFlowVariable.scala +++ b/akka-core/src/main/scala/dataflow/DataFlowVariable.scala @@ -2,7 +2,7 @@ * Copyright (C) 2009-2010 Scalable Solutions AB */ -package se.scalablesolutions.akka.stm +package se.scalablesolutions.akka.dataflow import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.{ConcurrentLinkedQueue, LinkedBlockingQueue} @@ -102,10 +102,10 @@ import se.scalablesolutions.akka.dispatch.CompletableFuture else { val out = actorOf(new Out(this)).start blockedReaders.offer(out) - val result = out !! Get + val result = (out !! Get).as[T] out ! Exit - result.getOrElse(throw new DataFlowVariableException( - "Timed out (after " + TIME_OUT + " milliseconds) while waiting for result")) + if (result.isDefined) result.get + else throw new DataFlowVariableException("Timed out (after " + TIME_OUT + " milliseconds) while waiting for result") } } diff --git a/akka-core/src/main/scala/remote/RemoteServer.scala b/akka-core/src/main/scala/remote/RemoteServer.scala index 0d1540ab9a..54dfa4f075 100644 --- a/akka-core/src/main/scala/remote/RemoteServer.scala +++ b/akka-core/src/main/scala/remote/RemoteServer.scala @@ -10,6 +10,7 @@ import java.util.concurrent.{ConcurrentHashMap, Executors} import java.util.{Map => JMap} import se.scalablesolutions.akka.actor._ +import se.scalablesolutions.akka.actor.Actor._ import se.scalablesolutions.akka.util._ import se.scalablesolutions.akka.remote.protocol.RemoteProtocol._ import se.scalablesolutions.akka.config.Config.config @@ -369,8 +370,8 @@ class RemoteServerHandler( if (request.getIsOneWay) actorRef.!(message)(sender) else { try { - val resultOrNone = actorRef.!!(message)(sender) - val result: AnyRef = if (resultOrNone.isDefined) resultOrNone.get else null + val resultOrNone = (actorRef.!!(message)(sender)).as[AnyRef] + val result = if (resultOrNone.isDefined) resultOrNone.get else null log.debug("Returning result from actor invocation [%s]", result) val replyBuilder = RemoteReplyProtocol.newBuilder .setId(request.getId) diff --git a/akka-core/src/main/scala/stm/HashTrie.scala b/akka-core/src/main/scala/stm/HashTrie.scala deleted file mode 100644 index b1cd992428..0000000000 --- a/akka-core/src/main/scala/stm/HashTrie.scala +++ /dev/null @@ -1,364 +0,0 @@ -/** - Copyright (c) 2007-2008, Rich Hickey - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of Clojure nor the names of its contributors - may be used to endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - */ - -package se.scalablesolutions.akka.stm - -trait PersistentDataStructure - -/** - * A clean-room port of Rich Hickey's persistent hash trie implementation from - * Clojure (http://clojure.org). Originally presented as a mutable structure in - * a paper by Phil Bagwell. - * - * @author Daniel Spiewak - * @author Rich Hickey - */ -@serializable -final class HashTrie[K, +V] private (root: Node[K, V]) extends Map[K, V] with PersistentDataStructure { - override lazy val size = root.size - - def this() = this(new EmptyNode[K]) - - def get(key: K) = root(key, key.hashCode) - - override def +[A >: V](pair: (K, A)) = update(pair._1, pair._2) - - override def update[A >: V](key: K, value: A) = new HashTrie(root(0, key, key.hashCode) = value) - - def -(key: K) = new HashTrie(root.remove(key, key.hashCode)) - - def iterator = root.elements - - def empty[A]: HashTrie[K, A] = new HashTrie(new EmptyNode[K]) - - def diagnose = root.toString -} - -object HashTrie { - def apply[K, V](pairs: (K, V)*) = pairs.foldLeft(new HashTrie[K, V]) { _ + _ } - - def unapplySeq[K, V](map: HashTrie[K, V]) = map.toSeq -} - -// ============================================================================ -// nodes - -@serializable -private[stm] sealed trait Node[K, +V] { - val size: Int - - def apply(key: K, hash: Int): Option[V] - - def update[A >: V](shift: Int, key: K, hash: Int, value: A): Node[K, A] - - def remove(key: K, hash: Int): Node[K, V] - - def elements: Iterator[(K, V)] -} - -@serializable -private[stm] class EmptyNode[K] extends Node[K, Nothing] { - val size = 0 - - def apply(key: K, hash: Int) = None - - def update[V](shift: Int, key: K, hash: Int, value: V) = new LeafNode(key, hash, value) - - def remove(key: K, hash: Int) = this - - lazy val elements = new Iterator[(K, Nothing)] { - val hasNext = false - - val next = null - } -} - -private[stm] abstract class SingleNode[K, +V] extends Node[K, V] { - val hash: Int -} - - -private[stm] class LeafNode[K, +V](key: K, val hash: Int, value: V) extends SingleNode[K, V] { - val size = 1 - - def apply(key: K, hash: Int) = if (this.key == key) Some(value) else None - - def update[A >: V](shift: Int, key: K, hash: Int, value: A) = { - if (this.key == key) { - if (this.value == value) this else new LeafNode(key, hash, value) - } else if (this.hash == hash) { - new CollisionNode(hash, this.key -> this.value, key -> value) - } else { - BitmappedNode(shift)(this, key, hash, value) - } - } - - def remove(key: K, hash: Int) = if (this.key == key) new EmptyNode[K] else this - - def elements = new Iterator[(K, V)] { - var hasNext = true - - def next = { - hasNext = false - (key, value) - } - } - - override def toString = "LeafNode(" + key + " -> " + value + ")" -} - - -private[stm] class CollisionNode[K, +V](val hash: Int, bucket: List[(K, V)]) extends SingleNode[K, V] { - lazy val size = bucket.length - - def this(hash: Int, pairs: (K, V)*) = this(hash, pairs.toList) - - def apply(key: K, hash: Int) = { - for { - (_, v) <- bucket find { case (k, _) => k == key } - } yield v - } - - override def update[A >: V](shift: Int, key: K, hash: Int, value: A): Node[K, A] = { - if (this.hash == hash) { - var found = false - - val newBucket = for ((k, v) <- bucket) yield { - if (k == key) { - found = true - (key, value) - } else (k, v) - } - - new CollisionNode(hash, if (found) newBucket else (key, value) :: bucket) - } else { - BitmappedNode(shift)(this, key, hash, value) - } - } - - override def remove(key: K, hash: Int) = { - val newBucket = bucket filter { case (k, _) => k != key } - - if (newBucket.length == bucket.length) this else { - if (newBucket.length == 1) { - val (key, value) = newBucket.head - new LeafNode(key, hash, value) - } else new CollisionNode(hash, newBucket) - } - } - - def iterator = bucket.iterator - - def elements = bucket.iterator - - override def toString = "CollisionNode(" + bucket.toString + ")" -} - -private[stm] class BitmappedNode[K, +V](shift: Int)(table: Array[Node[K, V]], bits: Int) extends Node[K, V] { - lazy val size = { - val sizes = for { - n <- table - if n != null - } yield n.size - - sizes.foldLeft(0) { _ + _ } - } - - def apply(key: K, hash: Int) = { - val i = (hash >>> shift) & 0x01f - val mask = 1 << i - - if ((bits & mask) == mask) table(i)(key, hash) else None - } - - override def update[A >: V](levelShift: Int, key: K, hash: Int, value: A): Node[K, A] = { - val i = (hash >>> shift) & 0x01f - val mask = 1 << i - - if ((bits & mask) == mask) { - val node = (table(i)(shift + 5, key, hash) = value) - - if (node == table(i)) this else { - val newTable = new Array[Node[K, A]](table.length) - Array.copy(table, 0, newTable, 0, table.length) - - newTable(i) = node - - new BitmappedNode(shift)(newTable, bits) - } - } else { - val newTable = new Array[Node[K, A]](math.max(table.length, i + 1)) - Array.copy(table, 0, newTable, 0, table.length) - - newTable(i) = new LeafNode(key, hash, value) - - val newBits = bits | mask - if (newBits == ~0) { - new FullNode(shift)(newTable) - } else { - new BitmappedNode(shift)(newTable, newBits) - } - } - } - - def remove(key: K, hash: Int) = { - val i = (hash >>> shift) & 0x01f - val mask = 1 << i - - if ((bits & mask) == mask) { - val node = table(i).remove(key, hash) - - if (node == table(i)) { - this - } else if (node.isInstanceOf[EmptyNode[_]]) { - if (size == 1) new EmptyNode[K] else { - val adjustedBits = bits ^ mask - val log = math.log(adjustedBits) / math.log(2) - - if (log.toInt.toDouble == log) { // last one - table(log.toInt) - } else { - val newTable = new Array[Node[K, V]](table.length) - Array.copy(table, 0, newTable, 0, newTable.length) - - newTable(i) = null - - new BitmappedNode(shift)(newTable, adjustedBits) - } - } - } else { - val newTable = new Array[Node[K, V]](table.length) - Array.copy(table, 0, newTable, 0, table.length) - - newTable(i) = node - - new BitmappedNode(shift)(newTable, bits) - } - } else this - } - - def elements = { - table.foldLeft(emptyElements) { (it, e) => - if (e eq null) it else it ++ e.elements - } - } - - override def toString = "BitmappedNode(" + size + "," + table.filter(_ ne null).toList.toString + ")" - - private lazy val emptyElements: Iterator[(K, V)] = new Iterator[(K, V)] { - val hasNext = false - - val next = null - } -} - - -private[stm] object BitmappedNode { - def apply[K, V](shift: Int)(node: SingleNode[K, V], key: K, hash: Int, value: V) = { - val table = new Array[Node[K, V]](math.max((hash >>> shift) & 0x01f, (node.hash >>> shift) & 0x01f) + 1) - - val preBits = { - val i = (node.hash >>> shift) & 0x01f - table(i) = node - 1 << i - } - - val bits = { - val i = (hash >>> shift) & 0x01f - val mask = 1 << i - - if ((preBits & mask) == mask) { - table(i) = (table(i)(shift + 5, key, hash) = value) - } else { - table(i) = new LeafNode(key, hash, value) - } - - preBits | mask - } - - new BitmappedNode(shift)(table, bits) - } -} - - -private[stm] class FullNode[K, +V](shift: Int)(table: Array[Node[K, V]]) extends Node[K, V] { - lazy val size = table.foldLeft(0) { _ + _.size } - - def apply(key: K, hash: Int) = table((hash >>> shift) & 0x01f)(key, hash) - - def update[A >: V](levelShift: Int, key: K, hash: Int, value: A) = { - val i = (hash >>> shift) & 0x01f - - val node = (table(i)(shift + 5, key, hash) = value) - - if (node == table(i)) this else { - val newTable = new Array[Node[K, A]](32) - Array.copy(table, 0, newTable, 0, 32) - - newTable(i) = node - - new FullNode(shift)(newTable) - } - } - - def remove(key: K, hash: Int) = { - val i = (hash >>> shift) & 0x01f - val mask = 1 << i - - val node = table(i).remove(key, hash) - - if (node == table(i)) this else { - val newTable = new Array[Node[K, V]](32) - Array.copy(table, 0, newTable, 0, 32) - - if (node.isInstanceOf[EmptyNode[_]]) { - newTable(i) = null - new BitmappedNode(shift)(newTable, ~mask) - } else { - newTable(i) = node - new FullNode(shift)(newTable) - } - } - } - - def elements = table.foldLeft(emptyElements) { _ ++ _.elements } - - override def toString = "FullNode(" + table.foldLeft("") { _.toString + ", " + _.toString } + ")" - - private lazy val emptyElements: Iterator[(K, V)] = new Iterator[(K, V)] { - val hasNext = false - - val next = null - } -} diff --git a/akka-core/src/main/scala/stm/Ref.scala b/akka-core/src/main/scala/stm/Ref.scala new file mode 100644 index 0000000000..3b13d32971 --- /dev/null +++ b/akka-core/src/main/scala/stm/Ref.scala @@ -0,0 +1,150 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package se.scalablesolutions.akka.stm + +import se.scalablesolutions.akka.util.UUID + +import org.multiverse.api.GlobalStmInstance.getGlobalStmInstance + +object RefFactory { + private val factory = getGlobalStmInstance.getProgrammaticRefFactoryBuilder.build + + def createRef[T] = factory.atomicCreateRef[T]() + + def createRef[T](value: T) = factory.atomicCreateRef(value) +} + +/** + * Ref. + * + * @author Jonas Bonér + */ +object Ref { + def apply[T]() = new Ref[T] + + def apply[T](initialValue: T) = new Ref[T](Some(initialValue)) + + /** + * An implicit conversion that converts a Ref to an Iterable value. + */ + implicit def ref2Iterable[T](ref: Ref[T]): Iterable[T] = ref.toList +} + +/** + * Implements a transactional managed reference. + * + * @author Jonas Bonér + */ +class Ref[T](initialOpt: Option[T] = None) extends Transactional { + self => + + def this() = this(None) // Java compatibility + + import org.multiverse.api.ThreadLocalTransaction._ + + val uuid = UUID.newUuid.toString + + private[this] val ref = { + if (initialOpt.isDefined) RefFactory.createRef(initialOpt.get) + else RefFactory.createRef[T] + } + + def swap(elem: T) = { + ensureIsInTransaction + ref.set(elem) + } + + def alter(f: T => T): T = { + ensureIsInTransaction + ensureNotNull + ref.set(f(ref.get)) + ref.get + } + + def get: Option[T] = { + ensureIsInTransaction + if (ref.isNull) None + else Some(ref.get) + } + + def getOrWait: T = { + ensureIsInTransaction + ref.getOrAwait + } + + def getOrElse(default: => T): T = { + ensureIsInTransaction + if (ref.isNull) default + else ref.get + } + + def isDefined: Boolean = { + ensureIsInTransaction + !ref.isNull + } + + def isEmpty: Boolean = { + ensureIsInTransaction + ref.isNull + } + + def map[B](f: T => B): Ref[B] = { + ensureIsInTransaction + if (isEmpty) Ref[B] else Ref(f(ref.get)) + } + + def flatMap[B](f: T => Ref[B]): Ref[B] = { + ensureIsInTransaction + if (isEmpty) Ref[B] else f(ref.get) + } + + def filter(p: T => Boolean): Ref[T] = { + ensureIsInTransaction + if (isDefined && p(ref.get)) Ref(ref.get) else Ref[T] + } + + /** + * Necessary to keep from being implicitly converted to Iterable in for comprehensions. + */ + def withFilter(p: T => Boolean): WithFilter = new WithFilter(p) + + class WithFilter(p: T => Boolean) { + def map[B](f: T => B): Ref[B] = self filter p map f + def flatMap[B](f: T => Ref[B]): Ref[B] = self filter p flatMap f + def foreach[U](f: T => U): Unit = self filter p foreach f + def withFilter(q: T => Boolean): WithFilter = new WithFilter(x => p(x) && q(x)) + } + + def foreach[U](f: T => U): Unit = { + ensureIsInTransaction + if (isDefined) f(ref.get) + } + + def elements: Iterator[T] = { + ensureIsInTransaction + if (isEmpty) Iterator.empty else Iterator(ref.get) + } + + def toList: List[T] = { + ensureIsInTransaction + if (isEmpty) List() else List(ref.get) + } + + def toRight[X](left: => X) = { + ensureIsInTransaction + if (isEmpty) Left(left) else Right(ref.get) + } + + def toLeft[X](right: => X) = { + ensureIsInTransaction + if (isEmpty) Right(right) else Left(ref.get) + } + + private def ensureIsInTransaction = + if (getThreadLocalTransaction eq null) throw new NoTransactionInScopeException + + private def ensureNotNull = + if (ref.isNull) throw new RuntimeException("Cannot alter Ref's value when it is null") +} diff --git a/akka-core/src/main/scala/stm/ResultOrFailure.scala b/akka-core/src/main/scala/stm/ResultOrFailure.scala deleted file mode 100644 index 6ddf8c9751..0000000000 --- a/akka-core/src/main/scala/stm/ResultOrFailure.scala +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (C) 2009-2010 Scalable Solutions AB - */ - -package se.scalablesolutions.akka.stm - -/** - * Reference that can hold either a typed value or an exception. - * - * Usage: - *
- * scala> ResultOrFailure(1)
- * res0: ResultOrFailure[Int] = ResultOrFailure@a96606
- *
- * scala> res0()
- * res1: Int = 1
- *
- * scala> res0() = 3
- *
- * scala> res0()
- * res3: Int = 3
- *
- * scala> res0() = { println("Hello world"); 3}
- * Hello world
- *
- * scala> res0()
- * res5: Int = 3
- *
- * scala> res0() = error("Lets see what happens here...")
- *
- * scala> res0()
- * java.lang.RuntimeException: Lets see what happens here...
- *      at ResultOrFailure.apply(RefExcept.scala:11)
- *      at .(:6)
- *      at .()
- *      at Re...
- * 
- * - * @author Jonas Bonér - */ -class ResultOrFailure[Payload](payload: Payload, val tx: Option[Transaction]) { - private[this] var contents: Either[Throwable, Payload] = Right(payload) - - def update(value: => Payload) = { - contents = try { Right(value) } catch { case (e : Throwable) => Left(e) } - } - - def apply() = contents match { - case Right(payload) => payload - case Left(e) => throw e - } - - override def toString(): String = "ResultOrFailure[" + contents + "]" -} -object ResultOrFailure { - def apply[Payload](payload: Payload, tx: Option[Transaction]) = new ResultOrFailure(payload, tx) - def apply[AnyRef](tx: Option[Transaction]) = new ResultOrFailure(new Object, tx) -} diff --git a/akka-core/src/main/scala/stm/Transaction.scala b/akka-core/src/main/scala/stm/Transaction.scala index cdcd4ad088..b4fb0cda4c 100644 --- a/akka-core/src/main/scala/stm/Transaction.scala +++ b/akka-core/src/main/scala/stm/Transaction.scala @@ -6,7 +6,6 @@ package se.scalablesolutions.akka.stm import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.TimeUnit import javax.transaction.{TransactionManager, UserTransaction, Status, TransactionSynchronizationRegistry} @@ -17,283 +16,51 @@ import se.scalablesolutions.akka.config.Config._ import org.multiverse.api.{Transaction => MultiverseTransaction} import org.multiverse.api.lifecycle.{TransactionLifecycleListener, TransactionLifecycleEvent} -import org.multiverse.api.GlobalStmInstance.getGlobalStmInstance import org.multiverse.api.ThreadLocalTransaction._ -import org.multiverse.templates.{TransactionTemplate, OrElseTemplate} -import org.multiverse.api.backoff.ExponentialBackoffPolicy -import org.multiverse.stms.alpha.AlphaStm +import org.multiverse.api.{TraceLevel => MultiverseTraceLevel} class NoTransactionInScopeException extends RuntimeException class TransactionRetryException(message: String) extends RuntimeException(message) class StmConfigurationException(message: String) extends RuntimeException(message) -/** - * FIXDOC: document AtomicTemplate - * AtomicTemplate can be used to create atomic blocks from Java code. - *
- * User newUser = new AtomicTemplate[User]() {
- *   User atomic() {
- *     ... // create user atomically
- *     return user;
- *   }
- * }.execute();
- * 
- */ -trait AtomicTemplate[T] { - def atomic: T - def execute: T = Transaction.Local.atomic { - atomic - } -} - object Transaction { val idFactory = new AtomicLong(-1L) - /** - * Creates a STM atomic transaction and by-passes all transactions hooks - * such as persistence etc. - * - * Only for internal usage. - */ - private[akka] def atomic0[T](body: => T): T = new TransactionTemplate[T]() { - def execute(mtx: MultiverseTransaction): T = body - }.execute() + @deprecated("Use the se.scalablesolutions.akka.stm.local package object instead.") + object Local extends LocalStm + + @deprecated("Use the se.scalablesolutions.akka.stm.global package object instead.") + object Global extends GlobalStm + + object Util extends StmUtil /** - * Module for "local" transaction management, local in the context of threads. - * You should only use these if you do not need to have one transaction span - * multiple threads (or Actors). - *

- * Example of atomic transaction management using the atomic block. - *

- *

-   * import se.scalablesolutions.akka.stm.Transaction.Local._
-   *
-   * atomic  {
-   *   .. // do something within a transaction
-   * }
-   * 
- * - * Example of atomically-orElse transaction management. - * Which is a good way to reduce contention and transaction collisions. - *
-   * import se.scalablesolutions.akka.stm.Transaction.Local._
-   *
-   * atomically  {
-   *   .. // try to do something
-   * } orElse  {
-   *   .. // if transaction clashes try do do something else to minimize contention
-   * }
-   * 
- * - * Example of atomic transaction management using for comprehensions (monadic): - * - *
-   * import se.scalablesolutions.akka.stm.Transaction.Local._
-   * for (tx <- Transaction.Local)  {
-   *   ... // do transactional stuff
-   * }
-   *
-   * val result = for (tx <- Transaction.Local) yield  {
-   *   ... // do transactional stuff yielding a result
-   * }
-   * 
- * - * Example of using Transaction and TransactionalRef in for comprehensions (monadic): - * - *
-   * // For example, if you have a List with TransactionalRef
-   * val refs: List[TransactionalRef] = ...
-   *
-   * // You can use them together with Transaction in a for comprehension since
-   * // TransactionalRef is also monadic
-   * for  {
-   *   tx <- Transaction.Local
-   *   ref <- refs
-   * } {
-   *   ... // use the ref inside a transaction
-   * }
-   *
-   * val result = for  {
-   *   tx <- Transaction.Local
-   *   ref <- refs
-   * } yield  {
-   *   ... // use the ref inside a transaction, yield a result
-   * }
-   * 
- * - * @author Jonas Bonér + * Attach an Akka-specific Transaction to the current Multiverse transaction. + * Must be called within a Multiverse transaction. Used by TransactionFactory.addHooks */ - object Local extends TransactionManagement with Logging { - - /** - * See ScalaDoc on Transaction.Local class. - */ - def map[T](f: => T): T = atomic {f} - - /** - * See ScalaDoc on Transaction.Local class. - */ - def flatMap[T](f: => T): T = atomic {f} - - /** - * See ScalaDoc on Transaction.Local class. - */ - def foreach(f: => Unit): Unit = atomic {f} - - /** - * See ScalaDoc on Transaction.Local class. - */ - def atomic[T](body: => T): T = { - new TransactionTemplate[T]() { - def execute(mtx: MultiverseTransaction): T = body - - override def onStart(mtx: MultiverseTransaction) = { - val tx = new Transaction - tx.transaction = Some(mtx) - setTransaction(Some(tx)) - mtx.registerLifecycleListener(new TransactionLifecycleListener() { - def notify(mtx: MultiverseTransaction, event: TransactionLifecycleEvent) = event.name match { - case "postCommit" => tx.commit - case "postAbort" => tx.abort - case _ => {} - } - }) - } - }.execute() - } - - /** - * See ScalaDoc on Transaction.Local class. - */ - def atomically[A](firstBody: => A) = elseBody(firstBody) - - /** - * Should only be used together with atomically to form atomically-orElse constructs. - * See ScalaDoc on class. - */ - def elseBody[A](firstBody: => A) = new { - def orElse(secondBody: => A) = new OrElseTemplate[A] { - def run(t: MultiverseTransaction) = firstBody - def orelserun(t: MultiverseTransaction) = secondBody - }.execute() - } + private[akka] def attach = { + val mtx = getRequiredThreadLocalTransaction + val tx = new Transaction + tx.begin + tx.transaction = Some(mtx) + TransactionManagement.transaction.set(Some(tx)) + mtx.registerLifecycleListener(new TransactionLifecycleListener() { + def notify(mtx: MultiverseTransaction, event: TransactionLifecycleEvent) = event match { + case TransactionLifecycleEvent.PostCommit => tx.commit + case TransactionLifecycleEvent.PostAbort => tx.abort + case _ => {} + } + }) } /** - * Module for "global" transaction management, global in the context of multiple threads. - * You have to use these if you do need to have one transaction span multiple threads (or Actors). - *

- * Example of atomic transaction management using the atomic block. - *

- * Here are some examples (assuming implicit transaction family name in scope): - *

-   * import se.scalablesolutions.akka.stm.Transaction.Global._
-   *
-   * atomic  {
-   *   .. // do something within a transaction
-   * }
-   * 
- * - * Example of atomic transaction management using for comprehensions (monadic): - * - *
-   * import se.scalablesolutions.akka.stm.Transaction
-   * for (tx <- Transaction.Global)  {
-   *   ... // do transactional stuff
-   * }
-   *
-   * val result = for (tx <- Transaction.Global) yield  {
-   *   ... // do transactional stuff yielding a result
-   * }
-   * 
- * - * Example of using Transaction and TransactionalRef in for comprehensions (monadic): - * - *
-   * // For example, if you have a List with TransactionalRef
-   * val refs: List[TransactionalRef] = ...
-   *
-   * // You can use them together with Transaction in a for comprehension since
-   * // TransactionalRef is also monadic
-   * for  {
-   *   tx <- Transaction.Global
-   *   ref <- refs
-   * } {
-   *   ... // use the ref inside a transaction
-   * }
-   *
-   * val result = for  {
-   *   tx <- Transaction.Global
-   *   ref <- refs
-   * } yield  {
-   *   ... // use the ref inside a transaction, yield a result
-   * }
-   * 
- * - * @author Jonas Bonér + * Mapping to Multiverse TraceLevel. */ - object Global extends TransactionManagement with Logging { - - /** - * See ScalaDoc on Transaction.Global class. - */ - def map[T](f: => T): T = atomic {f} - - /** - * See ScalaDoc on Transaction.Global class. - */ - def flatMap[T](f: => T): T = atomic {f} - - /** - * See ScalaDoc on Transaction.Global class. - */ - def foreach(f: => Unit): Unit = atomic {f} - - -// FIXME tryJoinCommit(mtx, TransactionManagement.TRANSACTION_TIMEOUT, TimeUnit.MILLISECONDS) -//getTransactionSetInScope.tryJoinCommit(mtx, TransactionManagement.TRANSACTION_TIMEOUT, TimeUnit.MILLISECONDS) - - /** - * See ScalaDoc on Transaction.Global class. - */ - def atomic[T](body: => T): T = { - var isTopLevelTransaction = false - new TransactionTemplate[T]() { - def execute(mtx: MultiverseTransaction): T = { - val result = body - - val txSet = getTransactionSetInScope - log.trace("Committing transaction [%s]\n\tby joining transaction set [%s]", mtx, txSet) - txSet.joinCommit(mtx) - clearTransaction - result - } - - override def onStart(mtx: MultiverseTransaction) = { - val txSet = - if (!isTransactionSetInScope) { - isTopLevelTransaction = true - createNewTransactionSet - } else getTransactionSetInScope - val tx = new Transaction - tx.begin - tx.transaction = Some(mtx) - setTransaction(Some(tx)) - mtx.registerLifecycleListener(new TransactionLifecycleListener() { - def notify(mtx: MultiverseTransaction, event: TransactionLifecycleEvent) = event.name match { - case "postCommit" => - log.trace("Committing transaction [%s]", mtx) - tx.commit - case "postAbort" => - log.trace("Aborting transaction [%s]", mtx) - tx.abort - case _ => {} - } - }) - } - }.execute() - } + object TraceLevel { + val None = MultiverseTraceLevel.none + val Coarse = MultiverseTraceLevel.course // mispelling? + val Course = MultiverseTraceLevel.course + val Fine = MultiverseTraceLevel.fine } } @@ -412,3 +179,25 @@ object TransactionStatus { case object Completed extends TransactionStatus } +/** + * @author Jonas Bonér + */ +@serializable +trait Transactional { + val uuid: String +} + +/** + * @author Jonas Bonér + */ +trait Committable { + def commit: Unit +} + +/** + * @author Jonas Bonér + */ +trait Abortable { + def abort: Unit +} + diff --git a/akka-core/src/main/scala/stm/TransactionFactory.scala b/akka-core/src/main/scala/stm/TransactionFactory.scala new file mode 100644 index 0000000000..a7a81c4212 --- /dev/null +++ b/akka-core/src/main/scala/stm/TransactionFactory.scala @@ -0,0 +1,187 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package se.scalablesolutions.akka.stm + +import java.lang.{Boolean => JBoolean} + +import se.scalablesolutions.akka.config.Config._ +import se.scalablesolutions.akka.util.Duration + +import org.multiverse.api.GlobalStmInstance.getGlobalStmInstance +import org.multiverse.stms.alpha.AlphaStm +import org.multiverse.templates.TransactionBoilerplate +import org.multiverse.api.TraceLevel + +/** + * For configuring multiverse transactions. + */ +object TransactionConfig { + // note: null values are so that we can default to Multiverse inference when not set + val FAMILY_NAME = "DefaultTransaction" + val READONLY = null.asInstanceOf[JBoolean] + val MAX_RETRIES = config.getInt("akka.stm.max-retries", 1000) + val TIMEOUT = config.getLong("akka.stm.timeout", Long.MaxValue) + val TIME_UNIT = config.getString("akka.stm.time-unit", "seconds") + val TRACK_READS = null.asInstanceOf[JBoolean] + val WRITE_SKEW = config.getBool("akka.stm.write-skew", true) + val EXPLICIT_RETRIES = config.getBool("akka.stm.explicit-retries", false) + val INTERRUPTIBLE = config.getBool("akka.stm.interruptible", false) + val SPECULATIVE = config.getBool("akka.stm.speculative", true) + val QUICK_RELEASE = config.getBool("akka.stm.quick-release", true) + val TRACE_LEVEL = traceLevel(config.getString("akka.stm.trace-level", "none")) + val HOOKS = config.getBool("akka.stm.hooks", true) + + val DefaultTimeout = Duration(TIMEOUT, TIME_UNIT) + + def traceLevel(level: String) = level.toLowerCase match { + case "coarse" | "course" => Transaction.TraceLevel.Coarse + case "fine" => Transaction.TraceLevel.Fine + case _ => Transaction.TraceLevel.None + } + + /** + * For configuring multiverse transactions. + * + * @param familyName Family name for transactions. Useful for debugging. + * @param readonly Sets transaction as readonly. Readonly transactions are cheaper. + * @param maxRetries The maximum number of times a transaction will retry. + * @param timeout The maximum time a transaction will block for. + * @param trackReads Whether all reads should be tracked. Needed for blocking operations. + * @param writeSkew Whether writeskew is allowed. Disable with care. + * @param explicitRetries Whether explicit retries are allowed. + * @param interruptible Whether a blocking transaction can be interrupted. + * @param speculative Whether speculative configuration should be enabled. + * @param quickRelease Whether locks should be released as quickly as possible (before whole commit). + * @param traceLevel Transaction trace level. + * @param hooks Whether hooks for persistence modules and JTA should be added to the transaction. + */ + def apply(familyName: String = FAMILY_NAME, + readonly: JBoolean = READONLY, + maxRetries: Int = MAX_RETRIES, + timeout: Duration = DefaultTimeout, + trackReads: JBoolean = TRACK_READS, + writeSkew: Boolean = WRITE_SKEW, + explicitRetries: Boolean = EXPLICIT_RETRIES, + interruptible: Boolean = INTERRUPTIBLE, + speculative: Boolean = SPECULATIVE, + quickRelease: Boolean = QUICK_RELEASE, + traceLevel: TraceLevel = TRACE_LEVEL, + hooks: Boolean = HOOKS) = { + new TransactionConfig(familyName, readonly, maxRetries, timeout, trackReads, writeSkew, + explicitRetries, interruptible, speculative, quickRelease, traceLevel, hooks) + } +} + +/** + * For configuring multiverse transactions. + * + *

familyName - Family name for transactions. Useful for debugging. + *

readonly - Sets transaction as readonly. Readonly transactions are cheaper. + *

maxRetries - The maximum number of times a transaction will retry. + *

timeout - The maximum time a transaction will block for. + *

trackReads - Whether all reads should be tracked. Needed for blocking operations. + *

writeSkew - Whether writeskew is allowed. Disable with care. + *

explicitRetries - Whether explicit retries are allowed. + *

interruptible - Whether a blocking transaction can be interrupted. + *

speculative - Whether speculative configuration should be enabled. + *

quickRelease - Whether locks should be released as quickly as possible (before whole commit). + *

traceLevel - Transaction trace level. + *

hooks - Whether hooks for persistence modules and JTA should be added to the transaction. + */ +class TransactionConfig(val familyName: String = TransactionConfig.FAMILY_NAME, + val readonly: JBoolean = TransactionConfig.READONLY, + val maxRetries: Int = TransactionConfig.MAX_RETRIES, + val timeout: Duration = TransactionConfig.DefaultTimeout, + val trackReads: JBoolean = TransactionConfig.TRACK_READS, + val writeSkew: Boolean = TransactionConfig.WRITE_SKEW, + val explicitRetries: Boolean = TransactionConfig.EXPLICIT_RETRIES, + val interruptible: Boolean = TransactionConfig.INTERRUPTIBLE, + val speculative: Boolean = TransactionConfig.SPECULATIVE, + val quickRelease: Boolean = TransactionConfig.QUICK_RELEASE, + val traceLevel: TraceLevel = TransactionConfig.TRACE_LEVEL, + val hooks: Boolean = TransactionConfig.HOOKS) + +object DefaultTransactionConfig extends TransactionConfig + +/** + * Wrapper for transaction config, factory, and boilerplate. Used by atomic. + */ +object TransactionFactory { + def apply(config: TransactionConfig) = new TransactionFactory(config) + + def apply(config: TransactionConfig, defaultName: String) = new TransactionFactory(config, defaultName) + + def apply(familyName: String = TransactionConfig.FAMILY_NAME, + readonly: JBoolean = TransactionConfig.READONLY, + maxRetries: Int = TransactionConfig.MAX_RETRIES, + timeout: Duration = TransactionConfig.DefaultTimeout, + trackReads: JBoolean = TransactionConfig.TRACK_READS, + writeSkew: Boolean = TransactionConfig.WRITE_SKEW, + explicitRetries: Boolean = TransactionConfig.EXPLICIT_RETRIES, + interruptible: Boolean = TransactionConfig.INTERRUPTIBLE, + speculative: Boolean = TransactionConfig.SPECULATIVE, + quickRelease: Boolean = TransactionConfig.QUICK_RELEASE, + traceLevel: TraceLevel = TransactionConfig.TRACE_LEVEL, + hooks: Boolean = TransactionConfig.HOOKS) = { + val config = new TransactionConfig(familyName, readonly, maxRetries, timeout, trackReads, writeSkew, + explicitRetries, interruptible, speculative, quickRelease, traceLevel, hooks) + new TransactionFactory(config) + } +} + +/** + * Wrapper for transaction config, factory, and boilerplate. Used by atomic. + * Can be passed to atomic implicitly or explicitly. + *

+ *

+ * implicit val txFactory = TransactionFactory(readonly = true)
+ * ...
+ * atomic {
+ *   // do something within a readonly transaction
+ * }
+ * 
+ *

+ * Can be created at different levels as needed. For example: as an implicit object + * used throughout a package, as a static implicit val within a singleton object and + * imported where needed, or as an implicit val within each instance of a class. + *

+ * If no explicit transaction factory is passed to atomic and there is no implicit + * transaction factory in scope, then a default transaction factory is used. + * + * @see TransactionConfig for configuration options. + */ +class TransactionFactory(val config: TransactionConfig = DefaultTransactionConfig, defaultName: String = TransactionConfig.FAMILY_NAME) { + self => + + // use the config family name if it's been set, otherwise defaultName - used by actors to set class name as default + val familyName = if (config.familyName != TransactionConfig.FAMILY_NAME) config.familyName else defaultName + + val factory = { + var builder = (getGlobalStmInstance().asInstanceOf[AlphaStm].getTransactionFactoryBuilder() + .setFamilyName(familyName) + .setMaxRetries(config.maxRetries) + .setTimeoutNs(config.timeout.toNanos) + .setWriteSkewAllowed(config.writeSkew) + .setExplicitRetryAllowed(config.explicitRetries) + .setInterruptible(config.interruptible) + .setSpeculativeConfigurationEnabled(config.speculative) + .setQuickReleaseEnabled(config.quickRelease) + .setTraceLevel(config.traceLevel)) + + if (config.readonly ne null) { + builder = builder.setReadonly(config.readonly.booleanValue) + } // otherwise default to Multiverse inference + + if (config.trackReads ne null) { + builder = builder.setReadTrackingEnabled(config.trackReads.booleanValue) + } // otherwise default to Multiverse inference + + builder.build() + } + + val boilerplate = new TransactionBoilerplate(factory) + + def addHooks = if (config.hooks) Transaction.attach +} diff --git a/akka-core/src/main/scala/stm/TransactionManagement.scala b/akka-core/src/main/scala/stm/TransactionManagement.scala index a6ec288466..e6485ff761 100644 --- a/akka-core/src/main/scala/stm/TransactionManagement.scala +++ b/akka-core/src/main/scala/stm/TransactionManagement.scala @@ -8,8 +8,11 @@ import se.scalablesolutions.akka.util.Logging import java.util.concurrent.atomic.AtomicBoolean +import org.multiverse.api.{StmUtils => MultiverseStmUtils} import org.multiverse.api.ThreadLocalTransaction._ +import org.multiverse.api.{Transaction => MultiverseTransaction} import org.multiverse.commitbarriers.CountDownCommitBarrier +import org.multiverse.templates.{TransactionalCallable, OrElseTemplate} class StmException(msg: String) extends RuntimeException(msg) @@ -20,15 +23,8 @@ class TransactionAwareWrapperException(val cause: Throwable, val tx: Option[Tran object TransactionManagement extends TransactionManagement { import se.scalablesolutions.akka.config.Config._ - val TRANSACTION_ENABLED = new AtomicBoolean(config.getBool("akka.stm.service", true)) - val FAIR_TRANSACTIONS = config.getBool("akka.stm.fair", true) - val INTERRUPTIBLE = config.getBool("akka.stm.interruptible", true) - val MAX_NR_OF_RETRIES = config.getInt("akka.stm.max-nr-of-retries", 1000) - val TRANSACTION_TIMEOUT = config.getInt("akka.stm.timeout", 10000) - val SMART_TX_LENGTH_SELECTOR = config.getBool("akka.stm.smart-tx-length-selector", true) - def isTransactionalityEnabled = TRANSACTION_ENABLED.get - - def disableTransactions = TRANSACTION_ENABLED.set(false) + // move to stm.global.fair? + val FAIR_TRANSACTIONS = config.getBool("akka.stm.fair", true) private[akka] val transactionSet = new ThreadLocal[Option[CountDownCommitBarrier]]() { override protected def initialValue: Option[CountDownCommitBarrier] = None @@ -88,3 +84,103 @@ trait TransactionManagement { (option ne null) && option.isDefined } } + +/** + * Local transaction management, local in the context of threads. + * Use this if you do not need to have one transaction span + * multiple threads (or Actors). + *

+ * Example of atomic transaction management using the atomic block. + *

+ *

+ * import se.scalablesolutions.akka.stm.local._
+ *
+ * atomic  {
+ *   // do something within a transaction
+ * }
+ * 
+ */ +class LocalStm extends TransactionManagement with Logging { + + val DefaultLocalTransactionConfig = TransactionConfig() + val DefaultLocalTransactionFactory = TransactionFactory(DefaultLocalTransactionConfig, "DefaultLocalTransaction") + + def atomic[T](body: => T)(implicit factory: TransactionFactory = DefaultLocalTransactionFactory): T = atomic(factory)(body) + + def atomic[T](factory: TransactionFactory)(body: => T): T = { + factory.boilerplate.execute(new TransactionalCallable[T]() { + def call(mtx: MultiverseTransaction): T = { + factory.addHooks + body + } + }) + } +} + +/** + * Global transaction management, global in the context of multiple threads. + * Use this if you need to have one transaction span multiple threads (or Actors). + *

+ * Example of atomic transaction management using the atomic block: + *

+ *

+ * import se.scalablesolutions.akka.stm.global._
+ *
+ * atomic  {
+ *   // do something within a transaction
+ * }
+ * 
+ */ +class GlobalStm extends TransactionManagement with Logging { + + val DefaultGlobalTransactionConfig = TransactionConfig() + val DefaultGlobalTransactionFactory = TransactionFactory(DefaultGlobalTransactionConfig, "DefaultGlobalTransaction") + + def atomic[T](body: => T)(implicit factory: TransactionFactory = DefaultGlobalTransactionFactory): T = atomic(factory)(body) + + def atomic[T](factory: TransactionFactory)(body: => T): T = { + factory.boilerplate.execute(new TransactionalCallable[T]() { + def call(mtx: MultiverseTransaction): T = { + if (!isTransactionSetInScope) createNewTransactionSet + factory.addHooks + val result = body + val txSet = getTransactionSetInScope + log.trace("Committing transaction [%s]\n\tby joining transaction set [%s]", mtx, txSet) + // FIXME ? txSet.tryJoinCommit(mtx, TransactionManagement.TRANSACTION_TIMEOUT, TimeUnit.MILLISECONDS) + txSet.joinCommit(mtx) + clearTransaction + result + } + }) + } +} + +trait StmUtil { + /** + * Schedule a deferred task on the thread local transaction (use within an atomic). + * This is executed when the transaction commits. + */ + def deferred[T](body: => T): Unit = MultiverseStmUtils.scheduleDeferredTask(new Runnable { def run = body }) + + /** + * Schedule a compensating task on the thread local transaction (use within an atomic). + * This is executed when the transaction aborts. + */ + def compensating[T](body: => T): Unit = MultiverseStmUtils.scheduleCompensatingTask(new Runnable { def run = body }) + + /** + * STM retry for blocking transactions (use within an atomic). + * Can be used to wait for a condition. + */ + def retry = MultiverseStmUtils.retry + + /** + * Use either-orElse to combine two blocking transactions. + */ + def either[T](firstBody: => T) = new { + def orElse(secondBody: => T) = new OrElseTemplate[T] { + def either(mtx: MultiverseTransaction) = firstBody + def orelse(mtx: MultiverseTransaction) = secondBody + }.execute() + } +} diff --git a/akka-core/src/main/scala/stm/TransactionWatcher.scala b/akka-core/src/main/scala/stm/TransactionWatcher.scala deleted file mode 100644 index 400bc9b763..0000000000 --- a/akka-core/src/main/scala/stm/TransactionWatcher.scala +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright (C) 2009-2010 Scalable Solutions AB - */ - -package se.scalablesolutions.akka.stm - -/* -import kernel.util.Logging -import org.apache.zookeeper.jmx.ManagedUtil -import org.apache.zookeeper.server.persistence.FileTxnSnapLog -import org.apache.zookeeper.server.{ServerConfig, NIOServerCnxn} -import org.apache.zookeeper.{KeeperException, WatchedEvent, Watcher, ZooKeeper, DataMonitor} -*/ -/** - * @author Jonas Bonér - * -class TransactionWatcher extends Logging with Watcher { - - val SERVER_URL = "localhost" - - val ZOO_KEEPER_URL = SERVER_URL - val ZOO_KEEPER_PORT = 2181 - val znode = "master" - - private[this] val db = new scala.collection.mutable.HashMap[String, String] - - private[this] val zk = new ZooKeeper(ZOO_KEEPER_URL + ":" + ZOO_KEEPER_PORT, 3000, this) - private[this] val dm = new DataMonitor(zk, znode, null, this) - - override def process(event: WatchedEvent) = { - log.debug("New ZooKeeper event: %s", event) - val path = event.getPath(); - if (event.getType == Event.EventType.None) { - // We are are being told that the state of the connection has changed - event.getState match { - case SyncConnected => - // In this particular example we don't need to do anything - // here - watches are automatically re-registered with - // server and any watches triggered while the client was - // disconnected will be delivered (in order of course) - case Expired => - dead = true - listener.closing(KeeperException.Code.SessionExpired) - } - } else { - if (path != null && path.equals(znode)) { - // Something has changed on the node, let's find out - zk.exists(znode, true, this, null) - } - } - if (chainedWatcher ne null) chainedWatcher.process(event); - } - - - - def run: Unit = synchronized { - try { - while (!dm.dead) wait - } catch { - case e: InterruptedException => Thread.currentThread.interrupt - } - } - - def closing(rc: Int): Unit = synchronized { notifyAll() } -} - - */ -object TransactionWatcher { - def main(args: Array[String]): Unit = { - println("Connecting to ZooKeeper...") - //new TransactionWatcher - } -} - - // private[akka] def startZooKeeper = { - // try { - // ManagedUtil.registerLog4jMBeans - // ServerConfig.parse(args) - // } catch { - // case e: JMException => log.warning("Unable to register log4j JMX control: s%", e) - // case e => log.fatal("Error in ZooKeeper config: s%", e) - // } - // val factory = new ZooKeeperServer.Factory() { - // override def createConnectionFactory = new NIOServerCnxn.Factory(ServerConfig.getClientPort) - // override def createServer = { - // val server = new ZooKeeperServer - // val txLog = new FileTxnSnapLog( - // new File(ServerConfig.getDataLogDir), - // new File(ServerConfig.getDataDir)) - // server.setTxnLogFactory(txLog) - // server - // } - // } - // try { - // val zooKeeper = factory.createServer - // zooKeeper.startup - // log.info("ZooKeeper started") - // // TODO: handle clean shutdown as below in separate thread - // // val cnxnFactory = serverFactory.createConnectionFactory - // // cnxnFactory.setZooKeeperServer(zooKeeper) - // // cnxnFactory.join - // // if (zooKeeper.isRunning) zooKeeper.shutdown - // } catch { case e => log.fatal("Unexpected exception: s%",e) } - // } - diff --git a/akka-core/src/main/scala/stm/TransactionalMap.scala b/akka-core/src/main/scala/stm/TransactionalMap.scala new file mode 100644 index 0000000000..be7b9c5189 --- /dev/null +++ b/akka-core/src/main/scala/stm/TransactionalMap.scala @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package se.scalablesolutions.akka.stm + +import scala.collection.immutable.HashMap + +import se.scalablesolutions.akka.util.UUID + +import org.multiverse.api.ThreadLocalTransaction.getThreadLocalTransaction + +object TransactionalMap { + def apply[K, V]() = new TransactionalMap[K, V] + + def apply[K, V](pairs: (K, V)*) = new TransactionalMap(Some(HashMap(pairs: _*))) +} + +/** + * Transactional map that implements the mutable map interface with an underlying ref and hash map. + * + * @author Jonas Bonér + */ +class TransactionalMap[K, V](initialOpt: Option[HashMap[K, V]] = None) extends Transactional with scala.collection.mutable.Map[K, V] { + def this() = this(None) // Java compatibility + + val uuid = UUID.newUuid.toString + + protected[this] val ref = new Ref(initialOpt.orElse(Some(HashMap[K, V]()))) + + def -=(key: K) = { + remove(key) + this + } + + def +=(key: K, value: V) = put(key, value) + + def +=(kv: (K, V)) = { + put(kv._1,kv._2) + this + } + + override def remove(key: K) = { + val map = ref.get.get + val oldValue = map.get(key) + ref.swap(ref.get.get - key) + oldValue + } + + def get(key: K): Option[V] = ref.get.get.get(key) + + override def put(key: K, value: V): Option[V] = { + val map = ref.get.get + val oldValue = map.get(key) + ref.swap(map.updated(key, value)) + oldValue + } + + override def update(key: K, value: V) = { + val map = ref.get.get + val oldValue = map.get(key) + ref.swap(map.updated(key, value)) + } + + def iterator = ref.get.get.iterator + + override def elements: Iterator[(K, V)] = ref.get.get.iterator + + override def contains(key: K): Boolean = ref.get.get.contains(key) + + override def clear = ref.swap(HashMap[K, V]()) + + override def size: Int = ref.get.get.size + + override def hashCode: Int = System.identityHashCode(this); + + override def equals(other: Any): Boolean = + other.isInstanceOf[TransactionalMap[_, _]] && + other.hashCode == hashCode + + override def toString = if (outsideTransaction) "" else super.toString + + def outsideTransaction = getThreadLocalTransaction eq null +} diff --git a/akka-core/src/main/scala/stm/TransactionalState.scala b/akka-core/src/main/scala/stm/TransactionalState.scala deleted file mode 100644 index 8f449db1b1..0000000000 --- a/akka-core/src/main/scala/stm/TransactionalState.scala +++ /dev/null @@ -1,343 +0,0 @@ -/** - * Copyright (C) 2009-2010 Scalable Solutions AB - */ - -package se.scalablesolutions.akka.stm - -import se.scalablesolutions.akka.util.UUID - -import org.multiverse.api.GlobalStmInstance.getGlobalStmInstance - -/** - * Example Scala usage: - *
- * val myMap = TransactionalState.newMap
- * val myVector = TransactionalState.newVector
- * val myRef = TransactionalState.newRef
- * 
- * Or: - *
- * val myMap = TransactionalMap()
- * val myVector = TransactionalVector()
- * val myRef = TransactionalRef()
- * 
- * - *

- * Example Java usage: - *

- * TransactionalMap myMap = TransactionalState.newMap();
- * 
- * - * @author Jonas Bonér - */ -object TransactionalState { - def newMap[K, V] = TransactionalMap[K, V]() - def newMap[K, V](pairs: (K, V)*) = TransactionalMap(pairs: _*) - - def newVector[T] = TransactionalVector[T]() - def newVector[T](elems: T*) = TransactionalVector(elems :_*) - - def newRef[T] = TransactionalRef[T]() - def newRef[T](initialValue: T) = TransactionalRef(initialValue) -} - -/** - * @author Jonas Bonér - */ -@serializable -trait Transactional { - val uuid: String -} - -/** - * @author Jonas Bonér - */ -trait Committable { - def commit: Unit -} - -/** - * @author Jonas Bonér - */ -trait Abortable { - def abort: Unit -} - -object RefFactory { - private val factory = getGlobalStmInstance.getProgrammaticReferenceFactoryBuilder.build - - def createRef[T] = factory.atomicCreateReference[T]() - - def createRef[T](value: T) = factory.atomicCreateReference(value) -} - -/** - * Alias to TransactionalRef. - * - * @author Jonas Bonér - */ -object Ref { - type Ref[T] = TransactionalRef[T] - - def apply[T]() = new Ref[T] - - def apply[T](initialValue: T) = new Ref[T](Some(initialValue)) -} - -/** - * Alias to Ref. - * - * @author Jonas Bonér - */ -object TransactionalRef { - - /** - * An implicit conversion that converts a TransactionalRef to an Iterable value. - */ - implicit def ref2Iterable[T](ref: TransactionalRef[T]): Iterable[T] = ref.toList - - def apply[T]() = new TransactionalRef[T] - - def apply[T](initialValue: T) = new TransactionalRef[T](Some(initialValue)) -} - -/** - * Implements a transactional managed reference. - * Alias to Ref. - * - * @author Jonas Bonér - */ -class TransactionalRef[T](initialOpt: Option[T] = None) extends Transactional { - self => - - import org.multiverse.api.ThreadLocalTransaction._ - - implicit val txInitName = "TransactionalRef:Init" - val uuid = UUID.newUuid.toString - - private[this] val ref = { - if (initialOpt.isDefined) RefFactory.createRef(initialOpt.get) - else RefFactory.createRef[T] - } - - def swap(elem: T) = { - ensureIsInTransaction - ref.set(elem) - } - - def alter(f: T => T): T = { - ensureIsInTransaction - ensureNotNull - ref.set(f(ref.get)) - ref.get - } - - def get: Option[T] = { - ensureIsInTransaction - if (ref.isNull) None - else Some(ref.get) - } - - def getOrWait: T = { - ensureIsInTransaction - ref.getOrAwait - } - - def getOrElse(default: => T): T = { - ensureIsInTransaction - if (ref.isNull) default - else ref.get - } - - def isDefined: Boolean = { - ensureIsInTransaction - !ref.isNull - } - - def isEmpty: Boolean = { - ensureIsInTransaction - ref.isNull - } - - def map[B](f: T => B): TransactionalRef[B] = { - ensureIsInTransaction - if (isEmpty) TransactionalRef[B] else TransactionalRef(f(ref.get)) - } - - def flatMap[B](f: T => TransactionalRef[B]): TransactionalRef[B] = { - ensureIsInTransaction - if (isEmpty) TransactionalRef[B] else f(ref.get) - } - - def filter(p: T => Boolean): TransactionalRef[T] = { - ensureIsInTransaction - if (isDefined && p(ref.get)) TransactionalRef(ref.get) else TransactionalRef[T] - } - - /** - * Necessary to keep from being implicitly converted to Iterable in for comprehensions. - */ - def withFilter(p: T => Boolean): WithFilter = new WithFilter(p) - - class WithFilter(p: T => Boolean) { - def map[B](f: T => B): TransactionalRef[B] = self filter p map f - def flatMap[B](f: T => TransactionalRef[B]): TransactionalRef[B] = self filter p flatMap f - def foreach[U](f: T => U): Unit = self filter p foreach f - def withFilter(q: T => Boolean): WithFilter = new WithFilter(x => p(x) && q(x)) - } - - def foreach[U](f: T => U): Unit = { - ensureIsInTransaction - if (isDefined) f(ref.get) - } - - def elements: Iterator[T] = { - ensureIsInTransaction - if (isEmpty) Iterator.empty else Iterator(ref.get) - } - - def toList: List[T] = { - ensureIsInTransaction - if (isEmpty) List() else List(ref.get) - } - - def toRight[X](left: => X) = { - ensureIsInTransaction - if (isEmpty) Left(left) else Right(ref.get) - } - - def toLeft[X](right: => X) = { - ensureIsInTransaction - if (isEmpty) Right(right) else Left(ref.get) - } - - private def ensureIsInTransaction = - ()// if (getThreadLocalTransaction eq null) throw new NoTransactionInScopeException - - private def ensureNotNull = - if (ref.isNull) throw new RuntimeException("Cannot alter Ref's value when it is null") -} - -object TransactionalMap { - def apply[K, V]() = new TransactionalMap[K, V] - - def apply[K, V](pairs: (K, V)*) = new TransactionalMap(Some(HashTrie(pairs: _*))) -} - -/** - * Implements an in-memory transactional Map based on Clojure's PersistentMap. - * - * Not thread-safe, but should only be using from within an Actor, e.g. one single thread at a time. - * - * @author Jonas Bonér - */ -class TransactionalMap[K, V](initialOpt: Option[HashTrie[K, V]] = None) extends Transactional with scala.collection.mutable.Map[K, V] { - val uuid = UUID.newUuid.toString - - protected[this] val ref = new TransactionalRef(initialOpt.orElse(Some(new HashTrie[K, V]))) - - def -=(key: K) = { - remove(key) - this - } - - def +=(key: K, value: V) = put(key, value) - - def +=(kv: (K, V)) = { - put(kv._1,kv._2) - this - } - - override def remove(key: K) = { - val map = ref.get.get - val oldValue = map.get(key) - ref.swap(ref.get.get - key) - oldValue - } - - def get(key: K): Option[V] = ref.get.get.get(key) - - override def put(key: K, value: V): Option[V] = { - val map = ref.get.get - val oldValue = map.get(key) - ref.swap(map.update(key, value)) - oldValue - } - - override def update(key: K, value: V) = { - val map = ref.get.get - val oldValue = map.get(key) - ref.swap(map.update(key, value)) - } - - def iterator = ref.get.get.iterator - - override def elements: Iterator[(K, V)] = ref.get.get.iterator - - override def contains(key: K): Boolean = ref.get.get.contains(key) - - override def clear = ref.swap(new HashTrie[K, V]) - - override def size: Int = ref.get.get.size - - override def hashCode: Int = System.identityHashCode(this); - - override def equals(other: Any): Boolean = - other.isInstanceOf[TransactionalMap[_, _]] && - other.hashCode == hashCode - - override def toString = if (outsideTransaction) "" else super.toString - - def outsideTransaction = - org.multiverse.api.ThreadLocalTransaction.getThreadLocalTransaction eq null -} - -object TransactionalVector { - def apply[T]() = new TransactionalVector[T] - - def apply[T](elems: T*) = new TransactionalVector(Some(Vector(elems: _*))) -} - -/** - * Implements an in-memory transactional Vector based on Clojure's PersistentVector. - * - * Not thread-safe, but should only be using from within an Actor, e.g. one single thread at a time. - * - * @author Jonas Bonér - */ -class TransactionalVector[T](initialOpt: Option[Vector[T]] = None) extends Transactional with IndexedSeq[T] { - val uuid = UUID.newUuid.toString - - private[this] val ref = new TransactionalRef(initialOpt.orElse(Some(EmptyVector))) - - def clear = ref.swap(EmptyVector) - - def +(elem: T) = add(elem) - - def add(elem: T) = ref.swap(ref.get.get + elem) - - def get(index: Int): T = ref.get.get.apply(index) - - /** - * Removes the tail element of this vector. - */ - def pop = ref.swap(ref.get.get.pop) - - def update(index: Int, elem: T) = ref.swap(ref.get.get.update(index, elem)) - - def length: Int = ref.get.get.length - - def apply(index: Int): T = ref.get.get.apply(index) - - override def hashCode: Int = System.identityHashCode(this); - - override def equals(other: Any): Boolean = - other.isInstanceOf[TransactionalVector[_]] && - other.hashCode == hashCode - - override def toString = if (outsideTransaction) "" else super.toString - - def outsideTransaction = - org.multiverse.api.ThreadLocalTransaction.getThreadLocalTransaction eq null -} - diff --git a/akka-core/src/main/scala/stm/TransactionalVector.scala b/akka-core/src/main/scala/stm/TransactionalVector.scala new file mode 100644 index 0000000000..e2ad6a2aeb --- /dev/null +++ b/akka-core/src/main/scala/stm/TransactionalVector.scala @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package se.scalablesolutions.akka.stm + +import scala.collection.immutable.Vector + +import se.scalablesolutions.akka.util.UUID + +import org.multiverse.api.ThreadLocalTransaction.getThreadLocalTransaction + +object TransactionalVector { + def apply[T]() = new TransactionalVector[T] + + def apply[T](elems: T*) = new TransactionalVector(Some(Vector(elems: _*))) +} + +/** + * Transactional vector that implements the indexed seq interface with an underlying ref and vector. + * + * @author Jonas Bonér + */ +class TransactionalVector[T](initialOpt: Option[Vector[T]] = None) extends Transactional with IndexedSeq[T] { + def this() = this(None) // Java compatibility + + val uuid = UUID.newUuid.toString + + private[this] val ref = new Ref(initialOpt.orElse(Some(Vector[T]()))) + + def clear = ref.swap(Vector[T]()) + + def +(elem: T) = add(elem) + + def add(elem: T) = ref.swap(ref.get.get :+ elem) + + def get(index: Int): T = ref.get.get.apply(index) + + /** + * Removes the tail element of this vector. + */ + def pop = ref.swap(ref.get.get.dropRight(1)) + + def update(index: Int, elem: T) = ref.swap(ref.get.get.updated(index, elem)) + + def length: Int = ref.get.get.length + + def apply(index: Int): T = ref.get.get.apply(index) + + override def hashCode: Int = System.identityHashCode(this); + + override def equals(other: Any): Boolean = + other.isInstanceOf[TransactionalVector[_]] && + other.hashCode == hashCode + + override def toString = if (outsideTransaction) "" else super.toString + + def outsideTransaction = + org.multiverse.api.ThreadLocalTransaction.getThreadLocalTransaction eq null +} + diff --git a/akka-core/src/main/scala/stm/Vector.scala b/akka-core/src/main/scala/stm/Vector.scala deleted file mode 100644 index 7d524cd2a8..0000000000 --- a/akka-core/src/main/scala/stm/Vector.scala +++ /dev/null @@ -1,353 +0,0 @@ -/** - Copyright (c) 2007-2008, Rich Hickey - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of Clojure nor the names of its contributors - may be used to endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - **/ - -package se.scalablesolutions.akka.stm - -import Vector._ - -/** - * A straight port of Clojure's PersistentVector class. - * - * @author Daniel Spiewak - * @author Rich Hickey - */ -@serializable -class Vector[+T] private (val length: Int, shift: Int, root: Array[AnyRef], tail: Array[AnyRef]) - extends IndexedSeq[T] with PersistentDataStructure { outer => - private val tailOff = length - tail.length - - /* - * The design of this data structure inherantly requires heterogenous arrays. - * It is *possible* to design around this, but the result is comparatively - * quite inefficient. With respect to this fact, I have left the original - * (somewhat dynamically-typed) implementation in place. - */ - - private[stm] def this() = this(0, 5, EmptyArray, EmptyArray) - - def apply(i: Int): T = { - if (i >= 0 && i < length) { - if (i >= tailOff) { - tail(i & 0x01f).asInstanceOf[T] - } else { - var arr = root - var level = shift - - while (level > 0) { - arr = arr((i >>> level) & 0x01f).asInstanceOf[Array[AnyRef]] - level -= 5 - } - - arr(i & 0x01f).asInstanceOf[T] - } - } else throw new IndexOutOfBoundsException(i.toString) - } - - def update[A >: T](i: Int, obj: A): Vector[A] = { - if (i >= 0 && i < length) { - if (i >= tailOff) { - val newTail = new Array[AnyRef](tail.length) - Array.copy(tail, 0, newTail, 0, tail.length) - newTail(i & 0x01f) = obj.asInstanceOf[AnyRef] - - new Vector[A](length, shift, root, newTail) - } else { - new Vector[A](length, shift, doAssoc(shift, root, i, obj), tail) - } - } else if (i == length) { - this + obj - } else throw new IndexOutOfBoundsException(i.toString) - } - - private def doAssoc[A >: T](level: Int, arr: Array[AnyRef], i: Int, obj: A): Array[AnyRef] = { - val ret = new Array[AnyRef](arr.length) - Array.copy(arr, 0, ret, 0, arr.length) - - if (level == 0) { - ret(i & 0x01f) = obj.asInstanceOf[AnyRef] - } else { - val subidx = (i >>> level) & 0x01f - ret(subidx) = doAssoc(level - 5, arr(subidx).asInstanceOf[Array[AnyRef]], i, obj) - } - - ret - } - - def ++[A >: T](other: Iterable[A]) = other.foldLeft(this:Vector[A]) { _ + _ } - - def +[A >: T](obj: A): Vector[A] = { - if (tail.length < 32) { - val newTail = new Array[AnyRef](tail.length + 1) - Array.copy(tail, 0, newTail, 0, tail.length) - newTail(tail.length) = obj.asInstanceOf[AnyRef] - - new Vector[A](length + 1, shift, root, newTail) - } else { - var (newRoot, expansion) = pushTail(shift - 5, root, tail, null) - var newShift = shift - - if (expansion ne null) { - newRoot = array(newRoot, expansion) - newShift += 5 - } - - new Vector[A](length + 1, newShift, newRoot, array(obj.asInstanceOf[AnyRef])) - } - } - - private def pushTail(level: Int, arr: Array[AnyRef], tailNode: Array[AnyRef], expansion: AnyRef): (Array[AnyRef], AnyRef) = { - val newChild = if (level == 0) tailNode else { - val (newChild, subExpansion) = pushTail(level - 5, arr(arr.length - 1).asInstanceOf[Array[AnyRef]], tailNode, expansion) - - if (subExpansion eq null) { - val ret = new Array[AnyRef](arr.length) - Array.copy(arr, 0, ret, 0, arr.length) - - ret(arr.length - 1) = newChild - - return (ret, null) - } else subExpansion - } - - // expansion - if (arr.length == 32) { - (arr, array(newChild)) - } else { - val ret = new Array[AnyRef](arr.length + 1) - Array.copy(arr, 0, ret, 0, arr.length) - ret(arr.length) = newChild - - (ret, null) - } - } - - /** - * Removes the tail element of this vector. - */ - def pop: Vector[T] = { - if (length == 0) { - throw new IllegalStateException("Can't pop empty vector") - } else if (length == 1) { - EmptyVector - } else if (tail.length > 1) { - val newTail = new Array[AnyRef](tail.length - 1) - Array.copy(tail, 0, newTail, 0, newTail.length) - - new Vector[T](length - 1, shift, root, newTail) - } else { - var (newRoot, pTail) = popTail(shift - 5, root, null) - var newShift = shift - - if (newRoot eq null) { - newRoot = EmptyArray - } - - if (shift > 5 && newRoot.length == 1) { - newRoot = newRoot(0).asInstanceOf[Array[AnyRef]] - newShift -= 5 - } - - new Vector[T](length - 1, newShift, newRoot, pTail.asInstanceOf[Array[AnyRef]]) - } - } - - private def popTail(shift: Int, arr: Array[AnyRef], pTail: AnyRef): (Array[AnyRef], AnyRef) = { - val newPTail = if (shift > 0) { - val (newChild, subPTail) = popTail(shift - 5, arr(arr.length - 1).asInstanceOf[Array[AnyRef]], pTail) - - if (newChild ne null) { - val ret = new Array[AnyRef](arr.length) - Array.copy(arr, 0, ret, 0, arr.length) - - ret(arr.length - 1) = newChild - - return (ret, subPTail) - } - subPTail - } else if (shift == 0) { - arr(arr.length - 1) - } else pTail - - // contraction - if (arr.length == 1) { - (null, newPTail) - } else { - val ret = new Array[AnyRef](arr.length - 1) - Array.copy(arr, 0, ret, 0, ret.length) - - (ret, newPTail) - } - } - - override def filter(p: (T)=>Boolean) = { - var back = new Vector[T] - var i = 0 - - while (i < length) { - val e = apply(i) - if (p(e)) back += e - - i += 1 - } - - back - } - - def flatMap[A](f: (T)=>Iterable[A]): Vector[A] = { - var back = new Vector[A] - var i = 0 - - while (i < length) { - f(apply(i)) foreach { back += _ } - i += 1 - } - - back - } - - def map[A](f: (T)=>A): Vector[A] = { - var back = new Vector[A] - var i = 0 - - while (i < length) { - back += f(apply(i)) - i += 1 - } - - back - } - - override def reverse: Vector[T] = new VectorProjection[T] { - override val length = outer.length - - override def apply(i: Int) = outer.apply(length - i - 1) - } - - def subseq(from: Int, end: Int) = subVector(from, end) - - def subVector(from: Int, end: Int): Vector[T] = { - if (from < 0) { - throw new IndexOutOfBoundsException(from.toString) - } else if (end >= length) { - throw new IndexOutOfBoundsException(end.toString) - } else if (end <= from) { - throw new IllegalArgumentException("Invalid range: " + from + ".." + end) - } else { - new VectorProjection[T] { - override val length = end - from - - override def apply(i: Int) = outer.apply(i + from) - } - } - } - - def zip[A](that: Vector[A]) = { - var back = new Vector[(T, A)] - var i = 0 - - val limit = math.min(length, that.length) - while (i < limit) { - back += (apply(i), that(i)) - i += 1 - } - - back - } - - def zipWithIndex = { - var back = new Vector[(T, Int)] - var i = 0 - - while (i < length) { - back += (apply(i), i) - i += 1 - } - - back - } - - override def equals(other: Any) = other match { - case vec: Vector[_] => { - var back = length == vec.length - var i = 0 - - while (i < length) { - back &&= apply(i) == vec.apply(i) - i += 1 - } - - back - } - - case _ => false - } - - override def hashCode = foldLeft(0) { _ ^ _.hashCode } -} - -object Vector { - private[stm] val EmptyArray = new Array[AnyRef](0) - - def apply[T](elems: T*) = elems.foldLeft(EmptyVector:Vector[T]) { _ + _ } - - def unapplySeq[T](vec: Vector[T]): Option[Seq[T]] = Some(vec) - - @inline - private[stm] def array(elems: AnyRef*) = { - val back = new Array[AnyRef](elems.length) - Array.copy(elems.toArray, 0, back, 0, back.length) - - back - } -} - -object EmptyVector extends Vector[Nothing] - -private[stm] abstract class VectorProjection[+T] extends Vector[T] { - override val length: Int - override def apply(i: Int): T - - override def +[A >: T](e: A) = innerCopy + e - - override def update[A >: T](i: Int, e: A) = { - if (i < 0) { - throw new IndexOutOfBoundsException(i.toString) - } else if (i > length) { - throw new IndexOutOfBoundsException(i.toString) - } else innerCopy(i) = e - } - - private lazy val innerCopy = foldLeft(EmptyVector:Vector[T]) { _ + _ } -} - diff --git a/akka-core/src/main/scala/stm/packages.scala b/akka-core/src/main/scala/stm/packages.scala new file mode 100644 index 0000000000..cbb3ad4804 --- /dev/null +++ b/akka-core/src/main/scala/stm/packages.scala @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package se.scalablesolutions.akka.stm + +/** + * For importing 'local' STM. + */ +package object local extends LocalStm with StmUtil with StmCommon + +/** + * For importing 'global' STM. + */ +package object global extends GlobalStm with StmUtil with StmCommon + +trait StmCommon { + type TransactionConfig = se.scalablesolutions.akka.stm.TransactionConfig + val TransactionConfig = se.scalablesolutions.akka.stm.TransactionConfig + + type TransactionFactory = se.scalablesolutions.akka.stm.TransactionFactory + val TransactionFactory = se.scalablesolutions.akka.stm.TransactionFactory + + type Ref[T] = se.scalablesolutions.akka.stm.Ref[T] + val Ref = se.scalablesolutions.akka.stm.Ref +} + +/** + * For importing the transactional data structures, including the primitive refs + * and transactional data structures from Multiverse. + */ +package object transactional { + type TransactionalMap[K,V] = se.scalablesolutions.akka.stm.TransactionalMap[K,V] + val TransactionalMap = se.scalablesolutions.akka.stm.TransactionalMap + + type TransactionalVector[T] = se.scalablesolutions.akka.stm.TransactionalVector[T] + val TransactionalVector = se.scalablesolutions.akka.stm.TransactionalVector + + type BooleanRef = org.multiverse.transactional.refs.BooleanRef + type ByteRef = org.multiverse.transactional.refs.ByteRef + type CharRef = org.multiverse.transactional.refs.CharRef + type DoubleRef = org.multiverse.transactional.refs.DoubleRef + type FloatRef = org.multiverse.transactional.refs.FloatRef + type IntRef = org.multiverse.transactional.refs.IntRef + type LongRef = org.multiverse.transactional.refs.LongRef + type ShortRef = org.multiverse.transactional.refs.ShortRef + + type TransactionalReferenceArray[T] = org.multiverse.transactional.arrays.TransactionalReferenceArray[T] + + // These won't compile - something to do with vararg constructors? Check for Scala bug. + + // type TransactionalArrayList[T] = org.multiverse.transactional.collections.TransactionalArrayList[T] + // type TransactionalLinkedList[T] = org.multiverse.transactional.collections.TransactionalLinkedList[T] + + type TransactionalThreadPoolExecutor = org.multiverse.transactional.executors.TransactionalThreadPoolExecutor +} diff --git a/akka-core/src/main/scala/util/Duration.scala b/akka-core/src/main/scala/util/Duration.scala new file mode 100644 index 0000000000..f49e1ae04b --- /dev/null +++ b/akka-core/src/main/scala/util/Duration.scala @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package se.scalablesolutions.akka.util + +import java.util.concurrent.TimeUnit + +object Duration { + def apply(length: Long, unit: TimeUnit) = new Duration(length, unit) + def apply(length: Long, unit: String) = new Duration(length, timeUnit(unit)) + + def timeUnit(unit: String) = unit.toLowerCase match { + case "nanoseconds" | "nanos" | "nanosecond" | "nano" => TimeUnit.NANOSECONDS + case "microseconds" | "micros" | "microsecond" | "micro" => TimeUnit.MICROSECONDS + case "milliseconds" | "millis" | "millisecond" | "milli" => TimeUnit.MILLISECONDS + case _ => TimeUnit.SECONDS + } +} + +/** + * Utility for working with java.util.concurrent.TimeUnit durations. + *

+ * Example: + *

+ * import se.scalablesolutions.akka.util.Duration
+ * import java.util.concurrent.TimeUnit
+ *
+ * val duration = Duration(100, TimeUnit.MILLISECONDS)
+ * val duration = Duration(100, "millis")
+ *
+ * duration.toNanos
+ * 
+ *

+ * Implicits are also provided for Int and Long. Example usage: + *

+ * import se.scalablesolutions.akka.util.duration._
+ *
+ * val duration = 100.millis
+ * 
+ */ +class Duration(val length: Long, val unit: TimeUnit) { + def toNanos = unit.toNanos(length) + def toMicros = unit.toMicros(length) + def toMillis = unit.toMillis(length) + def toSeconds = unit.toSeconds(length) + override def toString = "Duration(" + length + ", " + unit + ")" +} + +package object duration { + implicit def intToDurationInt(n: Int) = new DurationInt(n) + implicit def longToDurationLong(n: Long) = new DurationLong(n) +} + +class DurationInt(n: Int) { + def nanoseconds = Duration(n, TimeUnit.NANOSECONDS) + def nanos = Duration(n, TimeUnit.NANOSECONDS) + def nanosecond = Duration(n, TimeUnit.NANOSECONDS) + def nano = Duration(n, TimeUnit.NANOSECONDS) + + def microseconds = Duration(n, TimeUnit.MICROSECONDS) + def micros = Duration(n, TimeUnit.MICROSECONDS) + def microsecond = Duration(n, TimeUnit.MICROSECONDS) + def micro = Duration(n, TimeUnit.MICROSECONDS) + + def milliseconds = Duration(n, TimeUnit.MILLISECONDS) + def millis = Duration(n, TimeUnit.MILLISECONDS) + def millisecond = Duration(n, TimeUnit.MILLISECONDS) + def milli = Duration(n, TimeUnit.MILLISECONDS) + + def seconds = Duration(n, TimeUnit.SECONDS) + def second = Duration(n, TimeUnit.SECONDS) +} + +class DurationLong(n: Long) { + def nanoseconds = Duration(n, TimeUnit.NANOSECONDS) + def nanos = Duration(n, TimeUnit.NANOSECONDS) + def nanosecond = Duration(n, TimeUnit.NANOSECONDS) + def nano = Duration(n, TimeUnit.NANOSECONDS) + + def microseconds = Duration(n, TimeUnit.MICROSECONDS) + def micros = Duration(n, TimeUnit.MICROSECONDS) + def microsecond = Duration(n, TimeUnit.MICROSECONDS) + def micro = Duration(n, TimeUnit.MICROSECONDS) + + def milliseconds = Duration(n, TimeUnit.MILLISECONDS) + def millis = Duration(n, TimeUnit.MILLISECONDS) + def millisecond = Duration(n, TimeUnit.MILLISECONDS) + def milli = Duration(n, TimeUnit.MILLISECONDS) + + def seconds = Duration(n, TimeUnit.SECONDS) + def second = Duration(n, TimeUnit.SECONDS) +} diff --git a/akka-core/src/main/scala/util/Helpers.scala b/akka-core/src/main/scala/util/Helpers.scala index 4835a4dd05..ccbd896610 100644 --- a/akka-core/src/main/scala/util/Helpers.scala +++ b/akka-core/src/main/scala/util/Helpers.scala @@ -37,5 +37,26 @@ object Helpers extends Logging { }) sb.toString } -} + /** + * Convenience helper to cast the given Option of Any to an Option of the given type. Will throw a ClassCastException + * if the actual type is not assignable from the given one. + */ + def narrow[T](o: Option[Any]): Option[T] = { + require(o != null, "Option to be narrowed must not be null!") + o.asInstanceOf[Option[T]] + } + + /** + * Convenience helper to cast the given Option of Any to an Option of the given type. Will swallow a possible + * ClassCastException and return None in that case. + */ + def narrowSilently[T: Manifest](o: Option[Any]): Option[T] = + try { + narrow(o) + } catch { + case e: ClassCastException => + log.warning(e, "Cannot narrow %s to expected type %s!", o, implicitly[Manifest[T]].erasure.getName) + None + } +} diff --git a/akka-core/src/test/scala/ActorPatternsTest.scala b/akka-core/src/test/scala/ActorPatternsTest.scala index 0d4e9b6b08..f6205c2a91 100644 --- a/akka-core/src/test/scala/ActorPatternsTest.scala +++ b/akka-core/src/test/scala/ActorPatternsTest.scala @@ -39,9 +39,9 @@ class ActorPatternsTest extends junit.framework.TestCase with Suite with MustMat }.start val result = for { - a <- (d.!![Int](testMsg1,5000)) - b <- (d.!![Int](testMsg2,5000)) - c <- (d.!![Int](testMsg3,5000)) + a <- (d !! (testMsg1,5000)).as[Int] + b <- (d !! (testMsg2,5000)).as[Int] + c <- (d !! (testMsg3,5000)).as[Int] } yield a + b + c result.get must be(21) diff --git a/akka-core/src/test/scala/ExecutorBasedEventDrivenDispatcherActorSpec.scala b/akka-core/src/test/scala/ExecutorBasedEventDrivenDispatcherActorSpec.scala index f876d59527..e679bc6b4f 100644 --- a/akka-core/src/test/scala/ExecutorBasedEventDrivenDispatcherActorSpec.scala +++ b/akka-core/src/test/scala/ExecutorBasedEventDrivenDispatcherActorSpec.scala @@ -41,8 +41,8 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { @Test def shouldSendReplySync = { val actor = actorOf[TestActor].start - val result: String = (actor !! ("Hello", 10000)).get - assert("World" === result) + val result = (actor !! ("Hello", 10000)).as[String] + assert("World" === result.get) actor.stop } diff --git a/akka-core/src/test/scala/InMemoryActorSpec.scala b/akka-core/src/test/scala/InMemoryActorSpec.scala index 814e3fb841..fcc54399e2 100644 --- a/akka-core/src/test/scala/InMemoryActorSpec.scala +++ b/akka-core/src/test/scala/InMemoryActorSpec.scala @@ -4,7 +4,7 @@ import java.util.concurrent.{TimeUnit, CountDownLatch} import org.scalatest.junit.JUnitSuite import org.junit.Test -import se.scalablesolutions.akka.stm.{TransactionalState, TransactionalMap, TransactionalRef, TransactionalVector} +import se.scalablesolutions.akka.stm.{Ref, TransactionalMap, TransactionalVector} import Actor._ object InMemoryActorSpec { @@ -35,9 +35,9 @@ class InMemStatefulActor(expectedInvocationCount: Int) extends Transactor { val notifier = new CountDownLatch(expectedInvocationCount) - private lazy val mapState = TransactionalState.newMap[String, String] - private lazy val vectorState = TransactionalState.newVector[String] - private lazy val refState = TransactionalState.newRef[String] + private lazy val mapState = TransactionalMap[String, String]() + private lazy val vectorState = TransactionalVector[String]() + private lazy val refState = Ref[String]() def receive = { case GetNotifier => @@ -116,7 +116,7 @@ class InMemoryActorSpec extends JUnitSuite { stateful.start stateful ! SetMapStateOneWay("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "init") // set init state stateful ! SuccessOneWay("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "new state") // transactionrequired - val notifier: Option[CountDownLatch] = stateful !! GetNotifier + val notifier = (stateful !! GetNotifier).as[CountDownLatch] assert(notifier.get.await(1, TimeUnit.SECONDS)) assert("new state" === (stateful !! GetMapState("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess")).get) } @@ -138,7 +138,7 @@ class InMemoryActorSpec extends JUnitSuite { failer.start stateful ! SetMapStateOneWay("testShouldRollbackStateForStatefulServerInCaseOfFailure", "init") // set init state stateful ! FailureOneWay("testShouldRollbackStateForStatefulServerInCaseOfFailure", "new state", failer) // call failing transactionrequired method - val notifier: Option[CountDownLatch] = stateful !! GetNotifier + val notifier = (stateful !! GetNotifier).as[CountDownLatch] assert(notifier.get.await(1, TimeUnit.SECONDS)) assert("init" === (stateful !! GetMapState("testShouldRollbackStateForStatefulServerInCaseOfFailure")).get) // check that state is == init state } @@ -163,7 +163,7 @@ class InMemoryActorSpec extends JUnitSuite { stateful.start stateful ! SetVectorStateOneWay("init") // set init state stateful ! SuccessOneWay("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "new state") // transactionrequired - val notifier: Option[CountDownLatch] = stateful !! GetNotifier + val notifier = (stateful !! GetNotifier).as[CountDownLatch] assert(notifier.get.await(1, TimeUnit.SECONDS)) assert(2 === (stateful !! GetVectorSize).get) } @@ -186,7 +186,7 @@ class InMemoryActorSpec extends JUnitSuite { val failer = actorOf[InMemFailerActor] failer.start stateful ! FailureOneWay("testShouldRollbackStateForStatefulServerInCaseOfFailure", "new state", failer) // call failing transactionrequired method - val notifier: Option[CountDownLatch] = stateful !! GetNotifier + val notifier = (stateful !! GetNotifier).as[CountDownLatch] assert(notifier.get.await(1, TimeUnit.SECONDS)) assert(1 === (stateful !! GetVectorSize).get) } @@ -211,7 +211,7 @@ class InMemoryActorSpec extends JUnitSuite { stateful.start stateful ! SetRefStateOneWay("init") // set init state stateful ! SuccessOneWay("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "new state") // transactionrequired - val notifier: Option[CountDownLatch] = stateful !! GetNotifier + val notifier = (stateful !! GetNotifier).as[CountDownLatch] assert(notifier.get.await(1, TimeUnit.SECONDS)) assert("new state" === (stateful !! GetRefState).get) } @@ -234,7 +234,7 @@ class InMemoryActorSpec extends JUnitSuite { val failer = actorOf[InMemFailerActor] failer.start stateful ! FailureOneWay("testShouldRollbackStateForStatefulServerInCaseOfFailure", "new state", failer) // call failing transactionrequired method - val notifier: Option[CountDownLatch] = stateful !! GetNotifier + val notifier = (stateful !! GetNotifier).as[CountDownLatch] assert(notifier.get.await(1, TimeUnit.SECONDS)) assert("init" === (stateful !! (GetRefState, 1000000)).get) // check that state is == init state } diff --git a/akka-core/src/test/scala/ReactorBasedSingleThreadEventDrivenDispatcherActorSpec.scala b/akka-core/src/test/scala/ReactorBasedSingleThreadEventDrivenDispatcherActorSpec.scala index 726f79fa22..996c410977 100644 --- a/akka-core/src/test/scala/ReactorBasedSingleThreadEventDrivenDispatcherActorSpec.scala +++ b/akka-core/src/test/scala/ReactorBasedSingleThreadEventDrivenDispatcherActorSpec.scala @@ -44,7 +44,7 @@ class ReactorBasedSingleThreadEventDrivenDispatcherActorSpec extends JUnitSuite @Test def shouldSendReplySync = { val actor = actorOf[TestActor].start - val result: String = (actor !! ("Hello", 10000)).get + val result = (actor !! ("Hello", 10000)).as[String].get assert("World" === result) actor.stop } diff --git a/akka-core/src/test/scala/ReactorBasedThreadPoolEventDrivenDispatcherActorSpec.scala b/akka-core/src/test/scala/ReactorBasedThreadPoolEventDrivenDispatcherActorSpec.scala index b1f3dae678..b94a02a6fc 100644 --- a/akka-core/src/test/scala/ReactorBasedThreadPoolEventDrivenDispatcherActorSpec.scala +++ b/akka-core/src/test/scala/ReactorBasedThreadPoolEventDrivenDispatcherActorSpec.scala @@ -39,7 +39,7 @@ class ReactorBasedThreadPoolEventDrivenDispatcherActorSpec extends JUnitSuite { @Test def shouldSendReplySync = { val actor = actorOf[TestActor].start - val result: String = (actor !! ("Hello", 10000)).get + val result = (actor !! ("Hello", 10000)).as[String].get assert("World" === result) actor.stop } diff --git a/akka-core/src/test/scala/TransactionalRefSpec.scala b/akka-core/src/test/scala/RefSpec.scala similarity index 95% rename from akka-core/src/test/scala/TransactionalRefSpec.scala rename to akka-core/src/test/scala/RefSpec.scala index d21f2af872..805e0834ea 100644 --- a/akka-core/src/test/scala/TransactionalRefSpec.scala +++ b/akka-core/src/test/scala/RefSpec.scala @@ -8,10 +8,10 @@ import org.junit.runner.RunWith import se.scalablesolutions.akka.actor.Actor._ @RunWith(classOf[JUnitRunner]) -class TransactionalRefSpec extends Spec with ShouldMatchers { +class RefSpec extends Spec with ShouldMatchers { - describe("A TransactionalRef") { - import Transaction.Local._ + describe("A Ref") { + import local._ it("should optionally accept an initial value") { val emptyRef = Ref[Int] @@ -29,7 +29,7 @@ class TransactionalRefSpec extends Spec with ShouldMatchers { val ref = Ref(3) try { - atomic { + atomic(DefaultLocalTransactionFactory) { ref.swap(5) throw new Exception } diff --git a/akka-core/src/test/scala/StmSpec.scala b/akka-core/src/test/scala/StmSpec.scala index b7537b83b2..1544936446 100644 --- a/akka-core/src/test/scala/StmSpec.scala +++ b/akka-core/src/test/scala/StmSpec.scala @@ -1,7 +1,6 @@ -package se.scalablesolutions.akka.actor - -import se.scalablesolutions.akka.stm._ +package se.scalablesolutions.akka.stm +import se.scalablesolutions.akka.actor.{Actor, Transactor} import Actor._ import org.scalatest.Spec @@ -17,11 +16,11 @@ class StmSpec extends ShouldMatchers with BeforeAndAfterAll { - describe("Transaction.Local") { + describe("Local STM") { it("should be able to do multiple consecutive atomic {..} statements") { - import Transaction.Local._ + import local._ - lazy val ref = TransactionalState.newRef[Int] + lazy val ref = Ref[Int]() def increment = atomic { ref.swap(ref.get.getOrElse(0) + 1) @@ -38,9 +37,9 @@ class StmSpec extends } it("should be able to do nested atomic {..} statements") { - import Transaction.Local._ + import local._ - lazy val ref = TransactionalState.newRef[Int] + lazy val ref = Ref[Int]() def increment = atomic { ref.swap(ref.get.getOrElse(0) + 1) @@ -60,9 +59,9 @@ class StmSpec extends } it("should roll back failing nested atomic {..} statements") { - import Transaction.Local._ + import local._ - lazy val ref = TransactionalState.newRef[Int] + lazy val ref = Ref[Int]() def increment = atomic { ref.swap(ref.get.getOrElse(0) + 1) @@ -71,7 +70,7 @@ class StmSpec extends ref.get.getOrElse(0) } try { - atomic { + atomic(DefaultLocalTransactionFactory) { increment increment throw new Exception @@ -83,16 +82,16 @@ class StmSpec extends } } - describe("Transaction.Global") { + describe("Global STM") { it("should be able to initialize with atomic {..} block inside actor constructor") { import GlobalTransactionVectorTestActor._ try { val actor = actorOf[GlobalTransactionVectorTestActor].start actor !! Add(5) - val size1: Int = (actor !! Size).getOrElse(fail("Could not get Vector::size")) + val size1 = (actor !! Size).as[Int].getOrElse(fail("Could not get Vector::size")) size1 should equal(2) actor !! Add(2) - val size2: Int = (actor !! Size).getOrElse(fail("Could not get Vector::size")) + val size2 = (actor !! Size).as[Int].getOrElse(fail("Could not get Vector::size")) size2 should equal(3) } catch { case e => @@ -108,18 +107,18 @@ class StmSpec extends try { val actor = actorOf[NestedTransactorLevelOneActor].start actor !! Add(2) - val size1: Int = (actor !! Size).getOrElse(fail("Could not get size")) + val size1 = (actor !! Size).as[Int].getOrElse(fail("Could not get size")) size1 should equal(2) actor !! Add(7) actor ! "HiLevelOne" - val size2: Int = (actor !! Size).getOrElse(fail("Could not get size")) + val size2 = (actor !! Size).as[Int].getOrElse(fail("Could not get size")) size2 should equal(7) actor !! Add(0) actor ! "HiLevelTwo" - val size3: Int = (actor !! Size).getOrElse(fail("Could not get size")) + val size3 = (actor !! Size).as[Int].getOrElse(fail("Could not get size")) size3 should equal(0) actor !! Add(3) - val size4: Int = (actor !! Size).getOrElse(fail("Could not get size")) + val size4 = (actor !! Size).as[Int].getOrElse(fail("Could not get size")) size4 should equal(3) } catch { case e => @@ -182,17 +181,17 @@ object GlobalTransactionVectorTestActor { } class GlobalTransactionVectorTestActor extends Actor { import GlobalTransactionVectorTestActor._ - import se.scalablesolutions.akka.stm.Transaction.Global + import se.scalablesolutions.akka.stm.global._ - private val vector: TransactionalVector[Int] = Global.atomic { TransactionalVector(1) } + private val vector: TransactionalVector[Int] = atomic { TransactionalVector(1) } def receive = { case Add(value) => - Global.atomic { vector + value} + atomic { vector + value} self.reply(Success) case Size => - val size = Global.atomic { vector.size } + val size = atomic { vector.size } self.reply(size) } } @@ -213,7 +212,7 @@ class NestedTransactorLevelOneActor extends Actor { } } -class NestedTransactorLevelTwoActor extends Actor { +class NestedTransactorLevelTwoActor extends Transactor { import GlobalTransactionVectorTestActor._ private val ref = Ref(0) diff --git a/akka-core/src/test/scala/ThreadBasedActorSpec.scala b/akka-core/src/test/scala/ThreadBasedActorSpec.scala index d10a39965b..eda6f4d52c 100644 --- a/akka-core/src/test/scala/ThreadBasedActorSpec.scala +++ b/akka-core/src/test/scala/ThreadBasedActorSpec.scala @@ -40,8 +40,8 @@ class ThreadBasedActorSpec extends JUnitSuite { @Test def shouldSendReplySync = { val actor = actorOf[TestActor].start - val result: String = (actor !! ("Hello", 10000)).get - assert("World" === result) + val result = (actor !! ("Hello", 10000)).as[String] + assert("World" === result.get) actor.stop } diff --git a/akka-core/src/test/scala/VectorBugTestSuite.scala b/akka-core/src/test/scala/VectorBugTestSuite.scala deleted file mode 100644 index 658ace3681..0000000000 --- a/akka-core/src/test/scala/VectorBugTestSuite.scala +++ /dev/null @@ -1,17 +0,0 @@ -package se.scalablesolutions.akka.stm - -import org.scalatest.FunSuite -import Transaction.Global._ - -class TransactionalVectorBugTestSuite extends FunSuite { - - test("adding more than 32 items to a Vector shouldn't blow it up") { - atomic { - var v1 = new Vector[Int]() - for (i <- 0 to 31) { - v1 = v1 + i - } - v1 = v1 + 32 - } - } -} diff --git a/akka-http/src/main/scala/Security.scala b/akka-http/src/main/scala/Security.scala index 72d79db3d9..284d82d98e 100644 --- a/akka-http/src/main/scala/Security.scala +++ b/akka-http/src/main/scala/Security.scala @@ -23,8 +23,9 @@ package se.scalablesolutions.akka.security import se.scalablesolutions.akka.actor.{Scheduler, Actor, ActorRef, ActorRegistry} -import se.scalablesolutions.akka.util.Logging +import se.scalablesolutions.akka.actor.Actor._ import se.scalablesolutions.akka.config.Config +import se.scalablesolutions.akka.util.Logging import com.sun.jersey.api.model.AbstractMethod import com.sun.jersey.spi.container.{ResourceFilterFactory, ContainerRequest, ContainerRequestFilter, ContainerResponse, ContainerResponseFilter, ResourceFilter} @@ -87,7 +88,7 @@ class AkkaSecurityFilterFactory extends ResourceFilterFactory with Logging { override def filter(request: ContainerRequest): ContainerRequest = rolesAllowed match { case Some(roles) => { - val result : Option[AnyRef] = authenticator !! Authenticate(request, roles) + val result = (authenticator !! Authenticate(request, roles)).as[AnyRef] result match { case Some(OK) => request case Some(r) if r.isInstanceOf[Response] => diff --git a/akka-http/src/test/scala/SecuritySpec.scala b/akka-http/src/test/scala/SecuritySpec.scala index 2ee7596a25..6a3cf4f803 100644 --- a/akka-http/src/test/scala/SecuritySpec.scala +++ b/akka-http/src/test/scala/SecuritySpec.scala @@ -39,7 +39,7 @@ class BasicAuthenticatorSpec extends junit.framework.TestCase @Test def testChallenge = { val req = mock[ContainerRequest] - val result: Response = (authenticator !! (Authenticate(req, List("foo")), 10000)).get + val result = (authenticator !! (Authenticate(req, List("foo")), 10000)).as[Response].get // the actor replies with a challenge for the browser result.getStatus must equal(Response.Status.UNAUTHORIZED.getStatusCode) @@ -54,7 +54,7 @@ class BasicAuthenticatorSpec extends junit.framework.TestCase // fake a request authorization -> this will authorize the user when(req.isUserInRole("chef")).thenReturn(true) - val result: AnyRef = (authenticator !! (Authenticate(req, List("chef")), 10000)).get + val result = (authenticator !! (Authenticate(req, List("chef")), 10000)).as[AnyRef].get result must be(OK) // the authenticator must have set a security context @@ -68,7 +68,7 @@ class BasicAuthenticatorSpec extends junit.framework.TestCase when(req.getHeaderValue("Authorization")).thenReturn("Basic " + new String(Base64.encode("foo:bar"))) when(req.isUserInRole("chef")).thenReturn(false) // this will deny access - val result: Response = (authenticator !! (Authenticate(req, List("chef")), 10000)).get + val result = (authenticator !! (Authenticate(req, List("chef")), 10000)).as[Response].get result.getStatus must equal(Response.Status.FORBIDDEN.getStatusCode) diff --git a/akka-persistence/akka-persistence-cassandra/src/test/scala/CassandraPersistentActorSpec.scala b/akka-persistence/akka-persistence-cassandra/src/test/scala/CassandraPersistentActorSpec.scala index 97419f3231..74673f2041 100644 --- a/akka-persistence/akka-persistence-cassandra/src/test/scala/CassandraPersistentActorSpec.scala +++ b/akka-persistence/akka-persistence-cassandra/src/test/scala/CassandraPersistentActorSpec.scala @@ -80,7 +80,7 @@ class CassandraPersistentActorSpec extends JUnitSuite { stateful.start stateful !! SetMapState("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "init") // set init state stateful !! Success("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "new state") // transactionrequired - val result: Array[Byte] = (stateful !! GetMapState("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess")).get + val result = (stateful !! GetMapState("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess")).as[Array[Byte]].get assertEquals("new state", new String(result, 0, result.length, "UTF-8")) } @@ -95,7 +95,7 @@ class CassandraPersistentActorSpec extends JUnitSuite { stateful !! Failure("testShouldRollbackStateForStatefulServerInCaseOfFailure", "new state", failer) // call failing transactionrequired method fail("should have thrown an exception") } catch {case e: RuntimeException => {}} - val result: Array[Byte] = (stateful !! GetMapState("testShouldRollbackStateForStatefulServerInCaseOfFailure")).get + val result = (stateful !! GetMapState("testShouldRollbackStateForStatefulServerInCaseOfFailure")).as[Array[Byte]].get assertEquals("init", new String(result, 0, result.length, "UTF-8")) // check that state is == init state } @@ -128,7 +128,7 @@ class CassandraPersistentActorSpec extends JUnitSuite { stateful.start stateful !! SetRefState("init") // set init state stateful !! Success("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "new state") // transactionrequired - val result: Array[Byte] = (stateful !! GetRefState).get + val result = (stateful !! GetRefState).as[Array[Byte]].get assertEquals("new state", new String(result, 0, result.length, "UTF-8")) } @@ -143,7 +143,7 @@ class CassandraPersistentActorSpec extends JUnitSuite { stateful !! Failure("testShouldRollbackStateForStatefulServerInCaseOfFailure", "new state", failer) // call failing transactionrequired method fail("should have thrown an exception") } catch {case e: RuntimeException => {}} - val result: Array[Byte] = (stateful !! GetRefState).get + val result = (stateful !! GetRefState).as[Array[Byte]].get assertEquals("init", new String(result, 0, result.length, "UTF-8")) // check that state is == init state } diff --git a/akka-persistence/akka-persistence-common/src/main/scala/Storage.scala b/akka-persistence/akka-persistence-common/src/main/scala/Storage.scala index 6a0eb9a8d8..135ee584b9 100644 --- a/akka-persistence/akka-persistence-common/src/main/scala/Storage.scala +++ b/akka-persistence/akka-persistence-common/src/main/scala/Storage.scala @@ -82,9 +82,9 @@ trait Storage { */ trait PersistentMap[K, V] extends scala.collection.mutable.Map[K, V] with Transactional with Committable with Abortable with Logging { - protected val newAndUpdatedEntries = TransactionalState.newMap[K, V] - protected val removedEntries = TransactionalState.newVector[K] - protected val shouldClearOnCommit = TransactionalRef[Boolean]() + protected val newAndUpdatedEntries = TransactionalMap[K, V]() + protected val removedEntries = TransactionalVector[K]() + protected val shouldClearOnCommit = Ref[Boolean]() // to be concretized in subclasses val storage: MapStorageBackend[K, V] @@ -195,10 +195,10 @@ trait PersistentMap[K, V] extends scala.collection.mutable.Map[K, V] * @author Jonas Bonér */ trait PersistentVector[T] extends IndexedSeq[T] with Transactional with Committable with Abortable { - protected val newElems = TransactionalState.newVector[T] - protected val updatedElems = TransactionalState.newMap[Int, T] - protected val removedElems = TransactionalState.newVector[T] - protected val shouldClearOnCommit = TransactionalRef[Boolean]() + protected val newElems = TransactionalVector[T]() + protected val updatedElems = TransactionalMap[Int, T]() + protected val removedElems = TransactionalVector[T]() + protected val shouldClearOnCommit = Ref[Boolean]() val storage: VectorStorageBackend[T] @@ -276,7 +276,7 @@ trait PersistentVector[T] extends IndexedSeq[T] with Transactional with Committa * @author Jonas Bonér */ trait PersistentRef[T] extends Transactional with Committable with Abortable { - protected val ref = new TransactionalRef[T] + protected val ref = Ref[T]() val storage: RefStorageBackend[T] @@ -343,14 +343,14 @@ trait PersistentQueue[A] extends scala.collection.mutable.Queue[A] import scala.collection.immutable.Queue // current trail that will be played on commit to the underlying store - protected val enqueuedNDequeuedEntries = TransactionalState.newVector[(Option[A], QueueOp)] - protected val shouldClearOnCommit = TransactionalRef[Boolean]() + protected val enqueuedNDequeuedEntries = TransactionalVector[(Option[A], QueueOp)]() + protected val shouldClearOnCommit = Ref[Boolean]() // local queue that will record all enqueues and dequeues in the current txn - protected val localQ = TransactionalRef[Queue[A]]() + protected val localQ = Ref[Queue[A]]() // keeps a pointer to the underlying storage for the enxt candidate to be dequeued - protected val pickMeForDQ = TransactionalRef[Int]() + protected val pickMeForDQ = Ref[Int]() localQ.swap(Queue.empty) pickMeForDQ.swap(0) @@ -481,8 +481,8 @@ trait PersistentQueue[A] extends scala.collection.mutable.Queue[A] */ trait PersistentSortedSet[A] extends Transactional with Committable with Abortable { - protected val newElems = TransactionalState.newMap[A, Float] - protected val removedElems = TransactionalState.newVector[A] + protected val newElems = TransactionalMap[A, Float]() + protected val removedElems = TransactionalVector[A]() val storage: SortedSetStorageBackend[A] diff --git a/akka-persistence/akka-persistence-redis/src/test/scala/RedisInconsistentSizeBugTest.scala b/akka-persistence/akka-persistence-redis/src/test/scala/RedisInconsistentSizeBugTest.scala index 22d294c735..74d1e95cc9 100644 --- a/akka-persistence/akka-persistence-redis/src/test/scala/RedisInconsistentSizeBugTest.scala +++ b/akka-persistence/akka-persistence-redis/src/test/scala/RedisInconsistentSizeBugTest.scala @@ -8,7 +8,7 @@ import se.scalablesolutions.akka.actor.{Actor, ActorRef} import se.scalablesolutions.akka.config.OneForOneStrategy import Actor._ import se.scalablesolutions.akka.persistence.common.PersistentVector -import se.scalablesolutions.akka.stm.Transaction.Global._ +import se.scalablesolutions.akka.stm.global._ import se.scalablesolutions.akka.config.ScalaConfig._ import se.scalablesolutions.akka.util.Logging @@ -66,9 +66,9 @@ object Runner { def run { val proc = actorOf[RedisSampleStorage] proc.start - val i: Option[String] = proc !! SETFOO("debasish") + val i = (proc !! SETFOO("debasish")).as[String] println("i = " + i) - val ev: Option[Int] = proc !! GETFOO("debasish") + val ev = (proc !! GETFOO("debasish")).as[Int] println(ev) } } diff --git a/akka-persistence/akka-persistence-redis/src/test/scala/RedisPersistentActorSpec.scala b/akka-persistence/akka-persistence-redis/src/test/scala/RedisPersistentActorSpec.scala index 18dd4ce94d..236519abd8 100644 --- a/akka-persistence/akka-persistence-redis/src/test/scala/RedisPersistentActorSpec.scala +++ b/akka-persistence/akka-persistence-redis/src/test/scala/RedisPersistentActorSpec.scala @@ -113,7 +113,7 @@ class RedisPersistentActorSpec extends JUnitSuite { bactor !! Debit("a-123", 8000, failer) assertEquals(BigInt(1000), (bactor !! Balance("a-123")).get) - val c: Int = (bactor !! LogSize).get + val c = (bactor !! LogSize).as[Int].get assertTrue(7 == c) } @@ -134,7 +134,7 @@ class RedisPersistentActorSpec extends JUnitSuite { assertEquals(BigInt(5000), (bactor !! Balance("a-123")).get) // should not count the failed one - val c: Int = (bactor !! LogSize).get + val c = (bactor !! LogSize).as[Int].get assertTrue(3 == c) } @@ -156,7 +156,7 @@ class RedisPersistentActorSpec extends JUnitSuite { assertEquals(BigInt(5000), (bactor !! (Balance("a-123"), 5000)).get) // should not count the failed one - val c: Int = (bactor !! LogSize).get + val c = (bactor !! LogSize).as[Int].get assertTrue(3 == c) } } diff --git a/akka-persistence/akka-persistence-redis/src/test/scala/RedisPersistentQSpec.scala b/akka-persistence/akka-persistence-redis/src/test/scala/RedisPersistentQSpec.scala index 6cdd192593..5522b00d45 100644 --- a/akka-persistence/akka-persistence-redis/src/test/scala/RedisPersistentQSpec.scala +++ b/akka-persistence/akka-persistence-redis/src/test/scala/RedisPersistentQSpec.scala @@ -58,7 +58,7 @@ class RedisPersistentQSpec extends JUnitSuite { qa !! NQ("a-123") qa !! NQ("a-124") qa !! NQ("a-125") - val t: Int = (qa !! SZ).get + val t = (qa !! SZ).as[Int].get assertTrue(3 == t) } @@ -69,12 +69,12 @@ class RedisPersistentQSpec extends JUnitSuite { qa !! NQ("a-123") qa !! NQ("a-124") qa !! NQ("a-125") - val s: Int = (qa !! SZ).get + val s = (qa !! SZ).as[Int].get assertTrue(3 == s) assertEquals("a-123", (qa !! DQ).get) assertEquals("a-124", (qa !! DQ).get) assertEquals("a-125", (qa !! DQ).get) - val t: Int = (qa !! SZ).get + val t = (qa !! SZ).as[Int].get assertTrue(0 == t) } @@ -88,13 +88,13 @@ class RedisPersistentQSpec extends JUnitSuite { qa !! NQ("a-123") qa !! NQ("a-124") qa !! NQ("a-125") - val t: Int = (qa !! SZ).get + val t = (qa !! SZ).as[Int].get assertTrue(3 == t) assertEquals("a-123", (qa !! DQ).get) - val s: Int = (qa !! SZ).get + val s = (qa !! SZ).as[Int].get assertTrue(2 == s) qa !! MNDQ(List("a-126", "a-127"), 2, failer) - val u: Int = (qa !! SZ).get + val u = (qa !! SZ).as[Int].get assertTrue(2 == u) } @@ -110,25 +110,25 @@ class RedisPersistentQSpec extends JUnitSuite { qa !! NQ("a-124") qa !! NQ("a-125") - val t: Int = (qa !! SZ).get + val t = (qa !! SZ).as[Int].get assertTrue(3 == t) // dequeue 1 assertEquals("a-123", (qa !! DQ).get) // size == 2 - val s: Int = (qa !! SZ).get + val s = (qa !! SZ).as[Int].get assertTrue(2 == s) // enqueue 2, dequeue 2 => size == 2 qa !! MNDQ(List("a-126", "a-127"), 2, failer) - val u: Int = (qa !! SZ).get + val u = (qa !! SZ).as[Int].get assertTrue(2 == u) // enqueue 2 => size == 4 qa !! NQ("a-128") qa !! NQ("a-129") - val v: Int = (qa !! SZ).get + val v = (qa !! SZ).as[Int].get assertTrue(4 == v) // enqueue 1 => size 5 @@ -138,7 +138,7 @@ class RedisPersistentQSpec extends JUnitSuite { qa !! MNDQ(List("a-130"), 6, failer) } catch { case e: Exception => {} } - val w: Int = (qa !! SZ).get + val w = (qa !! SZ).as[Int].get assertTrue(4 == w) } } diff --git a/akka-samples/akka-sample-ants/src/main/scala/Ants.scala b/akka-samples/akka-sample-ants/src/main/scala/Ants.scala index 05fe245b10..f1cc0ba628 100644 --- a/akka-samples/akka-sample-ants/src/main/scala/Ants.scala +++ b/akka-samples/akka-sample-ants/src/main/scala/Ants.scala @@ -7,15 +7,13 @@ package sample.ants import java.util.concurrent.TimeUnit import scala.util.Random.{nextInt => randomInt} import se.scalablesolutions.akka -import akka.actor.{ActorRef, Transactor, Scheduler} +import akka.actor.{Actor, ActorRef, Scheduler} import akka.actor.Actor.actorOf -import akka.stm.{Vector => _, _} -import akka.stm.Ref.Ref -import akka.stm.Transaction.Local._ +import akka.stm.local._ object Config { val Dim = 80 // dimensions of square world - val AntsSqrt = 7 // number of ants = AntsSqrt^2 + val AntsSqrt = 20 // number of ants = AntsSqrt^2 val FoodPlaces = 35 // number of places with food val FoodRange = 100 // range of amount of food at a place val PherScale = 10 // scale factor for pheromone drawing @@ -43,7 +41,7 @@ case class Cell(food: Int = 0, pher: Float = 0, ant: Option[Ant] = None, home: B object EmptyCell extends Cell class Place(initCell: Cell = EmptyCell) extends Ref(Some(initCell)) { - def cell: Cell = get.get + def cell: Cell = getOrElse(EmptyCell) def food: Int = cell.food def food(i: Int) = alter(_.addFood(i)) def hasFood = food > 0 @@ -60,6 +58,8 @@ class Place(initCell: Cell = EmptyCell) extends Ref(Some(initCell)) { def home: Boolean = cell.home } +case object Ping + object World { import Config._ @@ -68,6 +68,10 @@ object World { lazy val ants = setup lazy val evaporator = actorOf[Evaporator].start + private val snapshotFactory = TransactionFactory(readonly = true, familyName = "snapshot") + + def snapshot = atomic(snapshotFactory) { Array.tabulate(Dim, Dim)(place(_, _).get) } + def place(loc: (Int, Int)) = places(loc._1)(loc._2) private def setup = atomic { @@ -83,12 +87,12 @@ object World { } def start = { - ants foreach (pingEvery(_, AntMillis)) - pingEvery(evaporator, EvapMillis) + ants foreach pingEvery(AntMillis) + pingEvery(EvapMillis)(evaporator) } - private def pingEvery(actor: ActorRef, millis: Long) = - Scheduler.schedule(actor, "ping", Config.StartDelay, millis, TimeUnit.MILLISECONDS) + private def pingEvery(millis: Long)(actor: ActorRef) = + Scheduler.schedule(actor, Ping, Config.StartDelay, millis, TimeUnit.MILLISECONDS) } object Util { @@ -123,9 +127,9 @@ object Util { } } -trait WorldActor extends Transactor { +trait WorldActor extends Actor { def act - def receive = { case "ping" => act } + def receive = { case Ping => act } } class AntActor(initLoc: (Int, Int)) extends WorldActor { @@ -133,13 +137,17 @@ class AntActor(initLoc: (Int, Int)) extends WorldActor { import Util._ val locRef = Ref(initLoc) + + val name = "ant-from-" + initLoc._1 + "-" + initLoc._2 + implicit val txFactory = TransactionFactory(familyName = name) + val homing = (p: Place) => p.pher + (100 * (if (p.home) 0 else 1)) val foraging = (p: Place) => p.pher + p.food - def loc = locRef.get.getOrElse(initLoc) + def loc = locRef.getOrElse(initLoc) def newLoc(l: (Int, Int)) = locRef swap l - def act = { + def act = atomic { val (x, y) = loc val current = place(x, y) for (ant <- current.ant) { @@ -202,6 +210,11 @@ class AntActor(initLoc: (Int, Int)) extends WorldActor { class Evaporator extends WorldActor { import Config._ import World._ + + implicit val txFactory = TransactionFactory(familyName = "evaporator") val evaporate = (pher: Float) => pher * EvapRate - def act = for (x <- 0 until Dim; y <- 0 until Dim) place(x, y) pher evaporate + + def act = for (x <- 0 until Dim; y <- 0 until Dim) { + atomic { place(x, y) pher evaporate } + } } diff --git a/akka-samples/akka-sample-ants/src/main/spde/Ants.spde b/akka-samples/akka-sample-ants/src/main/spde/Ants.spde index 1d5bf4c39f..05565673d6 100644 --- a/akka-samples/akka-sample-ants/src/main/spde/Ants.spde +++ b/akka-samples/akka-sample-ants/src/main/spde/Ants.spde @@ -1,6 +1,6 @@ import sample.ants._ import sample.ants.Config._ -import se.scalablesolutions.akka.stm.Transaction.Local._ +import se.scalablesolutions.akka.stm.local._ val scale = 5 @@ -8,13 +8,13 @@ size(Dim * scale, Dim * scale) smooth() override def setup() { - background(255) + background(255) World.start } def draw() { - for (x <- 0 until Dim; y <- 0 until Dim) { - val cell = atomic { World.place(x, y).cell } + val world = World.snapshot + for (x <- 0 until Dim; y <- 0 until Dim; cell <- world(x)(y)) { val (rx, ry, rw, rh) = (x * scale, y * scale, scale, scale) noStroke() fill(255) @@ -39,7 +39,7 @@ val s = scale - 1 val m = s / 2 def antLine(dir: Int) = dir match { - case 0|4 => (m, 0, m, s) + case 0|4 => (m, 0, m, s) case 1|5 => (s, 0, 0, s) case 2|6 => (s, m, 0, m) case _ => (s, s, 0, 0) diff --git a/akka-samples/akka-sample-camel/src/main/resources/context-standalone.xml b/akka-samples/akka-sample-camel/src/main/resources/context-standalone.xml index a493678817..f0932ce1b3 100644 --- a/akka-samples/akka-sample-camel/src/main/resources/context-standalone.xml +++ b/akka-samples/akka-sample-camel/src/main/resources/context-standalone.xml @@ -7,6 +7,6 @@ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.akkasource.org/schema/akka http://scalablesolutions.se/akka/akka.xsd"> - + diff --git a/akka-samples/akka-sample-camel/src/main/scala/Actors.scala b/akka-samples/akka-sample-camel/src/main/scala/Actors.scala index f9feaa7f4d..7ab8b0dae5 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/Actors.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/Actors.scala @@ -32,8 +32,6 @@ class Producer1 extends Actor with Producer { override def oneway = false // default override def async = true // default - - protected def receive = produce } class Consumer1 extends Actor with Consumer with Logging { @@ -102,7 +100,6 @@ class Publisher(name: String, uri: String) extends Actor with Producer { self.id = name def endpointUri = uri override def oneway = true - protected def receive = produce } class PublisherBridge(uri: String, publisher: ActorRef) extends Actor with Consumer { diff --git a/akka-samples/akka-sample-camel/src/main/scala/Boot.scala b/akka-samples/akka-sample-camel/src/main/scala/Boot.scala index f5335e5ecd..924cb6c9e5 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/Boot.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/Boot.scala @@ -27,14 +27,18 @@ class Boot { CamelContextManager.context.addRoutes(new CustomRouteBuilder) // ----------------------------------------------------------------------- - // Basic example (using a supervisor for consumer actors) + // Basic example // ----------------------------------------------------------------------- - val supervisor = Supervisor( - SupervisorConfig( - RestartStrategy(OneForOne, 3, 100, List(classOf[Exception])), - Supervise(actorOf[Consumer1], LifeCycle(Permanent)) :: - Supervise(actorOf[Consumer2], LifeCycle(Permanent)) :: Nil)) + actorOf[Consumer1].start + actorOf[Consumer2].start + + // Alternatively, use a supervisor for these actors + //val supervisor = Supervisor( + // SupervisorConfig( + // RestartStrategy(OneForOne, 3, 100, List(classOf[Exception])), + // Supervise(actorOf[Consumer1], LifeCycle(Permanent)) :: + // Supervise(actorOf[Consumer2], LifeCycle(Permanent)) :: Nil)) // ----------------------------------------------------------------------- // Routing example diff --git a/akka-samples/akka-sample-camel/src/main/scala/ServerApplication.scala b/akka-samples/akka-sample-camel/src/main/scala/ServerApplication.scala index 8f53bbb866..7d90e89720 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/ServerApplication.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/ServerApplication.scala @@ -14,8 +14,7 @@ object ServerApplication { // def main(args: Array[String]) { - val camelService = CamelService.newInstance - camelService.load + val camelService = CamelService.newInstance.load RemoteNode.start("localhost", 7777) RemoteNode.register("remote2", actorOf[RemoteActor2].start) } diff --git a/akka-samples/akka-sample-camel/src/main/scala/StandaloneApplication.scala b/akka-samples/akka-sample-camel/src/main/scala/StandaloneApplication.scala index ebfabe9ce2..aa4292696a 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/StandaloneApplication.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/StandaloneApplication.scala @@ -2,6 +2,8 @@ package sample.camel import org.apache.camel.impl.{DefaultCamelContext, SimpleRegistry} import org.apache.camel.builder.RouteBuilder +import org.apache.camel.spring.spi.ApplicationContextRegistry +import org.springframework.context.support.ClassPathXmlApplicationContext import se.scalablesolutions.akka.camel.{CamelService, CamelContextManager} import se.scalablesolutions.akka.actor.{ActorRegistry, ActiveObject} @@ -9,7 +11,7 @@ import se.scalablesolutions.akka.actor.{ActorRegistry, ActiveObject} /** * @author Martin Krasser */ -object PlainApplication { +object StandaloneApplication { def main(args: Array[String]) { import CamelContextManager.context @@ -20,19 +22,18 @@ object PlainApplication { // customize CamelContext CamelContextManager.init(new DefaultCamelContext(registry)) - CamelContextManager.context.addRoutes(new PlainApplicationRoute) + CamelContextManager.context.addRoutes(new StandaloneApplicationRoute) // start CamelService - val camelService = CamelService.newInstance - camelService.load + val camelService = CamelService.newInstance.load + + // access 'externally' registered active objects + assert("hello msg1" == context.createProducerTemplate.requestBody("direct:test1", "msg1")) + assert("hello msg2" == context.createProducerTemplate.requestBody("direct:test2", "msg2")) // 'internally' register active object (requires CamelService) ActiveObject.newInstance(classOf[ConsumerPojo2]) - // access 'externally' registered active objects with active-object component - assert("hello msg1" == context.createProducerTemplate.requestBody("direct:test1", "msg1")) - assert("hello msg2" == context.createProducerTemplate.requestBody("direct:test2", "msg2")) - // internal registration is done in background. Wait a bit ... Thread.sleep(1000) @@ -48,13 +49,43 @@ object PlainApplication { } } -class PlainApplicationRoute extends RouteBuilder { +class StandaloneApplicationRoute extends RouteBuilder { def configure = { + // routes to active objects (in SimpleRegistry) from("direct:test1").to("active-object:pojo1?method=foo") from("direct:test2").to("active-object:pojo2?method=foo") } } -object SpringApplication { - // TODO +object StandaloneSpringApplication { + def main(args: Array[String]) { + import CamelContextManager.context + + // use Spring application context as active object registry + val springctx = new ClassPathXmlApplicationContext("/context-standalone.xml") + val registry = new ApplicationContextRegistry(springctx) + + // customize CamelContext + CamelContextManager.init(new DefaultCamelContext(registry)) + CamelContextManager.context.addRoutes(new StandaloneSpringApplicationRoute) + + // start CamelService + val camelService = CamelService.newInstance.load + + // access 'externally' registered active objects with active-object component + assert("hello msg3" == context.createProducerTemplate.requestBody("direct:test3", "msg3")) + + // shutdown CamelService + camelService.unload + + // shutdown all (internally) created actors + ActorRegistry.shutdownAll + } } + +class StandaloneSpringApplicationRoute extends RouteBuilder { + def configure = { + // routes to active object (in ApplicationContextRegistry) + from("direct:test3").to("active-object:pojo3?method=foo") + } +} \ No newline at end of file diff --git a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala index f244f8eeef..01e1738b87 100644 --- a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala +++ b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala @@ -10,7 +10,7 @@ import se.scalablesolutions.akka.actor.{SupervisorFactory, Actor, ActorRef, Remo import se.scalablesolutions.akka.remote.{RemoteNode, RemoteClient} import se.scalablesolutions.akka.persistence.common.PersistentVector import se.scalablesolutions.akka.persistence.redis.RedisStorage -import se.scalablesolutions.akka.stm.Transaction.Global._ +import se.scalablesolutions.akka.stm.global._ import se.scalablesolutions.akka.config.ScalaConfig._ import se.scalablesolutions.akka.config.OneForOneStrategy import se.scalablesolutions.akka.util.Logging @@ -63,10 +63,10 @@ case class ChatMessage(from: String, message: String) extends Event class ChatClient(val name: String) { val chat = RemoteClient.actorFor("chat:service", "localhost", 9999) - def login = chat ! Login(name) - def logout = chat ! Logout(name) + def login = chat ! Login(name) + def logout = chat ! Logout(name) def post(message: String) = chat ! ChatMessage(name, name + ": " + message) - def chatLog: ChatLog = (chat !! GetChatLog(name)).getOrElse(throw new Exception("Couldn't get the chat log from ChatServer")) + def chatLog = (chat !! GetChatLog(name)).as[ChatLog].getOrElse(throw new Exception("Couldn't get the chat log from ChatServer")) } /** diff --git a/akka-samples/akka-sample-lift/src/main/scala/akka/SimpleService.scala b/akka-samples/akka-sample-lift/src/main/scala/akka/SimpleService.scala index 33037b8d8b..7557404da9 100644 --- a/akka-samples/akka-sample-lift/src/main/scala/akka/SimpleService.scala +++ b/akka-samples/akka-sample-lift/src/main/scala/akka/SimpleService.scala @@ -2,7 +2,7 @@ package sample.lift import se.scalablesolutions.akka.actor.{Transactor, Actor} import se.scalablesolutions.akka.config.ScalaConfig._ -import se.scalablesolutions.akka.stm.TransactionalState +import se.scalablesolutions.akka.stm.TransactionalMap import se.scalablesolutions.akka.persistence.cassandra.CassandraStorage import Actor._ @@ -22,7 +22,7 @@ class SimpleService extends Transactor { case object Tick private val KEY = "COUNTER" private var hasStartedTicking = false - private lazy val storage = TransactionalState.newMap[String, Integer] + private lazy val storage = TransactionalMap[String, Integer]() @GET @Produces(Array("text/html")) diff --git a/akka-samples/akka-sample-rest-java/src/main/java/sample/rest/java/SimpleService.java b/akka-samples/akka-sample-rest-java/src/main/java/sample/rest/java/SimpleService.java index 44d23e873c..097ba810b5 100644 --- a/akka-samples/akka-sample-rest-java/src/main/java/sample/rest/java/SimpleService.java +++ b/akka-samples/akka-sample-rest-java/src/main/java/sample/rest/java/SimpleService.java @@ -9,7 +9,6 @@ import se.scalablesolutions.akka.actor.ActiveObjectContext; import se.scalablesolutions.akka.actor.annotation.transactionrequired; import se.scalablesolutions.akka.actor.annotation.prerestart; import se.scalablesolutions.akka.actor.annotation.postrestart; -import se.scalablesolutions.akka.stm.TransactionalState; import se.scalablesolutions.akka.stm.TransactionalMap; @transactionrequired @@ -21,7 +20,7 @@ public class SimpleService { private Receiver receiver = ActiveObject.newInstance(Receiver.class); public String count() { - if (storage == null) storage = TransactionalState.newMap(); + if (storage == null) storage = new TransactionalMap(); if (!hasStartedTicking) { storage.put(KEY, 0); hasStartedTicking = true; diff --git a/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala b/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala index 90e208d7e1..870739bf8f 100644 --- a/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala +++ b/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala @@ -6,7 +6,7 @@ package sample.rest.scala import se.scalablesolutions.akka.actor.{Transactor, SupervisorFactory, Actor} import se.scalablesolutions.akka.actor.Actor._ -import se.scalablesolutions.akka.stm.TransactionalState +import se.scalablesolutions.akka.stm.TransactionalMap import se.scalablesolutions.akka.persistence.cassandra.CassandraStorage import se.scalablesolutions.akka.config.ScalaConfig._ import se.scalablesolutions.akka.util.Logging @@ -54,7 +54,7 @@ class SimpleService { //Fetch the first actor of type SimpleServiceActor //Send it the "Tick" message and expect a NodeSeq back val result = for{a <- actorsFor(classOf[SimpleServiceActor]).headOption - r <- a.!![NodeSeq]("Tick")} yield r + r <- (a !! "Tick").as[NodeSeq]} yield r //Return either the resulting NodeSeq or a default one result getOrElse Error in counter } @@ -63,7 +63,7 @@ class SimpleService { class SimpleServiceActor extends Transactor { private val KEY = "COUNTER" private var hasStartedTicking = false - private lazy val storage = TransactionalState.newMap[String, Integer] + private lazy val storage = TransactionalMap[String, Integer]() def receive = { case "Tick" => if (hasStartedTicking) { @@ -109,7 +109,7 @@ class PersistentSimpleService { //Fetch the first actor of type PersistentSimpleServiceActor //Send it the "Tick" message and expect a NodeSeq back val result = for{a <- actorsFor(classOf[PersistentSimpleServiceActor]).headOption - r <- a.!![NodeSeq]("Tick")} yield r + r <- (a !! "Tick").as[NodeSeq]} yield r //Return either the resulting NodeSeq or a default one result getOrElse Error in counter } @@ -156,7 +156,7 @@ class Chat { //Fetch the first actor of type ChatActor //Send it the "Tick" message and expect a NodeSeq back val result = for{a <- actorsFor(classOf[ChatActor]).headOption - r <- a.!![String](msg)} yield r + r <- (a !! msg).as[String]} yield r //Return either the resulting String or a default one result getOrElse "System__error" } diff --git a/akka-samples/akka-sample-security/src/main/scala/SimpleService.scala b/akka-samples/akka-sample-security/src/main/scala/SimpleService.scala index e6892c7b62..e5c8029eb8 100644 --- a/akka-samples/akka-sample-security/src/main/scala/SimpleService.scala +++ b/akka-samples/akka-sample-security/src/main/scala/SimpleService.scala @@ -9,7 +9,7 @@ import se.scalablesolutions.akka.actor.Actor._ import se.scalablesolutions.akka.config.ScalaConfig._ import se.scalablesolutions.akka.util.Logging import se.scalablesolutions.akka.security.{BasicAuthenticationActor,BasicCredentials,SpnegoAuthenticationActor,DigestAuthenticationActor, UserInfo} -import se.scalablesolutions.akka.stm.TransactionalState +import se.scalablesolutions.akka.stm.TransactionalMap import se.scalablesolutions.akka.actor.ActorRegistry.actorsFor class Boot { @@ -123,7 +123,7 @@ class SecureTickService { //Fetch the first actor of type PersistentSimpleServiceActor //Send it the "Tick" message and expect a NdeSeq back val result = for{a <- actorsFor(classOf[SecureTickActor]).headOption - r <- a.!![Integer]("Tick")} yield r + r <- (a !! "Tick").as[Integer]} yield r //Return either the resulting NodeSeq or a default one result match { case (Some(counter)) => (Tick: {counter}) @@ -135,7 +135,7 @@ class SecureTickService { class SecureTickActor extends Transactor with Logging { private val KEY = "COUNTER" private var hasStartedTicking = false - private lazy val storage = TransactionalState.newMap[String, Integer] + private lazy val storage = TransactionalMap[String, Integer]() def receive = { case "Tick" => if (hasStartedTicking) { val counter = storage.get(KEY).get.intValue diff --git a/akka-spring/akka-spring-test-java/pom.xml b/akka-spring/akka-spring-test-java/pom.xml index 11a29262ac..ff36202071 100644 --- a/akka-spring/akka-spring-test-java/pom.xml +++ b/akka-spring/akka-spring-test-java/pom.xml @@ -17,6 +17,11 @@ + + akka + Akka Repo + http://www.scalablesolutions.se/akka/repository/ + project.embedded.module Project Embedded Repository @@ -146,23 +151,23 @@ se.scalablesolutions.akka - akka-core_2.8.0.Beta1 - 0.9 + akka-core_2.8.0.RC3 + 0.9.1 se.scalablesolutions.akka akka-util_2.8.0.Beta1 - 0.9 + 0.8.1 se.scalablesolutions.akka akka-util-java_2.8.0.Beta1 - 0.9 + 0.8.1 se.scalablesolutions.akka - akka-spring_2.8.0.Beta1 - 0.9 + akka-spring_2.8.0.RC3 + 0.9.1 org.springframework diff --git a/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/StatefulPojo.java b/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/StatefulPojo.java index f2308e194f..17332c696a 100644 --- a/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/StatefulPojo.java +++ b/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/StatefulPojo.java @@ -3,21 +3,20 @@ package se.scalablesolutions.akka.spring.foo; import se.scalablesolutions.akka.actor.annotation.inittransactionalstate; import se.scalablesolutions.akka.stm.TransactionalMap; import se.scalablesolutions.akka.stm.TransactionalVector; -import se.scalablesolutions.akka.stm.TransactionalRef; -import se.scalablesolutions.akka.stm.TransactionalState; +import se.scalablesolutions.akka.stm.Ref; public class StatefulPojo { private TransactionalMap mapState; private TransactionalVector vectorState; - private TransactionalRef refState; + private Ref refState; private boolean isInitialized = false; @inittransactionalstate public void init() { if (!isInitialized) { - mapState = TransactionalState.newMap(); - vectorState = TransactionalState.newVector(); - refState = TransactionalState.newRef(); + mapState = new TransactionalMap(); + vectorState = new TransactionalVector(); + refState = new Ref(); isInitialized = true; } } diff --git a/akka-spring/src/main/resources/META-INF/spring.schemas b/akka-spring/src/main/resources/META-INF/spring.schemas index d04d65566a..27f7704018 100644 --- a/akka-spring/src/main/resources/META-INF/spring.schemas +++ b/akka-spring/src/main/resources/META-INF/spring.schemas @@ -1 +1 @@ -http\://www.akkasource.org/schema/akka=se/scalablesolutions/akka/spring/akka.xsd +http\://scalablesolutions.se/akka/akka-0.10.xsd=se/scalablesolutions/akka/spring/akka-0.10.xsd diff --git a/akka-spring/src/main/resources/se/scalablesolutions/akka/spring/akka-0.10.xsd b/akka-spring/src/main/resources/se/scalablesolutions/akka/spring/akka-0.10.xsd new file mode 100644 index 0000000000..c81fb12af3 --- /dev/null +++ b/akka-spring/src/main/resources/se/scalablesolutions/akka/spring/akka-0.10.xsd @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the remote host. + + + + + + + Port of the remote host. + + + + + + + + + + + Pre restart callback method that is called during restart. + + + + + + + Post restart callback method that is called during restart. + + + + + + + + + + + + + + + + + + + Name of the target class. + + + + + + + default timeout for '!!' invocations + + + + + + + Set to true if messages should have REQUIRES_NEW semantics + + + + + + + Interface implemented by target class. + + + + + + + Lifecycle, permanent or temporary + + + + + + + Supported scopes are singleton and prototype + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failover scheme, AllForOne or OneForOne + + + + + + + Maximal number of retries. + + + + + + + Timerange for restart. + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/akka-spring/src/main/resources/se/scalablesolutions/akka/spring/akka.xsd b/akka-spring/src/main/resources/se/scalablesolutions/akka/spring/akka.xsd index 20cb966b8f..862cd06987 100644 --- a/akka-spring/src/main/resources/se/scalablesolutions/akka/spring/akka.xsd +++ b/akka-spring/src/main/resources/se/scalablesolutions/akka/spring/akka.xsd @@ -25,6 +25,13 @@ + + + + + + + @@ -158,6 +165,13 @@ Lifecycle, permanent or temporary + + + + + Supported scopes are singleton and prototype + + diff --git a/akka-spring/src/main/scala/ActiveObjectFactoryBean.scala b/akka-spring/src/main/scala/ActiveObjectFactoryBean.scala index 66ef87a15d..b6a51138dc 100644 --- a/akka-spring/src/main/scala/ActiveObjectFactoryBean.scala +++ b/akka-spring/src/main/scala/ActiveObjectFactoryBean.scala @@ -4,19 +4,31 @@ package se.scalablesolutions.akka.spring +import java.beans.PropertyDescriptor + +import java.lang.reflect.Method +import org.springframework.beans.BeanWrapperImpl +import org.springframework.beans.BeanWrapper +import org.springframework.beans.BeanUtils +import org.springframework.util.ReflectionUtils +import org.springframework.util.StringUtils +import org.springframework.beans.factory.BeanFactory import org.springframework.beans.factory.config.AbstractFactoryBean import se.scalablesolutions.akka.actor.ActiveObject import reflect.BeanProperty import se.scalablesolutions.akka.config.ScalaConfig.RestartCallbacks import se.scalablesolutions.akka.dispatch.MessageDispatcher - +import se.scalablesolutions.akka.util.Logging /** * Factory bean for active objects. + * * @author michaelkober + * @author Johan Rask */ -class ActiveObjectFactoryBean extends AbstractFactoryBean[AnyRef] { +class ActiveObjectFactoryBean extends AbstractFactoryBean[AnyRef] with Logging { import StringReflect._ + import AkkaSpringConfigurationTags._ @BeanProperty var target: String = "" @BeanProperty var timeout: Long = _ @@ -28,6 +40,8 @@ class ActiveObjectFactoryBean extends AbstractFactoryBean[AnyRef] { @BeanProperty var port: Int = _ @BeanProperty var lifecycle: String = "" @BeanProperty var dispatcher: DispatcherProperties = _ + @BeanProperty var scope:String = VAL_SCOPE_SINGLETON + @BeanProperty var property:PropertyEntries = _ /* * @see org.springframework.beans.factory.FactoryBean#getObjectType() @@ -39,11 +53,44 @@ class ActiveObjectFactoryBean extends AbstractFactoryBean[AnyRef] { * @see org.springframework.beans.factory.config.AbstractFactoryBean#createInstance() */ def createInstance: AnyRef = { + if(scope.equals(VAL_SCOPE_SINGLETON)) { + setSingleton(true) + } else { + setSingleton(false) + } var argumentList = "" if (isRemote) argumentList += "r" if (hasInterface) argumentList += "i" if (hasDispatcher) argumentList += "d" - create(argumentList) + + setProperties( + create(argumentList)) +} + + /** + * This method manages element by injecting either + * values () and bean references () + */ + private def setProperties(ref:AnyRef) : AnyRef = { + log.debug("Processing properties and dependencies for target class %s",target) + val beanWrapper = new BeanWrapperImpl(ref); + for(entry <- property.entryList) { + val propertyDescriptor = BeanUtils.getPropertyDescriptor(ref.getClass,entry.name) + val method = propertyDescriptor.getWriteMethod(); + + if(StringUtils.hasText(entry.ref)) { + log.debug("Setting property %s with bean ref %s using method %s", + entry.name,entry.ref,method.getName) + method.invoke(ref,getBeanFactory().getBean(entry.ref)) + } else if(StringUtils.hasText(entry.value)) { + log.debug("Setting property %s with value %s using method %s", + entry.name,entry.value,method.getName) + beanWrapper.setPropertyValue(entry.name,entry.value) + } else { + throw new AkkaBeansException("Either property@ref or property@value must be set on property element") + } + } + ref } // TODO: check if this works in 2.8 (type inferred to Nothing instead of AnyRef here) @@ -63,15 +110,15 @@ class ActiveObjectFactoryBean extends AbstractFactoryBean[AnyRef] { if (argList == "r") { ActiveObject.newRemoteInstance(target.toClass, timeout, transactional, host, port, callbacks) } else if (argList == "ri" ) { - ActiveObject.newRemoteInstance(interface.toClass, target.toClass, timeout, transactional, host, port, callbacks) + ActiveObject.newRemoteInstance(interface.toClass, aNewInstance(target.toClass), timeout, transactional, host, port, callbacks) } else if (argList == "rd") { ActiveObject.newRemoteInstance(target.toClass, timeout, transactional, dispatcherInstance, host, port, callbacks) } else if (argList == "rid") { - ActiveObject.newRemoteInstance(interface.toClass, target.toClass, timeout, transactional, dispatcherInstance, host, port, callbacks) + ActiveObject.newRemoteInstance(interface.toClass, aNewInstance(target.toClass), timeout, transactional, dispatcherInstance, host, port, callbacks) } else if (argList == "i") { - ActiveObject.newInstance(interface.toClass, target.toClass, timeout, transactional, callbacks) + ActiveObject.newInstance(interface.toClass, aNewInstance(target.toClass), timeout, transactional, callbacks) } else if (argList == "id") { - ActiveObject.newInstance(interface.toClass, target.toClass, timeout, transactional, dispatcherInstance, callbacks) + ActiveObject.newInstance(interface.toClass, aNewInstance(target.toClass), timeout, transactional, dispatcherInstance, callbacks) } else if (argList == "d") { ActiveObject.newInstance(target.toClass, timeout, transactional, dispatcherInstance, callbacks) } else { @@ -79,6 +126,10 @@ class ActiveObjectFactoryBean extends AbstractFactoryBean[AnyRef] { } } + def aNewInstance[T <: AnyRef](clazz: Class[T]) : T = { + clazz.newInstance().asInstanceOf[T] + } + /** * create Option[RestartCallback] */ diff --git a/akka-spring/src/main/scala/ActiveObjectParser.scala b/akka-spring/src/main/scala/ActiveObjectParser.scala index dd48f8dbe1..d3b25791c6 100644 --- a/akka-spring/src/main/scala/ActiveObjectParser.scala +++ b/akka-spring/src/main/scala/ActiveObjectParser.scala @@ -5,10 +5,12 @@ package se.scalablesolutions.akka.spring import org.springframework.util.xml.DomUtils import org.w3c.dom.Element +import scala.collection.JavaConversions._ /** * Parser trait for custom namespace configuration for active-object. * @author michaelkober + * @author Johan Rask */ trait ActiveObjectParser extends BeanParser with DispatcherParser { import AkkaSpringConfigurationTags._ @@ -23,6 +25,7 @@ trait ActiveObjectParser extends BeanParser with DispatcherParser { val remoteElement = DomUtils.getChildElementByTagName(element, REMOTE_TAG); val callbacksElement = DomUtils.getChildElementByTagName(element, RESTART_CALLBACKS_TAG); val dispatcherElement = DomUtils.getChildElementByTagName(element, DISPATCHER_TAG) + val propertyEntries = DomUtils.getChildElementsByTagName(element,PROPERTYENTRY_TAG) if (remoteElement != null) { objectProperties.host = mandatory(remoteElement, HOST) @@ -42,6 +45,14 @@ trait ActiveObjectParser extends BeanParser with DispatcherParser { objectProperties.dispatcher = dispatcherProperties } + for(element <- propertyEntries) { + val entry = new PropertyEntry() + entry.name = element.getAttribute("name"); + entry.value = element.getAttribute("value") + entry.ref = element.getAttribute("ref") + objectProperties.propertyEntries.add(entry) + } + try { objectProperties.timeout = mandatory(element, TIMEOUT).toLong } catch { @@ -58,8 +69,13 @@ trait ActiveObjectParser extends BeanParser with DispatcherParser { } if (!element.getAttribute(LIFECYCLE).isEmpty) { - objectProperties.lifecyclye = element.getAttribute(LIFECYCLE) + objectProperties.lifecycle = element.getAttribute(LIFECYCLE) } + + if (!element.getAttribute(SCOPE).isEmpty) { + objectProperties.scope = element.getAttribute(SCOPE) + } + objectProperties } diff --git a/akka-spring/src/main/scala/ActiveObjectProperties.scala b/akka-spring/src/main/scala/ActiveObjectProperties.scala index e273d27a8d..ba4828e2f9 100644 --- a/akka-spring/src/main/scala/ActiveObjectProperties.scala +++ b/akka-spring/src/main/scala/ActiveObjectProperties.scala @@ -20,8 +20,10 @@ class ActiveObjectProperties { var postRestart: String = "" var host: String = "" var port: Int = _ - var lifecyclye: String = "" + var lifecycle: String = "" + var scope:String = "" var dispatcher: DispatcherProperties = _ + var propertyEntries = new PropertyEntries() /** @@ -37,8 +39,10 @@ class ActiveObjectProperties { builder.addPropertyValue(TARGET, target) builder.addPropertyValue(INTERFACE, interface) builder.addPropertyValue(TRANSACTIONAL, transactional) - builder.addPropertyValue(LIFECYCLE, lifecyclye) + builder.addPropertyValue(LIFECYCLE, lifecycle) + builder.addPropertyValue(SCOPE, scope) builder.addPropertyValue(DISPATCHER_TAG, dispatcher) - } + builder.addPropertyValue(PROPERTYENTRY_TAG,propertyEntries) +} } diff --git a/akka-spring/src/main/scala/AkkaBeansException.scala b/akka-spring/src/main/scala/AkkaBeansException.scala new file mode 100644 index 0000000000..58928cf69e --- /dev/null +++ b/akka-spring/src/main/scala/AkkaBeansException.scala @@ -0,0 +1,14 @@ +package se.scalablesolutions.akka.spring + +import org.springframework.beans.BeansException + +/** +* Exception to use when something goes wrong during bean creation +@author Johan Rask +*/ +class AkkaBeansException(errorMsg:String,t:Throwable) extends BeansException(errorMsg,t) { + + def this(errorMsg:String) = { + this(errorMsg,null) + } +} \ No newline at end of file diff --git a/akka-spring/src/main/scala/AkkaSpringConfigurationTags.scala b/akka-spring/src/main/scala/AkkaSpringConfigurationTags.scala index 8ceb1b8f6a..39cb09dd64 100644 --- a/akka-spring/src/main/scala/AkkaSpringConfigurationTags.scala +++ b/akka-spring/src/main/scala/AkkaSpringConfigurationTags.scala @@ -15,6 +15,7 @@ object AkkaSpringConfigurationTags { val ACTIVE_OBJECT_TAG = "active-object" val SUPERVISION_TAG = "supervision" val DISPATCHER_TAG = "dispatcher" + val PROPERTYENTRY_TAG = "property" // active-object sub tags val RESTART_CALLBACKS_TAG = "restart-callbacks" @@ -41,6 +42,7 @@ object AkkaSpringConfigurationTags { val PRE_RESTART = "pre" val POST_RESTART = "post" val LIFECYCLE = "lifecycle" + val SCOPE = "scope" // supervision attributes val FAILOVER = "failover" @@ -68,6 +70,9 @@ object AkkaSpringConfigurationTags { val VAL_LIFECYCYLE_TEMPORARY = "temporary" val VAL_LIFECYCYLE_PERMANENT = "permanent" + val VAL_SCOPE_SINGLETON = "singleton" + val VAL_SCOPE_PROTOTYPE = "prototype" + // Failover val VAL_ALL_FOR_ONE = "AllForOne" val VAL_ONE_FOR_ONE = "OneForOne" diff --git a/akka-spring/src/main/scala/PropertyEntries.scala b/akka-spring/src/main/scala/PropertyEntries.scala new file mode 100644 index 0000000000..935baac4f4 --- /dev/null +++ b/akka-spring/src/main/scala/PropertyEntries.scala @@ -0,0 +1,18 @@ +package se.scalablesolutions.akka.spring + +import org.springframework.beans.factory.support.BeanDefinitionBuilder + +import scala.collection.mutable._ + +/** +* Simple container for Properties +* @author Johan Rask +*/ +class PropertyEntries { + + var entryList:ListBuffer[PropertyEntry] = ListBuffer[PropertyEntry]() + + def add(entry:PropertyEntry) = { + entryList.append(entry) + } +} \ No newline at end of file diff --git a/akka-spring/src/main/scala/PropertyEntry.scala b/akka-spring/src/main/scala/PropertyEntry.scala new file mode 100644 index 0000000000..a01241635b --- /dev/null +++ b/akka-spring/src/main/scala/PropertyEntry.scala @@ -0,0 +1,17 @@ +package se.scalablesolutions.akka.spring + +/** +* Represents a property element +* @author Johan Rask +*/ +class PropertyEntry { + + var name:String = _ + var value:String = null + var ref:String = null + + + override def toString(): String = { + format("name = %s,value = %s, ref = %s", name,value,ref) + } +} \ No newline at end of file diff --git a/akka-spring/src/main/scala/SupervisionFactoryBean.scala b/akka-spring/src/main/scala/SupervisionFactoryBean.scala index d82d329f79..d8c44c3502 100644 --- a/akka-spring/src/main/scala/SupervisionFactoryBean.scala +++ b/akka-spring/src/main/scala/SupervisionFactoryBean.scala @@ -40,7 +40,7 @@ class SupervisionFactoryBean extends AbstractFactoryBean[ActiveObjectConfigurato */ private[akka] def createComponent(props: ActiveObjectProperties): Component = { import StringReflect._ - val lifeCycle = if (!props.lifecyclye.isEmpty && props.lifecyclye.equalsIgnoreCase(VAL_LIFECYCYLE_TEMPORARY)) new LifeCycle(new Temporary()) else new LifeCycle(new Permanent()) + val lifeCycle = if (!props.lifecycle.isEmpty && props.lifecycle.equalsIgnoreCase(VAL_LIFECYCYLE_TEMPORARY)) new LifeCycle(new Temporary()) else new LifeCycle(new Permanent()) val isRemote = (props.host != null) && (!props.host.isEmpty) val withInterface = (props.interface != null) && (!props.interface.isEmpty) if (isRemote) { diff --git a/akka-spring/src/test/scala/ActiveObjectBeanDefinitionParserTest.scala b/akka-spring/src/test/scala/ActiveObjectBeanDefinitionParserTest.scala index e7ed68c379..23972e8f2e 100644 --- a/akka-spring/src/test/scala/ActiveObjectBeanDefinitionParserTest.scala +++ b/akka-spring/src/test/scala/ActiveObjectBeanDefinitionParserTest.scala @@ -25,13 +25,18 @@ class ActiveObjectBeanDefinitionParserTest extends Spec with ShouldMatchers { val xml = + transactional="true" + scope="prototype"> + + val props = parser.parseActiveObject(dom(xml).getDocumentElement); assert(props != null) - assert(props.timeout == 1000) - assert(props.target == "foo.bar.MyPojo") + assert(props.timeout === 1000) + assert(props.target === "foo.bar.MyPojo") assert(props.transactional) + assert(props.scope === "prototype") + assert(props.propertyEntries.entryList.size === 1) } it("should throw IllegalArgumentException on missing mandatory attributes") { @@ -50,7 +55,7 @@ class ActiveObjectBeanDefinitionParserTest extends Spec with ShouldMatchers { val props = parser.parseActiveObject(dom(xml).getDocumentElement); assert(props != null) assert(props.dispatcher.dispatcherType == "thread-based") - } +} it("should parse remote ActiveObjects configuration") { val xml = - service = on - fair = on # should transactions be fair or non-fair (non fair yield better performance) - max-nr-of-retries = 1000 # max nr of retries of a failing transaction before giving up - timeout = 10000 # transaction timeout; if transaction has not committed within the timeout then it is aborted + fair = on # should global transactions be fair or non-fair (non fair yield better performance) jta-aware = off # 'on' means that if there JTA Transaction Manager available then the STM will # begin (or join), commit or rollback the JTA transaction. Default is 'off'. diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index 01d228c397..5f1f2d1264 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -19,7 +19,7 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { val CASSANDRA_VERSION = "0.6.1" val LIFT_VERSION = "2.0-scala280-SNAPSHOT" val SCALATEST_VERSION = "1.2-for-scala-2.8.0.RC3-SNAPSHOT" - val MULTIVERSE_VERSION = "0.5.2" + val MULTIVERSE_VERSION = "0.6-SNAPSHOT" // ------------------------------------------------------------ lazy val deployPath = info.projectPath / "deploy" @@ -65,6 +65,8 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { val grizzlyModuleConfig = ModuleConfiguration("com.sun.grizzly", javaNetRepo) // val atmosphereModuleConfig = ModuleConfiguration("org.atmosphere", sonatypeSnapshotRepo) val liftModuleConfig = ModuleConfiguration("net.liftweb", ScalaToolsSnapshots) + def codehausSnapshotRepo = "Codehaus Snapshots" at "http://snapshots.repository.codehaus.org" + val multiverseModuleConfig = ModuleConfiguration("org.multiverse", codehausSnapshotRepo) // ------------------------------------------------------------ // project defintions @@ -174,6 +176,7 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { // subprojects class AkkaCoreProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { val netty = "org.jboss.netty" % "netty" % "3.2.0.CR1" % "compile" + val commons_codec = "commons-codec" % "commons-codec" % "1.4" % "compile" val commons_io = "commons-io" % "commons-io" % "1.4" % "compile" val dispatch_json = "net.databinder" % "dispatch-json_2.8.0.RC3" % "0.7.4" % "compile" val dispatch_htdisttp = "net.databinder" % "dispatch-http_2.8.0.RC3" % "0.7.4" % "compile"