Skip to content

Commit

Permalink
Remove finance dependencies
Browse files Browse the repository at this point in the history
Re-enable code now DealState PR is in.

Add plugable JSON serialisation

Add docs for new plugin api.

Move parseCurrency back to core to prevent dependency issues with crash shell parsing.

Use :finance module as a proper CorDapp

Move parseCurrency back onto Amount companion.

Fix smoke tests

Fixup after merge.
  • Loading branch information
Matthew Nesbit committed Aug 18, 2017
1 parent 4278bce commit 8ec33d6
Show file tree
Hide file tree
Showing 30 changed files with 285 additions and 173 deletions.
1 change: 0 additions & 1 deletion client/jackson/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ apply plugin: 'com.jfrog.artifactory'

dependencies {
compile project(':core')
compile project(':finance')
testCompile project(':test-utils')

compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.*
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers
import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import net.corda.contracts.BusinessCalendar
import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
Expand All @@ -29,12 +27,10 @@ import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.finance.parseCurrency
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.*

/**
Expand Down Expand Up @@ -84,8 +80,6 @@ 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
addSerializer(EdDSAPublicKey::class.java, PublicKeySerializer)
Expand Down Expand Up @@ -277,36 +271,6 @@ 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>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar {
return try {
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}")
}
}
}

object PublicKeySerializer : JsonSerializer<EdDSAPublicKey>() {
override fun serialize(obj: EdDSAPublicKey, generator: JsonGenerator, provider: SerializerProvider) {
check(obj.params == Crypto.EDDSA_ED25519_SHA512.algSpec)
Expand Down Expand Up @@ -349,7 +313,7 @@ object JacksonSupport {
object AmountDeserializer : JsonDeserializer<Amount<*>>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Amount<*> {
try {
return parseCurrency(parser.text)
return Amount.parseCurrency(parser.text)
} catch (e: Exception) {
try {
val tree = parser.readValueAsTree<JsonNode>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.generateKeyPair
import net.corda.core.transactions.SignedTransaction
import net.corda.finance.parseCurrency
import net.corda.testing.ALICE_PUBKEY
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.MINI_CORP
Expand Down Expand Up @@ -53,7 +52,7 @@ class JacksonSupportTest : TestDependencyInjectionBase() {
@Test
fun writeAmount() {
val writer = mapper.writer().without(SerializationFeature.INDENT_OUTPUT)
assertEquals("""{"notional":"25000000.00 USD"}""", writer.writeValueAsString(Dummy(parseCurrency("$25000000"))))
assertEquals("""{"notional":"25000000.00 USD"}""", writer.writeValueAsString(Dummy(Amount.parseCurrency("$25000000"))))
}

@Test
Expand Down
3 changes: 3 additions & 0 deletions client/rpc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ processSmokeTestResources {
from(project(':node:capsule').tasks['buildCordaJAR']) {
rename 'corda-(.*)', 'corda.jar'
}
from(project(':finance').tasks['jar']) {
rename 'finance-(.*)', 'finance.jar'
}
}

// To find potential version conflicts, run "gradle htmlDependencyReport" and then look in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

import static kotlin.test.AssertionsKt.assertEquals;
import static kotlin.test.AssertionsKt.fail;
import static net.corda.contracts.GetBalances.getCashBalance;

public class StandaloneCordaRPCJavaClientTest {
Expand All @@ -32,6 +38,7 @@ public class StandaloneCordaRPCJavaClientTest {

private AtomicInteger port = new AtomicInteger(15000);

private NodeProcess.Factory factory;
private NodeProcess notary;
private CordaRPCOps rpcProxy;
private CordaRPCConnection connection;
Expand All @@ -49,7 +56,9 @@ public class StandaloneCordaRPCJavaClientTest {

@Before
public void setUp() {
notary = new NodeProcess.Factory().create(notaryConfig);
factory = new NodeProcess.Factory();
copyFinanceCordapp();
notary = factory.create(notaryConfig);
connection = notary.connect();
rpcProxy = connection.getProxy();
notaryNode = fetchNotaryIdentity();
Expand All @@ -64,6 +73,28 @@ public void done() {
}
}

private void copyFinanceCordapp() {
Path pluginsDir = (factory.baseDirectory(notaryConfig).resolve("plugins"));
try {
Files.createDirectories(pluginsDir);
} catch (IOException ex) {
fail("Failed to create directories");
}
try (Stream<Path> paths = Files.walk(Paths.get("build", "resources", "smokeTest"))) {
paths.forEach(file -> {
if (file.toString().contains("corda-finance")) {
try {
Files.copy(file, pluginsDir.resolve(file.getFileName()));
} catch (IOException ex) {
fail("Failed to copy finance jar");
}
}
});
} catch (IOException e) {
fail("Failed to walk files");
}
}

private NodeInfo fetchNotaryIdentity() {
List<NodeInfo> nodeDataSnapshot = rpcProxy.networkMapSnapshot();
return nodeDataSnapshot.get(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import net.corda.contracts.asset.Cash
import net.corda.contracts.getCashBalance
import net.corda.contracts.getCashBalances
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.InputStreamAndHash
import net.corda.core.internal.*
import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.Vault
Expand All @@ -32,8 +32,10 @@ import org.junit.Before
import org.junit.Test
import java.io.FilterInputStream
import java.io.InputStream
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.streams.toList
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
Expand All @@ -43,11 +45,12 @@ class StandaloneCordaRPClientTest {
private companion object {
val log = loggerFor<StandaloneCordaRPClientTest>()
val user = User("user1", "test", permissions = setOf("ALL"))
val port = AtomicInteger(15000)
val port = AtomicInteger(15200)
const val attachmentSize = 2116
val timeout = 60.seconds
}

private lateinit var factory: NodeProcess.Factory
private lateinit var notary: NodeProcess
private lateinit var rpcProxy: CordaRPCOps
private lateinit var connection: CordaRPCConnection
Expand All @@ -64,7 +67,9 @@ class StandaloneCordaRPClientTest {

@Before
fun setUp() {
notary = NodeProcess.Factory().create(notaryConfig)
factory = NodeProcess.Factory()
copyFinanceCordapp()
notary = factory.create(notaryConfig)
connection = notary.connect()
rpcProxy = connection.proxy
notaryNode = fetchNotaryIdentity()
Expand All @@ -79,6 +84,15 @@ class StandaloneCordaRPClientTest {
}
}

private fun copyFinanceCordapp() {
val pluginsDir = (factory.baseDirectory(notaryConfig) / "plugins").createDirectories()
// Find the finance jar file for the smoke tests of this module
val financeJar = Paths.get("build", "resources", "smokeTest").list {
it.filter { "corda-finance" in it.toString() }.toList().single()
}
financeJar.copyToDirectory(pluginsDir)
}

@Test
fun `test attachments`() {
val attachment = InputStreamAndHash.createInMemoryTestZip(attachmentSize, 1)
Expand Down Expand Up @@ -189,6 +203,7 @@ class StandaloneCordaRPClientTest {
@Test
fun `test cash balances`() {
val startCash = rpcProxy.getCashBalances()
println(startCash)
assertTrue(startCash.isEmpty(), "Should not start with any cash")

val flowHandle = rpcProxy.startFlow(::CashIssueFlow, 629.DOLLARS, OpaqueBytes.of(0), notaryNode.legalIdentity)
Expand Down
64 changes: 64 additions & 0 deletions core/src/main/kotlin/net/corda/core/contracts/Amount.kt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,70 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
*/
@JvmStatic
fun <T : Any> Iterable<Amount<T>>.sumOrZero(token: T) = if (iterator().hasNext()) sumOrThrow() else Amount.zero(token)

private val currencySymbols: Map<String, Currency> = mapOf(
"$" to Currency.getInstance("USD"),
"£" to Currency.getInstance("GBP"),
"" to Currency.getInstance("EUR"),
"¥" to Currency.getInstance("JPY"),
"" to Currency.getInstance("RUB")
)

private val currencyCodes: Map<String, Currency> by lazy {
Currency.getAvailableCurrencies().associateBy { it.currencyCode }
}

/**
* Returns an amount that is equal to the given currency amount in text. Examples of what is supported:
*
* - 12 USD
* - 14.50 USD
* - 10 USD
* - 30 CHF
* - $10.24
* - £13
* - €5000
*
* Note this method does NOT respect internationalisation rules: it ignores commas and uses . as the
* decimal point separator, always. It also ignores the users locale:
*
* - $ is always USD,
* - £ is always GBP
* - € is always the Euro
* - ¥ is always Japanese Yen.
* - ₽ is always the Russian ruble.
*
* Thus an input of $12 expecting some other countries dollar will not work. Do your own parsing if
* you need correct handling of currency amounts with locale-sensitive handling.
*
* @throws IllegalArgumentException if the input string was not understood.
*/
@JvmStatic
fun parseCurrency(input: String): Amount<Currency> {
val i = input.filter { it != ',' }
try {
// First check the symbols at the front.
for ((symbol, currency) in currencySymbols) {
if (i.startsWith(symbol)) {
val rest = i.substring(symbol.length)
return Amount.fromDecimal(BigDecimal(rest), currency)
}
}
// Now check the codes at the end.
val split = i.split(' ')
if (split.size == 2) {
val (rest, code) = split
for ((cc, currency) in currencyCodes) {
if (cc == code) {
return Amount.fromDecimal(BigDecimal(rest), currency)
}
}
}
} catch(e: Exception) {
throw IllegalArgumentException("Could not parse $input as a currency", e)
}
throw IllegalArgumentException("Did not recognise the currency in $input or could not parse")
}
}

