Initial commit

master
eater 5 years ago
commit e4b0e720fa
Signed by: eater
GPG Key ID: AD2560A0F84F0759

6
.gitignore vendored

@ -0,0 +1,6 @@
*.iml
*.ipr
*.iws
.gradle
build

@ -0,0 +1,31 @@
# Servitor
> A workflow engine
# Components
This project is split is multiple components which _should_ allow for effective clustering of relevant parts.
## [Engine](/engine)
The main component of Servitor, this part executes all logic noted in the workflow files
## [Relay](/relay)
The relay component does all the talking to remote endpoints, waits for their answers and puts the result back in the queue for the engine to process
## [Gateway](/gateway)
The gateway component observes and records all actions happening on the relay and in the engine.
It also serves the API (and When Time Comes the web frontend).
## [Monolith](/monolith)
Monolith is a special version of Servitor, which combines all tech necessary to run Servitor in a single jar.
This will be most likely the first thing you'll want to use if you want to play around with Servitor or want to deploy it in a small setup.
Monolith will not require -any- outside dependencies, it will use a bundled software for the message queues (ActiveMQ), key value store (???), and relation database (SQLite).

@ -0,0 +1,10 @@
[servitor]
[web]
bind = ":8888"
[queue]
uri = "tcp://localhost:61616"
username = "artemis"
password = "simetraehcapa"

@ -0,0 +1,79 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
id 'java'
id 'idea'
}
repositories {
mavenCentral()
jcenter()
}
subprojects {
// Hack to allow defining global dependencies
apply plugin: 'java'
apply plugin: 'org.jetbrains.kotlin.jvm'
group = 'wf.servitor'
version = '1.0-SNAPSHOT'
def versions = project.ext.versions = [
kotlin : '1.3.61',
jackson : '2.9.10',
joda_time : '2.10.5',
koin : '2.0.1',
kotlinx_coroutines: '1.3.2',
jexl : '3.1',
junit : '5.5.2',
activemq : '2.11.0',
]
repositories {
mavenCentral()
jcenter()
}
dependencies {
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}"
// Kotlin tests
implementation "org.jetbrains.kotlin:kotlin-test:${versions.kotlin}"
implementation "org.jetbrains.kotlin:kotlin-test-junit5:${versions.kotlin}"
// Kotlin Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.kotlinx_coroutines}"
// Koin
implementation "org.koin:koin-core:${versions.koin}"
// ActiveMQ
implementation "org.apache.activemq:artemis-core-client:${versions.activemq}"
// JUnit
testImplementation "org.junit.jupiter:junit-jupiter:${versions.junit}"
if (project.name != 'common') {
implementation project(':common')
}
}
compileKotlin {
kotlinOptions.jvmTarget = "12"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "12"
}
sourceCompatibility = 12
targetCompatibility = 12
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
}

@ -0,0 +1,25 @@
plugins {
id 'org.jetbrains.kotlin.jvm'
id 'java'
}
def versions = project.ext.versions;
repositories {
mavenCentral()
}
dependencies {
// Jackson
implementation "com.fasterxml.jackson.core:jackson-core:${versions.jackson}"
implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}"
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:${versions.jackson}"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${versions.jackson}"
// Joda Time
implementation "joda-time:joda-time:${versions.joda_time}"
// Konf
implementation 'com.uchuhimo:konf-core:0.22.1'
implementation 'com.uchuhimo:konf-toml:0.22.1'
}

@ -0,0 +1,38 @@
package wf.servitor.common
import wf.servitor.common.workflow.Workflow
import java.io.Serializable
import java.util.*
sealed class Event : Serializable {
data class Task(
override val workflow: Workflow,
val context: Map<String, Any?> = mapOf(),
override val flow: String = "entry",
override val path: List<Int> = listOf(0),
val dispatchedValues: List<Any?> = listOf(),
override val id: UUID = UUID.randomUUID()
) : Event(), TaskLike
data class Relay(
val service: String,
val method: String,
val arguments: List<Any?>,
val task: Task,
val id: UUID = UUID.randomUUID()
) : Event()
data class TaskUpdate(
val task: TaskLike,
val status: String
) : Event()
interface TaskLike : Serializable {
val id: UUID
val workflow: Workflow
val flow: String
val path: List<Int>
}
}

