From 140f1eb31b9d37eb9936741a1f92b240bf447352 Mon Sep 17 00:00:00 2001 From: takezoe Date: Sun, 25 Jan 2015 02:31:21 +0900 Subject: [PATCH 01/14] Add sbt-assembly configuration --- project/build.scala | 15 ++++++++++++++- project/plugins.sbt | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/project/build.scala b/project/build.scala index 8a4844cde..dedd3e359 100644 --- a/project/build.scala +++ b/project/build.scala @@ -4,6 +4,8 @@ import org.scalatra.sbt._ import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys import play.twirl.sbt.SbtTwirl import play.twirl.sbt.Import.TwirlKeys._ +import sbtassembly._ +import sbtassembly.AssemblyKeys._ object MyBuild extends Build { val Organization = "jp.sf.amateras" @@ -17,6 +19,17 @@ object MyBuild extends Build { file(".") ) .settings(ScalatraPlugin.scalatraWithJRebel: _*) + .settings( + test in assembly := {}, + assemblyMergeStrategy in assembly := { + case PathList("META-INF", xs @ _*) => + (xs map {_.toLowerCase}) match { + case ("manifest.mf" :: Nil) => MergeStrategy.discard + case _ => MergeStrategy.discard + } + case x => MergeStrategy.first + } + ) .settings( sourcesInBase := false, organization := Organization, @@ -45,7 +58,7 @@ object MyBuild extends Build { "com.typesafe.slick" %% "slick" % "2.1.0", "com.novell.ldap" % "jldap" % "2009-10-07", "com.h2database" % "h2" % "1.4.180", - "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime", +// "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime", "org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided", "org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"), "junit" % "junit" % "4.11" % "test", diff --git a/project/plugins.sbt b/project/plugins.sbt index de95ab6af..dee3cdf87 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -7,3 +7,5 @@ addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.5") addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.2") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.4") + +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0") \ No newline at end of file From 73c76a5a88d7f688535d736937ccc74c4033e808 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sat, 7 Feb 2015 00:53:41 +0900 Subject: [PATCH 02/14] Add plugin interfaces --- src/main/scala/plugin/Plugin.scala | 14 +++ src/main/scala/plugin/PluginRegistory.scala | 87 +++++++++++++++++++ .../scala/servlet/AutoUpdateListener.scala | 7 ++ src/main/twirl/main.scala.html | 5 ++ 4 files changed, 113 insertions(+) create mode 100644 src/main/scala/plugin/Plugin.scala create mode 100644 src/main/scala/plugin/PluginRegistory.scala diff --git a/src/main/scala/plugin/Plugin.scala b/src/main/scala/plugin/Plugin.scala new file mode 100644 index 000000000..0c8164e3a --- /dev/null +++ b/src/main/scala/plugin/Plugin.scala @@ -0,0 +1,14 @@ +package plugin + +trait Plugin { + + val pluginId: String + val pluginName: String + val description: String + val version: String + + def initialize(registry: PluginRegistry): Unit + + def shutdown(registry: PluginRegistry): Unit + +} diff --git a/src/main/scala/plugin/PluginRegistory.scala b/src/main/scala/plugin/PluginRegistory.scala new file mode 100644 index 000000000..9abb6bd50 --- /dev/null +++ b/src/main/scala/plugin/PluginRegistory.scala @@ -0,0 +1,87 @@ +package plugin + +import java.io.{FilenameFilter, File} +import java.net.URLClassLoader + +import org.slf4j.LoggerFactory +import util.Directory._ + +import scala.collection.mutable.ListBuffer + +class PluginRegistry { + + private val plugins = new ListBuffer[PluginInfo] + private val javaScripts = new ListBuffer[(String, String)] + + def addPlugin(pluginInfo: PluginInfo): Unit = { + plugins += pluginInfo + } + + def getPlugins(): List[PluginInfo] = plugins.toList + + def addJavaScript(path: String, script: String): Unit = { + javaScripts += Tuple2(path, script) + } + + def getJavaScripts(): List[(String, String)] = javaScripts.toList + + def getJavaScript(currentPath: String): Option[String] = { + println(currentPath) + getJavaScripts().find(x => currentPath.matches(x._1)).map(_._2) + } + +} + +object PluginRegistry { + + private val logger = LoggerFactory.getLogger(classOf[PluginRegistry]) + + private val instance = new PluginRegistry() + + def apply(): PluginRegistry = instance + + def initialize(): Unit = { + new File(PluginHome).listFiles(new FilenameFilter { + override def accept(dir: File, name: String): Boolean = name.endsWith(".jar") + }).foreach { pluginJar => + val classLoader = new URLClassLoader(Array(pluginJar.toURI.toURL), Thread.currentThread.getContextClassLoader) + try { + val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin] + plugin.initialize(instance) + instance.addPlugin(PluginInfo( + pluginId = plugin.pluginId, + pluginName = plugin.pluginName, + version = plugin.version, + description = plugin.description, + pluginClass = plugin + )) + } catch { + case e: Exception => { + logger.error(s"Error during plugin initialization", e) + } + } + } + } + + def shutdown(): Unit = { + instance.getPlugins().foreach { pluginInfo => + try { + pluginInfo.pluginClass.shutdown(instance) + } catch { + case e: Exception => { + logger.error(s"Error during plugin shutdown", e) + } + } + } + } + + +} + +case class PluginInfo( + pluginId: String, + pluginName: String, + version: String, + description: String, + pluginClass: Plugin +) \ No newline at end of file diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/AutoUpdateListener.scala index 6020a0f8f..c8ed68387 100644 --- a/src/main/scala/servlet/AutoUpdateListener.scala +++ b/src/main/scala/servlet/AutoUpdateListener.scala @@ -11,6 +11,7 @@ import util.ControlUtil._ import util.JDBCUtil._ import org.eclipse.jgit.api.Git import util.Directory +import plugin._ object AutoUpdate { @@ -211,6 +212,7 @@ class AutoUpdateListener extends ServletContextListener { val context = event.getServletContext context.setInitParameter("db.url", s"jdbc:h2:${DatabaseHome};MVCC=true") + // Migration defining(getConnection(event.getServletContext)){ conn => logger.debug("Start schema update") try { @@ -234,9 +236,14 @@ class AutoUpdateListener extends ServletContextListener { } logger.debug("End schema update") } + + // Load plugins + PluginRegistry.initialize() } def contextDestroyed(sce: ServletContextEvent): Unit = { + // Shutdown plugins + PluginRegistry.shutdown() } private def getConnection(servletContext: ServletContext): Connection = diff --git a/src/main/twirl/main.scala.html b/src/main/twirl/main.scala.html index 9b8ae0280..6d35ff1d1 100644 --- a/src/main/twirl/main.scala.html +++ b/src/main/twirl/main.scala.html @@ -82,5 +82,10 @@ }); }); + @plugin.PluginRegistry().getJavaScript(request.getRequestURI).map { script => + + } From 7e7739864547dd6faad784c6c84454e74c6fa7f0 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sat, 7 Feb 2015 13:12:05 +0900 Subject: [PATCH 03/14] Add prototype of global action support --- src/main/scala/ScalatraBootstrap.scala | 4 +- src/main/scala/plugin/PluginRegistory.scala | 94 ++++++++++++++----- .../scala/servlet/AutoUpdateListener.scala | 14 ++- .../scala/servlet/PluginActionFilter.scala | 38 ++++++++ src/main/scala/util/ControlUtil.scala | 6 ++ src/main/scala/util/JDBCUtil.scala | 8 ++ 6 files changed, 138 insertions(+), 26 deletions(-) create mode 100644 src/main/scala/servlet/PluginActionFilter.scala diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala index bdbd89867..aa9077610 100644 --- a/src/main/scala/ScalatraBootstrap.scala +++ b/src/main/scala/ScalatraBootstrap.scala @@ -1,4 +1,4 @@ -import _root_.servlet.{BasicAuthenticationFilter, TransactionFilter} +import _root_.servlet.{BasicAuthenticationFilter, TransactionFilter, PluginActionFilter} import app._ //import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider import org.scalatra._ @@ -12,6 +12,8 @@ class ScalatraBootstrap extends LifeCycle { context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter) context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*") + context.addFilter("pluginActionFilter", new PluginActionFilter) + context.getFilterRegistration("pluginActionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") // Register controllers context.mount(new AnonymousAccessController, "/*") diff --git a/src/main/scala/plugin/PluginRegistory.scala b/src/main/scala/plugin/PluginRegistory.scala index 9abb6bd50..18658e505 100644 --- a/src/main/scala/plugin/PluginRegistory.scala +++ b/src/main/scala/plugin/PluginRegistory.scala @@ -2,9 +2,12 @@ package plugin import java.io.{FilenameFilter, File} import java.net.URLClassLoader +import javax.servlet.http.{HttpServletRequest, HttpServletResponse} import org.slf4j.LoggerFactory +import service.RepositoryService.RepositoryInfo import util.Directory._ +import util.JDBCUtil._ import scala.collection.mutable.ListBuffer @@ -12,6 +15,8 @@ class PluginRegistry { private val plugins = new ListBuffer[PluginInfo] private val javaScripts = new ListBuffer[(String, String)] + private val globalActions = new ListBuffer[GlobalAction] + private val repositoryActions = new ListBuffer[RepositoryAction] def addPlugin(pluginInfo: PluginInfo): Unit = { plugins += pluginInfo @@ -19,17 +24,51 @@ class PluginRegistry { def getPlugins(): List[PluginInfo] = plugins.toList + def addGlobalAction(method: String, path: String)(f: (HttpServletRequest, HttpServletResponse) => Any): Unit = { + globalActions += GlobalAction(method.toLowerCase, path, f) + } + + //def getGlobalActions(): List[GlobalAction] = globalActions.toList + + def getGlobalAction(method: String, path: String): Option[(HttpServletRequest, HttpServletResponse) => Any] = { + globalActions.find { globalAction => + globalAction.method == method.toLowerCase && path.matches(globalAction.path) + }.map(_.function) + } + + def addRepositoryAction(method: String, path: String)(f: (HttpServletRequest, HttpServletResponse, RepositoryInfo) => Any): Unit = { + repositoryActions += RepositoryAction(method.toLowerCase, path, f) + } + + //def getRepositoryActions(): List[RepositoryAction] = repositoryActions.toList + + def getRepositoryAction(method: String, path: String): Option[(HttpServletRequest, HttpServletResponse, RepositoryInfo) => Any] = { + // TODO + null + } + def addJavaScript(path: String, script: String): Unit = { javaScripts += Tuple2(path, script) } - def getJavaScripts(): List[(String, String)] = javaScripts.toList + //def getJavaScripts(): List[(String, String)] = javaScripts.toList def getJavaScript(currentPath: String): Option[String] = { - println(currentPath) - getJavaScripts().find(x => currentPath.matches(x._1)).map(_._2) + javaScripts.find(x => currentPath.matches(x._1)).map(_._2) } + private case class GlobalAction( + method: String, + path: String, + function: (HttpServletRequest, HttpServletResponse) => Any + ) + + private case class RepositoryAction( + method: String, + path: String, + function: (HttpServletRequest, HttpServletResponse, RepositoryInfo) => Any + ) + } object PluginRegistry { @@ -40,24 +79,37 @@ object PluginRegistry { def apply(): PluginRegistry = instance - def initialize(): Unit = { - new File(PluginHome).listFiles(new FilenameFilter { - override def accept(dir: File, name: String): Boolean = name.endsWith(".jar") - }).foreach { pluginJar => - val classLoader = new URLClassLoader(Array(pluginJar.toURI.toURL), Thread.currentThread.getContextClassLoader) - try { - val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin] - plugin.initialize(instance) - instance.addPlugin(PluginInfo( - pluginId = plugin.pluginId, - pluginName = plugin.pluginName, - version = plugin.version, - description = plugin.description, - pluginClass = plugin - )) - } catch { - case e: Exception => { - logger.error(s"Error during plugin initialization", e) + def initialize(conn: java.sql.Connection): Unit = { + val pluginDir = new File(PluginHome) + if(pluginDir.exists && pluginDir.isDirectory){ + pluginDir.listFiles(new FilenameFilter { + override def accept(dir: File, name: String): Boolean = name.endsWith(".jar") + }).foreach { pluginJar => + val classLoader = new URLClassLoader(Array(pluginJar.toURI.toURL), Thread.currentThread.getContextClassLoader) + try { + val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin] + plugin.initialize(instance) + instance.addPlugin(PluginInfo( + pluginId = plugin.pluginId, + pluginName = plugin.pluginName, + version = plugin.version, + description = plugin.description, + pluginClass = plugin + )) + + conn.find("SELECT * FROM PLUGIN WHERE PLUGIN_ID = ?", plugin.pluginId)(_.getString("VERSION")) match { + // Update if the plugin is already registered + case Some(currentVersion) => conn.update("UPDATE PLUGIN SET VERSION = ? WHERE PLUGIN_ID = ?", plugin.version, plugin.pluginId) + // Insert if the plugin does not exist + case None => conn.update("INSERT INTO PLUGIN (PLUGIN_ID, VERSION) VALUES (?, ?)", plugin.pluginId, plugin.version) + } + + // TODO Migration + + } catch { + case e: Exception => { + logger.error(s"Error during plugin initialization", e) + } } } } diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/AutoUpdateListener.scala index c8ed68387..6816bc368 100644 --- a/src/main/scala/servlet/AutoUpdateListener.scala +++ b/src/main/scala/servlet/AutoUpdateListener.scala @@ -13,6 +13,8 @@ import org.eclipse.jgit.api.Git import util.Directory import plugin._ +import scala.slick.jdbc.meta.MBestRowIdentifierColumn.Scope.Session + object AutoUpdate { /** @@ -103,8 +105,10 @@ object AutoUpdate { conn.update("UPDATE ACTIVITY SET ADDITIONAL_INFO = ? WHERE ACTIVITY_ID = ?", newInfo, rs.getInt("ACTIVITY_ID")) } } - FileUtils.deleteDirectory(Directory.getPluginCacheDir()) - FileUtils.deleteDirectory(new File(Directory.PluginHome)) + ignore { + FileUtils.deleteDirectory(Directory.getPluginCacheDir()) + //FileUtils.deleteDirectory(new File(Directory.PluginHome)) + } } }, new Version(2, 2), @@ -235,10 +239,12 @@ class AutoUpdateListener extends ServletContextListener { } } logger.debug("End schema update") + + // Load plugins + + PluginRegistry.initialize(conn) } - // Load plugins - PluginRegistry.initialize() } def contextDestroyed(sce: ServletContextEvent): Unit = { diff --git a/src/main/scala/servlet/PluginActionFilter.scala b/src/main/scala/servlet/PluginActionFilter.scala new file mode 100644 index 000000000..d434aee0f --- /dev/null +++ b/src/main/scala/servlet/PluginActionFilter.scala @@ -0,0 +1,38 @@ +package servlet + +import javax.servlet._ +import javax.servlet.http.{HttpServletResponse, HttpServletRequest} + +import play.twirl.api.Html +import plugin.PluginRegistry + +class PluginActionFilter extends Filter { + + def init(config: FilterConfig) = {} + + def destroy(): Unit = {} + + def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = (req, res) match { + case (req: HttpServletRequest, res: HttpServletResponse) => { + val method = req.getMethod.toLowerCase + val path = req.getRequestURI.substring(req.getContextPath.length) + val registry = PluginRegistry() + registry.getGlobalAction(method, path).map { action => + action(req, res) match { + // TODO to be type classes? + case x: String => + res.setContentType("text/plain; charset=UTF-8") + res.getWriter.write(x) + res.getWriter.flush() + case x: Html => + res.setContentType("text/html; charset=UTF-8") + res.getWriter.write(x.body) + res.getWriter.flush() + } + }.getOrElse { + chain.doFilter(req, res) + } + } + } + +} \ No newline at end of file diff --git a/src/main/scala/util/ControlUtil.scala b/src/main/scala/util/ControlUtil.scala index c231fb01f..7945f32a5 100644 --- a/src/main/scala/util/ControlUtil.scala +++ b/src/main/scala/util/ControlUtil.scala @@ -37,4 +37,10 @@ object ControlUtil { def using[T](treeWalk: TreeWalk)(f: TreeWalk => T): T = try f(treeWalk) finally treeWalk.release() + def ignore[T](f: => Unit): Unit = try { + f + } catch { + case e: Exception => () + } + } diff --git a/src/main/scala/util/JDBCUtil.scala b/src/main/scala/util/JDBCUtil.scala index 375ea1464..5d880d0c5 100644 --- a/src/main/scala/util/JDBCUtil.scala +++ b/src/main/scala/util/JDBCUtil.scala @@ -18,6 +18,14 @@ object JDBCUtil { } } + def find[T](sql: String, params: Any*)(f: ResultSet => T): Option[T] = { + execute(sql, params: _*){ stmt => + using(stmt.executeQuery()){ rs => + if(rs.next) Some(f(rs)) else None + } + } + } + def select[T](sql: String, params: Any*)(f: ResultSet => T): Seq[T] = { execute(sql, params: _*){ stmt => using(stmt.executeQuery()){ rs => From 6888d959e1aadefac363b732b28c6933edaa455e Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 8 Feb 2015 22:31:09 +0900 Subject: [PATCH 04/14] Add migration system for plugins --- src/main/scala/plugin/Plugin.scala | 4 +- src/main/scala/plugin/PluginRegistory.scala | 33 ++++++--- .../scala/servlet/AutoUpdateListener.scala | 2 - src/main/scala/util/Version.scala | 67 +++++++++++++++++++ 4 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 src/main/scala/util/Version.scala diff --git a/src/main/scala/plugin/Plugin.scala b/src/main/scala/plugin/Plugin.scala index 0c8164e3a..48ae95599 100644 --- a/src/main/scala/plugin/Plugin.scala +++ b/src/main/scala/plugin/Plugin.scala @@ -1,11 +1,13 @@ package plugin +import util.Version + trait Plugin { val pluginId: String val pluginName: String val description: String - val version: String + val versions: List[Version] def initialize(registry: PluginRegistry): Unit diff --git a/src/main/scala/plugin/PluginRegistory.scala b/src/main/scala/plugin/PluginRegistory.scala index 18658e505..5d940108e 100644 --- a/src/main/scala/plugin/PluginRegistory.scala +++ b/src/main/scala/plugin/PluginRegistory.scala @@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory import service.RepositoryService.RepositoryInfo import util.Directory._ import util.JDBCUtil._ +import util.{Version, Versions} import scala.collection.mutable.ListBuffer @@ -88,24 +89,36 @@ object PluginRegistry { val classLoader = new URLClassLoader(Array(pluginJar.toURI.toURL), Thread.currentThread.getContextClassLoader) try { val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin] + + // Migration + val headVersion = plugin.versions.head + val currentVersion = conn.find("SELECT * FROM PLUGIN WHERE PLUGIN_ID = ?", plugin.pluginId)(_.getString("VERSION")) match { + case Some(x) => { + val dim = x.split("\\.") + Version(dim(0).toInt, dim(1).toInt) + } + case None => Version(0, 0) + } + + Versions.update(conn, headVersion, currentVersion, plugin.versions, new URLClassLoader(Array(pluginJar.toURI.toURL))){ conn => + currentVersion.versionString match { + case "0.0" => + conn.update("INSERT INTO PLUGIN (PLUGIN_ID, VERSION) VALUES (?, ?)", plugin.pluginId, headVersion.versionString) + case _ => + conn.update("UPDATE PLUGIN SET VERSION = ? WHERE PLUGIN_ID = ?", headVersion.versionString, plugin.pluginId) + } + } + + // Initialize plugin.initialize(instance) instance.addPlugin(PluginInfo( pluginId = plugin.pluginId, pluginName = plugin.pluginName, - version = plugin.version, + version = plugin.versions.head.versionString, description = plugin.description, pluginClass = plugin )) - conn.find("SELECT * FROM PLUGIN WHERE PLUGIN_ID = ?", plugin.pluginId)(_.getString("VERSION")) match { - // Update if the plugin is already registered - case Some(currentVersion) => conn.update("UPDATE PLUGIN SET VERSION = ? WHERE PLUGIN_ID = ?", plugin.version, plugin.pluginId) - // Insert if the plugin does not exist - case None => conn.update("INSERT INTO PLUGIN (PLUGIN_ID, VERSION) VALUES (?, ?)", plugin.pluginId, plugin.version) - } - - // TODO Migration - } catch { case e: Exception => { logger.error(s"Error during plugin initialization", e) diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/AutoUpdateListener.scala index 6816bc368..07edf7ec7 100644 --- a/src/main/scala/servlet/AutoUpdateListener.scala +++ b/src/main/scala/servlet/AutoUpdateListener.scala @@ -13,8 +13,6 @@ import org.eclipse.jgit.api.Git import util.Directory import plugin._ -import scala.slick.jdbc.meta.MBestRowIdentifierColumn.Scope.Session - object AutoUpdate { /** diff --git a/src/main/scala/util/Version.scala b/src/main/scala/util/Version.scala new file mode 100644 index 000000000..00d485479 --- /dev/null +++ b/src/main/scala/util/Version.scala @@ -0,0 +1,67 @@ +package util + +import java.sql.Connection + +import org.apache.commons.io.IOUtils +import org.slf4j.LoggerFactory +import util.ControlUtil._ + +case class Version(majorVersion: Int, minorVersion: Int) { + + private val logger = LoggerFactory.getLogger(classOf[Version]) + + /** + * Execute update/MAJOR_MINOR.sql to update schema to this version. + * If corresponding SQL file does not exist, this method do nothing. + */ + def update(conn: Connection, cl: ClassLoader): Unit = { + val sqlPath = s"update/${majorVersion}_${minorVersion}.sql" + + using(cl.getResourceAsStream(sqlPath)){ in => + if(in != null){ + val sql = IOUtils.toString(in, "UTF-8") + using(conn.createStatement()){ stmt => + logger.debug(sqlPath + "=" + sql) + stmt.executeUpdate(sql) + } + } + } + } + + + /** + * MAJOR.MINOR + */ + val versionString = s"${majorVersion}.${minorVersion}" + +} + +object Versions { + + private val logger = LoggerFactory.getLogger(Versions.getClass) + + def update(conn: Connection, headVersion: Version, currentVersion: Version, versions: List[Version], cl: ClassLoader) + (save: Connection => Unit): Unit = { + logger.debug("Start schema update") + try { + if(currentVersion == headVersion){ + logger.debug("No update") + } else if(currentVersion.versionString != "0.0" && !versions.contains(currentVersion)){ + logger.warn(s"Skip migration because ${currentVersion.versionString} is illegal version.") + } else { + versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn, cl)) + save(conn) + logger.debug(s"Updated from ${currentVersion.versionString} to ${headVersion.versionString}") + } + } catch { + case ex: Throwable => { + logger.error("Failed to schema update", ex) + ex.printStackTrace() + conn.rollback() + } + } + logger.debug("End schema update") + } + +} + From 22d12d048882a2de41bc901d0ed0b35570f188a1 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 8 Feb 2015 22:35:38 +0900 Subject: [PATCH 05/14] Use util.Version for GitBucket migration --- src/main/scala/plugin/Plugin.scala | 2 +- .../scala/servlet/AutoUpdateListener.scala | 76 +++---------------- src/main/scala/util/Version.scala | 2 +- 3 files changed, 14 insertions(+), 66 deletions(-) diff --git a/src/main/scala/plugin/Plugin.scala b/src/main/scala/plugin/Plugin.scala index 48ae95599..3c9fe4ad8 100644 --- a/src/main/scala/plugin/Plugin.scala +++ b/src/main/scala/plugin/Plugin.scala @@ -7,7 +7,7 @@ trait Plugin { val pluginId: String val pluginName: String val description: String - val versions: List[Version] + val versions: Seq[Version] def initialize(registry: PluginRegistry): Unit diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/AutoUpdateListener.scala index 07edf7ec7..241eafe84 100644 --- a/src/main/scala/servlet/AutoUpdateListener.scala +++ b/src/main/scala/servlet/AutoUpdateListener.scala @@ -11,43 +11,10 @@ import util.ControlUtil._ import util.JDBCUtil._ import org.eclipse.jgit.api.Git import util.Directory +import util.{Version, Versions} import plugin._ object AutoUpdate { - - /** - * Version of GitBucket - * - * @param majorVersion the major version - * @param minorVersion the minor version - */ - case class Version(majorVersion: Int, minorVersion: Int){ - - private val logger = LoggerFactory.getLogger(classOf[servlet.AutoUpdate.Version]) - - /** - * Execute update/MAJOR_MINOR.sql to update schema to this version. - * If corresponding SQL file does not exist, this method do nothing. - */ - def update(conn: Connection): Unit = { - val sqlPath = s"update/${majorVersion}_${minorVersion}.sql" - - using(Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath)){ in => - if(in != null){ - val sql = IOUtils.toString(in, "UTF-8") - using(conn.createStatement()){ stmt => - logger.debug(sqlPath + "=" + sql) - stmt.executeUpdate(sql) - } - } - } - } - - /** - * MAJOR.MINOR - */ - val versionString = s"${majorVersion}.${minorVersion}" - } /** * The history of versions. A head of this sequence is the current BitBucket version. @@ -55,8 +22,8 @@ object AutoUpdate { val versions = Seq( new Version(2, 8), new Version(2, 7) { - override def update(conn: Connection): Unit = { - super.update(conn) + override def update(conn: Connection, cl: ClassLoader): Unit = { + super.update(conn, cl) conn.select("SELECT * FROM REPOSITORY"){ rs => // Rename attached files directory from /issues to /comments val userName = rs.getString("USER_NAME") @@ -94,8 +61,8 @@ object AutoUpdate { new Version(2, 5), new Version(2, 4), new Version(2, 3) { - override def update(conn: Connection): Unit = { - super.update(conn) + override def update(conn: Connection, cl: ClassLoader): Unit = { + super.update(conn, cl) conn.select("SELECT ACTIVITY_ID, ADDITIONAL_INFO FROM ACTIVITY WHERE ACTIVITY_TYPE='push'"){ rs => val curInfo = rs.getString("ADDITIONAL_INFO") val newInfo = curInfo.split("\n").filter(_ matches "^[0-9a-z]{40}:.*").mkString("\n") @@ -112,13 +79,13 @@ object AutoUpdate { new Version(2, 2), new Version(2, 1), new Version(2, 0){ - override def update(conn: Connection): Unit = { + override def update(conn: Connection, cl: ClassLoader): Unit = { import eu.medsea.mimeutil.{MimeUtil2, MimeType} val mimeUtil = new MimeUtil2() mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector") - super.update(conn) + super.update(conn, cl) conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs => defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir => if(dir.exists && dir.isDirectory){ @@ -146,8 +113,8 @@ object AutoUpdate { Version(1, 5), Version(1, 4), new Version(1, 3){ - override def update(conn: Connection): Unit = { - super.update(conn) + override def update(conn: Connection, cl: ClassLoader): Unit = { + super.update(conn, cl) // Fix wiki repository configuration conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs => using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git => @@ -214,32 +181,13 @@ class AutoUpdateListener extends ServletContextListener { val context = event.getServletContext context.setInitParameter("db.url", s"jdbc:h2:${DatabaseHome};MVCC=true") - // Migration defining(getConnection(event.getServletContext)){ conn => + // Migration logger.debug("Start schema update") - try { - defining(getCurrentVersion()){ currentVersion => - if(currentVersion == headVersion){ - logger.debug("No update") - } else if(!versions.contains(currentVersion)){ - logger.warn(s"Skip migration because ${currentVersion.versionString} is illegal version.") - } else { - versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn)) - FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8") - logger.debug(s"Updated from ${currentVersion.versionString} to ${headVersion.versionString}") - } - } - } catch { - case ex: Throwable => { - logger.error("Failed to schema update", ex) - ex.printStackTrace() - conn.rollback() - } + Versions.update(conn, headVersion, getCurrentVersion(), versions, Thread.currentThread.getContextClassLoader){ conn => + FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8") } - logger.debug("End schema update") - // Load plugins - PluginRegistry.initialize(conn) } diff --git a/src/main/scala/util/Version.scala b/src/main/scala/util/Version.scala index 00d485479..fed74a8e0 100644 --- a/src/main/scala/util/Version.scala +++ b/src/main/scala/util/Version.scala @@ -40,7 +40,7 @@ object Versions { private val logger = LoggerFactory.getLogger(Versions.getClass) - def update(conn: Connection, headVersion: Version, currentVersion: Version, versions: List[Version], cl: ClassLoader) + def update(conn: Connection, headVersion: Version, currentVersion: Version, versions: Seq[Version], cl: ClassLoader) (save: Connection => Unit): Unit = { logger.debug("Start schema update") try { From fb6bb12c52d4798b3c94491b4943175d57ab5d9f Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 8 Feb 2015 23:09:30 +0900 Subject: [PATCH 06/14] Give Context to plugin actions --- src/main/scala/plugin/PluginRegistory.scala | 13 +++++++------ src/main/scala/servlet/PluginActionFilter.scala | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/scala/plugin/PluginRegistory.scala b/src/main/scala/plugin/PluginRegistory.scala index 5d940108e..186b38a6c 100644 --- a/src/main/scala/plugin/PluginRegistory.scala +++ b/src/main/scala/plugin/PluginRegistory.scala @@ -11,6 +11,7 @@ import util.JDBCUtil._ import util.{Version, Versions} import scala.collection.mutable.ListBuffer +import app.Context class PluginRegistry { @@ -25,25 +26,25 @@ class PluginRegistry { def getPlugins(): List[PluginInfo] = plugins.toList - def addGlobalAction(method: String, path: String)(f: (HttpServletRequest, HttpServletResponse) => Any): Unit = { + def addGlobalAction(method: String, path: String)(f: (HttpServletRequest, HttpServletResponse, Context) => Any): Unit = { globalActions += GlobalAction(method.toLowerCase, path, f) } //def getGlobalActions(): List[GlobalAction] = globalActions.toList - def getGlobalAction(method: String, path: String): Option[(HttpServletRequest, HttpServletResponse) => Any] = { + def getGlobalAction(method: String, path: String): Option[(HttpServletRequest, HttpServletResponse, Context) => Any] = { globalActions.find { globalAction => globalAction.method == method.toLowerCase && path.matches(globalAction.path) }.map(_.function) } - def addRepositoryAction(method: String, path: String)(f: (HttpServletRequest, HttpServletResponse, RepositoryInfo) => Any): Unit = { + def addRepositoryAction(method: String, path: String)(f: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any): Unit = { repositoryActions += RepositoryAction(method.toLowerCase, path, f) } //def getRepositoryActions(): List[RepositoryAction] = repositoryActions.toList - def getRepositoryAction(method: String, path: String): Option[(HttpServletRequest, HttpServletResponse, RepositoryInfo) => Any] = { + def getRepositoryAction(method: String, path: String): Option[(HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any] = { // TODO null } @@ -61,13 +62,13 @@ class PluginRegistry { private case class GlobalAction( method: String, path: String, - function: (HttpServletRequest, HttpServletResponse) => Any + function: (HttpServletRequest, HttpServletResponse, Context) => Any ) private case class RepositoryAction( method: String, path: String, - function: (HttpServletRequest, HttpServletResponse, RepositoryInfo) => Any + function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any ) } diff --git a/src/main/scala/servlet/PluginActionFilter.scala b/src/main/scala/servlet/PluginActionFilter.scala index d434aee0f..d5b72c75c 100644 --- a/src/main/scala/servlet/PluginActionFilter.scala +++ b/src/main/scala/servlet/PluginActionFilter.scala @@ -3,10 +3,14 @@ package servlet import javax.servlet._ import javax.servlet.http.{HttpServletResponse, HttpServletRequest} +import model.Account import play.twirl.api.Html import plugin.PluginRegistry +import service.SystemSettingsService +import util.Keys +import app.Context -class PluginActionFilter extends Filter { +class PluginActionFilter extends Filter with SystemSettingsService { def init(config: FilterConfig) = {} @@ -17,8 +21,14 @@ class PluginActionFilter extends Filter { val method = req.getMethod.toLowerCase val path = req.getRequestURI.substring(req.getContextPath.length) val registry = PluginRegistry() + registry.getGlobalAction(method, path).map { action => - action(req, res) match { + // Create Context + val loginAccount = req.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account] + val context = Context(loadSystemSettings(), Option(loginAccount), req) + + // Invoke global action + action(req, res, context) match { // TODO to be type classes? case x: String => res.setContentType("text/plain; charset=UTF-8") From 925420734e4e28ce2b463bd2d7426c2a5b7c5cd7 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 8 Feb 2015 23:51:21 +0900 Subject: [PATCH 07/14] Render Html with layout template --- src/main/scala/servlet/PluginActionFilter.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/scala/servlet/PluginActionFilter.scala b/src/main/scala/servlet/PluginActionFilter.scala index d5b72c75c..1480193ea 100644 --- a/src/main/scala/servlet/PluginActionFilter.scala +++ b/src/main/scala/servlet/PluginActionFilter.scala @@ -25,7 +25,7 @@ class PluginActionFilter extends Filter with SystemSettingsService { registry.getGlobalAction(method, path).map { action => // Create Context val loginAccount = req.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account] - val context = Context(loadSystemSettings(), Option(loginAccount), req) + implicit val context = Context(loadSystemSettings(), Option(loginAccount), req) // Invoke global action action(req, res, context) match { @@ -36,7 +36,8 @@ class PluginActionFilter extends Filter with SystemSettingsService { res.getWriter.flush() case x: Html => res.setContentType("text/html; charset=UTF-8") - res.getWriter.write(x.body) + // TODO title of plugin action + res.getWriter.write(html.main("TODO")(x).body) res.getWriter.flush() } }.getOrElse { From 9f6afaed0733a5b785c50b2cc5b0b189a736e881 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 10 Feb 2015 02:28:22 +0900 Subject: [PATCH 08/14] Add Result case classes for plugin --- src/main/scala/plugin/Results.scala | 8 ++++++++ src/main/scala/servlet/PluginActionFilter.scala | 6 ++++++ 2 files changed, 14 insertions(+) create mode 100644 src/main/scala/plugin/Results.scala diff --git a/src/main/scala/plugin/Results.scala b/src/main/scala/plugin/Results.scala new file mode 100644 index 000000000..c31c1700e --- /dev/null +++ b/src/main/scala/plugin/Results.scala @@ -0,0 +1,8 @@ +package plugin + +import play.twirl.api.Html + +object Results { + case class Redirect(path: String) + case class Fragment(html: Html) +} diff --git a/src/main/scala/servlet/PluginActionFilter.scala b/src/main/scala/servlet/PluginActionFilter.scala index 1480193ea..169bb0e3f 100644 --- a/src/main/scala/servlet/PluginActionFilter.scala +++ b/src/main/scala/servlet/PluginActionFilter.scala @@ -9,6 +9,7 @@ import plugin.PluginRegistry import service.SystemSettingsService import util.Keys import app.Context +import plugin.Results._ class PluginActionFilter extends Filter with SystemSettingsService { @@ -39,6 +40,11 @@ class PluginActionFilter extends Filter with SystemSettingsService { // TODO title of plugin action res.getWriter.write(html.main("TODO")(x).body) res.getWriter.flush() + case Redirect(x) => + res.sendRedirect(x) + case Fragment(x) => + res.getWriter.write(x.body) + res.getWriter.flush() } }.getOrElse { chain.doFilter(req, res) From 0b15ecbacd645bd932a1af87448030a0d148c9b6 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Wed, 11 Feb 2015 22:28:29 +0900 Subject: [PATCH 09/14] Add pom.xml for the assembly jar --- etc/pom.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 etc/pom.xml diff --git a/etc/pom.xml b/etc/pom.xml new file mode 100644 index 000000000..40693f237 --- /dev/null +++ b/etc/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + jp.sf.amateras + gitbucket-assembly + 0.0.1 + + + + org.apache.maven.wagon + wagon-ssh + 1.0-beta-6 + + + + \ No newline at end of file From b10839a5c2c3f410f2fd389cf7c6247cea52a898 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sat, 14 Feb 2015 23:28:21 +0900 Subject: [PATCH 10/14] Add comment --- src/main/scala/plugin/Plugin.scala | 12 ++++++++++++ src/main/scala/plugin/PluginRegistory.scala | 9 +++++++++ src/main/scala/plugin/Results.scala | 3 +++ 3 files changed, 24 insertions(+) diff --git a/src/main/scala/plugin/Plugin.scala b/src/main/scala/plugin/Plugin.scala index 3c9fe4ad8..e7976995d 100644 --- a/src/main/scala/plugin/Plugin.scala +++ b/src/main/scala/plugin/Plugin.scala @@ -2,6 +2,10 @@ package plugin import util.Version +/** + * Trait for define plugin interface. + * To provide plugin, put Plugin class which mixed in this trait into the package root. + */ trait Plugin { val pluginId: String @@ -9,8 +13,16 @@ trait Plugin { val description: String val versions: Seq[Version] + /** + * This method is invoked in initialization of plugin system. + * Register plugin functionality to PluginRegistry. + */ def initialize(registry: PluginRegistry): Unit + /** + * This method is invoked in shutdown of plugin system. + * If the plugin has any resources, release them in this method. + */ def shutdown(registry: PluginRegistry): Unit } diff --git a/src/main/scala/plugin/PluginRegistory.scala b/src/main/scala/plugin/PluginRegistory.scala index 186b38a6c..c924922bf 100644 --- a/src/main/scala/plugin/PluginRegistory.scala +++ b/src/main/scala/plugin/PluginRegistory.scala @@ -73,14 +73,23 @@ class PluginRegistry { } +/** + * Provides entry point to PluginRegistry. + */ object PluginRegistry { private val logger = LoggerFactory.getLogger(classOf[PluginRegistry]) private val instance = new PluginRegistry() + /** + * Returns the PluginRegistry singleton instance. + */ def apply(): PluginRegistry = instance + /** + * Initializes all installed plugins. + */ def initialize(conn: java.sql.Connection): Unit = { val pluginDir = new File(PluginHome) if(pluginDir.exists && pluginDir.isDirectory){ diff --git a/src/main/scala/plugin/Results.scala b/src/main/scala/plugin/Results.scala index c31c1700e..18fdb7fc4 100644 --- a/src/main/scala/plugin/Results.scala +++ b/src/main/scala/plugin/Results.scala @@ -2,6 +2,9 @@ package plugin import play.twirl.api.Html +/** + * Defines result case classes returned by plugin controller. + */ object Results { case class Redirect(path: String) case class Fragment(html: Html) From f6e7401d1b70306161c879eacc00119f7e70236b Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sat, 14 Feb 2015 23:56:42 +0900 Subject: [PATCH 11/14] Add script to deploy assembly jar --- etc/deploy-assemby-jar.sh | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 etc/deploy-assemby-jar.sh diff --git a/etc/deploy-assemby-jar.sh b/etc/deploy-assemby-jar.sh new file mode 100644 index 000000000..11f276d1d --- /dev/null +++ b/etc/deploy-assemby-jar.sh @@ -0,0 +1,9 @@ +#!/bin/sh +mvn deploy:deploy-file \ + -DgroupId=jp.sf.amateras\ + -DartifactId=gitbucket-assembly\ + -Dversion=0.0.1\ + -Dpackaging=jar\ + -Dfile=../target/scala-2.11/gitbucket-assembly-0.0.1.jar\ + -DrepositoryId=sourceforge.jp\ + -Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/mvn/ \ No newline at end of file From 8c588cbd669e2a18f2a61f677fc112381ce7eb3f Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 16 Feb 2015 13:14:52 +0900 Subject: [PATCH 12/14] Provides Slick Session to plug-ins via ThreadLocal --- src/main/scala/plugin/Sessions.scala | 11 +++++ .../scala/servlet/PluginActionFilter.scala | 43 +++++++++++-------- 2 files changed, 35 insertions(+), 19 deletions(-) create mode 100644 src/main/scala/plugin/Sessions.scala diff --git a/src/main/scala/plugin/Sessions.scala b/src/main/scala/plugin/Sessions.scala new file mode 100644 index 000000000..7398c9a8d --- /dev/null +++ b/src/main/scala/plugin/Sessions.scala @@ -0,0 +1,11 @@ +package plugin + +import slick.jdbc.JdbcBackend.Session + +/** + * Provides Slick Session to Plug-ins. + */ +object Sessions { + val sessions = new ThreadLocal[Session] + implicit def session: Session = sessions.get() +} diff --git a/src/main/scala/servlet/PluginActionFilter.scala b/src/main/scala/servlet/PluginActionFilter.scala index 169bb0e3f..1ef7642f6 100644 --- a/src/main/scala/servlet/PluginActionFilter.scala +++ b/src/main/scala/servlet/PluginActionFilter.scala @@ -10,6 +10,7 @@ import service.SystemSettingsService import util.Keys import app.Context import plugin.Results._ +import plugin.Sessions._ class PluginActionFilter extends Filter with SystemSettingsService { @@ -27,24 +28,28 @@ class PluginActionFilter extends Filter with SystemSettingsService { // Create Context val loginAccount = req.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account] implicit val context = Context(loadSystemSettings(), Option(loginAccount), req) - - // Invoke global action - action(req, res, context) match { - // TODO to be type classes? - case x: String => - res.setContentType("text/plain; charset=UTF-8") - res.getWriter.write(x) - res.getWriter.flush() - case x: Html => - res.setContentType("text/html; charset=UTF-8") - // TODO title of plugin action - res.getWriter.write(html.main("TODO")(x).body) - res.getWriter.flush() - case Redirect(x) => - res.sendRedirect(x) - case Fragment(x) => - res.getWriter.write(x.body) - res.getWriter.flush() + sessions.set(Database.getSession(req)) + try { + // Invoke global action + action(req, res, context) match { + // TODO to be type classes? + case x: String => + res.setContentType("text/plain; charset=UTF-8") + res.getWriter.write(x) + res.getWriter.flush() + case x: Html => + res.setContentType("text/html; charset=UTF-8") + // TODO title of plugin action + res.getWriter.write(html.main("TODO")(x).body) + res.getWriter.flush() + case Redirect(x) => + res.sendRedirect(x) + case Fragment(x) => + res.getWriter.write(x.body) + res.getWriter.flush() + } + } finally { + sessions.remove() } }.getOrElse { chain.doFilter(req, res) @@ -52,4 +57,4 @@ class PluginActionFilter extends Filter with SystemSettingsService { } } -} \ No newline at end of file +} From b124c31f650653442941a21c3c708ca991b187e7 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Fri, 20 Feb 2015 09:16:16 +0900 Subject: [PATCH 13/14] Plug-in action to be Scalatra controller --- src/main/scala/ScalatraBootstrap.scala | 11 +++- src/main/scala/plugin/Plugin.scala | 2 + src/main/scala/plugin/PluginRegistory.scala | 33 +++------- ...istener.scala => InitializeListener.scala} | 13 ++-- .../scala/servlet/PluginActionFilter.scala | 60 ------------------- src/main/webapp/WEB-INF/web.xml | 4 +- 6 files changed, 27 insertions(+), 96 deletions(-) rename src/main/scala/servlet/{AutoUpdateListener.scala => InitializeListener.scala} (94%) delete mode 100644 src/main/scala/servlet/PluginActionFilter.scala diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala index aa9077610..c2dfb3e23 100644 --- a/src/main/scala/ScalatraBootstrap.scala +++ b/src/main/scala/ScalatraBootstrap.scala @@ -1,5 +1,7 @@ -import _root_.servlet.{BasicAuthenticationFilter, TransactionFilter, PluginActionFilter} +import _root_.servlet.{BasicAuthenticationFilter, TransactionFilter} import app._ +import plugin.PluginRegistry + //import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider import org.scalatra._ import javax.servlet._ @@ -12,8 +14,6 @@ class ScalatraBootstrap extends LifeCycle { context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter) context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*") - context.addFilter("pluginActionFilter", new PluginActionFilter) - context.getFilterRegistration("pluginActionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") // Register controllers context.mount(new AnonymousAccessController, "/*") @@ -24,6 +24,11 @@ class ScalatraBootstrap extends LifeCycle { context.mount(new UserManagementController, "/*") context.mount(new SystemSettingsController, "/*") context.mount(new AccountController, "/*") + + PluginRegistry().getControllers.foreach { case (controller, path) => + context.mount(controller, path) + } + context.mount(new RepositoryViewerController, "/*") context.mount(new WikiController, "/*") context.mount(new LabelsController, "/*") diff --git a/src/main/scala/plugin/Plugin.scala b/src/main/scala/plugin/Plugin.scala index e7976995d..c17687466 100644 --- a/src/main/scala/plugin/Plugin.scala +++ b/src/main/scala/plugin/Plugin.scala @@ -1,5 +1,7 @@ package plugin +import javax.servlet.ServletContext + import util.Version /** diff --git a/src/main/scala/plugin/PluginRegistory.scala b/src/main/scala/plugin/PluginRegistory.scala index c924922bf..bb7c2cb3b 100644 --- a/src/main/scala/plugin/PluginRegistory.scala +++ b/src/main/scala/plugin/PluginRegistory.scala @@ -2,6 +2,7 @@ package plugin import java.io.{FilenameFilter, File} import java.net.URLClassLoader +import javax.servlet.ServletContext import javax.servlet.http.{HttpServletRequest, HttpServletResponse} import org.slf4j.LoggerFactory @@ -11,14 +12,13 @@ import util.JDBCUtil._ import util.{Version, Versions} import scala.collection.mutable.ListBuffer -import app.Context +import app.{ControllerBase, Context} class PluginRegistry { private val plugins = new ListBuffer[PluginInfo] private val javaScripts = new ListBuffer[(String, String)] - private val globalActions = new ListBuffer[GlobalAction] - private val repositoryActions = new ListBuffer[RepositoryAction] + private val controllers = new ListBuffer[(ControllerBase, String)] def addPlugin(pluginInfo: PluginInfo): Unit = { plugins += pluginInfo @@ -26,28 +26,11 @@ class PluginRegistry { def getPlugins(): List[PluginInfo] = plugins.toList - def addGlobalAction(method: String, path: String)(f: (HttpServletRequest, HttpServletResponse, Context) => Any): Unit = { - globalActions += GlobalAction(method.toLowerCase, path, f) + def addController(controller: ControllerBase, path: String): Unit = { + controllers += ((controller, path)) } - //def getGlobalActions(): List[GlobalAction] = globalActions.toList - - def getGlobalAction(method: String, path: String): Option[(HttpServletRequest, HttpServletResponse, Context) => Any] = { - globalActions.find { globalAction => - globalAction.method == method.toLowerCase && path.matches(globalAction.path) - }.map(_.function) - } - - def addRepositoryAction(method: String, path: String)(f: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any): Unit = { - repositoryActions += RepositoryAction(method.toLowerCase, path, f) - } - - //def getRepositoryActions(): List[RepositoryAction] = repositoryActions.toList - - def getRepositoryAction(method: String, path: String): Option[(HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any] = { - // TODO - null - } + def getControllers(): List[(ControllerBase, String)] = controllers.toList def addJavaScript(path: String, script: String): Unit = { javaScripts += Tuple2(path, script) @@ -90,7 +73,7 @@ object PluginRegistry { /** * Initializes all installed plugins. */ - def initialize(conn: java.sql.Connection): Unit = { + def initialize(context: ServletContext, conn: java.sql.Connection): Unit = { val pluginDir = new File(PluginHome) if(pluginDir.exists && pluginDir.isDirectory){ pluginDir.listFiles(new FilenameFilter { @@ -138,7 +121,7 @@ object PluginRegistry { } } - def shutdown(): Unit = { + def shutdown(context: ServletContext): Unit = { instance.getPlugins().foreach { pluginInfo => try { pluginInfo.pluginClass.shutdown(instance) diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/InitializeListener.scala similarity index 94% rename from src/main/scala/servlet/AutoUpdateListener.scala rename to src/main/scala/servlet/InitializeListener.scala index 2c198ba95..fdc3c48bf 100644 --- a/src/main/scala/servlet/AutoUpdateListener.scala +++ b/src/main/scala/servlet/InitializeListener.scala @@ -162,12 +162,13 @@ object AutoUpdate { } /** - * Update database schema automatically in the context initializing. + * Initialize GitBucket system. + * Update database schema and load plug-ins automatically in the context initializing. */ -class AutoUpdateListener extends ServletContextListener { +class InitializeListener extends ServletContextListener { import AutoUpdate._ - private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener]) + private val logger = LoggerFactory.getLogger(classOf[InitializeListener]) override def contextInitialized(event: ServletContextEvent): Unit = { val dataDir = event.getServletContext.getInitParameter("gitbucket.home") @@ -184,14 +185,14 @@ class AutoUpdateListener extends ServletContextListener { } // Load plugins logger.debug("Initialize plugins") - PluginRegistry.initialize(conn) + PluginRegistry.initialize(event.getServletContext, conn) } } - def contextDestroyed(sce: ServletContextEvent): Unit = { + def contextDestroyed(event: ServletContextEvent): Unit = { // Shutdown plugins - PluginRegistry.shutdown() + PluginRegistry.shutdown(event.getServletContext) } private def getConnection(): Connection = diff --git a/src/main/scala/servlet/PluginActionFilter.scala b/src/main/scala/servlet/PluginActionFilter.scala deleted file mode 100644 index 1ef7642f6..000000000 --- a/src/main/scala/servlet/PluginActionFilter.scala +++ /dev/null @@ -1,60 +0,0 @@ -package servlet - -import javax.servlet._ -import javax.servlet.http.{HttpServletResponse, HttpServletRequest} - -import model.Account -import play.twirl.api.Html -import plugin.PluginRegistry -import service.SystemSettingsService -import util.Keys -import app.Context -import plugin.Results._ -import plugin.Sessions._ - -class PluginActionFilter extends Filter with SystemSettingsService { - - def init(config: FilterConfig) = {} - - def destroy(): Unit = {} - - def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = (req, res) match { - case (req: HttpServletRequest, res: HttpServletResponse) => { - val method = req.getMethod.toLowerCase - val path = req.getRequestURI.substring(req.getContextPath.length) - val registry = PluginRegistry() - - registry.getGlobalAction(method, path).map { action => - // Create Context - val loginAccount = req.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account] - implicit val context = Context(loadSystemSettings(), Option(loginAccount), req) - sessions.set(Database.getSession(req)) - try { - // Invoke global action - action(req, res, context) match { - // TODO to be type classes? - case x: String => - res.setContentType("text/plain; charset=UTF-8") - res.getWriter.write(x) - res.getWriter.flush() - case x: Html => - res.setContentType("text/html; charset=UTF-8") - // TODO title of plugin action - res.getWriter.write(html.main("TODO")(x).body) - res.getWriter.flush() - case Redirect(x) => - res.sendRedirect(x) - case Fragment(x) => - res.getWriter.write(x.body) - res.getWriter.flush() - } - } finally { - sessions.remove() - } - }.getOrElse { - chain.doFilter(req, res) - } - } - } - -} diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 51513a1e8..c015d240d 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -12,10 +12,10 @@ - + - servlet.AutoUpdateListener + servlet.InitializeListener From 1bed38f175ac598007db955879382886c220fbfa Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Fri, 20 Feb 2015 13:08:38 +0900 Subject: [PATCH 14/14] Change order of plug-in controller registration --- src/main/scala/ScalatraBootstrap.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala index c2dfb3e23..cfa37e62d 100644 --- a/src/main/scala/ScalatraBootstrap.scala +++ b/src/main/scala/ScalatraBootstrap.scala @@ -17,6 +17,11 @@ class ScalatraBootstrap extends LifeCycle { // Register controllers context.mount(new AnonymousAccessController, "/*") + + PluginRegistry().getControllers.foreach { case (controller, path) => + context.mount(controller, path) + } + context.mount(new IndexController, "/") context.mount(new SearchController, "/") context.mount(new FileUploadController, "/upload") @@ -24,11 +29,6 @@ class ScalatraBootstrap extends LifeCycle { context.mount(new UserManagementController, "/*") context.mount(new SystemSettingsController, "/*") context.mount(new AccountController, "/*") - - PluginRegistry().getControllers.foreach { case (controller, path) => - context.mount(controller, path) - } - context.mount(new RepositoryViewerController, "/*") context.mount(new WikiController, "/*") context.mount(new LabelsController, "/*")