Merge pull request #72 from corda/clint-simmdemointegrationtest

Add SIMM valuation demo integration test
This commit is contained in:
Clinton
2017-01-04 15:32:44 +00:00
committed by GitHub
8 changed files with 306 additions and 214 deletions

View File

@ -25,49 +25,53 @@ import java.time.LocalDateTime
* the java.time API, some core types, and Kotlin data classes. * the java.time API, some core types, and Kotlin data classes.
*/ */
object JsonSupport { object JsonSupport {
val javaTimeModule : Module by lazy {
fun createDefaultMapper(identities: IdentityService): ObjectMapper { SimpleModule("java.time").apply {
val mapper = ServiceHubObjectMapper(identities) addSerializer(LocalDate::class.java, ToStringSerializer)
mapper.enable(SerializationFeature.INDENT_OUTPUT) addDeserializer(LocalDate::class.java, LocalDateDeserializer)
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) addKeyDeserializer(LocalDate::class.java, LocalDateKeyDeserializer)
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS) addSerializer(LocalDateTime::class.java, ToStringSerializer)
}
val timeModule = SimpleModule("java.time")
timeModule.addSerializer(LocalDate::class.java, ToStringSerializer)
timeModule.addDeserializer(LocalDate::class.java, LocalDateDeserializer)
timeModule.addKeyDeserializer(LocalDate::class.java, LocalDateKeyDeserializer)
timeModule.addSerializer(LocalDateTime::class.java, ToStringSerializer)
val cordaModule = SimpleModule("core")
cordaModule.addSerializer(Party::class.java, PartySerializer)
cordaModule.addDeserializer(Party::class.java, PartyDeserializer)
cordaModule.addSerializer(BigDecimal::class.java, ToStringSerializer)
cordaModule.addDeserializer(BigDecimal::class.java, NumberDeserializers.BigDecimalDeserializer())
cordaModule.addSerializer(SecureHash::class.java, SecureHashSerializer)
// It's slightly remarkable, but apparently Jackson works out that this is the only possibility
// for a SecureHash at the moment and tries to use SHA256 directly even though we only give it SecureHash
cordaModule.addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
cordaModule.addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
// For ed25519 pubkeys
cordaModule.addSerializer(EdDSAPublicKey::class.java, PublicKeySerializer)
cordaModule.addDeserializer(EdDSAPublicKey::class.java, PublicKeyDeserializer)
// For composite keys
cordaModule.addSerializer(CompositeKey::class.java, CompositeKeySerializer)
cordaModule.addDeserializer(CompositeKey::class.java, CompositeKeyDeserializer)
// For NodeInfo
// TODO this tunnels the Kryo representation as a Base58 encoded string. Replace when RPC supports this.
cordaModule.addSerializer(NodeInfo::class.java, NodeInfoSerializer)
cordaModule.addDeserializer(NodeInfo::class.java, NodeInfoDeserializer)
mapper.registerModule(timeModule)
mapper.registerModule(cordaModule)
mapper.registerModule(KotlinModule())
return mapper
} }
val cordaModule : Module by lazy {
SimpleModule("core").apply {
addSerializer(Party::class.java, PartySerializer)
addDeserializer(Party::class.java, PartyDeserializer)
addSerializer(BigDecimal::class.java, ToStringSerializer)
addDeserializer(BigDecimal::class.java, NumberDeserializers.BigDecimalDeserializer())
addSerializer(SecureHash::class.java, SecureHashSerializer)
// It's slightly remarkable, but apparently Jackson works out that this is the only possibility
// for a SecureHash at the moment and tries to use SHA256 directly even though we only give it SecureHash
addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
// For ed25519 pubkeys
addSerializer(EdDSAPublicKey::class.java, PublicKeySerializer)
addDeserializer(EdDSAPublicKey::class.java, PublicKeyDeserializer)
// For composite keys
addSerializer(CompositeKey::class.java, CompositeKeySerializer)
addDeserializer(CompositeKey::class.java, CompositeKeyDeserializer)
// For NodeInfo
// TODO this tunnels the Kryo representation as a Base58 encoded string. Replace when RPC supports this.
addSerializer(NodeInfo::class.java, NodeInfoSerializer)
addDeserializer(NodeInfo::class.java, NodeInfoDeserializer)
}
}
fun createDefaultMapper(identities: IdentityService): ObjectMapper =
ServiceHubObjectMapper(identities).apply {
enable(SerializationFeature.INDENT_OUTPUT)
enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
registerModule(javaTimeModule)
registerModule(cordaModule)
registerModule(KotlinModule())
}
class ServiceHubObjectMapper(val identities: IdentityService) : ObjectMapper() class ServiceHubObjectMapper(val identities: IdentityService) : ObjectMapper()
object ToStringSerializer : JsonSerializer<Any>() { object ToStringSerializer : JsonSerializer<Any>() {

View File

@ -34,6 +34,18 @@ sourceSets {
srcDir "../../config/test" srcDir "../../config/test"
} }
} }
integrationTest {
kotlin {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integration-test/kotlin')
}
}
}
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
} }
dependencies { dependencies {
@ -105,6 +117,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
} }
} }
task integrationTest(type: Test, dependsOn: []) {
testClassesDir = sourceSets.integrationTest.output.classesDir
classpath = sourceSets.integrationTest.runtimeClasspath
}
task npmInstall(type: Exec) { task npmInstall(type: Exec) {
workingDir 'src/main/web' workingDir 'src/main/web'

View File

@ -0,0 +1,78 @@
package net.corda.vega
import com.opengamma.strata.product.common.BuySell
import net.corda.core.getOrThrow
import net.corda.core.node.services.ServiceInfo
import net.corda.node.driver.NodeHandle
import net.corda.node.driver.driver
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.IntegrationTestCategory
import net.corda.testing.getHostAndPort
import net.corda.testing.http.HttpApi
import net.corda.vega.api.PortfolioApi
import net.corda.vega.api.PortfolioApiUtils
import net.corda.vega.api.SwapDataModel
import net.corda.vega.api.SwapDataView
import net.corda.vega.portfolio.Portfolio
import org.junit.Test
import java.math.BigDecimal
import java.time.LocalDate
import java.util.*
import java.util.concurrent.Future
class SimmValuationTest: IntegrationTestCategory {
private companion object {
// SIMM demo can only currently handle one valuation date due to a lack of market data or a market data source.
val valuationDate = LocalDate.parse("2016-06-06")
val nodeALegalName = "Bank A"
val nodeBLegalName = "Bank B"
val testTradeId = "trade1"
}
@Test fun `runs SIMM valuation demo`() {
driver(isDebug = true) {
startNode("Controller", setOf(ServiceInfo(SimpleNotaryService.type))).getOrThrow()
val nodeA = getSimmNodeApi(startNode(nodeALegalName))
val nodeB = getSimmNodeApi(startNode(nodeBLegalName))
val nodeBParty = getPartyWithName(nodeA, nodeBLegalName)
val nodeAParty = getPartyWithName(nodeB, nodeALegalName)
assert(createTradeBetween(nodeA, nodeBParty, testTradeId))
assert(tradeExists(nodeB, nodeAParty, testTradeId))
assert(runValuationsBetween(nodeA, nodeBParty))
assert(valuationExists(nodeB, nodeAParty))
}
}
private fun getSimmNodeApi(futureNode: Future<NodeHandle>): HttpApi {
val nodeAddr = futureNode.getOrThrow().config.getHostAndPort("webAddress")
return HttpApi.fromHostAndPort(nodeAddr, "api/simmvaluationdemo")
}
private fun getPartyWithName(partyApi: HttpApi, countryparty: String): PortfolioApi.ApiParty =
getAvailablePartiesFor(partyApi).counterparties.single { it.text == countryparty }
private fun getAvailablePartiesFor(partyApi: HttpApi): PortfolioApi.AvailableParties {
return partyApi.getJson<PortfolioApi.AvailableParties>("whoami")
}
private fun createTradeBetween(partyApi: HttpApi, counterparty: PortfolioApi.ApiParty, tradeId: String): Boolean {
val trade = SwapDataModel(tradeId, "desc", valuationDate, "EUR_FIXED_1Y_EURIBOR_3M",
valuationDate, LocalDate.parse("2020-01-02"), BuySell.BUY, BigDecimal.valueOf(1000), BigDecimal.valueOf(0.1))
return partyApi.putJson("${counterparty.id}/trades", trade)
}
private fun tradeExists(partyApi: HttpApi, counterparty: PortfolioApi.ApiParty, tradeId: String): Boolean {
val trades = partyApi.getJson<Array<SwapDataView>>("${counterparty.id}/trades")
return (trades.find { it.id == tradeId } != null)
}
private fun runValuationsBetween(partyApi: HttpApi, counterparty: PortfolioApi.ApiParty): Boolean {
return partyApi.postJson("${counterparty.id}/portfolio/valuations/calculate", PortfolioApi.ValuationCreationParams(valuationDate))
}
private fun valuationExists(partyApi: HttpApi, counterparty: PortfolioApi.ApiParty): Boolean {
val valuations = partyApi.getJson<PortfolioApiUtils.ValuationsView>("${counterparty.id}/portfolio/valuations")
return (valuations.initialMargin.call["total"] != 0.0)
}
}

View File

@ -1,20 +0,0 @@
package net.corda.vega.api
/**
* A small JSON DSL to create structures for APIs on the fly that mimic JSON in structure.
* Use: json { obj("a" to 100, "b" to "hello", "c" to arr(1, 2, "c")) }
*/
class JsonBuilder {
fun obj(vararg objs: Pair<String, Any>): Map<String, Any> {
return objs.toMap()
}
fun arr(vararg objs: Any): List<Any> {
return objs.toList()
}
}
fun json(body: JsonBuilder.() -> Map<String, Any>): Map<String, Any> {
val jsonWrapper = JsonBuilder()
return jsonWrapper.body()
}

View File

@ -98,11 +98,9 @@ class PortfolioApi(val rpc: CordaRPCOps) {
@Path("business-date") @Path("business-date")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
fun getBusinessDate(): Any { fun getBusinessDate(): Any {
return json { return mapOf(
obj( "business-date" to LocalDateTime.ofInstant(rpc.currentNodeTime(), ZoneId.systemDefault()).toLocalDate()
"business-date" to LocalDateTime.ofInstant(rpc.currentNodeTime(), ZoneId.systemDefault()).toLocalDate() )
)
}
} }
/** /**
@ -195,12 +193,10 @@ class PortfolioApi(val rpc: CordaRPCOps) {
return withParty(partyName) { party -> return withParty(partyName) { party ->
val trades = getTradesWith(party) val trades = getTradesWith(party)
val portfolio = Portfolio(trades) val portfolio = Portfolio(trades)
val summary = json { val summary = mapOf(
obj( "trades" to portfolio.trades.size,
"trades" to portfolio.trades.size, "notional" to portfolio.getNotionalForParty(ownParty).toDouble()
"notional" to portfolio.getNotionalForParty(ownParty).toDouble() )
)
}
Response.ok().entity(summary).build() Response.ok().entity(summary).build()
} }
} }
@ -239,28 +235,23 @@ class PortfolioApi(val rpc: CordaRPCOps) {
} }
} }
data class ApiParty(val id: String, val text: String)
data class AvailableParties(val self: ApiParty, val counterparties: List<ApiParty>)
/** /**
* Returns the identity of the current node as well as a list of other counterparties that it is aware of. * Returns the identity of the current node as well as a list of other counterparties that it is aware of.
*/ */
@GET @GET
@Path("whoami") @Path("whoami")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
fun getWhoAmI(): Any { fun getWhoAmI(): AvailableParties {
val counterParties = rpc.networkMapUpdates().first.filter { it.legalIdentity.name != "NetworkMapService" && it.legalIdentity.name != "Notary" && it.legalIdentity.name != ownParty.name } val counterParties = rpc.networkMapUpdates().first.filter {
return json { it.legalIdentity.name != "NetworkMapService" && it.legalIdentity.name != "Notary" && it.legalIdentity.name != ownParty.name
obj(
"self" to obj(
"id" to ownParty.owningKey.toBase58String(),
"text" to ownParty.name
),
"counterparties" to counterParties.map {
obj(
"id" to it.legalIdentity.owningKey.toBase58String(),
"text" to it.legalIdentity.name
)
}
)
} }
return AvailableParties(
self = ApiParty(ownParty.owningKey.toBase58String(), ownParty.name),
counterparties = counterParties.map { ApiParty(it.legalIdentity.owningKey.toBase58String(), it.legalIdentity.name) })
} }
data class ValuationCreationParams(val valuationDate: LocalDate) data class ValuationCreationParams(val valuationDate: LocalDate)

View File

@ -15,7 +15,16 @@ import java.time.LocalDate
* API JSON generation functions for larger JSON outputs. * API JSON generation functions for larger JSON outputs.
*/ */
class PortfolioApiUtils(private val ownParty: Party) { class PortfolioApiUtils(private val ownParty: Party) {
fun createValuations(state: PortfolioState, portfolio: Portfolio): Any { data class InitialMarginView(val baseCurrency: String, val post: Map<String, Double>, val call: Map<String, Double>, val agreed: Boolean)
data class ValuationsView(
val businessDate: LocalDate,
val portfolio: Map<String, Any>,
val marketData: Map<String, Any>,
val sensitivities: Map<String, Any>,
val initialMargin: InitialMarginView,
val confirmation: Map<String, Any>)
fun createValuations(state: PortfolioState, portfolio: Portfolio): ValuationsView {
val valuation = state.valuation!! val valuation = state.valuation!!
val currency = if (portfolio.trades.isNotEmpty()) { val currency = if (portfolio.trades.isNotEmpty()) {
@ -32,141 +41,137 @@ class PortfolioApiUtils(private val ownParty: Party) {
val completeSubgroups = subgroups.mapValues { it.value.mapValues { it.value[0].third.toDouble() }.toSortedMap() } val completeSubgroups = subgroups.mapValues { it.value.mapValues { it.value[0].third.toDouble() }.toSortedMap() }
val yieldCurves = json { val yieldCurves = mapOf(
obj( "name" to "EUR",
"name" to "EUR", "values" to completeSubgroups.get("EUR")!!.filter { !it.key.contains("Fixing") }.map {
"values" to completeSubgroups.get("EUR")!!.filter { !it.key.contains("Fixing") }.map { mapOf(
json { "tenor" to it.key,
obj( "rate" to it.value
"tenor" to it.key, )
"rate" to it.value }
) )
}
} val fixings = mapOf(
) "name" to "EUR",
} "values" to completeSubgroups.get("EUR")!!.filter { it.key.contains("Fixing") }.map {
val fixings = json { mapOf(
obj( "tenor" to it.key,
"name" to "EUR", "rate" to it.value
"values" to completeSubgroups.get("EUR")!!.filter { it.key.contains("Fixing") }.map { )
json { }
obj( )
"tenor" to it.key,
"rate" to it.value
)
}
}
)
}
val processedSensitivities = valuation.totalSensivities.sensitivities.map { it.marketDataName to it.parameterMetadata.map { it.label }.zip(it.sensitivity.toList()).toMap() }.toMap() val processedSensitivities = valuation.totalSensivities.sensitivities.map { it.marketDataName to it.parameterMetadata.map { it.label }.zip(it.sensitivity.toList()).toMap() }.toMap()
return json { val initialMarginView = InitialMarginView(
obj( baseCurrency = currency,
"businessDate" to LocalDate.now(), post = mapOf(
"portfolio" to obj( "IRFX" to valuation.margin.first,
"trades" to tradeCount, "commodity" to 0.0,
"baseCurrency" to currency, "equity" to 0.0,
"IRFX" to tradeCount, "credit" to 0.0,
"commodity" to 0, "total" to valuation.margin.first
"equity" to 0, ),
"credit" to 0, call = mapOf(
"total" to tradeCount, "IRFX" to valuation.margin.first,
"agreed" to true "commodity" to 0.0,
), "equity" to 0.0,
"marketData" to obj( "credit" to 0.0,
"yieldCurves" to yieldCurves, "total" to valuation.margin.first
"fixings" to fixings, ),
"agreed" to true agreed = true)
),
"sensitivities" to obj("curves" to processedSensitivities, return ValuationsView(
"currency" to valuation.currencySensitivies.amounts.toList().map { businessDate = LocalDate.now(),
obj( portfolio = mapOf(
"currency" to it.currency.code, "trades" to tradeCount,
"amount" to it.amount "baseCurrency" to currency,
) "IRFX" to tradeCount,
}, "commodity" to 0,
"agreed" to true "equity" to 0,
), "credit" to 0,
"initialMargin" to obj( "total" to tradeCount,
"baseCurrency" to currency, "agreed" to true
"post" to obj( ),
"IRFX" to valuation.margin.first, marketData = mapOf(
"commodity" to 0, "yieldCurves" to yieldCurves,
"equity" to 0, "fixings" to fixings,
"credit" to 0, "agreed" to true
"total" to valuation.margin.first ),
), sensitivities = mapOf("curves" to processedSensitivities,
"call" to obj( "currency" to valuation.currencySensitivies.amounts.toList().map {
"IRFX" to valuation.margin.first, mapOf(
"commodity" to 0, "currency" to it.currency.code,
"equity" to 0, "amount" to it.amount
"credit" to 0, )
"total" to valuation.margin.first },
), "agreed" to true
"agreed" to true ),
), initialMargin = initialMarginView,
"confirmation" to obj( confirmation = mapOf(
"hash" to state.hash().toString(), "hash" to state.hash().toString(),
"agreed" to true "agreed" to true
) )
) )
}
} }
fun createTradeView(state: IRSState): Any { data class TradeView(
val fixedLeg: Map<String, Any>,
val floatingLeg: Map<String, Any>,
val common: Map<String, Any>,
val ref: String)
fun createTradeView(state: IRSState): TradeView {
val trade = if (state.buyer.name == ownParty.name) state.swap.toFloatingLeg() else state.swap.toFloatingLeg() val trade = if (state.buyer.name == ownParty.name) state.swap.toFloatingLeg() else state.swap.toFloatingLeg()
val fixedLeg = trade.product.legs.first { it.type == SwapLegType.FIXED } as RateCalculationSwapLeg val fixedLeg = trade.product.legs.first { it.type == SwapLegType.FIXED } as RateCalculationSwapLeg
val floatingLeg = trade.product.legs.first { it.type != SwapLegType.FIXED } as RateCalculationSwapLeg val floatingLeg = trade.product.legs.first { it.type != SwapLegType.FIXED } as RateCalculationSwapLeg
val fixedRate = fixedLeg.calculation as FixedRateCalculation val fixedRate = fixedLeg.calculation as FixedRateCalculation
val floatingRate = floatingLeg.calculation as IborRateCalculation val floatingRate = floatingLeg.calculation as IborRateCalculation
return json { return TradeView(
obj( fixedLeg = mapOf(
"fixedLeg" to obj( "fixedRatePayer" to state.buyer.name,
"fixedRatePayer" to state.buyer.name, "notional" to mapOf(
"notional" to obj( "token" to fixedLeg.currency.code,
"token" to fixedLeg.currency.code, "quantity" to fixedLeg.notionalSchedule.amount.initialValue
"quantity" to fixedLeg.notionalSchedule.amount.initialValue ),
), "paymentFrequency" to fixedLeg.paymentSchedule.paymentFrequency.toString(),
"paymentFrequency" to fixedLeg.paymentSchedule.paymentFrequency.toString(), "effectiveDate" to fixedLeg.startDate.unadjusted,
"effectiveDate" to fixedLeg.startDate.unadjusted, "terminationDate" to fixedLeg.endDate.unadjusted,
"terminationDate" to fixedLeg.endDate.unadjusted, "fixedRate" to mapOf(
"fixedRate" to obj( "value" to fixedRate.rate.initialValue
"value" to fixedRate.rate.initialValue ),
), "paymentRule" to fixedLeg.paymentSchedule.paymentRelativeTo.name,
"paymentRule" to fixedLeg.paymentSchedule.paymentRelativeTo.name, "calendar" to listOf("TODO"),
"calendar" to arr("TODO"), "paymentCalendar" to mapOf<String, Any>() // TODO
"paymentCalendar" to obj() // TODO ),
), floatingLeg = mapOf(
"floatingLeg" to obj( "floatingRatePayer" to state.seller.name,
"floatingRatePayer" to state.seller.name, "notional" to mapOf(
"notional" to obj( "token" to floatingLeg.currency.code,
"token" to floatingLeg.currency.code, "quantity" to floatingLeg.notionalSchedule.amount.initialValue
"quantity" to floatingLeg.notionalSchedule.amount.initialValue ),
), "paymentFrequency" to floatingLeg.paymentSchedule.paymentFrequency.toString(),
"paymentFrequency" to floatingLeg.paymentSchedule.paymentFrequency.toString(), "effectiveDate" to floatingLeg.startDate.unadjusted,
"effectiveDate" to floatingLeg.startDate.unadjusted, "terminationDate" to floatingLeg.endDate.unadjusted,
"terminationDate" to floatingLeg.endDate.unadjusted, "index" to floatingRate.index.name,
"index" to floatingRate.index.name, "paymentRule" to floatingLeg.paymentSchedule.paymentRelativeTo,
"paymentRule" to floatingLeg.paymentSchedule.paymentRelativeTo, "calendar" to listOf("TODO"),
"calendar" to arr("TODO"), "paymentCalendar" to listOf("TODO"),
"paymentCalendar" to arr("TODO"), "fixingCalendar" to mapOf<String, Any>() // TODO
"fixingCalendar" to obj() // TODO ),
), common = mapOf(
"common" to obj( "valuationDate" to trade.product.startDate.unadjusted,
"valuationDate" to trade.product.startDate.unadjusted, "hashLegalDocs" to state.contract.legalContractReference.toString(),
"hashLegalDocs" to state.contract.legalContractReference.toString(), "interestRate" to mapOf(
"interestRate" to obj( "name" to "TODO",
"name" to "TODO", "oracle" to "TODO",
"oracle" to "TODO", "tenor" to mapOf(
"tenor" to obj( "name" to "TODO"
"name" to "TODO" )
) )
) ),
), ref = trade.info.id.get().value
"ref" to trade.info.id.get().value )
)
}
} }
} }

View File

@ -18,7 +18,12 @@ class HttpApi(val root: URL) {
*/ */
fun postJson(path: String, data: Any = Unit) = HttpUtils.postJson(URL(root, path), toJson(data)) fun postJson(path: String, data: Any = Unit) = HttpUtils.postJson(URL(root, path), toJson(data))
private fun toJson(any: Any) = if (any is String) any else ObjectMapper().writeValueAsString(any) /**
* Send a GET request to the path on the API specified.
*/
inline fun<reified T: Any> getJson(path: String, params: Map<String, String> = mapOf()) = HttpUtils.getJson<T>(URL(root, path), params)
private fun toJson(any: Any) = any as? String ?: HttpUtils.defaultMapper.writeValueAsString(any)
companion object { companion object {
fun fromHostAndPort(hostAndPort: HostAndPort, base: String, protocol: String = "http"): HttpApi fun fromHostAndPort(hostAndPort: HostAndPort, base: String, protocol: String = "http"): HttpApi

View File

@ -1,6 +1,9 @@
package net.corda.testing.http package net.corda.testing.http
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.node.utilities.JsonSupport
import okhttp3.* import okhttp3.*
import java.net.URL import java.net.URL
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -15,6 +18,9 @@ object HttpUtils {
.connectTimeout(5, TimeUnit.SECONDS) .connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS).build() .readTimeout(60, TimeUnit.SECONDS).build()
} }
val defaultMapper: ObjectMapper by lazy {
ObjectMapper().registerModule(JsonSupport.javaTimeModule).registerModule(KotlinModule())
}
fun putJson(url: URL, data: String) : Boolean { fun putJson(url: URL, data: String) : Boolean {
val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), data) val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), data)
@ -26,12 +32,19 @@ object HttpUtils {
return makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").post(body).build()) return makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").post(body).build())
} }
inline fun<reified T: Any> getJson(url: URL, params: Map<String, String> = mapOf()) : T {
val paramString = if(params.isEmpty()) "" else "?" + params.map { "${it.key}=${it.value}" }.joinToString("&")
val parameterisedUrl = URL(url.toExternalForm() + paramString)
return defaultMapper.readValue(parameterisedUrl, T::class.java)
}
private fun makeRequest(request: Request): Boolean { private fun makeRequest(request: Request): Boolean {
val response = client.newCall(request).execute() val response = client.newCall(request).execute()
if (!response.isSuccessful) { if (!response.isSuccessful) {
logger.error("Could not fulfill HTTP request of type ${request.method()} to ${request.url()}. Status Code: ${response.code()}. Message: ${response.body().string()}") logger.error("Could not fulfill HTTP request of type ${request.method()} to ${request.url()}. Status Code: ${response.code()}. Message: ${response.body().string()}")
} }
return response.isSuccessful return response.isSuccessful
} }
} }