Skip to content

Commit 72945da

Browse files
committed
feat(model-server): added ModelQL support
1 parent 2bc95b8 commit 72945da

File tree

10 files changed

+188
-44
lines changed

10 files changed

+188
-44
lines changed

model-server-lib/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ dependencies {
77
implementation(project(":model-api"))
88
implementation(project(":model-server-api"))
99
implementation(project(":modelql-core"))
10+
implementation(project(":modelql-server"))
1011
implementation(project(":modelql-untyped"))
1112

1213
implementation(kotlin("stdlib"))

model-server-lib/src/main/kotlin/org/modelix/model/server/light/LightModelServer.kt

Lines changed: 4 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,24 @@ import io.ktor.server.routing.*
2727
import io.ktor.server.websocket.*
2828
import io.ktor.websocket.*
2929
import kotlinx.coroutines.*
30-
import kotlinx.serialization.KSerializer
3130
import org.modelix.model.api.*
3231
import org.modelix.model.server.api.AddNewChildNodeOpData
3332
import org.modelix.model.server.api.ChangeSetId
3433
import org.modelix.model.server.api.DeleteNodeOpData
3534
import org.modelix.model.server.api.ExceptionData
3635
import org.modelix.model.server.api.MessageFromClient
3736
import org.modelix.model.server.api.MessageFromServer
37+
import org.modelix.model.server.api.ModelQuery
3838
import org.modelix.model.server.api.MoveNodeOpData
3939
import org.modelix.model.server.api.NodeData
4040
import org.modelix.model.server.api.NodeId
4141
import org.modelix.model.server.api.OperationData
42-
import org.modelix.model.server.api.ModelQuery
4342
import org.modelix.model.server.api.SetPropertyOpData
4443
import org.modelix.model.server.api.SetReferenceOpData
4544
import org.modelix.model.server.api.VersionData
4645
import org.modelix.model.server.api.buildModelQuery
47-
import org.modelix.modelql.core.VersionAndData
48-
import org.modelix.modelql.core.IMonoUnboundQuery
49-
import org.modelix.modelql.core.IStepOutput
50-
import org.modelix.modelql.core.QueryGraphDescriptor
5146
import org.modelix.modelql.core.modelqlVersion
52-
import org.modelix.modelql.core.upcast
53-
import org.modelix.modelql.untyped.UntypedModelQL
54-
import org.modelix.modelql.untyped.createQueryExecutor
47+
import org.modelix.modelql.server.ModelQLServer
5548
import java.time.Duration
5649
import java.util.*
5750
import kotlin.time.Duration.Companion.seconds
@@ -121,6 +114,7 @@ class LightModelServer @JvmOverloads constructor (val port: Int, val rootNodePro
121114
}) + additionalHealthChecks
122115

123116
val rootNode: INode get() = rootNodeProvider() ?: throw IllegalStateException("Root node not available yet")
117+
private val modelqlServer = ModelQLServer.builder(rootNodeProvider).build()
124118

