diff --git a/project/build.scala b/project/build.scala index 37ec82c..6418f50 100644 --- a/project/build.scala +++ b/project/build.scala @@ -40,7 +40,6 @@ "org.apache.httpcomponents" % "httpclient" % "4.3", "org.apache.sshd" % "apache-sshd" % "0.11.0", "com.typesafe.slick" %% "slick" % "2.1.0-RC3", - "org.mozilla" % "rhino" % "1.7R4", "com.novell.ldap" % "jldap" % "2009-10-07", "org.quartz-scheduler" % "quartz" % "2.2.1", "com.h2database" % "h2" % "1.4.180", diff --git a/src/main/scala/app/SystemSettingsController.scala b/src/main/scala/app/SystemSettingsController.scala index 72b71d2..82aabbd 100644 --- a/src/main/scala/app/SystemSettingsController.scala +++ b/src/main/scala/app/SystemSettingsController.scala @@ -111,15 +111,15 @@ redirect("/admin/plugins") }) -// get("/admin/plugins/console")(adminOnly { -// admin.plugins.html.console() -// }) -// -// post("/admin/plugins/console")(adminOnly { -// val script = request.getParameter("script") -// val result = plugin.JavaScriptPlugin.evaluateJavaScript(script) -// Ok(result) -// }) + get("/admin/plugins/console")(adminOnly { + admin.plugins.html.console() + }) + + post("/admin/plugins/console")(adminOnly { + val script = request.getParameter("script") + val result = plugin.ScalaPlugin.eval(script) + Ok() + }) // TODO Move these methods to PluginSystem or Service? private def deletePlugins(pluginIds: List[String]): Unit = { diff --git a/src/main/scala/plugin/JavaScriptPlugin.scala b/src/main/scala/plugin/JavaScriptPlugin.scala deleted file mode 100644 index adaca5e..0000000 --- a/src/main/scala/plugin/JavaScriptPlugin.scala +++ /dev/null @@ -1,117 +0,0 @@ -package plugin - -import org.mozilla.javascript.{Context => JsContext} -import org.mozilla.javascript.{Function => JsFunction} -import scala.collection.mutable.ListBuffer -import plugin.PluginSystem._ -import util.ControlUtil._ -import plugin.PluginSystem.GlobalMenu -import plugin.PluginSystem.RepositoryAction -import plugin.PluginSystem.Action -import plugin.PluginSystem.RepositoryMenu - -class JavaScriptPlugin(val id: String, val version: String, - val author: String, val url: String, val description: String) extends Plugin { - - private val repositoryMenuList = ListBuffer[RepositoryMenu]() - private val globalMenuList = ListBuffer[GlobalMenu]() - private val repositoryActionList = ListBuffer[RepositoryAction]() - private val globalActionList = ListBuffer[Action]() - - def repositoryMenus : List[RepositoryMenu] = repositoryMenuList.toList - def globalMenus : List[GlobalMenu] = globalMenuList.toList - def repositoryActions : List[RepositoryAction] = repositoryActionList.toList - def globalActions : List[Action] = globalActionList.toList - - def addRepositoryMenu(label: String, name: String, url: String, icon: String, condition: JsFunction): Unit = { - repositoryMenuList += RepositoryMenu(label, name, url, icon, (context) => { - val context = JsContext.enter() - try { - condition.call(context, condition, condition, Array(context)).asInstanceOf[Boolean] - } finally { - JsContext.exit() - } - }) - } - - def addGlobalMenu(label: String, url: String, icon: String, condition: JsFunction): Unit = { - globalMenuList += GlobalMenu(label, url, icon, (context) => { - val context = JsContext.enter() - try { - condition.call(context, condition, condition, Array(context)).asInstanceOf[Boolean] - } finally { - JsContext.exit() - } - }) - } - - def addGlobalAction(path: String, function: JsFunction): Unit = { - globalActionList += Action(path, (request, response) => { - val context = JsContext.enter() - try { - function.call(context, function, function, Array(request, response)) - } finally { - JsContext.exit() - } - }) - } - - def addRepositoryAction(path: String, function: JsFunction): Unit = { - repositoryActionList += RepositoryAction(path, (request, response, repository) => { - val context = JsContext.enter() - try { - function.call(context, function, function, Array(request, response, repository)) - } finally { - JsContext.exit() - } - }) - } - - object db { - // TODO Use JavaScript Map instead of java.util.Map - def select(sql: String): Array[java.util.Map[String, String]] = { - defining(PluginConnectionHolder.threadLocal.get){ conn => - using(conn.prepareStatement(sql)){ stmt => - using(stmt.executeQuery()){ rs => - val list = new java.util.ArrayList[java.util.Map[String, String]]() - while(rs.next){ - defining(rs.getMetaData){ meta => - val map = new java.util.HashMap[String, String]() - Range(1, meta.getColumnCount).map { i => - val name = meta.getColumnName(i) - map.put(name, rs.getString(name)) - } - list.add(map) - } - } - list.toArray(new Array[java.util.Map[String, String]](list.size)) - } - } - } - } - } - -} - -object JavaScriptPlugin { - - def define(id: String, version: String, author: String, url: String, description: String) - = new JavaScriptPlugin(id, version, author, url, description) - - def evaluateJavaScript(script: String, vars: Map[String, Any] = Map.empty): Any = { - val context = JsContext.enter() - try { - val scope = context.initStandardObjects() - scope.put("PluginSystem", scope, PluginSystem) - scope.put("JavaScriptPlugin", scope, this) - vars.foreach { case (key, value) => - scope.put(key, scope, value) - } - val result = context.evaluateString(scope, script, "", 1, null) - result - } finally { - JsContext.exit - } - } - -} \ No newline at end of file diff --git a/src/main/scala/plugin/PluginSystem.scala b/src/main/scala/plugin/PluginSystem.scala index 6c96ac9..6ba432b 100644 --- a/src/main/scala/plugin/PluginSystem.scala +++ b/src/main/scala/plugin/PluginSystem.scala @@ -10,6 +10,9 @@ import util.JGitUtil import org.eclipse.jgit.api.Git import service.RepositoryService.RepositoryInfo +import scala.reflect.runtime.currentMirror +import scala.tools.reflect.ToolBox + /** * Provides extension points to plug-ins. @@ -54,25 +57,26 @@ // TODO Method name seems to not so good. def installPlugin(id: String): Unit = { val pluginDir = new java.io.File(PluginHome) - val javaScriptFile = new java.io.File(pluginDir, id + "/plugin.js") - if(javaScriptFile.exists && javaScriptFile.isFile){ + val scalaFile = new java.io.File(pluginDir, id + "/plugin.scala") + if(scalaFile.exists && scalaFile.isFile){ val properties = new java.util.Properties() using(new java.io.FileInputStream(new java.io.File(pluginDir, id + "/plugin.properties"))){ in => properties.load(in) } - val script = FileUtils.readFileToString(javaScriptFile, "UTF-8") + val source = s""" + |val id = "${properties.getProperty("id")}" + |val version = "${properties.getProperty("version")}" + |val author = "${properties.getProperty("author")}" + |val url = "${properties.getProperty("url")}" + |val description = "${properties.getProperty("description")}" + """.stripMargin + FileUtils.readFileToString(scalaFile, "UTF-8") + try { - JavaScriptPlugin.evaluateJavaScript(script, Map( - "id" -> properties.getProperty("id"), - "version" -> properties.getProperty("version"), - "author" -> properties.getProperty("author"), - "url" -> properties.getProperty("url"), - "description" -> properties.getProperty("description") - )) + ScalaPlugin.eval(source) } catch { - case e: Exception => logger.warn(s"Error in plugin loading for ${javaScriptFile.getAbsolutePath}", e) + case e: Exception => logger.warn(s"Error in plugin loading for ${scalaFile.getAbsolutePath}", e) } } } @@ -109,17 +113,6 @@ } } - // TODO This is a test -// addGlobalMenu("Google", "http://www.google.co.jp/", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAEvwAABL8BkeKJvAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIgSURBVEiJtdZNiI1hFAfw36ORhSFFPgYLszOKJAsWRLGzks1gYyFZKFs7C7K2Y2XDRiwmq9kIJWQjJR9Tk48xRtTIRwjH4p473nm99yLNqdNTz/mf//+555x7ektEmEmbNaPs6OkUKKX0YBmWp6/IE8bwIs8xjEfEt0aiiJBl6sEuXMRLfEf8pX/PnIvJ0TPFWxE4+w+Ef/Kzbd5qDx5l8H8tkku7LG17gH7sxWatevdhEUoXsjda5RnDTZzH6jagtMe0lHIa23AJw3iOiSRZlmJ9mfcyfTzFl2AldmI3rkbEkbrAYKrX7S1eVRyWVnxhQ87eiLjQ+o2/mtyve+PuYy3W4+EfsP2/TVGKTHRI+Iz9Fdx8XOmAnZjGWRMYqoF/4ESW4hpOYk1iZ2WsLjDUTeBYBfgeuyux2XiNT5hXud+DD5W8Y90EtifoSfultfjx7MVtrKzcr8No5m7vJtCLx1hQJ8/4IZzClpyoy5ibsYUYQW81Z9o2jYgPeKr15+poEXE9+1XF9WIkOaasaV2P4k4pZUdDbEm+VEQcjIgtEfGxlLIVd/Gs6TX1MhzQquU3HK1t23f4IsuS94fxNXMO/MbXIDBg+tidw5yMbcCmylSdqWEH/kagYLKWeAt9Fcxi3KhhJuXq6SqQBMO15NDalvswmLWux4cbuToIbMS9BpJOfg8bm7imtmmTlVJWaa3hpnU9nufziBjtyDHTny0/AaA7Qnb4AM4aAAAAAElFTkSuQmCC") -// { context => context.loginAccount.isDefined } -// -// addRepositoryMenu("Board", "board", "/board", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAEvwAABL8BkeKJvAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIgSURBVEiJtdZNiI1hFAfw36ORhSFFPgYLszOKJAsWRLGzks1gYyFZKFs7C7K2Y2XDRiwmq9kIJWQjJR9Tk48xRtTIRwjH4p473nm99yLNqdNTz/mf//+555x7ektEmEmbNaPs6OkUKKX0YBmWp6/IE8bwIs8xjEfEt0aiiJBl6sEuXMRLfEf8pX/PnIvJ0TPFWxE4+w+Ef/Kzbd5qDx5l8H8tkku7LG17gH7sxWatevdhEUoXsjda5RnDTZzH6jagtMe0lHIa23AJw3iOiSRZlmJ9mfcyfTzFl2AldmI3rkbEkbrAYKrX7S1eVRyWVnxhQ87eiLjQ+o2/mtyve+PuYy3W4+EfsP2/TVGKTHRI+Iz9Fdx8XOmAnZjGWRMYqoF/4ESW4hpOYk1iZ2WsLjDUTeBYBfgeuyux2XiNT5hXud+DD5W8Y90EtifoSfultfjx7MVtrKzcr8No5m7vJtCLx1hQJ8/4IZzClpyoy5ibsYUYQW81Z9o2jYgPeKr15+poEXE9+1XF9WIkOaasaV2P4k4pZUdDbEm+VEQcjIgtEfGxlLIVd/Gs6TX1MhzQquU3HK1t23f4IsuS94fxNXMO/MbXIDBg+tidw5yMbcCmylSdqWEH/kagYLKWeAt9Fcxi3KhhJuXq6SqQBMO15NDalvswmLWux4cbuToIbMS9BpJOfg8bm7imtmmTlVJWaa3hpnU9nufziBjtyDHTny0/AaA7Qnb4AM4aAAAAAElFTkSuQmCC") -// { context => true} -// -// addGlobalAction("/hello"){ (request, response) => -// "Hello World!" -// } - } diff --git a/src/main/scala/plugin/ScalaPlugin.scala b/src/main/scala/plugin/ScalaPlugin.scala index e9ccc3b..af43d94 100644 --- a/src/main/scala/plugin/ScalaPlugin.scala +++ b/src/main/scala/plugin/ScalaPlugin.scala @@ -1,10 +1,15 @@ package plugin -import app.Context import scala.collection.mutable.ListBuffer -import plugin.PluginSystem._ import javax.servlet.http.{HttpServletResponse, HttpServletRequest} +import plugin.PluginSystem.GlobalMenu +import plugin.PluginSystem.Action +import plugin.PluginSystem.RepositoryAction +import app.Context +import plugin.PluginSystem.RepositoryMenu import service.RepositoryService.RepositoryInfo +import scala.reflect.runtime.currentMirror +import scala.tools.reflect.ToolBox // TODO This is a sample implementation for Scala based plug-ins. class ScalaPlugin(val id: String, val version: String, @@ -37,3 +42,16 @@ } } + +object ScalaPlugin { + + def define(id: String, version: String, author: String, url: String, description: String) + = new ScalaPlugin(id, version, author, url, description) + + def eval(source: String): Any = { + val toolbox = currentMirror.mkToolBox() + val tree = toolbox.parse(source) + toolbox.eval(tree) + } + +} diff --git a/src/main/scala/plugin/package.scala b/src/main/scala/plugin/package.scala new file mode 100644 index 0000000..f90c1a9 --- /dev/null +++ b/src/main/scala/plugin/package.scala @@ -0,0 +1,29 @@ +import util.ControlUtil._ +import scala.collection.mutable.ListBuffer + +package object plugin { + + object db { + // TODO Use JavaScript Map instead of java.util.Map + def select(sql: String): Seq[Map[String, String]] = { + defining(PluginConnectionHolder.threadLocal.get){ conn => + using(conn.prepareStatement(sql)){ stmt => + using(stmt.executeQuery()){ rs => + val list = new ListBuffer[Map[String, String]]() + while(rs.next){ + defining(rs.getMetaData){ meta => + val map = Range(1, meta.getColumnCount + 1).map { i => + val name = meta.getColumnName(i) + (name, rs.getString(name)) + }.toMap + list += map + } + } + list + } + } + } + } + } + +} diff --git a/src/main/scala/servlet/PluginActionInvokeFilter.scala b/src/main/scala/servlet/PluginActionInvokeFilter.scala index 486587b..7da143c 100644 --- a/src/main/scala/servlet/PluginActionInvokeFilter.scala +++ b/src/main/scala/servlet/PluginActionInvokeFilter.scala @@ -10,6 +10,7 @@ import plugin.PluginConnectionHolder import service.RepositoryService.RepositoryInfo import service.SystemSettingsService.SystemSettings +import org.json4s.jackson.Json class PluginActionInvokeFilter extends Filter with SystemSettingsService with RepositoryService with AccountService { @@ -36,12 +37,7 @@ val systemSettings = loadSystemSettings() result match { case x: String => renderGlobalHtml(request, response, systemSettings, x) - case x: org.mozilla.javascript.NativeObject => { - x.get("format") match { - case "html" => renderGlobalHtml(request, response, systemSettings, x.get("body").toString) - case "json" => renderJson(request, response, x.get("body").toString) - } - } + case x: AnyRef => renderJson(request, response, x) } true } getOrElse false @@ -65,12 +61,7 @@ } result match { case x: String => renderRepositoryHtml(request, response, systemSettings, repository, x) - case x: org.mozilla.javascript.NativeObject => { - x.get("format") match { - case "html" => renderRepositoryHtml(request, response, systemSettings, repository, x.get("body").toString) - case "json" => renderJson(request, response, x.get("body").toString) - } - } + case x: AnyRef => renderJson(request, response, x) } true } @@ -96,9 +87,16 @@ IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream) } - private def renderJson(request: HttpServletRequest, response: HttpServletResponse, body: String): Unit = { + private def renderJson(request: HttpServletRequest, response: HttpServletResponse, obj: AnyRef): Unit = { + import org.json4s._ + import org.json4s.jackson.Serialization + import org.json4s.jackson.Serialization.write + implicit val formats = Serialization.formats(NoTypeHints) + + val json = write(obj) + response.setContentType("application/json; charset=UTF-8") - IOUtils.write(body.getBytes("UTF-8"), response.getOutputStream) + IOUtils.write(json.getBytes("UTF-8"), response.getOutputStream) } }