Merge branch 'master' into clint-irsdemowindowsfix

This commit is contained in:
Clinton 2017-01-05 09:51:02 +00:00 committed by GitHub
commit f74fc67737
32 changed files with 397 additions and 290 deletions

View File

@ -6,17 +6,29 @@ buildscript {
// Our version: bump this on release. // Our version: bump this on release.
ext.corda_version = "0.7-SNAPSHOT" ext.corda_version = "0.7-SNAPSHOT"
ext.gradle_plugins_version = props.getProperty("gradlePluginsVersion") ext.gradle_plugins_version = props.getProperty("gradlePluginsVersion")
// Dependency versions. Can run 'gradle dependencyUpdates' to find new versions of things.
//
// TODO: Sort this alphabetically.
ext.kotlin_version = '1.0.5-2' ext.kotlin_version = '1.0.5-2'
ext.quasar_version = '0.7.6' ext.quasar_version = '0.7.6' // TODO: Upgrade to 0.7.7+ when Quasar bug 238 is resolved.
ext.asm_version = '0.5.3' ext.asm_version = '0.5.3'
ext.artemis_version = '1.4.0' ext.artemis_version = '1.5.1'
ext.jackson_version = '2.8.0.rc2' ext.jackson_version = '2.8.5'
ext.jetty_version = '9.3.9.v20160517' ext.jetty_version = '9.3.9.v20160517'
ext.jersey_version = '2.23.1' ext.jersey_version = '2.25'
ext.jolokia_version = '2.0.0-M1' ext.jolokia_version = '2.0.0-M3'
ext.assertj_version = '3.5.1' ext.assertj_version = '3.6.1'
ext.log4j_version = '2.6.2' ext.log4j_version = '2.7'
ext.bouncycastle_version = '1.54' ext.bouncycastle_version = '1.56'
ext.guava_version = '19.0'
ext.quickcheck_version = '0.7'
ext.okhttp_version = '3.5.0'
ext.typesafe_config_version = '1.3.1'
ext.junit_version = '4.12'
ext.jopt_simple_version = '5.0.2'
ext.jansi_version = '1.14'
ext.hibernate_version = '5.2.6.Final'
repositories { repositories {
mavenLocal() mavenLocal()
@ -33,9 +45,7 @@ buildscript {
classpath "net.corda.plugins:publish-utils:$gradle_plugins_version" classpath "net.corda.plugins:publish-utils:$gradle_plugins_version"
classpath "net.corda.plugins:quasar-utils:$gradle_plugins_version" classpath "net.corda.plugins:quasar-utils:$gradle_plugins_version"
classpath "net.corda.plugins:cordformation:$gradle_plugins_version" classpath "net.corda.plugins:cordformation:$gradle_plugins_version"
classpath 'com.github.ben-manes:gradle-versions-plugin:0.13.0'
// Can run 'gradle dependencyUpdates' to find new versions of things.
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
} }
} }
@ -95,7 +105,7 @@ repositories {
// Required for building out the fat JAR. // Required for building out the fat JAR.
dependencies { dependencies {
compile project(':node') compile project(':node')
compile "com.google.guava:guava:19.0" compile "com.google.guava:guava:$guava_version"
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts') runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
} }

View File

@ -5,5 +5,6 @@ repositories {
} }
dependencies { dependencies {
compile "com.google.guava:guava:19.0" // Cannot use ext.guava_version here :(
compile "com.google.guava:guava:20.0"
} }

View File

