-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #85 from andacata/feature/charging_profiles_module
feat(chargingprofiles): Implementation of the ChargingProfiles module
- Loading branch information
Showing
25 changed files
with
861 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,4 @@ fun generateUUIDv4Token(): String { | |
} | ||
|
||
typealias CiString = String | ||
typealias URL = String |
129 changes: 129 additions & 0 deletions
129
...main/kotlin/com/izivia/ocpi/toolkit/modules/chargingProfiles/ChargingProfilesCpoClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package com.izivia.ocpi.toolkit.modules.chargingProfiles | ||
|
||
import com.izivia.ocpi.toolkit.common.* | ||
import com.izivia.ocpi.toolkit.modules.chargingProfiles.domain.ActiveChargingProfile | ||
import com.izivia.ocpi.toolkit.modules.chargingProfiles.domain.ActiveChargingProfileResult | ||
import com.izivia.ocpi.toolkit.modules.chargingProfiles.domain.ChargingProfileResult | ||
import com.izivia.ocpi.toolkit.modules.chargingProfiles.domain.ClearProfileResult | ||
import com.izivia.ocpi.toolkit.modules.credentials.repositories.PartnerRepository | ||
import com.izivia.ocpi.toolkit.modules.versions.domain.ModuleID | ||
import com.izivia.ocpi.toolkit.transport.TransportClient | ||
import com.izivia.ocpi.toolkit.transport.TransportClientBuilder | ||
import com.izivia.ocpi.toolkit.transport.domain.HttpException | ||
import com.izivia.ocpi.toolkit.transport.domain.HttpMethod | ||
import com.izivia.ocpi.toolkit.transport.domain.HttpRequest | ||
import com.izivia.ocpi.toolkit.transport.domain.HttpStatus | ||
|
||
/** | ||
* Send calls to the SCSP | ||
* | ||
* @property transportClientBuilder used to build transport client | ||
* @property serverVersionsEndpointUrl used to know which partner to communicate with | ||
* @property partnerRepository used to get information about the partner (endpoint, token) | ||
* @property callbackBaseUrl used to build the callback URL sent to the other partner | ||
*/ | ||
class ChargingProfilesCpoClient( | ||
private val transportClientBuilder: TransportClientBuilder, | ||
private val serverVersionsEndpointUrl: String, | ||
private val partnerRepository: PartnerRepository, | ||
private val callbackBaseUrl: String | ||
) { | ||
|
||
private fun buildCallbackTransport(): TransportClient = | ||
transportClientBuilder.build(callbackBaseUrl) | ||
|
||
private suspend fun buildTransport(): TransportClient = transportClientBuilder | ||
.buildFor( | ||
module = ModuleID.chargingprofiles, | ||
partnerUrl = serverVersionsEndpointUrl, | ||
partnerRepository = partnerRepository | ||
) | ||
|
||
suspend fun postCallbackActiveChargingProfile( | ||
responseUrl: String, | ||
result: ActiveChargingProfileResult | ||
): OcpiResponseBody<Any> = with(buildCallbackTransport()) { | ||
send( | ||
HttpRequest( | ||
method = HttpMethod.POST, | ||
path = responseUrl, | ||
body = mapper.writeValueAsString(result) | ||
) | ||
.withRequiredHeaders( | ||
requestId = generateRequestId(), | ||
correlationId = generateCorrelationId() | ||
) | ||
.authenticate(partnerRepository = partnerRepository, partnerUrl = serverVersionsEndpointUrl) | ||
) | ||
.also { | ||
if (it.status != HttpStatus.OK) throw HttpException(it.status, "status should be ${HttpStatus.OK}") | ||
} | ||
.parseBody() | ||
} | ||
|
||
suspend fun postCallbackChargingProfile( | ||
responseUrl: String, | ||
result: ChargingProfileResult | ||
): OcpiResponseBody<Any> = with(buildCallbackTransport()) { | ||
send( | ||
HttpRequest( | ||
method = HttpMethod.POST, | ||
path = responseUrl, | ||
body = mapper.writeValueAsString(result) | ||
) | ||
.withRequiredHeaders( | ||
requestId = generateRequestId(), | ||
correlationId = generateCorrelationId() | ||
) | ||
.authenticate(partnerRepository = partnerRepository, partnerUrl = serverVersionsEndpointUrl) | ||
) | ||
.also { | ||
if (it.status != HttpStatus.OK) throw HttpException(it.status, "status should be ${HttpStatus.OK}") | ||
} | ||
.parseBody() | ||
} | ||
|
||
suspend fun postCallbackClearProfile( | ||
responseUrl: String, | ||
result: ClearProfileResult | ||
): OcpiResponseBody<Any> = with(buildCallbackTransport()) { | ||
send( | ||
HttpRequest( | ||
method = HttpMethod.POST, | ||
path = responseUrl, | ||
body = mapper.writeValueAsString(result) | ||
) | ||
.withRequiredHeaders( | ||
requestId = generateRequestId(), | ||
correlationId = generateCorrelationId() | ||
) | ||
.authenticate(partnerRepository = partnerRepository, partnerUrl = serverVersionsEndpointUrl) | ||
) | ||
.also { | ||
if (it.status != HttpStatus.OK) throw HttpException(it.status, "status should be ${HttpStatus.OK}") | ||
} | ||
.parseBody() | ||
} | ||
|
||
suspend fun putActiveChargingProfile( | ||
sessionId: CiString, | ||
activeChargingProfile: ActiveChargingProfile | ||
): OcpiResponseBody<Any> = with(buildTransport()) { | ||
send( | ||
HttpRequest( | ||
method = HttpMethod.PUT, | ||
path = "/$sessionId", | ||
body = mapper.writeValueAsString(activeChargingProfile) | ||
) | ||
.withRequiredHeaders( | ||
requestId = generateRequestId(), | ||
correlationId = generateCorrelationId() | ||
) | ||
.authenticate(partnerRepository = partnerRepository, partnerUrl = serverVersionsEndpointUrl) | ||
) | ||
.also { | ||
if (it.status != HttpStatus.OK) throw HttpException(it.status, "status should be ${HttpStatus.OK}") | ||
} | ||
.parseBody() | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
...n/kotlin/com/izivia/ocpi/toolkit/modules/chargingProfiles/ChargingProfilesCpoInterface.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package com.izivia.ocpi.toolkit.modules.chargingProfiles | ||
|
||
import com.izivia.ocpi.toolkit.common.CiString | ||
import com.izivia.ocpi.toolkit.common.OcpiResponseBody | ||
import com.izivia.ocpi.toolkit.common.URL | ||
import com.izivia.ocpi.toolkit.modules.chargingProfiles.domain.ChargingProfileResponse | ||
import com.izivia.ocpi.toolkit.modules.chargingProfiles.domain.SetChargingProfile | ||
|
||
/** | ||
* The ChargingProfiles module consists of two interfaces: a Receiver interface that enables a Sender (and its clients) | ||
* to send ChargingProfiles to a Location/EVSE, and an Sender interface to receive the response from the Location/EVSE | ||
* asynchronously. | ||
* | ||
* This is the receiver interface | ||
* | ||
* Method: Description | ||
* - GET: Gets the ActiveChargingProfile for a specific charging session. | ||
* - POST: n/a (use PUT) | ||
* - PUT: Creates/updates a ChargingProfile for a specific charging session. | ||
* - PATCH: n/a (use PUT) | ||
* - DELETE: Cancels an existing ChargingProfile for a specific charging session. | ||
*/ | ||
interface ChargingProfilesCpoInterface { | ||
|
||
/** | ||
* Retrieves the ActiveChargingProfile as it is currently planned for the the given session. | ||
* | ||
* Endpoint structure definition: | ||
* - /ocpi/cpo/2.2.1/chargingprofiles/{session_id}?duration={duration}&response_url={url} | ||
* | ||
* @param sessionId (max-length=36) The unique id that identifies the session in the CPO platform. | ||
* @param duration Length of the requested ActiveChargingProfile in seconds Duration in seconds. | ||
* @param responseUrl (max-length=255) URL that the ActiveChargingProfileResult POST should be send to. This URL | ||
* might contain an unique ID to be able to distinguish between GET ActiveChargingProfile requests. | ||
*/ | ||
suspend fun getActiveChargingProfile( | ||
sessionId: CiString, | ||
duration: Int, | ||
responseUrl: URL | ||
): OcpiResponseBody<ChargingProfileResponse> | ||
|
||
/** | ||
* Creates a new ChargingProfile on a session, or replaces an existing ChargingProfile on the EVSE. | ||
* | ||
* Endpoint structure definition: | ||
* - /ocpi/cpo/2.2.1/chargingprofiles/{session_id} | ||
* | ||
* @param sessionId (max-length=36) The unique id that identifies the session in the CPO platform. | ||
*/ | ||
suspend fun putChargingProfile( | ||
sessionId: CiString, | ||
setChargingProfile: SetChargingProfile | ||
): OcpiResponseBody<ChargingProfileResponse> | ||
|
||
/** | ||
* Clears the ChargingProfile set by the eMSP on the given session. | ||
* | ||
* Endpoint structure definition: | ||
* - /ocpi/cpo/2.2.1/chargingprofiles/{session_id}?response_url={url} | ||
* | ||
* @param sessionId (max-length=36) The unique id that identifies the session in the CPO platform. | ||
* @param responseUrl (max-length=255) URL that the ClearProfileResult POST should be send to. This URL might | ||
* contain an unique ID to be able to distinguish between DELETE ChargingProfile requests. | ||
*/ | ||
suspend fun deleteChargingProfile( | ||
sessionId: CiString, | ||
responseUrl: URL | ||
): OcpiResponseBody<ChargingProfileResponse> | ||
} |
76 changes: 76 additions & 0 deletions
76
...main/kotlin/com/izivia/ocpi/toolkit/modules/chargingProfiles/ChargingProfilesCpoServer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package com.izivia.ocpi.toolkit.modules.chargingProfiles | ||
|
||
import com.izivia.ocpi.toolkit.common.OcpiSelfRegisteringModuleServer | ||
import com.izivia.ocpi.toolkit.common.httpResponse | ||
import com.izivia.ocpi.toolkit.common.mapper | ||
import com.izivia.ocpi.toolkit.modules.chargingProfiles.domain.SetChargingProfile | ||
import com.izivia.ocpi.toolkit.modules.versions.domain.InterfaceRole | ||
import com.izivia.ocpi.toolkit.modules.versions.domain.ModuleID | ||
import com.izivia.ocpi.toolkit.modules.versions.domain.VersionNumber | ||
import com.izivia.ocpi.toolkit.modules.versions.repositories.MutableVersionsRepository | ||
import com.izivia.ocpi.toolkit.transport.TransportServer | ||
import com.izivia.ocpi.toolkit.transport.domain.HttpMethod | ||
import com.izivia.ocpi.toolkit.transport.domain.VariablePathSegment | ||
|
||
class ChargingProfilesCpoServer( | ||
private val service: ChargingProfilesCpoInterface, | ||
versionsRepository: MutableVersionsRepository? = null, | ||
basePathOverride: String? = null | ||
) : OcpiSelfRegisteringModuleServer( | ||
ocpiVersion = VersionNumber.V2_2_1, | ||
moduleID = ModuleID.chargingprofiles, | ||
interfaceRole = InterfaceRole.RECEIVER, | ||
versionsRepository = versionsRepository, | ||
basePathOverride = basePathOverride | ||
) { | ||
|
||
override suspend fun doRegisterOn(transportServer: TransportServer) { | ||
transportServer.handle( | ||
method = HttpMethod.GET, | ||
path = basePathSegments + listOf( | ||
VariablePathSegment("sessionId") | ||
), | ||
queryParams = listOf("duration", "response_url") | ||
) { req -> | ||
req.httpResponse { | ||
service | ||
.getActiveChargingProfile( | ||
sessionId = req.pathParams["sessionId"]!!, | ||
duration = req.queryParams["duration"]!!.toInt(), | ||
responseUrl = req.queryParams["response_url"]!! | ||
) | ||
} | ||
} | ||
|
||
transportServer.handle( | ||
method = HttpMethod.PUT, | ||
path = basePathSegments + listOf( | ||
VariablePathSegment("sessionId") | ||
) | ||
) { req -> | ||
req.httpResponse { | ||
service | ||
.putChargingProfile( | ||
sessionId = req.pathParams["sessionId"]!!, | ||
setChargingProfile = mapper.readValue(req.body, SetChargingProfile::class.java) | ||
) | ||
} | ||
} | ||
|
||
transportServer.handle( | ||
method = HttpMethod.DELETE, | ||
path = basePathSegments + listOf( | ||
VariablePathSegment("sessionId") | ||
), | ||
queryParams = listOf("response_url") | ||
) { req -> | ||
req.httpResponse { | ||
service | ||
.deleteChargingProfile( | ||
sessionId = req.pathParams["sessionId"]!!, | ||
responseUrl = req.queryParams["response_url"]!! | ||
) | ||
} | ||
} | ||
} | ||
} |
112 changes: 112 additions & 0 deletions
112
...ain/kotlin/com/izivia/ocpi/toolkit/modules/chargingProfiles/ChargingProfilesScspClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package com.izivia.ocpi.toolkit.modules.chargingProfiles | ||
|
||
import com.izivia.ocpi.toolkit.common.* | ||
import com.izivia.ocpi.toolkit.modules.chargingProfiles.domain.ChargingProfile | ||
import com.izivia.ocpi.toolkit.modules.chargingProfiles.domain.ChargingProfileResponse | ||
import com.izivia.ocpi.toolkit.modules.chargingProfiles.domain.SetChargingProfile | ||
import com.izivia.ocpi.toolkit.modules.credentials.repositories.PartnerRepository | ||
import com.izivia.ocpi.toolkit.modules.versions.domain.ModuleID | ||
import com.izivia.ocpi.toolkit.transport.TransportClient | ||
import com.izivia.ocpi.toolkit.transport.TransportClientBuilder | ||
import com.izivia.ocpi.toolkit.transport.domain.HttpException | ||
import com.izivia.ocpi.toolkit.transport.domain.HttpMethod | ||
import com.izivia.ocpi.toolkit.transport.domain.HttpRequest | ||
import com.izivia.ocpi.toolkit.transport.domain.HttpStatus | ||
|
||
class ChargingProfilesScspClient( | ||
private val transportClientBuilder: TransportClientBuilder, | ||
private val serverVersionsEndpointUrl: String, | ||
private val partnerRepository: PartnerRepository, | ||
private val callbackBaseUrl: URL | ||
) { | ||
|
||
private suspend fun buildTransport(): TransportClient = transportClientBuilder | ||
.buildFor( | ||
module = ModuleID.chargingprofiles, | ||
partnerUrl = serverVersionsEndpointUrl, | ||
partnerRepository = partnerRepository | ||
) | ||
|
||
suspend fun getActiveChargingProfile( | ||
sessionId: CiString, | ||
duration: Int, | ||
requestId: String | ||
): OcpiResponseBody<ChargingProfileResponse> = with(buildTransport()) { | ||
send( | ||
HttpRequest( | ||
method = HttpMethod.GET, | ||
path = "/$sessionId", | ||
queryParams = mapOf( | ||
"duration" to duration.toString(), | ||
"response_url" to "$callbackBaseUrl/" + | ||
"${ChargingProfilesScspServer.ACTIVE_CHARGING_PROFILE_CALLBACK_URL}/$requestId" | ||
) | ||
) | ||
.withRequiredHeaders( | ||
requestId = generateRequestId(), | ||
correlationId = generateCorrelationId() | ||
) | ||
.authenticate(partnerRepository = partnerRepository, partnerUrl = serverVersionsEndpointUrl) | ||
) | ||
.also { | ||
if (it.status != HttpStatus.OK) throw HttpException(it.status, "status should be ${HttpStatus.OK}") | ||
} | ||
.parseBody() | ||
} | ||
|
||
suspend fun putChargingProfile( | ||
sessionId: CiString, | ||
chargingProfile: ChargingProfile, | ||
requestId: String | ||
): OcpiResponseBody<ChargingProfileResponse> = with(buildTransport()) { | ||
send( | ||
HttpRequest( | ||
method = HttpMethod.PUT, | ||
path = "/$sessionId", | ||
body = mapper.writeValueAsString( | ||
SetChargingProfile( | ||
chargingProfile = chargingProfile, | ||
responseUrl = "$callbackBaseUrl/" + | ||
"${ChargingProfilesScspServer.CHARGING_PROFILE_CALLBACK_URL}/$requestId" | ||
) | ||
) | ||
) | ||
.withRequiredHeaders( | ||
requestId = generateRequestId(), | ||
correlationId = generateCorrelationId() | ||
) | ||
.authenticate(partnerRepository = partnerRepository, partnerUrl = serverVersionsEndpointUrl) | ||
) | ||
.also { | ||
if (it.status != HttpStatus.OK && it.status != HttpStatus.CREATED) { | ||
throw HttpException(it.status, "status should be ${HttpStatus.OK} or ${HttpStatus.CREATED}") | ||
} | ||
} | ||
.parseBody() | ||
} | ||
|
||
suspend fun deleteChargingProfile( | ||
sessionId: CiString, | ||
requestId: String | ||
): OcpiResponseBody<ChargingProfileResponse> = with(buildTransport()) { | ||
send( | ||
HttpRequest( | ||
method = HttpMethod.DELETE, | ||
path = "/$sessionId", | ||
queryParams = mapOf( | ||
"response_url" to "$callbackBaseUrl/" + | ||
"${ChargingProfilesScspServer.CLEAR_PROFILE_CALLBACK_URL}/$requestId" | ||
) | ||
) | ||
.withRequiredHeaders( | ||
requestId = generateRequestId(), | ||
correlationId = generateCorrelationId() | ||
) | ||
.authenticate(partnerRepository = partnerRepository, partnerUrl = serverVersionsEndpointUrl) | ||
) | ||
.also { | ||
if (it.status != HttpStatus.OK) throw HttpException(it.status, "status should be ${HttpStatus.OK}") | ||
} | ||
.parseBody() | ||
} | ||
} |
Oops, something went wrong.