diff --git a/akka-spring/pom.xml b/akka-spring/pom.xml index 5a2cd11eac..126be1c991 100644 --- a/akka-spring/pom.xml +++ b/akka-spring/pom.xml @@ -1,77 +1,76 @@ - - 4.0.0 + 4.0.0 + akka-spring + Akka Spring Integration + jar - akka-spring - Akka Spring Module - - jar - - - akka - se.scalablesolutions.akka - 0.6 - ../pom.xml - + + akka + se.scalablesolutions.akka + 0.7-SNAPSHOT + + + - junit - junit - 3.8.1 - test + akka-core + ${project.groupId} + ${project.version} + + akka-util-java + ${project.groupId} + ${project.version} + + + akka-util + ${project.groupId} + ${project.version} + + org.springframework spring - 2.5.5 + 2.5.6 - - akka-core - se.scalablesolutions.akka - 0.6 + org.scala-lang scala-library - 2.7.5 + ${scala.version} + + + org.scala-lang + scala-compiler + ${scala.version} + + + + org.scalatest + scalatest + 1.0 + test - org.codehaus.aspectwerkz - aspectwerkz-nodeps-jdk5 - 2.1 - - - org.codehaus.aspectwerkz - aspectwerkz-jdk5 - 2.1 + junit + junit + 4.5 + test - - - src/main/java - src/test/java - - - src/test/resources - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.5 - 1.5 - - **/* - - - - - + diff --git a/akka-spring/src/main/java/se/scalablesolutions/akka/spring/AkkaSpringInterceptor.java b/akka-spring/src/main/java/se/scalablesolutions/akka/spring/AkkaSpringInterceptor.java deleted file mode 100644 index 456dd15572..0000000000 --- a/akka-spring/src/main/java/se/scalablesolutions/akka/spring/AkkaSpringInterceptor.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (C) 2009-2010 Scalable Solutions AB - */ - -package se.scalablesolutions.akka.spring; - -import se.scalablesolutions.akka.actor.ActiveObjectAspect; -import se.scalablesolutions.akka.actor.AspectInit; -import se.scalablesolutions.akka.actor.AspectInitRegistry; -import se.scalablesolutions.akka.actor.Dispatcher; - -import org.springframework.beans.factory.config.BeanPostProcessor; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; - -import java.lang.reflect.Method; - -public class AkkaSpringInterceptor extends ActiveObjectAspect implements MethodInterceptor, BeanPostProcessor { - - static final String TRANSACTION_MANAGER_CLASS_NAME = "org.springframework.transaction.support.TransactionSynchronizationManager"; - static final String IS_TRANSACTION_ALIVE_METHOD_NAME = "isActualTransactionActive"; - - static private Method IS_TRANSACTION_ALIVE_METHOD = null; - - static { - try { - ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - Class clazz = null; - if (contextClassLoader != null) { - clazz = contextClassLoader.loadClass(TRANSACTION_MANAGER_CLASS_NAME); - } else { - ClassLoader springClassLoader = AkkaSpringInterceptor.class.getClassLoader(); - clazz = springClassLoader.loadClass(TRANSACTION_MANAGER_CLASS_NAME); - } - if (clazz != null) IS_TRANSACTION_ALIVE_METHOD = clazz.getDeclaredMethod(IS_TRANSACTION_ALIVE_METHOD_NAME, null); - } catch (Exception e) { - } - } - - // FIXME make configurable - static final int TIME_OUT = 1000; - - @Override - public Object invoke(MethodInvocation methodInvocation) throws Throwable { - Dispatcher dispatcher = new Dispatcher(isTransactional()); - dispatcher.start(); - try { - AspectInitRegistry.register(methodInvocation.getThis(), new AspectInit( - methodInvocation.getThis().getClass(), - dispatcher, - TIME_OUT)); - return this.invoke(AkkaSpringJoinPointWrapper.createSpringAkkaAspectWerkzWrapper(methodInvocation)); - } finally { - dispatcher.stop(); - } - } - - @Override - public Object postProcessAfterInitialization(Object bean, String arg) throws BeansException { - return bean; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String arg) throws BeansException { - return bean; - } - - /** - * Checks if intercepted Spring bean is in a transaction. - */ - private boolean isTransactional() { - try { - return (Boolean) IS_TRANSACTION_ALIVE_METHOD.invoke(null, null); - } catch (Exception e) { - throw new RuntimeException("Could not check if the Spring bean is executing within a transaction", e); - } - } -} \ No newline at end of file diff --git a/akka-spring/src/main/java/se/scalablesolutions/akka/spring/AkkaSpringJoinPointWrapper.java b/akka-spring/src/main/java/se/scalablesolutions/akka/spring/AkkaSpringJoinPointWrapper.java deleted file mode 100644 index 569bb4511d..0000000000 --- a/akka-spring/src/main/java/se/scalablesolutions/akka/spring/AkkaSpringJoinPointWrapper.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (C) 2009-2010 Scalable Solutions AB - */ - -package se.scalablesolutions.akka.spring; - -import org.aopalliance.intercept.MethodInvocation; - -import org.codehaus.aspectwerkz.joinpoint.*; -import org.codehaus.aspectwerkz.joinpoint.management.JoinPointType; - -public class AkkaSpringJoinPointWrapper implements JoinPoint { - - private MethodInvocation methodInvocation = null; - - public static AkkaSpringJoinPointWrapper createSpringAkkaAspectWerkzWrapper(MethodInvocation methodInvocation) { - AkkaSpringJoinPointWrapper joinPointWrapper = new AkkaSpringJoinPointWrapper(); - joinPointWrapper.setMethodInvocation(methodInvocation); - return joinPointWrapper; - } - - public MethodInvocation getMethodInvocation() { - return methodInvocation; - } - - public void setMethodInvocation(MethodInvocation methodInvocation) { - this.methodInvocation = methodInvocation; - } - - public Object proceed() throws Throwable { - return methodInvocation.proceed(); - } - - public Rtti getRtti() { - return new AkkaSpringRttiWrapper(methodInvocation); - } - - public Object getTarget() { - return methodInvocation.getThis(); - } - - public Object getThis() { - return methodInvocation.getThis(); - } - - public Object getCallee() { - throw new UnsupportedOperationException(); - } - - public Object getCaller() { - throw new UnsupportedOperationException(); - } - - public void addMetaData(Object arg0, Object arg1) { - throw new UnsupportedOperationException(); - } - - public Class getCalleeClass() { - throw new UnsupportedOperationException(); - } - - public Class getCallerClass() { - throw new UnsupportedOperationException(); - } - - public EnclosingStaticJoinPoint getEnclosingStaticJoinPoint() { - throw new UnsupportedOperationException(); - } - - public Object getMetaData(Object arg0) { - throw new UnsupportedOperationException(); - } - - public Signature getSignature() { - throw new UnsupportedOperationException(); - } - - public Class getTargetClass() { - throw new UnsupportedOperationException(); - } - - public JoinPointType getType() { - throw new UnsupportedOperationException(); - } - - public StaticJoinPoint copy() { - throw new UnsupportedOperationException(); - } -} diff --git a/akka-spring/src/main/java/se/scalablesolutions/akka/spring/AkkaSpringRttiWrapper.java b/akka-spring/src/main/java/se/scalablesolutions/akka/spring/AkkaSpringRttiWrapper.java deleted file mode 100644 index 35f07065b6..0000000000 --- a/akka-spring/src/main/java/se/scalablesolutions/akka/spring/AkkaSpringRttiWrapper.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (C) 2009-2010 Scalable Solutions AB - */ - -package se.scalablesolutions.akka.spring; - -import org.aopalliance.intercept.MethodInvocation; - -import org.codehaus.aspectwerkz.joinpoint.MethodRtti; -import org.codehaus.aspectwerkz.joinpoint.Rtti; - -import java.lang.reflect.Method; - -public class AkkaSpringRttiWrapper implements MethodRtti { - - private MethodInvocation methodInvocation = null; - - public AkkaSpringRttiWrapper(MethodInvocation methodInvocation) { - this.methodInvocation = methodInvocation; - } - - public Method getMethod() { - return methodInvocation.getMethod(); - } - - public Class getReturnType() { - throw new UnsupportedOperationException(); - } - - public Object getReturnValue() { - throw new UnsupportedOperationException(); - } - - public Class[] getExceptionTypes() { - throw new UnsupportedOperationException(); - } - - public Class[] getParameterTypes() { - throw new UnsupportedOperationException(); - } - - public Object[] getParameterValues() { - throw new UnsupportedOperationException(); - } - - public void setParameterValues(Object[] arg0) { - throw new UnsupportedOperationException(); - } - - public Rtti cloneFor(Object arg0, Object arg1) { - throw new UnsupportedOperationException(); - } - - public Class getDeclaringType() { - throw new UnsupportedOperationException(); - } - - public int getModifiers() { - throw new UnsupportedOperationException(); - } - - public String getName() { - throw new UnsupportedOperationException(); - } - - public Object getTarget() { - throw new UnsupportedOperationException(); - } - - public Object getThis() { - throw new UnsupportedOperationException(); - } -} diff --git a/akka-spring/src/main/resources/META-INF/spring.handlers b/akka-spring/src/main/resources/META-INF/spring.handlers new file mode 100644 index 0000000000..c8d9dc55ae --- /dev/null +++ b/akka-spring/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.akkasource.org/schema/akka=se.scalablesolutions.akka.spring.AkkaNamespaceHandler \ No newline at end of file diff --git a/akka-spring/src/main/resources/META-INF/spring.schemas b/akka-spring/src/main/resources/META-INF/spring.schemas new file mode 100644 index 0000000000..d04d65566a --- /dev/null +++ b/akka-spring/src/main/resources/META-INF/spring.schemas @@ -0,0 +1 @@ +http\://www.akkasource.org/schema/akka=se/scalablesolutions/akka/spring/akka.xsd 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 new file mode 100644 index 0000000000..134e53e82f --- /dev/null +++ b/akka-spring/src/main/resources/se/scalablesolutions/akka/spring/akka.xsd @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failover scheme, AllForOne or OneForOne + + + + + + + Maximal number of retries. + + + + + + + Timerange for restart. + + + + + + + + + + + + + + + + + + + + + diff --git a/akka-spring/src/main/scala/ActiveObjectBeanDefinitionParser.scala b/akka-spring/src/main/scala/ActiveObjectBeanDefinitionParser.scala new file mode 100644 index 0000000000..e4b976188d --- /dev/null +++ b/akka-spring/src/main/scala/ActiveObjectBeanDefinitionParser.scala @@ -0,0 +1,88 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ +package se.scalablesolutions.akka.spring + +import org.springframework.util.xml.DomUtils +import se.scalablesolutions.akka.util.Logging +import org.w3c.dom.Element + +/** + * Parser for custom namespace configuration for active-object. + * @author michaelkober + */ +trait ActiveObjectBeanDefinitionParser extends Logging { + import AkkaSpringConfigurationTags._ + + /** + * Parses the given element and returns a ActiveObjectProperties. + * @param element dom element to parse + * @return configuration for the active object + */ + def parseActiveObject(element: Element): ActiveObjectProperties = { + val objectProperties = new ActiveObjectProperties() + val remoteElement = DomUtils.getChildElementByTagName(element, REMOTE_TAG); + val callbacksElement = DomUtils.getChildElementByTagName(element, RESTART_CALLBACKS_TAG); + + if (remoteElement != null) { + objectProperties.host = mandatory(remoteElement, HOST) + objectProperties.port = mandatory(remoteElement, PORT).toInt + } + + if (callbacksElement != null) { + objectProperties.preRestart = callbacksElement.getAttribute(PRE_RESTART) + objectProperties.postRestart = callbacksElement.getAttribute(POST_RESTART) + if ((objectProperties.preRestart.isEmpty) && (objectProperties.preRestart.isEmpty)) { + throw new IllegalStateException("At least one of pre or post must be defined.") + } + } + + try { + objectProperties.timeout = mandatory(element, TIMEOUT).toLong + } catch { + case nfe: NumberFormatException => + log.error(nfe, "could not parse timeout %s", element.getAttribute(TIMEOUT)) + throw nfe + } + + objectProperties.target = mandatory(element, TARGET) + objectProperties.transactional = if (element.getAttribute(TRANSACTIONAL).isEmpty) false else element.getAttribute(TRANSACTIONAL).toBoolean + + if (!element.getAttribute(INTERFACE).isEmpty) { + objectProperties.interface = element.getAttribute(INTERFACE) + } + + if (!element.getAttribute(LIFECYCLE).isEmpty) { + objectProperties.lifecyclye = element.getAttribute(LIFECYCLE) + } + objectProperties + } + + /** + * Get a mandatory element attribute. + * @param element the element with the mandatory attribute + * @param attribute name of the mandatory attribute + */ + def mandatory(element: Element, attribute: String): String = { + if ((element.getAttribute(attribute) == null) || (element.getAttribute(attribute).isEmpty)) { + throw new IllegalArgumentException("Mandatory attribute missing: " + attribute) + } else { + element.getAttribute(attribute) + } + } + + /** + * Get a mandatory child element. + * @param element the parent element + * @param childName name of the mandatory child element + */ + def mandatoryElement(element: Element, childName: String): Element = { + val childElement = DomUtils.getChildElementByTagName(element, childName); + if (childElement == null) { + throw new IllegalArgumentException("Mandatory element missing: ''") + } else { + childElement + } + } + +} \ No newline at end of file diff --git a/akka-spring/src/main/scala/ActiveObjectFactoryBean.scala b/akka-spring/src/main/scala/ActiveObjectFactoryBean.scala new file mode 100644 index 0000000000..3d4a4508de --- /dev/null +++ b/akka-spring/src/main/scala/ActiveObjectFactoryBean.scala @@ -0,0 +1,81 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package se.scalablesolutions.akka.spring + +import org.springframework.beans.factory.config.AbstractFactoryBean +import se.scalablesolutions.akka.actor.ActiveObject +import reflect.BeanProperty +import se.scalablesolutions.akka.config.ScalaConfig.RestartCallbacks + + + + +/** + * Factory bean for active objects. + * @author michaelkober + */ +class ActiveObjectFactoryBean extends AbstractFactoryBean { + import StringReflect._ + + @BeanProperty var target: String = "" + @BeanProperty var timeout: Long = _ + @BeanProperty var interface: String = "" + @BeanProperty var transactional: Boolean = false + @BeanProperty var pre: String = "" + @BeanProperty var post: String = "" + @BeanProperty var host: String = "" + @BeanProperty var port: Int = _ + @BeanProperty var lifecycle: String = "" + + + /* + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + def getObjectType = target.toClass + + + /* + * @see org.springframework.beans.factory.config.AbstractFactoryBean#createInstance() + */ + def createInstance: AnyRef = { + ActiveObject.newInstance(target.toClass, timeout, transactional, restartCallbacks) + if (isRemote) { + newRemoteInstance(target, timeout, interface, transactional, restartCallbacks, host, port) + } else { + newInstance(target, timeout, interface, transactional, restartCallbacks); + } + } + + private[akka] def isRemote = (host != null) && (!host.isEmpty) + + /** + * create Option[RestartCallback] + */ + private def restartCallbacks: Option[RestartCallbacks] = { + if (((pre == null) || pre.isEmpty) && ((post == null) || post.isEmpty)) { + None + } else { + val callbacks = new RestartCallbacks(pre, post) + Some(callbacks) + } + } + + private def newInstance(target: String, timeout: Long, interface: String, transactional: Boolean, callbacks: Option[RestartCallbacks]): AnyRef = { + if ((interface == null) || interface.isEmpty) { + ActiveObject.newInstance(target.toClass, timeout, transactional, callbacks) + } else { + ActiveObject.newInstance(interface.toClass, target.toClass, timeout, transactional, callbacks) + } + } + + private def newRemoteInstance(target: String, timeout: Long, interface: String, transactional: Boolean, callbacks: Option[RestartCallbacks], host: String, port: Int): AnyRef = { + if ((interface == null) || interface.isEmpty) { + ActiveObject.newRemoteInstance(target.toClass, timeout, transactional, host, port, callbacks) + } else { + ActiveObject.newRemoteInstance(interface.toClass, target.toClass, timeout, transactional, host, port, callbacks) + } + } + +} \ No newline at end of file diff --git a/akka-spring/src/main/scala/ActiveObjectProperties.scala b/akka-spring/src/main/scala/ActiveObjectProperties.scala new file mode 100644 index 0000000000..bd1f838d9b --- /dev/null +++ b/akka-spring/src/main/scala/ActiveObjectProperties.scala @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package se.scalablesolutions.akka.spring + +import org.springframework.beans.factory.support.BeanDefinitionBuilder +import AkkaSpringConfigurationTags._ + +/** + * Data container for active object configuration data. + * @author michaelkober + */ +class ActiveObjectProperties { + var target: String = "" + var timeout: Long = _ + var interface: String = "" + var transactional: Boolean = false + var preRestart: String = "" + var postRestart: String = "" + var host: String = "" + var port: Int = _ + var lifecyclye: String = "" + + /** + * Sets the properties to the given builder. + * @param builder bean definition builder + */ + def setAsProperties(builder: BeanDefinitionBuilder) { + builder.addPropertyValue(HOST, host) + builder.addPropertyValue(PORT, port) + builder.addPropertyValue(PRE_RESTART, preRestart) + builder.addPropertyValue(POST_RESTART, postRestart) + builder.addPropertyValue(TIMEOUT, timeout) + builder.addPropertyValue(TARGET, target) + builder.addPropertyValue(INTERFACE, interface) + builder.addPropertyValue(TRANSACTIONAL, transactional) + builder.addPropertyValue(LIFECYCLE, lifecyclye) + } + +} \ No newline at end of file diff --git a/akka-spring/src/main/scala/AkkaNamespaceHandler.scala b/akka-spring/src/main/scala/AkkaNamespaceHandler.scala new file mode 100644 index 0000000000..ebf70a7ae3 --- /dev/null +++ b/akka-spring/src/main/scala/AkkaNamespaceHandler.scala @@ -0,0 +1,18 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ +package se.scalablesolutions.akka.spring + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport +import AkkaSpringConfigurationTags._ + +/** + * Custom spring namespace handler for Akka. + * @author michaelkober + */ +class AkkaNamespaceHandler extends NamespaceHandlerSupport { + def init = { + registerBeanDefinitionParser(ACTIVE_OBJECT_TAG, new AkkaObjectBeanDefinitionParser()); + registerBeanDefinitionParser(SUPERVISION_TAG, new SupervisionBeanDefinitionParser()); + } +} \ No newline at end of file diff --git a/akka-spring/src/main/scala/AkkaObjectBeanDefinitionParser.scala b/akka-spring/src/main/scala/AkkaObjectBeanDefinitionParser.scala new file mode 100644 index 0000000000..1f90c17454 --- /dev/null +++ b/akka-spring/src/main/scala/AkkaObjectBeanDefinitionParser.scala @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ +package se.scalablesolutions.akka.spring + +import org.springframework.beans.factory.support.BeanDefinitionBuilder +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser +import org.springframework.beans.factory.xml.ParserContext +import org.w3c.dom.Element +import se.scalablesolutions.akka.util.Logging + + +/** + * Parser for custom namespace configuration. + * @author michaelkober + */ +class AkkaObjectBeanDefinitionParser extends AbstractSingleBeanDefinitionParser with ActiveObjectBeanDefinitionParser { + /* + * @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#doParse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext, org.springframework.beans.factory.support.BeanDefinitionBuilder) + */ + override def doParse(element: Element, parserContext: ParserContext, builder: BeanDefinitionBuilder) { + val activeObjectConf = parseActiveObject(element) + activeObjectConf.setAsProperties(builder) + } + + /* + * @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#getBeanClass(org.w3c.dom.Element) + */ + override def getBeanClass(element: Element) = classOf[ActiveObjectFactoryBean] +} \ No newline at end of file diff --git a/akka-spring/src/main/scala/AkkaSpringConfigurationTags.scala b/akka-spring/src/main/scala/AkkaSpringConfigurationTags.scala new file mode 100644 index 0000000000..058d654ea7 --- /dev/null +++ b/akka-spring/src/main/scala/AkkaSpringConfigurationTags.scala @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ +package se.scalablesolutions.akka.spring + +/** + * XML configuration tags. + * @author michaelkober + */ +object AkkaSpringConfigurationTags { + // top level tags + val ACTIVE_OBJECT_TAG = "active-object" + val SUPERVISION_TAG = "supervision" + // active-object sub tags + val RESTART_CALLBACKS_TAG = "restart-callbacks" + val REMOTE_TAG = "remote"; + // superivision sub tags + val ACTIVE_OBJECTS_TAG = "active-objects" + val STRATEGY_TAG = "restart-strategy" + val TRAP_EXISTS_TAG = "trap-exits" + val TRAP_EXIT_TAG = "trap-exit" + // active object attributes + val TIMEOUT = "timeout" + val TARGET = "target" + val INTERFACE = "interface" + val TRANSACTIONAL = "transactional" + val HOST = "host" + val PORT = "port" + val PRE_RESTART = "pre" + val POST_RESTART = "post" + val LIFECYCLE = "lifecycle" + // supervision attributes + val FAILOVER = "failover" + val RETRIES = "retries" + val TIME_RANGE = "timerange" + // Value types + val VAL_LIFECYCYLE_TEMPORARY = "temporary" + val VAL_LIFECYCYLE_PERMANENT = "permanent" + val VAL_ALL_FOR_ONE = "AllForOne" + val VAL_ONE_FOR_ONE = "OneForOne" +} \ No newline at end of file diff --git a/akka-spring/src/main/scala/StringReflect.scala b/akka-spring/src/main/scala/StringReflect.scala new file mode 100644 index 0000000000..7dda9dba08 --- /dev/null +++ b/akka-spring/src/main/scala/StringReflect.scala @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ +package se.scalablesolutions.akka.spring + +object StringReflect { + /** + * Implicit conversion from String to StringReflect. + */ + implicit def string2StringReflect(x: String) = new StringReflect(x) +} + +/** + * Reflection helper class. + * @author michaelkober + */ +class StringReflect(val self: String) { + def toClass[T <: AnyRef]: Class[T] = { + val clazz = Class.forName(self) + clazz.asInstanceOf[Class[T]] + } +} + + diff --git a/akka-spring/src/main/scala/SupervisionBeanDefinitionParser.scala b/akka-spring/src/main/scala/SupervisionBeanDefinitionParser.scala new file mode 100644 index 0000000000..7134675af1 --- /dev/null +++ b/akka-spring/src/main/scala/SupervisionBeanDefinitionParser.scala @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ +package se.scalablesolutions.akka.spring + +import se.scalablesolutions.akka.util.Logging +import org.springframework.beans.factory.support.BeanDefinitionBuilder +import org.springframework.beans.factory.xml.{ParserContext, AbstractSingleBeanDefinitionParser} +import se.scalablesolutions.akka.config.JavaConfig._ +import AkkaSpringConfigurationTags._ + + +import org.w3c.dom.Element +import org.springframework.util.xml.DomUtils + + +/** + * Parser for custom namespace for Akka declarative supervisor configuration. + * @author michaelkober + */ +class SupervisionBeanDefinitionParser extends AbstractSingleBeanDefinitionParser with ActiveObjectBeanDefinitionParser { + /* (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#doParse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext, org.springframework.beans.factory.support.BeanDefinitionBuilder) + */ + override def doParse(element: Element, parserContext: ParserContext, builder: BeanDefinitionBuilder) { + parseSupervisor(element, builder) + } + + /** + * made accessible for testing + */ + private[akka] def parseSupervisor(element: Element, builder: BeanDefinitionBuilder) { + val strategyElement = mandatoryElement(element, STRATEGY_TAG); + val activeObjectsElement = mandatoryElement(element, ACTIVE_OBJECTS_TAG); + parseRestartStrategy(strategyElement, builder) + parseActiveObjectList(activeObjectsElement, builder) + } + + private[akka] def parseRestartStrategy(element: Element, builder: BeanDefinitionBuilder) { + val failover = if (mandatory(element, FAILOVER) == "AllForOne") new AllForOne() else new OneForOne() + val timeRange = mandatory(element, TIME_RANGE).toInt + val retries = mandatory(element, RETRIES).toInt + val trapExitsElement = mandatoryElement(element, TRAP_EXISTS_TAG) + val trapExceptions = parseTrapExits(trapExitsElement) + val restartStrategy = new RestartStrategy(failover, retries, timeRange, trapExceptions) + builder.addPropertyValue("restartStrategy", restartStrategy) + } + + private[akka] def parseActiveObjectList(element: Element, builder: BeanDefinitionBuilder) { + val activeObjects = DomUtils.getChildElementsByTagName(element, ACTIVE_OBJECT_TAG).toArray.toList.asInstanceOf[List[Element]] + val activeObjectProperties = activeObjects.map(parseActiveObject(_)) + builder.addPropertyValue("supervised", activeObjectProperties) + } + + private def parseTrapExits(element: Element): Array[Class[_ <: Throwable]] = { + import StringReflect._ + val trapExits = DomUtils.getChildElementsByTagName(element, TRAP_EXIT_TAG).toArray.toList.asInstanceOf[List[Element]] + trapExits.map(DomUtils.getTextValue(_).toClass).toArray + } + + /* + * @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#getBeanClass(org.w3c.dom.Element) + */ + override def getBeanClass(element: Element) = classOf[SupervisionFactoryBean] +} \ No newline at end of file diff --git a/akka-spring/src/main/scala/SupervisionFactoryBean.scala b/akka-spring/src/main/scala/SupervisionFactoryBean.scala new file mode 100644 index 0000000000..f02ed6c283 --- /dev/null +++ b/akka-spring/src/main/scala/SupervisionFactoryBean.scala @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ +package se.scalablesolutions.akka.spring + +import org.springframework.beans.factory.config.AbstractFactoryBean +import se.scalablesolutions.akka.config.ActiveObjectConfigurator +import se.scalablesolutions.akka.config.JavaConfig._ +import AkkaSpringConfigurationTags._ +import reflect.BeanProperty + + +/** + * Factory bean for supervisor configuration. + * @author michaelkober + */ +class SupervisionFactoryBean extends AbstractFactoryBean { + @BeanProperty var restartStrategy: RestartStrategy = _ + @BeanProperty var supervised: List[ActiveObjectProperties] = _ + + /* + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + def getObjectType = classOf[ActiveObjectConfigurator] + + /* + * @see org.springframework.beans.factory.config.AbstractFactoryBean#createInstance() + */ + def createInstance: AnyRef = { + val configurator = new ActiveObjectConfigurator() + + configurator.configure( + restartStrategy, + supervised.map(createComponent(_)).toArray + ).supervise + } + + /** + * Create configuration for ActiveObject + */ + 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 isRemote = (props.host != null) && (!props.host.isEmpty) + val withInterface = (props.interface != null) && (!props.interface.isEmpty) + // FIXME: timeout int vs long + val timeout = props.timeout.asInstanceOf[Int] + if (isRemote) { + val remote = new RemoteAddress(props.host, props.port) + if (withInterface) { + new Component(props.interface.toClass, props.target.toClass, lifeCycle, timeout, props.transactional, remote) + } else { + new Component(props.target.toClass, lifeCycle, timeout, props.transactional, remote) + } + } else { + if (withInterface) { + new Component(props.interface.toClass, props.target.toClass, lifeCycle, timeout, props.transactional) + } else { + new Component(props.target.toClass, lifeCycle, timeout, props.transactional) + } + } + } +} \ No newline at end of file diff --git a/akka-spring/src/test/java/se/scalablesolutions/akka/spring/AkkaSpringInterceptorTest.java b/akka-spring/src/test/java/se/scalablesolutions/akka/spring/AkkaSpringInterceptorTest.java deleted file mode 100644 index 73c08adeca..0000000000 --- a/akka-spring/src/test/java/se/scalablesolutions/akka/spring/AkkaSpringInterceptorTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package se.scalablesolutions.akka.spring; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -public class AkkaSpringInterceptorTest extends TestCase { - - public AkkaSpringInterceptorTest(String testName) { - super(testName); - } - - public static Test suite() { - return new TestSuite(AkkaSpringInterceptorTest.class); - } - - public void testInvokingAkkaEnabledSpringBeanMethodWithReturnValue() { - ApplicationContext context = new ClassPathXmlApplicationContext("spring-test-config.xml"); - MyService myService = (MyService) context.getBean("actorBeanService"); - Object obj = myService.getNumbers(12); - assertEquals(new Integer(12), obj); - } - - public void testThatAkkaEnabledSpringBeanMethodCallIsInvokedInADifferentThreadThanTheTest() { - ApplicationContext context = new ClassPathXmlApplicationContext("spring-test-config.xml"); - MyService myService = (MyService) context.getBean("actorBeanService"); - assertNotSame(Thread.currentThread().getName(), myService.getThreadName()); - } - - public void testInvokingAkkaEnabledSpringBeanMethodWithTransactionalMethod() { - ApplicationContext context = new ClassPathXmlApplicationContext("spring-test-config.xml"); - MyService myService = (MyService) context.getBean("actorBeanService"); - myService.init(); - myService.setMapState("key", "value"); - myService.setRefState("value"); - myService.setVectorState("value"); - assertEquals("value", myService.getMapState("key")); - assertEquals("value", myService.getRefState()); - assertEquals("value", myService.getVectorState()); - } -} diff --git a/akka-spring/src/test/java/se/scalablesolutions/akka/spring/MyService.java b/akka-spring/src/test/java/se/scalablesolutions/akka/spring/MyService.java deleted file mode 100644 index 117e282af5..0000000000 --- a/akka-spring/src/test/java/se/scalablesolutions/akka/spring/MyService.java +++ /dev/null @@ -1,79 +0,0 @@ -package se.scalablesolutions.akka.spring; - -import org.springframework.transaction.annotation.Transactional; - -import se.scalablesolutions.akka.state.TransactionalState; -import se.scalablesolutions.akka.state.TransactionalRef; -import se.scalablesolutions.akka.state.TransactionalVector; -import se.scalablesolutions.akka.state.TransactionalMap; - -import se.scalablesolutions.akka.annotation.oneway; -import se.scalablesolutions.akka.annotation.prerestart; -import se.scalablesolutions.akka.annotation.postrestart; - -public class MyService { - - private TransactionalMap mapState; - private TransactionalVector vectorState; - private TransactionalRef refState; - private boolean isInitialized = false; - - @Transactional - public void init() { - if (!isInitialized) { - mapState = TransactionalState.newMap(); - vectorState = TransactionalState.newVector(); - refState = TransactionalState.newRef(); - isInitialized = true; - } - } - - public String getThreadName() { - return Thread.currentThread().getName(); - } - - public Integer getNumbers(int aTestNumber) { - return new Integer(aTestNumber); - } - - @Transactional - public String getMapState(String key) { - return (String)mapState.get(key).get(); - } - - @Transactional - public String getVectorState() { - return (String)vectorState.last(); - } - - @Transactional - public String getRefState() { - return (String)refState.get().get(); - } - - @Transactional - public void setMapState(String key, String msg) { - mapState.put(key, msg); - } - - @Transactional - public void setVectorState(String msg) { - vectorState.add(msg); - } - - @Transactional - public void setRefState(String msg) { - refState.swap(msg); - } - - @prerestart - public void preRestart() { - System.out.println("################ PRE RESTART"); - } - - @postrestart - public void postRestart() { - System.out.println("################ POST RESTART"); - } -} - diff --git a/akka-spring/src/test/resources/spring-test-config.xml b/akka-spring/src/test/resources/spring-test-config.xml deleted file mode 100644 index dadbcf56d1..0000000000 --- a/akka-spring/src/test/resources/spring-test-config.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - akkaInterceptor - - - - - \ No newline at end of file diff --git a/akka-spring/src/test/resources/test-config.xml b/akka-spring/src/test/resources/test-config.xml new file mode 100644 index 0000000000..d3a8077945 --- /dev/null +++ b/akka-spring/src/test/resources/test-config.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.io.IOException + java.lang.NullPointerException + + + + + + + + + + + + + \ No newline at end of file diff --git a/akka-spring/src/test/scala/ActiveObjectBeanDefinitionParserTest.scala b/akka-spring/src/test/scala/ActiveObjectBeanDefinitionParserTest.scala new file mode 100644 index 0000000000..a445d92cba --- /dev/null +++ b/akka-spring/src/test/scala/ActiveObjectBeanDefinitionParserTest.scala @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ +package se.scalablesolutions.akka.spring + +import org.scalatest.Spec +import org.scalatest.matchers.ShouldMatchers +import org.scalatest.junit.JUnitRunner +import org.junit.runner.RunWith +import ScalaDom._ + +import org.w3c.dom.Element + +/** + * Test for ActiveObjectBeanDefinitionParser + * @author michaelkober + */ +@RunWith(classOf[JUnitRunner]) +class ActiveObjectBeanDefinitionParserTest extends Spec with ShouldMatchers { + private class Parser extends ActiveObjectBeanDefinitionParser + + describe("An ActiveObjectBeanDefinitionParser") { + val parser = new Parser() + it("should parse the active object configuration") { + val props = parser.parseActiveObject(createTestElement); + assert(props != null) + assert(props.timeout == 1000) + assert(props.target == "foo.bar.MyPojo") + assert(props.transactional) + } + + it("should throw IllegalArgumentException on missing mandatory attributes") { + evaluating { parser.parseActiveObject(createTestElement2) } should produce [IllegalArgumentException] + } + } + + private def createTestElement : Element = { + val xml = + dom(xml).getDocumentElement + } + + private def createTestElement2 : Element = { + val xml = + dom(xml).getDocumentElement + } +} \ No newline at end of file diff --git a/akka-spring/src/test/scala/ActiveObjectFactoryBeanTest.scala b/akka-spring/src/test/scala/ActiveObjectFactoryBeanTest.scala new file mode 100644 index 0000000000..e0eec060bf --- /dev/null +++ b/akka-spring/src/test/scala/ActiveObjectFactoryBeanTest.scala @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ +package se.scalablesolutions.akka.spring + +import org.scalatest.Spec +import org.scalatest.matchers.ShouldMatchers +import org.scalatest.junit.JUnitRunner +import org.junit.runner.RunWith + +/** + * Test for ActiveObjectFactoryBean + * @author michaelkober + */ +@RunWith(classOf[JUnitRunner]) +class ActiveObjectFactoryBeanTest extends Spec with ShouldMatchers { + + describe("A ActiveObjectFactoryBean") { + val bean = new ActiveObjectFactoryBean + it("should have java getters and setters for all properties") { + bean.setTarget("java.lang.String") + assert(bean.getTarget == "java.lang.String") + bean.setTimeout(1000) + assert(bean.getTimeout == 1000) + } + + it("should create a remote active object when a host is set") { + bean.setHost("some.host.com"); + assert(bean.isRemote) + } + + it("should return the object type") { + bean.setTarget("java.lang.String") + assert(bean.getObjectType == classOf[String]) + } + + it("should create an active object") { + // TODO: + } + } +} diff --git a/akka-spring/src/test/scala/ScalaDom.scala b/akka-spring/src/test/scala/ScalaDom.scala new file mode 100644 index 0000000000..70531861e1 --- /dev/null +++ b/akka-spring/src/test/scala/ScalaDom.scala @@ -0,0 +1,40 @@ +package se.scalablesolutions.akka.spring +/** + * from http://stackoverflow.com/questions/2002685/any-conversion-from-scalas-xml-to-w3c-dom + */ + +object ScalaDom { + import scala.xml._ + import org.w3c.dom.{Document => JDocument, Node => JNode} + import javax.xml.parsers.DocumentBuilderFactory + + def dom(n: Node): JDocument = { + + val doc = DocumentBuilderFactory + .newInstance + .newDocumentBuilder + .getDOMImplementation + .createDocument(null, null, null) + + def build(node: Node, parent: JNode): Unit = { + val jnode: JNode = node match { + case e: Elem => { + val jn = doc.createElement(e.label) + e.attributes foreach { a => jn.setAttribute(a.key, a.value.mkString) } + jn + } + case a: Atom[_] => doc.createTextNode(a.text) + case c: Comment => doc.createComment(c.commentText) + case er: EntityRef => doc.createEntityReference(er.entityName) + case pi: ProcInstr => doc.createProcessingInstruction(pi.target, pi.proctext) + } + parent.appendChild(jnode) + node.child.map { build(_, jnode) } + } + + build(n, doc) + doc + + } +} + \ No newline at end of file diff --git a/akka-spring/src/test/scala/SupervisionBeanDefinitionParserTest.scala b/akka-spring/src/test/scala/SupervisionBeanDefinitionParserTest.scala new file mode 100644 index 0000000000..e5677113af --- /dev/null +++ b/akka-spring/src/test/scala/SupervisionBeanDefinitionParserTest.scala @@ -0,0 +1,122 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ +package se.scalablesolutions.akka.spring + +import org.scalatest.Spec +import org.scalatest.matchers.ShouldMatchers +import org.scalatest.junit.JUnitRunner +import org.junit.runner.RunWith +import ScalaDom._ + +import se.scalablesolutions.akka.config.JavaConfig._ + +import org.w3c.dom.Element +import org.springframework.beans.factory.support.BeanDefinitionBuilder + +/** + * Test for SupervisionBeanDefinitionParser + * @author michaelkober + */ +@RunWith(classOf[JUnitRunner]) +class SupervisionBeanDefinitionParserTest extends Spec with ShouldMatchers { + private class Parser extends SupervisionBeanDefinitionParser + + describe("A SupervisionBeanDefinitionParser") { + val parser = new Parser() + val builder = BeanDefinitionBuilder.genericBeanDefinition("foo.bar.Foo") + + it("should be able to parse active object configuration") { + val props = parser.parseActiveObject(createActiveObjectElement); + assert(props != null) + assert(props.timeout == 1000) + assert(props.target == "foo.bar.MyPojo") + assert(props.transactional) + } + + it("should parse the supervisor restart strategy") { + parser.parseSupervisor(createSupervisorElement, builder); + val strategy = builder.getBeanDefinition.getPropertyValues.getPropertyValue("restartStrategy").getValue.asInstanceOf[RestartStrategy] + assert(strategy != null) + assert(strategy.scheme match { + case x:AllForOne => true + case _ => false }) + expect(3) { strategy.maxNrOfRetries } + expect(1000) { strategy.withinTimeRange } + } + + it("should parse the supervised active objects") { + parser.parseSupervisor(createSupervisorElement, builder); + val supervised = builder.getBeanDefinition.getPropertyValues.getPropertyValue("supervised").getValue.asInstanceOf[List[ActiveObjectProperties]] + assert(supervised != null) + expect(3) { supervised.length } + val iterator = supervised.elements + expect("foo.bar.Foo") { iterator.next.target } + expect("foo.bar.Bar") { iterator.next.target } + expect("foo.bar.MyPojo") { iterator.next.target } + } + + it("should throw IllegalArgumentException on missing mandatory attributes") { + evaluating { parser.parseSupervisor(createSupervisorMissingAttribute, builder) } should produce [IllegalArgumentException] + } + + it("should throw IllegalArgumentException on missing mandatory elements") { + evaluating { parser.parseSupervisor(createSupervisorMissingElement, builder) } should produce [IllegalArgumentException] + } + } + + private def createActiveObjectElement : Element = { + val xml = + dom(xml).getDocumentElement + } + + private def createSupervisorElement : Element = { + val xml = + + + java.io.IOException + java.lang.NullPointerException + + + + + + + + + + + dom(xml).getDocumentElement + } + + + private def createSupervisorMissingAttribute : Element = { + val xml = + + + java.io.IOException + + + + + + + dom(xml).getDocumentElement + } + + private def createSupervisorMissingElement : Element = { + val xml = + + + + + + + + dom(xml).getDocumentElement + } +} + diff --git a/akka-spring/src/test/scala/SupervisionFactoryBeanTest.scala b/akka-spring/src/test/scala/SupervisionFactoryBeanTest.scala new file mode 100644 index 0000000000..dbb0798c9d --- /dev/null +++ b/akka-spring/src/test/scala/SupervisionFactoryBeanTest.scala @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ +package se.scalablesolutions.akka.spring + +import org.scalatest.Spec +import org.scalatest.matchers.ShouldMatchers +import org.scalatest.junit.JUnitRunner +import org.junit.runner.RunWith +import se.scalablesolutions.akka.config.JavaConfig._ +import se.scalablesolutions.akka.config.ActiveObjectConfigurator + +private[akka] class Foo + +@RunWith(classOf[JUnitRunner]) +class SupervisionFactoryBeanTest extends Spec with ShouldMatchers { + + val restartStrategy = new RestartStrategy(new AllForOne(), 3, 1000, Array(classOf[Throwable])) + val activeObjects = List(createActiveObjectProperties("se.scalablesolutions.akka.spring.Foo", 1000L)) + + def createActiveObjectProperties(target: String, timeout: Long) : ActiveObjectProperties = { + val properties = new ActiveObjectProperties() + properties.target = target + properties.timeout = timeout + properties + } + + describe("A SupervisionFactoryBean") { + val bean = new SupervisionFactoryBean + it("should have java getters and setters for all properties") { + bean.setRestartStrategy(restartStrategy) + assert(bean.getRestartStrategy == restartStrategy) + bean.setSupervised(activeObjects) + assert(bean.getSupervised == activeObjects) + } + + it("should return the object type ActiveObjectConfigurator") { + assert(bean.getObjectType == classOf[ActiveObjectConfigurator]) + } + } +} \ No newline at end of file