@ -0,0 +1,43 @@
package wf.servitor.common
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
import org.apache.activemq.artemis.api.core.client.ClientSession
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory
import org.apache.activemq.artemis.api.core.client.ServerLocator
import org.koin.core.qualifier.named
import org.koin.core.scope.Scope
import org.koin.dsl.module
import wf.servitor.common.config.Config
import wf.servitor.common.config.createConfig
import wf.servitor.common.event.Session
fun Scope.config(): Config = get()
val commonModule = module {
single { createConfig(getOrNull(named("argument.config"))) }
single { ActiveMQClient.createServerLocator(config().queue.url) }
single { get<ServerLocator>().createSessionFactory() }
single {
val serverLocator: ServerLocator = get()
val config = config().queue
get<ClientSessionFactory>().createSession(
config.username,
config.password,
false,
true,
true,
false,
serverLocator.ackBatchSize
)
}
single { get<ClientSession>().createProducer() }
single(named("queue.task")) {
Session(get(), config().queue.names.taskQueue)
}
}

@ -0,0 +1,4 @@
package wf.servitor.common.config
class Config(val queue: Queue) {
}

@ -0,0 +1,28 @@
package wf.servitor.common.config
class Queue(
val url: String,
val username: String?,
val password: String?,
val names: Names
) {
class Names(
val prefix: String,
val task: String,
val relay: String,
val observer: String
) {
val taskAddress
get() = "$prefix.$task"
val taskQueue
get() = "$prefix.$task.queue"
val relayAddress
get() = "$prefix.$relay"
val relayQueue
get() = "$prefix.$relay.queue"
}
}

@ -0,0 +1,34 @@
package wf.servitor.common.config
import com.uchuhimo.konf.ConfigSpec
object QueueSpec : ConfigSpec() {
val url by optional("tcp://localhost:61616", description = "url of ActiveMQ server")
val username by optional<String?>(null, description = "username for ActiveMQ server")
val password by optional<String?>(null, description = "password for ActiveMQ server")
object Names : ConfigSpec() {
val namePrefix by optional(
"servitor",
name = "prefix",
description = "the prefix of all queues and addresses used in ActiveMQ"
)
val task by optional(
"task",
description = "the name of the task address, the queue will always appear at [prefix].[task].queue"
)
val observation by optional(
"observe",
description = "the name of the observation queue, which will listen to all messages send to [prefix].#"
)
val relay by optional(
"relay",
description = "the name of the relay address, the queue will appear at [prefix].[relay].queue"
)
}
}

@ -0,0 +1,39 @@
package wf.servitor.common.config
import com.uchuhimo.konf.source.toml
import com.uchuhimo.konf.Config.Companion as KonfConfig
fun createConfig(file: String? = null): Config {
val config = run {
var config = KonfConfig {
addSpec(QueueSpec)
}
.from.toml.file("/etc/servitor/config.toml", optional = true)
.from.json.file("/etc/servitor/config.json", optional = true)
if (file !== null) {
config = if (file.endsWith("json")) {
config.from.json.file(file)
} else {
config.from.toml.file(file)
}
}
config
}
return Config(
Queue(
config[QueueSpec.url],
config[QueueSpec.username],
config[QueueSpec.password],
Queue.Names(
config[QueueSpec.Names.namePrefix],
config[QueueSpec.Names.task],
config[QueueSpec.Names.relay],
config[QueueSpec.Names.observation]
)
)
)
}

@ -0,0 +1,86 @@
package wf.servitor.common.event
import org.apache.activemq.artemis.api.core.Message
import org.apache.activemq.artemis.api.core.RoutingType
import org.apache.activemq.artemis.api.core.client.ClientMessage
import org.apache.activemq.artemis.api.core.client.ClientSession
import wf.servitor.common.Event
import java.io.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
class Session(private val session: ClientSession, private val queue: String) {
private val consumer by lazy {
lock.withLock {
session.start()
}
session.createConsumer(queue)
}
private val lock = ReentrantLock()
private val producer by lazy { session.createProducer() }
private fun makeMessage(routingType: RoutingType, obj: Event, group: String? = null): Message {
return session.createMessage(true).also {
it.routingType = routingType
group?.let(it::setGroupID)
val bbos = ByteArrayOutputStream()
val oos = ObjectOutputStream(bbos)
oos.writeObject(obj)
it.bodyBuffer.writeBytes(bbos.toByteArray())
}
}
fun sendUpdate(update: Event.TaskUpdate) = lock.withLock {
println("Sending update")
println(update)
producer.send(
"servitor.observer",
makeMessage(RoutingType.ANYCAST, update, "servitor.task.${update.task.id}")
)
}
fun queueTask(task: Event.Task) = lock.withLock {
println("Sending task")
println(task)
producer.send(
"servitor.task",
makeMessage(RoutingType.ANYCAST, task, "servitor.task.${task.id}")
)
}
fun queueRelay(relay: Event.Relay) = lock.withLock {
producer.send(
"servitor.relay",
makeMessage(RoutingType.ANYCAST, relay, "servitor.task.${relay.task.id}")
)
}
fun onMessage(block: (ClientMessage) -> Unit) {
lock.withLock {
consumer.setMessageHandler(block)
}
}
fun close() {
session.commit()
session.close()
}
inline fun <reified T : Serializable> extract(message: ClientMessage): T {
message.acknowledge()
val byteArray = ByteArray(message.bodySize)
message.bodyBuffer.readBytes(byteArray)
val obj = ObjectInputStream(ByteArrayInputStream(byteArray)).readObject()
if (obj !is T) {
throw RuntimeException("Queue is FUCKED")
}
return obj
}
}

