TinkerPop でグラフ操作 - Kotlin

前回 の処理を Kotlin で実装してみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20170724/

a. ビルド定義・設定ファイル

今回は Gradle のサブプロジェクトとして実行します。

build.gradle (Gradle ビルド定義ファイル)
buildscript {
    ext.kotlin_version = '1.1.3-2'

    repositories {
        jcenter()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}"
    }
}
// サブプロジェクト共通設定
subprojects {
    apply plugin: 'kotlin'
    apply plugin: 'application'

    mainClassName = 'AppKt'

    sourceCompatibility = JavaVersion.VERSION_1_8

    compileKotlin {
        kotlinOptions.jvmTarget = '1.8'
    }

    repositories {
        jcenter()
    }

    dependencies {
        compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlin_version}"
        compile 'org.apache.tinkerpop:neo4j-gremlin:3.2.5'

        runtime 'org.neo4j:neo4j-tinkerpop-api-impl:0.6-3.2.2'
        runtime 'org.slf4j:slf4j-nop:1.7.25'
    }

    run {
        if (project.hasProperty('args')) {
            args project.args.split(' ')
        }
    }
}

サブプロジェクトは以下の通り

settings.gradle (Gradle 設定ファイル)
include 'add-data'
include 'find-data'

TinkerPop の設定ファイルは基本的に 前回 と同じものを使いますが、Gradle のサブプロジェクトとして実行する関係でディレクトリのパスを変えています。

conf/setting.properties (TinkerPop 設定ファイル)
gremlin.graph=org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jGraph
gremlin.neo4j.directory=../neo4jdb

b. グラフデータ作成

処理内容は 前回 と同じです。

add-data/src/main/kotlin/App.kt
import org.apache.tinkerpop.gremlin.structure.Graph
import org.apache.tinkerpop.gremlin.structure.Vertex
import org.apache.tinkerpop.gremlin.structure.util.GraphFactory

fun main(args: Array<String>) {
    val conf = args[0]

    GraphFactory.open(conf).use { g ->
        g.tx().use { tx ->
            createData(g)

            tx.commit()
        }
    }
}

fun createData(g: Graph) {
    val p = addNode(g, "Principals", "principals")

    val u1 = addNode(g, "User", "user1")
    val u2 = addNode(g, "User", "user2")
    val ad = addNode(g, "User", "admin")

    val g1 = addNode(g, "Group", "group1")

    listOf(u1, u2, ad, g1).forEach {
        it.addEdge("PART_OF", p)
    }

    u2.addEdge("PART_OF", g1)

    val r = addNode(g, "Resources", "resources")

    val s1 = addNode(g, "Service", "service1")

    val s2 = addNode(g, "Service", "service2")
    val s2o1 = addNode(g, "Operation", "service2.get", "get")
    val s2o2 = addNode(g, "Operation", "service2.post", "post")

    listOf(s2o1, s2o2).forEach {
        s2.addEdge("METHOD", it)
    }

    listOf(s1, s2).forEach {
        r.addEdge("RESOURCE", it)
    }

    u1.addEdge("PERMIT", s1)
    g1.addEdge("PERMIT", s2o2)
    ad.addEdge("PERMIT", r)
}

fun addNode(g: Graph, label: String, id: String, name: String = id): Vertex {
    val node = g.addVertex(label)

    node.property("oid", id)
    node.property("name", name)

    return node
}

add-data サブプロジェクトを実行するため :add-data:run とします。

実行
> gradle :add-data:run -q -Pargs="../conf/setting.properties"

c. 経路の探索

Kotlin では __as予約語なので、`as` のようにエスケープする必要があります。

また、Groovy と違って outEhas では型を指定する必要がありました。

find-data/src/main/kotlin/App.kt
import org.apache.tinkerpop.gremlin.structure.Vertex
import org.apache.tinkerpop.gremlin.structure.util.GraphFactory
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.`__`.*
import org.apache.tinkerpop.gremlin.structure.Element

fun main(args: Array<String>) {
    val conf = args[0]
    val start = args[1]
    val end = args[2]

    GraphFactory.open(conf).use { g ->
        g.tx().use {
            val p = g.traversal().V()
                    .has("oid", start)
                    .repeat(outE<Vertex>().`as`("e").inV())
                    .until(has<Vertex>("oid", end))
                    .where(select<Vertex, Vertex>("e").unfold<Vertex>().hasLabel("PERMIT"))
                    .path()

            p.forEach {
                println(it.objects().asSequence().map(::toStr).joinToString(" -> "))
            }
        }
    }
}

fun toStr(n: Any) = when(n) {
    is Element -> "${n.label()}[${n.id()}]{${n.properties<String>().asSequence().joinToString(", ")}}"
    else -> ""
}
実行結果
> gradle :find-data:run -q -Pargs="../conf/setting.properties user2 service2.post"

User[2]{vp[oid->user2], vp[name->user2]} -> PART_OF[4]{} -> Group[4]{vp[oid->group1], vp[name->group1]} -> PERMIT[10]{} -> Operation[9]{vp[oid->service2.post], vp[name->post]}