Skip to content

Commit 24e44e2

Browse files
author
Robert Winkler
committed
DSL Updates
1 parent 7bc6500 commit 24e44e2

File tree

12 files changed

+195
-30
lines changed

12 files changed

+195
-30
lines changed

kotlin-wot-binding-http/src/main/kotlin/http/HttpProtocolServer.kt

Lines changed: 126 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package ai.ancf.lmos.wot.binding.http
22

33
import ai.ancf.lmos.wot.Servient
4+
import ai.ancf.lmos.wot.thing.ContentManager
45
import ai.ancf.lmos.wot.thing.ExposedThing
6+
import ai.ancf.lmos.wot.thing.action.ExposedThingAction
7+
import ai.ancf.lmos.wot.thing.form.Form
8+
import ai.ancf.lmos.wot.thing.form.Operation
9+
import ai.ancf.lmos.wot.thing.schema.InteractionAffordance
10+
import ai.anfc.lmos.wot.binding.Content
511
import ai.anfc.lmos.wot.binding.ProtocolServer
612
import ai.anfc.lmos.wot.binding.ProtocolServerException
713
import com.fasterxml.jackson.databind.DeserializationFeature
@@ -30,6 +36,7 @@ class HttpProtocolServer(
3036
val things: MutableMap<String, ExposedThing> = mutableMapOf()
3137
var started = false
3238
private var server: EmbeddedServer<*, *>? = null
39+
private val actualAddresses: List<String> = listOf()
3340

3441
companion object {
3542
private val log = LoggerFactory.getLogger(HttpProtocolServer::class.java)
@@ -56,7 +63,106 @@ class HttpProtocolServer(
5663
log.info("Exposing thing '{}'", thing.id)
5764
things[thing.id] = thing
5865

59-
// Add logic to expose the thing's properties, actions, and events via routes
66+
for (address in actualAddresses) {
67+
for (contentType in ContentManager.getOfferedMediaTypes()) {
68+
// make reporting of all properties optional?
69+
val href = (address + "/" + thing.id).toString() + "/all/properties"
70+
val form = Form(href = href, contentType = contentType, op = listOf( Operation.READ_ALL_PROPERTIES,
71+
Operation.READ_MULTIPLE_PROPERTIES))
72+
73+
thing.forms += form
74+
log.debug("Assign '{}' for reading all properties", href)
75+
76+
exposeProperties(thing, address, contentType)
77+
exposeActions(thing, address, contentType)
78+
exposeEvents(thing, address, contentType)
79+
}
80+
}
81+
}
82+
83+
fun exposeProperties(thing: ExposedThing, address: String, contentType: String) {
84+
val properties = thing.exposedProperties
85+
86+
properties.forEach { (name, property) ->
87+
88+
val href = getHrefWithVariablePattern(address, thing, "properties", name, property)
89+
90+
// Determine the operations based on readOnly/writeOnly status
91+
val operations = when {
92+
property.readOnly -> listOf(Operation.READ_PROPERTY)
93+
property.writeOnly -> listOf(Operation.WRITE_PROPERTY)
94+
else -> listOf(Operation.READ_PROPERTY, Operation.WRITE_PROPERTY)
95+
}
96+
97+
// Create the main form and add it to the property
98+
val form = Form(href = href, contentType = contentType, op = operations)
99+
property.forms?.plusAssign(form)
100+
log.debug("Assign '{}' to Property '{}'", href, name)
101+
102+
// If the property is observable, add an additional form with an observable href
103+
if (property.observable) {
104+
val observableHref = "$href/observable"
105+
val observableForm = Form(
106+
href = observableHref,
107+
contentType = contentType,
108+
op = listOf(Operation.OBSERVE_PROPERTY),
109+
subprotocol = "longpoll"
110+
)
111+
property.forms?.plusAssign(observableForm)
112+
log.debug("Assign '{}' to observe Property '{}'", observableHref, name)
113+
}
114+
}
115+
}
116+
117+
fun exposeActions(thing: ExposedThing, address: String, contentType: String) {
118+
val actions: Map<String, ExposedThingAction<*, *>> = thing.exposedActions
119+
actions.forEach { (name, action) ->
120+
val href: String = getHrefWithVariablePattern(address, thing, "actions", name, action)
121+
// Initialize the form using named parameters
122+
val form = Form(
123+
href = href,
124+
contentType = contentType,
125+
op = listOf(Operation.INVOKE_ACTION)
126+
)
127+
128+
// Add the form to the action
129+
action.forms?.plusAssign(form)
130+
log.debug("Assign '{}' to Action '{}'", href, name)
131+
}
132+
}
133+
134+
fun exposeEvents(thing: ExposedThing, address: String, contentType: String) {
135+
val events = thing.exposedEvents
136+
events.forEach { (name, event) ->
137+
val href = getHrefWithVariablePattern(address, thing, "events", name, event)
138+
139+
// Create the form using named parameters directly
140+
val form = Form(
141+
href = href,
142+
contentType = contentType,
143+
subprotocol = "longpoll",
144+
op = listOf(Operation.SUBSCRIBE_EVENT)
145+
)
146+
147+
// Add the form to the event
148+
event.forms?.plusAssign(form)
149+
log.debug("Assign '{}' to Event '{}'", href, name)
150+
}
151+
}
152+
153+
private fun getHrefWithVariablePattern(
154+
address: String,
155+
thing: ExposedThing,
156+
type: String,
157+
interactionName: String,
158+
interaction: InteractionAffordance
159+
): String {
160+
var variables = ""
161+
val uriVariables = interaction.uriVariables?.keys
162+
if (uriVariables != null) {
163+
variables = "{?" + java.lang.String.join(",", uriVariables) + "}"
164+
}
165+
return "$address/${thing.id}/$type/$interactionName$variables"
60166
}
61167

62168
// Destroy a thing
@@ -102,9 +208,9 @@ fun Application.setupRouting(servient: Servient) {
102208
val id = call.parameters["id"] ?: return@get call.response.status(HttpStatusCode.BadRequest)
103209
val propertyName = call.parameters["name"]
104210
val thing = servient.things[id] ?: return@get call.response.status(HttpStatusCode.NotFound)
105-
val property = thing.properties[propertyName]
211+
val property = thing.exposedProperties[propertyName]
106212
if (property != null) {
107-
call.respond("TODO Return property value", typeInfo<String>())
213+
// call.respond(property.read())
108214
} else {
109215
call.response.status(HttpStatusCode.NotFound)
110216
}
@@ -113,13 +219,15 @@ fun Application.setupRouting(servient: Servient) {
113219
val id = call.parameters["id"] ?: return@put call.response.status(HttpStatusCode.BadRequest)
114220
val propertyName = call.parameters["name"]
115221
val thing = servient.things[id] ?: return@put call.response.status(HttpStatusCode.NotFound)
116-
val property = thing.properties[propertyName]
222+
val property = thing.exposedProperties[propertyName]
117223
if (property != null) {
118224
if (!property.readOnly) {
119-
val newValue = call.receive<Any>()
120-
// TODO handle write logic
225+
val contentType = getOrDefaultRequestContentType(call.request)
226+
val content = Content(contentType.toString(), call.receiveText())
227+
val input = ContentManager.contentToValue(content, property)
121228

122-
call.response.status(HttpStatusCode.OK)
229+
//val newValue = property.write(input)
230+
//call.respond(newValue)
123231
} else {
124232
call.response.status(HttpStatusCode.BadRequest)
125233
}
@@ -132,7 +240,7 @@ fun Application.setupRouting(servient: Servient) {
132240
val id = call.parameters["id"] ?: return@post call.response.status(HttpStatusCode.BadRequest)
133241
val actionName = call.parameters["name"]
134242
val thing = servient.things[id] ?: return@post call.response.status(HttpStatusCode.NotFound)
135-
val action = thing.actions[actionName]
243+
val action = thing.exposedActions[actionName]
136244
if (action != null) {
137245
val input = call.receive<Any>()
138246
call.respond("TODO Return action response", typeInfo<String>())
@@ -154,4 +262,14 @@ private fun Application.setupJackson() {
154262
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
155263
}
156264
}
265+
}
266+
267+
private fun getOrDefaultRequestContentType(request: RoutingRequest): ContentType {
268+
val contentType = request.contentType()
269+
// Check if the content type is of type `Any` and return the default
270+
return if (contentType == ContentType.Any) {
271+
ContentType.Application.Json
272+
} else {
273+
contentType
274+
}
157275
}

kotlin-wot/src/main/kotlin/thing/ContentManager.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ interface ContentManager {
1313
fun valueToContent(parameters: Map<String, Any>, contentType: String): Content {
1414
TODO("Not yet implemented")
1515
}
16+
17+
fun getOfferedMediaTypes(): List<String> {
18+
TODO("Not yet implemented")
19+
}
1620
}
1721

1822
}

kotlin-wot/src/main/kotlin/thing/ExposedThing.kt

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ import java.util.*
4444
@Serializable
4545
data class ExposedThing(private val thing: Thing = Thing()) : ThingDescription by thing {
4646

47-
private val _properties : MutableMap<String, ExposedThingProperty<*>> = mutableMapOf()
48-
private val _actions : MutableMap<String, ExposedThingAction<Any, Any>> = mutableMapOf()
49-
private val _events : MutableMap<String, ExposedThingEvent<Any, Any, Any>> = mutableMapOf()
47+
val exposedProperties : MutableMap<String, ExposedThingProperty<*>> = mutableMapOf()
48+
val exposedActions : MutableMap<String, ExposedThingAction<*, *>> = mutableMapOf()
49+
val exposedEvents : MutableMap<String, ExposedThingEvent<*, *, *>> = mutableMapOf()
5050

5151
init {
5252
thing.properties.forEach { (name, property) ->
@@ -59,15 +59,15 @@ data class ExposedThing(private val thing: Thing = Thing()) : ThingDescription b
5959
is ArrayProperty<*> -> ExposedArrayProperty(thing = thing, property = property)
6060
is ObjectProperty -> ExposedObjectProperty(thing = thing, property = property)
6161
}
62-
_properties[name] = exposedProperty
62+
exposedProperties[name] = exposedProperty
6363
}
6464

6565
thing.actions.forEach { (name, action) ->
66-
_actions[name] = ExposedThingAction(action, thing)
66+
exposedActions[name] = ExposedThingAction(action, thing)
6767
}
6868

6969
thing.events.forEach { (name, event) ->
70-
_events[name] = ExposedThingEvent(event)
70+
exposedEvents[name] = ExposedThingEvent(event)
7171
}
7272
}
7373

@@ -78,7 +78,7 @@ data class ExposedThing(private val thing: Thing = Thing()) : ThingDescription b
7878
*/
7979
suspend fun readProperties(): Map<String, Any> {
8080
val values: MutableMap<String, Any> = HashMap()
81-
_properties.forEach { (name, property) ->
81+
exposedProperties.forEach { (name, property) ->
8282
values[name] = property.read() as Any
8383
}
8484
return values
@@ -94,7 +94,7 @@ data class ExposedThing(private val thing: Thing = Thing()) : ThingDescription b
9494
suspend fun writeProperties(values: Map<String, Any>): Map<String, Any> {
9595
val returnValues: MutableMap<String, Any> = HashMap()
9696
values.forEach { (name, value) ->
97-
val property = _properties[name]
97+
val property = exposedProperties[name]
9898
if (property != null) {
9999
// Check if the property's type matches the value's type
100100
when (property) {
@@ -222,8 +222,8 @@ data class Thing (
222222
@JsonInclude(NON_EMPTY) override var description: String? = null,
223223
@JsonInclude(NON_EMPTY) override var descriptions: MutableMap<String, String>? = null,
224224
@JsonInclude(NON_EMPTY) override var properties: MutableMap<String, ThingProperty<*>> = mutableMapOf(),
225-
@JsonInclude(NON_EMPTY) override var actions: MutableMap<String, ThingAction<Any, Any>> = mutableMapOf(),
226-
@JsonInclude(NON_EMPTY) override var events: MutableMap<String, ThingEvent<Any, Any, Any>> = mutableMapOf(),
225+
@JsonInclude(NON_EMPTY) override var actions: MutableMap<String, ThingAction<*, *>> = mutableMapOf(),
226+
@JsonInclude(NON_EMPTY) override var events: MutableMap<String, ThingEvent<*, *, *>> = mutableMapOf(),
227227
@JsonInclude(NON_EMPTY) override var forms: List<Form> = emptyList(),
228228
@JsonFormat(with = [JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY]) @JsonInclude(NON_EMPTY) override var security: List<String> = emptyList(),
229229
@JsonInclude(NON_EMPTY) override var securityDefinitions: MutableMap<String, SecurityScheme> = mutableMapOf(),
@@ -238,6 +238,17 @@ data class Thing (
238238
@JsonInclude(NON_EMPTY) override var uriVariables: MutableMap<String, DataSchema<@Contextual Any>>? = null
239239
) : ThingDescription {
240240

241+
inline fun <reified T> property(name: String, configure: ThingProperty<T>.() -> Unit) {
242+
val property: ThingProperty<T> = when (T::class) {
243+
String::class -> StringProperty() as ThingProperty<T>
244+
Int::class -> IntProperty() as ThingProperty<T>
245+
Boolean::class -> BooleanProperty() as ThingProperty<T>
246+
else -> throw IllegalArgumentException("Unsupported property type")
247+
}
248+
property.configure()
249+
properties[name] = property
250+
}
251+
241252
fun Thing.stringProperty(name: String, configure: StringProperty.() -> Unit) {
242253
this.properties[name] = StringProperty().apply(configure)
243254
}
@@ -247,15 +258,15 @@ data class Thing (
247258
fun Thing.booleanProperty(name: String, configure: BooleanProperty.() -> Unit) {
248259
this.properties[name] = BooleanProperty().apply(configure)
249260
}
250-
251-
fun Thing.action(name: String, configure: ThingAction<Any, Any>.() -> Unit) {
252-
this.actions[name] = ThingAction<Any, Any>().apply(configure)
261+
fun <I : Any, O : Any> Thing.action(name: String, configure: ThingAction<I, O>.() -> Unit) {
262+
val action = ThingAction<I, O>().apply(configure)
263+
actions[name] = action
253264
}
254265

255-
fun Thing.event(name: String, configure: ThingEvent<Any, Any, Any>.() -> Unit) {
256-
this.events[name] = ThingEvent<Any, Any, Any>().apply(configure)
266+
fun <T, S, C> Thing.event(name: String, configure: ThingEvent<T, S, C>.() -> Unit) {
267+
val event = ThingEvent<T, S, C>().apply(configure)
268+
events[name] = event
257269
}
258-
259270
/*
260271
261272
fun getPropertiesByObjectType(objectType: String): Map<String, ThingProperty<Any>> {

kotlin-wot/src/main/kotlin/thing/schema/ArraySchema.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,4 @@ open class ArraySchema<T>(
9191
result = 31 * result + (maxItems ?: 0)
9292
return result
9393
}
94-
}
94+
}

kotlin-wot/src/main/kotlin/thing/schema/BooleanSchema.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,8 @@ open class BooleanSchema(
7979
result = 31 * result + (format?.hashCode() ?: 0)
8080
return result
8181
}
82+
}
83+
84+
fun booleanSchema(initializer: BooleanSchema.() -> Unit): BooleanSchema {
85+
return BooleanSchema().apply(initializer)
8286
}

kotlin-wot/src/main/kotlin/thing/schema/IntegerSchema.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,8 @@ open class IntegerSchema(
105105
result = 31 * result + (multipleOf ?: 0)
106106
return result
107107
}
108+
}
109+
110+
fun integerSchema(initializer: IntegerSchema.() -> Unit): IntegerSchema {
111+
return IntegerSchema().apply(initializer)
108112
}

kotlin-wot/src/main/kotlin/thing/schema/NumberSchema.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,8 @@ open class NumberSchema(
9999
result = 31 * result + (multipleOf ?: 0)
100100
return result
101101
}
102+
}
103+
104+
fun numberSchema(initializer: NumberSchema.() -> Unit): NumberSchema {
105+
return NumberSchema().apply(initializer)
102106
}

kotlin-wot/src/main/kotlin/thing/schema/ObjectSchema.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,8 @@ open class ObjectSchema(
8585
result = 31 * result + (format?.hashCode() ?: 0)
8686
return result
8787
}
88+
}
89+
90+
fun objectSchema(initializer: ObjectSchema.() -> Unit): ObjectSchema {
91+
return ObjectSchema().apply(initializer)
8892
}

kotlin-wot/src/main/kotlin/thing/schema/StringSchema.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,7 @@ open class StringSchema(
105105
return result
106106
}
107107
}
108+
109+
fun stringSchema(initializer: StringSchema.() -> Unit): StringSchema {
110+
return StringSchema().apply(initializer)
111+
}

kotlin-wot/src/main/kotlin/thing/schema/ThingDescription.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,15 @@ interface ThingDescription : BaseSchema {
8585
* @return a map of action affordances.
8686
*/
8787
@get:JsonInclude(JsonInclude.Include.NON_EMPTY)
88-
var actions: MutableMap<String, ThingAction<Any, Any>> // Optional: Map of ActionAffordance
88+
var actions: MutableMap<String, ThingAction<*, *>> // Optional: Map of ActionAffordance
8989

9090
/**
9191
* All Event-based Interaction Affordances of the Thing.
9292
*
9393
* @return a map of event affordances.
9494
*/
9595
@get:JsonInclude(JsonInclude.Include.NON_EMPTY)
96-
var events: MutableMap<String, ThingEvent<Any, Any, Any>> // Optional: Map of EventAffordance
96+
var events: MutableMap<String, ThingEvent<*, *, *>> // Optional: Map of EventAffordance
9797

9898
/**
9999
* Provides Web links to arbitrary resources that relate to the specified Thing Description.

0 commit comments

Comments
 (0)