11package ai.ancf.lmos.wot.binding.http
22
33import ai.ancf.lmos.wot.Servient
4+ import ai.ancf.lmos.wot.thing.ContentManager
45import 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
511import ai.anfc.lmos.wot.binding.ProtocolServer
612import ai.anfc.lmos.wot.binding.ProtocolServerException
713import 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}
0 commit comments