mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-08 14:35:52 +01:00
Merge branch 'new-plugin-system'
Conflicts: src/main/scala/servlet/InitializeListener.scala
This commit is contained in:
9
etc/deploy-assemby-jar.sh
Normal file
9
etc/deploy-assemby-jar.sh
Normal file
@@ -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/
|
||||||
17
etc/pom.xml
Normal file
17
etc/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>jp.sf.amateras</groupId>
|
||||||
|
<artifactId>gitbucket-assembly</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
<build>
|
||||||
|
<extensions>
|
||||||
|
<extension>
|
||||||
|
<groupId>org.apache.maven.wagon</groupId>
|
||||||
|
<artifactId>wagon-ssh</artifactId>
|
||||||
|
<version>1.0-beta-6</version>
|
||||||
|
</extension>
|
||||||
|
</extensions>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -4,6 +4,8 @@ import org.scalatra.sbt._
|
|||||||
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
|
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
|
||||||
import play.twirl.sbt.SbtTwirl
|
import play.twirl.sbt.SbtTwirl
|
||||||
import play.twirl.sbt.Import.TwirlKeys._
|
import play.twirl.sbt.Import.TwirlKeys._
|
||||||
|
import sbtassembly._
|
||||||
|
import sbtassembly.AssemblyKeys._
|
||||||
|
|
||||||
object MyBuild extends Build {
|
object MyBuild extends Build {
|
||||||
val Organization = "jp.sf.amateras"
|
val Organization = "jp.sf.amateras"
|
||||||
@@ -17,6 +19,17 @@ object MyBuild extends Build {
|
|||||||
file(".")
|
file(".")
|
||||||
)
|
)
|
||||||
.settings(ScalatraPlugin.scalatraWithJRebel: _*)
|
.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(
|
.settings(
|
||||||
sourcesInBase := false,
|
sourcesInBase := false,
|
||||||
organization := Organization,
|
organization := Organization,
|
||||||
@@ -45,7 +58,7 @@ object MyBuild extends Build {
|
|||||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
"com.typesafe.slick" %% "slick" % "2.1.0",
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
"com.h2database" % "h2" % "1.4.180",
|
"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" % "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"),
|
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"),
|
||||||
"junit" % "junit" % "4.11" % "test",
|
"junit" % "junit" % "4.11" % "test",
|
||||||
|
|||||||
@@ -7,3 +7,5 @@ addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.5")
|
|||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.2")
|
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.2")
|
||||||
|
|
||||||
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.4")
|
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.4")
|
||||||
|
|
||||||
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import _root_.servlet.{BasicAuthenticationFilter, TransactionFilter}
|
import _root_.servlet.{BasicAuthenticationFilter, TransactionFilter}
|
||||||
import app._
|
import app._
|
||||||
|
import plugin.PluginRegistry
|
||||||
|
|
||||||
//import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider
|
//import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
import javax.servlet._
|
import javax.servlet._
|
||||||
@@ -15,6 +17,11 @@ class ScalatraBootstrap extends LifeCycle {
|
|||||||
|
|
||||||
// Register controllers
|
// Register controllers
|
||||||
context.mount(new AnonymousAccessController, "/*")
|
context.mount(new AnonymousAccessController, "/*")
|
||||||
|
|
||||||
|
PluginRegistry().getControllers.foreach { case (controller, path) =>
|
||||||
|
context.mount(controller, path)
|
||||||
|
}
|
||||||
|
|
||||||
context.mount(new IndexController, "/")
|
context.mount(new IndexController, "/")
|
||||||
context.mount(new SearchController, "/")
|
context.mount(new SearchController, "/")
|
||||||
context.mount(new FileUploadController, "/upload")
|
context.mount(new FileUploadController, "/upload")
|
||||||
|
|||||||
30
src/main/scala/plugin/Plugin.scala
Normal file
30
src/main/scala/plugin/Plugin.scala
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext
|
||||||
|
|
||||||
|
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
|
||||||
|
val pluginName: String
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
145
src/main/scala/plugin/PluginRegistory.scala
Normal file
145
src/main/scala/plugin/PluginRegistory.scala
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
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
|
||||||
|
import service.RepositoryService.RepositoryInfo
|
||||||
|
import util.Directory._
|
||||||
|
import util.JDBCUtil._
|
||||||
|
import util.{Version, Versions}
|
||||||
|
|
||||||
|
import scala.collection.mutable.ListBuffer
|
||||||
|
import app.{ControllerBase, Context}
|
||||||
|
|
||||||
|
class PluginRegistry {
|
||||||
|
|
||||||
|
private val plugins = new ListBuffer[PluginInfo]
|
||||||
|
private val javaScripts = new ListBuffer[(String, String)]
|
||||||
|
private val controllers = new ListBuffer[(ControllerBase, String)]
|
||||||
|
|
||||||
|
def addPlugin(pluginInfo: PluginInfo): Unit = {
|
||||||
|
plugins += pluginInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
def getPlugins(): List[PluginInfo] = plugins.toList
|
||||||
|
|
||||||
|
def addController(controller: ControllerBase, path: String): Unit = {
|
||||||
|
controllers += ((controller, path))
|
||||||
|
}
|
||||||
|
|
||||||
|
def getControllers(): List[(ControllerBase, String)] = controllers.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] = {
|
||||||
|
javaScripts.find(x => currentPath.matches(x._1)).map(_._2)
|
||||||
|
}
|
||||||
|
|
||||||
|
private case class GlobalAction(
|
||||||
|
method: String,
|
||||||
|
path: String,
|
||||||
|
function: (HttpServletRequest, HttpServletResponse, Context) => Any
|
||||||
|
)
|
||||||
|
|
||||||
|
private case class RepositoryAction(
|
||||||
|
method: String,
|
||||||
|
path: String,
|
||||||
|
function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(context: ServletContext, 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]
|
||||||
|
|
||||||
|
// 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.versions.head.versionString,
|
||||||
|
description = plugin.description,
|
||||||
|
pluginClass = plugin
|
||||||
|
))
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
case e: Exception => {
|
||||||
|
logger.error(s"Error during plugin initialization", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def shutdown(context: ServletContext): 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
|
||||||
|
)
|
||||||
11
src/main/scala/plugin/Results.scala
Normal file
11
src/main/scala/plugin/Results.scala
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
11
src/main/scala/plugin/Sessions.scala
Normal file
11
src/main/scala/plugin/Sessions.scala
Normal file
@@ -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()
|
||||||
|
}
|
||||||
@@ -4,49 +4,16 @@ import java.io.File
|
|||||||
import java.sql.{DriverManager, Connection}
|
import java.sql.{DriverManager, Connection}
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import javax.servlet.{ServletContextListener, ServletContextEvent}
|
import javax.servlet.{ServletContextListener, ServletContextEvent}
|
||||||
import org.apache.commons.io.IOUtils
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import util.Directory._
|
import util.Directory._
|
||||||
import util.ControlUtil._
|
import util.ControlUtil._
|
||||||
import util.JDBCUtil._
|
import util.JDBCUtil._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
import util.{Version, Versions}
|
||||||
|
import plugin._
|
||||||
import util.{DatabaseConfig, Directory}
|
import util.{DatabaseConfig, Directory}
|
||||||
|
|
||||||
object AutoUpdate {
|
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.
|
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||||
@@ -54,8 +21,8 @@ object AutoUpdate {
|
|||||||
val versions = Seq(
|
val versions = Seq(
|
||||||
new Version(2, 8),
|
new Version(2, 8),
|
||||||
new Version(2, 7) {
|
new Version(2, 7) {
|
||||||
override def update(conn: Connection): Unit = {
|
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
||||||
super.update(conn)
|
super.update(conn, cl)
|
||||||
conn.select("SELECT * FROM REPOSITORY"){ rs =>
|
conn.select("SELECT * FROM REPOSITORY"){ rs =>
|
||||||
// Rename attached files directory from /issues to /comments
|
// Rename attached files directory from /issues to /comments
|
||||||
val userName = rs.getString("USER_NAME")
|
val userName = rs.getString("USER_NAME")
|
||||||
@@ -93,8 +60,8 @@ object AutoUpdate {
|
|||||||
new Version(2, 5),
|
new Version(2, 5),
|
||||||
new Version(2, 4),
|
new Version(2, 4),
|
||||||
new Version(2, 3) {
|
new Version(2, 3) {
|
||||||
override def update(conn: Connection): Unit = {
|
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
||||||
super.update(conn)
|
super.update(conn, cl)
|
||||||
conn.select("SELECT ACTIVITY_ID, ADDITIONAL_INFO FROM ACTIVITY WHERE ACTIVITY_TYPE='push'"){ rs =>
|
conn.select("SELECT ACTIVITY_ID, ADDITIONAL_INFO FROM ACTIVITY WHERE ACTIVITY_TYPE='push'"){ rs =>
|
||||||
val curInfo = rs.getString("ADDITIONAL_INFO")
|
val curInfo = rs.getString("ADDITIONAL_INFO")
|
||||||
val newInfo = curInfo.split("\n").filter(_ matches "^[0-9a-z]{40}:.*").mkString("\n")
|
val newInfo = curInfo.split("\n").filter(_ matches "^[0-9a-z]{40}:.*").mkString("\n")
|
||||||
@@ -102,20 +69,22 @@ object AutoUpdate {
|
|||||||
conn.update("UPDATE ACTIVITY SET ADDITIONAL_INFO = ? WHERE ACTIVITY_ID = ?", newInfo, rs.getInt("ACTIVITY_ID"))
|
conn.update("UPDATE ACTIVITY SET ADDITIONAL_INFO = ? WHERE ACTIVITY_ID = ?", newInfo, rs.getInt("ACTIVITY_ID"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FileUtils.deleteDirectory(Directory.getPluginCacheDir())
|
ignore {
|
||||||
FileUtils.deleteDirectory(new File(Directory.PluginHome))
|
FileUtils.deleteDirectory(Directory.getPluginCacheDir())
|
||||||
|
//FileUtils.deleteDirectory(new File(Directory.PluginHome))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Version(2, 2),
|
new Version(2, 2),
|
||||||
new Version(2, 1),
|
new Version(2, 1),
|
||||||
new Version(2, 0){
|
new Version(2, 0){
|
||||||
override def update(conn: Connection): Unit = {
|
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
||||||
import eu.medsea.mimeutil.{MimeUtil2, MimeType}
|
import eu.medsea.mimeutil.{MimeUtil2, MimeType}
|
||||||
|
|
||||||
val mimeUtil = new MimeUtil2()
|
val mimeUtil = new MimeUtil2()
|
||||||
mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector")
|
mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector")
|
||||||
|
|
||||||
super.update(conn)
|
super.update(conn, cl)
|
||||||
conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs =>
|
conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs =>
|
||||||
defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir =>
|
defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir =>
|
||||||
if(dir.exists && dir.isDirectory){
|
if(dir.exists && dir.isDirectory){
|
||||||
@@ -143,8 +112,8 @@ object AutoUpdate {
|
|||||||
Version(1, 5),
|
Version(1, 5),
|
||||||
Version(1, 4),
|
Version(1, 4),
|
||||||
new Version(1, 3){
|
new Version(1, 3){
|
||||||
override def update(conn: Connection): Unit = {
|
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
||||||
super.update(conn)
|
super.update(conn, cl)
|
||||||
// Fix wiki repository configuration
|
// Fix wiki repository configuration
|
||||||
conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs =>
|
conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs =>
|
||||||
using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
|
using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
|
||||||
@@ -193,14 +162,14 @@ 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._
|
import AutoUpdate._
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener])
|
private val logger = LoggerFactory.getLogger(classOf[InitializeListener])
|
||||||
// private val scheduler = StdSchedulerFactory.getDefaultScheduler
|
|
||||||
|
|
||||||
override def contextInitialized(event: ServletContextEvent): Unit = {
|
override def contextInitialized(event: ServletContextEvent): Unit = {
|
||||||
val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
|
val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
|
||||||
if(dataDir != null){
|
if(dataDir != null){
|
||||||
@@ -209,31 +178,21 @@ class AutoUpdateListener extends ServletContextListener {
|
|||||||
org.h2.Driver.load()
|
org.h2.Driver.load()
|
||||||
|
|
||||||
defining(getConnection()){ conn =>
|
defining(getConnection()){ conn =>
|
||||||
|
// Migration
|
||||||
logger.debug("Start schema update")
|
logger.debug("Start schema update")
|
||||||
try {
|
Versions.update(conn, headVersion, getCurrentVersion(), versions, Thread.currentThread.getContextClassLoader){ conn =>
|
||||||
defining(getCurrentVersion()){ currentVersion =>
|
FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
logger.debug("End schema update")
|
// Load plugins
|
||||||
|
logger.debug("Initialize plugins")
|
||||||
|
PluginRegistry.initialize(event.getServletContext, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def contextDestroyed(sce: ServletContextEvent): Unit = {
|
def contextDestroyed(event: ServletContextEvent): Unit = {
|
||||||
|
// Shutdown plugins
|
||||||
|
PluginRegistry.shutdown(event.getServletContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def getConnection(): Connection =
|
private def getConnection(): Connection =
|
||||||
@@ -37,4 +37,10 @@ object ControlUtil {
|
|||||||
def using[T](treeWalk: TreeWalk)(f: TreeWalk => T): T =
|
def using[T](treeWalk: TreeWalk)(f: TreeWalk => T): T =
|
||||||
try f(treeWalk) finally treeWalk.release()
|
try f(treeWalk) finally treeWalk.release()
|
||||||
|
|
||||||
|
def ignore[T](f: => Unit): Unit = try {
|
||||||
|
f
|
||||||
|
} catch {
|
||||||
|
case e: Exception => ()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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] = {
|
def select[T](sql: String, params: Any*)(f: ResultSet => T): Seq[T] = {
|
||||||
execute(sql, params: _*){ stmt =>
|
execute(sql, params: _*){ stmt =>
|
||||||
using(stmt.executeQuery()){ rs =>
|
using(stmt.executeQuery()){ rs =>
|
||||||
|
|||||||
67
src/main/scala/util/Version.scala
Normal file
67
src/main/scala/util/Version.scala
Normal file
@@ -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: Seq[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")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -82,5 +82,10 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@plugin.PluginRegistry().getJavaScript(request.getRequestURI).map { script =>
|
||||||
|
<script>
|
||||||
|
@Html(script)
|
||||||
|
</script>
|
||||||
|
}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -12,10 +12,10 @@
|
|||||||
</listener>
|
</listener>
|
||||||
|
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
<!-- Automatic migration -->
|
<!-- Automatic migration and plug-in initialization -->
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
<listener>
|
<listener>
|
||||||
<listener-class>servlet.AutoUpdateListener</listener-class>
|
<listener-class>servlet.InitializeListener</listener-class>
|
||||||
</listener>
|
</listener>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user