125119
@JvmOverloads
126120
fun start(wait: Boolean = false) {
@@ -225,36 +219,7 @@ class LightModelServer @JvmOverloads constructor (val port: Int, val rootNodePro
225219
call.respond(HttpStatusCode.InternalServerError, "unhealthy\n\n$output")
226220
}
227221
}
228-
post("query") {
229-
try {
230-
val serializedQuery = call.receiveText()
231-
val json = UntypedModelQL.json
232-
val queryDescriptor = VersionAndData.deserialize(serializedQuery, QueryGraphDescriptor.serializer(), json).data
233-
val query = queryDescriptor.createRootQuery() as IMonoUnboundQuery<INode, Any?>
234-
LOG.debug { "query: $query" }
235-
val nodeResolutionScope: INodeResolutionScope = getArea()
236-
val transactionBody: () -> IStepOutput<Any?> = {
237-
runBlocking {
238-
withContext(nodeResolutionScope) {
239-
query.bind(rootNode.createQueryExecutor()).execute()
240-
}
241-
}
242-
}
243-
val result: IStepOutput<Any?> = if (query.requiresWriteAccess()) {
244-
getArea().executeWrite(transactionBody)
245-
} else {
246-
getArea().executeRead(transactionBody)
247-
}
248-
val serializer: KSerializer<IStepOutput<Any?>> =
249-
query.getAggregationOutputSerializer(json.serializersModule).upcast()
250-
251-
val versionAndResult = VersionAndData(result)
252-
val serializedResult = json.encodeToString(VersionAndData.serializer(serializer), versionAndResult)
253-
call.respondText(text = serializedResult, contentType = ContentType.Application.Json)
254-
} catch (ex: Throwable) {
255-
call.respondText(text = "server version: $modelqlVersion\n" + ex.stackTraceToString(), status = HttpStatusCode.InternalServerError)
256-
}
257-
}
222+
modelqlServer.installHandler(this)
258223
}
259224
install(CORS) {
260225
anyHost()

model-server/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies {
3131
implementation(project(":model-api"))
3232
implementation(project(":model-server-api"))
3333
implementation(project(":model-client", configuration = "jvmRuntimeElements"))
34+
implementation(project(":modelql-server"))
3435
implementation(project(":authorization"))
3536
implementation(libs.apache.commons.lang)
3637

model-server/src/main/kotlin/org/modelix/model/server/handlers/ModelReplicationServer.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,19 @@ import kotlinx.coroutines.Job
2727
import kotlinx.serialization.encodeToString
2828
import kotlinx.serialization.json.Json
2929
import org.modelix.authorization.getUserName
30+
import org.modelix.model.api.PBranch
31+
import org.modelix.model.api.getRootNode
32+
import org.modelix.model.area.getArea
33+
import org.modelix.model.lazy.CLTree
34+
import org.modelix.model.lazy.CLVersion
3035
import org.modelix.model.lazy.RepositoryId
36+
import org.modelix.model.operations.OTBranch
3137
import org.modelix.model.persistent.HashUtil
3238
import org.modelix.model.server.api.ModelQuery
3339
import org.modelix.model.server.api.v2.VersionDelta
3440
import org.modelix.model.server.store.IStoreClient
3541
import org.modelix.model.server.store.LocalModelClient
42+
import org.modelix.modelql.server.ModelQLServer
3643
import org.slf4j.LoggerFactory
3744
import java.util.*
3845

@@ -132,6 +139,25 @@ class ModelReplicationServer(val repositoriesManager: RepositoriesManager) {
132139
lastVersionHash = newVersionHash
133140
}
134141
}
142+
post("query") {
143+
val branchRef = branchRef()
144+
val version = repositoriesManager.getVersion(branchRef)
145+
val initialTree = version!!.getTree()
146+
val branch = OTBranch(PBranch(initialTree, repositoriesManager.client.idGenerator), repositoriesManager.client.idGenerator, repositoriesManager.client.storeCache)
147+
ModelQLServer.handleCall(call, branch.getRootNode(), branch.getArea())
148+
149+
val (ops, newTree) = branch.operationsAndTree
150+
if (newTree != initialTree) {
151+
val newVersion = CLVersion.createRegularVersion(
152+
id = repositoriesManager.client.idGenerator.generate(),
153+
author = getUserName(),
154+
tree = newTree as CLTree,
155+
baseVersion = version,
156+
operations = ops.map { it.getOriginalOp() }.toTypedArray()
157+
)
158+
repositoriesManager.mergeChanges(branchRef, newVersion.getContentHash())
159+
}
160+
}
135161
}
136162
}
137163
}

