closes #314 akka-spring to support active object lifecycle management
closes #315 akka-spring to support configuration of shutdown callback method
This commit is contained in:
parent
460dcfe516
commit
fd9fbb1b05
12 changed files with 168 additions and 90 deletions
|
|
@ -37,6 +37,7 @@ object Annotations {
|
||||||
final class ActiveObjectConfiguration {
|
final class ActiveObjectConfiguration {
|
||||||
private[akka] var _timeout: Long = Actor.TIMEOUT
|
private[akka] var _timeout: Long = Actor.TIMEOUT
|
||||||
private[akka] var _restartCallbacks: Option[RestartCallbacks] = None
|
private[akka] var _restartCallbacks: Option[RestartCallbacks] = None
|
||||||
|
private[akka] var _shutdownCallback: Option[ShutdownCallback] = None
|
||||||
private[akka] var _transactionRequired = false
|
private[akka] var _transactionRequired = false
|
||||||
private[akka] var _host: Option[InetSocketAddress] = None
|
private[akka] var _host: Option[InetSocketAddress] = None
|
||||||
private[akka] var _messageDispatcher: Option[MessageDispatcher] = None
|
private[akka] var _messageDispatcher: Option[MessageDispatcher] = None
|
||||||
|
|
@ -51,6 +52,11 @@ final class ActiveObjectConfiguration {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def shutdownCallback(down: String) : ActiveObjectConfiguration = {
|
||||||
|
_shutdownCallback = Some(new ShutdownCallback(down))
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
def makeTransactionRequired() : ActiveObjectConfiguration = {
|
def makeTransactionRequired() : ActiveObjectConfiguration = {
|
||||||
_transactionRequired = true;
|
_transactionRequired = true;
|
||||||
this
|
this
|
||||||
|
|
@ -153,25 +159,25 @@ object ActiveObject extends Logging {
|
||||||
private[actor] val AW_PROXY_PREFIX = "$$ProxiedByAW".intern
|
private[actor] val AW_PROXY_PREFIX = "$$ProxiedByAW".intern
|
||||||
|
|
||||||
def newInstance[T](target: Class[T], timeout: Long): T =
|
def newInstance[T](target: Class[T], timeout: Long): T =
|
||||||
newInstance(target, actorOf(new Dispatcher(false, None)), None, timeout)
|
newInstance(target, actorOf(new Dispatcher(false)), None, timeout)
|
||||||
|
|
||||||
def newInstance[T](target: Class[T]): T =
|
def newInstance[T](target: Class[T]): T =
|
||||||
newInstance(target, actorOf(new Dispatcher(false, None)), None, Actor.TIMEOUT)
|
newInstance(target, actorOf(new Dispatcher(false)), None, Actor.TIMEOUT)
|
||||||
|
|
||||||
def newInstance[T](intf: Class[T], target: AnyRef, timeout: Long): T =
|
def newInstance[T](intf: Class[T], target: AnyRef, timeout: Long): T =
|
||||||
newInstance(intf, target, actorOf(new Dispatcher(false, None)), None, timeout)
|
newInstance(intf, target, actorOf(new Dispatcher(false)), None, timeout)
|
||||||
|
|
||||||
def newInstance[T](intf: Class[T], target: AnyRef): T =
|
def newInstance[T](intf: Class[T], target: AnyRef): T =
|
||||||
newInstance(intf, target, actorOf(new Dispatcher(false, None)), None, Actor.TIMEOUT)
|
newInstance(intf, target, actorOf(new Dispatcher(false)), None, Actor.TIMEOUT)
|
||||||
|
|
||||||
def newRemoteInstance[T](target: Class[T], timeout: Long, hostname: String, port: Int): T =
|
def newRemoteInstance[T](target: Class[T], timeout: Long, hostname: String, port: Int): T =
|
||||||
newInstance(target, actorOf(new Dispatcher(false, None)), Some(new InetSocketAddress(hostname, port)), timeout)
|
newInstance(target, actorOf(new Dispatcher(false)), Some(new InetSocketAddress(hostname, port)), timeout)
|
||||||
|
|
||||||
def newRemoteInstance[T](target: Class[T], hostname: String, port: Int): T =
|
def newRemoteInstance[T](target: Class[T], hostname: String, port: Int): T =
|
||||||
newInstance(target, actorOf(new Dispatcher(false, None)), Some(new InetSocketAddress(hostname, port)), Actor.TIMEOUT)
|
newInstance(target, actorOf(new Dispatcher(false)), Some(new InetSocketAddress(hostname, port)), Actor.TIMEOUT)
|
||||||
|
|
||||||
def newInstance[T](target: Class[T], config: ActiveObjectConfiguration): T = {
|
def newInstance[T](target: Class[T], config: ActiveObjectConfiguration): T = {
|
||||||
val actor = actorOf(new Dispatcher(config._transactionRequired, config._restartCallbacks))
|
val actor = actorOf(new Dispatcher(config._transactionRequired, config._restartCallbacks, config._shutdownCallback))
|
||||||
if (config._messageDispatcher.isDefined) {
|
if (config._messageDispatcher.isDefined) {
|
||||||
actor.dispatcher = config._messageDispatcher.get
|
actor.dispatcher = config._messageDispatcher.get
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +185,7 @@ object ActiveObject extends Logging {
|
||||||
}
|
}
|
||||||
|
|
||||||
def newInstance[T](intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration): T = {
|
def newInstance[T](intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration): T = {
|
||||||
val actor = actorOf(new Dispatcher(config._transactionRequired, config._restartCallbacks))
|
val actor = actorOf(new Dispatcher(config._transactionRequired, config._restartCallbacks, config._shutdownCallback))
|
||||||
if (config._messageDispatcher.isDefined) {
|
if (config._messageDispatcher.isDefined) {
|
||||||
actor.dispatcher = config._messageDispatcher.get
|
actor.dispatcher = config._messageDispatcher.get
|
||||||
}
|
}
|
||||||
|
|
@ -515,8 +521,6 @@ private[akka] sealed case class AspectInit(
|
||||||
def this(target: Class[_], actorRef: ActorRef, timeout: Long) = this(target, actorRef, None, timeout)
|
def this(target: Class[_], actorRef: ActorRef, timeout: Long) = this(target, actorRef, None, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: add @shutdown callback to ActiveObject in which we get the Aspect through 'Aspects.aspectOf(MyAspect.class, targetInstance)' and shuts down the Dispatcher actor
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AspectWerkz Aspect that is turning POJOs into Active Object.
|
* AspectWerkz Aspect that is turning POJOs into Active Object.
|
||||||
* Is deployed on a 'per-instance' basis.
|
* Is deployed on a 'per-instance' basis.
|
||||||
|
|
@ -671,7 +675,7 @@ object Dispatcher {
|
||||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||||
*/
|
*/
|
||||||
private[akka] class Dispatcher(transactionalRequired: Boolean,
|
private[akka] class Dispatcher(transactionalRequired: Boolean,
|
||||||
var restartCallbacks: Option[RestartCallbacks],
|
var restartCallbacks: Option[RestartCallbacks] = None,
|
||||||
var shutdownCallback: Option[ShutdownCallback] = None) extends Actor {
|
var shutdownCallback: Option[ShutdownCallback] = None) extends Actor {
|
||||||
import Dispatcher._
|
import Dispatcher._
|
||||||
|
|
||||||
|
|
@ -805,12 +809,15 @@ private[akka] class Dispatcher(transactionalRequired: Boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
override def shutdown = {
|
override def shutdown = {
|
||||||
AspectInitRegistry.unregister(target.get);
|
|
||||||
try {
|
try {
|
||||||
if (zhutdown.isDefined) {
|
if (zhutdown.isDefined) {
|
||||||
zhutdown.get.invoke(target.get, ZERO_ITEM_OBJECT_ARRAY: _*)
|
zhutdown.get.invoke(target.get, ZERO_ITEM_OBJECT_ARRAY: _*)
|
||||||
}
|
}
|
||||||
} catch { case e: InvocationTargetException => throw e.getCause }
|
} catch {
|
||||||
|
case e: InvocationTargetException => throw e.getCause
|
||||||
|
} finally {
|
||||||
|
AspectInitRegistry.unregister(target.get);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def initTransactionalState = {
|
override def initTransactionalState = {
|
||||||
|
|
|
||||||
|
|
@ -143,5 +143,13 @@ class ActiveObjectLifecycleSpec extends Spec with ShouldMatchers with BeforeAndA
|
||||||
case e: Exception => { /* test passed */ }
|
case e: Exception => { /* test passed */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it("should shutdown non-supervised, non-initialized active object on ActiveObject.stop") {
|
||||||
|
val obj = ActiveObject.newInstance(classOf[SamplePojoAnnotated])
|
||||||
|
ActiveObject.stop(obj)
|
||||||
|
assert(!obj._pre)
|
||||||
|
assert(!obj._post)
|
||||||
|
assert(obj._down)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +105,7 @@
|
||||||
</xsd:attribute>
|
</xsd:attribute>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
<!-- callbacks -->
|
<!-- restart callbacks -->
|
||||||
<xsd:complexType name="restart-callbacks-type">
|
<xsd:complexType name="restart-callbacks-type">
|
||||||
<xsd:attribute name="pre" type="xsd:string">
|
<xsd:attribute name="pre" type="xsd:string">
|
||||||
<xsd:annotation>
|
<xsd:annotation>
|
||||||
|
|
@ -123,11 +123,23 @@
|
||||||
</xsd:attribute>
|
</xsd:attribute>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
|
<!-- shutdown callbacks -->
|
||||||
|
<xsd:complexType name="shutdown-callback-type">
|
||||||
|
<xsd:attribute name="method" type="xsd:string">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation>
|
||||||
|
Shutdown callback method that is called during shut down.
|
||||||
|
</xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
</xsd:attribute>
|
||||||
|
</xsd:complexType>
|
||||||
|
|
||||||
<!-- active object -->
|
<!-- active object -->
|
||||||
<xsd:complexType name="active-object-type">
|
<xsd:complexType name="active-object-type">
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="remote" type="remote-type" minOccurs="0" maxOccurs="1"/>
|
<xsd:element name="remote" type="remote-type" minOccurs="0" maxOccurs="1"/>
|
||||||
<xsd:element name="restart-callbacks" type="restart-callbacks-type" minOccurs="0" maxOccurs="1"/>
|
<xsd:element name="restart-callbacks" type="restart-callbacks-type" minOccurs="0" maxOccurs="1"/>
|
||||||
|
<xsd:element name="shutdown-callback" type="shutdown-callback-type" minOccurs="0" maxOccurs="1"/>
|
||||||
<xsd:element name="dispatcher" type="dispatcher-type" minOccurs="0" maxOccurs="1"/>
|
<xsd:element name="dispatcher" type="dispatcher-type" minOccurs="0" maxOccurs="1"/>
|
||||||
<xsd:element ref="dispatcher" minOccurs="0" maxOccurs="1"/>
|
<xsd:element ref="dispatcher" minOccurs="0" maxOccurs="1"/>
|
||||||
<xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded"/>
|
<xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded"/>
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,24 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package se.scalablesolutions.akka.spring
|
package se.scalablesolutions.akka.spring
|
||||||
|
|
||||||
import java.beans.PropertyDescriptor
|
import java.beans.PropertyDescriptor
|
||||||
|
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
|
import reflect.BeanProperty
|
||||||
|
|
||||||
import org.springframework.beans.BeanWrapperImpl
|
import org.springframework.beans.BeanWrapperImpl
|
||||||
import org.springframework.beans.BeanWrapper
|
import org.springframework.beans.BeanWrapper
|
||||||
import org.springframework.beans.BeanUtils
|
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.BeanFactory
|
||||||
import org.springframework.beans.factory.config.AbstractFactoryBean
|
import org.springframework.beans.factory.config.AbstractFactoryBean
|
||||||
import se.scalablesolutions.akka.actor.ActiveObject
|
import org.springframework.util.ReflectionUtils
|
||||||
import reflect.BeanProperty
|
import org.springframework.util.StringUtils
|
||||||
import se.scalablesolutions.akka.config.ScalaConfig.RestartCallbacks
|
|
||||||
|
import se.scalablesolutions.akka.actor.{ActiveObjectConfiguration, ActiveObject}
|
||||||
|
import se.scalablesolutions.akka.config.ScalaConfig.{ShutdownCallback, RestartCallbacks}
|
||||||
import se.scalablesolutions.akka.dispatch.MessageDispatcher
|
import se.scalablesolutions.akka.dispatch.MessageDispatcher
|
||||||
import se.scalablesolutions.akka.util.Logging
|
import se.scalablesolutions.akka.util.Logging
|
||||||
|
|
||||||
|
|
@ -25,6 +27,7 @@ import se.scalablesolutions.akka.util.Logging
|
||||||
*
|
*
|
||||||
* @author michaelkober
|
* @author michaelkober
|
||||||
* @author <a href="johan.rask@jayway.com">Johan Rask</a>
|
* @author <a href="johan.rask@jayway.com">Johan Rask</a>
|
||||||
|
* @author Martin Krasser
|
||||||
*/
|
*/
|
||||||
class ActiveObjectFactoryBean extends AbstractFactoryBean[AnyRef] with Logging {
|
class ActiveObjectFactoryBean extends AbstractFactoryBean[AnyRef] with Logging {
|
||||||
import StringReflect._
|
import StringReflect._
|
||||||
|
|
@ -36,6 +39,7 @@ class ActiveObjectFactoryBean extends AbstractFactoryBean[AnyRef] with Logging {
|
||||||
@BeanProperty var transactional: Boolean = false
|
@BeanProperty var transactional: Boolean = false
|
||||||
@BeanProperty var pre: String = ""
|
@BeanProperty var pre: String = ""
|
||||||
@BeanProperty var post: String = ""
|
@BeanProperty var post: String = ""
|
||||||
|
@BeanProperty var shutdown: String = ""
|
||||||
@BeanProperty var host: String = ""
|
@BeanProperty var host: String = ""
|
||||||
@BeanProperty var port: Int = _
|
@BeanProperty var port: Int = _
|
||||||
@BeanProperty var lifecycle: String = ""
|
@BeanProperty var lifecycle: String = ""
|
||||||
|
|
@ -67,15 +71,20 @@ class ActiveObjectFactoryBean extends AbstractFactoryBean[AnyRef] with Logging {
|
||||||
if (hasInterface) argumentList += "i"
|
if (hasInterface) argumentList += "i"
|
||||||
if (hasDispatcher) argumentList += "d"
|
if (hasDispatcher) argumentList += "d"
|
||||||
|
|
||||||
setProperties(
|
setProperties(create(argumentList))
|
||||||
create(argumentList))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method manages <property/> element by injecting either
|
* Stop the active object if it is a singleton.
|
||||||
* values (<property value="value"/>) and bean references (<property ref="beanId"/>)
|
|
||||||
*/
|
*/
|
||||||
private def setProperties(ref:AnyRef) : AnyRef = {
|
override def destroy = {
|
||||||
|
if(scope.equals(VAL_SCOPE_SINGLETON)) {
|
||||||
|
ActiveObject.stop(getObject)
|
||||||
|
}
|
||||||
|
super.destroy
|
||||||
|
}
|
||||||
|
|
||||||
|
private def setProperties(ref:AnyRef) : AnyRef = {
|
||||||
log.debug("Processing properties and dependencies for target class %s",target)
|
log.debug("Processing properties and dependencies for target class %s",target)
|
||||||
val beanWrapper = new BeanWrapperImpl(ref);
|
val beanWrapper = new BeanWrapperImpl(ref);
|
||||||
for(entry <- property.entryList) {
|
for(entry <- property.entryList) {
|
||||||
|
|
@ -97,60 +106,45 @@ class ActiveObjectFactoryBean extends AbstractFactoryBean[AnyRef] with Logging {
|
||||||
ref
|
ref
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check if this works in 2.8 (type inferred to Nothing instead of AnyRef here)
|
|
||||||
//
|
|
||||||
// private[akka] def create(argList : String) : AnyRef = argList match {
|
|
||||||
// case "r" => ActiveObject.newRemoteInstance(target.toClass, timeout, transactional, host, port, callbacks)
|
|
||||||
// case "ri" => ActiveObject.newRemoteInstance(interface.toClass, target.toClass, timeout, transactional, host, port, callbacks)
|
|
||||||
// case "rd" => ActiveObject.newRemoteInstance(target.toClass, timeout, transactional, dispatcherInstance, host, port, callbacks)
|
|
||||||
// case "rid" => ActiveObject.newRemoteInstance(interface.toClass, target.toClass, timeout, transactional, dispatcherInstance, host, port, callbacks)
|
|
||||||
// case "i" => ActiveObject.newInstance(interface.toClass, target.toClass, timeout, transactional, callbacks)
|
|
||||||
// case "id" => ActiveObject.newInstance(interface.toClass, target.toClass, timeout, transactional, dispatcherInstance, callbacks)
|
|
||||||
// case "d" => ActiveObject.newInstance(target.toClass, timeout, transactional, dispatcherInstance, callbacks)
|
|
||||||
// case _ => ActiveObject.newInstance(target.toClass, timeout, transactional, callbacks)
|
|
||||||
// }
|
|
||||||
|
|
||||||
private[akka] def create(argList : String) : AnyRef = {
|
private[akka] def create(argList : String) : AnyRef = {
|
||||||
if (argList == "r") {
|
if (argList == "r") {
|
||||||
ActiveObject.newRemoteInstance(target.toClass, timeout, transactional, host, port, callbacks)
|
ActiveObject.newInstance(target.toClass, createConfig.makeRemote(host, port))
|
||||||
} else if (argList == "ri" ) {
|
} else if (argList == "ri" ) {
|
||||||
ActiveObject.newRemoteInstance(interface.toClass, aNewInstance(target.toClass), timeout, transactional, host, port, callbacks)
|
ActiveObject.newInstance(interface.toClass, aNewInstance(target.toClass), createConfig.makeRemote(host, port))
|
||||||
} else if (argList == "rd") {
|
} else if (argList == "rd") {
|
||||||
ActiveObject.newRemoteInstance(target.toClass, timeout, transactional, dispatcherInstance, host, port, callbacks)
|
ActiveObject.newInstance(target.toClass, createConfig.makeRemote(host, port).dispatcher(dispatcherInstance))
|
||||||
} else if (argList == "rid") {
|
} else if (argList == "rid") {
|
||||||
ActiveObject.newRemoteInstance(interface.toClass, aNewInstance(target.toClass), timeout, transactional, dispatcherInstance, host, port, callbacks)
|
ActiveObject.newInstance(interface.toClass, aNewInstance(target.toClass), createConfig.makeRemote(host, port).dispatcher(dispatcherInstance))
|
||||||
} else if (argList == "i") {
|
} else if (argList == "i") {
|
||||||
ActiveObject.newInstance(interface.toClass, aNewInstance(target.toClass), timeout, transactional, callbacks)
|
ActiveObject.newInstance(interface.toClass, aNewInstance(target.toClass), createConfig)
|
||||||
} else if (argList == "id") {
|
} else if (argList == "id") {
|
||||||
ActiveObject.newInstance(interface.toClass, aNewInstance(target.toClass), timeout, transactional, dispatcherInstance, callbacks)
|
ActiveObject.newInstance(interface.toClass, aNewInstance(target.toClass), createConfig.dispatcher(dispatcherInstance))
|
||||||
} else if (argList == "d") {
|
} else if (argList == "d") {
|
||||||
ActiveObject.newInstance(target.toClass, timeout, transactional, dispatcherInstance, callbacks)
|
ActiveObject.newInstance(target.toClass, createConfig.dispatcher(dispatcherInstance))
|
||||||
} else {
|
} else {
|
||||||
ActiveObject.newInstance(target.toClass, timeout, transactional, callbacks)
|
ActiveObject.newInstance(target.toClass, createConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def aNewInstance[T <: AnyRef](clazz: Class[T]) : T = {
|
private[akka] def createConfig: ActiveObjectConfiguration = {
|
||||||
clazz.newInstance().asInstanceOf[T]
|
val config = new ActiveObjectConfiguration().timeout(timeout)
|
||||||
}
|
if (hasRestartCallbacks) config.restartCallbacks(pre, post)
|
||||||
|
if (hasShutdownCallback) config.shutdownCallback(shutdown)
|
||||||
|
if (transactional) config.makeTransactionRequired
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
private[akka] def aNewInstance[T <: AnyRef](clazz: Class[T]) : T = {
|
||||||
* create Option[RestartCallback]
|
clazz.newInstance().asInstanceOf[T]
|
||||||
*/
|
|
||||||
private def callbacks: Option[RestartCallbacks] = {
|
|
||||||
if (hasCallbacks) {
|
|
||||||
val callbacks = new RestartCallbacks(pre, post)
|
|
||||||
Some(callbacks)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private[akka] def isRemote = (host != null) && (!host.isEmpty)
|
private[akka] def isRemote = (host != null) && (!host.isEmpty)
|
||||||
|
|
||||||
private[akka] def hasInterface = (interface != null) && (!interface.isEmpty)
|
private[akka] def hasInterface = (interface != null) && (!interface.isEmpty)
|
||||||
|
|
||||||
private[akka] def hasCallbacks = ((pre != null) && !pre.isEmpty) || ((post != null) && !post.isEmpty)
|
private[akka] def hasRestartCallbacks = ((pre != null) && !pre.isEmpty) || ((post != null) && !post.isEmpty)
|
||||||
|
|
||||||
|
private[akka] def hasShutdownCallback = ((shutdown != null) && !shutdown.isEmpty)
|
||||||
|
|
||||||
private[akka] def hasDispatcher = (dispatcher != null) && (dispatcher.dispatcherType != null) && (!dispatcher.dispatcherType.isEmpty)
|
private[akka] def hasDispatcher = (dispatcher != null) && (dispatcher.dispatcherType != null) && (!dispatcher.dispatcherType.isEmpty)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import se.scalablesolutions.akka.actor.IllegalActorStateException
|
||||||
* Parser trait for custom namespace configuration for active-object.
|
* Parser trait for custom namespace configuration for active-object.
|
||||||
* @author michaelkober
|
* @author michaelkober
|
||||||
* @author <a href="johan.rask@jayway.com">Johan Rask</a>
|
* @author <a href="johan.rask@jayway.com">Johan Rask</a>
|
||||||
|
* @author Martin Krasser
|
||||||
*/
|
*/
|
||||||
trait ActiveObjectParser extends BeanParser with DispatcherParser {
|
trait ActiveObjectParser extends BeanParser with DispatcherParser {
|
||||||
import AkkaSpringConfigurationTags._
|
import AkkaSpringConfigurationTags._
|
||||||
|
|
@ -25,7 +26,8 @@ trait ActiveObjectParser extends BeanParser with DispatcherParser {
|
||||||
def parseActiveObject(element: Element): ActiveObjectProperties = {
|
def parseActiveObject(element: Element): ActiveObjectProperties = {
|
||||||
val objectProperties = new ActiveObjectProperties()
|
val objectProperties = new ActiveObjectProperties()
|
||||||
val remoteElement = DomUtils.getChildElementByTagName(element, REMOTE_TAG);
|
val remoteElement = DomUtils.getChildElementByTagName(element, REMOTE_TAG);
|
||||||
val callbacksElement = DomUtils.getChildElementByTagName(element, RESTART_CALLBACKS_TAG);
|
val restartCallbacksElement = DomUtils.getChildElementByTagName(element, RESTART_CALLBACKS_TAG);
|
||||||
|
val shutdownCallbackElement = DomUtils.getChildElementByTagName(element, SHUTDOWN_CALLBACK_TAG);
|
||||||
val dispatcherElement = DomUtils.getChildElementByTagName(element, DISPATCHER_TAG)
|
val dispatcherElement = DomUtils.getChildElementByTagName(element, DISPATCHER_TAG)
|
||||||
val propertyEntries = DomUtils.getChildElementsByTagName(element,PROPERTYENTRY_TAG)
|
val propertyEntries = DomUtils.getChildElementsByTagName(element,PROPERTYENTRY_TAG)
|
||||||
|
|
||||||
|
|
@ -34,14 +36,18 @@ trait ActiveObjectParser extends BeanParser with DispatcherParser {
|
||||||
objectProperties.port = mandatory(remoteElement, PORT).toInt
|
objectProperties.port = mandatory(remoteElement, PORT).toInt
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callbacksElement != null) {
|
if (restartCallbacksElement != null) {
|
||||||
objectProperties.preRestart = callbacksElement.getAttribute(PRE_RESTART)
|
objectProperties.preRestart = restartCallbacksElement.getAttribute(PRE_RESTART)
|
||||||
objectProperties.postRestart = callbacksElement.getAttribute(POST_RESTART)
|
objectProperties.postRestart = restartCallbacksElement.getAttribute(POST_RESTART)
|
||||||
if ((objectProperties.preRestart.isEmpty) && (objectProperties.preRestart.isEmpty)) {
|
if ((objectProperties.preRestart.isEmpty) && (objectProperties.preRestart.isEmpty)) {
|
||||||
throw new IllegalActorStateException("At least one of pre or post must be defined.")
|
throw new IllegalActorStateException("At least one of pre or post must be defined.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shutdownCallbackElement != null) {
|
||||||
|
objectProperties.shutdown = shutdownCallbackElement.getAttribute("method")
|
||||||
|
}
|
||||||
|
|
||||||
if (dispatcherElement != null) {
|
if (dispatcherElement != null) {
|
||||||
val dispatcherProperties = parseDispatcher(dispatcherElement)
|
val dispatcherProperties = parseDispatcher(dispatcherElement)
|
||||||
objectProperties.dispatcher = dispatcherProperties
|
objectProperties.dispatcher = dispatcherProperties
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import AkkaSpringConfigurationTags._
|
||||||
/**
|
/**
|
||||||
* Data container for active object configuration data.
|
* Data container for active object configuration data.
|
||||||
* @author michaelkober
|
* @author michaelkober
|
||||||
|
* @author Martin Krasser
|
||||||
*/
|
*/
|
||||||
class ActiveObjectProperties {
|
class ActiveObjectProperties {
|
||||||
var target: String = ""
|
var target: String = ""
|
||||||
|
|
@ -18,10 +19,11 @@ class ActiveObjectProperties {
|
||||||
var transactional: Boolean = false
|
var transactional: Boolean = false
|
||||||
var preRestart: String = ""
|
var preRestart: String = ""
|
||||||
var postRestart: String = ""
|
var postRestart: String = ""
|
||||||
|
var shutdown: String = ""
|
||||||
var host: String = ""
|
var host: String = ""
|
||||||
var port: Int = _
|
var port: Int = _
|
||||||
var lifecycle: String = ""
|
var lifecycle: String = ""
|
||||||
var scope:String = ""
|
var scope:String = VAL_SCOPE_SINGLETON
|
||||||
var dispatcher: DispatcherProperties = _
|
var dispatcher: DispatcherProperties = _
|
||||||
var propertyEntries = new PropertyEntries()
|
var propertyEntries = new PropertyEntries()
|
||||||
|
|
||||||
|
|
@ -35,6 +37,7 @@ class ActiveObjectProperties {
|
||||||
builder.addPropertyValue(PORT, port)
|
builder.addPropertyValue(PORT, port)
|
||||||
builder.addPropertyValue(PRE_RESTART, preRestart)
|
builder.addPropertyValue(PRE_RESTART, preRestart)
|
||||||
builder.addPropertyValue(POST_RESTART, postRestart)
|
builder.addPropertyValue(POST_RESTART, postRestart)
|
||||||
|
builder.addPropertyValue(SHUTDOWN, shutdown)
|
||||||
builder.addPropertyValue(TIMEOUT, timeout)
|
builder.addPropertyValue(TIMEOUT, timeout)
|
||||||
builder.addPropertyValue(TARGET, target)
|
builder.addPropertyValue(TARGET, target)
|
||||||
builder.addPropertyValue(INTERFACE, interface)
|
builder.addPropertyValue(INTERFACE, interface)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ package se.scalablesolutions.akka.spring
|
||||||
/**
|
/**
|
||||||
* XML configuration tags.
|
* XML configuration tags.
|
||||||
* @author michaelkober
|
* @author michaelkober
|
||||||
|
* @author Martin Krasser
|
||||||
*/
|
*/
|
||||||
object AkkaSpringConfigurationTags {
|
object AkkaSpringConfigurationTags {
|
||||||
|
|
||||||
|
|
@ -20,6 +21,7 @@ object AkkaSpringConfigurationTags {
|
||||||
|
|
||||||
// active-object sub tags
|
// active-object sub tags
|
||||||
val RESTART_CALLBACKS_TAG = "restart-callbacks"
|
val RESTART_CALLBACKS_TAG = "restart-callbacks"
|
||||||
|
val SHUTDOWN_CALLBACK_TAG = "shutdown-callback"
|
||||||
val REMOTE_TAG = "remote"
|
val REMOTE_TAG = "remote"
|
||||||
|
|
||||||
// superivision sub tags
|
// superivision sub tags
|
||||||
|
|
@ -45,6 +47,7 @@ object AkkaSpringConfigurationTags {
|
||||||
val PORT = "port"
|
val PORT = "port"
|
||||||
val PRE_RESTART = "pre"
|
val PRE_RESTART = "pre"
|
||||||
val POST_RESTART = "post"
|
val POST_RESTART = "post"
|
||||||
|
val SHUTDOWN = "shutdown"
|
||||||
val LIFECYCLE = "lifecycle"
|
val LIFECYCLE = "lifecycle"
|
||||||
val SCOPE = "scope"
|
val SCOPE = "scope"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,22 @@
|
||||||
package se.scalablesolutions.akka.spring;
|
package se.scalablesolutions.akka.spring;
|
||||||
|
|
||||||
|
import se.scalablesolutions.akka.actor.annotation.shutdown;
|
||||||
|
|
||||||
public class SampleBean {
|
public class SampleBean {
|
||||||
|
|
||||||
|
public boolean down;
|
||||||
|
|
||||||
|
public SampleBean() {
|
||||||
|
down = false;
|
||||||
|
}
|
||||||
|
|
||||||
public String foo(String s) {
|
public String foo(String s) {
|
||||||
return "hello " + s;
|
return "hello " + s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@shutdown
|
||||||
|
public void shutdown() {
|
||||||
|
down = true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,19 @@
|
||||||
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||||
http://www.akkasource.org/schema/akka
|
http://www.akkasource.org/schema/akka
|
||||||
http://scalablesolutions.se/akka/akka-0.10.xsd">
|
http://scalablesolutions.se/akka/akka-0.10.xsd">
|
||||||
|
|
||||||
<akka:active-object id="bean"
|
<akka:active-object id="bean-singleton" target="se.scalablesolutions.akka.spring.SampleBean" timeout="1000"/>
|
||||||
target="org.springframework.core.io.ResourceEditor"
|
<akka:active-object id="bean-prototype" target="se.scalablesolutions.akka.spring.SampleBean" timeout="1000" scope="prototype"/>
|
||||||
transactional="true"
|
|
||||||
timeout="1000"
|
<akka:active-object id="bean"
|
||||||
scope="prototype">
|
target="org.springframework.core.io.ResourceEditor"
|
||||||
<property name="source" ref="string"/>
|
transactional="true"
|
||||||
</akka:active-object>
|
timeout="1000"
|
||||||
|
scope="prototype">
|
||||||
<bean id="string" class="java.lang.String">
|
<property name="source" ref="string"/>
|
||||||
<constructor-arg value="someString"/>
|
</akka:active-object>
|
||||||
</bean>
|
|
||||||
</beans>
|
<bean id="string" class="java.lang.String">
|
||||||
|
<constructor-arg value="someString"/>
|
||||||
|
</bean>
|
||||||
|
</beans>
|
||||||
|
|
@ -54,8 +54,8 @@ class ActiveObjectBeanDefinitionParserTest extends Spec with ShouldMatchers {
|
||||||
</akka:active-object>
|
</akka:active-object>
|
||||||
val props = parser.parseActiveObject(dom(xml).getDocumentElement);
|
val props = parser.parseActiveObject(dom(xml).getDocumentElement);
|
||||||
assert(props != null)
|
assert(props != null)
|
||||||
assert(props.dispatcher.dispatcherType == "thread-based")
|
assert(props.dispatcher.dispatcherType === "thread-based")
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should parse remote ActiveObjects configuration") {
|
it("should parse remote ActiveObjects configuration") {
|
||||||
val xml = <akka:active-object id="remote active-object" target="se.scalablesolutions.akka.spring.foo.MyPojo"
|
val xml = <akka:active-object id="remote active-object" target="se.scalablesolutions.akka.spring.foo.MyPojo"
|
||||||
|
|
@ -64,8 +64,8 @@ class ActiveObjectBeanDefinitionParserTest extends Spec with ShouldMatchers {
|
||||||
</akka:active-object>
|
</akka:active-object>
|
||||||
val props = parser.parseActiveObject(dom(xml).getDocumentElement);
|
val props = parser.parseActiveObject(dom(xml).getDocumentElement);
|
||||||
assert(props != null)
|
assert(props != null)
|
||||||
assert(props.host == "com.some.host")
|
assert(props.host === "com.some.host")
|
||||||
assert(props.port == 9999)
|
assert(props.port === 9999)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,5 +70,21 @@ class ActiveObjectFactoryBeanTest extends Spec with ShouldMatchers {
|
||||||
val target:ResourceEditor = ctx.getBean("bean").asInstanceOf[ResourceEditor]
|
val target:ResourceEditor = ctx.getBean("bean").asInstanceOf[ResourceEditor]
|
||||||
assert(target.getSource === "someString")
|
assert(target.getSource === "someString")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it("should stop the created active object when scope is singleton and the context is closed") {
|
||||||
|
var ctx = new ClassPathXmlApplicationContext("appContext.xml");
|
||||||
|
val target = ctx.getBean("bean-singleton").asInstanceOf[SampleBean]
|
||||||
|
assert(!target.down)
|
||||||
|
ctx.close
|
||||||
|
assert(target.down)
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should not stop the created active object when scope is prototype and the context is closed") {
|
||||||
|
var ctx = new ClassPathXmlApplicationContext("appContext.xml");
|
||||||
|
val target = ctx.getBean("bean-prototype").asInstanceOf[SampleBean]
|
||||||
|
assert(!target.down)
|
||||||
|
ctx.close
|
||||||
|
assert(!target.down)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,11 +49,21 @@ class SupervisionBeanDefinitionParserTest extends Spec with ShouldMatchers {
|
||||||
parser.parseSupervisor(createSupervisorElement, builder);
|
parser.parseSupervisor(createSupervisorElement, builder);
|
||||||
val supervised = builder.getBeanDefinition.getPropertyValues.getPropertyValue("supervised").getValue.asInstanceOf[List[ActiveObjectProperties]]
|
val supervised = builder.getBeanDefinition.getPropertyValues.getPropertyValue("supervised").getValue.asInstanceOf[List[ActiveObjectProperties]]
|
||||||
assert(supervised != null)
|
assert(supervised != null)
|
||||||
expect(3) { supervised.length }
|
expect(4) { supervised.length }
|
||||||
val iterator = supervised.iterator
|
val iterator = supervised.iterator
|
||||||
expect("foo.bar.Foo") { iterator.next.target }
|
val prop1 = iterator.next
|
||||||
expect("foo.bar.Bar") { iterator.next.target }
|
val prop2 = iterator.next
|
||||||
expect("foo.bar.MyPojo") { iterator.next.target }
|
val prop3 = iterator.next
|
||||||
|
val prop4 = iterator.next
|
||||||
|
expect("foo.bar.Foo") { prop1.target }
|
||||||
|
expect("foo.bar.Bar") { prop2.target }
|
||||||
|
expect("foo.bar.MyPojo") { prop3.target }
|
||||||
|
expect("foo.bar.MyPojo") { prop4.target }
|
||||||
|
expect("preRestart") { prop3.preRestart }
|
||||||
|
expect("postRestart") { prop3.postRestart }
|
||||||
|
expect("shutdown") { prop4.shutdown }
|
||||||
|
expect("permanent") { prop1.lifecycle }
|
||||||
|
expect("temporary") { prop4.lifecycle }
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should throw IllegalArgumentException on missing mandatory attributes") {
|
it("should throw IllegalArgumentException on missing mandatory attributes") {
|
||||||
|
|
@ -87,6 +97,9 @@ class SupervisionBeanDefinitionParserTest extends Spec with ShouldMatchers {
|
||||||
<akka:active-object target="foo.bar.MyPojo" lifecycle="temporary" timeout="1000">
|
<akka:active-object target="foo.bar.MyPojo" lifecycle="temporary" timeout="1000">
|
||||||
<akka:restart-callbacks pre="preRestart" post="postRestart"/>
|
<akka:restart-callbacks pre="preRestart" post="postRestart"/>
|
||||||
</akka:active-object>
|
</akka:active-object>
|
||||||
|
<akka:active-object target="foo.bar.MyPojo" lifecycle="temporary" timeout="1000">
|
||||||
|
<akka:shutdown-callback method="shutdown"/>
|
||||||
|
</akka:active-object>
|
||||||
</akka:active-objects>
|
</akka:active-objects>
|
||||||
</akka:supervision>
|
</akka:supervision>
|
||||||
dom(xml).getDocumentElement
|
dom(xml).getDocumentElement
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue