Skip to content

Commit 7b11c65

Browse files
authored
Merge pull request #41 from sievericcardo/triple-store
#40 Fuseki Triple store
2 parents a1dd0c5 + 78d8a94 commit 7b11c65

File tree

12 files changed

+320
-14
lines changed

12 files changed

+320
-14
lines changed

DockerfileFuseki

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# syntax=docker/dockerfile:1.3-labs
2+
3+
# To build the container:
4+
# docker build -f DockerfileFuseki -t fuseki .
5+
# To run smol in the current directory:
6+
# docker run -it --rm -v "$PWD":/root/smol -p 3030:3030 fuseki
7+
8+
# Use a base image, for example, Ubuntu
9+
FROM ubuntu:latest
10+
11+
RUN <<EOF
12+
apt-get -y update
13+
DEBIAN_FRONTEND=noninteractive apt-get -y install openjdk-17-jdk-headless python3 python3-pip liblapack3 wget
14+
rm -rf /var/lib/apt/lists/*
15+
pip3 install pyzmq
16+
EOF
17+
COPY . /usr/local/src/smol
18+
COPY src/test/resources/docker_fuseki_config.ttl /usr/local/src/smol
19+
COPY src/test/resources/tree_shapes.ttl /usr/local/src/smol
20+
21+
# Set the working directory
22+
WORKDIR /usr/local/src/smol
23+
24+
# Download and copy the tar.gz file into the container
25+
ADD https://dlcdn.apache.org/jena/binaries/apache-jena-fuseki-4.10.0.tar.gz /usr/local/src/smol
26+
27+
# Extract the tar.gz file
28+
RUN tar -xvzf apache-jena-fuseki-4.10.0.tar.gz
29+
30+
# Execute the command
31+
CMD /usr/local/src/smol/apache-jena-fuseki-4.10.0/fuseki-server --update --config docker_fuseki_config.ttl
32+
33+
# Expose port 3030
34+
EXPOSE 3030
35+

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies {
3131
testImplementation 'io.kotest:kotest-runner-junit5:5.2.3'
3232
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit'
3333
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
34+
testImplementation 'org.apache.jena:jena-fuseki-main:3.16.0'
3435
implementation 'com.github.ajalt.clikt:clikt:3.4.2'
3536
implementation 'org.antlr:antlr4:4.8'
3637
antlr 'org.antlr:antlr4:4.8'

fuseki_server_test.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/bash
2+
3+
# Build the fuseki image
4+
docker build -f DockerfileFuseki -t fuseki .
5+
6+
# Run the fuseki image
7+
docker run -d --name fuseki_container -it --rm -v "$PWD":/root/smol -p 3030:3030 fuseki
8+
9+
# Define the FUSEKI_SERVER environment variable
10+
export FUSEKI_DOCKER=true
11+
12+
# Execute the test
13+
./gradlew --stop
14+
./gradlew test --tests no.uio.microobject.test.data.TripleManagerTest
15+
16+
# Stop the fuseki server
17+
kill $(ps aux | grep '[f]useki' | awk '{print $2}')
18+
19+
# Stop the Docker container when done testing (optional)
20+
docker stop fuseki_container
21+
docker kill fuseki_container # to kill the container if docker stop doesn't work
22+
docker rm fuseki_container
23+
24+
# Delete the fuseki image
25+
docker rmi fuseki

src/main/kotlin/no/uio/microobject/data/TripleManager.kt

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import com.github.owlcs.ontapi.OntologyManager
55
import com.github.owlcs.ontapi.config.OntLoaderConfiguration
66
import no.uio.microobject.ast.expr.LiteralExpr
77
import no.uio.microobject.ast.expr.TRUEEXPR
8+
import no.uio.microobject.main.ReasonerMode
89
import java.io.*
910
import no.uio.microobject.main.Settings
11+
import no.uio.microobject.main.testModel
1012
import no.uio.microobject.runtime.*
1113
import no.uio.microobject.type.*
1214
import org.apache.commons.io.IOUtils
@@ -18,7 +20,9 @@ import org.apache.jena.graph.Node_URI
1820
import org.apache.jena.graph.NodeFactory
1921
import org.apache.jena.graph.Triple
2022
import org.apache.jena.graph.compose.MultiUnion
23+
import org.apache.jena.query.*
2124
import org.apache.jena.rdf.model.*
25+
import org.apache.jena.rdfconnection.RDFConnectionFactory
2226
import org.apache.jena.reasoner.Reasoner
2327
import org.apache.jena.reasoner.ReasonerRegistry
2428
import org.apache.jena.util.iterator.ExtendedIterator
@@ -28,14 +32,16 @@ import org.semanticweb.owlapi.model.OWLOntology
2832
import java.net.URL
2933
import java.util.*
3034
import kotlin.collections.HashMap
35+
import kotlin.system.exitProcess
3136

3237

3338
// Settings controlling the TripleManager.
3439
data class TripleSettings(
3540
val sources: HashMap<String,Boolean>, // Which sources to include
3641
val guards: HashMap<String,Boolean>, // If true, then guard clauses are used.
3742
var virtualization: HashMap<String,Boolean>, // If true, virtualization is used. Otherwise, naive method is used.
38-
var jenaReasoner: String, // Must be either off, rdfs or owl
43+
var jenaReasoner: ReasonerMode, // Must be either off, rdfs or owl
44+
var fusekiModel: Model? = null // If given, then this model is used instead of the FusekiGraph
3945
)
4046

4147
// Class managing triples from all the different sources, how to reason over them, and how to query them using SPARQL or DL queries.
@@ -44,10 +50,11 @@ class TripleManager(private val settings: Settings, val staticTable: StaticTable
4450

4551
// Default settings. These can be changed with REPL commands.
4652
var currentTripleSettings = TripleSettings(
47-
sources = hashMapOf("heap" to true, "staticTable" to true, "vocabularyFile" to true, "fmos" to true, "externalOntology" to (settings.background != "")),
53+
sources = hashMapOf("heap" to true, "staticTable" to true, "vocabularyFile" to true, "fmos" to true, "externalOntology" to (settings.background != ""), "urlOntology" to (settings.tripleStore != "")),
4854
guards = hashMapOf("heap" to true, "staticTable" to true),
4955
virtualization = hashMapOf("heap" to true, "staticTable" to true, "fmos" to true),
50-
jenaReasoner = "owl"
56+
jenaReasoner = settings.reasoner,
57+
fusekiModel = null
5158
)
5259

5360

@@ -127,6 +134,11 @@ class TripleManager(private val settings: Settings, val staticTable: StaticTable
127134
if (tripleSettings.sources.getOrDefault("externalOntology", false)) {
128135
includedGraphs.add(getExternalOntologyAsModel().graph)
129136
}
137+
if (tripleSettings.sources.getOrDefault("urlOntology", false)) {
138+
if (tripleSettings.fusekiModel == null)
139+
tripleSettings.fusekiModel = getTripleStoreOntologyAsModel()
140+
includedGraphs.add(tripleSettings.fusekiModel!!.graph)
141+
}
130142
val model = ModelFactory.createModelForGraph(MultiUnion(includedGraphs.toTypedArray()))
131143
for ((key, value) in prefixMap) model.setNsPrefix(key, value) // Adding prefixes
132144
return model
@@ -146,6 +158,21 @@ class TripleManager(private val settings: Settings, val staticTable: StaticTable
146158
return model
147159
}
148160

161+
private fun getTripleStoreOntologyAsModel(): Model {
162+
// Test case only
163+
if (testModel != null) return testModel as Model
164+
// Normal behaviour with a Fuseki environment
165+
return RDFConnectionFactory.connect(settings.tripleStore + "/data").fetch()
166+
}
167+
168+
/**
169+
* Regenerate the triple store model. We'll do so by fetching again the data
170+
* This will be called when the triple store is updated, and we want to update the model.
171+
*/
172+
fun regenerateTripleStoreModel(): Unit {
173+
currentTripleSettings.fusekiModel = getTripleStoreOntologyAsModel()
174+
}
175+
149176
// Returns the Jena model containing statements from vocab.owl
150177
private fun getVocabularyModel(): Model {
151178
val vocabularyModel = ModelFactory.createDefaultModel()
@@ -157,15 +184,13 @@ class TripleManager(private val settings: Settings, val staticTable: StaticTable
157184
return vocabularyModel.read(iStream, null, "TTL")
158185
}
159186

160-
161187
// Get the requested Jena reasoner
162188
private fun getJenaReasoner(tripleSettings: TripleSettings): Reasoner? {
163189
when (tripleSettings.jenaReasoner) {
164-
"off" -> { return null }
165-
"owl" -> { return ReasonerRegistry.getOWLReasoner() }
166-
"rdfs" -> { return ReasonerRegistry.getRDFSReasoner() }
190+
ReasonerMode.off -> { return null }
191+
ReasonerMode.owl -> { return ReasonerRegistry.getOWLReasoner() }
192+
ReasonerMode.rdfs -> { return ReasonerRegistry.getRDFSReasoner() }
167193
}
168-
return null
169194
}
170195

171196
// A custom type of (nice)iterator which takes a list as input and iterates over them.

src/main/kotlin/no/uio/microobject/main/MainKt.kt

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,33 @@ import com.github.ajalt.clikt.parameters.options.*
55
import com.github.ajalt.clikt.parameters.types.path
66
import org.jline.reader.LineReaderBuilder
77
import no.uio.microobject.runtime.REPL
8+
import org.apache.jena.query.QueryFactory
9+
import org.apache.jena.query.ResultSet
10+
import org.apache.jena.rdf.model.Model
11+
import org.apache.jena.rdfconnection.RDFConnectionFactory
812
import java.io.File
913
import java.nio.file.Paths
1014
import kotlin.system.exitProcess
1115

16+
// test cases only
17+
var testModel : Model? = null
18+
19+
enum class ReasonerMode {
20+
off, rdfs, owl
21+
}
22+
1223
data class Settings(var verbose : Boolean, //Verbosity
1324
val materialize : Boolean, //Materialize
1425
var outdir : String, //path of temporary outputs
26+
val tripleStore : String, // url for the triple store database
1527
val background : String, //owl background knowledge
1628
val domainPrefix : String, //prefix used in the domain model (domain:)
1729
val progPrefix : String = "https://github.com/Edkamb/SemanticObjects/Program#", //prefix for the program (prog:)
1830
val runPrefix : String = "https://github.com/Edkamb/SemanticObjects/Run${System.currentTimeMillis()}#", //prefix for this run (run:)
1931
val langPrefix : String = "https://github.com/Edkamb/SemanticObjects#",
2032
val extraPrefixes : HashMap<String, String>,
21-
val useQueryType : Boolean = false
33+
val useQueryType : Boolean = false,
34+
val reasoner : ReasonerMode = ReasonerMode.owl
2235
){
2336
private var prefixMapCache: HashMap<String, String>? = null
2437
fun prefixMap() : HashMap<String, String> {
@@ -78,6 +91,8 @@ class Main : CliktCommand() {
7891
"--load" to "repl", "-l" to "repl",
7992
).default("repl")
8093

94+
private val reasoner by option("--jenaReasoner", "-j", help="Set value of the internally used reasoner to 'off', 'rdfs', or 'owl' (default -> 'owl')").default("owl")
95+
private val tripleStore by option("--sparqlEndpoint", "-s", help="url for SPARQL endpoint")
8196
private val back by option("--back", "-b", help="path to a file containing OWL class definitions as background knowledge.").path()
8297
private val domainPrefix by option("--domain", "-d", help="prefix for domain:.").default("https://github.com/Edkamb/SemanticObjects/ontologies/default#")
8398
private val input by option("--input", "-i", help="path to a .smol file which is loaded on startup.").path()
@@ -94,18 +109,53 @@ class Main : CliktCommand() {
94109
//check that background knowledge exists
95110
var backgr = ""
96111
if(back != null){
112+
assert(tripleStore == null)
97113
val file = File(back.toString())
98114
if(file.exists()){
99115
backgr = file.readText()
100116
}else println("Could not find file for background knowledge: ${file.path}")
101117
}
102118

119+
var tripleStoreUrl = ""
120+
if (tripleStore != null){
121+
assert(back == null)
122+
123+
val url = tripleStore.toString() + "/query"
124+
125+
// We check if the connection exists by querying the triple store for a single element
126+
// If the query fails, we exit the program. There might be a more elegant way of doing it
127+
val conn = RDFConnectionFactory.connect(url)
128+
129+
val query = QueryFactory.create("SELECT * WHERE { ?s ?p ?o } LIMIT 1")
130+
val qexec = conn.query(query)
131+
val result: ResultSet = qexec.execSelect()
132+
133+
// check that we retrieved something
134+
if (!result.hasNext()) {
135+
println("Error: the url for the triple store is not valid.")
136+
exitProcess(-1)
137+
} else {
138+
tripleStoreUrl = tripleStore.toString()
139+
conn.close()
140+
}
141+
}
142+
143+
val reasonerMode = when(reasoner){
144+
"off" -> ReasonerMode.off
145+
"rdfs" -> ReasonerMode.rdfs
146+
"owl" -> ReasonerMode.owl
147+
else -> {
148+
println("Error: the reasoner mode is not valid.")
149+
exitProcess(-1)
150+
}
151+
}
152+
103153
if (input == null && mainMode != "repl"){
104154
println("Error: please specify an input .smol file using \"--input\".")
105155
exitProcess(-1)
106156
}
107157

108-
val repl = REPL( Settings(verbose, materialize, outdir.toString(), backgr, domainPrefix, extraPrefixes=HashMap(extra), useQueryType = queryType))
158+
val repl = REPL( Settings(verbose, materialize, outdir.toString(), tripleStoreUrl, backgr, domainPrefix, extraPrefixes=HashMap(extra), useQueryType = queryType, reasoner = reasonerMode))
109159
if(input != null){
110160
repl.command("read", input.toString())
111161
}

src/main/kotlin/no/uio/microobject/runtime/REPL.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import no.uio.microobject.antlr.WhileParser
1111
import no.uio.microobject.ast.Expression
1212
import no.uio.microobject.ast.Translate
1313
import no.uio.microobject.data.TripleManager
14+
import no.uio.microobject.main.ReasonerMode
1415
import no.uio.microobject.main.Settings
1516
import no.uio.microobject.type.TypeChecker
1617
import org.antlr.v4.runtime.CharStreams
@@ -277,7 +278,7 @@ class REPL(private val settings: Settings) {
277278
else {
278279
if (!allowedParameters.contains(p[0])) { printRepl("\nParameter must one of: $allowedParameters") }
279280
else {
280-
interpreter!!.tripleManager.currentTripleSettings.jenaReasoner = p[0]
281+
interpreter!!.tripleManager.currentTripleSettings.jenaReasoner = ReasonerMode.valueOf(p[0])
281282
printRepl("Reasoner changed to: ${p[0]}")
282283
}
283284
}

src/test/kotlin/no/uio/microobject/test/MicroObjectTest.kt

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS
2222

2323
open class MicroObjectTest : StringSpec() {
2424
protected enum class StringLoad {STMT, CLASS, PRG, PATH, RES}
25+
private val IS_FUSEKI_DOCKER = System.getenv("FUSEKI_DOCKER") == "true"
2526

2627
val fmuNeedsWindows: (TestCase) -> Enabled = {
2728
if (IS_OS_WINDOWS) Enabled.enabled
@@ -35,12 +36,17 @@ open class MicroObjectTest : StringSpec() {
3536
if (IS_OS_LINUX) Enabled.enabled
3637
else Enabled.disabled("The FMU of this test needs to run on Linux.")
3738
}
39+
val tripleStoreToTest: (TestCase) -> Enabled = {
40+
if (IS_FUSEKI_DOCKER) Enabled.enabled
41+
else Enabled.disabled("The triple store needs to be running on a docker container for this test.")
42+
}
3843

3944
private var settings = Settings(false, false, "/tmp/mo","","","urn:", extraPrefixes = hashMapOf())
45+
private var tripleStoreSettings = Settings(false, false, "/tmp/mo","http://localhost:3030/ds","","urn:", extraPrefixes = hashMapOf())
4046
protected fun loadBackground(path : String, domainPrefix : String = ""){
4147
val file = File(path)
4248
val backgr = file.readText()
43-
settings = Settings(false, false, "/tmp/mo",backgr,domainPrefix,"urn:", extraPrefixes = hashMapOf())
49+
settings = Settings(false, false, "/tmp/mo","",backgr,domainPrefix,"urn:", extraPrefixes = hashMapOf())
4450
}
4551
private fun loadString(program : String) : WhileParser.ProgramContext{
4652
val stdLib = this::class.java.classLoader.getResource("StdLib.smol").readText() + "\n\n"
@@ -111,6 +117,38 @@ open class MicroObjectTest : StringSpec() {
111117
return Pair(interpreter, tC)
112118
}
113119

120+
protected fun initTripleStoreInterpreter(str : String, loadAs : StringLoad = StringLoad.PATH) : Pair<Interpreter, TypeChecker> {
121+
val ast = when(loadAs){
122+
StringLoad.STMT -> loadStatement(str)
123+
StringLoad.PRG -> loadString(str)
124+
StringLoad.CLASS -> loadClass(str)
125+
StringLoad.PATH -> loadPath(str)
126+
StringLoad.RES -> loadPath(this::class.java.classLoader.getResource("$str.smol").file)
127+
}
128+
val visitor = Translate()
129+
val pair = visitor.generateStatic(ast)
130+
131+
val tripleManager = TripleManager(tripleStoreSettings, pair.second, null)
132+
133+
val tC = TypeChecker(ast, tripleStoreSettings, tripleManager)
134+
tC.collect()
135+
var rules = ""
136+
137+
138+
val initGlobalStore: GlobalMemory = mutableMapOf(Pair(pair.first.obj, mutableMapOf()))
139+
140+
val initStack = Stack<StackEntry>()
141+
initStack.push(pair.first)
142+
val interpreter = Interpreter(
143+
initStack,
144+
initGlobalStore,
145+
mutableMapOf(),
146+
pair.second,
147+
tripleStoreSettings
148+
)
149+
return Pair(interpreter, tC)
150+
}
151+
114152
protected fun initTc(str : String, loadAs : StringLoad = StringLoad.PATH) : Pair<TypeChecker, WhileParser.ProgramContext> {
115153

116154
val ast = when(loadAs){

src/test/kotlin/no/uio/microobject/test/basic/BasicTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class BasicTest : StringSpec() {
3030
val pair = visitor.generateStatic(tree)
3131

3232

33-
val settings = Settings(false,false, "/tmp/mo","","urn:", "", extraPrefixes = hashMapOf())
33+
val settings = Settings(false,false, "/tmp/mo", "","","urn:", "", extraPrefixes = hashMapOf())
3434
val tripleManager = TripleManager(settings, pair.second, null)
3535

3636
val tC = TypeChecker(tree, settings, tripleManager)

0 commit comments

Comments
 (0)