@ -0,0 +1,31 @@
package wf.servitor.common.utils
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.module.SimpleModule
import org.joda.time.DateTime
import wf.servitor.common.workflow.Flow
import wf.servitor.common.workflow.Step
object JacksonModule : SimpleModule() {
val listStepType = object : TypeReference<List<Step>>() {}
init {
addDeserializer(Flow::class.java, object : JsonDeserializer<Flow>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Flow {
return Flow(p.readValueAs<List<Step>>(listStepType).toMutableList())
}
})
addSerializer(DateTime::class.java, object : JsonSerializer<DateTime>() {
override fun serialize(value: DateTime, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.toString())
}
})
}
}

@ -0,0 +1,9 @@
package wf.servitor.common.workflow
import java.io.Serializable
class Document(
val name: String = "",
val workflow: Workflow
) : Serializable

@ -0,0 +1,17 @@
package wf.servitor.common.workflow
import java.io.Serializable
data class Flow(val steps: List<Step> = listOf()) : Serializable {
fun getStep(path: List<Int>): Step? {
if (path.isEmpty()) {
return null
}
return path
.drop(1)
.fold(steps.getOrNull(path.first())) { s, index ->
s?.getChild(index)
}
}
}

@ -0,0 +1,23 @@
package wf.servitor.common.workflow
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import wf.servitor.common.utils.JacksonModule
object Jackson {
val yaml = ObjectMapper(YAMLFactory()).apply {
registerKotlinModule()
registerModule(JacksonModule)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
propertyNamingStrategy = PropertyNamingStrategy.LOWER_CAMEL_CASE
}
val json = ObjectMapper().apply {
registerKotlinModule()
registerModule(JacksonModule)
propertyNamingStrategy = PropertyNamingStrategy.LOWER_CAMEL_CASE
}
}

@ -0,0 +1,95 @@
package wf.servitor.common.workflow
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder
import wf.servitor.common.workflow.step.*
import java.io.Serializable
@Suppress("unused")
@JsonDeserialize(builder = Step.Builder::class)
interface Step : Serializable {
val name: String
fun getChild(index: Int): Step? {
return null
}
fun children() = 0
fun getScript(): String? = null
fun next(result: Any?): StepContinuation = StepContinuation.Continue
@JsonPOJOBuilder(buildMethodName = "build", withPrefix = "with")
class Builder {
var name: String = ""
var `if`: String? = null
var `do`: String? = null
var then: String? = null
var `else`: String? = null
var options: List<OptionsStep.Option>? = null
var collect: List<Step>? = null
fun withName(value: String) {
this.name = value
}
fun withIf(value: String) {
this.`if` = value
}
fun withDo(value: String) {
this.`do` = value
}
fun withThen(value: String) {
this.then = value
}
fun withElse(value: String) {
this.`else` = value
}
fun withOptions(value: List<OptionsStep.Option>) {
this.options = value
}
fun withCollect(value: List<Step>) {
this.collect = value
}
fun build(): Step {
val `if` = this.`if`
val `do` = this.`do`
if (`if` != null) {
if (`do` != null) {
return IfActionStep(`if`, `do`, name)
}
val then = then
val `else` = `else`
if (then != null || `else` != null) {
return IfJumpStep(`if`, then, `else`, name)
}
error("Step missing 'then', 'else' or 'do'")
}
val options = options
if (options != null) {
return OptionsStep(options, name)
}
val collect = collect
if (collect != null) {
return CollectStep(collect, name)
}
if (`do` != null) {
return ActionStep(`do`, name)
}
error("No matching type of step could be found")
}
}
}

