├── gradle.properties
├── LICENSE
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── kotlin-wot-lmos-protocol
├── build.gradle.kts
└── src
│ └── main
│ └── kotlin
│ └── protocol
│ └── LmosProtocol.kt
├── kotlin-wot-spring-boot-starter
├── src
│ ├── main
│ │ ├── resources
│ │ │ └── META-INF
│ │ │ │ └── spring
│ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
│ │ └── kotlin
│ │ │ └── spring
│ │ │ ├── MqttProperties.kt
│ │ │ ├── HttpProperties.kt
│ │ │ ├── CredentialsProperties.kt
│ │ │ └── WoTRuntime.kt
│ └── test
│ │ ├── kotlin
│ │ └── spring
│ │ │ ├── TestApplication.kt
│ │ │ ├── SpringApplicationTest.kt
│ │ │ └── CredentialsPropertiesTest.kt
│ │ └── resources
│ │ └── application.yaml
└── build.gradle.kts
├── .github
├── workflows
│ ├── gradle-pr.yml
│ ├── reuse-compliance.yml
│ ├── gradle.yml
│ └── release.yml
└── dependabot.yml
├── .devcontainer
└── devcontainer.json
├── kotlin-wot-binding-mqtt
├── build.gradle.kts
└── src
│ ├── main
│ └── kotlin
│ │ └── mqtt
│ │ ├── MqttProtocolException.kt
│ │ ├── MqttClientConfig.kt
│ │ ├── MqttsProtocolClientFactory.kt
│ │ └── MqttProtocolClientFactory.kt
│ └── test
│ ├── resources
│ └── logback.xml
│ └── kotlin
│ └── integration
│ └── MqttProtocolClientFactoryTest.kt
├── kotlin-wot-reflection
├── src
│ ├── test
│ │ └── kotlin
│ │ │ └── reflection
│ │ │ ├── things
│ │ │ ├── SimpleThingInterface.kt
│ │ │ └── SimpleThing.kt
│ │ │ ├── ConsumedThingBuilderTest.kt
│ │ │ ├── MapTypeToSchemaTest.kt
│ │ │ ├── BuildObjectSchemaTest.kt
│ │ │ └── AddHandlerTest.kt
│ └── main
│ │ └── kotlin
│ │ └── reflection
│ │ └── annotations
│ │ └── Annotations.kt
└── build.gradle.kts
├── kotlin-wot-binding-http
├── src
│ ├── test
│ │ ├── resources
│ │ │ └── logback.xml
│ │ └── kotlin
│ │ │ └── http
│ │ │ ├── HttpProtocolClientFactoryTest.kt
│ │ │ ├── HttpsProtocolClientFactoryTest.kt
│ │ │ └── HttpProtocolClientTest.kt
│ └── main
│ │ └── kotlin
│ │ └── http
│ │ ├── HttpClientConfig.kt
│ │ ├── HttpsProtocolClientFactory.kt
│ │ ├── HttpProtocolClientFactory.kt
│ │ └── routes
│ │ ├── ThingsRoute.kt
│ │ └── AbstractRoute.kt
└── build.gradle.kts
├── kotlin-wot-tool-example
├── src
│ └── main
│ │ ├── resources
│ │ ├── logback.xml
│ │ └── application.yaml
│ │ └── kotlin
│ │ └── example
│ │ ├── ToolApplication.kt
│ │ └── HtmlTool.kt
└── build.gradle.kts
├── kotlin-wot-binding-websocket
├── src
│ ├── test
│ │ ├── resources
│ │ │ └── logback.xml
│ │ └── kotlin
│ │ │ └── websocket
│ │ │ └── WebsocketProtocolClientFactoryTest.kt
│ └── main
│ │ └── kotlin
│ │ └── websocket
│ │ ├── HttpClientConfig.kt
│ │ ├── SecureWebSocketProtocolClientFactory.kt
│ │ └── WebSocketProtocolClientFactory.kt
└── build.gradle.kts
├── settings.gradle.kts
├── kotlin-wot
├── src
│ ├── main
│ │ └── kotlin
│ │ │ ├── binding
│ │ │ ├── ProtocolServerException.kt
│ │ │ ├── ProtocolClientException.kt
│ │ │ ├── ProtocolClientNotImplementedException.kt
│ │ │ ├── ProtocolServerNotImplementedException.kt
│ │ │ ├── ProtocolClientFactory.kt
│ │ │ └── ProtocolServer.kt
│ │ │ ├── filter
│ │ │ ├── ThingFilter.kt
│ │ │ ├── DiscoveryMethod.kt
│ │ │ └── ThingQuery.kt
│ │ │ ├── thing
│ │ │ ├── security
│ │ │ │ ├── PSKSecurityScheme.kt
│ │ │ │ ├── PublicSecurityScheme.kt
│ │ │ │ ├── CertSecurityScheme.kt
│ │ │ │ ├── NoSecurityScheme.kt
│ │ │ │ ├── APIKeySecurityScheme.kt
│ │ │ │ ├── DigestSecurityScheme.kt
│ │ │ │ ├── BasicSecurityScheme.kt
│ │ │ │ ├── SecurityScheme.kt
│ │ │ │ ├── OAuth2SecurityScheme.kt
│ │ │ │ ├── PoPSecurityScheme.kt
│ │ │ │ └── BearerSecurityScheme.kt
│ │ │ ├── schema
│ │ │ │ ├── Type.kt
│ │ │ │ ├── InteractionOutput.kt
│ │ │ │ ├── Context.kt
│ │ │ │ ├── Exentions.kt
│ │ │ │ └── Link.kt
│ │ │ ├── OperationsDeserializer.kt
│ │ │ ├── TypeSerializer.kt
│ │ │ ├── UriTemplate.kt
│ │ │ ├── action
│ │ │ │ └── ThingAction.kt
│ │ │ ├── TypeDeserializer.kt
│ │ │ ├── event
│ │ │ │ └── ThingEvent.kt
│ │ │ ├── form
│ │ │ │ ├── Operation.kt
│ │ │ │ └── AugmentedForm.kt
│ │ │ ├── ContextDeserializer.kt
│ │ │ ├── SessionAwareProtocolListenerRegistry.kt
│ │ │ ├── ContextSerializer.kt
│ │ │ ├── ProtocolHelpers.kt
│ │ │ └── ProtocolListenerRegistry.kt
│ │ │ ├── content
│ │ │ ├── Content.kt
│ │ │ ├── JsonCodec.kt
│ │ │ ├── LinkFormatCodec.kt
│ │ │ └── ContentCodec.kt
│ │ │ ├── credentials
│ │ │ ├── Credentials.kt
│ │ │ └── DefaultCredentialsProvider.kt
│ │ │ ├── tracing
│ │ │ └── Tracing.kt
│ │ │ ├── Utils.kt
│ │ │ ├── DefaultWot.kt
│ │ │ └── Wot.kt
│ └── test
│ │ └── kotlin
│ │ ├── thing
│ │ ├── form
│ │ │ ├── OperationTest.kt
│ │ │ ├── FormTest.kt
│ │ │ └── AugmentedFormTest.kt
│ │ ├── TypeTest.kt
│ │ ├── UriTemplateTest.kt
│ │ ├── action
│ │ │ └── ThingActionTest.kt
│ │ ├── event
│ │ │ └── ThingEventTest.kt
│ │ ├── ContextTest.kt
│ │ ├── ProtocolHelpersTest.kt
│ │ ├── credentials
│ │ │ └── DefaultCredentialsProviderTest.kt
│ │ ├── SessionAwareProtocolListenerRegistryTest.kt
│ │ └── ProtocolListenerRegistryTest.kt
│ │ ├── content
│ │ ├── JsonCodecText.kt
│ │ └── ContentManagerTest.kt
│ │ └── WotTest.kt
└── build.gradle.kts
├── REUSE.toml
├── .gitignore
└── gradlew.bat
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | SPDX-FileCopyrightText: Robert Winkler
2 |
3 | SPDX-License-Identifier: Apache-2.0
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eclipse-thingweb/kotlin-wot/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/kotlin-wot-lmos-protocol/build.gradle.kts:
--------------------------------------------------------------------------------
1 | dependencies {
2 | implementation(platform("io.ktor:ktor-bom:3.0.3"))
3 | implementation("io.ktor:ktor-serialization-jackson")
4 | }
--------------------------------------------------------------------------------
/kotlin-wot-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
--------------------------------------------------------------------------------
1 | org.eclipse.thingweb.spring.ServientAutoConfiguration
2 |
--------------------------------------------------------------------------------
/.github/workflows/gradle-pr.yml:
--------------------------------------------------------------------------------
1 | name: build-pr
2 |
3 | on:
4 | push:
5 | branches-ignore:
6 | - 'master'
7 |
8 | jobs:
9 | CI:
10 | uses: eclipse-lmos/.github/.github/workflows/gradle-ci.yml@main
11 | permissions:
12 | contents: read
13 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kotlin-wot",
3 | "image": "mcr.microsoft.com/devcontainers/java:21",
4 | "customizations": {
5 | "vscode": {
6 | "extensions": [
7 | "vscjava.vscode-java-pack",
8 | "vscjava.vscode-java-test",
9 | ]
10 | }
11 | },
12 | "postCreateCommand": "./gradlew build"
13 | }
14 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 Deutsche Telekom AG
2 | #
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | version: 2
6 | updates:
7 | - package-ecosystem: "gradle"
8 | directory: "/"
9 | schedule:
10 | interval: "daily"
11 | - package-ecosystem: "github-actions"
12 | directory: "/"
13 | schedule:
14 | interval: "daily"
--------------------------------------------------------------------------------
/.github/workflows/reuse-compliance.yml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 Deutsche Telekom AG
2 | #
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | name: REUSE Compliance Check
6 |
7 | on: [push, pull_request]
8 |
9 | jobs:
10 | test:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - name: REUSE Compliance Check
15 | uses: fsfe/reuse-action@v4
--------------------------------------------------------------------------------
/kotlin-wot-binding-mqtt/build.gradle.kts:
--------------------------------------------------------------------------------
1 | dependencies {
2 | api(project(":kotlin-wot"))
3 | implementation("org.slf4j:slf4j-api:2.0.16")
4 | implementation("com.hivemq:hivemq-mqtt-client:1.3.3")
5 | testImplementation("ch.qos.logback:logback-classic:1.5.12")
6 | testImplementation("app.cash.turbine:turbine:1.2.0")
7 | testImplementation("org.testcontainers:testcontainers:1.20.3")
8 | }
9 |
--------------------------------------------------------------------------------
/kotlin-wot-reflection/src/test/kotlin/reflection/things/SimpleThingInterface.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.reflection.things
8 |
9 | import org.eclipse.thingweb.reflection.annotations.Action
10 |
11 | interface SimpleThingInterface {
12 |
13 | @Action()
14 | suspend fun outputAction() : String
15 | }
--------------------------------------------------------------------------------
/kotlin-wot-reflection/build.gradle.kts:
--------------------------------------------------------------------------------
1 | dependencies {
2 | api(project(":kotlin-wot"))
3 | implementation("org.slf4j:slf4j-api:2.0.16")
4 | //implementation("ch.qos.logback:logback-classic:1.5.12")
5 | implementation("org.jetbrains.kotlin:kotlin-reflect")
6 | testImplementation("io.mockk:mockk:1.13.13")
7 | testImplementation(project(":kotlin-wot-binding-http"))
8 | testImplementation("com.willowtreeapps.assertk:assertk:0.28.1")
9 | }
--------------------------------------------------------------------------------
/kotlin-wot-binding-http/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/kotlin-wot-tool-example/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-http/src/main/kotlin/http/HttpClientConfig.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package http
8 |
9 | import org.eclipse.thingweb.security.SecurityScheme
10 |
11 | data class HttpClientConfig(
12 | val port: Int?,
13 | val address: String?,
14 | val allowSelfSigned: Boolean,
15 | val serverKey: String,
16 | val serverCert: String,
17 | val security: SecurityScheme
18 | )
--------------------------------------------------------------------------------
/kotlin-wot-binding-mqtt/src/main/kotlin/mqtt/MqttProtocolException.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.mqtt
8 |
9 |
10 | internal class MqttProtocolException : Exception {
11 | constructor(message: String) : super(message)
12 |
13 | constructor(message: String, cause: Throwable?) : super(message, cause)
14 | constructor(cause: Throwable?) : super(cause)
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/kotlin-wot-spring-boot-starter/src/test/kotlin/spring/TestApplication.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.spring
8 |
9 | import org.springframework.boot.autoconfigure.SpringBootApplication
10 | import org.springframework.boot.runApplication
11 |
12 |
13 | fun main(args: Array) {
14 | runApplication(*args)
15 | }
16 |
17 | @SpringBootApplication
18 | class TestApplication {
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/kotlin-wot-spring-boot-starter/src/test/resources/application.yaml:
--------------------------------------------------------------------------------
1 | #
2 | # SPDX-FileCopyrightText: Robert Winkler
3 | #
4 | # SPDX-License-Identifier: Apache-2.0
5 | #
6 |
7 | wot:
8 | servient:
9 | security:
10 | credentials:
11 | "[urn:dev:wot:org:eclipse:thingweb:security-example]":
12 | type: bearer
13 | token: test
14 | websocket:
15 | server:
16 | enabled: false
17 | port: 9090
18 | http:
19 | server:
20 | enabled: false
21 | port: 9090
--------------------------------------------------------------------------------
/kotlin-wot-binding-mqtt/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-websocket/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-websocket/src/main/kotlin/websocket/HttpClientConfig.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.websocket
8 |
9 | import org.eclipse.thingweb.security.SecurityScheme
10 |
11 | data class HttpClientConfig(
12 | val port: Int?,
13 | val address: String?,
14 | val allowSelfSigned: Boolean,
15 | val serverKey: String,
16 | val serverCert: String,
17 | val security: SecurityScheme
18 | )
19 |
--------------------------------------------------------------------------------
/kotlin-wot-tool-example/src/main/kotlin/example/ToolApplication.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.example
8 |
9 | import org.springframework.boot.autoconfigure.SpringBootApplication
10 | import org.springframework.boot.runApplication
11 |
12 |
13 | fun main(args: Array) {
14 | runApplication(*args)
15 | }
16 |
17 | @SpringBootApplication
18 | class ThingAgentApplication {
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-http/src/main/kotlin/http/HttpsProtocolClientFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.http
8 |
9 | /**
10 | * Creates new [HttpProtocolClient] instances that allow consuming Things via HTTPS.
11 | */
12 | class HttpsProtocolClientFactory() : HttpProtocolClientFactory() {
13 |
14 | override fun toString(): String {
15 | return "HttpsClient"
16 | }
17 |
18 | override val scheme: String
19 | get() = "https"
20 | }
21 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-mqtt/src/main/kotlin/mqtt/MqttClientConfig.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.mqtt
8 |
9 | import java.util.*
10 |
11 | data class MqttClientConfig(val host: String,
12 | val port: Int,
13 | val clientId: String = UUID.randomUUID().toString(),
14 | private val username: String? = null,
15 | private val password: String? = null)
16 |
17 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | mavenCentral()
4 | gradlePluginPortal()
5 | }
6 | }
7 |
8 | plugins {
9 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
10 | }
11 |
12 | rootProject.name = "kotlin-wot"
13 | include("kotlin-wot")
14 | include("kotlin-wot-binding-http")
15 | include("kotlin-wot-binding-mqtt")
16 | include("kotlin-wot-binding-websocket")
17 | include("kotlin-wot-reflection")
18 | include("kotlin-wot-spring-boot-starter")
19 | include("kotlin-wot-tool-example")
20 | include("kotlin-wot-lmos-protocol")
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/binding/ProtocolServerException.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package ai.anfc.lmos.wot.binding
8 |
9 | import org.eclipse.thingweb.ServientException
10 |
11 |
12 | /**
13 | * A ProtocolServerException is thrown by [ProtocolServer] implementations when errors occur.
14 | */
15 | open class ProtocolServerException : ServientException {
16 | constructor(message: String?) : super(message)
17 | constructor(cause: Throwable?) : super(cause)
18 | constructor() : super()
19 | }
20 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-websocket/src/main/kotlin/websocket/SecureWebSocketProtocolClientFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.websocket
8 |
9 | /**
10 | * Creates new [WebSocketProtocolClient] instances that allow consuming Things via WSS.
11 | */
12 | class SecureWebSocketProtocolClientFactory() : WebSocketProtocolClientFactory() {
13 |
14 | override fun toString(): String {
15 | return "SecureWebSocketProtocolClient"
16 | }
17 |
18 | override val scheme: String
19 | get() = "wss"
20 | }
21 |
--------------------------------------------------------------------------------
/kotlin-wot-lmos-protocol/src/main/kotlin/protocol/LmosProtocol.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.protocol
8 |
9 | class LMOSThingType {
10 |
11 | companion object {
12 | const val TOOL = "lmos:Tool"
13 | const val AGENT = "lmos:Agent"
14 | }
15 | }
16 |
17 | class LMOSContext {
18 |
19 | companion object {
20 | const val prefix = "lmos"
21 | const val url = "https://eclipse.dev/lmos/protocol/v1"
22 | }
23 | }
24 |
25 | const val LMOS_PROTOCOL_NAME = "lmosprotocol"
26 |
27 |
28 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/binding/ProtocolClientException.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package ai.anfc.lmos.wot.binding
8 |
9 | import org.eclipse.thingweb.ServientException
10 |
11 |
12 | /**
13 | * A ProtocolClientException is thrown by [ProtocolClient] implementations when errors occur.
14 | */
15 | open class ProtocolClientException : ServientException {
16 | constructor(message: String) : super(message)
17 | constructor(cause: Throwable) : super(cause)
18 | constructor(message: String, cause: Exception): super(message, cause)
19 | }
20 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-http/src/test/kotlin/http/HttpProtocolClientFactoryTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.http
8 |
9 | import kotlin.test.Test
10 | import kotlin.test.assertEquals
11 | import kotlin.test.assertIs
12 |
13 | class HttpProtocolClientFactoryTest {
14 | @Test
15 | fun getScheme() {
16 | assertEquals("http", HttpProtocolClientFactory().scheme)
17 | }
18 |
19 | @Test
20 | fun getClient() {
21 | assertIs( HttpProtocolClientFactory().createClient())
22 | }
23 | }
--------------------------------------------------------------------------------
/kotlin-wot-binding-http/src/test/kotlin/http/HttpsProtocolClientFactoryTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.http
8 |
9 | import kotlin.test.Test
10 | import kotlin.test.assertEquals
11 | import kotlin.test.assertIs
12 |
13 | class HttpsProtocolClientFactoryTest {
14 |
15 | @Test
16 | fun getScheme() {
17 | assertEquals("https", HttpsProtocolClientFactory().scheme)
18 | }
19 |
20 | @Test
21 | fun getClient() {
22 | assertIs( HttpsProtocolClientFactory().createClient())
23 | }
24 | }
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/binding/ProtocolClientNotImplementedException.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package ai.anfc.lmos.wot.binding
8 |
9 |
10 | /**
11 | * This exception is thrown when the a [ProtocolClient] implementation does not support a
12 | * requested functionality.
13 | */
14 | class ProtocolClientNotImplementedException : ProtocolClientException {
15 | constructor(
16 | clazz: Class<*>,
17 | operation: String
18 | ) : super(clazz.getSimpleName() + " does not implement '" + operation + "'")
19 |
20 | constructor(message: String) : super(message)
21 | }
22 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/binding/ProtocolServerNotImplementedException.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package ai.anfc.lmos.wot.binding
8 |
9 |
10 | /**
11 | * This exception is thrown when the a [ProtocolServer] implementation does not support a
12 | * requested functionality.
13 | */
14 | class ProtocolServerNotImplementedException : ProtocolServerException {
15 | constructor(
16 | clazz: Class<*>,
17 | operation: String
18 | ) : super(clazz.getSimpleName() + " does not implement '" + operation + "'")
19 |
20 | constructor(message: String?) : super(message)
21 | }
22 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-mqtt/src/test/kotlin/integration/MqttProtocolClientFactoryTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.mqtt
8 |
9 | import kotlin.test.Test
10 | import kotlin.test.assertEquals
11 |
12 | class MqttProtocolClientFactoryTest {
13 |
14 | @Test
15 | fun getMqttScheme() {
16 | assertEquals("mqtt", MqttProtocolClientFactory(MqttClientConfig("test", 1, "test")).scheme)
17 | }
18 | @Test
19 | fun getMqttsScheme() {
20 | assertEquals("mqtts", MqttsProtocolClientFactory(MqttClientConfig("test", 2, "test")).scheme)
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/REUSE.toml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 Deutsche Telekom AG
2 | #
3 | # SPDX-License-Identifier: Apache-2.0
4 | version = 1
5 |
6 | [[annotations]]
7 | path = [
8 | ".github/**",
9 | ".gitignore",
10 | "*.md",
11 | ".devcontainer/**",
12 | "*.properties",
13 | "*.kts",
14 | "**/*.kts",
15 | "**/*.xml",
16 | "**/*.conf",
17 | "**/*.yaml",
18 | "**/*.imports"
19 | ]
20 | SPDX-FileCopyrightText = "2024 Robert Winkler"
21 | SPDX-License-Identifier = "Apache-2.0"
22 |
23 | [[annotations]]
24 | path = [
25 | "gradle/**",
26 | "gradlew",
27 | "gradlew.bat"
28 | ]
29 | SPDX-FileCopyrightText = "Copyright 2015 the original author or authors."
30 | SPDX-License-Identifier = "Apache-2.0"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 | !gradle/wrapper/gradle-wrapper.jar
4 | !**/src/main/**/build/
5 | !**/src/test/**/build/
6 | .kotlin
7 |
8 | ### IntelliJ IDEA ###
9 | .idea
10 | .idea/modules.xml
11 | .idea/jarRepositories.xml
12 | .idea/compiler.xml
13 | .idea/libraries/
14 | *.iws
15 | *.iml
16 | *.ipr
17 | out/
18 | !**/src/main/**/out/
19 | !**/src/test/**/out/
20 |
21 | ### Eclipse ###
22 | .apt_generated
23 | .classpath
24 | .factorypath
25 | .project
26 | .settings
27 | .springBeans
28 | .sts4-cache
29 | bin/
30 | !**/src/main/**/bin/
31 | !**/src/test/**/bin/
32 |
33 | ### NetBeans ###
34 | /nbproject/private/
35 | /nbbuild/
36 | /dist/
37 | /nbdist/
38 | /.nb-gradle/
39 |
40 | ### VS Code ###
41 | .vscode/
42 |
43 | ### Mac OS ###
44 | .DS_Store
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/filter/ThingFilter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing.filter
8 |
9 | import java.net.URI
10 |
11 | /**
12 | * ThingFilter is used for the discovery process and specifies what things to look for and where to
13 | * look for them.
14 | */
15 | data class ThingFilter(val url: URI? = null, val query: ThingQuery? = null, var method: DiscoveryMethod = DiscoveryMethod.ANY) {
16 |
17 | override fun toString(): String {
18 | return "ThingFilter{" +
19 | "method=" + method +
20 | ", url=" + url +
21 | ", query=" + query +
22 | '}'
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/filter/DiscoveryMethod.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing.filter
8 |
9 | /**
10 | * Defines "Where" to search for things during a discovery process.
11 | */
12 | enum class DiscoveryMethod {
13 | /**
14 | * Uses the discovery mechanisms provided by all [ProtocolClient] implementations to
15 | * consider all available Things.
16 | */
17 | ANY,
18 |
19 | /**
20 | * Searches only on the local [Servient].
21 | */
22 | LOCAL,
23 |
24 | /**
25 | * Is used together with a URL to search in a specific Thing Directory.
26 | */
27 | DIRECTORY
28 | // MULTICAST
29 | }
30 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/security/PSKSecurityScheme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.security
8 |
9 | import com.fasterxml.jackson.annotation.JsonInclude
10 |
11 | /**
12 | * Pre-shared key authentication security configuration identified by the term psk (i.e., "scheme":
13 | * "psk").
See also: https://www.w3.org/2019/wot/security#psksecurityscheme
14 | */
15 | class PSKSecurityScheme(@field:JsonInclude(JsonInclude.Include.NON_EMPTY) val identity: String) : SecurityScheme {
16 |
17 | override fun toString(): String {
18 | return "PSKSecurityScheme{" +
19 | "identity='" + identity + '\'' +
20 | '}'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/security/PublicSecurityScheme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.security
8 |
9 | import com.fasterxml.jackson.annotation.JsonInclude
10 |
11 | /**
12 | * Raw public key asymmetric key security configuration identified by the term public (i.e.,
13 | * "scheme": "public").
See also: https://www.w3.org/2019/wot/security#publicsecurityscheme
14 | */
15 | class PublicSecurityScheme(@field:JsonInclude(JsonInclude.Include.NON_EMPTY) val identity: String) : SecurityScheme {
16 |
17 | override fun toString(): String {
18 | return "PublicSecurityScheme{" +
19 | "identity='" + identity + '\'' +
20 | '}'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-websocket/src/test/kotlin/websocket/WebsocketProtocolClientFactoryTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.mqtt
8 |
9 | import org.eclipse.thingweb.binding.websocket.SecureWebSocketProtocolClientFactory
10 | import org.eclipse.thingweb.binding.websocket.WebSocketProtocolClientFactory
11 | import kotlin.test.Test
12 | import kotlin.test.assertEquals
13 |
14 | class WebsocketProtocolClientFactoryTest {
15 |
16 | @Test
17 | fun getWsScheme() {
18 | assertEquals("ws", WebSocketProtocolClientFactory().scheme)
19 | }
20 | @Test
21 | fun getWssScheme() {
22 | assertEquals("wss", SecureWebSocketProtocolClientFactory().scheme)
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/kotlin-wot/build.gradle.kts:
--------------------------------------------------------------------------------
1 | dependencies {
2 | api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
3 | api("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.3")
4 | implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.3")
5 |
6 | // Tracing
7 | api(platform("io.opentelemetry:opentelemetry-bom:1.47.0"))
8 | api("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:2.13.1")
9 | api("io.opentelemetry:opentelemetry-extension-kotlin")
10 | api("io.opentelemetry:opentelemetry-api")
11 |
12 | implementation("org.slf4j:slf4j-api:2.0.16")
13 | testImplementation("net.javacrumbs.json-unit:json-unit-assertj:3.4.1")
14 | testImplementation("io.mockk:mockk:1.13.13")
15 | testImplementation("app.cash.turbine:turbine:1.2.0")
16 | }
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/security/CertSecurityScheme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.security
8 |
9 | import com.fasterxml.jackson.annotation.JsonInclude
10 |
11 | /**
12 | * Certificate-based asymmetric key security configuration conformant with X509V3 identified by the
13 | * term cert (i.e., "scheme": "cert").
See also: https://www.w3.org/2019/wot/security#certsecurityscheme
14 | */
15 | class CertSecurityScheme(@field:JsonInclude(JsonInclude.Include.NON_EMPTY) val identity: String) : SecurityScheme {
16 |
17 | override fun toString(): String {
18 | return "CertSecurityScheme{" +
19 | "identity='" + identity + '\'' +
20 | '}'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/binding/ProtocolClientFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package ai.anfc.lmos.wot.binding
8 |
9 |
10 | /**
11 | * A ProtocolClientFactory is responsible for creating new [ProtocolClient] instances. There
12 | * is a separate client instance for each [ConsumedThing].
13 | */
14 | interface ProtocolClientFactory {
15 |
16 | val scheme: String
17 |
18 | /**
19 | * Is called on servient start.
20 | *
21 | * @return
22 | */
23 | suspend fun init()
24 |
25 | /**
26 | * Is called on servient shutdown.
27 | *
28 | * @return
29 | */
30 | suspend fun destroy()
31 |
32 | /** Creates a new [ProtocolClient] */
33 | fun createClient(): ProtocolClient
34 | }
35 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-http/src/main/kotlin/http/HttpProtocolClientFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.http
8 |
9 | import ai.anfc.lmos.wot.binding.ProtocolClient
10 | import ai.anfc.lmos.wot.binding.ProtocolClientFactory
11 |
12 | /**
13 | * Creates new [HttpProtocolClient] instances.
14 | */
15 | open class HttpProtocolClientFactory() : ProtocolClientFactory {
16 | override fun toString(): String {
17 | return "HttpClient"
18 | }
19 | override val scheme: String
20 | get() = "http"
21 |
22 | override suspend fun init() {
23 | // TODO
24 | }
25 |
26 | override suspend fun destroy() {
27 | // TODO
28 | }
29 |
30 | override fun createClient(): ProtocolClient = HttpProtocolClient()
31 | }
32 |
--------------------------------------------------------------------------------
/kotlin-wot-tool-example/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | #
2 | # SPDX-FileCopyrightText: Robert Winkler
3 | #
4 | # SPDX-License-Identifier: Apache-2.0
5 | #
6 |
7 | arc:
8 | ai:
9 | clients:
10 | - id: GPT-4o
11 | model-name: GPT35T-1106
12 | api-key: dummy
13 | client: azure
14 | url: https://gpt4-uk.openai.azure.com
15 |
16 | wot:
17 | servient:
18 | websocket:
19 | server:
20 | enabled: false
21 | host: localhost
22 | port: 9099
23 | http:
24 | server:
25 | enabled: true
26 | host: localhost
27 | port: 9099
28 | mqtt:
29 | server:
30 | enabled: false
31 | host: localhost
32 | port: 54801
33 | clientId: wot-servient
34 | client:
35 | enabled: false
36 | host: localhost
37 | port: 54801
38 | clientId: wot-client
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/schema/Type.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing.schema
8 |
9 | import org.eclipse.thingweb.thing.TypeDeserializer
10 | import org.eclipse.thingweb.thing.TypeSerializer
11 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize
12 | import com.fasterxml.jackson.databind.annotation.JsonSerialize
13 |
14 |
15 | @JsonDeserialize(using = TypeDeserializer::class)
16 | @JsonSerialize(using = TypeSerializer::class)
17 | data class Type(val types: MutableSet = HashSet()) {
18 |
19 | constructor(type: String) : this() {
20 | addType(type)
21 | }
22 |
23 | fun addType(type: String): Type {
24 | types.add(type)
25 | return this
26 | }
27 |
28 | val defaultType: String
29 | get() = types.first()
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/security/NoSecurityScheme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.security
8 |
9 |
10 | /**
11 | * A security configuration corresponding to identified by the term nosec (i.e., "scheme": "nosec"),
12 | * indicating there is no authentication or other mechanism required to access the resource.
See
13 | * also: https://www.w3.org/2019/wot/security#nosecurityscheme
14 | */
15 | class NoSecurityScheme : SecurityScheme {
16 | override fun hashCode(): Int {
17 | return 42
18 | }
19 |
20 | override fun equals(o: Any?): Boolean {
21 | return if (this === o) {
22 | true
23 | } else o != null && javaClass == o.javaClass
24 | }
25 |
26 | override fun toString(): String {
27 | return "NoSecurityScheme{}"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-http/src/main/kotlin/http/routes/ThingsRoute.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.http.routes
8 |
9 | import org.eclipse.thingweb.content.Content
10 | import org.eclipse.thingweb.content.ContentManager
11 | import org.eclipse.thingweb.thing.ExposedThing
12 | import io.ktor.http.cio.*
13 | import io.ktor.server.routing.*
14 | import io.ktor.util.reflect.*
15 |
16 | /**
17 | * Endpoint for listing all Things from the [io.github.sanecity.wot.Servient].
18 | */
19 | class ThingsRoute(private val things: Map) : AbstractRoute() {
20 | @Throws(Exception::class)
21 | fun handle(request: RoutingRequest): Content {
22 | val requestContentType: String = getOrDefaultRequestContentType(request).toString()
23 | return ContentManager.valueToContent(things, requestContentType)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-websocket/src/main/kotlin/websocket/WebSocketProtocolClientFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.websocket
8 |
9 | import ai.anfc.lmos.wot.binding.ProtocolClient
10 | import ai.anfc.lmos.wot.binding.ProtocolClientFactory
11 |
12 | /**
13 | * Creates new [WebSocketProtocolClient] instances.
14 | */
15 | open class WebSocketProtocolClientFactory(private val httpClientConfig: HttpClientConfig? = null) : ProtocolClientFactory {
16 | override fun toString(): String {
17 | return "WebSocketProtocolClient"
18 | }
19 | override val scheme: String
20 | get() = "ws"
21 |
22 | override suspend fun init() {
23 |
24 | }
25 |
26 | override suspend fun destroy() {
27 |
28 | }
29 |
30 | override fun createClient(): ProtocolClient = WebSocketProtocolClient(httpClientConfig)
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/OperationsDeserializer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing
8 |
9 | import org.eclipse.thingweb.thing.form.Operation
10 | import com.fasterxml.jackson.core.JsonParser
11 | import com.fasterxml.jackson.databind.DeserializationContext
12 | import com.fasterxml.jackson.databind.JsonDeserializer
13 | import com.fasterxml.jackson.databind.JsonNode
14 |
15 | class OperationsDeserializer : JsonDeserializer>() {
16 | override fun deserialize(p: JsonParser, ctxt: DeserializationContext): List {
17 | val node: JsonNode = p.codec.readTree(p)
18 |
19 | return when {
20 | node.isTextual -> listOf(Operation.fromJsonValue(node.asText()))
21 | node.isArray -> node.map { Operation.fromJsonValue(it.asText()) }
22 | else -> emptyList()
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/kotlin-wot/src/test/kotlin/thing/form/OperationTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing.form
8 |
9 | import org.eclipse.thingweb.JsonMapper
10 | import com.fasterxml.jackson.core.JsonProcessingException
11 | import java.io.IOException
12 | import kotlin.test.Test
13 | import kotlin.test.assertEquals
14 |
15 | class OperationTest {
16 | @Test
17 | @Throws(JsonProcessingException::class)
18 | fun toJson() {
19 | val op = Operation.READ_PROPERTY
20 | val json = JsonMapper.instance.writeValueAsString(op)
21 | assertEquals("\"readproperty\"", json)
22 | }
23 |
24 | @Test
25 | @Throws(IOException::class)
26 | fun fromJson() {
27 | val json = "\"writeproperty\""
28 | val op = JsonMapper.instance.readValue(json, Operation::class.java)
29 | assertEquals(Operation.WRITE_PROPERTY, op)
30 | }
31 | }
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/security/APIKeySecurityScheme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.security
8 |
9 | import com.fasterxml.jackson.annotation.JsonInclude
10 |
11 | /**
12 | * API key authentication security configuration identified by the term apikey (i.e., "scheme":
13 | * "apikey"). This is for the case where the access token is opaque and is not using a standard
14 | * token format.
See also: https://www.w3.org/2019/wot/security#apikeysecurityscheme
15 | */
16 | class APIKeySecurityScheme(
17 | @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val `in`: String, @field:JsonInclude(
18 | JsonInclude.Include.NON_EMPTY
19 | ) val name: String
20 | ) : SecurityScheme {
21 |
22 | override fun toString(): String {
23 | return "APIKeySecurityScheme{" +
24 | "in='" + `in` + '\'' +
25 | ", name='" + name + '\'' +
26 | '}'
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/TypeSerializer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing
8 |
9 | import org.eclipse.thingweb.thing.schema.Type
10 | import com.fasterxml.jackson.core.JsonGenerator
11 | import com.fasterxml.jackson.databind.JsonSerializer
12 | import com.fasterxml.jackson.databind.SerializerProvider
13 | import java.io.IOException
14 |
15 |
16 | class TypeSerializer : JsonSerializer() {
17 |
18 | @Throws(IOException::class)
19 | override fun serialize(type: Type, gen: JsonGenerator, serializers: SerializerProvider) {
20 | val types = type.types
21 | if (types.size == 1) {
22 | gen.writeString(types.iterator().next())
23 | } else if (types.size > 1) {
24 | gen.writeStartArray()
25 | for (t in types) {
26 | gen.writeString(t)
27 |
28 | }
29 | gen.writeEndArray()
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/schema/InteractionOutput.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing.schema
8 |
9 | import org.eclipse.thingweb.content.Content
10 | import org.eclipse.thingweb.content.ContentManager
11 | import com.fasterxml.jackson.databind.JsonNode
12 | import kotlinx.coroutines.flow.Flow
13 |
14 | class InteractionOutput(
15 | private val content: Content,
16 | override val schema: DataSchema<*>?
17 | ) : WoTInteractionOutput {
18 | override val data: Flow?
19 | get() = TODO("Not yet implemented")
20 |
21 | override var dataUsed: Boolean = false
22 |
23 | private val lazyValue: JsonNode? by lazy {
24 | schema?.let { ContentManager.contentToValue(content, schema) }
25 | }
26 | override fun arrayBuffer(): ByteArray {
27 | return content.body
28 | }
29 |
30 | override fun value(): JsonNode {
31 | return ContentManager.contentToValue(content, schema)
32 | }
33 | }
--------------------------------------------------------------------------------
/kotlin-wot-spring-boot-starter/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.springframework.boot.gradle.tasks.bundling.BootJar
2 |
3 | plugins {
4 | kotlin("plugin.spring") version "2.1.20"
5 | id("org.springframework.boot") version "3.1.5" // Use the latest compatible version
6 | id("io.spring.dependency-management") version "1.1.3"
7 | }
8 |
9 | dependencies {
10 | api(project(":kotlin-wot"))
11 | api(project(":kotlin-wot-reflection"))
12 | api("org.springframework.boot:spring-boot-starter")
13 | api("org.springframework.boot:spring-boot-starter-logging")
14 | compileOnly(project(":kotlin-wot-binding-http"))
15 | compileOnly(project(":kotlin-wot-binding-mqtt"))
16 | compileOnly(project(":kotlin-wot-binding-websocket"))
17 | testImplementation("org.springframework.boot:spring-boot-starter-test")
18 | testImplementation(project(":kotlin-wot-binding-http"))
19 | testImplementation(project(":kotlin-wot-binding-websocket"))
20 | }
21 |
22 | tasks.getByName("bootJar") {
23 | enabled = false
24 | }
25 |
26 | tasks.getByName("jar") {
27 | enabled = true
28 | }
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/filter/ThingQuery.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing.filter
8 |
9 | import org.eclipse.thingweb.ServientException
10 | import org.eclipse.thingweb.thing.ExposedThing
11 | import org.eclipse.thingweb.thing.schema.WoTExposedThing
12 |
13 | /**
14 | * Is used in the discovery process and filters the things according to certain properties
15 | */
16 | interface ThingQuery {
17 |
18 | /**
19 | * Applies the filter to the found things and returns only those things that meet the desired
20 | * criteria
21 | *
22 | * @param things
23 | * @return
24 | */
25 | @Throws(ThingQueryException::class)
26 | fun filter(things: Collection): List
27 | }
28 |
29 | /**
30 | * This exception is thrown when an invalid query is attempted to be used.
31 | */
32 | class ThingQueryException : ServientException {
33 | constructor(cause: Throwable?) : super(cause)
34 | constructor(message: String?) : super(message)
35 | }
36 |
37 |
38 |
--------------------------------------------------------------------------------
/kotlin-wot-spring-boot-starter/src/main/kotlin/spring/MqttProperties.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.spring
8 |
9 | import org.springframework.boot.context.properties.ConfigurationProperties
10 | import org.springframework.validation.annotation.Validated
11 |
12 | @ConfigurationProperties(prefix = "wot.servient.mqtt.server", ignoreUnknownFields = true)
13 | @Validated
14 | data class MqttServerProperties(
15 | var enabled: Boolean = true,
16 | var host: String = "localhost",
17 | var port: Int = 1883,
18 | var clientId : String = "wot-server",
19 | var username: String? = null,
20 | var password: String? = null
21 | )
22 |
23 | @ConfigurationProperties(prefix = "wot.servient.mqtt.client", ignoreUnknownFields = true)
24 | @Validated
25 | data class MqttClientProperties(
26 | var enabled: Boolean = true,
27 | var host: String = "localhost",
28 | var port: Int = 1883,
29 | var clientId : String = "wot-client",
30 | var username: String? = null,
31 | var password: String? = null
32 | )
--------------------------------------------------------------------------------
/kotlin-wot-binding-http/build.gradle.kts:
--------------------------------------------------------------------------------
1 | dependencies {
2 | implementation(platform("io.ktor:ktor-bom:3.0.3"))
3 | api(project(":kotlin-wot"))
4 | implementation("io.ktor:ktor-server-core")
5 | implementation("io.ktor:ktor-server-netty")
6 | implementation("io.ktor:ktor-client-core")
7 | implementation("io.ktor:ktor-server-status-pages")
8 | implementation("io.ktor:ktor-client-cio")
9 | implementation("io.ktor:ktor-client-auth")
10 | implementation("io.ktor:ktor-client-logging")
11 | implementation("io.ktor:ktor-server-content-negotiation")
12 | implementation("io.ktor:ktor-client-content-negotiation")
13 | implementation("io.ktor:ktor-serialization-jackson")
14 | implementation("io.ktor:ktor-server-metrics-micrometer")
15 | implementation("io.ktor:ktor-server-auto-head-response")
16 |
17 | implementation("io.opentelemetry.instrumentation:opentelemetry-ktor-3.0:2.13.1-alpha")
18 |
19 | testImplementation("io.ktor:ktor-server-test-host")
20 | testImplementation("ch.qos.logback:logback-classic:1.5.12")
21 | testImplementation("com.marcinziolo:kotlin-wiremock:2.1.1")
22 | }
23 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/security/DigestSecurityScheme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.security
8 |
9 | import com.fasterxml.jackson.annotation.JsonInclude
10 |
11 | /**
12 | * Digest authentication security configuration identified by the term digest (i.e., "scheme":
13 | * "digest"). This scheme is similar to basic authentication but with added features to avoid
14 | * man-in-the-middle attacks.
See also: https://www.w3.org/2019/wot/security#digestsecurityscheme
15 | */
16 | class DigestSecurityScheme(
17 | @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val `in`: String, @field:JsonInclude(
18 | JsonInclude.Include.NON_EMPTY
19 | ) val name: String, @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val qop: String
20 | ) : SecurityScheme {
21 |
22 | override fun toString(): String {
23 | return "DigestSecurityScheme{" +
24 | "in='" + `in` + '\'' +
25 | ", name='" + name + '\'' +
26 | ", qop='" + qop + '\'' +
27 | '}'
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/gradle.yml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 Deutsche Telekom AG
2 | #
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | # This workflow uses actions that are not certified by GitHub.
6 | # They are provided by a third-party and are governed by
7 | # separate terms of service, privacy policy, and support
8 | # documentation.
9 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
10 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
11 |
12 | name: build-publish
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 |
18 | jobs:
19 | build-publish:
20 | uses: eclipse-lmos/.github/.github/workflows/gradle-ci-main.yml@main
21 | permissions:
22 | contents: write
23 | packages: write
24 | secrets:
25 | oss-username: ${{ secrets.OSSRH_USERNAME }}
26 | oss-password: ${{ secrets.OSSRH_PASSWORD }}
27 | signing-key-id: ${{ secrets.GPG_SUBKEY_ID }}
28 | signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
29 | signing-key-password: ${{ secrets.GPG_PASSPHRASE }}
30 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-mqtt/src/main/kotlin/mqtt/MqttsProtocolClientFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.mqtt
8 |
9 | import ai.anfc.lmos.wot.binding.ProtocolClient
10 | import ai.anfc.lmos.wot.binding.ProtocolClientFactory
11 | import com.hivemq.client.mqtt.mqtt5.Mqtt5Client
12 |
13 | open class MqttsProtocolClientFactory(private val mqttClientConfig: MqttClientConfig) : ProtocolClientFactory {
14 | override fun toString(): String {
15 | return "MqttClient"
16 | }
17 | override val scheme: String
18 | get() = "mqtts"
19 |
20 | override suspend fun init() {
21 | }
22 |
23 | override suspend fun destroy() {
24 | }
25 |
26 | override fun createClient(): ProtocolClient =
27 | MqttProtocolClient(Mqtt5Client.builder()
28 | .identifier(mqttClientConfig.clientId)
29 | .serverHost(mqttClientConfig.host)
30 | .serverPort(mqttClientConfig.port)
31 | .sslWithDefaultConfig()
32 | .automaticReconnect().applyAutomaticReconnect()
33 | .build().toAsync(), true)
34 | }
35 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-websocket/build.gradle.kts:
--------------------------------------------------------------------------------
1 | dependencies {
2 | implementation(platform("io.ktor:ktor-bom:3.0.3"))
3 |
4 | api(project(":kotlin-wot"))
5 | api(project(":kotlin-wot-lmos-protocol"))
6 | implementation("io.ktor:ktor-server-netty")
7 | implementation("io.ktor:ktor-server-websockets")
8 | implementation("io.ktor:ktor-client-websocket:1.1.4")
9 | implementation("io.ktor:ktor-server-content-negotiation")
10 |
11 | // Tracing
12 | implementation("io.opentelemetry.instrumentation:opentelemetry-ktor-3.0:2.13.1-alpha")
13 |
14 | implementation("io.ktor:ktor-client-cio")
15 | implementation("io.ktor:ktor-client-auth")
16 | implementation("io.ktor:ktor-client-logging")
17 | implementation("io.ktor:ktor-server-call-logging")
18 | implementation("io.ktor:ktor-serialization-jackson")
19 | implementation("io.ktor:ktor-server-metrics-micrometer")
20 | testImplementation("io.ktor:ktor-server-test-host")
21 | testImplementation(project(":kotlin-wot-binding-http"))
22 | testImplementation("ch.qos.logback:logback-classic:1.5.12")
23 | testImplementation("com.marcinziolo:kotlin-wiremock:2.1.1")
24 | testImplementation("io.ktor:ktor-server-test-host-jvm:3.0.0")
25 | }
26 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-mqtt/src/main/kotlin/mqtt/MqttProtocolClientFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.mqtt
8 |
9 | import ai.anfc.lmos.wot.binding.ProtocolClient
10 | import ai.anfc.lmos.wot.binding.ProtocolClientFactory
11 | import com.hivemq.client.mqtt.mqtt5.Mqtt5Client
12 |
13 | open class MqttProtocolClientFactory(private val mqttClientConfig: MqttClientConfig) : ProtocolClientFactory {
14 |
15 | override fun toString(): String {
16 | return "MqttClient"
17 | }
18 | override val scheme: String
19 | get() = "mqtt"
20 |
21 | override suspend fun init() {
22 |
23 | }
24 |
25 | override suspend fun destroy() {
26 |
27 | }
28 |
29 | override fun createClient(): ProtocolClient =
30 | MqttProtocolClient(
31 | Mqtt5Client.builder()
32 | .identifier(mqttClientConfig.clientId)
33 | .serverHost(mqttClientConfig.host)
34 | .serverPort(mqttClientConfig.port)
35 | //.automaticReconnect()
36 | //.applyAutomaticReconnect()
37 | .build()
38 | .toAsync())
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/kotlin-wot-spring-boot-starter/src/main/kotlin/spring/HttpProperties.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.spring
8 |
9 | import org.springframework.boot.context.properties.ConfigurationProperties
10 | import org.springframework.validation.annotation.Validated
11 |
12 | open class ServerProperties(
13 | var enabled: Boolean = true,
14 | var host: String = "0.0.0.0",
15 | var port: Int = 8080,
16 | var baseUrls: List
17 | )
18 |
19 |
20 | @ConfigurationProperties(prefix = "wot.servient.http.server", ignoreUnknownFields = true)
21 | @Validated
22 | class HttpServerProperties(
23 | enabled: Boolean = true,
24 | host: String = "0.0.0.0",
25 | port: Int = 8080,
26 | baseUrls: List = listOf("http://localhost:$port")
27 | ) : ServerProperties(enabled, host, port, baseUrls)
28 |
29 | @ConfigurationProperties(prefix = "wot.servient.websocket.server", ignoreUnknownFields = true)
30 | @Validated
31 | class WebsocketProperties(
32 | enabled: Boolean = true,
33 | host: String = "0.0.0.0",
34 | port: Int = 8080,
35 | baseUrls: List = listOf("ws://localhost:$port")
36 | ) : ServerProperties(enabled, host, port, baseUrls)
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/content/Content.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.content
8 |
9 | /**
10 | * Represents any serialized content. Enables the transfer of arbitrary data structures.
11 | */
12 | data class Content(val type: String = ContentManager.DEFAULT_MEDIA_TYPE, val body: ByteArray = ByteArray(0)) {
13 |
14 | companion object {
15 | val EMPTY_CONTENT = Content(ContentManager.DEFAULT_MEDIA_TYPE, ByteArray(0))
16 | }
17 |
18 | override fun equals(other: Any?): Boolean {
19 | if (this === other) return true
20 | if (javaClass != other?.javaClass) return false
21 |
22 | other as Content
23 |
24 | if (type != other.type) return false
25 | if (!body.contentEquals(other.body)) return false
26 |
27 | return true
28 | }
29 |
30 | override fun hashCode(): Int {
31 | var result = type.hashCode()
32 | result = 31 * result + body.contentHashCode()
33 | return result
34 | }
35 | }
36 |
37 | fun String.toJsonContent(): Content{
38 | val jsonContent = """"$this""""
39 | return Content(ContentManager.DEFAULT_MEDIA_TYPE, jsonContent.toByteArray())
40 | }
41 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/binding/ProtocolServer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package ai.anfc.lmos.wot.binding
8 |
9 | import org.eclipse.thingweb.Servient
10 | import org.eclipse.thingweb.thing.ExposedThing
11 |
12 | /**
13 | * A ProtocolServer defines how to expose Thing for interaction via a specific protocol (e.g. HTTP,
14 | * MQTT, etc.).
15 | */
16 | interface ProtocolServer {
17 |
18 | /**
19 | * Starts the server (e.g. HTTP server) and makes it ready for requests to the exposed things.
20 | *
21 | * @param servient
22 | * @return
23 | */
24 | suspend fun start(servient: Servient)
25 |
26 | /**
27 | * Stops the server (e.g. HTTP server) and ends the exposure of the Things
28 | *
29 | * @return
30 | */
31 | suspend fun stop()
32 |
33 | /**
34 | * Exposes `thing` and allows interaction with it.
35 | *
36 | * @param thing
37 | * @return
38 | */
39 | suspend fun expose(thing: ExposedThing)
40 |
41 | /**
42 | * Stops the exposure of `thing` and allows no further interaction with the thing.
43 | *
44 | * @param thing
45 | * @return
46 | */
47 | suspend fun destroy(thing: ExposedThing)
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/kotlin-wot-reflection/src/test/kotlin/reflection/ConsumedThingBuilderTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package reflection
8 |
9 | import org.eclipse.thingweb.Servient
10 | import org.eclipse.thingweb.Wot
11 | import org.eclipse.thingweb.reflection.ExposedThingBuilder
12 | import org.eclipse.thingweb.reflection.things.ComplexThing
13 | import org.eclipse.thingweb.thing.schema.WoTExposedThing
14 | import org.eclipse.thingweb.thing.schema.WoTThingDescription
15 | import kotlin.test.BeforeTest
16 |
17 | class ConsumedThingBuilderTest {
18 |
19 | lateinit var servient: Servient
20 | lateinit var wot: Wot
21 | lateinit var complexThing: ComplexThing
22 | lateinit var exposedThing: WoTExposedThing
23 | lateinit var thingDescription: WoTThingDescription
24 |
25 | @BeforeTest
26 | fun setUp() {
27 | // Set up the servient and WoT instance
28 | servient = Servient()
29 | wot = Wot.create(servient)
30 |
31 | // Create an instance of ComplexThing
32 | complexThing = ComplexThing()
33 |
34 | // Generate ThingDescription from the class
35 | exposedThing = ExposedThingBuilder.createExposedThing(wot, complexThing, ComplexThing::class) as WoTExposedThing
36 | }
37 | }
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/security/BasicSecurityScheme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.security
8 |
9 | import com.fasterxml.jackson.annotation.JsonInclude
10 | import java.util.*
11 |
12 | /**
13 | * Basic authentication security configuration identified by the term basic (i.e., "scheme":
14 | * "basic"), using an unencrypted username and password. This scheme should be used with some other
15 | * security mechanism providing confidentiality, for example, TLS.
See also:
16 | * https://www.w3.org/2019/wot/security#basicsecurityscheme
17 | */
18 | class BasicSecurityScheme @JvmOverloads constructor(@field:JsonInclude(JsonInclude.Include.NON_EMPTY) var `in`: String? = null) :
19 | SecurityScheme {
20 |
21 | override fun hashCode(): Int {
22 | return Objects.hash(`in`)
23 | }
24 |
25 | override fun equals(o: Any?): Boolean {
26 | if (this === o) {
27 | return true
28 | }
29 | if (o !is BasicSecurityScheme) {
30 | return false
31 | }
32 | return `in` == o.`in`
33 | }
34 |
35 | override fun toString(): String {
36 | return "BasicSecurityScheme{" +
37 | "in='" + `in` + '\'' +
38 | '}'
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/kotlin-wot/src/test/kotlin/thing/form/FormTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing.form
8 |
9 | import org.eclipse.thingweb.JsonMapper
10 | import net.javacrumbs.jsonunit.assertj.JsonAssertions
11 | import net.javacrumbs.jsonunit.core.Option
12 | import kotlin.test.Test
13 | import kotlin.test.assertEquals
14 |
15 | class FormTest {
16 | @Test
17 | fun testForm() {
18 | val form = Form(
19 | href = "test:/foo",
20 | op = listOf( Operation.OBSERVE_PROPERTY),
21 | subprotocol = "longpolling",
22 | contentType = "application/json"
23 | )
24 | assertEquals("test:/foo", form.href)
25 | assertEquals(listOf(Operation.OBSERVE_PROPERTY), form.op)
26 | assertEquals("longpolling", form.subprotocol)
27 | assertEquals("application/json", form.contentType)
28 |
29 | val json = JsonMapper.instance.writeValueAsString(form)
30 |
31 | JsonAssertions.assertThatJson(json)
32 | .`when`(Option.IGNORING_ARRAY_ORDER)
33 | .isEqualTo(
34 | """{"href":"test:/foo",
35 | "op":["observeproperty"],
36 | "subprotocol":"longpolling",
37 | "contentType":"application/json"}
38 | """
39 | )
40 | }
41 | }
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/schema/Context.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing.schema
8 |
9 | import org.eclipse.thingweb.thing.ContextDeserializer
10 | import org.eclipse.thingweb.thing.ContextSerializer
11 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize
12 | import com.fasterxml.jackson.databind.annotation.JsonSerialize
13 |
14 | /**
15 | * Represents a JSON-LD context.
16 | */
17 | @JsonDeserialize(using = ContextDeserializer::class)
18 | @JsonSerialize(using = ContextSerializer::class)
19 | data class Context(val defaultUrls: MutableList = mutableListOf(), private val prefixeUrls: MutableMap = HashMap()) {
20 |
21 | constructor(url: String) : this() {
22 | addContext(url)
23 | }
24 |
25 | constructor(prefix: String, url: String) : this() {
26 | addContext(prefix, url)
27 | }
28 |
29 | fun addContext(url: String): Context {
30 | defaultUrls.add(url)
31 | return this
32 | }
33 |
34 | fun addContext(prefix: String, url: String): Context {
35 | prefixeUrls[prefix] = url
36 | return this
37 | }
38 |
39 | val prefixedUrls: Map
40 | get() = prefixeUrls.entries
41 | .filter { (key, _) -> key != null }
42 | .associate { (key, value) -> key!! to value }
43 | }
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/credentials/Credentials.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.credentials
8 |
9 | import org.eclipse.thingweb.thing.schema.WoTForm
10 | import com.fasterxml.jackson.annotation.JsonSubTypes
11 | import com.fasterxml.jackson.annotation.JsonTypeInfo
12 | import java.util.*
13 |
14 | @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
15 | @JsonSubTypes(
16 | JsonSubTypes.Type(value = BasicCredentials::class, name = "basic"),
17 | JsonSubTypes.Type(value = BearerCredentials::class, name = "bearer"),
18 | JsonSubTypes.Type(value = ApiKeyCredentials::class, name = "apikey")
19 | )
20 | interface Credentials {
21 | }
22 |
23 | fun interface CredentialsProvider {
24 | fun getCredentials(form : WoTForm): Credentials?
25 | }
26 |
27 | data class BearerCredentials(
28 | val token: String
29 | ) : Credentials{
30 | override fun toString(): String {
31 | return "Bearer $token"
32 | }
33 | }
34 |
35 | data class BasicCredentials(
36 | val username: String,
37 | val password: String
38 | ) : Credentials {
39 | override fun toString(): String {
40 | val basicAuth = Base64.getEncoder().encodeToString("$username:$password".toByteArray())
41 | return "Basic $basicAuth"
42 | }
43 | }
44 |
45 | data class ApiKeyCredentials(
46 | val apiKey: String
47 | ) : Credentials
--------------------------------------------------------------------------------
/kotlin-wot-tool-example/src/main/kotlin/example/HtmlTool.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.example
8 |
9 |
10 | import io.opentelemetry.api.trace.Span
11 | import io.opentelemetry.instrumentation.annotations.WithSpan
12 | import org.eclipse.thingweb.protocol.LMOSContext
13 | import org.eclipse.thingweb.protocol.LMOSThingType
14 | import org.eclipse.thingweb.reflection.annotations.*
15 | import org.jsoup.Jsoup
16 | import org.springframework.stereotype.Component
17 |
18 |
19 | @Thing(id= "scraper", title="Tool",
20 | description= "An HTML scraper.", type = LMOSThingType.TOOL)
21 | @VersionInfo(instance = "1.0.0")
22 | @Context(prefix = LMOSContext.prefix, url = LMOSContext.url)
23 | @Component
24 | class HtmlTool() {
25 |
26 | @Action(title = "Fetch Content", description = "Fetches the content from the specified URL.")
27 | @ActionInput(title = "url", description = "The URL to fetch content from.")
28 | @ActionOutput(title = "content", description = "The content fetched from the URL.")
29 | @WithSpan
30 | suspend fun fetchContent(url: String): String {
31 | Span.current().setAttribute("lmos.agent.scraper.input.url", url)
32 | return try {
33 | val document = Jsoup.connect(url).get()
34 | document.outerHtml()
35 | } catch (e: Exception) {
36 | "Error fetching content"
37 | }
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/security/SecurityScheme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.security
8 |
9 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties
10 | import com.fasterxml.jackson.annotation.JsonSubTypes
11 | import com.fasterxml.jackson.annotation.JsonTypeInfo
12 |
13 | /**
14 | * Describes properties of a security mechanism (e.g. password authentication).
See also:
15 | * https://www.w3.org/TR/wot-thing-description/#security-serialization-json
16 | */
17 | @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "scheme")
18 | @JsonSubTypes(
19 | JsonSubTypes.Type(value = APIKeySecurityScheme::class, name = "apikey"),
20 | JsonSubTypes.Type(value = BasicSecurityScheme::class, name = "basic"),
21 | JsonSubTypes.Type(value = BearerSecurityScheme::class, name = "bearer"),
22 | JsonSubTypes.Type(value = CertSecurityScheme::class, name = "cert"),
23 | JsonSubTypes.Type(value = DigestSecurityScheme::class, name = "digest"),
24 | JsonSubTypes.Type(value = NoSecurityScheme::class, name = "nosec"),
25 | JsonSubTypes.Type(value = OAuth2SecurityScheme::class, name = "oauth2"),
26 | JsonSubTypes.Type(value = PoPSecurityScheme::class, name = "pop"),
27 | JsonSubTypes.Type(value = PSKSecurityScheme::class, name = "psk"),
28 | JsonSubTypes.Type(value = PublicSecurityScheme::class, name = "public")
29 | )
30 | @JsonIgnoreProperties(ignoreUnknown = true)
31 | interface SecurityScheme
32 |
--------------------------------------------------------------------------------
/kotlin-wot/src/test/kotlin/thing/form/AugmentedFormTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing.form
8 |
9 | import AugmentedForm
10 | import org.eclipse.thingweb.security.BasicSecurityScheme
11 | import org.eclipse.thingweb.thing.schema.WoTForm
12 | import org.eclipse.thingweb.thing.schema.WoTThingDescription
13 | import io.mockk.every
14 | import io.mockk.mockk
15 | import kotlin.test.Test
16 | import kotlin.test.assertEquals
17 |
18 | class AugmentedFormTest {
19 |
20 | private val mockForm = mockk {
21 | every { href } returns "/resource/{id}"
22 | every { security } returns listOf("basic")
23 | }
24 |
25 | private val mockThingDescription = mockk {
26 | every { base } returns "https://example.com/api/"
27 | every { securityDefinitions } returns mutableMapOf(
28 | "basic" to BasicSecurityScheme("BasicAuth")
29 | )
30 | }
31 |
32 | @Test
33 | fun `href should resolve correctly with base`() {
34 | val augmentedForm = AugmentedForm(mockForm, mockThingDescription)
35 |
36 | val expectedHref = "https://example.com/api/resource/{id}"
37 | assertEquals(expectedHref, augmentedForm.href)
38 | }
39 |
40 | @Test
41 | fun `securityDefinitions should be mapped correctly`() {
42 | val augmentedForm = AugmentedForm(mockForm, mockThingDescription)
43 |
44 | assertEquals(1, augmentedForm.securityDefinitions.size)
45 | }
46 |
47 | }
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/UriTemplate.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing
8 |
9 | import java.util.regex.Pattern
10 |
11 | class UriTemplate(private val template: String) {
12 |
13 | companion object {
14 | // A method to create a UriTemplate from a template string
15 | fun fromTemplate(template: String): UriTemplate {
16 | return UriTemplate(template)
17 | }
18 | }
19 |
20 | // Expands the URI template with the provided uriVariables map
21 | fun expand(uriVariables: Map): String {
22 | var expandedUri = template
23 |
24 | // Iterate over all uriVariables and replace placeholders in the template
25 | uriVariables.forEach { (key, value) ->
26 | // Replace the placeholder {key} with the value from the uriVariables map
27 | expandedUri = expandedUri.replace("{$key}", value)
28 | }
29 |
30 | // Handle any remaining unresolved placeholders, for example, if a placeholder is missing a value.
31 | // For this example, let's throw an exception if we have unresolved placeholders
32 | val remainingPlaceholders = Pattern.compile("\\{([a-zA-Z0-9_]+)\\}")
33 | .matcher(expandedUri)
34 |
35 | if (remainingPlaceholders.find()) {
36 | throw IllegalArgumentException("Template contains unresolved placeholders: ${remainingPlaceholders.group()}")
37 | }
38 |
39 | return expandedUri
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/kotlin-wot/src/test/kotlin/thing/TypeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing
8 |
9 | import org.eclipse.thingweb.JsonMapper
10 | import org.eclipse.thingweb.thing.schema.Type
11 | import com.fasterxml.jackson.core.JsonProcessingException
12 | import net.javacrumbs.jsonunit.assertj.JsonAssertions
13 | import net.javacrumbs.jsonunit.core.Option
14 | import java.io.IOException
15 | import kotlin.test.Test
16 | import kotlin.test.assertEquals
17 |
18 | internal class TypeTest {
19 | @Test
20 | @Throws(IOException::class)
21 | fun fromJson() {
22 | // single value
23 | assertEquals(
24 | Type("Thing"),
25 | JsonMapper.instance.readValue("\"Thing\"", Type::class.java)
26 | )
27 |
28 | // array
29 | assertEquals(
30 | Type("Thing").addType("saref:LightSwitch"),
31 | JsonMapper.instance.readValue("[\"Thing\",\"saref:LightSwitch\"]", Type::class.java)
32 | )
33 | }
34 |
35 | @Test
36 | @Throws(JsonProcessingException::class)
37 | fun toJson() {
38 | // single value
39 | assertEquals(
40 | "\"Thing\"",
41 | JsonMapper.instance.writeValueAsString(Type("Thing"))
42 | )
43 |
44 | // multi type array
45 | JsonAssertions.assertThatJson(JsonMapper.instance.writeValueAsString(Type("Thing").addType("saref:LightSwitch")))
46 | .`when`(Option.IGNORING_ARRAY_ORDER)
47 | .isArray()
48 | .contains("Thing", "saref:LightSwitch")
49 | }
50 | }
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/security/OAuth2SecurityScheme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.security
8 |
9 | import com.fasterxml.jackson.annotation.JsonInclude
10 |
11 | /**
12 | * OAuth2 authentication security configuration for systems conformant with !RFC6749 and !RFC8252,
13 | * identified by the term oauth2 (i.e., "scheme": "oauth2"). For the implicit flow authorization
14 | * MUST be included. For the password and client flows token MUST be included. For the code flow
15 | * both authorization and token MUST be included. If no scopes are defined in the SecurityScheme
16 | * then they are considered to be empty.
See also: https://www.w3.org/2019/wot/security#oauth2securityscheme
17 | */
18 | class OAuth2SecurityScheme(
19 | @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val authorization: String?,
20 | @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val flow: String,
21 | @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val token: String?,
22 | @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val refresh: String?,
23 | @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val scopes: List?
24 | ) : SecurityScheme {
25 |
26 | override fun toString(): String {
27 | return "OAuth2SecurityScheme{" +
28 | "authorization='" + authorization + '\'' +
29 | ", flow='" + flow + '\'' +
30 | ", token='" + token + '\'' +
31 | ", refresh='" + refresh + '\'' +
32 | ", scopes=" + scopes +
33 | '}'
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/security/PoPSecurityScheme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.security
8 |
9 | import com.fasterxml.jackson.annotation.JsonInclude
10 |
11 | /**
12 | * Proof-of-possession (PoP) token authentication security configuration identified by the term pop
13 | * (i.e., "scheme": "pop"). Here jwt indicates conformance with !RFC7519, jws indicates conformance
14 | * with !RFC7797, cwt indicates conformance with !RFC8392, and jwe indicates conformance with
15 | * RFC7516, with values for alg interpreted consistently with those standards. Other formats and
16 | * algorithms for PoP tokens MAY be specified in vocabulary extensions.
See also:
17 | * https://www.w3.org/2019/wot/security#popsecurityscheme
18 | */
19 | class PoPSecurityScheme(
20 | @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val `in`: String,
21 | @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val name: String,
22 | @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val format: String,
23 | @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val authorization: String,
24 | @field:JsonInclude(JsonInclude.Include.NON_EMPTY) val alg: String
25 | ) : SecurityScheme {
26 |
27 | override fun toString(): String {
28 | return "PoPSecurityScheme{" +
29 | "in='" + `in` + '\'' +
30 | ", name='" + name + '\'' +
31 | ", format='" + format + '\'' +
32 | ", authorization='" + authorization + '\'' +
33 | ", alg='" + alg + '\'' +
34 | '}'
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/kotlin-wot-binding-http/src/main/kotlin/http/routes/AbstractRoute.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.binding.http.routes
8 |
9 | import org.eclipse.thingweb.binding.http.routes.AbstractRoute
10 | import org.eclipse.thingweb.content.ContentManager
11 | import org.eclipse.thingweb.content.ContentManager.isSupportedMediaType
12 | import io.ktor.http.*
13 | import io.ktor.server.request.*
14 | import io.ktor.server.routing.*
15 | import org.slf4j.LoggerFactory
16 |
17 | /**
18 | * Abstract route for exposing Things. Inherited from all other routes.
19 | */
20 | abstract class AbstractRoute {
21 |
22 | fun getOrDefaultRequestContentType(request: RoutingRequest): ContentType {
23 | val contentType = request.contentType()
24 | // Check if the content type is of type `Any` and return the default
25 | return if (contentType == ContentType.Any) {
26 | ContentType.Application.Json
27 | } else {
28 | contentType
29 | }
30 | }
31 |
32 | fun unsupportedMediaTypeResponse(response: RoutingResponse, requestContentType: String?): String? {
33 | return if (!isSupportedMediaType(requestContentType)) {
34 | response.status(HttpStatusCode.UnsupportedMediaType)
35 | "Unsupported Media Type (supported: " + java.lang.String.join(
36 | ", ",
37 | ContentManager.offeredMediaTypes
38 | ) + ")"
39 | } else {
40 | null
41 | }
42 | }
43 |
44 | companion object {
45 | val log = LoggerFactory.getLogger(AbstractRoute::class.java)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/tracing/Tracing.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.tracing
8 |
9 | import io.opentelemetry.api.GlobalOpenTelemetry
10 | import io.opentelemetry.api.trace.Span
11 | import io.opentelemetry.api.trace.SpanBuilder
12 | import io.opentelemetry.api.trace.StatusCode
13 | import io.opentelemetry.api.trace.Tracer
14 | import io.opentelemetry.extension.kotlin.asContextElement
15 | import kotlinx.coroutines.withContext
16 | import kotlin.coroutines.CoroutineContext
17 | import kotlin.coroutines.EmptyCoroutineContext
18 |
19 |
20 | val tracer = GlobalOpenTelemetry.getTracer("kotlin-wot")
21 |
22 | suspend fun Tracer.startSpan(
23 | spanName: String,
24 | parameters: (SpanBuilder.() -> Unit)? = null,
25 | coroutineContext: CoroutineContext = EmptyCoroutineContext,
26 | block: suspend (span: Span) -> T
27 | ): T {
28 | val span: Span = this.spanBuilder(spanName).run {
29 | if (parameters != null) parameters()
30 | startSpan()
31 | }
32 |
33 | return withContext(coroutineContext + span.asContextElement()) {
34 | try {
35 | block(span)
36 | } catch (throwable: Throwable) {
37 | span.setStatus(StatusCode.ERROR)
38 | span.recordException(throwable)
39 | throw throwable
40 | } finally {
41 | span.end()
42 | }
43 | }
44 | }
45 |
46 | suspend fun withSpan(
47 | spanName: String,
48 | parameters: (SpanBuilder.() -> Unit)? = null,
49 | coroutineContext: CoroutineContext = EmptyCoroutineContext,
50 | block: suspend (span: Span) -> T
51 | ): T = tracer.startSpan(spanName, parameters, coroutineContext, block)
--------------------------------------------------------------------------------
/kotlin-wot-spring-boot-starter/src/test/kotlin/spring/SpringApplicationTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.spring
8 |
9 | import org.eclipse.thingweb.Servient
10 | import org.eclipse.thingweb.Wot
11 | import org.eclipse.thingweb.credentials.BearerCredentials
12 | import org.springframework.beans.factory.annotation.Autowired
13 | import org.springframework.boot.test.context.SpringBootTest
14 | import org.springframework.core.env.Environment
15 | import kotlin.test.Test
16 | import kotlin.test.assertContains
17 | import kotlin.test.assertEquals
18 | import kotlin.test.assertNotNull
19 |
20 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
21 | class SpringApplicationTest {
22 |
23 | @Autowired
24 | private lateinit var credentialsProperties: CredentialsProperties
25 |
26 | @Autowired
27 | private lateinit var httpServerProperties: HttpServerProperties
28 |
29 | @Autowired
30 | private lateinit var wot: Wot
31 |
32 | @Autowired
33 | private lateinit var servient: Servient
34 |
35 | @Autowired
36 | lateinit var env: Environment
37 |
38 | @Test
39 | fun `should load http server properties from application properties`() {
40 | assertEquals(false, httpServerProperties.enabled)
41 | assertEquals(9090, httpServerProperties.port)
42 | }
43 |
44 | @Test
45 | fun `should initiate wot object`() {
46 | assertNotNull(wot)
47 | }
48 |
49 | @Test
50 | fun `should initiate servient object`() {
51 | assertContains(servient.getClientSchemes(), "https")
52 | assertEquals(BearerCredentials("test"), servient.credentialStore["urn:dev:wot:org:eclipse:thingweb:security-example"])
53 | }
54 | }
--------------------------------------------------------------------------------
/kotlin-wot/src/main/kotlin/thing/action/ThingAction.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Robert Winkler
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | package org.eclipse.thingweb.thing.action
8 |
9 | import org.eclipse.thingweb.thing.schema.Type
10 | import org.eclipse.thingweb.thing.form.Form
11 | import org.eclipse.thingweb.thing.schema.ActionAffordance
12 | import org.eclipse.thingweb.thing.schema.DataSchema
13 | import com.fasterxml.jackson.annotation.JsonInclude
14 | import com.fasterxml.jackson.annotation.JsonInclude.Include.*
15 | import com.fasterxml.jackson.annotation.JsonProperty
16 |
17 | data class ThingAction(
18 | @JsonInclude(NON_EMPTY)
19 | override var title: String? = null,
20 |
21 | @JsonInclude(NON_EMPTY)
22 | override var description: String? = null,
23 |
24 | @JsonInclude(NON_EMPTY)
25 | override var descriptions: MutableMap? = null,
26 |
27 | @JsonInclude(NON_EMPTY)
28 | override var uriVariables: MutableMap>? = null,
29 |
30 | @JsonInclude(NON_EMPTY)
31 | override var forms: MutableList