WIP - squash me.

This commit is contained in:
Clinton Alexander 2017-06-08 18:29:26 +01:00
parent de64d13647
commit 5fdd57ec48
7 changed files with 79 additions and 40 deletions

View File

@ -23,6 +23,7 @@ import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.bouncycastle.asn1.x500.X500Name
import java.math.BigDecimal
import java.security.PublicKey
import java.time.LocalDate
import java.util.*
/**
@ -76,6 +77,7 @@ object JacksonSupport {
addSerializer(SecureHash.SHA256::class.java, SecureHashSerializer)
addDeserializer(SecureHash::class.java, SecureHashDeserializer())
addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
addSerializer(BusinessCalendar::class.java, CalendarSerializer)
addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
// For ed25519 pubkeys
@ -243,11 +245,31 @@ object JacksonSupport {
}
}
data class BusinessCalendarWrapper(val holidayDates: List<LocalDate>) {
fun toCalendar() = BusinessCalendar(holidayDates)
}
object CalendarSerializer : JsonSerializer<BusinessCalendar>() {
override fun serialize(obj: BusinessCalendar, generator: JsonGenerator, context: SerializerProvider) {
println(obj)
val calendarName = BusinessCalendar.calendars.find { BusinessCalendar.getInstance(it) == obj }
if(calendarName != null) {
generator.writeString(calendarName)
} else {
generator.writeObject(BusinessCalendarWrapper(obj.holidayDates))
}
}
}
object CalendarDeserializer : JsonDeserializer<BusinessCalendar>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar {
return try {
val array = StringArrayDeserializer.instance.deserialize(parser, context)
BusinessCalendar.getInstance(*array)
try {
val array = StringArrayDeserializer.instance.deserialize(parser, context)
BusinessCalendar.getInstance(*array)
} catch (e: Exception) {
parser.readValueAs(BusinessCalendarWrapper::class.java).toCalendar()
}
} catch (e: Exception) {
throw JsonParseException(parser, "Invalid calendar(s) ${parser.text}: ${e.message}")
}

View File

@ -633,7 +633,7 @@ fun LocalDate.isWorkingDay(accordingToCalendar: BusinessCalendar): Boolean = acc
* no staff are around to handle problems.
*/
@CordaSerializable
open class BusinessCalendar private constructor(val holidayDates: List<LocalDate>) {
open class BusinessCalendar(val holidayDates: List<LocalDate>) {
@CordaSerializable
class UnknownCalendar(name: String) : Exception("$name not found")

View File

@ -4,7 +4,9 @@ import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.Futures
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.getOrThrow
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.services.ServiceInfo
import net.corda.core.toFuture
import net.corda.core.utilities.DUMMY_BANK_A
import net.corda.core.utilities.DUMMY_BANK_B
import net.corda.core.utilities.DUMMY_NOTARY
@ -22,14 +24,18 @@ import net.corda.testing.http.HttpApi
import org.apache.commons.io.IOUtils
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import rx.Observable
import rx.observables.BlockingObservable
import java.net.URL
import java.time.Duration
import java.time.LocalDate
import java.time.temporal.ChronoUnit
class IRSDemoTest : IntegrationTestCategory {
val rpcUser = User("user", "password", emptySet())
val currentDate: LocalDate = LocalDate.now()
val futureDate: LocalDate = currentDate.plusMonths(6)
val maxWaitTime: Duration = Duration.of(60, ChronoUnit.SECONDS)
@Test
fun `runs IRS demo`() {
@ -50,51 +56,50 @@ class IRSDemoTest : IntegrationTestCategory {
println("All webservers started")
val (controllerApi, nodeAApi, nodeBApi) = listOf(controller, nodeA, nodeB).zip(listOf(controllerAddr, nodeAAddr, nodeBAddr)).map {
val mapper = net.corda.jackson.JacksonSupport.createDefaultMapper(it.first.rpc)
HttpApi.fromHostAndPort(it.second, "api/irs", mapper = mapper)
}
val nextFixingDates = getFixingDateObservable(nodeA.configuration)
val numADeals = getTradeCount(nodeAAddr)
val numBDeals = getTradeCount(nodeBAddr)
val numADeals = getTradeCount(nodeAApi)
val numBDeals = getTradeCount(nodeBApi)
runUploadRates(controllerAddr)
runTrade(nodeAAddr)
runTrade(nodeAApi)
assertThat(getTradeCount(nodeAAddr)).isEqualTo(numADeals + 1)
assertThat(getTradeCount(nodeBAddr)).isEqualTo(numBDeals + 1)
assertThat(getTradeCount(nodeAApi)).isEqualTo(numADeals + 1)
assertThat(getTradeCount(nodeBApi)).isEqualTo(numBDeals + 1)
// Wait until the initial trade and all scheduled fixings up to the current date have finished
nextFixingDates.first { it == null || it > currentDate }
nextFixingDates.firstWithTimeout(maxWaitTime){ it == null || it > currentDate }
runDateChange(nodeBApi)
nextFixingDates.firstWithTimeout(maxWaitTime) { it == null || it > futureDate }
runDateChange(nodeBAddr)
nextFixingDates.first { it == null || it > futureDate }
assertThat(getTrades(nodeAAddr)[0] as InterestRateSwap.State)
assertThat(getTrades(nodeAApi)[0])
}
}
fun getFixingDateObservable(config: FullNodeConfiguration): BlockingObservable<LocalDate?> {
fun getFixingDateObservable(config: FullNodeConfiguration): Observable<LocalDate?> {
val client = CordaRPCClient(config.rpcAddress!!)
val proxy = client.start("user", "password").proxy
val vaultUpdates = proxy.vaultAndUpdates().second
val fixingDates = vaultUpdates.map { update ->
return vaultUpdates.map { update ->
val irsStates = update.produced.map { it.state.data }.filterIsInstance<InterestRateSwap.State>()
irsStates.mapNotNull { it.calculation.nextFixingDate() }.max()
}.cache().toBlocking()
return fixingDates
}.cache()
}
private fun runDateChange(nodeAddr: HostAndPort) {
println("Running date change against $nodeAddr")
val url = URL("http://$nodeAddr/api/irs/demodate")
assertThat(putJson(url, "\"$futureDate\"")).isTrue()
private fun runDateChange(nodeApi: HttpApi) {
println("Running date change against ${nodeApi.root}")
assertThat(nodeApi.putJson("demodate", "\"$futureDate\"")).isTrue()
}
private fun runTrade(nodeAddr: HostAndPort) {
println("Running trade against $nodeAddr")
private fun runTrade(nodeApi: HttpApi) {
println("Running trade against ${nodeApi.root}")
val fileContents = loadResourceFile("net/corda/irs/simulation/example-irs-trade.json")
val tradeFile = fileContents.replace("tradeXXX", "trade1")
val url = URL("http://$nodeAddr/api/irs/deals")
assertThat(postJson(url, tradeFile)).isTrue()
assertThat(nodeApi.postJson("deals", tradeFile)).isTrue()
}
private fun runUploadRates(host: HostAndPort) {
@ -108,17 +113,19 @@ class IRSDemoTest : IntegrationTestCategory {
return IOUtils.toString(Thread.currentThread().contextClassLoader.getResourceAsStream(filename), Charsets.UTF_8.name())
}
private fun getTradeCount(nodeAddr: HostAndPort): Int {
println("Getting trade count from $nodeAddr")
val api = HttpApi.fromHostAndPort(nodeAddr, "api/irs")
val deals = api.getJson<Array<*>>("deals")
private fun getTradeCount(nodeApi: HttpApi): Int {
println("Getting trade count from ${nodeApi.root}")
val deals = nodeApi.getJson<Array<*>>("deals")
return deals.size
}
private fun getTrades(nodeAddr: HostAndPort): Array<*> {
println("Getting trades from $nodeAddr")
val api = HttpApi.fromHostAndPort(nodeAddr, "api/irs")
val deals = api.getJson<Array<*>>("deals")
private fun getTrades(nodeApi: HttpApi): Array<InterestRateSwap.State> {
println("Getting trades from ${nodeApi.root}")
val deals = nodeApi.getJson<Array<InterestRateSwap.State>>("deals")
return deals
}
fun<T> Observable<T>.firstWithTimeout(timeout: Duration, pred: (T) -> Boolean) {
first(pred).toFuture().getOrThrow(timeout)
}
}

View File

@ -1,5 +1,8 @@
package net.corda.irs.contract
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import net.corda.core.contracts.*
import net.corda.core.contracts.clauses.*
import net.corda.core.crypto.SecureHash
@ -129,6 +132,7 @@ class FixedRatePaymentEvent(date: LocalDate,
* If the rate is null returns a zero payment. // TODO: Is this the desired behaviour?
*/
@CordaSerializable
@JsonIgnoreProperties(ignoreUnknown = true)
class FloatingRatePaymentEvent(date: LocalDate,
accrualStartDate: LocalDate,
accrualEndDate: LocalDate,

View File

@ -1,5 +1,6 @@
package net.corda.irs.contract
import com.fasterxml.jackson.annotation.JsonIgnore
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Tenor
import net.corda.core.serialization.CordaSerializable
@ -63,6 +64,7 @@ open class Rate(val ratioUnit: RatioUnit? = null) {
*/
@CordaSerializable
class FixedRate(ratioUnit: RatioUnit) : Rate(ratioUnit) {
@JsonIgnore
fun isPositive(): Boolean = ratioUnit!!.value > BigDecimal("0.0")
override fun equals(other: Any?) = other?.javaClass == javaClass && super.equals(other)

View File

@ -1,9 +1,10 @@
package net.corda.testing.http
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.common.net.HostAndPort
import java.net.URL
class HttpApi(val root: URL) {
class HttpApi(val root: URL, val mapper: ObjectMapper = defaultMapper) {
/**
* Send a PUT with a payload to the path on the API specified.
*
@ -21,12 +22,15 @@ class HttpApi(val root: URL) {
/**
* 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)
inline fun <reified T : Any> getJson(path: String, params: Map<String, String> = mapOf()) = HttpUtils.getJson<T>(URL(root, path), params, mapper)
private fun toJson(any: Any) = any as? String ?: HttpUtils.defaultMapper.writeValueAsString(any)
companion object {
fun fromHostAndPort(hostAndPort: HostAndPort, base: String, protocol: String = "http"): HttpApi
= HttpApi(URL("$protocol://$hostAndPort/$base/"))
fun fromHostAndPort(hostAndPort: HostAndPort, base: String, protocol: String = "http", mapper: ObjectMapper = defaultMapper): HttpApi
= HttpApi(URL("$protocol://$hostAndPort/$base/"), mapper)
private val defaultMapper: ObjectMapper by lazy {
net.corda.jackson.JacksonSupport.createNonRpcMapper()
}
}
}

View File

@ -35,10 +35,10 @@ object HttpUtils {
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 {
inline fun <reified T : Any> getJson(url: URL, params: Map<String, String> = mapOf(), mapper: ObjectMapper = defaultMapper): 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)
return mapper.readValue(parameterisedUrl, T::class.java)
}
private fun makeRequest(request: Request): Boolean {