Skip to content
This repository has been archived by the owner on Mar 19, 2019. It is now read-only.

Commit

Permalink
Handler JS/TS programming model as per #105
Browse files Browse the repository at this point in the history
  • Loading branch information
kipz committed Jan 18, 2017
1 parent 53bcd61 commit bf3cdd3
Show file tree
Hide file tree
Showing 19 changed files with 508 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.atomist.rug.kind.DefaultTypeRegistry
import com.atomist.rug.kind.dynamic.{DefaultViewFinder, ViewFinder}
import com.atomist.rug.kind.service.MessageBuilder
import com.atomist.rug.runtime.js.JavaScriptHandlerFinder
import com.atomist.rug.runtime.js.interop.ModelBackedAtomistFacade
import com.atomist.rug.runtime.js.interop.{JavaScriptHandlerContext, ModelBackedAtomistFacade}
import com.atomist.rug.runtime.rugdsl.DefaultEvaluator
import com.atomist.rug.spi.TypeRegistry
import com.atomist.source.ArtifactSource
Expand Down Expand Up @@ -38,7 +38,12 @@ class HandlerArchiveReader(
messageBuilder: MessageBuilder): Seq[SystemEventHandler] = {
val atomist = new ModelBackedAtomistFacade(teamId, messageBuilder, treeMaterializer)
JavaScriptHandlerFinder.registerHandlers(rugArchive, atomist)
atomist.handlers
val handlers = atomist.handlers
if(handlers.nonEmpty){
handlers
}else{
JavaScriptHandlerFinder.fromJavaScriptArchive(rugArchive, new JavaScriptHandlerContext(teamId,treeMaterializer, messageBuilder))
}
}
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.atomist.rug.kind.service

import java.util
import java.util.Collections