init {
Expand Down
5 changes: 5 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ UNRELEASED
no longer auto-suggests these extension functions in completion unless you add import lines for them yourself
(this is Kotlin IDE bug KT-15286).

* ``:finance`` module now acting as a CorDapp with regard to flow registration, schemas and serializable types.

* ``WebServerPluginRegistry`` now has a ``customizeJSONSerialization`` which can be overridden to extend the REST JSON
serializers. In particular the IRS demos must now register the ``BusinessCalendar`` serializers.

Milestone 14
------------

Expand Down
2 changes: 2 additions & 0 deletions docs/source/writing-cordapps.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ The ``WebServerPluginRegistry`` class defines the following:
be distributed within the CorDapp jars. This static content will not be available if the bundled web server is not
started

* ``customizeJSONSerialization``, which can be overridden to register custom JSON serializers if required by the REST api.

* The static web content itself should be placed inside the ``src/main/resources`` directory

To learn about how to use gradle to build your cordapp against Corda and generate an artifact please read
Expand Down
7 changes: 5 additions & 2 deletions finance/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ apply plugin: 'kotlin-jpa'
apply plugin: CanonicalizerPlugin
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'com.jfrog.artifactory'

description 'Corda finance modules'

dependencies {
compile project(':core')
compile project(':node-schemas')
// Note the :finance module is a CorDapp in its own right
// and CorDapps using :finance features should use 'cordapp' not 'compile' linkage.
cordaCompile project(':core')
cordaCompile project(':node-schemas')

testCompile project(':test-utils')
testCompile project(path: ':core', configuration: 'testArtifacts')
Expand Down
Loading

0 comments on commit 8ec33d6

Please sign in to comment.