@ -0,0 +1,9 @@
package wf.servitor.common.workflow
sealed class StepContinuation {
object Continue : StepContinuation()
object End : StepContinuation()
data class Flow(val name: String) : StepContinuation()
data class Step(val name: String) : StepContinuation()
}

@ -0,0 +1,11 @@
package wf.servitor.common.workflow
import java.io.Serializable
class Workflow(
val entry: Flow = Flow(),
val flows: MutableMap<String, Flow> = mutableMapOf(),
val services: MutableMap<String, Map<String, Any?>> = mutableMapOf()
) : Serializable {
fun getStep(flow: String, path: List<Int>): Step? = (if (flow == "entry") entry else flows.get(flow))?.getStep(path)
}

@ -0,0 +1,7 @@
package wf.servitor.common.workflow.step
import wf.servitor.common.workflow.Step
class ActionStep(val `do`: String, override val name: String) : Step {
override fun getScript() = `do`
}

@ -0,0 +1,5 @@
package wf.servitor.common.workflow.step
import wf.servitor.common.workflow.Step
class CollectStep(val collect: List<Step>, override val name: String) : Step

@ -0,0 +1,5 @@
package wf.servitor.common.workflow.step
import wf.servitor.common.workflow.Step
class IfActionStep(val `if`: String, val `do`: String, override val name: String = `if`) : Step

@ -0,0 +1,10 @@
package wf.servitor.common.workflow.step
import wf.servitor.common.workflow.Step
class IfJumpStep(val `if`: String, val then: String? = null, val `else`: String? = null, override val name: String = `if`) :
Step {
override fun getScript(): String? {
return `if`
}
}

@ -0,0 +1,7 @@
package wf.servitor.common.workflow.step
import wf.servitor.common.workflow.Step
class OptionsStep(val options: List<Option>, override val name: String) : Step {
class Option(val `when`: String, val `do`: String)
}

@ -0,0 +1,14 @@
plugins {
id 'org.jetbrains.kotlin.jvm'
id 'java'
}
def versions = project.ext.versions;
repositories {
mavenCentral()
}
dependencies {
implementation "org.apache.commons:commons-jexl3:${versions.jexl}"
}

@ -0,0 +1,189 @@
package wf.servitor.engine
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.sendBlocking
import kotlinx.coroutines.selects.whileSelect
import org.apache.activemq.artemis.api.core.client.ClientMessage
import org.apache.commons.jexl3.JexlEngine
import org.apache.commons.jexl3.JexlException
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.koin.core.qualifier.named
import wf.servitor.common.Event.*
import wf.servitor.common.event.Session
import wf.servitor.common.workflow.Step
import wf.servitor.common.workflow.StepContinuation
import wf.servitor.engine.dispatcher.NamespaceAwareMapContext
import wf.servitor.engine.exception.UnresolvedRemoteCallException
class Engine : KoinComponent {
private val jexl by inject<JexlEngine>()
private val session by inject<Session>(named("queue.task"))
private val channel = Channel<ClientMessage>(2)
private var cancel = Channel<Unit>()
suspend fun run() {
session.onMessage {
channel.sendBlocking(it)
}
whileSelect {
channel.onReceive {
val task: Task = session.extract(it)
println("Got task!")
println(task)
// TODO check tombstone store
executeTask(task)
true
}
cancel.onReceive {
false
}
}
session.close()
}
suspend fun stop() {
this.cancel.send(Unit)
}
private fun executeTask(task: Task) {
session.sendUpdate(TaskUpdate(task, "running"))
val workflow = task.workflow
val step = workflow.getStep(task.flow, task.path)
if (step === null) {
endTask(task)
return
}
val script = step.getScript()
if (script === null) {
session.sendUpdate(TaskUpdate(task, "done"))
nextTask(step, task)
return
}
try {
val (res, context) = this.execute(script, task)
session.sendUpdate(TaskUpdate(task.copy(context = context), "done"))
nextTask(step, task, context, res)
} catch (e: UnresolvedRemoteCallException) {
session.queueRelay(Relay(e.service, e.method, e.arguments, task))
return
} catch (e: Throwable) {
session.sendUpdate(TaskUpdate(task, "error"))
return
}
}
private fun nextTask(
step: Step,
task: Task,
context: Map<String, Any?> = task.context,
result: Any? = null
) {
when (val continuation = step.next(result)) {
StepContinuation.End -> {
val endedTask = task.copy(context = context, dispatchedValues = listOf())
endTask(endedTask)
}
StepContinuation.Continue -> {
val next: List<Int>? = run select@{
val newList = task.path.toMutableList()
if (newList.count() > 1) {
val tree = newList.indices.drop(1).map {
newList.subList(0, it) to task.workflow.getStep(task.flow, newList.subList(0, it - 1))
}.reversed()
for ((path, parent) in tree) {
if (parent == null) {
continue
}
path[path.lastIndex]++
if (parent.children() < path[path.lastIndex] && null != parent.getChild(path[path.lastIndex])) {
return@select path
}
}
}
val lastNext = listOf(newList.first() + 1)
if (task.workflow.getStep(task.flow, lastNext) !== null) {
lastNext
} else {
null
}
}
if (next === null) {
endTask(task, context)
} else {
session.queueTask(task.copy(context = context, path = next, dispatchedValues = listOf()))
}
}
is StepContinuation.Flow -> {
session.queueTask(Task(task.workflow, task.context, continuation.name))
}
is StepContinuation.Step -> {
val stepList: List<Step> = task.workflow.flows[task.flow]?.steps ?: listOf()
var found = false
for (item in 0..stepList.count()) {
if (stepList[item].name == continuation.name) {
session.queueTask(
task.copy(
context = context,
path = listOf(item),
dispatchedValues = listOf()
)
)
found = true
break
}
}
if (!found) {
session.sendUpdate(TaskUpdate(task, "error"))
TODO("Do correct error handling")
}
}
}
}
private fun endTask(
task: Task,
context: Map<String, Any?> = task.context
) {
session.sendUpdate(TaskUpdate(task.copy(dispatchedValues = listOf(), context = context), "ended"))
}
private fun execute(input: String, task: Task): Pair<Any?, Map<String, Any?>> {
try {
val mutableContext = task.context.toMutableMap()
val jc = NamespaceAwareMapContext(task.workflow, mutableContext, task.dispatchedValues.toMutableList())
val script = jexl.createScript(input)
return script.execute(jc) to mutableContext
} catch (j: JexlException) {
val cause = j.cause
if (cause is UnresolvedRemoteCallException) {
throw cause
}
throw j
}
}
}

