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

Commit

Permalink
Merge pull request #242 from atomist/params
Browse files Browse the repository at this point in the history
Decorator TS parameters. New js rug instance per invocation #229
  • Loading branch information
johnsonr authored Jan 31, 2017
2 parents 01283f7 + 00fb436 commit 34aec7d
Show file tree
Hide file tree
Showing 14 changed files with 404 additions and 201 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added

- Support for @parameter TS class field decorators as per
https://github.com/atomist/rug/issues/229

- Support for a new TS (JS) Handler programming model as per
https://github.com/atomist/rug/issues/105

Expand All @@ -36,6 +39,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Changed

- We now create a new JS rug for each thread for safety.
https://github.com/atomist/rug/issues/78

- **BREAKING** all JS based Rugs must export (a la Common-JS) vars implementing
the associated interfaces. Previously we scanned for all top level vars.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class HandlerArchiveReader(
if (handlers.nonEmpty) {
handlers
} else {
JavaScriptHandlerFinder.fromJavaScriptArchive(rugArchive, new JavaScriptHandlerContext(teamId, treeMaterializer, messageBuilder), None)
JavaScriptHandlerFinder.fromJavaScriptArchive(rugArchive, new JavaScriptHandlerContext(teamId, treeMaterializer, messageBuilder))
}
}
}
110 changes: 50 additions & 60 deletions src/main/scala/com/atomist/rug/runtime/js/JavaScriptContext.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.atomist.rug.runtime.js

import java.util.regex.Pattern
import javax.script.{ScriptContext, ScriptException}
import javax.script._

import com.atomist.project.archive.{AtomistConfig, DefaultAtomistConfig}
import com.atomist.rug.{RugJavaScriptException, RugRuntimeException}
import com.atomist.rug.RugJavaScriptException
import com.atomist.source.ArtifactSource
import com.coveo.nashorn_modules.{AbstractFolder, Folder, Require}
import com.typesafe.scalalogging.LazyLogging
import jdk.nashorn.api.scripting.{ClassFilter, NashornScriptEngine, NashornScriptEngineFactory, ScriptObjectMirror}
import jdk.nashorn.api.scripting.{NashornScriptEngine, NashornScriptEngineFactory, ScriptObjectMirror}

import scala.collection.JavaConverters._

