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/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..b1093a9a08 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) 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 =