@ -0,0 +1,8 @@
package wf.servitor.engine
import org.apache.commons.jexl3.JexlBuilder
import org.koin.dsl.module
val engineModule = module {
single { JexlBuilder().create() }
}

@ -0,0 +1,19 @@
package wf.servitor.engine
import kotlinx.coroutines.runBlocking
import org.koin.core.context.startKoin
import wf.servitor.common.commonModule
fun main(args: Array<String>) {
startKoin {
modules(commonModule)
modules(engineModule)
}
val engine = Engine()
runBlocking {
engine.run()
}
}

@ -0,0 +1,17 @@
package wf.servitor.engine.dispatcher
import org.apache.commons.jexl3.JexlContext
import org.apache.commons.jexl3.MapContext
import wf.servitor.common.workflow.Workflow
class NamespaceAwareMapContext(
private val workflow: Workflow,
map: Map<String, Any?>,
private val dispatchedValues: MutableList<Any?>
) : MapContext(map), JexlContext.NamespaceResolver {
override fun resolveNamespace(name: String): Any? {
workflow.services.get(name) ?: return null
return NamespaceFaker(name, dispatchedValues)
}
}

@ -0,0 +1,43 @@
package wf.servitor.engine.dispatcher
import org.apache.commons.jexl3.JexlContext
import org.apache.commons.jexl3.introspection.JexlMethod
import wf.servitor.engine.exception.UnresolvedRemoteCallException
class NamespaceFaker(
private val namespace: String,
private val dispatchedValues: MutableList<Any?>,
val nextValueIndex: Int = dispatchedValues.size
) : JexlContext {
override fun has(name: String) = true
override fun get(name: String): Any {
return CallExecutor {
if (dispatchedValues.isNotEmpty()) {
val first = dispatchedValues.first()
dispatchedValues.removeAt(0)
first
} else {
throw UnresolvedRemoteCallException(namespace, name, it.toList(), nextValueIndex)
}
}
}
override fun set(name: String, value: Any?) {
throw UnsupportedOperationException()
}
class CallExecutor(val dispatchedValue: (Array<out Any?>) -> Any?) : JexlMethod {
override fun tryInvoke(name: String?, obj: Any?, vararg params: Any?): Any {
TODO("not implemented")
}
override fun isCacheable() = false;
override fun getReturnType(): Class<*> = TODO()
override fun tryFailed(rval: Any?) = true
override fun invoke(obj: Any?, vararg params: Any?): Any? {
return dispatchedValue(params);
}
}
}

