mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-02 03:26:06 +01:00
(refs #464)Experimental implementation of Scala based plugin
This commit is contained in:
@@ -40,7 +40,6 @@ object MyBuild extends Build {
|
||||
"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",
|
||||
|
||||
@@ -111,15 +111,15 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
||||
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 = {
|
||||
|
||||
@@ -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, "<cmd>", 1, null)
|
||||
result
|
||||
} finally {
|
||||
JsContext.exit
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,9 @@ import org.apache.commons.io.FileUtils
|
||||
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 @@ object PluginSystem {
|
||||
// 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 @@ object PluginSystem {
|
||||
}
|
||||
}
|
||||
|
||||
// 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!"
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 @@ class ScalaPlugin(val id: String, val version: String,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
29
src/main/scala/plugin/package.scala
Normal file
29
src/main/scala/plugin/package.scala
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import util.{JGitUtil, Keys}
|
||||
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 @@ class PluginActionInvokeFilter extends Filter with SystemSettingsService with Re
|
||||
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 @@ class PluginActionInvokeFilter extends Filter with SystemSettingsService with Re
|
||||
}
|
||||
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 @@ class PluginActionInvokeFilter extends Filter with SystemSettingsService with Re
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user