mirror of
https://github.com/corda/corda.git
synced 2025-02-23 10:30:24 +00:00
Merge pull request #834 from corda/clint-irswebfix
IRS web demo now shows fixings + general IRS fixes
This commit is contained in:
commit
a6853be035
@ -23,6 +23,7 @@ import net.i2p.crypto.eddsa.EdDSAPublicKey
|
|||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
import java.time.LocalDate
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,6 +81,7 @@ object JacksonSupport {
|
|||||||
addSerializer(SecureHash.SHA256::class.java, SecureHashSerializer)
|
addSerializer(SecureHash.SHA256::class.java, SecureHashSerializer)
|
||||||
addDeserializer(SecureHash::class.java, SecureHashDeserializer())
|
addDeserializer(SecureHash::class.java, SecureHashDeserializer())
|
||||||
addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
|
addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
|
||||||
|
addSerializer(BusinessCalendar::class.java, CalendarSerializer)
|
||||||
addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
|
addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
|
||||||
|
|
||||||
// For ed25519 pubkeys
|
// For ed25519 pubkeys
|
||||||
@ -268,11 +270,30 @@ 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) {
|
||||||
|
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>() {
|
object CalendarDeserializer : JsonDeserializer<BusinessCalendar>() {
|
||||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar {
|
override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar {
|
||||||
return try {
|
return try {
|
||||||
val array = StringArrayDeserializer.instance.deserialize(parser, context)
|
try {
|
||||||
BusinessCalendar.getInstance(*array)
|
val array = StringArrayDeserializer.instance.deserialize(parser, context)
|
||||||
|
BusinessCalendar.getInstance(*array)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
parser.readValueAs(BusinessCalendarWrapper::class.java).toCalendar()
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw JsonParseException(parser, "Invalid calendar(s) ${parser.text}: ${e.message}")
|
throw JsonParseException(parser, "Invalid calendar(s) ${parser.text}: ${e.message}")
|
||||||
}
|
}
|
||||||
|
@ -633,7 +633,7 @@ fun LocalDate.isWorkingDay(accordingToCalendar: BusinessCalendar): Boolean = acc
|
|||||||
* no staff are around to handle problems.
|
* no staff are around to handle problems.
|
||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
open class BusinessCalendar private constructor(val holidayDates: List<LocalDate>) {
|
open class BusinessCalendar(val holidayDates: List<LocalDate>) {
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
class UnknownCalendar(name: String) : Exception("$name not found")
|
class UnknownCalendar(name: String) : Exception("$name not found")
|
||||||
|
|
||||||
|
@ -4,7 +4,9 @@ import com.google.common.net.HostAndPort
|
|||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.node.services.ServiceInfo
|
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_A
|
||||||
import net.corda.core.utilities.DUMMY_BANK_B
|
import net.corda.core.utilities.DUMMY_BANK_B
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
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.apache.commons.io.IOUtils
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import rx.Observable
|
||||||
import rx.observables.BlockingObservable
|
import rx.observables.BlockingObservable
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.time.Duration
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
|
||||||
class IRSDemoTest : IntegrationTestCategory {
|
class IRSDemoTest : IntegrationTestCategory {
|
||||||
val rpcUser = User("user", "password", emptySet())
|
val rpcUser = User("user", "password", emptySet())
|
||||||
val currentDate: LocalDate = LocalDate.now()
|
val currentDate: LocalDate = LocalDate.now()
|
||||||
val futureDate: LocalDate = currentDate.plusMonths(6)
|
val futureDate: LocalDate = currentDate.plusMonths(6)
|
||||||
|
val maxWaitTime: Duration = Duration.of(60, ChronoUnit.SECONDS)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `runs IRS demo`() {
|
fun `runs IRS demo`() {
|
||||||
@ -40,56 +46,67 @@ class IRSDemoTest : IntegrationTestCategory {
|
|||||||
startNode(DUMMY_BANK_B.name)
|
startNode(DUMMY_BANK_B.name)
|
||||||
).getOrThrow()
|
).getOrThrow()
|
||||||
|
|
||||||
|
println("All nodes started")
|
||||||
|
|
||||||
val (controllerAddr, nodeAAddr, nodeBAddr) = Futures.allAsList(
|
val (controllerAddr, nodeAAddr, nodeBAddr) = Futures.allAsList(
|
||||||
startWebserver(controller),
|
startWebserver(controller),
|
||||||
startWebserver(nodeA),
|
startWebserver(nodeA),
|
||||||
startWebserver(nodeB)
|
startWebserver(nodeB)
|
||||||
).getOrThrow().map { it.listenAddress }
|
).getOrThrow().map { it.listenAddress }
|
||||||
|
|
||||||
|
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 nextFixingDates = getFixingDateObservable(nodeA.configuration)
|
||||||
val numADeals = getTradeCount(nodeAAddr)
|
val numADeals = getTradeCount(nodeAApi)
|
||||||
val numBDeals = getTradeCount(nodeBAddr)
|
val numBDeals = getTradeCount(nodeBApi)
|
||||||
|
|
||||||
runUploadRates(controllerAddr)
|
runUploadRates(controllerAddr)
|
||||||
runTrade(nodeAAddr)
|
runTrade(nodeAApi)
|
||||||
|
|
||||||
assertThat(getTradeCount(nodeAAddr)).isEqualTo(numADeals + 1)
|
assertThat(getTradeCount(nodeAApi)).isEqualTo(numADeals + 1)
|
||||||
assertThat(getTradeCount(nodeBAddr)).isEqualTo(numBDeals + 1)
|
assertThat(getTradeCount(nodeBApi)).isEqualTo(numBDeals + 1)
|
||||||
|
assertThat(getFloatingLegFixCount(nodeAApi) == 0)
|
||||||
|
|
||||||
// Wait until the initial trade and all scheduled fixings up to the current date have finished
|
// 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)
|
assertThat(getFloatingLegFixCount(nodeAApi) > 0)
|
||||||
nextFixingDates.first { it == null || it > futureDate }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFixingDateObservable(config: FullNodeConfiguration): BlockingObservable<LocalDate?> {
|
fun getFloatingLegFixCount(nodeApi: HttpApi) = getTrades(nodeApi)[0].calculation.floatingLegPaymentSchedule.count { it.value.rate.ratioUnit != null }
|
||||||
|
|
||||||
|
fun getFixingDateObservable(config: FullNodeConfiguration): Observable<LocalDate?> {
|
||||||
val client = CordaRPCClient(config.rpcAddress!!)
|
val client = CordaRPCClient(config.rpcAddress!!)
|
||||||
val proxy = client.start("user", "password").proxy
|
val proxy = client.start("user", "password").proxy
|
||||||
val vaultUpdates = proxy.vaultAndUpdates().second
|
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>()
|
val irsStates = update.produced.map { it.state.data }.filterIsInstance<InterestRateSwap.State>()
|
||||||
irsStates.mapNotNull { it.calculation.nextFixingDate() }.max()
|
irsStates.mapNotNull { it.calculation.nextFixingDate() }.max()
|
||||||
}.cache().toBlocking()
|
}.cache()
|
||||||
|
|
||||||
return fixingDates
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runDateChange(nodeAddr: HostAndPort) {
|
private fun runDateChange(nodeApi: HttpApi) {
|
||||||
val url = URL("http://$nodeAddr/api/irs/demodate")
|
println("Running date change against ${nodeApi.root}")
|
||||||
assertThat(putJson(url, "\"$futureDate\"")).isTrue()
|
assertThat(nodeApi.putJson("demodate", "\"$futureDate\"")).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runTrade(nodeAddr: HostAndPort) {
|
private fun runTrade(nodeApi: HttpApi) {
|
||||||
|
println("Running trade against ${nodeApi.root}")
|
||||||
val fileContents = loadResourceFile("net/corda/irs/simulation/example-irs-trade.json")
|
val fileContents = loadResourceFile("net/corda/irs/simulation/example-irs-trade.json")
|
||||||
val tradeFile = fileContents.replace("tradeXXX", "trade1")
|
val tradeFile = fileContents.replace("tradeXXX", "trade1")
|
||||||
val url = URL("http://$nodeAddr/api/irs/deals")
|
assertThat(nodeApi.postJson("deals", tradeFile)).isTrue()
|
||||||
assertThat(postJson(url, tradeFile)).isTrue()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runUploadRates(host: HostAndPort) {
|
private fun runUploadRates(host: HostAndPort) {
|
||||||
|
println("Running upload rates against $host")
|
||||||
val fileContents = loadResourceFile("net/corda/irs/simulation/example.rates.txt")
|
val fileContents = loadResourceFile("net/corda/irs/simulation/example.rates.txt")
|
||||||
val url = URL("http://$host/upload/interest-rates")
|
val url = URL("http://$host/upload/interest-rates")
|
||||||
assertThat(uploadFile(url, fileContents)).isTrue()
|
assertThat(uploadFile(url, fileContents)).isTrue()
|
||||||
@ -99,9 +116,19 @@ class IRSDemoTest : IntegrationTestCategory {
|
|||||||
return IOUtils.toString(Thread.currentThread().contextClassLoader.getResourceAsStream(filename), Charsets.UTF_8.name())
|
return IOUtils.toString(Thread.currentThread().contextClassLoader.getResourceAsStream(filename), Charsets.UTF_8.name())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTradeCount(nodeAddr: HostAndPort): Int {
|
private fun getTradeCount(nodeApi: HttpApi): Int {
|
||||||
val api = HttpApi.fromHostAndPort(nodeAddr, "api/irs")
|
println("Getting trade count from ${nodeApi.root}")
|
||||||
val deals = api.getJson<Array<*>>("deals")
|
val deals = nodeApi.getJson<Array<*>>("deals")
|
||||||
return deals.size
|
return deals.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package net.corda.irs.contract
|
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.*
|
||||||
import net.corda.core.contracts.clauses.*
|
import net.corda.core.contracts.clauses.*
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
@ -106,6 +109,7 @@ abstract class RatePaymentEvent(date: LocalDate,
|
|||||||
* Assumes that the rate is valid.
|
* Assumes that the rate is valid.
|
||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
class FixedRatePaymentEvent(date: LocalDate,
|
class FixedRatePaymentEvent(date: LocalDate,
|
||||||
accrualStartDate: LocalDate,
|
accrualStartDate: LocalDate,
|
||||||
accrualEndDate: LocalDate,
|
accrualEndDate: LocalDate,
|
||||||
@ -129,6 +133,7 @@ class FixedRatePaymentEvent(date: LocalDate,
|
|||||||
* If the rate is null returns a zero payment. // TODO: Is this the desired behaviour?
|
* If the rate is null returns a zero payment. // TODO: Is this the desired behaviour?
|
||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
class FloatingRatePaymentEvent(date: LocalDate,
|
class FloatingRatePaymentEvent(date: LocalDate,
|
||||||
accrualStartDate: LocalDate,
|
accrualStartDate: LocalDate,
|
||||||
accrualEndDate: LocalDate,
|
accrualEndDate: LocalDate,
|
||||||
@ -651,6 +656,7 @@ class InterestRateSwap : Contract {
|
|||||||
/**
|
/**
|
||||||
* The state class contains the 4 major data classes.
|
* The state class contains the 4 major data classes.
|
||||||
*/
|
*/
|
||||||
|
@JsonIgnoreProperties("parties", "participants", ignoreUnknown = true)
|
||||||
data class State(
|
data class State(
|
||||||
val fixedLeg: FixedLeg,
|
val fixedLeg: FixedLeg,
|
||||||
val floatingLeg: FloatingLeg,
|
val floatingLeg: FloatingLeg,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package net.corda.irs.contract
|
package net.corda.irs.contract
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.contracts.Tenor
|
import net.corda.core.contracts.Tenor
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
@ -36,6 +38,7 @@ val String.percent: PercentageRatioUnit get() = PercentageRatioUnit(this)
|
|||||||
/**
|
/**
|
||||||
* Parent of the Rate family. Used to denote fixed rates, floating rates, reference rates etc.
|
* Parent of the Rate family. Used to denote fixed rates, floating rates, reference rates etc.
|
||||||
*/
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
open class Rate(val ratioUnit: RatioUnit? = null) {
|
open class Rate(val ratioUnit: RatioUnit? = null) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
@ -63,6 +66,7 @@ open class Rate(val ratioUnit: RatioUnit? = null) {
|
|||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
class FixedRate(ratioUnit: RatioUnit) : Rate(ratioUnit) {
|
class FixedRate(ratioUnit: RatioUnit) : Rate(ratioUnit) {
|
||||||
|
@JsonIgnore
|
||||||
fun isPositive(): Boolean = ratioUnit!!.value > BigDecimal("0.0")
|
fun isPositive(): Boolean = ratioUnit!!.value > BigDecimal("0.0")
|
||||||
|
|
||||||
override fun equals(other: Any?) = other?.javaClass == javaClass && super.equals(other)
|
override fun equals(other: Any?) = other?.javaClass == javaClass && super.equals(other)
|
||||||
|
@ -6,10 +6,12 @@ define([
|
|||||||
'utils/semantic',
|
'utils/semantic',
|
||||||
'utils/dayCountBasisLookup',
|
'utils/dayCountBasisLookup',
|
||||||
'services/NodeApi',
|
'services/NodeApi',
|
||||||
'Deal'
|
'Deal',
|
||||||
|
'services/HttpErrorHandler'
|
||||||
], (angular, maskedInput, semantic, dayCountBasisLookup, nodeApi, Deal) => {
|
], (angular, maskedInput, semantic, dayCountBasisLookup, nodeApi, Deal) => {
|
||||||
angular.module('irsViewer').controller('CreateDealController', function CreateDealController($http, $scope, $location, nodeService) {
|
angular.module('irsViewer').controller('CreateDealController', function CreateDealController($http, $scope, $location, nodeService, httpErrorHandler) {
|
||||||
semantic.init($scope, nodeService.isLoading);
|
semantic.init($scope, nodeService.isLoading);
|
||||||
|
let handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
||||||
|
|
||||||
$scope.dayCountBasisLookup = dayCountBasisLookup;
|
$scope.dayCountBasisLookup = dayCountBasisLookup;
|
||||||
$scope.deal = nodeService.newDeal();
|
$scope.deal = nodeService.newDeal();
|
||||||
@ -17,7 +19,7 @@ define([
|
|||||||
nodeService.createDeal(new Deal($scope.deal))
|
nodeService.createDeal(new Deal($scope.deal))
|
||||||
.then((tradeId) => $location.path('#/deal/' + tradeId), (resp) => {
|
.then((tradeId) => $location.path('#/deal/' + tradeId), (resp) => {
|
||||||
$scope.formError = resp.data;
|
$scope.formError = resp.data;
|
||||||
});
|
}, handleHttpFail);
|
||||||
};
|
};
|
||||||
$('input.percent').mask("9.999999%", {placeholder: "", autoclear: false});
|
$('input.percent').mask("9.999999%", {placeholder: "", autoclear: false});
|
||||||
$('#swapirscolumns').click(() => {
|
$('#swapirscolumns').click(() => {
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define(['angular', 'utils/semantic', 'services/NodeApi'], (angular, semantic, nodeApi) => {
|
define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], (angular, semantic) => {
|
||||||
angular.module('irsViewer').controller('DealController', function DealController($http, $scope, $routeParams, nodeService) {
|
angular.module('irsViewer').controller('DealController', function DealController($http, $scope, $routeParams, nodeService, httpErrorHandler) {
|
||||||
semantic.init($scope, nodeService.isLoading);
|
semantic.init($scope, nodeService.isLoading);
|
||||||
|
let handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
||||||
|
let decorateDeal = (deal) => {
|
||||||
|
let paymentSchedule = deal.calculation.floatingLegPaymentSchedule;
|
||||||
|
Object.keys(paymentSchedule).map((key, index) => {
|
||||||
|
const sign = paymentSchedule[key].rate.positive ? 1 : -1;
|
||||||
|
paymentSchedule[key].ratePercent = paymentSchedule[key].rate.ratioUnit ? (paymentSchedule[key].rate.ratioUnit.value * 100 * sign).toFixed(5) + "%": "";
|
||||||
|
});
|
||||||
|
|
||||||
nodeService.getDeal($routeParams.dealId).then((deal) => $scope.deal = deal);
|
return deal;
|
||||||
|
};
|
||||||
|
|
||||||
|
nodeService.getDeal($routeParams.dealId).then((deal) => $scope.deal = decorateDeal(deal), handleHttpFail);
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,12 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define(['angular', 'utils/semantic', 'services/NodeApi'], (angular, semantic, nodeApi) => {
|
define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], (angular, semantic) => {
|
||||||
angular.module('irsViewer').controller('HomeController', function HomeController($http, $scope, nodeService) {
|
angular.module('irsViewer').controller('HomeController', function HomeController($http, $scope, nodeService, httpErrorHandler) {
|
||||||
semantic.addLoadingModal($scope, nodeService.isLoading);
|
semantic.addLoadingModal($scope, nodeService.isLoading);
|
||||||
|
|
||||||
let handleHttpFail = (resp) => {
|
let handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
||||||
$scope.httpError = resp.data
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.infoMsg = "";
|
$scope.infoMsg = "";
|
||||||
$scope.errorText = "";
|
$scope.errorText = "";
|
||||||
@ -28,7 +26,7 @@ define(['angular', 'utils/semantic', 'services/NodeApi'], (angular, semantic, no
|
|||||||
return name;
|
return name;
|
||||||
};
|
};
|
||||||
|
|
||||||
nodeService.getDate().then((date) => $scope.date = date);
|
nodeService.getDate().then((date) => $scope.date = date, handleHttpFail);
|
||||||
nodeService.getDeals().then((deals) => $scope.deals = deals);
|
nodeService.getDeals().then((deals) => $scope.deals = deals, handleHttpFail);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _) => {
|
||||||
|
angular.module('irsViewer').factory('httpErrorHandler', () => {
|
||||||
|
return {
|
||||||
|
createErrorHandler: (scope) => {
|
||||||
|
return (resp) => {
|
||||||
|
if(resp.status == -1) {
|
||||||
|
scope.httpError = "Could not connect to node web server";
|
||||||
|
} else {
|
||||||
|
scope.httpError = resp.data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
@ -5,6 +5,7 @@ define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => {
|
|||||||
return new (function() {
|
return new (function() {
|
||||||
let date = new Date(2016, 0, 1, 0, 0, 0);
|
let date = new Date(2016, 0, 1, 0, 0, 0);
|
||||||
let curLoading = {};
|
let curLoading = {};
|
||||||
|
let serverAddr = ''; // Leave empty to target the same host this page is served from
|
||||||
|
|
||||||
let load = (type, promise) => {
|
let load = (type, promise) => {
|
||||||
curLoading[type] = true;
|
curLoading[type] = true;
|
||||||
@ -17,19 +18,20 @@ define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let endpoint = (target) => serverAddr + target;
|
||||||
|
|
||||||
let changeDateOnNode = (newDate) => {
|
let changeDateOnNode = (newDate) => {
|
||||||
const dateStr = formatDateForNode(newDate);
|
const dateStr = formatDateForNode(newDate);
|
||||||
let endpoint = '/api/irs/demodate';
|
return load('date', $http.put(endpoint('/api/irs/demodate'), "\"" + dateStr + "\"")).then((resp) => {
|
||||||
return load('date', $http.put(endpoint, "\"" + dateStr + "\"")).then((resp) => {
|
|
||||||
date = newDate;
|
date = newDate;
|
||||||
return this.getDateModel(date);
|
return this.getDateModel(date);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getDate = () => {
|
this.getDate = () => {
|
||||||
return load('date', $http.get('/api/irs/demodate')).then((resp) => {
|
return load('date', $http.get(endpoint('/api/irs/demodate'))).then((resp) => {
|
||||||
const parts = resp.data.split("-");
|
const dateParts = resp.data;
|
||||||
date = new Date(parts[0], parts[1] - 1, parts[2]); // JS uses 0 based months
|
date = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); // JS uses 0 based months
|
||||||
return this.getDateModel(date);
|
return this.getDateModel(date);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -54,13 +56,13 @@ define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.getDeals = () => {
|
this.getDeals = () => {
|
||||||
return load('deals', $http.get('/api/irs/deals')).then((resp) => {
|
return load('deals', $http.get(endpoint('/api/irs/deals'))).then((resp) => {
|
||||||
return resp.data.reverse();
|
return resp.data.reverse();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getDeal = (dealId) => {
|
this.getDeal = (dealId) => {
|
||||||
return load('deal' + dealId, $http.get('/api/irs/deals/' + dealId)).then((resp) => {
|
return load('deal' + dealId, $http.get(endpoint('/api/irs/deals/' + dealId))).then((resp) => {
|
||||||
// Do some data modification to simplify the model
|
// Do some data modification to simplify the model
|
||||||
let deal = resp.data;
|
let deal = resp.data;
|
||||||
deal.fixedLeg.fixedRate.value = (deal.fixedLeg.fixedRate.ratioUnit.value * 100).toString().slice(0, 6);
|
deal.fixedLeg.fixedRate.value = (deal.fixedLeg.fixedRate.ratioUnit.value * 100).toString().slice(0, 6);
|
||||||
@ -87,7 +89,7 @@ define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.createDeal = (deal) => {
|
this.createDeal = (deal) => {
|
||||||
return load('create-deal', $http.post('/api/irs/deals', deal.toJson()))
|
return load('create-deal', $http.post(endpoint('/api/irs/deals'), deal.toJson()))
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
return deal.tradeId;
|
return deal.tradeId;
|
||||||
}, (resp) => {
|
}, (resp) => {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
|
<div class="ui hidden divider"></div>
|
||||||
<div class="ui negative message" id="form-error" ng-show="formError">{{formError}}</div>
|
<div class="ui negative message" id="form-error" ng-show="formError">{{formError}}</div>
|
||||||
|
<div class="ui negative message" id="http-error" ng-show="httpError">{{httpError}}</div>
|
||||||
<h3 class="ui horizontal divider header">
|
<h3 class="ui horizontal divider header">
|
||||||
<i class="list icon"></i>
|
<i class="list icon"></i>
|
||||||
New Deal
|
New Deal
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div class="ui hidden divider"></div>
|
<div class="ui hidden divider"></div>
|
||||||
|
<div class="ui negative message" id="http-error" ng-show="httpError">{{httpError}}</div>
|
||||||
<div class="ui grid">
|
<div class="ui grid">
|
||||||
<div class="sixteen wide column" id="common">
|
<div class="sixteen wide column" id="common">
|
||||||
<table class="ui striped table">
|
<table class="ui striped table">
|
||||||
@ -199,6 +200,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr class="center aligned">
|
||||||
|
<td colspan="2">
|
||||||
|
<div class="ui accordion">
|
||||||
|
<div class="title">
|
||||||
|
<i class="dropdown icon"></i>
|
||||||
|
Fixings
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<table class="ui celled small table">
|
||||||
|
<tbody>
|
||||||
|
<tr class="center aligned" ng-repeat="fixing in deal.calculation.floatingLegPaymentSchedule">
|
||||||
|
<td>{{fixing.fixingDate[0]}}-{{fixing.fixingDate[1]}}-{{fixing.fixingDate[2]}}</td>
|
||||||
|
<td>{{fixing.ratePercent}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
|
<div class="ui hidden divider"></div>
|
||||||
<div class="ui negative message" id="http-error" ng-show="httpError">{{httpError}}</div>
|
<div class="ui negative message" id="http-error" ng-show="httpError">{{httpError}}</div>
|
||||||
<div class="ui info message" id="info-message" ng-show="infoMsg">{{infoMsg}}</div>
|
<div class="ui info message" id="info-message" ng-show="infoMsg">{{infoMsg}}</div>
|
||||||
<div class="ui active dimmer" ng-show="isLoading()">
|
<div class="ui active dimmer" ng-show="isLoading()">
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package net.corda.testing.http
|
package net.corda.testing.http
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import java.net.URL
|
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.
|
* 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.
|
* 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)
|
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", mapper: ObjectMapper = defaultMapper): HttpApi
|
||||||
= HttpApi(URL("$protocol://$hostAndPort/$base/"))
|
= HttpApi(URL("$protocol://$hostAndPort/$base/"), mapper)
|
||||||
|
private val defaultMapper: ObjectMapper by lazy {
|
||||||
|
net.corda.jackson.JacksonSupport.createNonRpcMapper()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,10 +35,10 @@ 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 {
|
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 paramString = if (params.isEmpty()) "" else "?" + params.map { "${it.key}=${it.value}" }.joinToString("&")
|
||||||
val parameterisedUrl = URL(url.toExternalForm() + paramString)
|
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 {
|
private fun makeRequest(request: Request): Boolean {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user