diff --git a/kotlin-wot-integration-tests/src/main/kotlin/integration/ChatAgent.kt b/kotlin-wot-integration-tests/src/main/kotlin/integration/ChatAgent.kt index 4cb09b1..e33e541 100644 --- a/kotlin-wot-integration-tests/src/main/kotlin/integration/ChatAgent.kt +++ b/kotlin-wot-integration-tests/src/main/kotlin/integration/ChatAgent.kt @@ -25,6 +25,7 @@ import org.springframework.stereotype.Component @Thing(id="chatagent", title="Chat Agent", description="A chat agent.", type= LMOSThingType.AGENT) @Context(prefix = LMOSContext.prefix, url = LMOSContext.url) +@Link(href = "lmos/capabilities", rel = "service-meta", type = "application/json") @VersionInfo(instance = "1.0.0") @Component class ChatAgent(agentProvider: AgentProvider, @Property(readOnly = true) diff --git a/kotlin-wot-reflection/src/main/kotlin/reflection/ExposedThingBuilder.kt b/kotlin-wot-reflection/src/main/kotlin/reflection/ExposedThingBuilder.kt index 9708e1f..e786cc6 100644 --- a/kotlin-wot-reflection/src/main/kotlin/reflection/ExposedThingBuilder.kt +++ b/kotlin-wot-reflection/src/main/kotlin/reflection/ExposedThingBuilder.kt @@ -4,6 +4,7 @@ import ai.ancf.lmos.wot.JsonMapper import ai.ancf.lmos.wot.Wot import ai.ancf.lmos.wot.reflection.annotations.* import ai.ancf.lmos.wot.reflection.annotations.Context +import ai.ancf.lmos.wot.reflection.annotations.Link import ai.ancf.lmos.wot.reflection.annotations.VersionInfo import ai.ancf.lmos.wot.thing.DEFAULT_CONTEXT import ai.ancf.lmos.wot.thing.ExposedThing @@ -77,6 +78,26 @@ object ExposedThingBuilder { if(versionInfoAnnotation != null){ version = ai.ancf.lmos.wot.thing.schema.VersionInfo(versionInfoAnnotation.instance, versionInfoAnnotation.model) } + clazz.findAnnotation()?.let { linkAnnotation -> + links.add(ai.ancf.lmos.wot.thing.schema.Link( + href = linkAnnotation.href, + type = linkAnnotation.type, + rel = linkAnnotation.rel, + anchor = linkAnnotation.anchor, + sizes = linkAnnotation.sizes, + hreflang = linkAnnotation.hreflang.toList() + )) + } + clazz.findAnnotation()?.values?.forEach { linkAnnotation -> + links.add(ai.ancf.lmos.wot.thing.schema.Link( + href = linkAnnotation.href, + type = linkAnnotation.type, + rel = linkAnnotation.rel, + anchor = linkAnnotation.anchor, + sizes = linkAnnotation.sizes, + hreflang = linkAnnotation.hreflang.toList() + )) + } // 3. Inspect the properties of the class and find @Property annotations clazz.memberProperties.forEach { property -> val propertyAnnotation = property.findAnnotation() diff --git a/kotlin-wot-reflection/src/main/kotlin/reflection/annotations/Annotations.kt b/kotlin-wot-reflection/src/main/kotlin/reflection/annotations/Annotations.kt index 57f0838..7c96410 100644 --- a/kotlin-wot-reflection/src/main/kotlin/reflection/annotations/Annotations.kt +++ b/kotlin-wot-reflection/src/main/kotlin/reflection/annotations/Annotations.kt @@ -15,6 +15,21 @@ annotation class Context(val prefix: String, val url : String) @Retention(AnnotationRetention.RUNTIME) annotation class VersionInfo(val instance : String, val model : String = "") +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class Link( + val href: String, + val type: String = "", + val rel: String = "", + val anchor: String = "", + val sizes: String = "", + val hreflang: Array = [] +) + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class Links(val values: Array) + @Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE_PARAMETER) @Retention(AnnotationRetention.RUNTIME) annotation class Property(val title: String = "", val description: String = "", val readOnly: Boolean = false, val writeOnly: Boolean = false) diff --git a/kotlin-wot-reflection/src/test/kotlin/reflection/ComplexThingTest.kt b/kotlin-wot-reflection/src/test/kotlin/reflection/ComplexThingTest.kt index 3b3ce61..99a585a 100644 --- a/kotlin-wot-reflection/src/test/kotlin/reflection/ComplexThingTest.kt +++ b/kotlin-wot-reflection/src/test/kotlin/reflection/ComplexThingTest.kt @@ -35,6 +35,7 @@ class ComplexThingTest { assertEquals("Complex Thing", thingDescription.title, "ThingDescription title should match") assertEquals("A thing with complex properties, actions, and events.", thingDescription.description, "ThingDescription description should match") assertEquals("1.0.0", thingDescription.version?.instance) + assertEquals(listOf(Link("my/link", "my/type", "my-rel", "my-anchor", "my-sizes", listOf("my-lang-1", "my-lang-2"))), thingDescription.links) } @Test diff --git a/kotlin-wot-reflection/src/test/kotlin/reflection/SimpleThingTest.kt b/kotlin-wot-reflection/src/test/kotlin/reflection/SimpleThingTest.kt index cd63503..e330b10 100644 --- a/kotlin-wot-reflection/src/test/kotlin/reflection/SimpleThingTest.kt +++ b/kotlin-wot-reflection/src/test/kotlin/reflection/SimpleThingTest.kt @@ -8,7 +8,9 @@ import ai.ancf.lmos.wot.content.toJsonContent import ai.ancf.lmos.wot.reflection.things.SimpleThing import ai.ancf.lmos.wot.thing.ExposedThing import ai.ancf.lmos.wot.thing.schema.ContentListener +import ai.ancf.lmos.wot.thing.schema.Link import ai.ancf.lmos.wot.thing.schema.StringSchema +import ai.ancf.lmos.wot.thing.schema.WoTThingDescription import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import java.util.concurrent.CountDownLatch @@ -21,6 +23,7 @@ class SimpleThingTest { lateinit var wot: Wot lateinit var simpleThing: SimpleThing lateinit var exposedThing: ExposedThing + lateinit var thingDescription: WoTThingDescription @BeforeTest fun setUp() = runTest { @@ -35,11 +38,22 @@ class SimpleThingTest { // Generate ThingDescription from the class exposedThing = ExposedThingBuilder.createExposedThing(wot, simpleThing, SimpleThing::class) as ExposedThing + thingDescription = exposedThing.getThingDescription() servient.addThing(exposedThing) servient.expose("simpleThing") } + @Test + fun `test ThingDescription creation for SimpleThing`() { + // Validate Thing metadata + assertEquals("simpleThing", thingDescription.id, "ThingDescription ID should match the class ID") + assertEquals("Simple Thing", thingDescription.title, "ThingDescription title should match") + assertEquals("A thing with complex properties, actions, and events.", thingDescription.description, "ThingDescription description should match") + assertEquals("1.0.0", thingDescription.version?.instance) + assertEquals(listOf(Link("my/link", "my/type", "my-rel", "my-anchor", "my-sizes", listOf("my-lang-1", "my-lang-2"))), thingDescription.links) + } + @Test fun `Read mutable property`() = runTest { val content = exposedThing.handleReadProperty("mutableProperty") diff --git a/kotlin-wot-reflection/src/test/kotlin/reflection/things/ComplexThing.kt b/kotlin-wot-reflection/src/test/kotlin/reflection/things/ComplexThing.kt index 39cbbb5..2116772 100644 --- a/kotlin-wot-reflection/src/test/kotlin/reflection/things/ComplexThing.kt +++ b/kotlin-wot-reflection/src/test/kotlin/reflection/things/ComplexThing.kt @@ -13,6 +13,16 @@ import kotlin.random.Random title = "Complex Thing", description = "A thing with complex properties, actions, and events." ) +@Links( + values = [Link( + href = "my/link", + rel = "my-rel", + type = "my/type", + anchor = "my-anchor", + sizes = "my-sizes", + hreflang = ["my-lang-1", "my-lang-2"] + )] +) @VersionInfo(instance = "1.0.0") class ComplexThing(@Property(readOnly = true) val constructorProperty: String = "Hello World") { diff --git a/kotlin-wot-reflection/src/test/kotlin/reflection/things/SimpleThing.kt b/kotlin-wot-reflection/src/test/kotlin/reflection/things/SimpleThing.kt index 6c8812e..0ea02b3 100644 --- a/kotlin-wot-reflection/src/test/kotlin/reflection/things/SimpleThing.kt +++ b/kotlin-wot-reflection/src/test/kotlin/reflection/things/SimpleThing.kt @@ -10,6 +10,14 @@ import kotlinx.coroutines.flow.flow title = "Simple Thing", description = "A thing with complex properties, actions, and events." ) +@Link( + href = "my/link", + rel = "my-rel", + type = "my/type", + anchor = "my-anchor", + sizes = "my-sizes", + hreflang = ["my-lang-1", "my-lang-2"] +) @VersionInfo(instance = "1.0.0") class SimpleThing { diff --git a/kotlin-wot/src/main/kotlin/thing/ThingDescription.kt b/kotlin-wot/src/main/kotlin/thing/ThingDescription.kt index cf678ee..638d4b8 100644 --- a/kotlin-wot/src/main/kotlin/thing/ThingDescription.kt +++ b/kotlin-wot/src/main/kotlin/thing/ThingDescription.kt @@ -74,7 +74,7 @@ data class ThingDescription @JsonCreator constructor( @JsonInclude(NON_EMPTY) override var created: String? = null, @JsonInclude(NON_EMPTY) override var modified: String? = null, @JsonInclude(NON_EMPTY) override var support: String? = null, - @JsonInclude(NON_EMPTY) override var links: List? = null, + @JsonInclude(NON_EMPTY) override var links: MutableList = mutableListOf(), @JsonFormat(with = [JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY]) @JsonInclude(NON_EMPTY) override var profile: List? = null, @JsonInclude(NON_EMPTY) override var schemaDefinitions: MutableMap>? = null, @JsonInclude(NON_EMPTY) override var uriVariables: MutableMap>? = null diff --git a/kotlin-wot/src/main/kotlin/thing/schema/WoTThingDescription.kt b/kotlin-wot/src/main/kotlin/thing/schema/WoTThingDescription.kt index 6b01146..5a0983b 100644 --- a/kotlin-wot/src/main/kotlin/thing/schema/WoTThingDescription.kt +++ b/kotlin-wot/src/main/kotlin/thing/schema/WoTThingDescription.kt @@ -103,7 +103,7 @@ interface WoTThingDescription : BaseSchema { * @return an array of links. */ @get:JsonInclude(JsonInclude.Include.NON_EMPTY) - var links: List? // Optional: Array of Link + var links: MutableList // Optional: Array of Link /** * Set of form hypermedia controls that describe how an operation can be performed.