import com.atomist.tree.TreeNode
Expand Down Expand Up @@ -27,7 +28,7 @@ case class ImmutableMessage(
node: TreeNode = null,
message: String = null,
address: String = null,
actions: java.util.List[Action] = Collections.emptyList())
actions: java.util.List[Action] = new util.ArrayList[Action]())
extends Message {

// We use null for interop and JSON
Expand Down
4 changes: 1 addition & 3 deletions src/main/scala/com/atomist/rug/kind/service/message.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.atomist.rug.kind.service

import java.util

import com.atomist.param.{ParameterValue, SimpleParameterValue}
import com.atomist.param.{ParameterValue}
import com.atomist.tree.TreeNode

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.atomist.rug.kind.service.{ServiceSource, ServicesMutableView}
import com.atomist.rug.runtime.js.interop.{ContextMatch, jsPathExpressionEngine}
import com.atomist.source.ArtifactSource
import com.atomist.tree.content.text.SimpleMutableContainerTreeNode
import com.atomist.tree.pathexpression.{NamedNodeTest, PathExpressionParser}
import com.atomist.tree.pathexpression.{NamedNodeTest, PathExpression, PathExpressionParser}
import jdk.nashorn.api.scripting.ScriptObjectMirror

class JavaScriptEventHandler(
Expand All @@ -27,7 +27,7 @@ class JavaScriptEventHandler(

override def description: String = name

val pathExpression = PathExpressionParser.parsePathExpression(pathExpressionStr)
val pathExpression: PathExpression = PathExpressionParser.parsePathExpression(pathExpressionStr)

override val rootNodeName: String = pathExpression.locationSteps.head.test match {
case nnt: NamedNodeTest => nnt.name
Expand All @@ -36,7 +36,7 @@ class JavaScriptEventHandler(

import com.atomist.tree.pathexpression.ExpressionEngine.NodePreparer

private def nodePreparer(hc: HandlerContext): NodePreparer = {
protected def nodePreparer(hc: HandlerContext): NodePreparer = {
case mca: ModelContextAware =>
mca.setContext(hc)
mca
Expand All @@ -53,29 +53,20 @@ class JavaScriptEventHandler(
val root = new SimpleMutableContainerTreeNode("root", Seq(targetNode), null, null)
pexe.ee.evaluate(root, pathExpression, DefaultTypeRegistry, Some(np)) match {
case Right(Nil) =>
println("Nothing to do: No nodes found")
case Right(matches) =>
val cm = ContextMatch(
targetNode,
//matches.map(m => m.asInstanceOf[Object]).asJava,
pexe.wrap(matches),
s2,
teamId = e.teamId)
invokeHandlerFunction(e, cm)
//as.persist()
case Left(failure) =>
throw new RugRuntimeException(pathExpressionStr,
s"Error evaluating path expression $pathExpression: [$failure]")
}
}

protected def invokeHandlerFunction(e: SystemEvent, cm: ContextMatch): Unit = {
//val context = new ResultsMutableView(rugAs, hc.servicesMutableView, as)
val args = Seq(cm)

// Signature is
// on<R,N>(pathExpression: string, handler: (m: ContextMatch<R,N>) => void): void

handlerFunction.call("apply", args:_*)
protected def invokeHandlerFunction(e: SystemEvent, cm: ContextMatch): Object = {
handlerFunction.call("apply", cm)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package com.atomist.rug.runtime.js

import com.atomist.event.SystemEventHandler
import com.atomist.param.Tag
import com.atomist.plan.TreeMaterializer
import com.atomist.project.archive.{AtomistConfig, DefaultAtomistConfig}
import com.atomist.rug.runtime.js.interop.AtomistFacade
import com.atomist.rug.kind.service.TeamContext
import com.atomist.rug.runtime.js.interop._
import com.atomist.source.ArtifactSource
import com.atomist.tree.pathexpression.PathExpressionEngine
import jdk.nashorn.api.scripting.ScriptObjectMirror

import scala.util.Try
import scala.collection.JavaConverters._


/**
* Finds and evaluates handlers in a Rug archive.
Expand All @@ -15,7 +25,7 @@ object JavaScriptHandlerFinder {
* @param rugAs archive to look into
* @param atomist facade to Atomist
* @return a sequence of instantiated operations backed by JavaScript
*
*/
def registerHandlers(rugAs: ArtifactSource,
atomist: AtomistFacade,
Expand All @@ -26,4 +36,52 @@ object JavaScriptHandlerFinder {
jsc.engine.put("atomist", atomist)
jsc.load(rugAs)
}

def fromJavaScriptArchive(rugAs: ArtifactSource,
ctx: JavaScriptHandlerContext,
context: JavaScriptContext = null): Seq[SystemEventHandler] = {
val jsc: JavaScriptContext =
if (context == null)
new JavaScriptContext()
else
context

jsc.load(rugAs)
handlersFromVars(rugAs, jsc, ctx)
}

private def handlersFromVars(rugAs: ArtifactSource, jsc: JavaScriptContext, ctx: JavaScriptHandlerContext): Seq[SystemEventHandler] = {
jsc.vars.foldLeft(Seq[SystemEventHandler]())((acc: Seq[SystemEventHandler], jsVar) => {
val obj = jsVar.scriptObjectMirror
val name = obj.getMember("name").asInstanceOf[String]
val description = obj.getMember("description").asInstanceOf[String]
val handle = obj.getMember("handle").asInstanceOf[ScriptObjectMirror]
val expression = obj.getMember("expression") match {
case x: String => x
case o: ScriptObjectMirror => o.getMember("expression").asInstanceOf[String]
case _ => Nil.asInstanceOf[String]
}

val tags = readTagsFromMetadata(obj)
if(name != null && description != null && handle != null && expression != null){
acc :+ new NamedJavaScriptEventHandler(expression, handle, obj, rugAs, ctx, name, description,tags)
}else{
acc
}
})
}

protected def readTagsFromMetadata(someVar: ScriptObjectMirror): Seq[Tag] = {
Try {
someVar.getMember("tags") match {
case som: ScriptObjectMirror =>
val stringValues = som.values().asScala collect {
case s: String => s
}
stringValues.map(s => Tag(s, s)).toSeq
case _ => Nil
}
}.getOrElse(Nil)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,14 @@ object JavaScriptOperationFinder {
*/
def fromJavaScriptArchive(rugAs: ArtifactSource,
context: JavaScriptContext = null): Seq[ProjectOperation] = {

val jsc: JavaScriptContext =
if (context == null)
new JavaScriptContext()
else
context

jsc.load(rugAs)
val operations = operationsFromVars(rugAs, jsc)
operations
operationsFromVars(rugAs, jsc)
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.atomist.rug.runtime.js.interop

import com.atomist.plan.TreeMaterializer
import com.atomist.rug.kind.service.{MessageBuilder, TeamContext}
import com.atomist.tree.pathexpression.PathExpressionEngine

class JavaScriptHandlerContext(val teamId: String,
_treeMaterializer: TreeMaterializer,
_messageBuilder: MessageBuilder)

extends UserModelContext with TeamContext {

val pathExpressionEngine = new jsPathExpressionEngine(teamContext = this, ee = new PathExpressionEngine)

val messageBuilder: MessageBuilder = _messageBuilder

override val treeMaterializer: TreeMaterializer = _treeMaterializer

override def registry: Map[String, Object] = Map(
"PathExpressionEngine" -> pathExpressionEngine
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.atomist.rug.runtime.js.interop

import com.atomist.event.{HandlerContext, SystemEvent}
import com.atomist.param.Tag
import com.atomist.rug.RugRuntimeException
import com.atomist.rug.kind.DefaultTypeRegistry
import com.atomist.rug.kind.service._
import com.atomist.rug.runtime.js.JavaScriptEventHandler
import com.atomist.source.ArtifactSource
import com.atomist.tree.TreeNode
import com.atomist.tree.content.text.SimpleMutableContainerTreeNode
import jdk.nashorn.api.scripting.ScriptObjectMirror

import scala.collection.JavaConverters._

/**
* Like super, except that we require a proper name, description, tags etc.
* and we wrap the match in an Event
*/
class NamedJavaScriptEventHandler(pathExpressionStr: String,
handlerFunction: ScriptObjectMirror,
thiz: ScriptObjectMirror,
rugAs: ArtifactSource,
ctx: JavaScriptHandlerContext,
_name: String,
_description: String,
_tags: Seq[Tag] = Nil)
extends JavaScriptEventHandler(pathExpressionStr, handlerFunction, rugAs, ctx.treeMaterializer, ctx.pathExpressionEngine) {

override def name: String = _name

override def tags: Seq[Tag] = _tags

override def description: String = _description

override def handle(e: SystemEvent, s2: ServiceSource): Unit = {
val smv = new ServicesMutableView(rugAs, s2)
val handlerContext = HandlerContext(smv)
val np = nodePreparer(handlerContext)

val targetNode = ctx.treeMaterializer.rootNodeFor(e, pathExpression)
// Put a new artificial root above to make expression work
val root = new SimpleMutableContainerTreeNode("root", Seq(targetNode), null, null)
ctx.pathExpressionEngine.ee.evaluate(root, pathExpression, DefaultTypeRegistry, Some(np)) match {
case Right(Nil) =>
case Right(matches) =>
val cm = ContextMatch(
targetNode,
ctx.pathExpressionEngine.wrap(matches),
s2,
teamId = e.teamId)
dispatch(invokeHandlerFunction(e, cm))

case Left(failure) =>
throw new RugRuntimeException(pathExpressionStr,
s"Error evaluating path expression $pathExpression: [$failure]")
}
}

override protected def invokeHandlerFunction(e: SystemEvent, cm: ContextMatch): Object = {
handlerFunction.call(thiz, Event(cm))
}

/**
* Extract all messages and use messageBuilder/actionRegistry to find and dispatch actions
* @param plan
*/
def dispatch(plan: Object): Unit = {
plan match {
case o: ScriptObjectMirror => {
o.get("messages") match {
case messages: ScriptObjectMirror if messages.isArray => {
messages.values().asScala.foreach(msg => {
val m = msg.asInstanceOf[ScriptObjectMirror]
var responseMessage = ctx.messageBuilder.regarding(m.get("regarding").asInstanceOf[TreeNode])
responseMessage =m.get("text") match {
case text: String => responseMessage.say(text)
case _ => responseMessage
}
responseMessage = m.get("channelId") match {
case c: String => responseMessage.on(c)
case _ => responseMessage
}
responseMessage = m.get("rugs") match {
case rugs: ScriptObjectMirror if rugs.isArray => {
addActions(responseMessage, rugs)
}
case _ => responseMessage
}
responseMessage.send()
})
}
}
}
}
}

/**
* Extract action from JS and bind to Message
*
* Beware use of var!
*
* @param msg current message
* @param rugs array of Rugs
* @return new message
*/
def addActions(msg: Message, rugs: ScriptObjectMirror) : Message = {
var responseMessage = msg
for (rug <- rugs.values().asScala) {
val r = rug.asInstanceOf[ScriptObjectMirror]
//TODO - this is a reimplementation of the @cd's label hack - but at least it's not in TS
val actionName = r.get("label") match {
case label: String => s"${r.get("name").asInstanceOf[String]}|$label"
case _ => r.get("name").asInstanceOf[String]
}
var action = responseMessage.actionRegistry.findByName(actionName)
r.get("params") match {
case params: ScriptObjectMirror => {
for (param <- params.entrySet().asScala) {
action = responseMessage.actionRegistry.bindParameter(action, param.getKey, param.getValue)
}
}
case _ =>
}
responseMessage = msg.withAction(action)
}
responseMessage
}
}


/**
* Represents an event that drives a handler
*
* @param cm the root node in the tree
*/
case class Event(cm: ContextMatch) {
def child: TreeNode = cm.root.asInstanceOf[TreeNode]
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import scala.collection.JavaConverters._

object RugOperationSupport {

val YmlFormat = DateTimeFormatter.ofPattern("MMM d yyyy")
val YmlFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("MMM d yyyy")

// May have been passed in via the infrastructure but couldn't be declared in Rug: Suppress
// so it doesn't upset binding into JavaScript
Expand Down
Loading

0 comments on commit bf3cdd3

Please sign in to comment.