modelql-client/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ kotlin {
6363
implementation(kotlin("test"))
6464

6565
implementation(project(":model-client", configuration = "jvmRuntimeElements"))
66-
implementation(project(":model-server-lib"))
66+
implementation(project(":modelql-server"))
6767
implementation(project(":modelql-html"))
6868

6969
implementation(libs.ktor.server.core)

modelql-client/src/jvmTest/kotlin/org/modelix/modelql/client/HtmlBuilderTest.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package org.modelix.modelql.client
1515

1616
import io.ktor.client.HttpClient
17+
import io.ktor.server.routing.routing
1718
import io.ktor.server.testing.testApplication
1819
import kotlinx.coroutines.withTimeout
1920
import kotlinx.html.FlowContent
@@ -33,12 +34,12 @@ import org.modelix.model.client.IdGenerator
3334
import org.modelix.model.lazy.CLTree
3435
import org.modelix.model.lazy.ObjectStoreCache
3536
import org.modelix.model.persistent.MapBaseStore
36-
import org.modelix.model.server.light.LightModelServer
3737
import org.modelix.modelql.core.IFragmentBuilder
3838
import org.modelix.modelql.core.IRecursiveFragmentBuilder
3939
import org.modelix.modelql.core.buildModelQLFragment
4040
import org.modelix.modelql.core.isNotEmpty
4141
import org.modelix.modelql.html.buildHtmlQuery
42+
import org.modelix.modelql.server.ModelQLServer
4243
import org.modelix.modelql.untyped.allChildren
4344
import org.modelix.modelql.untyped.children
4445
import org.modelix.modelql.untyped.createQueryExecutor
@@ -62,7 +63,9 @@ class HtmlBuilderTest {
6263
val model1b = module1.addNewChild("models", -1, null as IConceptReference?)
6364
model1b.setPropertyValue("name", "model1b")
6465
}
65-
LightModelServer(80, rootNode).apply { installHandlers() }
66+
routing {
67+
ModelQLServer.builder(rootNode).build().installHandler(this)
68+
}
6669
}
6770
val httpClient = createClient {
6871
}

modelql-client/src/jvmTest/kotlin/org/modelix/modelql/client/ModelQLClientTest.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package org.modelix.modelql.client
1515

1616
import io.ktor.client.HttpClient
17+
import io.ktor.server.routing.routing
1718
import io.ktor.server.testing.testApplication
1819
import kotlinx.coroutines.withTimeout
1920
import org.modelix.model.api.IConceptReference
@@ -24,7 +25,6 @@ import org.modelix.model.client.IdGenerator
2425
import org.modelix.model.lazy.CLTree
2526
import org.modelix.model.lazy.ObjectStoreCache
2627
import org.modelix.model.persistent.MapBaseStore
27-
import org.modelix.model.server.light.LightModelServer
2828
import org.modelix.modelql.core.IFluxUnboundQuery
2929
import org.modelix.modelql.core.buildFluxQuery
3030
import org.modelix.modelql.core.contains
@@ -39,6 +39,7 @@ import org.modelix.modelql.core.plus
3939
import org.modelix.modelql.core.toList
4040
import org.modelix.modelql.core.toSet
4141
import org.modelix.modelql.core.zip
42+
import org.modelix.modelql.server.ModelQLServer
4243
import org.modelix.modelql.untyped.addNewChild
4344
import org.modelix.modelql.untyped.allChildren
4445
import org.modelix.modelql.untyped.allReferences
@@ -66,7 +67,9 @@ class ModelQLClientTest {
6667
val model1a = module1.addNewChild("models", -1, null as IConceptReference?)
6768
model1a.setPropertyValue("name", "model1a")
6869
}
69-
LightModelServer(80, rootNode).apply { installHandlers() }
70+
routing {
71+
ModelQLServer.builder(rootNode).build().installHandler(this)
72+
}
7073
}
7174
val httpClient = createClient {
7275
}