Expand All @@ -17,57 +17,65 @@ import scala.collection.JavaConverters._
* Creates a Nashorn ScriptEngineManager and can evaluate files and JavaScript fragments,
* exposing the known vars in a typesafe way so we partly avoid the horrific detyped
* Nashorn API.
*
* One of these per rug please, or else they may stomp on one-another
*/
class JavaScriptContext(allowedClasses: Set[String] = Set.empty[String], atomistConfig: AtomistConfig = DefaultAtomistConfig) extends LazyLogging {
class JavaScriptContext(rugAs: ArtifactSource,
atomistConfig: AtomistConfig = DefaultAtomistConfig,
bindings: Bindings = new SimpleBindings()) extends LazyLogging {

private val commonOptions = Array("--optimistic-types", "--language=es6")

/**
* At the time of writing, allowedClasses were only used for test.
*
* If you do need to expose some classes to JS, then make sure you configure to use a locked down classloader and security manager
*/
val engine: NashornScriptEngine =
new NashornScriptEngineFactory().getScriptEngine(
if (allowedClasses.isEmpty) commonOptions :+ "--no-java" else commonOptions,
if (allowedClasses.isEmpty) null else Thread.currentThread().getContextClassLoader, //TODO - do we need our own loader here?
new ClassFilter {
override def exposeToScripts(s: String): Boolean = {
allowedClasses.contains(s)
}
}
).asInstanceOf[NashornScriptEngine]

def load(rugAs: ArtifactSource) : Unit = {

configureEngine(engine, rugAs)
val filtered = atomistConfig.atomistContent(rugAs)
.filter(d => true,
f => atomistConfig.isJsSource(f))

//require all the atomist stuff
for (f <- filtered.allFiles) {
val varName = f.path.dropRight(3).replaceAll("/", "_").replaceAll("\\.", "\\$")
try {
engine.eval(s"exports.$varName = require('./${f.path.dropRight(3)}');") //because otherwise the loader doesn't know about the paths and can't resolve relative modules
} catch {
case x: ScriptException => throw new RugJavaScriptException(s"Error during eval of: ${f.path}",x)
case x: RuntimeException => x.getCause match {
case c: ScriptException => throw new RugJavaScriptException(s"Error during eval of: ${f.path}",c)
case c => throw x
}
new NashornScriptEngineFactory()
.getScriptEngine("--optimistic-types", "--language=es6", "--no-java")
.asInstanceOf[NashornScriptEngine]

engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE)

private val consoleJs =
"""
|console = {
| log: print,
| warn: print,
| error: print
|};
""".stripMargin

//so we can print stuff out from TS
engine.eval(consoleJs)

try {
Require.enable(engine, new ArtifactSourceBasedFolder(rugAs))
} catch {
case e: Exception =>
throw new RuntimeException("Unable to set up ArtifactSource based module loader", e)
}

private val filtered = atomistConfig.atomistContent(rugAs)
.filter(d => true,
f => atomistConfig.isJsSource(f))

//require all the atomist stuff
for (f <- filtered.allFiles) {
val varName = f.path.dropRight(3).replaceAll("/", "_").replaceAll("\\.", "\\$")
try {
engine.eval(s"exports.$varName = require('./${f.path.dropRight(3)}');") //because otherwise the loader doesn't know about the paths and can't resolve relative modules
} catch {
case x: ScriptException => throw new RugJavaScriptException(s"Error during eval of: ${f.path}", x)
case x: RuntimeException => x.getCause match {
case c: ScriptException => throw new RugJavaScriptException(s"Error during eval of: ${f.path}", c)
case c => throw x
}
}
}


/**
* Information about a JavaScript var exposed in the project scripts
*
* @param key name of the var
* @param scriptObjectMirror interface for working with Var
*/
case class Var(key: String, scriptObjectMirror: ScriptObjectMirror) {
}
case class Var(key: String, scriptObjectMirror: ScriptObjectMirror) {}

/**
* Return all the vars known to the engine that expose ScriptObjectMirror objects, with the key
Expand All @@ -93,25 +101,6 @@ class JavaScriptContext(allowedClasses: Set[String] = Set.empty[String], atomist
}).toSeq
}

private def configureEngine(scriptEngine: NashornScriptEngine, rugAs: ArtifactSource): Unit = {
//so we can print stuff out from TS
val consoleJs =
"""
|console = {
| log: print,
| warn: print,
| error: print
|};
""".stripMargin
scriptEngine.eval(consoleJs)
try{
Require.enable(engine, new ArtifactSourceBasedFolder(rugAs))
}catch {
case e: Exception =>
throw new RuntimeException("Unable to set up ArtifactSource based module loader", e)
}
}

private class ArtifactSourceBasedFolder private(val artifacts: ArtifactSource, val parent: Folder, val path: String) extends AbstractFolder(parent, path) {

private val commentPattern: Pattern = Pattern.compile("^//.*$", Pattern.MULTILINE)
Expand All @@ -133,4 +122,5 @@ class JavaScriptContext(allowedClasses: Set[String] = Set.empty[String], atomist
new ArtifactSourceBasedFolder(artifacts, this, getPath + s + "/")
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.atomist.rug.runtime.js

import javax.script.SimpleBindings

import com.atomist.event.SystemEventHandler
import com.atomist.param.Tag
import com.atomist.project.archive.{AtomistConfig, DefaultAtomistConfig}
Expand All @@ -16,7 +18,7 @@ import scala.util.Try
object JavaScriptHandlerFinder {

/**
* Find and handlers operations in the given Rug archive
* Find handler operations in the given Rug archive
*
* @param rugAs archive to look into
* @param atomist facade to Atomist
Expand All @@ -26,24 +28,18 @@ object JavaScriptHandlerFinder {
def registerHandlers(rugAs: ArtifactSource,
atomist: AtomistFacade,
atomistConfig: AtomistConfig = DefaultAtomistConfig): Unit = {
val jsc = new JavaScriptContext()

//TODO - remove this when new Handler model put in
jsc.engine.put("atomist", atomist)
jsc.load(rugAs)
val bindings = new SimpleBindings()
bindings.put("atomist", atomist)
new JavaScriptContext(rugAs,atomistConfig,bindings)
}

def fromJavaScriptArchive(rugAs: ArtifactSource,
ctx: JavaScriptHandlerContext,
context: Option[JavaScriptContext]): Seq[SystemEventHandler] = {
ctx: JavaScriptHandlerContext): Seq[SystemEventHandler] = {

val jsc: JavaScriptContext =
if (context.isEmpty)
new JavaScriptContext()
else
context.get
val jsc = new JavaScriptContext(rugAs)

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

Expand Down
Loading

0 comments on commit 34aec7d

Please sign in to comment.