Skip to content

Commit 8e77920

Browse files
committed
migrate from derby to h2, derby is retired.
1 parent 9c975e5 commit 8e77920

File tree

12 files changed

+204
-91
lines changed

12 files changed

+204
-91
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ plugin for development, just open the project to get started.
7272
* [Java](https://www.java.com/)
7373
* [Scala](http://www.scala-lang.org)
7474
* [Scalafx](http://scalafx.org) as wrapper for [JavaFX](http://docs.oracle.com/javafx) for the graphical user interface
75-
* [Squeryl](http://squeryl.org) as database ORM & DSL, using [Apache Derby](http://db.apache.org/derby) embedded as backend
75+
* [Squeryl](http://squeryl.org) as database ORM & DSL, using [H2](https://www.h2database.com/) embedded as backend
7676
* [Apache Pdfbox](https://pdfbox.apache.org) to access PDF files
7777
* [JBibtex](https://github.com/jbibtex/jbibtex) to parse and write bibtex and latex
7878
* [LaTeX2Unicode](https://github.com/tomtung/latex2unicode)

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ dependencies {
6363
implementation("org.apache.derby:derby:$derbyVersion")
6464
implementation("org.apache.derby:derbytools:$derbyVersion")
6565
implementation("org.apache.derby:derbyshared:$derbyVersion")
66+
implementation("com.h2database:h2:2.4.240")
6667
implementation("org.squeryl:squeryl_2.13:0.10.1")
6768
implementation("org.scala-lang.modules:scala-parser-combinators_2.13:2.4.0")
6869
implementation("org.apache.pdfbox:pdfbox:3.0.6")

src/main/scala/db/DB.scala

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import java.io.OutputStream
44
import java.sql.SQLException
55
import java.text.SimpleDateFormat
66
import java.time.Instant
7-
import org.squeryl.adapters.DerbyAdapter
7+
import org.squeryl.adapters.H2Adapter
88
import org.squeryl.dsl._
99
import org.squeryl._
1010
import org.squeryl.annotations.Transient
1111
import util._
1212
import util.AppStorage
13-
import framework.{ApplicationController, Logging}
13+
import framework.{ApplicationController, Helpers, Logging}
1414
import org.squeryl.internals.LifecycleEvent
1515

1616
import scala.collection.mutable.ArrayBuffer
@@ -151,8 +151,6 @@ class Topic2Article(val TOPIC: Long = 0, val ARTICLE: Long = 0, var color: Int =
151151

152152
object ReftoolDB extends Schema with Logging {
153153

154-
val lastschemaversion = 3
155-
156154
val longFieldLength = 10000
157155
val mediumFieldLength = 4096
158156

@@ -181,7 +179,7 @@ object ReftoolDB extends Schema with Logging {
181179

182180
on(settings)(t => declare(
183181
t.id.is(dbType("varchar(1024)"), named("ID")),
184-
t.value.is(dbType("varchar(1024)"), named("VALUE")), t.value.defaultsTo("")
182+
t.value.is(dbType("varchar(1024)"), named("CONTENT")), t.value.defaultsTo("")
185183
))
186184

187185
on(topics)(t => declare(
@@ -264,18 +262,17 @@ object ReftoolDB extends Schema with Logging {
264262

265263
def dbShutdown(dbpath: String = null): Unit = {
266264
val dbpath2 = if (dbpath == null) AppStorage.config.dbpath else dbpath
267-
try { java.sql.DriverManager.getConnection(s"jdbc:derby:$dbpath2;shutdown=true") } catch {
268-
case se: SQLException => if (!se.getSQLState.equals("08006")) throw se
269-
// case se: SQLNonTransientConnectionException => if (!se.getSQLState.equals("08006")) throw se
265+
try { java.sql.DriverManager.getConnection(s"jdbc:h2:$dbpath2;shutdown=true") } catch {
266+
case se: SQLException => if (!se.getSQLState.equals("08006")) throw se // TODO: still needed with H2?
270267
}
271268
}
272-
def dbGetSchemaVersion: Int = {
273-
val res = FileHelper.readString(new MFile(AppStorage.config.dbschemaversionpath))
269+
def dbGetSchemaVersion(dbpath: String): Int = {
270+
val res = FileHelper.readString(new MFile(dbpath + "/schemaversion.txt"))
274271
assert(res.nonEmpty, "db schema version file not present!")
275272
res.get.toInt
276273
}
277-
def dbSetSchemaVersion(v: Int): Unit = {
278-
FileHelper.writeString(new MFile(AppStorage.config.dbschemaversionpath), v.toString)
274+
def dbSetSchemaVersion(dbpath: String, v: Int): Unit = {
275+
FileHelper.writeString(new MFile(dbpath + "/schemaversion.txt"), v.toString)
279276
}
280277

281278
def getDBstats: String = {
@@ -293,40 +290,51 @@ object ReftoolDB extends Schema with Logging {
293290

294291
def initialize(startwithempty: Boolean): Unit = {
295292

296-
System.setProperty("derby.stream.error.method", "db.DerbyUtil.getOS")
297-
// System.setProperty("derby.language.logStatementText", "true")
298-
299293
val pp = new MFile(AppStorage.config.pdfpath)
300294
if (!pp.exists) pp.mkdir()
301295

302-
if (!startwithempty) {
303-
if (!new MFile(AppStorage.config.dbpath).exists && new MFile(AppStorage.config.olddbpath).exists) {
296+
if (!startwithempty) { // open existing database
297+
// upgrade reftool4 -> reftool5
298+
if (!new MFile(AppStorage.config.dbpath).exists && !new MFile(AppStorage.config.olddbpath5).exists && new MFile(AppStorage.config.olddbpath).exists) {
299+
if (!Helpers.showWarning("About to update your database (4->5), have you backed it up?", "Continue?")) assert(assertion = false, "")
304300
DBupgrades.upgrade4to5()
305-
dbSetSchemaVersion(1)
301+
dbSetSchemaVersion(AppStorage.config.olddbpath5, 1)
302+
assert(new MFile(AppStorage.config.olddbpath5).exists, "Cannot find database path below data directory!")
303+
Helpers.showInformation("Database upgrade finished", s"Please delete the old database folder\n ${AppStorage.config.olddbpath}!")
304+
}
305+
// upgrade reftool5 derby to schema 4, then migrate to h2, schema 5
306+
if (!new MFile(AppStorage.config.dbpath).exists && new MFile(AppStorage.config.olddbpath5).exists) {
307+
if (!Helpers.showWarning("About to update your database (derby->h2), have you backed it up?", "Continue?")) assert(assertion = false, "")
308+
// upgrade derby schema to version 4 (rename SETTING.VALUE -> CONTENT)
309+
assert(dbGetSchemaVersion(AppStorage.config.olddbpath5) <= 4, "derby DB schema version is higher than reftool can handle - old reftool version?")
310+
while (dbGetSchemaVersion(AppStorage.config.olddbpath5) != 4) dbSetSchemaVersion(AppStorage.config.olddbpath5, DBupgrades.upgradeSchema(dbGetSchemaVersion(AppStorage.config.olddbpath5)))
311+
DBupgradesH2.upgrade5to5h2()
312+
dbSetSchemaVersion(AppStorage.config.dbpath, 5)
313+
Helpers.showInformation("Database upgrade finished", s"Please delete the old database folder\n ${AppStorage.config.olddbpath5}!")
306314
}
307315
assert(new MFile(AppStorage.config.dbpath).exists, "Cannot find database path below data directory!")
308316
assert(pp.exists, "Cannot find pdf path below data directory")
309317
// upgrade DB schema if needed
310-
assert(dbGetSchemaVersion <= lastschemaversion, "DB schema version is higher than reftool can handle - old reftool version?")
311-
while (dbGetSchemaVersion != lastschemaversion) dbSetSchemaVersion(DBupgrades.upgradeSchema(dbGetSchemaVersion))
318+
assert(dbGetSchemaVersion(AppStorage.config.dbpath) <= DBupgradesH2.lastschemaversion, "DB schema version is higher than reftool can handle - old reftool version?")
319+
if (dbGetSchemaVersion(AppStorage.config.dbpath) != DBupgradesH2.lastschemaversion)
320+
if (!Helpers.showWarning("About to update your database, have you backed it up?", "Continue?")) assert(assertion = false, "")
321+
while (dbGetSchemaVersion(AppStorage.config.dbpath) != DBupgradesH2.lastschemaversion) dbSetSchemaVersion(AppStorage.config.dbpath, DBupgrades.upgradeSchema(dbGetSchemaVersion(AppStorage.config.dbpath)))
312322
}
313323

314324
info("Loading database at " + AppStorage.config.dbpath + " ...")
315325
val dbs = if (!startwithempty)
316-
s"jdbc:derby:${AppStorage.config.dbpath};upgrade=true"
326+
s"jdbc:h2:${AppStorage.config.dbpath}/h2db;IFEXISTS=TRUE;DB_CLOSE_DELAY=10" //;TRACE_LEVEL_SYSTEM_OUT=2"
317327
else
318-
s"jdbc:derby:${AppStorage.config.dbpath};upgrade=true;create=true"
319-
320-
Class.forName("org.apache.derby.jdbc.EmbeddedDriver")
328+
s"jdbc:h2:${AppStorage.config.dbpath}/h2db"
321329

322-
SessionFactory.concreteFactory = Some(() => Session.create(java.sql.DriverManager.getConnection(dbs), new DerbyAdapter))
330+
SessionFactory.concreteFactory = Some(() => Session.create(java.sql.DriverManager.getConnection(dbs), new H2Adapter))
323331

324332
transaction {
325333
// Session.currentSession.setLogger( s => debug("sq: " + s) )
326-
// ReftoolDB.printDdl
334+
// ReftoolDB.printDdl // print schema
327335
if (startwithempty) {
328336
ReftoolDB.create
329-
dbSetSchemaVersion(lastschemaversion)
337+
dbSetSchemaVersion(AppStorage.config.dbpath, DBupgradesH2.lastschemaversion)
330338
}
331339
// ensure essential topics are present
332340
rootTopic = topics.where(t => t.parent === 0).headOption.orNull
@@ -362,6 +370,7 @@ object ReftoolDB extends Schema with Logging {
362370

363371
}
364372

373+
// TODO
365374
object DerbyUtil extends Logging {
366375
var sss = ""
367376
def getOS: OutputStream = (b: Int) => {

src/main/scala/db/DBupgrades.scala

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
11
package db
22

3-
import java.sql.Connection
4-
3+
import java.sql.{Connection, SQLException}
54
import framework.Logging
65
import util.AppStorage
7-
import ReftoolDB.dbShutdown
86

7+
// This is only for derby upgrades, derby is now retired
98

109
object DBupgrades extends Logging {
1110

12-
def dbGetConnection: Connection = java.sql.DriverManager.getConnection(s"jdbc:derby:${AppStorage.config.dbpath};upgrade=true")
11+
val lastschemaversion = 4
12+
13+
def dbGetConnection: Connection = java.sql.DriverManager.getConnection(s"jdbc:derby:${AppStorage.config.olddbpath5};upgrade=true")
14+
15+
def dbShutdown(dbpath: String = null): Unit = {
16+
val dbpath2 = if (dbpath == null) AppStorage.config.olddbpath5 else dbpath
17+
try { java.sql.DriverManager.getConnection(s"jdbc:derby:$dbpath2;shutdown=true") } catch {
18+
case se: SQLException => if (!se.getSQLState.equals("08006")) throw se
19+
// case se: SQLNonTransientConnectionException => if (!se.getSQLState.equals("08006")) throw se
20+
}
21+
}
1322

1423
def upgradeSchema(oldv: Int): Int = {
15-
info("upgrade database from schemaversion " + oldv + " ...")
24+
info("upgrade derby database from schemaversion " + oldv + " ...")
1625
val dbc = dbGetConnection
1726
val s = dbc.createStatement()
1827
val newVersion = oldv match {
@@ -23,6 +32,9 @@ object DBupgrades extends Logging {
2332
case 2 =>
2433
s.execute("alter table ARTICLES add column LASTTIMESTAMP bigint not null default 0")
2534
3
35+
case 3 =>
36+
s.execute("rename column SETTING.VALUE to CONTENT")
37+
4
2638
}
2739
dbc.close()
2840
dbShutdown()
@@ -69,7 +81,7 @@ object DBupgrades extends Logging {
6981
debug("checking old db...")
7082
dbStats(AppStorage.config.olddbpath, clobx = true)
7183
debug("importing db...")
72-
java.sql.DriverManager.getConnection(s"jdbc:derby:${AppStorage.config.dbpath};createFrom=${AppStorage.config.olddbpath}")
84+
java.sql.DriverManager.getConnection(s"jdbc:derby:${AppStorage.config.olddbpath5};createFrom=${AppStorage.config.olddbpath}")
7385
dbShutdown()
7486
debug("modify db (hard upgrade if needed)...")
7587
val dbc = dbGetConnection
@@ -130,7 +142,7 @@ object DBupgrades extends Logging {
130142
dbc.close()
131143
dbShutdown()
132144
debug("checking new db...")
133-
dbStats(AppStorage.config.dbpath, clobx = false)
145+
dbStats(AppStorage.config.olddbpath5, clobx = false)
134146
info("Upgrade4to5 finished!")
135147
}
136148

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package db
2+
3+
import db.SquerylEntrypointForMyApp.transaction
4+
import framework.Logging
5+
import org.squeryl.adapters.H2Adapter
6+
import org.squeryl.{Session, SessionFactory}
7+
import util.AppStorage
8+
9+
import java.sql.{Connection, ResultSet, Statement}
10+
11+
object DBupgradesH2 extends Logging {
12+
13+
val lastschemaversion = 5
14+
15+
def upgrade5to5h2(): Unit = {
16+
info("upgrade5to5h2 of " + AppStorage.config.olddbpath5 + " to " + AppStorage.config.dbpath)
17+
// create new h2 database schema with squeryl, messes up column order!
18+
var h2con: Connection = java.sql.DriverManager.getConnection(s"jdbc:h2:${AppStorage.config.dbpath}/h2db")
19+
SessionFactory.concreteFactory = Some(() => Session.create(h2con, new H2Adapter))
20+
transaction {
21+
ReftoolDB.create
22+
}
23+
// reconnect
24+
h2con = java.sql.DriverManager.getConnection(s"jdbc:h2:${AppStorage.config.dbpath}/h2db")
25+
// connect via jdbc to old db
26+
val derbycon: Connection = java.sql.DriverManager.getConnection(s"jdbc:derby:${AppStorage.config.olddbpath5}")
27+
// copy over data
28+
copyTable(derbycon, h2con, "SETTING", 1)
29+
copyTable(derbycon, h2con, "TOPICS", 20)
30+
copyTable(derbycon, h2con, "ARTICLES", 100)
31+
copyTable(derbycon, h2con, "TOPIC2ARTICLE", 100)
32+
h2con.close()
33+
derbycon.close()
34+
}
35+
36+
// based on https://technology.amis.nl/software-development/java/jdbc-database-table-contents-copy/
37+
def copyTable(fromDB: Connection, toDB: Connection, tableName: String, debugmod: Int): Unit = {
38+
info(s"copytable: table=$tableName")
39+
def surroundWithQuotesAddToBuffer(coltype: String, sb: StringBuffer, o: AnyRef): Unit = {
40+
if (coltype == "uniqueidentifier" || coltype == "string" || coltype == "varchar2" || coltype == "varchar" || coltype == "datetime") if (o != null) {
41+
sb.append("\'")
42+
val string = o.toString
43+
val newString = string.replaceAll("\'", "\'\'")
44+
sb.append(newString)
45+
sb.append("\'")
46+
}
47+
else sb.append("null")
48+
else sb.append(o)
49+
}
50+
51+
var fromStatement: Statement = null
52+
var toStatement: Statement = null
53+
var resultSet: ResultSet = null
54+
fromStatement = fromDB.createStatement
55+
toStatement = toDB.createStatement
56+
resultSet = fromStatement.executeQuery("select * from " + tableName)
57+
// Find out the number of columns
58+
val columnCount = resultSet.getMetaData.getColumnCount
59+
var rowCount = 0
60+
while (resultSet.next()) {
61+
rowCount += 1
62+
var o: AnyRef = null
63+
var i = 0
64+
val insertValues = new StringBuffer
65+
var insertColumnNames = ""
66+
if (insertColumnNames == "") { // only do once
67+
for (i <- 1 to columnCount) {
68+
insertColumnNames += resultSet.getMetaData.getColumnName(i)
69+
if (i != columnCount) insertColumnNames += ","
70+
}
71+
}
72+
for (i <- 1 to columnCount) {
73+
o = resultSet.getObject(i)
74+
val coltype = resultSet.getMetaData.getColumnTypeName(i).toLowerCase
75+
surroundWithQuotesAddToBuffer(coltype, insertValues, o)
76+
if (i != columnCount) insertValues.append(", ")
77+
}
78+
val s = s"insert into $tableName ($insertColumnNames) values ($insertValues)"
79+
if (rowCount % debugmod == 0) info(s"copytable row $rowCount : $s")
80+
toStatement.executeUpdate(s)
81+
}
82+
fromStatement.close()
83+
toStatement.close()
84+
resultSet.close()
85+
}
86+
87+
}

src/main/scala/framework/Helpers.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,27 @@ object Helpers extends Logging {
140140
}.showAndWait()
141141
}
142142

143+
// returns true if ok clicked, false for cancel
144+
def showWarning(header: String, content: String): Boolean = {
145+
val res = new Alert(AlertType.Confirmation) {
146+
title = "Confirmation"
147+
headerText = header
148+
contentText = content
149+
}.showAndWait()
150+
res match {
151+
case Some(ButtonType.OK) => true
152+
case _ => false
153+
}
154+
}
155+
156+
def showInformation(header: String, content: String): Unit = {
157+
val res = new Alert(AlertType.Information) {
158+
title = "Confirmation"
159+
headerText = header
160+
contentText = content
161+
}.showAndWait()
162+
}
163+
143164
// https://stackoverflow.com/a/22404140
144165
import java.net.URISyntaxException
145166
def getClassBuildTime: Date = {

src/main/scala/util/AppStorage.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ class Config extends Logging {
1818
var autshrinkpdfs = false
1919
var gspath = ""
2020

21-
def dbpath: String = datadir + "/db5"
22-
def dbschemaversionpath: String = dbpath + "/schemaversion.txt"
21+
def dbpath: String = datadir + "/db5h2" // reftool5 with h2 database
2322
def olddbpath: String = datadir + "/db" // reftool4
23+
def olddbpath5: String = datadir + "/db5" // reftool5 before h2
2424
def pdfpath: String = datadir + "/files"
2525
val importfolderprefix = "folder-" // after this a number to limit #files in folder
2626
val csspath: String = getClass.getResource("/reftool.css").toExternalForm

src/main/scala/views/ArticleDetailView.scala

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,7 @@ class ArticleDetailView extends GenericView("articledetailview") with Logging {
2727

2828
override def canClose: Boolean = {
2929
if (isDirty.value) {
30-
val res = new MyAlert(AlertType.Confirmation) {
31-
headerText = "Application close requested"
32-
contentText = "Press OK to discard changes to Article \n" + article
33-
}.showAndWait()
34-
res match {
35-
case Some(ButtonType.OK) => true
36-
case _ => false
37-
}
30+
Helpers.showWarning("Application close requested", "Press OK to discard changes to Article \n" + article)
3831
} else true
3932
}
4033

src/main/scala/views/ArticleDocumentsView.scala

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package views
33
import db.{Article, Document, ReftoolDB}
44
import db.SquerylEntrypointForMyApp._
55
import framework.Helpers.{FixedSfxTooltip, MyAlert}
6-
import framework.{ApplicationController, GenericView, Logging, MyAction}
6+
import framework.{ApplicationController, GenericView, Helpers, Logging, MyAction}
77
import util.FileHelper._
88
import util.{FileHelper, ImportHelper, MFile}
99

@@ -80,17 +80,15 @@ class ArticleDocumentsView extends GenericView("articledocumentsview") with Logg
8080
tooltipString = "Delete selected documents from article and delete document"
8181
image = new Image(getClass.getResource("/images/delete_obj.gif").toExternalForm)
8282
action = _ => {
83-
new MyAlert(AlertType.Confirmation, "Really deleted selected documents", ButtonType.Yes, ButtonType.No).showAndWait() match {
84-
case Some(ButtonType.Yes) =>
85-
val ds = new ArrayBuffer[Document] ++ lv.selectionModel.value.getSelectedItems
86-
ds.foreach( dd => {
87-
getDocumentFileAbs(dd.docPath).delete()
88-
lv.getItems.remove(dd)
89-
})
90-
ApplicationController.showNotification(s"Document deleted!")
91-
updateArticle()
92-
setArticle(article)
93-
case _ =>
83+
if (Helpers.showWarning("Really deleted selected documents?", "")) {
84+
val ds = new ArrayBuffer[Document] ++ lv.selectionModel.value.getSelectedItems
85+
ds.foreach(dd => {
86+
getDocumentFileAbs(dd.docPath).delete()
87+
lv.getItems.remove(dd)
88+
})
89+
ApplicationController.showNotification(s"Document deleted!")
90+
updateArticle()
91+
setArticle(article)
9492
}
9593
}
9694
}

0 commit comments

Comments
 (0)