modelql-server/build.gradle.kts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
plugins {
2+
kotlin("jvm")
3+
`maven-publish`
4+
alias(libs.plugins.ktlint)
5+
}
6+
7+
dependencies {
8+
implementation(project(":model-api"))
9+
implementation(project(":model-server-api"))
10+
implementation(project(":modelql-core"))
11+
implementation(project(":modelql-untyped"))
12+
13+
implementation(kotlin("stdlib"))
14+
15+
implementation(libs.kotlin.serialization.json)
16+
17+
implementation(libs.kotlin.coroutines.core)
18+
implementation(libs.kotlin.coroutines.swing)
19+
implementation(libs.kotlin.logging)
20+
21+
implementation(libs.ktor.server.core)
22+
implementation(libs.ktor.server.cors)
23+
implementation(libs.ktor.server.netty)
24+
implementation(libs.ktor.server.websockets)
25+
}
26+
27+
publishing {
28+
publications {
29+
create<MavenPublication>("maven") {
30+
from(components["kotlin"])
31+
}
32+
}
33+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package org.modelix.modelql.server
15+
16+
import io.ktor.http.ContentType
17+
import io.ktor.http.HttpStatusCode
18+
import io.ktor.server.application.ApplicationCall
19+
import io.ktor.server.application.call
20+
import io.ktor.server.request.receiveText
21+
import io.ktor.server.response.respondText
22+
import io.ktor.server.routing.Route
23+
import io.ktor.server.routing.post
24+
import kotlinx.coroutines.runBlocking
25+
import kotlinx.coroutines.withContext
26+
import kotlinx.serialization.KSerializer
27+
import org.modelix.model.api.INode
28+
import org.modelix.model.api.INodeResolutionScope
29+
import org.modelix.model.area.IArea
30+
import org.modelix.modelql.core.IMonoUnboundQuery
31+
import org.modelix.modelql.core.IStepOutput
32+
import org.modelix.modelql.core.QueryGraphDescriptor
33+
import org.modelix.modelql.core.VersionAndData
34+
import org.modelix.modelql.core.modelqlVersion
35+
import org.modelix.modelql.core.upcast
36+
import org.modelix.modelql.untyped.UntypedModelQL
37+
import org.modelix.modelql.untyped.createQueryExecutor
38+
39+
class ModelQLServer private constructor(val rootNodeProvider: () -> INode?, val area: IArea? = null) {
40+
fun installHandler(route: Route) {
41+
route.post("query") {
42+
val rootNode = rootNodeProvider()!!
43+
handleCall(call, rootNode, area ?: rootNode.getArea())
44+
}
45+
}
46+
47+
class Builder {
48+
private var rootNodeProvider: () -> INode? = { null }
49+
private var area: IArea? = null
50+
51+
fun rootNode(rootNode: INode): Builder {
52+
this.rootNodeProvider = { rootNode }
53+
return this
54+
}
55+
56+
fun rootNode(rootNodeProvider: () -> INode?): Builder {
57+
this.rootNodeProvider = rootNodeProvider
58+
return this
59+
}
60+
61+
fun area(area: IArea): Builder {
62+
this.area = area
63+
return this
64+
}
65+
66+
fun build(): ModelQLServer {
67+
return ModelQLServer(
68+
rootNodeProvider,
69+
area
70+
)
71+
}
72+
}
73+
74+
companion object {
75+
private val LOG = mu.KotlinLogging.logger { }
76+
77+
fun builder(rootNode: INode): Builder = Builder().also { it.rootNode(rootNode) }
78+
fun builder(rootNodeProvider: () -> INode?): Builder = Builder().also { it.rootNode(rootNodeProvider) }
79+
80+
suspend fun handleCall(call: ApplicationCall, rootNode: INode, area: IArea) {
81+
try {
82+
val serializedQuery = call.receiveText()
83+
val json = UntypedModelQL.json
84+
val queryDescriptor = VersionAndData.deserialize(serializedQuery, QueryGraphDescriptor.serializer(), json).data
85+
val query = queryDescriptor.createRootQuery() as IMonoUnboundQuery<INode, Any?>
86+
LOG.debug { "query: $query" }
87+
val nodeResolutionScope: INodeResolutionScope = area
88+
val transactionBody: () -> IStepOutput<Any?> = {
89+
runBlocking {
90+
withContext(nodeResolutionScope) {
91+
query.bind(rootNode.createQueryExecutor()).execute()
92+
}
93+
}
94+
}
95+
val result: IStepOutput<Any?> = if (query.requiresWriteAccess()) {
96+
area.executeWrite(transactionBody)
97+
} else {
98+
area.executeRead(transactionBody)
99+
}
100+
val serializer: KSerializer<IStepOutput<Any?>> =
101+
query.getAggregationOutputSerializer(json.serializersModule).upcast()
102+
103+
val versionAndResult = VersionAndData(result)
104+
val serializedResult = json.encodeToString(VersionAndData.serializer(serializer), versionAndResult)
105+
call.respondText(text = serializedResult, contentType = ContentType.Application.Json)
106+
} catch (ex: Throwable) {
107+
call.respondText(text = "server version: $modelqlVersion\n" + ex.stackTraceToString(), status = HttpStatusCode.InternalServerError)
108+
}
109+
}
110+
}
111+
}

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ include("model-sync-lib")
2727
include("modelql-client")
2828
include("modelql-core")
2929
include("modelql-html")
30+
include("modelql-server")
3031
include("modelql-typed")
3132
include("modelql-untyped")
3233
include("mps-model-adapters")

0 commit comments

Comments
 (0)