@ -0,0 +1,9 @@
package wf.servitor.engine.exception
class UnresolvedRemoteCallException(val service: String, val method: String, val arguments: List<Any?>, val index: Int) :
RuntimeException("Halted execution for external call", null, true, false) {
// Don't create a stack trace for this exception.
override fun fillInStackTrace(): Throwable {
return this
}
}

@ -0,0 +1,5 @@
package wf.servitor.engine
class EngineInABox {
}

@ -0,0 +1,123 @@
package wf.servitor.engine
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
import org.apache.activemq.artemis.api.core.QueueAttributes
import org.apache.activemq.artemis.api.core.RoutingType
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
import org.apache.activemq.artemis.api.core.client.ClientSession
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory
import org.apache.activemq.artemis.api.core.client.ServerLocator
import org.koin.core.context.startKoin
import org.koin.core.qualifier.named
import org.koin.dsl.module
import wf.servitor.common.Event
import wf.servitor.common.event.Session
import wf.servitor.common.workflow.Flow
import wf.servitor.common.workflow.Workflow
import wf.servitor.common.workflow.step.ActionStep
import kotlin.test.Test
class EngineTests {
fun workflow(): Workflow {
return Workflow(
Flow(
listOf(
ActionStep("sum = 1 + 1", "sum"),
ActionStep("sum += 4", "more-sum"),
ActionStep("sum += nice:plus(sum, 4)", "remote-sum")
)
),
services = mutableMapOf(
"nice" to mapOf()
)
)
}
@Test
fun testOk() {
val app = startKoin {
modules(module {
single { ActiveMQClient.createServerLocator("tcp://localhost:61616") }
single { get<ServerLocator>().createSessionFactory() }
single {
val serverLocator: ServerLocator = get()
get<ClientSessionFactory>().createSession(
"artemis",
"simetraehcapa",
false,
true,
true,
serverLocator.isPreAcknowledge,
serverLocator.ackBatchSize
)
}
single { get<ClientSession>().createProducer() }
single(named("queue.task")) {
val session: ClientSession = get()
println("Creating address")
session.createAddress(SimpleString("servitor.task"), RoutingType.ANYCAST, true)
println("Checking task queue")
if (!session.queueQuery(SimpleString("servitor.task.queue")).isExists) {
println("Creating task queue")
session.createQueue(
SimpleString("servitor.task"),
SimpleString("servitor.task.queue"),
true,
QueueAttributes().apply {
routingType = RoutingType.ANYCAST
durable = true
purgeOnNoConsumers = false
maxConsumers = ActiveMQDefaultConfiguration.getDefaultMaxQueueConsumers()
}
)
}
println("Checking observation queue")
if (!session.queueQuery(SimpleString("servitor.observe")).isExists) {
println("Creating observation queue")
session.createQueue(
SimpleString("servitor.#"),
SimpleString("servitor.observe"),
true,
QueueAttributes().apply {
routingType = RoutingType.ANYCAST
durable = true
purgeOnNoConsumers = false
maxConsumers = ActiveMQDefaultConfiguration.getDefaultMaxQueueConsumers()
}
)
}
Session(get(), "servitor.task.queue")
}
})
modules(engineModule)
}
val session: Session = app.koin.get(named("queue.task"))
val engine = Engine()
val engineJob = GlobalScope.launch {
engine.run()
}
runBlocking {
println("Queueing task")
session.queueTask(Event.Task(workflow()))
println("Send task")
delay(5000)
engine.stop()
engineJob.join()
}
}
}

@ -0,0 +1,11 @@
plugins {
id 'org.jetbrains.kotlin.jvm'
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
}

@ -0,0 +1 @@
kotlin.code.style=official

Binary file not shown.

@ -0,0 +1,6 @@
#Tue Jan 14 22:21:44 CET 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

172
gradlew vendored

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

@ -0,0 +1,12 @@
plugins {
id 'org.jetbrains.kotlin.jvm'
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
}

@ -0,0 +1,12 @@
plugins {
id 'org.jetbrains.kotlin.jvm'
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
}

@ -0,0 +1,5 @@
package wf.servitor.relay
fun main() {
}

@ -0,0 +1,2 @@
rootProject.name = 'servitor'
include 'common', 'engine', 'gateway', 'relay', 'monolith'
Loading…
Cancel
Save