Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,39 @@ import org.http4k.contract.bindContract
import org.http4k.contract.contract
import org.http4k.core.*
import org.http4k.server.Http4kServer
import org.http4k.server.ServerConfig
import org.http4k.server.Undertow
import org.http4k.server.asServer
import org.slf4j.LoggerFactory
import java.util.*
import kotlin.reflect.KClass

class OcppSoapClientTransport(
private val clientSettings: SoapClientSettings,
class OcppSoapClientTransport private constructor(
private val ocppId: String,
target: String,
private val ocppSoapParser: OcppSoapParser,
private val headers: RequestHeaders = listOf(
"Content-Type" to "application/soap+xml;charset=utf-8;"
),
private val newMessageId: () -> String = { UUID.randomUUID().toString() }
private val headers: RequestHeaders,
private val newMessageId: () -> String,
private val path: String,
config: ServerConfig,
) : ClientTransport {

companion object {
private val logger = LoggerFactory.getLogger(OcppSoapClientTransport::class.java)

fun createClient(
clientSettings: SoapClientSettings,
ocppId: String,
target: String,
ocppSoapParser: OcppSoapParser,
headers: RequestHeaders = listOf(
"Content-Type" to "application/soap+xml;charset=utf-8;"
),
newMessageId: () -> String = { UUID.randomUUID().toString() }
) = OcppSoapClientTransport(ocppId, target, ocppSoapParser, headers, newMessageId, clientSettings.path, Undertow(port = clientSettings.port))
}

private val server: Http4kServer?
private val server: Http4kServer
private val client = JavaHttpClient()

private val handlers = mutableListOf<(HttpMessage) -> HttpMessage?>()
Expand All @@ -40,7 +51,7 @@ class OcppSoapClientTransport(
val app = contract {
routes += route
}
server = app.asServer(Undertow(port = clientSettings.port))
server = app.asServer(config)
}

private val targetRoute = target.removeSuffix("/") + "/"
Expand All @@ -61,12 +72,12 @@ class OcppSoapClientTransport(
ocppSoapParser.readToEnvelop(payload).header.action?.value?.removePrefix("/") ?: ""

override fun connect() {
server?.start()
?.also { logger.info("starting http server on port ${clientSettings.port}") }
server.start()
logger.info("starting http server on port ${server.port()}")
}

override fun close() {
server?.close()
server.close()
}

override fun <T, P : Any> sendMessageClass(clazz: KClass<P>, action: String, message: T): P {
Expand All @@ -78,7 +89,7 @@ class OcppSoapClientTransport(
messageId = newMessageId(),
chargingStationId = ocppId,
action = action,
from = clientSettings.path + ":" + clientSettings.port,
from = path + ":" + server.port(),
to = targetRoute,
payload = message
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,53 +16,46 @@ import org.http4k.core.Response
import org.http4k.core.Status.Companion.NOT_FOUND
import org.http4k.core.Status.Companion.OK
import org.http4k.lens.Path
import org.http4k.server.Http4kServer
import org.http4k.server.Undertow
import org.http4k.server.asServer
import org.http4k.routing.RoutingHttpHandler
import org.slf4j.LoggerFactory
import java.util.*
import kotlin.reflect.KClass

class OcppSoapServerTransport private constructor(
private val ocppVersion: OcppVersion,
private val port: Int,
private val path: String?,
private val ocppSoapParser: OcppSoapParser,
private val newMessageId: () -> String = { UUID.randomUUID().toString() },
private var server: Http4kServer? = null
private val path: String,
) : ServerTransport {

companion object {
private val logger = LoggerFactory.getLogger(OcppSoapServerTransport::class.java)

fun createServer(
fun create(
ocppVersion: OcppVersion,
port: Int,
path: String,
ocppSoapParser: OcppSoapParser,
newMessageId: () -> String = { UUID.randomUUID().toString() }
) = OcppSoapServerTransport(ocppVersion, port, path, ocppSoapParser, newMessageId)

fun createServer(
ocppVersion: OcppVersion,
ocppSoapParser: OcppSoapParser,
newMessageId: () -> String = { UUID.randomUUID().toString() },
server: Http4kServer
) = OcppSoapServerTransport(ocppVersion, server.port(), null, ocppSoapParser, newMessageId, server)
) = OcppSoapServerTransport(ocppVersion, ocppSoapParser, newMessageId, path = path)
}

private val handler: RoutingHttpHandler
private val handlers = mutableListOf<OcppHttpServerHandler>()

override fun start() {
if (server == null) {
val route = path!! / Path.of("action") / Path.of("ocppId") bindContract POST to ::routeHandler
val app = contract {
routes += route
}
server = app.asServer(Undertow(port = port))
init {
this.handler = createHandler(path)
}

val serverConfig = object : ServerConfig {
override val handler: RoutingHttpHandler
get() = this@OcppSoapServerTransport.handler
}

private fun createHandler(path: String): RoutingHttpHandler {
val route = path / Path.of("action") / Path.of("ocppId") bindContract POST to ::routeHandler
return contract {
routes += route
}
server!!.start()
logger.info("starting http server on port $port")
}

private fun routeHandler(action: String, ocppId: String): HttpHandler = { request: Request ->
Expand All @@ -81,10 +74,6 @@ class OcppSoapServerTransport private constructor(
?: Response(NOT_FOUND).also { logger.warn("no action handler found for $message") }
}

override fun stop() {
server!!.stop()
}

override fun <T, P : Any> sendMessageClass(clazz: KClass<P>, csOcppId: String, action: String, message: T): P {
TODO("Not yet implemented")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
package com.izivia.ocpp.http

import org.http4k.routing.RoutingHttpHandler
import org.http4k.server.Http4kServer
import org.http4k.server.asServer

data class SoapClientSettings(
val path: String,
val port: Int,
)

interface ServerConfig {
val handler: RoutingHttpHandler
}

fun OcppSoapServerTransport.asServer(port: Int = 8000): Http4kServer {
return this.serverConfig.handler.asServer(org.http4k.server.Undertow(port))
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class OcppSoapClientTransportTest {
""".trimIndent()
).start()

val client = OcppSoapClientTransport(
val client = OcppSoapClientTransport.createClient(
SoapClientSettings(
path = "/ocpp/soap",
port = 5001
Expand All @@ -69,7 +69,7 @@ class OcppSoapClientTransportTest {

@Test
fun `should throw an OcppCallErrorException because the target is unreachable`() {
val client = OcppSoapClientTransport(
val client = OcppSoapClientTransport.createClient(
SoapClientSettings(
path = "/ocpp/soap",
port = 5001
Expand All @@ -91,7 +91,7 @@ class OcppSoapClientTransportTest {
fun `should receive an OCPP 1-6 HeartBeat request`() {
var received = false
val time = Instant.parse("2022-05-17T15:42:00.503Z")
val client = OcppSoapClientTransport(
val client = OcppSoapClientTransport.createClient(
SoapClientSettings(
path = "",
port = 5001
Expand Down Expand Up @@ -148,7 +148,7 @@ class OcppSoapClientTransportTest {
@Test
fun `should not receive the OCPP 1-6 HeartBeat request because there is no handler`() {
val received = false
val client = OcppSoapClientTransport(
val client = OcppSoapClientTransport.createClient(
SoapClientSettings(
path = "/ocpp/soap",
port = 5001
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.izivia.ocpp.http.test
import com.izivia.ocpp.core16.model.heartbeat.HeartbeatReq
import com.izivia.ocpp.core16.model.heartbeat.HeartbeatResp
import com.izivia.ocpp.http.OcppSoapServerTransport
import com.izivia.ocpp.http.asServer
import com.izivia.ocpp.operation.information.ChargingStationConfig
import com.izivia.ocpp.operation.information.RequestMetadata
import com.izivia.ocpp.soap16.Ocpp16SoapParser
Expand All @@ -23,16 +24,16 @@ class OcppSoapServerTransportTest {

@Test
fun `should receive the OCPP 1-6 HeartBeat message`() {
val server = OcppSoapServerTransport.createServer(
port = 5002,
val transport = OcppSoapServerTransport.create(
path = "/ocpp/soap/",
ocppSoapParser = Ocpp16SoapParser(),
ocppVersion = OcppVersion.OCPP_1_6
)
val server = transport.asServer(5002)
server.start()
var received = false

server.receiveMessage("Heartbeat", OcppVersion.OCPP_1_6, { meta: RequestMetadata, req: HeartbeatReq ->
transport.receiveMessage("Heartbeat", OcppVersion.OCPP_1_6, { meta: RequestMetadata, req: HeartbeatReq ->
received = true
HeartbeatResp(currentTime = Instant.parse("2022-05-17T15:42:00.503Z"))
}, { ChargingStationConfig(true, null) })
Expand Down Expand Up @@ -77,16 +78,16 @@ class OcppSoapServerTransportTest {

@Test
fun `should not receive the OCPP 1-5 HeartBeat message because server is for 1-6`() {
val server = OcppSoapServerTransport.createServer(
port = 5002,
val transport = OcppSoapServerTransport.create(
path = "/ocpp/soap/",
ocppSoapParser = Ocpp16SoapParser(),
ocppVersion = OcppVersion.OCPP_1_6
)
val server = transport.asServer(5002)
server.start()
var received = false

server.receiveMessage("Heartbeat", OcppVersion.OCPP_1_5, { meta: RequestMetadata, req: HeartbeatReq ->
transport.receiveMessage("Heartbeat", OcppVersion.OCPP_1_5, { meta: RequestMetadata, req: HeartbeatReq ->
received = true
HeartbeatResp(currentTime = Instant.parse("2022-05-17T15:42:00.503Z"))
}, { ChargingStationConfig(true, null) })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ private val logger = KotlinLogging.logger {}
class WebsocketClient(ocppId: String,
ocppVersion: OcppVersion,
target: String,
headers: RequestHeaders = emptyList()
headers: RequestHeaders = emptyList(),
val newMessageId: () -> String = { UUID.randomUUID().toString() },
) : ClientTransport {
private val client: OcppWampClient =
OcppWampClient.newClient(Uri.of(target), ocppId, ocppVersion, headers = headers)
Expand All @@ -35,7 +36,7 @@ class WebsocketClient(ocppId: String,
@Throws(IllegalStateException::class, ConnectException::class)
override fun <T, P : Any> sendMessageClass(clazz: KClass<P>, action: String, message: T): P =
try {
val msgId: String = UUID.randomUUID().toString()
val msgId: String = newMessageId()
val response = client.sendBlocking(WampMessage.Call(msgId, action, parser.mapPayloadToString(message)))
if (response.msgId != msgId) {
throw IllegalStateException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,15 @@ import com.izivia.ocpp.OcppVersion as OcppVersionWamp
private val logger = KotlinLogging.logger {}

class WebsocketServer(
port: Int,
ocppVersions: Set<OcppVersion>,
path: String,
val newMessageId: () -> String = { UUID.randomUUID().toString() },
listeners: EventsListeners = EventsListeners(),
) : ServerTransport {

private val server: OcppWampServer =
OcppWampServer.newServer(port = port, ocppVersions = ocppVersions.map { OcppVersionWamp.valueOf(it.name) }.toSet(), path = path, listeners = listeners)

override fun start(): Unit = server.start()

override fun stop(): Unit = server.stop()
OcppWampServer.newServer(ocppVersions = ocppVersions.map { OcppVersionWamp.valueOf(it.name) }.toSet(), path = path, listeners = listeners)
val serverConfig = server.config()

override fun <T, P : Any> sendMessageClass(clazz: KClass<P>, csOcppId: String, action: String, message: T): P =
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import com.izivia.ocpp.wamp.messages.WampMessage
import com.izivia.ocpp.wamp.messages.WampMessageMeta
import com.izivia.ocpp.wamp.server.OcppWampServer
import com.izivia.ocpp.wamp.server.OcppWampServerHandler
import com.izivia.ocpp.wamp.server.asServer
import com.izivia.ocpp.websocket.WebsocketClient
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.unmockkAll
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
Expand All @@ -29,7 +29,6 @@ import strikt.assertions.isA
import strikt.assertions.isEqualTo
import strikt.assertions.isFailure
import java.net.ServerSocket
import java.util.*

class WebsocketTest {

Expand All @@ -44,22 +43,19 @@ class WebsocketTest {
@Test
fun `sendMessageClass success`() {
val id = "a727d144-82bb-497a-a0c7-4ef2295910d4"
val uuid = UUID.fromString(id)
mockkStatic(UUID::class)
every { UUID.randomUUID() } returns uuid

val ocppWampClient = mockk<OkHttpOcppWampClient>()
every { ocppWampClient.connect() } returns Unit
every { ocppWampClient.close() } returns Unit
every { ocppWampClient.onAction(any()) } returns Unit
every { ocppWampClient.sendBlocking(any()) } returns WampMessage.CallResult(
msgId = "a727d144-82bb-497a-a0c7-4ef2295910d4",
msgId = id,
payload = "{\"currentTime\":\"2022-02-15T00:00:00.000Z\"}"
)
mockkObject(OcppWampClient.Companion)
every { OcppWampClient.Companion.newClient(any(), any(), any(), any()) } returns ocppWampClient

val websocketClient = WebsocketClient("chargePoint2", OcppVersion.OCPP_1_6, "")
val websocketClient = WebsocketClient("chargePoint2", OcppVersion.OCPP_1_6, "", newMessageId = { id })
val heartbeatResponse =
websocketClient.sendMessageClass(HeartbeatResp::class, "heartbeat", HeartbeatReq())
expectThat(heartbeatResponse)
Expand All @@ -69,9 +65,6 @@ class WebsocketTest {
@Test
fun `wrong msgId`() {
val id = "00000000-0000-0000-0000-000000000000"
val uuid = UUID.fromString(id)
mockkStatic(UUID::class)
every { UUID.randomUUID() } returns uuid

val ocppWampClient = mockk<OkHttpOcppWampClient>()
every { ocppWampClient.connect() } returns Unit
Expand All @@ -84,7 +77,7 @@ class WebsocketTest {
mockkObject(OcppWampClient.Companion)
every { OcppWampClient.Companion.newClient(any(), any(), any(), any()) } returns ocppWampClient

val websocketClient = WebsocketClient("chargePoint2", OcppVersion.OCPP_1_6, "")
val websocketClient = WebsocketClient("chargePoint2", OcppVersion.OCPP_1_6, "", newMessageId = { id })
expectCatching { websocketClient.sendMessageClass(HeartbeatResp::class, "heartbeat", HeartbeatReq()) }
.isFailure()
.isA<IllegalStateException>()
Expand All @@ -94,8 +87,9 @@ class WebsocketTest {
fun `receiveMessageClass success`() {
val port = getFreePort()

val server = OcppWampServer.newServer(port, setOf(OcppVersion.OCPP_1_6, OcppVersion.OCPP_2_0))
server.register(object : OcppWampServerHandler {
val transport = OcppWampServer.newServer(setOf(OcppVersion.OCPP_1_6, OcppVersion.OCPP_2_0))
val server = transport.asServer(port)
transport.register(object : OcppWampServerHandler {
override fun accept(ocppId: String): Boolean = "chargePoint2" == ocppId

override fun onAction(meta: WampMessageMeta, msg: WampMessage): WampMessage? = null
Expand All @@ -115,12 +109,12 @@ class WebsocketTest {
websocketClient.connect()
Thread.sleep(100) // wait for connection to be fully established, it seems to cause issues on GH action

server.sendBlocking(
transport.sendBlocking(
"chargePoint2",
WampMessage.Call("1", "authorize", "{\"idToken\": {\"idToken\": \"Tag1\", \"type\": \"Central\"}}")
)

server.sendBlocking("chargePoint2", WampMessage.Call("2", "heartbeat", "{}"))
transport.sendBlocking("chargePoint2", WampMessage.Call("2", "heartbeat", "{}"))

websocketClient.close()
} finally {
Expand Down
Loading
Loading