-
Notifications
You must be signed in to change notification settings - Fork 10
Scala plugin development guide
Plugin context is implemented as as spring configuration bean
Plugin context should be annotated with @GenesisPlugin with mandatory id field.
It's main responsibility is to provide step coordination factories and step builder factories to genesis workflow context.
It's advised for plugin context not to export other types of beans to global genesis spring context
@Configuration
@GenesisPlugin(id = "ping", description = "simple ping plugin")
class PingPluginContex {
}
To introduce new steps definition to genesis template dsl one should provide:
- new step class marked with com.griddynamics.genesis.workflow.Step trait - step definition to be processed by appropriate executors
- custom com.griddynamics.genesis.plugin.StepBuilder trait implementation - the class is respobsible for collecting attribute values from template and creating Step definition.
- custom com.griddynamics.genesis.plugin.StepBuilderFactory trait implementaion - the class responsible for connecting StepBuilder with step name representation in template dsl
Example:
Lets say you want to introduce new ping step with one attibute url:
ping {
url = "http://google.com"
}
To support this syntax the followin code is needed:
case class PingStep(url: String) extends Step {
override val stepDescription = "Ping signal"
}
class PingStepBuilderFactory extends com.griddynamics.genesis.plugin.StepBuilderFactory {
val stepName = "ping" //the value should be exactly the same as it's expected to be in template
override def newStepBuilder = new StepBuilder {
@BeanProperty var url : String = _ //the name of the variable should be exactly the same as the name of the parameter in template
def getDetails = new PingStep(url)
}
}
In order for genesis workflow system to support this new step PingStepBuilderFactory should be exported in plugin context
@Configuration
@GenesisPlugin(id = "ping", description = "simple ping plugin")
class PingPluginContex {
@Bean def stepBuilderFactory = new PingStepBuilderFactory
}
In case of step builder doesn't having some conversion logic plugin developer can use com.griddynamics.genesis.plugin.StepDefinition
class to declare new step without providing new custom StepBuilderFactory and StepBuilder
@Configuration
@GenesisPlugin(id = "ping", description = "simple ping plugin")
class PingPluginContex {
@Bean def pingStepDefinition = new StepDefinition("ping", classOf[PingStep])
}
In this case genesis core is capable of instantion of PingStep based on constructor arguments.
Following basic convertions from groovy to scala is supported:
- value/null (or absense of parameter) to Some(_)/None
- groovy list(java.lang.List) to scala Seq, Set, List
- groovy map(java.lang.Map) to scala.Map
Case class default constructor parameters is also supported.
More complex example
Action is the tiniest peace of work workflow engine can execute. Each workflow step can consist of several actions.
class PingAction(url: String) extends Action
Actions have no representations in template dsl. Information of how to execute each action is coded in action executor.
Information how to split step into actions and coordinate action executors is coded in step coordinator
Actions are data objects, to provide appropriate handling of each action one should provide specific action executor. Genesis provide several trait that should be used to implement asynchronous or synchronous executors:
- ActionExecutor - base executor class
- AsyncActionExecutor - for async execution
- SyncActionExecutor - for sync execution
class PingResult(val success: Boolean) extends ActionResult
class PingActionExecutor(val action: PingAction) SyncActionExecutor extends ActionExecutor {
def startSync() = {
try {
new Url(action.url).openConnection()
new PingResult(true)
} catch {
case _ => new PingResult(false)
}
}
}
To coordinate execution of several actions while executing particular step one should implement specific com.griddynamics.genesis.workflow.StepCoordinator
Genesis provide several template classes for different variations, like ActionOrientedStepCoordinator.
Because in this sample step consist of only one action coordinator looks simplified
class PingStepCoordinator(val step: PingStep, context: StepExecutionContext) extends ActionOrientedStepCoordinator {
var failed = false;
def getActionExecutor(action: Action) = action match {
case a: PingAction => new PingActionExecutor(a) // in case of step consisting of several actions, several matches will be here
}
def getStepResult = {
GenesisStepResult(context.step, isStepFailed = failed)
}
def onActionFinish(result: ActionResult) = {
result match {
case a: PingResult => {
failed = !a.success
Seq()
}
case _: ExecutorThrowable => {
failed = true
Seq()
}
case _: ExecutorInterrupt => {
Seq()
}
}
}
def onStepInterrupt(signal: Signal) = Seq()
def onStepStart() = List(new PingAction(step.url)) // list of actions to be executed to finish step. only one action in our case
}
The last peace of the puzzle is a factory class that should map specific step(constructed from template) to appropriate step coordinator.
To provide such class one should implement com.griddynamics.genesis.plugin.PartialStepCoordinatorFactoryor use one of template classes.
class PingStepCoordinatorFactory extends PartialStepCoordinatorFactory {
def isDefinedAt(step: Step) = step.isInstanceOf[PingStep]
def apply(step: Step, context: StepExecutionContext) = {
new PingStepCoordinator(step.asInstanceOf[PingStep], context)
}
}
This implementation should be exported by plugin context
@Configuration
@GenesisPlugin(id = "ping", description = "simple ping plugin")
class PingPluginContex {
@Bean def stepBuilderFactory = new PingStepBuilderFactory
@Bean def stepCoordinator = new PingStepCoordinatorFactory()
}
Plugin can use genesis main configuration subsystem. To get access to it plugin must conform to following rules:
- All properties must start with
genesis.plugin.<plugin_id>.
prefix - Plugin should provid genesis-plugin.properties in package root directory with all properties with default values
If the plugin have no mean of runtime reconfiguration it's possible to use spring @Value
annotation for easy configuration properties access
@Value("${genesis.plugin.ping.timeout}") var timeout: Int = _
Genesis provide several services that can be discovered and used by plugins. The definition of services can be found in internal-api module in com.griddynamics.genesis.service package.
Discovery of the services is implemented via @Autowired
spring feature
@Autowired var sshService: SshService = _