@@ -2,7 +2,6 @@ package org.modelix.model.server.handlers.ui
2
2
3
3
import io.ktor.http.HttpStatusCode
4
4
import io.ktor.server.application.Application
5
- import io.ktor.server.application.call
6
5
import io.ktor.server.html.respondHtml
7
6
import io.ktor.server.html.respondHtmlTemplate
8
7
import io.ktor.server.request.receive
@@ -24,6 +23,7 @@ import kotlinx.html.div
24
23
import kotlinx.html.getForm
25
24
import kotlinx.html.h1
26
25
import kotlinx.html.h3
26
+ import kotlinx.html.i
27
27
import kotlinx.html.id
28
28
import kotlinx.html.label
29
29
import kotlinx.html.li
@@ -45,13 +45,16 @@ import kotlinx.html.ul
45
45
import kotlinx.html.unsafe
46
46
import org.modelix.authorization.checkPermission
47
47
import org.modelix.authorization.requiresLogin
48
+ import org.modelix.kotlin.utils.urlDecode
49
+ import org.modelix.kotlin.utils.urlEncode
48
50
import org.modelix.model.api.BuiltinLanguages
49
- import org.modelix.model.api.INodeResolutionScope
50
- import org.modelix.model.api.ITree
51
- import org.modelix.model.api.PNodeAdapter
52
- import org.modelix.model.api.TreePointer
51
+ import org.modelix.model.api.INodeReference
52
+ import org.modelix.model.api.IReadableNode
53
+ import org.modelix.model.api.NodeReference
54
+ import org.modelix.model.api.ancestors
53
55
import org.modelix.model.lazy.BranchReference
54
56
import org.modelix.model.lazy.RepositoryId
57
+ import org.modelix.model.mutable.asModelSingleThreaded
55
58
import org.modelix.model.server.ModelServerPermissionSchema
56
59
import org.modelix.model.server.handlers.IRepositoriesManager
57
60
import org.modelix.model.server.handlers.NodeNotFoundException
@@ -110,7 +113,7 @@ class ContentExplorer(private val repoManager: IRepositoriesManager) {
110
113
// IMPORTANT Do not let `expandTo` be an arbitrary string to avoid code injection.
111
114
// The value of `expandTo` is expanded into JavaScript.
112
115
val expandTo = call.request.queryParameters[" expandTo" ]?.let {
113
- it.toLongOrNull() ? : return @get call.respondText( " Invalid expandTo value. Provide a node id. " , status = HttpStatusCode . BadRequest )
116
+ NodeReference (it )
114
117
}
115
118
116
119
val version = repoManager.getTransactionManager().runReadIO {
@@ -120,10 +123,21 @@ class ContentExplorer(private val repoManager: IRepositoriesManager) {
120
123
call.respondText(" version $versionHash not found" , status = HttpStatusCode .NotFound )
121
124
return @get
122
125
}
123
- val tree = version.getTree()
124
- val rootNode = PNodeAdapter (ITree .ROOT_ID , TreePointer (tree))
126
+ val tree = version.getModelTree()
127
+ val model = tree.asModelSingleThreaded()
128
+ val rootNode = model.getRootNode()
125
129
126
- val expandedNodes = expandTo?.let { nodeId -> getAncestorsAndSelf(nodeId, tree) }.orEmpty()
130
+ val expandedNodes = expandTo?.let {
131
+ try {
132
+ model.tryResolveNode(it)
133
+ } catch (ex: IllegalArgumentException ) {
134
+ return @get call.respondText(" Invalid expandTo value. Provide a node id." , status = HttpStatusCode .BadRequest )
135
+ } ? : throw NodeNotFoundException (" Node not found: $it " )
136
+ }
137
+ ?.ancestors(true )
138
+ .orEmpty()
139
+ .map { it.getNodeReference() }
140
+ .toSet()
127
141
128
142
call.respondHtmlTemplate(PageWithMenuBar (" repos/" , " ../../../../.." )) {
129
143
headContent {
@@ -168,12 +182,12 @@ class ContentExplorer(private val repoManager: IRepositoriesManager) {
168
182
call.respondText(" version $versionHash not found" , status = HttpStatusCode .NotFound )
169
183
return @post
170
184
}
171
- val tree = version.getTree ()
172
- val rootNode = PNodeAdapter ( ITree . ROOT_ID , TreePointer (tree) )
185
+ val tree = version.getModelTree ()
186
+ val rootNode = tree.asModelSingleThreaded().getRootNode( )
173
187
174
- var expandedNodeIds = expandedNodes.expandedNodeIds
188
+ var expandedNodeIds: List < INodeReference > = expandedNodes.expandedNodeIds.mapNotNull { it.urlDecode() }.map { NodeReference (it) }
175
189
if (expandedNodes.expandAll) {
176
- expandedNodeIds = expandedNodeIds + collectExpandableChildNodes(rootNode, expandedNodes.expandedNodeIds.toSet())
190
+ expandedNodeIds = expandedNodeIds + collectExpandableChildNodes(rootNode, expandedNodes.expandedNodeIds.map { NodeReference (it) }. toSet())
177
191
}
178
192
179
193
call.respondText(
@@ -185,7 +199,7 @@ class ContentExplorer(private val repoManager: IRepositoriesManager) {
185
199
)
186
200
}
187
201
get(" /content/repositories/{repository}/versions/{versionHash}/{nodeId}" ) {
188
- val id = call.parameters[" nodeId" ]?.toLongOrNull()
202
+ val id = call.parameters[" nodeId" ]?.urlDecode()?. let { NodeReference (it) }
189
203
? : return @get call.respondText(" node id not found" , status = HttpStatusCode .NotFound )
190
204
191
205
val versionHash = call.parameters[" versionHash" ]
@@ -203,7 +217,7 @@ class ContentExplorer(private val repoManager: IRepositoriesManager) {
203
217
call.respondText(" version $versionHash not found" , status = HttpStatusCode .NotFound )
204
218
return @get
205
219
}
206
- val node = PNodeAdapter (id, TreePointer ( version.getTree())). takeIf { it.isValid }
220
+ val node = version.getModelTree().asModelSingleThreaded().tryResolveNode(id)
207
221
208
222
if (node != null ) {
209
223
call.respondHtml { body { nodeInspector(node) } }
@@ -215,40 +229,29 @@ class ContentExplorer(private val repoManager: IRepositoriesManager) {
215
229
}
216
230
}
217
231
218
- private fun getAncestorsAndSelf (expandTo : Long , tree : ITree ): Set <String > {
219
- val seq = generateSequence(expandTo) { id ->
220
- try {
221
- tree.getParent(id).takeIf { it != 0L } // getParent returns 0L for root node
222
- } catch (e: org.modelix.datastructures.model.NodeNotFoundException ) {
223
- throw NodeNotFoundException (id, e)
224
- }
225
- }
226
- return seq.map { it.toString() }.toSet()
227
- }
228
-
229
232
// The method traverses the expanded tree based on the alreadyExpandedNodeIds and
230
233
// collects the expandable (not empty) nodes which are not expanded yet
231
- private fun collectExpandableChildNodes (under : PNodeAdapter , alreadyExpandedNodeIds : Set <String >): Set <String > {
232
- if (alreadyExpandedNodeIds.contains(under.nodeId.toString ())) {
233
- val expandableIds = mutableSetOf<String >()
234
- for (child in under.allChildren ) {
235
- expandableIds.addAll(collectExpandableChildNodes(child as PNodeAdapter , alreadyExpandedNodeIds))
234
+ private fun collectExpandableChildNodes (under : IReadableNode , alreadyExpandedNodeIds : Set <INodeReference >): Set <INodeReference > {
235
+ if (alreadyExpandedNodeIds.contains(under.getNodeReference ())) {
236
+ val expandableIds = mutableSetOf<INodeReference >()
237
+ for (child in under.getAllChildren() ) {
238
+ expandableIds.addAll(collectExpandableChildNodes(child, alreadyExpandedNodeIds))
236
239
}
237
240
return expandableIds
238
241
}
239
242
240
- if (under.allChildren.toList ().isNotEmpty()) {
243
+ if (under.getAllChildren ().isNotEmpty()) {
241
244
// Node is collected if it is expandable
242
- return setOf (under.nodeId.toString ())
245
+ return setOf (under.getNodeReference ())
243
246
}
244
247
return emptySet()
245
248
}
246
249
247
250
private fun FlowContent.contentPageBody (
248
- rootNode : PNodeAdapter ,
251
+ rootNode : IReadableNode ,
249
252
versionHash : String ,
250
- expandedNodeIds : Set <String >,
251
- expandTo : Long ? ,
253
+ expandedNodeIds : Set <INodeReference >,
254
+ expandTo : INodeReference ? ,
252
255
) {
253
256
h1 { + " Model Server Content" }
254
257
small {
@@ -291,62 +294,54 @@ class ContentExplorer(private val repoManager: IRepositoriesManager) {
291
294
}
292
295
}
293
296
294
- private fun UL.nodeItem (node : PNodeAdapter , expandedNodeIds : Set <String >, expandTo : Long ? = null) {
297
+ private fun UL.nodeItem (node : IReadableNode , expandedNodeIds : Set <INodeReference >, expandTo : INodeReference ? = null) {
295
298
li(" nodeItem" ) {
296
- id = node.nodeId.toString ()
297
- val expanded = expandedNodeIds.contains(node.nodeId.toString ())
298
- if (node.allChildren .toList().isNotEmpty()) {
299
+ id = node.getNodeReference().serialize ()
300
+ val expanded = expandedNodeIds.contains(node.getNodeReference ())
301
+ if (node.getAllChildren() .toList().isNotEmpty()) {
299
302
div(if (expanded) " expander expander-expanded" else " expander" ) { unsafe { + " ▶" } }
300
303
}
301
304
div(" nameField" ) {
302
- if (expandTo == node.nodeId ) {
305
+ if (expandTo == node.getNodeReference() ) {
303
306
classes + = " expandedToNameField"
304
307
}
305
- attributes[" data-nodeid" ] = node.nodeId.toString()
306
- b {
307
- val namePropertyUID = BuiltinLanguages .jetbrains_mps_lang_core.INamedConcept .name.getUID()
308
- val namedConceptName = node.getPropertyValue(namePropertyUID)
309
- if (namedConceptName != null ) {
310
- + namedConceptName
311
- } else if (node.getPropertyRoles().contains(" name" )) {
312
- + " ${node.getPropertyValue(" name" )} "
313
- } else {
314
- + " Unnamed Node"
315
- }
308
+ attributes[" data-nodeid" ] = node.getNodeReference().serialize().urlEncode()
309
+ val namePropertyUID = BuiltinLanguages .jetbrains_mps_lang_core.INamedConcept .name.toReference()
310
+ val nodeName = node.getPropertyValue(namePropertyUID)
311
+ if (nodeName != null ) {
312
+ b { + nodeName }
313
+ } else {
314
+ i { + " <no name>" }
316
315
}
317
- small { + " | ${node.nodeId} | $node " }
316
+ small { + " | ${node.getNodeReference().serialize()} " }
318
317
br { }
319
- val conceptRef = node.getConceptReference()
320
318
small {
321
- if (conceptRef != null ) {
322
- + conceptRef.getUID()
323
- } else {
324
- + " No concept reference"
325
- }
319
+ + " Concept: "
320
+ + node.getConceptReference().getUID()
326
321
}
327
322
}
328
323
div(if (expanded) " nested active" else " nested" ) {
329
324
if (expanded) {
330
325
ul(" nodeTree" ) {
331
- for (child in node.allChildren ) {
332
- nodeItem(child as PNodeAdapter , expandedNodeIds, expandTo)
326
+ for (child in node.getAllChildren() ) {
327
+ nodeItem(child, expandedNodeIds, expandTo)
333
328
}
334
329
}
335
330
}
336
331
}
337
332
}
338
333
}
339
334
340
- private fun BODY.nodeInspector (node : PNodeAdapter ) {
335
+ private fun BODY.nodeInspector (node : IReadableNode ) {
341
336
div {
342
337
h3 { + " Node Details" }
343
338
}
344
- val nodeEmpty = node.getReferenceRoles ().isEmpty() && node.getPropertyRoles ().isEmpty()
339
+ val nodeEmpty = node.getAllReferenceTargetRefs ().isEmpty() && node.getAllProperties ().isEmpty()
345
340
if (nodeEmpty) {
346
341
div { + " No roles." }
347
342
return
348
343
}
349
- if (node.getPropertyRoles ().isEmpty()) {
344
+ if (node.getAllProperties ().isEmpty()) {
350
345
div { + " No properties." }
351
346
} else {
352
347
table {
@@ -357,44 +352,37 @@ class ContentExplorer(private val repoManager: IRepositoriesManager) {
357
352
}
358
353
}
359
354
tbody {
360
- for (propertyRole in node.getPropertyRoles ()) {
355
+ for (property in node.getAllProperties ()) {
361
356
tr {
362
- td { + propertyRole }
363
- td { + " ${node.getPropertyValue(propertyRole)} " }
357
+ td { + property.first.getNameOrId() }
358
+ td { + property.second }
364
359
}
365
360
}
366
361
}
367
362
}
368
363
}
369
- if (node.getReferenceRoles ().isEmpty()) {
364
+ if (node.getAllReferenceTargetRefs ().isEmpty()) {
370
365
div { + " No references." }
371
366
} else {
372
367
table {
373
368
thead {
374
369
tr {
375
370
th { + " ReferenceRole" }
376
- th { + " Target NodeId" }
377
371
th { + " Target Reference" }
378
372
}
379
373
}
380
374
tbody {
381
- INodeResolutionScope .runWithAdditionalScope(node.getArea()) {
382
- for (referenceRole in node.getReferenceRoles()) {
383
- tr {
384
- td { + referenceRole }
385
- td {
386
- val nodeId = (node.getReferenceTarget(referenceRole) as ? PNodeAdapter )?.nodeId
387
- if (nodeId != null ) {
388
- a(" ?expandTo=$nodeId " ) {
389
- + " $nodeId "
390
- }
391
- } else {
392
- + " null"
375
+ for ((referenceRole, targetId) in node.getAllReferenceTargetRefs()) {
376
+ tr {
377
+ td { + referenceRole.getNameOrId() }
378
+ td {
379
+ if (node.getModel().tryResolveNode(targetId) == null ) {
380
+ + targetId.serialize()
381
+ } else {
382
+ a(" ?expandTo=${targetId.serialize().urlEncode()} " ) {
383
+ + targetId.serialize()
393
384
}
394
385
}
395
- td {
396
- + " ${node.getReferenceTargetRef(referenceRole)?.serialize()} "
397
- }
398
386
}
399
387
}
400
388
}
0 commit comments