initial version of spring custom namespace

This commit is contained in:
Michael Kober 2010-03-14 22:19:20 +01:00
parent 4afd6a9f54
commit 9533b54fbf
25 changed files with 1028 additions and 445 deletions

View file

@ -1,77 +1,76 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<modelVersion>4.0.0</modelVersion>
<artifactId>akka-spring</artifactId>
<name>Akka Spring Integration</name>
<packaging>jar</packaging>
<artifactId>akka-spring</artifactId>
<name>Akka Spring Module</name>
<packaging>jar</packaging>
<parent>
<artifactId>akka</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
<relativePath>../pom.xml</relativePath>
</parent>
<parent>
<artifactId>akka</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.7-SNAPSHOT</version>
</parent>
<dependencies>
<!-- Core deps -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
<artifactId>akka-core</artifactId>
<groupId>${project.groupId}</groupId>
<version>${project.version}</version>
</dependency>
<dependency>
<artifactId>akka-util-java</artifactId>
<groupId>${project.groupId}</groupId>
<version>${project.version}</version>
</dependency>
<dependency>
<artifactId>akka-util</artifactId>
<groupId>${project.groupId}</groupId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.5</version>
<version>2.5.6</version>
</dependency>
<dependency>
<artifactId>akka-core</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
<!--dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.0.1.RELEASE</version>
</dependency-->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.7.5</version>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-compiler</artifactId>
<version>${scala.version}</version>
</dependency>
<!-- For Testing -->
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.aspectwerkz</groupId>
<artifactId>aspectwerkz-nodeps-jdk5</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.aspectwerkz</groupId>
<artifactId>aspectwerkz-jdk5</artifactId>
<version>2.1</version>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<testResources>
<testResource>
<directory>src/test/resources</directory>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
<includes>
<include>**/*</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,81 +0,0 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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);
}
}
}

View file

@ -1,89 +0,0 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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();
}
}

View file

@ -1,73 +0,0 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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();
}
}

View file

@ -0,0 +1 @@
http\://www.akkasource.org/schema/akka=se.scalablesolutions.akka.spring.AkkaNamespaceHandler

View file

@ -0,0 +1 @@
http\://www.akkasource.org/schema/akka=se/scalablesolutions/akka/spring/akka.xsd

View file

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.akkasource.org/schema/akka"
targetNamespace="http://www.akkasource.org/schema/akka"
elementFormDefault="qualified" attributeFormDefault="unqualified"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!-- base types -->
<!-- restart strategies enumeration -->
<xsd:simpleType name="failover-type">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="AllForOne"/>
<xsd:enumeration value="OneForOne"/>
</xsd:restriction>
</xsd:simpleType>
<!-- restart strategies enumeration -->
<xsd:simpleType name="lifecycle-type">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="permanent"/>
<xsd:enumeration value="temporary"/>
</xsd:restriction>
</xsd:simpleType>
<!-- Remote -->
<xsd:complexType name="remote-type">
<xsd:attribute name="host" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
Name of the remote host.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="port" type="xsd:integer" use="required">
<xsd:annotation>
<xsd:documentation>
Port of the remote host.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<!-- callbacks -->
<xsd:complexType name="restart-callbacks-type">
<xsd:attribute name="pre" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Pre restart callback method that is called during restart.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="post" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Post restart callback method that is called during restart.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<!-- active object -->
<xsd:complexType name="active-object-type">
<xsd:sequence>
<xsd:element name="restart-callbacks" type="restart-callbacks-type" minOccurs="0" maxOccurs="1" />
<xsd:element name="remote" type="remote-type" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" />
<xsd:attribute name="target" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
Name of the target class.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="timeout" type="xsd:long" use="required">
<xsd:annotation>
<xsd:documentation>
default timeout for '!!' invocations
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="transactional" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation>
Set to true if messages should have REQUIRES_NEW semantics
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="interface" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Interface implemented by target class.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="lifecycle" type="lifecycle-type">
<xsd:annotation>
<xsd:documentation>
Lifecycle, permanent or temporary
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<!-- trap exits -->
<xsd:complexType name="trap-exits-type">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element name="trap-exit" type="xsd:string"/>
</xsd:choice>
</xsd:complexType>
<!-- active objects -->
<xsd:complexType name="active-objects-type">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element name="active-object" type="active-object-type"/>
</xsd:choice>
</xsd:complexType>
<!-- Supervisor -->
<xsd:complexType name="strategy-type" >
<xsd:sequence>
<xsd:element name="trap-exits" type="trap-exits-type" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="failover" type="failover-type">
<xsd:annotation>
<xsd:documentation>
Failover scheme, AllForOne or OneForOne
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="retries" type="xsd:int">
<xsd:annotation>
<xsd:documentation>
Maximal number of retries.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="timerange" type="xsd:int">
<xsd:annotation>
<xsd:documentation>
Timerange for restart.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<!-- ActiveObject -->
<xsd:element name="active-object" type="active-object-type"/>
<!-- Supervision -->
<xsd:element name="supervision">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="restart-strategy" type="strategy-type" minOccurs="1" maxOccurs="1"/>
<xsd:element name="active-objects" type="active-objects-type" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View file

@ -0,0 +1,88 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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: '<akka:" + childName + ">'")
} else {
childElement
}
}
}

View file

@ -0,0 +1,81 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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)
}
}
}

View file

@ -0,0 +1,41 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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)
}
}

View file

@ -0,0 +1,18 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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());
}
}

View file

@ -0,0 +1,30 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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]
}

View file

@ -0,0 +1,41 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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"
}

View file

@ -0,0 +1,24 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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]]
}
}

View file

@ -0,0 +1,65 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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]
}

View file

@ -0,0 +1,63 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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)
}
}
}
}

View file

@ -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());
}
}

View file

@ -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<String, String> mapState;
private TransactionalVector<String> vectorState;
private TransactionalRef<String> 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");
}
}

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="akkaInterceptor" class="se.scalablesolutions.akka.spring.AkkaSpringInterceptor">
</bean>
<bean id="service" class="se.scalablesolutions.akka.spring.MyService">
</bean>
<bean id="actorBeanService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref bean="service"/>
</property>
<property name="interceptorNames">
<list>
<value>akkaInterceptor</value>
</list>
</property>
</bean>
</beans>

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:akka="http://www.akkasource.org/schema/akka"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.akkasource.org/schema/akka
file:////../../main/resources/se/scalablesolutions/akka/spring/akka.xsd">
<bean id="wrappedService"
class="se.scalablesolutions.akka.actor.ActiveObject"
factory-method="newInstance">
<constructor-arg index="0" type="java.lang.Class" value="foo.bar.MyPojo"/>
<constructor-arg index="1" value="1000"/>
</bean>
<akka:active-object id="active-object1"
target="foo.bar.MyPojo"
timeout="1000"/>
<akka:active-object id="service2"
target="foo.bar.MyPojo"
timeout="2000"
transactional="true"/>
<akka:active-object id="service3"
target="foo.bar.MyPojo"
timeout="2000"
transactional="true">
<akka:restart-callbacks pre="preRestart"/>
</akka:active-object>
<akka:active-object id="active-object2"
target="foo.bar.MyPojo"
timeout="2000"
transactional="true">
<akka:restart-callbacks pre="preRestartMethod" post="postRestartMethod"/>
<akka:remote host="localhost" port="9998" />
</akka:active-object>
<akka:active-object id="remote-service1" target="foo.bar.MyPojo" timeout="1000">
<akka:remote host="localhost" port="9998" />
</akka:active-object>
<akka:supervision id="supervision1">
<akka:restart-strategy failover="AllForOne" retries="3" timerange="1000">
<akka:trap-exits>
<akka:trap-exit>java.io.IOException</akka:trap-exit>
<akka:trap-exit>java.lang.NullPointerException</akka:trap-exit>
</akka:trap-exits>
</akka:restart-strategy>
<akka:active-objects>
<akka:active-object target="foo.bar.Foo" lifecycle="permanent" timeout="1000"/>
<akka:active-object interface="foo.bar.Bar" target="foo.bar.BarImpl" lifecycle="permanent" timeout="1000"/>
<akka:active-object target="foo.bar.SomeService" lifecycle="temporary" timeout="1000">
<akka:restart-callbacks pre="preRestartMethod" post="postRestartMethod"/>
</akka:active-object>
</akka:active-objects>
</akka:supervision>
</beans>

View file

@ -0,0 +1,51 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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 = <akka:active-object id="active-object1"
target="foo.bar.MyPojo"
timeout="1000"
transactional="true"/>
dom(xml).getDocumentElement
}
private def createTestElement2 : Element = {
val xml = <akka:active-object id="active-object1"
timeout="1000"
transactional="true"/>
dom(xml).getDocumentElement
}
}

View file

@ -0,0 +1,41 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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:
}
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,122 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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 = <akka:active-object id="active-object1"
target="foo.bar.MyPojo"
timeout="1000"
transactional="true"/>
dom(xml).getDocumentElement
}
private def createSupervisorElement : Element = {
val xml = <akka:supervision id="supervision1">
<akka:restart-strategy failover="AllForOne" retries="3" timerange="1000">
<akka:trap-exits>
<akka:trap-exit>java.io.IOException</akka:trap-exit>
<akka:trap-exit>java.lang.NullPointerException</akka:trap-exit>
</akka:trap-exits>
</akka:restart-strategy>
<akka:active-objects>
<akka:active-object target="foo.bar.Foo" lifecycle="permanent" timeout="1000"/>
<akka:active-object interface="foo.bar.IBar" target="foo.bar.Bar" lifecycle="permanent" timeout="1000"/>
<akka:active-object target="foo.bar.MyPojo" lifecycle="temporary" timeout="1000">
<akka:restart-callbacks pre="preRestart" post="postRestart"/>
</akka:active-object>
</akka:active-objects>
</akka:supervision>
dom(xml).getDocumentElement
}
private def createSupervisorMissingAttribute : Element = {
val xml = <akka:supervision id="supervision1">
<akka:restart-strategy failover="AllForOne" retries="3">
<akka:trap-exits>
<akka:trap-exit>java.io.IOException</akka:trap-exit>
</akka:trap-exits>
</akka:restart-strategy>
<akka:active-objects>
<akka:active-object target="foo.bar.Foo" lifecycle="permanent" timeout="1000"/>
</akka:active-objects>
</akka:supervision>
dom(xml).getDocumentElement
}
private def createSupervisorMissingElement : Element = {
val xml = <akka:supervision id="supervision1">
<akka:restart-strategy failover="AllForOne" retries="3" timerange="1000">
</akka:restart-strategy>
<akka:active-objects>
<akka:active-object target="foo.bar.Foo" lifecycle="permanent" timeout="1000"/>
<akka:active-object interface="foo.bar.IBar" target="foo.bar.Bar" lifecycle="permanent" timeout="1000"/>
</akka:active-objects>
</akka:supervision>
dom(xml).getDocumentElement
}
}

View file

@ -0,0 +1,41 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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])
}
}
}