@ -51,7 +51,7 @@ dependencies {
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
compile "org.apache.logging.log4j:log4j-core:${log4j_version}" compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
compile "com.google.guava:guava:19.0" compile "com.google.guava:guava:$guava_version"
// ReactFX: Functional reactive UI programming. // ReactFX: Functional reactive UI programming.
compile 'org.reactfx:reactfx:2.0-M5' compile 'org.reactfx:reactfx:2.0-M5'
@ -61,13 +61,13 @@ dependencies {
compile "org.apache.activemq:artemis-core-client:${artemis_version}" compile "org.apache.activemq:artemis-core-client:${artemis_version}"
// Unit testing helpers. // Unit testing helpers.
testCompile 'junit:junit:4.12' testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}" testCompile "org.assertj:assertj-core:${assertj_version}"
testCompile project(':test-utils') testCompile project(':test-utils')
// Integration test helpers // Integration test helpers
integrationTestCompile 'junit:junit:4.12' integrationTestCompile "junit:junit:$junit_version"
} }
task integrationTest(type: Test) { task integrationTest(type: Test) {

View File

@ -31,11 +31,11 @@ sourceSets {
} }
dependencies { dependencies {
testCompile 'junit:junit:4.12' testCompile "junit:junit:$junit_version"
testCompile "commons-fileupload:commons-fileupload:1.3.2" testCompile "commons-fileupload:commons-fileupload:1.3.2"
// Guava: Google test library (collections test suite) // Guava: Google test library (collections test suite)
testCompile "com.google.guava:guava-testlib:19.0" testCompile "com.google.guava:guava-testlib:$guava_version"
// Bring in the MockNode infrastructure for writing protocol unit tests. // Bring in the MockNode infrastructure for writing protocol unit tests.
testCompile project(":node") testCompile project(":node")
@ -43,7 +43,7 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.2" compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.3"
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
// Thread safety annotations // Thread safety annotations
@ -56,18 +56,19 @@ dependencies {
// AssertJ: for fluent assertions for testing // AssertJ: for fluent assertions for testing
testCompile "org.assertj:assertj-core:${assertj_version}" testCompile "org.assertj:assertj-core:${assertj_version}"
compile 'com.pholser:junit-quickcheck-core:0.6' compile "com.pholser:junit-quickcheck-core:$quickcheck_version"
compile 'com.pholser:junit-quickcheck-generators:0.6' compile "com.pholser:junit-quickcheck-generators:$quickcheck_version"
// Guava: Google utilities library. // Guava: Google utilities library.
compile "com.google.guava:guava:19.0" compile "com.google.guava:guava:$guava_version"
// RxJava: observable streams of events. // RxJava: observable streams of events.
// TODO: We can't upgrade past 1.1.6 due to a behaviour change in RxJava breaking our code. See PR #99 for discussion. Resolve.
compile "io.reactivex:rxjava:1.1.6" compile "io.reactivex:rxjava:1.1.6"
// Kryo: object graph serialization. // Kryo: object graph serialization.
compile "com.esotericsoftware:kryo:4.0.0" compile "com.esotericsoftware:kryo:4.0.0"
compile "de.javakaffee:kryo-serializers:0.38" compile "de.javakaffee:kryo-serializers:0.41"
// Apache JEXL: An embeddable expression evaluation library. // Apache JEXL: An embeddable expression evaluation library.
// This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API. // This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API.

View File

@ -5,7 +5,7 @@ package net.corda.core.utilities
*/ */
object Emoji { object Emoji {
// Unfortunately only Apple has a terminal that can do colour emoji AND an emoji font installed by default. // Unfortunately only Apple has a terminal that can do colour emoji AND an emoji font installed by default.
val hasEmojiTerminal by lazy { System.getenv("TERM_PROGRAM") == "Apple_Terminal" } val hasEmojiTerminal by lazy { listOf("Apple_Terminal", "iTerm.app").contains(System.getenv("TERM_PROGRAM")) }
const val CODE_SANTA_CLAUS = "\ud83c\udf85" const val CODE_SANTA_CLAUS = "\ud83c\udf85"
const val CODE_DIAMOND = "\ud83d\udd37" const val CODE_DIAMOND = "\ud83d\udd37"

View File

@ -28,6 +28,6 @@ dependencies {
compile project(':core') compile project(':core')
compile project(':finance') compile project(':finance')
testCompile 'junit:junit:4.12' testCompile "junit:junit:$junit_version"
testCompile project(':test-utils') testCompile project(':test-utils')
} }

View File

@ -22,10 +22,10 @@ dependencies {
compile "org.ow2.asm:asm-commons:$asm_version" compile "org.ow2.asm:asm-commons:$asm_version"
// JOptSimple: command line option parsing // JOptSimple: command line option parsing
compile "net.sf.jopt-simple:jopt-simple:5.0.1" compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version"
// Simple Logging Facade: makes the code independent of the chosen logging framework. // Simple Logging Facade: makes the code independent of the chosen logging framework.
compile "org.slf4j:slf4j-api:1.7.21" compile "org.slf4j:slf4j-api:1.7.21"
testCompile group: 'junit', name: 'junit', version: '4.11' testCompile "junit:junit:$junit_version"
} }

View File

@ -21,7 +21,7 @@ dependencies {
compile project(':core') compile project(':core')
testCompile project(':test-utils') testCompile project(':test-utils')
testCompile 'junit:junit:4.12' testCompile "junit:junit:$junit_version"
} }
sourceSets { sourceSets {

View File

@ -16,7 +16,7 @@ buildscript {
dependencies { dependencies {
classpath "net.corda.plugins:publish-utils:$gradle_plugins_version" classpath "net.corda.plugins:publish-utils:$gradle_plugins_version"
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
} }
} }

View File

@ -11,6 +11,7 @@ dependencies {
compile gradleApi() compile gradleApi()
compile localGroovy() compile localGroovy()
compile "com.typesafe:config:1.3.0" // TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:$typesafe_config_version"
} }

View File

@ -66,10 +66,10 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
compile "com.google.guava:guava:19.0" compile "com.google.guava:guava:$guava_version"
// JOpt: for command line flags. // JOpt: for command line flags.
compile "net.sf.jopt-simple:jopt-simple:5.0.2" compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version"
// Artemis: for reliable p2p message queues. // Artemis: for reliable p2p message queues.
compile "org.apache.activemq:artemis-server:${artemis_version}" compile "org.apache.activemq:artemis-server:${artemis_version}"
@ -77,7 +77,7 @@ dependencies {
runtime "org.apache.activemq:artemis-amqp-protocol:${artemis_version}" runtime "org.apache.activemq:artemis-amqp-protocol:${artemis_version}"
// JAnsi: for drawing things to the terminal in nicely coloured ways. // JAnsi: for drawing things to the terminal in nicely coloured ways.
compile "org.fusesource.jansi:jansi:1.13" compile "org.fusesource.jansi:jansi:$jansi_version"
// GraphStream: For visualisation // GraphStream: For visualisation
testCompile "org.graphstream:gs-core:1.3" testCompile "org.graphstream:gs-core:1.3"
@ -95,7 +95,7 @@ dependencies {
compile "org.eclipse.jetty:jetty-servlet:${jetty_version}" compile "org.eclipse.jetty:jetty-servlet:${jetty_version}"
compile "org.eclipse.jetty:jetty-webapp:${jetty_version}" compile "org.eclipse.jetty:jetty-webapp:${jetty_version}"
compile "javax.servlet:javax.servlet-api:3.1.0" compile "javax.servlet:javax.servlet-api:3.1.0"
compile "org.jolokia:jolokia-agent-war:2.0.0-M1" compile "org.jolokia:jolokia-agent-war:$jolokia_version"
compile "commons-fileupload:commons-fileupload:1.3.2" compile "commons-fileupload:commons-fileupload:1.3.2"
// Jersey for JAX-RS implementation for use in Jetty // Jersey for JAX-RS implementation for use in Jetty
@ -121,26 +121,27 @@ dependencies {
compile "com.google.jimfs:jimfs:1.1" compile "com.google.jimfs:jimfs:1.1"
// TypeSafe Config: for simple and human friendly config files. // TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:1.3.0" compile "com.typesafe:config:$typesafe_config_version"
// Unit testing helpers. // Unit testing helpers.
testCompile 'junit:junit:4.12' testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}" testCompile "org.assertj:assertj-core:${assertj_version}"
testCompile 'com.pholser:junit-quickcheck-core:0.6' testCompile "com.pholser:junit-quickcheck-core:$quickcheck_version"
// For H2 database support in persistence // For H2 database support in persistence
compile "com.h2database:h2:1.4.192" compile "com.h2database:h2:1.4.193"
// Exposed: Kotlin SQL library - under evaluation // Exposed: Kotlin SQL library - under evaluation
// TODO: Upgrade to Exposed 0.7 (has API changes)
compile "org.jetbrains.exposed:exposed:0.5.0" compile "org.jetbrains.exposed:exposed:0.5.0"
// SQL connection pooling library // SQL connection pooling library
compile "com.zaxxer:HikariCP:2.4.7" compile "com.zaxxer:HikariCP:2.5.1"
// Hibernate: an object relational mapper for writing state objects to the database automatically. // Hibernate: an object relational mapper for writing state objects to the database automatically.
compile "org.hibernate:hibernate-core:5.2.2.Final" compile "org.hibernate:hibernate-core:$hibernate_version"
compile "org.hibernate:hibernate-java8:5.2.2.Final" compile "org.hibernate:hibernate-java8:$hibernate_version"
// Capsule is a library for building independently executable fat JARs. // Capsule is a library for building independently executable fat JARs.
compile 'co.paralleluniverse:capsule:1.0.3' compile 'co.paralleluniverse:capsule:1.0.3'
@ -151,9 +152,9 @@ dependencies {
compile 'io.atomix.catalyst:catalyst-netty:1.1.1' compile 'io.atomix.catalyst:catalyst-netty:1.1.1'
// Integration test helpers // Integration test helpers
integrationTestCompile 'junit:junit:4.12' integrationTestCompile "junit:junit:$junit_version"
testCompile "com.nhaarman:mockito-kotlin:0.6.1" testCompile "com.nhaarman:mockito-kotlin:1.1.0"
} }
task integrationTest(type: Test) { task integrationTest(type: Test) {

View File

@ -25,47 +25,51 @@ 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 {
SimpleModule("java.time").apply {
addSerializer(LocalDate::class.java, ToStringSerializer)
addDeserializer(LocalDate::class.java, LocalDateDeserializer)
addKeyDeserializer(LocalDate::class.java, LocalDateKeyDeserializer)
addSerializer(LocalDateTime::class.java, ToStringSerializer)
}
}
fun createDefaultMapper(identities: IdentityService): ObjectMapper { val cordaModule : Module by lazy {
val mapper = ServiceHubObjectMapper(identities) SimpleModule("core").apply {
mapper.enable(SerializationFeature.INDENT_OUTPUT) addSerializer(Party::class.java, PartySerializer)
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) addDeserializer(Party::class.java, PartyDeserializer)
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS) addSerializer(BigDecimal::class.java, ToStringSerializer)
addDeserializer(BigDecimal::class.java, NumberDeserializers.BigDecimalDeserializer())
val timeModule = SimpleModule("java.time") addSerializer(SecureHash::class.java, SecureHashSerializer)
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 // 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 // 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()) addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
cordaModule.addDeserializer(BusinessCalendar::class.java, CalendarDeserializer) addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
// For ed25519 pubkeys // For ed25519 pubkeys
cordaModule.addSerializer(EdDSAPublicKey::class.java, PublicKeySerializer) addSerializer(EdDSAPublicKey::class.java, PublicKeySerializer)
cordaModule.addDeserializer(EdDSAPublicKey::class.java, PublicKeyDeserializer) addDeserializer(EdDSAPublicKey::class.java, PublicKeyDeserializer)
// For composite keys // For composite keys
cordaModule.addSerializer(CompositeKey::class.java, CompositeKeySerializer) addSerializer(CompositeKey::class.java, CompositeKeySerializer)
cordaModule.addDeserializer(CompositeKey::class.java, CompositeKeyDeserializer) addDeserializer(CompositeKey::class.java, CompositeKeyDeserializer)
// For NodeInfo // For NodeInfo
// TODO this tunnels the Kryo representation as a Base58 encoded string. Replace when RPC supports this. // TODO this tunnels the Kryo representation as a Base58 encoded string. Replace when RPC supports this.
cordaModule.addSerializer(NodeInfo::class.java, NodeInfoSerializer) addSerializer(NodeInfo::class.java, NodeInfoSerializer)
cordaModule.addDeserializer(NodeInfo::class.java, NodeInfoDeserializer) addDeserializer(NodeInfo::class.java, NodeInfoDeserializer)
}
}
mapper.registerModule(timeModule) fun createDefaultMapper(identities: IdentityService): ObjectMapper =
mapper.registerModule(cordaModule) ServiceHubObjectMapper(identities).apply {
mapper.registerModule(KotlinModule()) enable(SerializationFeature.INDENT_OUTPUT)
return mapper 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()

View File

@ -42,7 +42,7 @@ configurations {
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile group: 'junit', name: 'junit', version: '4.11' testCompile "junit:junit:$junit_version"
// Corda integration dependencies // Corda integration dependencies
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts') runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')

View File

@ -42,7 +42,7 @@ configurations {
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile group: 'junit', name: 'junit', version: '4.11' testCompile "junit:junit:$junit_version"
// Corda integration dependencies // Corda integration dependencies
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts') runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')

View File

@ -45,7 +45,7 @@ configurations {
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile group: 'junit', name: 'junit', version: '4.11' testCompile "junit:junit:$junit_version"
// Corda integration dependencies // Corda integration dependencies
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts') runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
@ -58,7 +58,7 @@ dependencies {
// Cordapp dependencies // Cordapp dependencies
// Specify your cordapp's dependencies below, including dependent cordapps // Specify your cordapp's dependencies below, including dependent cordapps
compile 'com.squareup.okhttp3:okhttp:3.3.1' compile "com.squareup.okhttp3:okhttp:$okhttp_version"
} }
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) { task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {

View File

@ -32,7 +32,7 @@ class IRSDemoTest: IntegrationTestCategory {
private fun runDateChange(nodeAddr: HostAndPort) { private fun runDateChange(nodeAddr: HostAndPort) {
val url = URL("http://$nodeAddr/api/irs/demodate") val url = URL("http://$nodeAddr/api/irs/demodate")
assert(putJson(url, "\"2017-01-02\"")) assert(putJson(url, "\"2017-06-05\""))
} }
private fun runTrade(nodeAddr: HostAndPort) { private fun runTrade(nodeAddr: HostAndPort) {

View File

@ -18,7 +18,7 @@ dependencies {
compile project(':samples:irs-demo') compile project(':samples:irs-demo')
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile group: 'junit', name: 'junit', version: '4.11' testCompile "junit:junit:$junit_version"
// Corda integration dependencies // Corda integration dependencies
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts') runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')

View File

@ -42,7 +42,7 @@ configurations {
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile group: 'junit', name: 'junit', version: '4.11' testCompile "junit:junit:$junit_version"
// Corda integration dependencies // Corda integration dependencies
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts') runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.strata_version = '1.0.0' ext.strata_version = '1.1.2'
} }
apply plugin: 'java' apply plugin: 'java'
@ -34,11 +34,23 @@ 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 {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile group: 'junit', name: 'junit', version: '4.11' testCompile "junit:junit:$junit_version"
// Corda integration dependencies // Corda integration dependencies
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts') runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
@ -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,12 +98,10 @@ 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()
) )
} }
}
/** /**
* Get a list of all current trades in the portfolio. This will not represent an agreed portfolio which is a * Get a list of all current trades in the portfolio. This will not represent an agreed portfolio which is a
@ -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,39 +41,49 @@ 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 {
json { mapOf(
obj(
"tenor" to it.key, "tenor" to it.key,
"rate" to it.value "rate" to it.value
) )
} }
}
) )
}
val fixings = json { val fixings = 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 {
json { mapOf(
obj(
"tenor" to it.key, "tenor" to it.key,
"rate" to it.value "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,
"commodity" to 0.0,
"equity" to 0.0,
"credit" to 0.0,
"total" to valuation.margin.first
),
call = mapOf(
"IRFX" to valuation.margin.first,
"commodity" to 0.0,
"equity" to 0.0,
"credit" to 0.0,
"total" to valuation.margin.first
),
agreed = true)
return ValuationsView(
businessDate = LocalDate.now(),
portfolio = mapOf(
"trades" to tradeCount, "trades" to tradeCount,
"baseCurrency" to currency, "baseCurrency" to currency,
"IRFX" to tradeCount, "IRFX" to tradeCount,
@ -74,74 +93,61 @@ class PortfolioApiUtils(private val ownParty: Party) {
"total" to tradeCount, "total" to tradeCount,
"agreed" to true "agreed" to true
), ),
"marketData" to obj( marketData = mapOf(
"yieldCurves" to yieldCurves, "yieldCurves" to yieldCurves,
"fixings" to fixings, "fixings" to fixings,
"agreed" to true "agreed" to true
), ),
"sensitivities" to obj("curves" to processedSensitivities, sensitivities = mapOf("curves" to processedSensitivities,
"currency" to valuation.currencySensitivies.amounts.toList().map { "currency" to valuation.currencySensitivies.amounts.toList().map {
obj( mapOf(
"currency" to it.currency.code, "currency" to it.currency.code,
"amount" to it.amount "amount" to it.amount
) )
}, },
"agreed" to true "agreed" to true
), ),
"initialMargin" to obj( initialMargin = initialMarginView,
"baseCurrency" to currency, confirmation = mapOf(
"post" to obj(
"IRFX" to valuation.margin.first,
"commodity" to 0,
"equity" to 0,
"credit" to 0,
"total" to valuation.margin.first
),
"call" to obj(
"IRFX" to valuation.margin.first,
"commodity" to 0,
"equity" to 0,
"credit" to 0,
"total" to valuation.margin.first
),
"agreed" to true
),
"confirmation" to obj(
"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 obj( "notional" to mapOf(
"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 obj( "fixedRate" to mapOf(
"value" to fixedRate.rate.initialValue "value" to fixedRate.rate.initialValue
), ),
"paymentRule" to fixedLeg.paymentSchedule.paymentRelativeTo.name, "paymentRule" to fixedLeg.paymentSchedule.paymentRelativeTo.name,
"calendar" to arr("TODO"), "calendar" to listOf("TODO"),
"paymentCalendar" to obj() // TODO "paymentCalendar" to mapOf<String, Any>() // TODO
), ),
"floatingLeg" to obj( floatingLeg = mapOf(
"floatingRatePayer" to state.seller.name, "floatingRatePayer" to state.seller.name,
"notional" to obj( "notional" to mapOf(
"token" to floatingLeg.currency.code, "token" to floatingLeg.currency.code,
"quantity" to floatingLeg.notionalSchedule.amount.initialValue "quantity" to floatingLeg.notionalSchedule.amount.initialValue
), ),
@ -150,23 +156,22 @@ class PortfolioApiUtils(private val ownParty: Party) {
"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 arr("TODO"), "calendar" to listOf("TODO"),
"paymentCalendar" to arr("TODO"), "paymentCalendar" to listOf("TODO"),
"fixingCalendar" to obj() // TODO "fixingCalendar" to mapOf<String, Any>() // TODO
), ),
"common" to obj( common = mapOf(
"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 obj( "interestRate" to mapOf(
"name" to "TODO", "name" to "TODO",
"oracle" to "TODO", "oracle" to "TODO",
"tenor" to obj( "tenor" to mapOf(
"name" to "TODO" "name" to "TODO"
) )
) )
), ),
"ref" to trade.info.id.get().value ref = trade.info.id.get().value
) )
} }
} }
}

View File

@ -42,7 +42,7 @@ configurations {
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile group: 'junit', name: 'junit', version: '4.11' testCompile "junit:junit:$junit_version"
// Corda integration dependencies // Corda integration dependencies
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts') runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')

View File

@ -35,17 +35,17 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
compile "com.google.guava:guava:19.0" compile "com.google.guava:guava:$guava_version"
// Force commons logging to version 1.2 to override Artemis, which pulls in 1.1.3 (ARTEMIS-424) // Force commons logging to version 1.2 to override Artemis, which pulls in 1.1.3 (ARTEMIS-424)
compile "commons-logging:commons-logging:1.2" compile "commons-logging:commons-logging:1.2"
// Unit testing helpers. // Unit testing helpers.
compile 'junit:junit:4.12' compile "junit:junit:$junit_version"
// Guava: Google test library (collections test suite) // Guava: Google test library (collections test suite)
compile "com.google.guava:guava-testlib:19.0" compile "com.google.guava:guava-testlib:19.0"
// OkHTTP: Simple HTTP library. // OkHTTP: Simple HTTP library.
compile 'com.squareup.okhttp3:okhttp:3.3.1' compile "com.squareup.okhttp3:okhttp:$okhttp_version"
} }

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
} }
} }

View File

@ -45,10 +45,10 @@ repositories {
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile group: 'junit', name: 'junit', version: '4.11' testCompile "junit:junit:$junit_version"
// TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's. // TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's.
compile 'no.tornado:tornadofx:1.5.7' compile 'no.tornado:tornadofx:1.5.9'
// Corda Core: Data structures and basic types needed to work with Corda. // Corda Core: Data structures and basic types needed to work with Corda.
compile project(':core') compile project(':core')
@ -57,7 +57,7 @@ dependencies {
compile project(':finance') compile project(':finance')
// FontAwesomeFX: The "FontAwesome" icon library. // FontAwesomeFX: The "FontAwesome" icon library.
compile 'de.jensd:fontawesomefx-fontawesome:4.6.1-2' compile 'de.jensd:fontawesomefx-fontawesome:4.7.0'
// ReactFX: Functional reactive UI programming. // ReactFX: Functional reactive UI programming.
compile 'org.reactfx:reactfx:2.0-M5' compile 'org.reactfx:reactfx:2.0-M5'

View File

@ -35,7 +35,8 @@ dependencies {
// https://mvnrepository.com/artifact/de.danielbechler/java-object-diff // https://mvnrepository.com/artifact/de.danielbechler/java-object-diff
compile group: 'de.danielbechler', name: 'java-object-diff', version: '0.10.2' compile group: 'de.danielbechler', name: 'java-object-diff', version: '0.10.2'
compile "com.typesafe:config:1.3.0" // TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:$typesafe_config_version"
} }
run { run {