diff --git a/build.gradle b/build.gradle index dda451e6bb..5b81750589 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,10 @@ buildscript { ext.jsr305_version = constants.getProperty("jsr305Version") ext.shiro_version = '1.4.0' ext.artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion') + ext.snake_yaml_version = constants.getProperty('snakeYamlVersion') + ext.docker_compose_rule_version = '0.33.0' + ext.selenium_version = '3.8.1' + ext.ghostdriver_version = '2.1.0' // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: ext.java8_minUpdateVersion = '131' diff --git a/constants.properties b/constants.properties index 7744042d3c..b2c39c9ce2 100644 --- a/constants.properties +++ b/constants.properties @@ -1,8 +1,9 @@ -gradlePluginsVersion=3.0.4 +gradlePluginsVersion=3.0.5 kotlinVersion=1.1.60 platformVersion=2 guavaVersion=21.0 bouncycastleVersion=1.57 typesafeConfigVersion=1.3.1 jsr305Version=3.0.2 -artifactoryPluginVersion=4.4.18 \ No newline at end of file +artifactoryPluginVersion=4.4.18 +snakeYamlVersion=1.19 \ No newline at end of file diff --git a/gradle-plugins/build.gradle b/gradle-plugins/build.gradle index 1e04813546..32763f85f6 100644 --- a/gradle-plugins/build.gradle +++ b/gradle-plugins/build.gradle @@ -14,6 +14,7 @@ buildscript { jsr305_version = constants.getProperty("jsr305Version") kotlin_version = constants.getProperty("kotlinVersion") artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion') + snake_yaml_version = constants.getProperty('snakeYamlVersion') } repositories { diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java index 52300f0f63..c3dc01a5da 100644 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java +++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java @@ -20,7 +20,8 @@ public class CordformNode implements NodeDefinition { protected static final String DEFAULT_HOST = "localhost"; /** - * Name of the node. + * Name of the node. Node will be placed in directory based on this name - all lowercase with whitespaces removed. + * Actual node name inside node.conf will be as set here. */ private String name; @@ -28,6 +29,20 @@ public class CordformNode implements NodeDefinition { return name; } + /** + * p2p Port. + */ + private int p2pPort = 10002; + + public int getP2pPort() { return p2pPort; } + + /** + * RPC Port. + */ + private int rpcPort = 10003; + + public int getRpcPort() { return rpcPort; } + /** * Set the RPC users for this node. This configuration block allows arbitrary configuration. * The recommended current structure is: @@ -79,6 +94,7 @@ public class CordformNode implements NodeDefinition { */ public void p2pPort(int p2pPort) { p2pAddress(DEFAULT_HOST + ':' + p2pPort); + this.p2pPort = p2pPort; } /** @@ -110,6 +126,7 @@ public class CordformNode implements NodeDefinition { @Deprecated public void rpcPort(int rpcPort) { rpcAddress(DEFAULT_HOST + ':' + rpcPort); + this.rpcPort = rpcPort; } /** diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/RpcSettings.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/RpcSettings.java index e429bb0ca6..1869733271 100644 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/RpcSettings.java +++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/RpcSettings.java @@ -8,6 +8,17 @@ public final class RpcSettings { private Config config = ConfigFactory.empty(); + private int port = 10003; + private int adminPort = 10005; + + public int getPort() { + return port; + } + + public int getAdminPort() { + return adminPort; + } + /** * RPC address for the node. */ @@ -15,6 +26,14 @@ public final class RpcSettings { setValue("address", value); } + /** + * RPC Port for the node + */ + public final void port(final int value) { + this.port = value; + setValue("address", "localhost:"+port); + } + /** * RPC admin address for the node (necessary if [useSsl] is false or unset). */ @@ -22,6 +41,11 @@ public final class RpcSettings { setValue("adminAddress", value); } + public final void adminPort(final int value) { + this.adminPort = value; + setValue("adminAddress", "localhost:"+adminPort); + } + /** * Specifies whether the node RPC layer will require SSL from clients. */ @@ -43,7 +67,7 @@ public final class RpcSettings { config = options.addTo("ssl", config); } - final Config addTo(final String key, final Config config) { + public final Config addTo(final String key, final Config config) { if (this.config.isEmpty()) { return config; } diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/SslOptions.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/SslOptions.java index da3cc22288..1444d4ed8c 100644 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/SslOptions.java +++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/SslOptions.java @@ -43,7 +43,7 @@ public final class SslOptions { setValue("trustStoreFile", value); } - final Config addTo(final String key, final Config config) { + public final Config addTo(final String key, final Config config) { if (this.config.isEmpty()) { return config; } diff --git a/gradle-plugins/cordformation/build.gradle b/gradle-plugins/cordformation/build.gradle index 6c22f78a9e..5ac1e33473 100644 --- a/gradle-plugins/cordformation/build.gradle +++ b/gradle-plugins/cordformation/build.gradle @@ -40,6 +40,8 @@ dependencies { noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" compile project(':cordform-common') + // Docker-compose file generation + compile "org.yaml:snakeyaml:$snake_yaml_version" } task createNodeRunner(type: Jar, dependsOn: [classes]) { diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Baseform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Baseform.kt new file mode 100644 index 0000000000..ea528aaf08 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Baseform.kt @@ -0,0 +1,174 @@ +package net.corda.plugins + +import groovy.lang.Closure +import net.corda.cordform.CordformDefinition +import org.apache.tools.ant.filters.FixCrLfFilter +import org.gradle.api.DefaultTask +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME +import org.gradle.api.tasks.TaskAction +import java.io.File +import java.lang.reflect.InvocationTargetException +import java.net.URLClassLoader +import java.nio.file.Path +import java.nio.file.Paths +import java.util.jar.JarInputStream + +/** + * Creates nodes based on the configuration of this task in the gradle configuration DSL. + * + * See documentation for examples. + */ +@Suppress("unused") +open class Baseform : DefaultTask() { + private companion object { + val nodeJarName = "corda.jar" + private val defaultDirectory: Path = Paths.get("build", "nodes") + } + + /** + * Optionally the name of a CordformDefinition subclass to which all configuration will be delegated. + */ + @Suppress("MemberVisibilityCanPrivate") + var definitionClass: String? = null + var directory = defaultDirectory + protected val nodes = mutableListOf() + + /** + * Set the directory to install nodes into. + * + * @param directory The directory the nodes will be installed into. + */ + fun directory(directory: String) { + this.directory = Paths.get(directory) + } + + /** + * Add a node configuration. + * + * @param configureClosure A node configuration that will be deployed. + */ + @Suppress("MemberVisibilityCanPrivate") + fun node(configureClosure: Closure) { + nodes += project.configure(Node(project), configureClosure) as Node + } + + /** + * Add a node configuration + * + * @param configureFunc A node configuration that will be deployed + */ + @Suppress("MemberVisibilityCanPrivate") + fun node(configureFunc: Node.() -> Any?): Node { + val node = Node(project).apply { configureFunc() } + nodes += node + return node + } + + /** + * Returns a node by name. + * + * @param name The name of the node as specified in the node configuration DSL. + * @return A node instance. + */ + private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name } + + /** + * The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. + */ + private fun loadCordformDefinition(): CordformDefinition { + val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) + val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath + val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() + return URLClassLoader(urls, CordformDefinition::class.java.classLoader) + .loadClass(definitionClass) + .asSubclass(CordformDefinition::class.java) + .newInstance() + } + + /** + * The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. + */ + private fun loadNetworkBootstrapperClass(): Class<*> { + val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) + val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath + val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() + return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper") + } + + /** + * Installs the corda fat JAR to the root directory, for the network bootstrapper to use. + */ + protected fun installCordaJar() { + val cordaJar = Cordformation.verifyAndGetRuntimeJar(project, "corda") + project.copy { + it.apply { + from(cordaJar) + into(directory) + rename(cordaJar.name, nodeJarName) + fileMode = Cordformation.executableFileMode + } + } + } + + protected fun initializeConfiguration() { + if (definitionClass != null) { + val cd = loadCordformDefinition() + // If the user has specified their own directory (even if it's the same default path) then let them know + // it's not used and should just rely on the one in CordformDefinition + require(directory === defaultDirectory) { + "'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead." + } + directory = cd.nodesDirectory + val cordapps = cd.getMatchingCordapps() + cd.nodeConfigurers.forEach { + val node = node { } + it.accept(node) + node.additionalCordapps.addAll(cordapps) + node.rootDir(directory) + } + cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) } + } else { + nodes.forEach { + it.rootDir(directory) + } + } + } + + protected fun bootstrapNetwork() { + val networkBootstrapperClass = loadNetworkBootstrapperClass() + val networkBootstrapper = networkBootstrapperClass.newInstance() + val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true } + // Call NetworkBootstrapper.bootstrap + try { + val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize() + bootstrapMethod.invoke(networkBootstrapper, rootDir) + } catch (e: InvocationTargetException) { + throw e.cause!! + } + } + + private fun CordformDefinition.getMatchingCordapps(): List { + val cordappJars = project.configuration("cordapp").files + return cordappPackages.map { `package` -> + val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) } + when (cordappsWithPackage.size) { + 0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`") + 1 -> cordappsWithPackage[0] + else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage") + } + } + } + + private fun File.containsPackage(`package`: String): Boolean { + JarInputStream(inputStream()).use { + while (true) { + val name = it.nextJarEntry?.name ?: break + if (name.endsWith(".class") && name.replace('/', '.').startsWith(`package`)) { + return true + } + } + return false + } + } +} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 1d44484eb2..f207dc1818 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -1,18 +1,12 @@ package net.corda.plugins -import groovy.lang.Closure -import net.corda.cordform.CordformDefinition import org.apache.tools.ant.filters.FixCrLfFilter import org.gradle.api.DefaultTask import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME import org.gradle.api.tasks.TaskAction -import java.io.File -import java.lang.reflect.InvocationTargetException -import java.net.URLClassLoader import java.nio.file.Path import java.nio.file.Paths -import java.util.jar.JarInputStream /** * Creates nodes based on the configuration of this task in the gradle configuration DSL. @@ -20,59 +14,12 @@ import java.util.jar.JarInputStream * See documentation for examples. */ @Suppress("unused") -open class Cordform : DefaultTask() { +open class Cordform : Baseform() { private companion object { val nodeJarName = "corda.jar" private val defaultDirectory: Path = Paths.get("build", "nodes") } - /** - * Optionally the name of a CordformDefinition subclass to which all configuration will be delegated. - */ - @Suppress("MemberVisibilityCanPrivate") - var definitionClass: String? = null - private var directory = defaultDirectory - private val nodes = mutableListOf() - - /** - * Set the directory to install nodes into. - * - * @param directory The directory the nodes will be installed into. - */ - fun directory(directory: String) { - this.directory = Paths.get(directory) - } - - /** - * Add a node configuration. - * - * @param configureClosure A node configuration that will be deployed. - */ - @Suppress("MemberVisibilityCanPrivate") - fun node(configureClosure: Closure) { - nodes += project.configure(Node(project), configureClosure) as Node - } - - /** - * Add a node configuration - * - * @param configureFunc A node configuration that will be deployed - */ - @Suppress("MemberVisibilityCanPrivate") - fun node(configureFunc: Node.() -> Any?): Node { - val node = Node(project).apply { configureFunc() } - nodes += node - return node - } - - /** - * Returns a node by name. - * - * @param name The name of the node as specified in the node configuration DSL. - * @return A node instance. - */ - private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name } - /** * Installs the run script into the nodes directory. */ @@ -103,29 +50,6 @@ open class Cordform : DefaultTask() { } } - /** - * The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. - */ - private fun loadCordformDefinition(): CordformDefinition { - val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) - val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath - val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() - return URLClassLoader(urls, CordformDefinition::class.java.classLoader) - .loadClass(definitionClass) - .asSubclass(CordformDefinition::class.java) - .newInstance() - } - - /** - * The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. - */ - private fun loadNetworkBootstrapperClass(): Class<*> { - val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) - val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath - val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() - return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper") - } - /** * This task action will create and install the nodes based on the node configurations added. */ @@ -139,80 +63,4 @@ open class Cordform : DefaultTask() { bootstrapNetwork() nodes.forEach(Node::build) } - - /** - * Installs the corda fat JAR to the root directory, for the network bootstrapper to use. - */ - private fun installCordaJar() { - val cordaJar = Cordformation.verifyAndGetRuntimeJar(project, "corda") - project.copy { - it.apply { - from(cordaJar) - into(directory) - rename(cordaJar.name, nodeJarName) - fileMode = Cordformation.executableFileMode - } - } - } - - private fun initializeConfiguration() { - if (definitionClass != null) { - val cd = loadCordformDefinition() - // If the user has specified their own directory (even if it's the same default path) then let them know - // it's not used and should just rely on the one in CordformDefinition - require(directory === defaultDirectory) { - "'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead." - } - directory = cd.nodesDirectory - val cordapps = cd.getMatchingCordapps() - cd.nodeConfigurers.forEach { - val node = node { } - it.accept(node) - node.additionalCordapps.addAll(cordapps) - node.rootDir(directory) - } - cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) } - } else { - nodes.forEach { - it.rootDir(directory) - } - } - } - - private fun bootstrapNetwork() { - val networkBootstrapperClass = loadNetworkBootstrapperClass() - val networkBootstrapper = networkBootstrapperClass.newInstance() - val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true } - // Call NetworkBootstrapper.bootstrap - try { - val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize() - bootstrapMethod.invoke(networkBootstrapper, rootDir) - } catch (e: InvocationTargetException) { - throw e.cause!! - } - } - - private fun CordformDefinition.getMatchingCordapps(): List { - val cordappJars = project.configuration("cordapp").files - return cordappPackages.map { `package` -> - val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) } - when (cordappsWithPackage.size) { - 0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`") - 1 -> cordappsWithPackage[0] - else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage") - } - } - } - - private fun File.containsPackage(`package`: String): Boolean { - JarInputStream(inputStream()).use { - while (true) { - val name = it.nextJarEntry?.name ?: break - if (name.endsWith(".class") && name.replace('/', '.').startsWith(`package`)) { - return true - } - } - return false - } - } } diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt new file mode 100644 index 0000000000..4e86e5caac --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt @@ -0,0 +1,66 @@ +package net.corda.plugins + +import org.apache.tools.ant.filters.FixCrLfFilter +import org.gradle.api.DefaultTask +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME +import org.gradle.api.tasks.TaskAction +import org.yaml.snakeyaml.DumperOptions +import java.nio.file.Path +import java.nio.file.Paths +import org.yaml.snakeyaml.Yaml +import java.nio.charset.StandardCharsets +import java.nio.file.Files + +/** + * Creates docker-compose file and image definitions based on the configuration of this task in the gradle configuration DSL. + * + * See documentation for examples. + */ +@Suppress("unused") +open class Dockerform : Baseform() { + private companion object { + val nodeJarName = "corda.jar" + private val defaultDirectory: Path = Paths.get("build", "docker") + + private val dockerComposeFileVersion = "3" + + private val yamlOptions = DumperOptions().apply { + indent = 2 + defaultFlowStyle = DumperOptions.FlowStyle.BLOCK + } + private val yaml = Yaml(yamlOptions) + } + + private val directoryPath = project.projectDir.toPath().resolve(directory) + + val dockerComposePath = directoryPath.resolve("docker-compose.yml") + + /** + * This task action will create and install the nodes based on the node configurations added. + */ + @TaskAction + fun build() { + project.logger.info("Running Cordform task") + initializeConfiguration() + nodes.forEach(Node::installDockerConfig) + installCordaJar() + bootstrapNetwork() + nodes.forEach(Node::buildDocker) + + + // Transform nodes path the absolute ones + val services = nodes.map { it.containerName to mapOf( + "build" to directoryPath.resolve(it.nodeDir.name).toAbsolutePath().toString(), + "ports" to listOf(it.rpcPort)) }.toMap() + + + val dockerComposeObject = mapOf( + "version" to dockerComposeFileVersion, + "services" to services) + + val dockerComposeContent = yaml.dump(dockerComposeObject) + + Files.write(dockerComposePath, dockerComposeContent.toByteArray(StandardCharsets.UTF_8)) + } +} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index 5fb670cceb..59f9a7f6f5 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -3,8 +3,10 @@ package net.corda.plugins import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigValueFactory +import com.typesafe.config.ConfigObject import groovy.lang.Closure import net.corda.cordform.CordformNode +import net.corda.cordform.RpcSettings import org.gradle.api.Project import java.io.File import java.nio.charset.StandardCharsets @@ -34,6 +36,11 @@ class Node(private val project: Project) : CordformNode() { private set internal lateinit var rootDir: File private set + internal lateinit var containerName: String + private set + + internal var rpcSettings: RpcSettings = RpcSettings() + private set /** * Sets whether this node will use HTTPS communication. @@ -59,7 +66,7 @@ class Node(private val project: Project) : CordformNode() { * Specifies RPC settings for the node. */ fun rpcSettings(configureClosure: Closure) { - val rpcSettings = project.configure(RpcSettings(project), configureClosure) as RpcSettings + rpcSettings = project.configure(RpcSettings(), configureClosure) as RpcSettings config = rpcSettings.addTo("rpcSettings", config) } @@ -81,6 +88,19 @@ class Node(private val project: Project) : CordformNode() { installCordapps() } + internal fun buildDocker() { + project.copy { + it.apply { + from(Cordformation.getPluginFile(project, "net/corda/plugins/Dockerfile")) + from(Cordformation.getPluginFile(project, "net/corda/plugins/run-corda.sh")) + into("$nodeDir/") + } + } + installAgentJar() + installBuiltCordapp() + installCordapps() + } + internal fun rootDir(rootDir: Path) { if (name == null) { project.logger.error("Node has a null name - cannot create node") @@ -90,8 +110,9 @@ class Node(private val project: Project) : CordformNode() { // with loading our custom X509EdDSAEngine. val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=") val dirName = organizationName ?: name + containerName = dirName.replace("\\s+".toRegex(), "-").toLowerCase() this.rootDir = rootDir.toFile() - nodeDir = File(this.rootDir, dirName) + nodeDir = File(this.rootDir, dirName.replace("\\s", "")) Files.createDirectories(nodeDir.toPath()) } @@ -156,14 +177,14 @@ class Node(private val project: Project) : CordformNode() { } } - private fun createTempConfigFile(): File { + private fun createTempConfigFile(configObject: ConfigObject): File { val options = ConfigRenderOptions .defaults() .setOriginComments(false) .setComments(false) .setFormatted(true) .setJson(false) - val configFileText = config.root().render(options).split("\n").toList() + val configFileText = configObject.render(options).split("\n").toList() // Need to write a temporary file first to use the project.copy, which resolves directories correctly. val tmpDir = File(project.buildDir, "tmp") Files.createDirectories(tmpDir.toPath()) @@ -178,7 +199,27 @@ class Node(private val project: Project) : CordformNode() { */ internal fun installConfig() { configureProperties() - val tmpConfFile = createTempConfigFile() + val tmpConfFile = createTempConfigFile(config.root()) + appendOptionalConfig(tmpConfFile) + project.copy { + it.apply { + from(tmpConfFile) + into(rootDir) + } + } + } + + /** + * Installs the Dockerized configuration file to the root directory and detokenises it. + */ + internal fun installDockerConfig() { + configureProperties() + val dockerConf = config + .withValue("p2pAddress", ConfigValueFactory.fromAnyRef("$containerName:$p2pPort")) + .withValue("rpcSettings.address", ConfigValueFactory.fromAnyRef("$containerName:${rpcSettings.port}")) + .withValue("rpcSettings.adminAddress", ConfigValueFactory.fromAnyRef("$containerName:${rpcSettings.adminPort}")) + .withValue("detectPublicIp", ConfigValueFactory.fromAnyRef(false)) + val tmpConfFile = createTempConfigFile(dockerConf.root()) appendOptionalConfig(tmpConfFile) project.copy { it.apply { diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/RpcSettings.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/RpcSettings.kt deleted file mode 100644 index 2f46f0b2c5..0000000000 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/RpcSettings.kt +++ /dev/null @@ -1,59 +0,0 @@ -package net.corda.plugins - -import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigValueFactory -import groovy.lang.Closure -import org.gradle.api.Project - -class RpcSettings(private val project: Project) { - private var config: Config = ConfigFactory.empty() - - /** - * RPC address for the node. - */ - fun address(value: String) { - config += "address" to value - } - - /** - * RPC admin address for the node (necessary if [useSsl] is false or unset). - */ - fun adminAddress(value: String) { - config += "adminAddress" to value - } - - /** - * Specifies whether the node RPC layer will require SSL from clients. - */ - fun useSsl(value: Boolean) { - config += "useSsl" to value - } - - /** - * Specifies whether the RPC broker is separate from the node. - */ - fun standAloneBroker(value: Boolean) { - config += "standAloneBroker" to value - } - - /** - * Specifies SSL certificates options for the RPC layer. - */ - fun ssl(configureClosure: Closure) { - val sslOptions = project.configure(SslOptions(), configureClosure) as SslOptions - config = sslOptions.addTo("ssl", config) - } - - internal fun addTo(key: String, config: Config): Config { - if (this.config.isEmpty) { - return config - } - return config + (key to this.config.root()) - } -} - -internal operator fun Config.plus(entry: Pair): Config { - - return withValue(entry.first, ConfigValueFactory.fromAnyRef(entry.second)) -} \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/SslOptions.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/SslOptions.kt deleted file mode 100644 index bb8fed288e..0000000000 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/SslOptions.kt +++ /dev/null @@ -1,50 +0,0 @@ -package net.corda.plugins - -import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory - -class SslOptions { - private var config: Config = ConfigFactory.empty() - - /** - * Password for the keystore. - */ - fun keyStorePassword(value: String) { - config += "keyStorePassword" to value - } - - /** - * Password for the truststore. - */ - fun trustStorePassword(value: String) { - config += "trustStorePassword" to value - } - - /** - * Directory under which key stores are to be placed. - */ - fun certificatesDirectory(value: String) { - config += "certificatesDirectory" to value - } - - /** - * Absolute path to SSL keystore. Default: "[certificatesDirectory]/sslkeystore.jks" - */ - fun sslKeystore(value: String) { - config += "sslKeystore" to value - } - - /** - * Absolute path to SSL truststore. Default: "[certificatesDirectory]/truststore.jks" - */ - fun trustStoreFile(value: String) { - config += "trustStoreFile" to value - } - - internal fun addTo(key: String, config: Config): Config { - if (this.config.isEmpty) { - return config - } - return config + (key to this.config.root()) - } -} \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/Dockerfile b/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/Dockerfile new file mode 100644 index 0000000000..66ab277852 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/Dockerfile @@ -0,0 +1,44 @@ +# Base image from (http://phusion.github.io/baseimage-docker) +FROM openjdk:8u151-jre-alpine + +ENV CORDA_VERSION=${BUILDTIME_CORDA_VERSION} +ENV JAVA_OPTIONS=${BUILDTIME_JAVA_OPTIONS} + +# Set image labels +LABEL net.corda.version = ${CORDA_VERSION} \ + maintainer = "" \ + vendor = "R3" + +RUN apk upgrade --update && \ + apk add --update --no-cache bash iputils && \ + rm -rf /var/cache/apk/* && \ + # Add user to run the app && \ + addgroup corda && \ + adduser -G corda -D -s /bin/bash corda && \ + # Create /opt/corda directory && \ + mkdir -p /opt/corda/plugins && \ + mkdir -p /opt/corda/logs + +# Copy corda files +ADD --chown=corda:corda corda.jar /opt/corda/corda.jar +ADD --chown=corda:corda node.conf /opt/corda/node.conf +ADD --chown=corda:corda network-parameters /opt/corda/ +ADD --chown=corda:corda cordapps/ /opt/corda/cordapps +ADD --chown=corda:corda additional-node-infos/ /opt/corda/additional-node-infos +ADD --chown=corda:corda certificates/ /opt/corda/certificates +ADD --chown=corda:corda drivers/ /opt/corda/drivers +ADD --chown=corda:corda persistence* /opt/corda/ + +COPY run-corda.sh /run-corda.sh + +RUN chmod +x /run-corda.sh && \ + sync && \ + chown -R corda:corda /opt/corda + +# Working directory for Corda +WORKDIR /opt/corda +ENV HOME=/opt/corda +USER corda + +# Start it +CMD ["/run-corda.sh"] \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/run-corda.sh b/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/run-corda.sh new file mode 100644 index 0000000000..c10604f859 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/run-corda.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# If variable not present use default values +: ${CORDA_HOME:=/opt/corda} +: ${JAVA_OPTIONS:=-Xmx512m} + +export CORDA_HOME JAVA_OPTIONS + +cd ${CORDA_HOME} +java $JAVA_OPTIONS -jar ${CORDA_HOME}/corda.jar 2>&1 \ No newline at end of file diff --git a/samples/irs-demo/README.md b/samples/irs-demo/README.md index a22d7acaaf..22b3364c4a 100644 --- a/samples/irs-demo/README.md +++ b/samples/irs-demo/README.md @@ -35,3 +35,26 @@ view it. *Note:* The IRS web UI currently has a bug when changing the clock time where it may show no numbers or apply fixings inconsistently. The issues will be addressed in a future milestone release. Meanwhile, you can take a look at a simpler oracle example here: https://github.com/corda/oracle-example. + +## Running the system test + +The system test utilize docker. Amount of RAM required to run the IRS system test is around 2.5GB, it is important +to allocated appropriate system resources (On MacOS/Windows this may require explicit changes to docker configuration) + +### Gradle + +The system test is designed to exercise the entire stack, including Corda nodes and the web frontend. It uses [Docker](https://www.docker.com), [docker-compose](https://docs.docker.com/compose/), and +[PhantomJS](http://phantomjs.org/). Docker and docker-compose need to be installed and configured to be inside the system path +(default installation). PhantomJs binary have to be put in a known location and have execution permission enabled +(``chmod a+x phantomjs`` on Unix) and the full path to the binary exposed as system property named ``phantomjs.binary.path`` or +a system variable named ``PHANTOMJS_BINARY_PATH``. +Having this done, the system test can be run by running the Gradle task ``:samples:irs-demo:systemTest``. + +### Other + +In order to run the the test by other means that the Gradle task - two more system properties are expected - +``CORDAPP_DOCKER_COMPOSE`` and ``WEB_DOCKER_COMPOSE`` which should specify full path docker-compose file for IRS cordapp + and web frontend respectively. Those can be obtained by running ``:samples:irs-demo:cordapp:prepareDockerNodes`` and +``web:generateDockerCompose`` Gradle tasks. ``systemTest`` task simply executes those two and set proper system properties up. + + \ No newline at end of file diff --git a/samples/irs-demo/build.gradle b/samples/irs-demo/build.gradle index 8bfc3785db..c738eb6c48 100644 --- a/samples/irs-demo/build.gradle +++ b/samples/irs-demo/build.gradle @@ -15,6 +15,7 @@ buildscript { // See https://github.com/spring-gradle-plugins/dependency-management-plugin/blob/master/README.md#changing-the-value-of-a-version-property ext['artemis.version'] = "$artemis_version" ext['hibernate.version'] = "$hibernate_version" +ext['selenium.version'] = "$selenium_version" apply plugin: 'java' apply plugin: 'kotlin' @@ -33,12 +34,29 @@ sourceSets { srcDir file('src/integration-test/kotlin') } } + systemTest { + kotlin { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/system-test/kotlin') + } + } } configurations { integrationTestCompile.extendsFrom testCompile integrationTestRuntime.extendsFrom testRuntime demoArtifacts.extendsFrom testRuntime + systemTestCompile.extendsFrom testCompile +} + +repositories { + maven { + url 'https://dl.bintray.com/palantir/releases' // docker-compose-rule is published on bintray + } + repositories { + maven { url 'https://jitpack.io' } + } } dependencies { @@ -55,6 +73,9 @@ dependencies { testCompile "org.assertj:assertj-core:${assertj_version}" integrationTestCompile project(path: ":samples:irs-demo:web", configuration: "demoArtifacts") + testCompile "com.palantir.docker.compose:docker-compose-rule-junit4:$docker_compose_rule_version" + testCompile "org.seleniumhq.selenium:selenium-java:$selenium_version" + testCompile "com.github.detro:ghostdriver:$ghostdriver_version" } bootRepackage { @@ -66,6 +87,22 @@ task integrationTest(type: Test, dependsOn: []) { classpath = sourceSets.integrationTest.runtimeClasspath } +evaluationDependsOn("cordapp") +evaluationDependsOn("web") + +task systemTest(type: Test, dependsOn: ["cordapp:prepareDockerNodes", "web:generateDockerCompose"]) { + testClassesDirs = sourceSets.systemTest.output.classesDirs + classpath = sourceSets.systemTest.runtimeClasspath + + systemProperty "CORDAPP_DOCKER_COMPOSE", tasks.getByPath("cordapp:prepareDockerNodes").dockerComposePath.toString() + systemProperty "WEB_DOCKER_COMPOSE", tasks.getByPath("web:generateDockerCompose").dockerComposePath.toString() + + def phantomJsPath = System.getProperty("phantomjs.binary.path") ?: System.getenv("PHANTOMJS_BINARY_PATH") + if (phantomJsPath != null) { + systemProperty "phantomjs.binary.path", phantomJsPath + } +} + idea { module { downloadJavadoc = true // defaults to false diff --git a/samples/irs-demo/cordapp/build.gradle b/samples/irs-demo/cordapp/build.gradle index 3a2dab78d5..00cf16c846 100644 --- a/samples/irs-demo/cordapp/build.gradle +++ b/samples/irs-demo/cordapp/build.gradle @@ -43,63 +43,96 @@ dependencies { testCompile "org.assertj:assertj-core:${assertj_version}" } +def rpcUsersList = [ + ['username' : "user", + 'password' : "password", + 'permissions' : [ + "StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester", + "StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast", + "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow", + "InvokeRpc.vaultQueryBy", + "InvokeRpc.networkMapSnapshot", + "InvokeRpc.currentNodeTime", + "InvokeRpc.wellKnownPartyFromX500Name" + ]] +] + task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { - ext.rpcUsers = [ - ['username' : "user", - 'password' : "password", - 'permissions' : [ - "StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester", - "StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast", - "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow", - "InvokeRpc.vaultQueryBy", - "InvokeRpc.networkMapSnapshot", - "InvokeRpc.currentNodeTime", - "InvokeRpc.wellKnownPartyFromX500Name" - ]] - ] - - directory "./build/nodes" node { name "O=Notary Service,L=Zurich,C=CH" notary = [validating : true] p2pPort 10002 rpcSettings { - address "localhost:10003" - adminAddress "localhost:10023" + port 10003 + adminPort 10023 } - cordapps = ["${project.group}:finance:$corda_release_version"] - rpcUsers = ext.rpcUsers + cordapps = ["${project(":finance").group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList useTestClock true } node { name "O=Bank A,L=London,C=GB" p2pPort 10005 rpcSettings { - address "localhost:10006" - adminAddress "localhost:10026" + port 10006 + adminPort 10026 } - cordapps = ["${project.group}:finance:$corda_release_version"] - rpcUsers = ext.rpcUsers + cordapps = ["${project(":finance").group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList useTestClock true } node { name "O=Bank B,L=New York,C=US" p2pPort 10008 rpcSettings { - address "localhost:10009" - adminAddress "localhost:10029" + port 10009 + adminPort 10029 } cordapps = ["${project.group}:finance:$corda_release_version"] - rpcUsers = ext.rpcUsers + rpcUsers = rpcUsersList useTestClock true } node { name "O=Regulator,L=Moscow,C=RU" p2pPort 10010 - rpcPort 10011 + rpcSettings { + port 10009 + adminPort 10029 + } cordapps = ["${project.group}:finance:$corda_release_version"] - rpcUsers = ext.rpcUsers + cordapps = ["${project(":finance").group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList + useTestClock true + } + +} + +task prepareDockerNodes(type: net.corda.plugins.Dockerform, dependsOn: ['jar']) { + + node { + name "O=Notary Service,L=Zurich,C=CH" + notary = [validating : true] + cordapps = ["${project(":finance").group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList + useTestClock true + } + node { + name "O=Bank A,L=London,C=GB" + cordapps = ["${project(":finance").group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList + useTestClock true + } + node { + name "O=Bank B,L=New York,C=US" + cordapps = ["${project(":finance").group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList + useTestClock true + } + node { + name "O=Regulator,L=Moscow,C=RU" + cordapps = ["${project.group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList useTestClock true } } diff --git a/samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt b/samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt new file mode 100644 index 0000000000..5f0769f669 --- /dev/null +++ b/samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt @@ -0,0 +1,80 @@ +package net.corda.irs + +import com.palantir.docker.compose.DockerComposeRule +import com.palantir.docker.compose.configuration.DockerComposeFiles +import com.palantir.docker.compose.connection.waiting.HealthChecks +import org.junit.ClassRule +import org.junit.Test +import org.openqa.selenium.By +import org.openqa.selenium.OutputType +import org.openqa.selenium.WebElement +import org.openqa.selenium.phantomjs.PhantomJSDriver +import org.openqa.selenium.support.ui.WebDriverWait +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardCopyOption +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class IRSDemoDockerTest { + companion object { + + private fun ensureSystemVariable(variable: String) { + if (System.getProperty(variable) == null) { + throw IllegalStateException("System variable $variable not set. Please refer to README file for proper setup instructions.") + } + } + + init { + ensureSystemVariable("CORDAPP_DOCKER_COMPOSE") + ensureSystemVariable("WEB_DOCKER_COMPOSE") + ensureSystemVariable("phantomjs.binary.path") + } + + @ClassRule + @JvmField + var docker = DockerComposeRule.builder() + .files(DockerComposeFiles.from( + System.getProperty("CORDAPP_DOCKER_COMPOSE"), + System.getProperty("WEB_DOCKER_COMPOSE"))) + .waitingForService("web-a", HealthChecks.toRespondOverHttp(8080, { port -> port.inFormat("http://\$HOST:\$EXTERNAL_PORT") })) + .build() + } + + @Test + fun `runs IRS demo selenium phantomjs`() { + + + val driver = PhantomJSDriver() + + val webAPort = docker.containers() + .container("web-a") + .port(8080) + + + driver.get("http://${webAPort.ip}:${webAPort.externalPort}"); + + //no deals on fresh interface + val dealRows = driver.findElementsByCssSelector("table#deal-list tbody tr") + assertTrue(dealRows.isEmpty()) + + // Click Angular link and wait for form to appear + val findElementByLinkText = driver.findElementByLinkText("Create Deal") + findElementByLinkText.click() + + val driverWait = WebDriverWait(driver, 120) + + val form = driverWait.until({ + it?.findElement(By.cssSelector("form")) + }) + + form.submit() + + //Wait for deals to appear in a rows table + val dealsList = driverWait.until({ + it?.findElement(By.cssSelector("table#deal-list tbody tr")) + }) + + assertNotNull(dealsList) + } +} diff --git a/samples/irs-demo/web/build.gradle b/samples/irs-demo/web/build.gradle index bad8f8da07..ce592344f6 100644 --- a/samples/irs-demo/web/build.gradle +++ b/samples/irs-demo/web/build.gradle @@ -1,17 +1,28 @@ +import java.nio.charset.StandardCharsets +import java.nio.file.Files + + buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") - classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}") - } + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}") + classpath 'com.bmuschko:gradle-docker-plugin:3.2.1' + classpath "org.yaml:snakeyaml:1.19" + } } +import org.yaml.snakeyaml.DumperOptions + + plugins { - id 'com.craigburke.client-dependencies' version '1.4.0' + id 'com.craigburke.client-dependencies' version '1.4.0' } +group = "${parent.group}.irs-demo" + clientDependencies { registry 'realBower', type:'bower', url:'https://registry.bower.io' realBower { @@ -69,6 +80,8 @@ jar { dependsOn clientInstall } +def docker_dir = file("$project.buildDir/docker") + task deployWebapps(type: Copy, dependsOn: ['jar', 'bootRepackage']) { ext.webappDir = file("build/webapps") @@ -89,4 +102,58 @@ task demoJar(type: Jar) { artifacts { demoArtifacts demoJar +} + +task createDockerfile(type: com.bmuschko.gradle.docker.tasks.image.Dockerfile, dependsOn: [bootRepackage]) { + destFile = file("$docker_dir/Dockerfile") + + from 'azul/zulu-openjdk-alpine:8u152' + copyFile jar.archiveName, "/opt/irs/web/" + workingDir "/opt/irs/web/" + defaultCommand "sh", "-c", "java -Dcorda.host=\$CORDA_HOST -jar ${jar.archiveName}" +} + +task prepareDockerDir(type: Copy, dependsOn: [bootRepackage, createDockerfile]) { + from jar + into docker_dir +} + +task generateDockerCompose(dependsOn: [prepareDockerDir]) { + + def outFile = new File(project.buildDir, "docker-compose.yml") + + ext['dockerComposePath'] = outFile + + doLast { + def dockerComposeObject = [ + "version": "3", + "services": [ + "web-a": [ + "build": "$docker_dir".toString(), + "environment": [ + "CORDA_HOST": "bank-a:10003" + ], + "ports": ["8080"] + ], + "web-b": [ + "build": "$docker_dir".toString(), + "environment": [ + "CORDA_HOST": "bank-b:10003" + ], + "ports": ["8080"] + ] + ] + ] + + + def options = new org.yaml.snakeyaml.DumperOptions() + options.indent = 2 + options.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK + + def dockerComposeContent = new org.yaml.snakeyaml.Yaml(options).dump(dockerComposeObject) + + Files.write(outFile.toPath(), dockerComposeContent.getBytes(StandardCharsets.UTF_8)) + } + + outputs.file(outFile) } \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/IrsDemoWebApplication.kt b/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/IrsDemoWebApplication.kt index b6e81f928f..77d5c31368 100644 --- a/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/IrsDemoWebApplication.kt +++ b/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/IrsDemoWebApplication.kt @@ -6,6 +6,8 @@ import net.corda.client.rpc.CordaRPCClient import net.corda.core.messaging.CordaRPCOps import net.corda.core.utilities.NetworkHostAndPort import net.corda.finance.plugin.registerFinanceJSONMappers +import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException +import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.SpringApplication @@ -29,10 +31,22 @@ class IrsDemoWebApplication { @Value("\${corda.password}") lateinit var cordaPassword:String - @Bean fun rpcClient(): CordaRPCOps { - return CordaRPCClient(NetworkHostAndPort.parse(cordaHost)).start(cordaUser, cordaPassword).proxy + log.info("Connecting to Corda on $cordaHost using username $cordaUser and password $cordaPassword") + // TODO remove this when CordaRPC gets proper connection retry, please + var maxRetries = 100; + do { + try { + return CordaRPCClient(NetworkHostAndPort.parse(cordaHost)).start(cordaUser, cordaPassword).proxy + } catch (ex: ActiveMQNotConnectedException) { + if (maxRetries-- > 0) { + Thread.sleep(1000) + } else { + throw ex + } + } + } while (true) } @Bean @@ -44,6 +58,8 @@ class IrsDemoWebApplication { // running as standalone java app companion object { + private val log = LoggerFactory.getLogger(this::class.java) + @JvmStatic fun main(args: Array) { SpringApplication.run(IrsDemoWebApplication::class.java, *args) } diff --git a/samples/irs-demo/web/src/main/resources/application-NotaryService.properties b/samples/irs-demo/web/src/main/resources/application-NotaryService.properties index e3de4502b7..e424a238ad 100644 --- a/samples/irs-demo/web/src/main/resources/application-NotaryService.properties +++ b/samples/irs-demo/web/src/main/resources/application-NotaryService.properties @@ -1,2 +1,2 @@ corda.host=localhost:10003 -server.port=10004 \ No newline at end of file +server.port=10004 diff --git a/samples/irs-demo/web/src/main/resources/static/js/Deal.js b/samples/irs-demo/web/src/main/resources/static/js/Deal.js index 712ea2d8a2..206870087b 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/Deal.js +++ b/samples/irs-demo/web/src/main/resources/static/js/Deal.js @@ -1,36 +1,32 @@ "use strict"; -define(['viewmodel/FixedRate'], (fixedRateViewModel) => { - let calculationModel = { +define(['viewmodel/FixedRate'], function (fixedRateViewModel) { + var calculationModel = { expression: "( fixedLeg.notional.quantity * (fixedLeg.fixedRate.ratioUnit.value)) - (floatingLeg.notional.quantity * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))", - floatingLegPaymentSchedule: { - - }, - fixedLegPaymentSchedule: { - - } + floatingLegPaymentSchedule: {}, + fixedLegPaymentSchedule: {} }; - let indexLookup = { + var indexLookup = { "GBP": "ICE LIBOR", "USD": "ICE LIBOR", "EUR": "EURIBOR" }; - let calendarLookup = { + var calendarLookup = { "GBP": "London", "USD": "NewYork", "EUR": "London" }; - let Deal = function(dealViewModel) { - let now = new Date(); - let tradeId = `T${now.getUTCFullYear()}-${now.getUTCMonth()}-${now.getUTCDate()}.${now.getUTCHours()}:${now.getUTCMinutes()}:${now.getUTCSeconds()}:${now.getUTCMilliseconds()}`; + var Deal = function Deal(dealViewModel) { + var now = new Date(); + var tradeId = "T" + now.getUTCFullYear() + "-" + now.getUTCMonth() + "-" + now.getUTCDate() + "." + now.getUTCHours() + ":" + now.getUTCMinutes() + ":" + now.getUTCSeconds() + ":" + now.getUTCMilliseconds(); - this.toJson = () => { - let fixedLeg = {}; - let floatingLeg = {}; - let common = {}; + this.toJson = function () { + var fixedLeg = {}; + var floatingLeg = {}; + var common = {}; _.assign(fixedLeg, dealViewModel.fixedLeg); _.assign(floatingLeg, dealViewModel.floatingLeg); _.assign(common, dealViewModel.common); @@ -65,7 +61,7 @@ define(['viewmodel/FixedRate'], (fixedRateViewModel) => { delete common.effectiveDate; delete common.terminationDate; - let json = { + var json = { fixedLeg: fixedLeg, floatingLeg: floatingLeg, calculation: calculationModel, @@ -77,4 +73,4 @@ define(['viewmodel/FixedRate'], (fixedRateViewModel) => { }; }; return Deal; -}); +}); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/app.js b/samples/irs-demo/web/src/main/resources/static/js/app.js index fef33d756e..0e5256d66c 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/app.js +++ b/samples/irs-demo/web/src/main/resources/static/js/app.js @@ -2,23 +2,17 @@ function formatDateForNode(date) { // Produces yyyy-dd-mm. JS is missing proper date formatting libs - let day = ("0" + (date.getDate())).slice(-2); - let month = ("0" + (date.getMonth() + 1)).slice(-2); - return `${date.getFullYear()}-${month}-${day}`; + var day = ("0" + date.getDate()).slice(-2); + var month = ("0" + (date.getMonth() + 1)).slice(-2); + return date.getFullYear() + "-" + month + "-" + day; } function formatDateForAngular(dateStr) { - let parts = dateStr.split("-"); + var parts = dateStr.split("-"); return new Date(parts[0], parts[1], parts[2]); } -define([ - 'angular', - 'angularRoute', - 'jquery', - 'fcsaNumber', - 'semantic' -], (angular, angularRoute, $, fcsaNumber, semantic) => { +define(['angular', 'angularRoute', 'jquery', 'fcsaNumber', 'semantic'], function (angular, angularRoute, $, fcsaNumber, semantic) { angular.module('irsViewer', ['ngRoute', 'fcsa-number']); requirejs(['routes']); }); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js b/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js index 15b7af2381..c021f6b781 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js +++ b/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js @@ -1,34 +1,27 @@ 'use strict'; -define([ - 'angular', - 'maskedInput', - 'utils/semantic', - 'utils/dayCountBasisLookup', - 'services/NodeApi', - 'Deal', - 'services/HttpErrorHandler' -], (angular, maskedInput, semantic, dayCountBasisLookup, nodeApi, Deal) => { +define(['angular', 'maskedInput', 'utils/semantic', 'utils/dayCountBasisLookup', 'services/NodeApi', 'Deal', 'services/HttpErrorHandler'], function (angular, maskedInput, semantic, dayCountBasisLookup, nodeApi, Deal) { angular.module('irsViewer').controller('CreateDealController', function CreateDealController($http, $scope, $location, nodeService, httpErrorHandler) { semantic.init($scope, nodeService.isLoading); - let handleHttpFail = httpErrorHandler.createErrorHandler($scope); + var handleHttpFail = httpErrorHandler.createErrorHandler($scope); $scope.dayCountBasisLookup = dayCountBasisLookup; $scope.deal = nodeService.newDeal(); - $scope.createDeal = () => { - nodeService.createDeal(new Deal($scope.deal)) - .then((tradeId) => $location.path('#/deal/' + tradeId), (resp) => { + $scope.createDeal = function () { + nodeService.createDeal(new Deal($scope.deal)).then(function (tradeId) { + return $location.path('#/deal/' + tradeId); + }, function (resp) { $scope.formError = resp.data; }, handleHttpFail); }; - $('input.percent').mask("9.999999", {placeholder: "", autoclear: false}); - $('#swapirscolumns').click(() => { - let first = $('#irscolumns .irscolumn:eq( 0 )'); - let last = $('#irscolumns .irscolumn:eq( 1 )'); + $('input.percent').mask("9.999999", { placeholder: "", autoclear: false }); + $('#swapirscolumns').click(function () { + var first = $('#irscolumns .irscolumn:eq( 0 )'); + var last = $('#irscolumns .irscolumn:eq( 1 )'); first.before(last); - let swapPayers = () => { - let tmp = $scope.deal.floatingLeg.floatingRatePayer; + var swapPayers = function swapPayers() { + var tmp = $scope.deal.floatingLeg.floatingRatePayer; $scope.deal.floatingLeg.floatingRatePayer = $scope.deal.fixedLeg.fixedRatePayer; $scope.deal.fixedLeg.fixedRatePayer = tmp; }; diff --git a/samples/irs-demo/web/src/main/resources/static/js/controllers/Deal.js b/samples/irs-demo/web/src/main/resources/static/js/controllers/Deal.js index 2ccaca2e9b..48d4f4432d 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/controllers/Deal.js +++ b/samples/irs-demo/web/src/main/resources/static/js/controllers/Deal.js @@ -1,19 +1,21 @@ 'use strict'; -define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], (angular, semantic) => { +define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], function (angular, semantic) { angular.module('irsViewer').controller('DealController', function DealController($http, $scope, $routeParams, nodeService, httpErrorHandler) { 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) + "%": ""; + var handleHttpFail = httpErrorHandler.createErrorHandler($scope); + var decorateDeal = function decorateDeal(deal) { + var paymentSchedule = deal.calculation.floatingLegPaymentSchedule; + Object.keys(paymentSchedule).map(function (key, index) { + var sign = paymentSchedule[key].rate.positive ? 1 : -1; + paymentSchedule[key].ratePercent = paymentSchedule[key].rate.ratioUnit ? (paymentSchedule[key].rate.ratioUnit.value * 100 * sign).toFixed(5) + "%" : ""; }); return deal; }; - nodeService.getDeal($routeParams.dealId).then((deal) => $scope.deal = decorateDeal(deal), handleHttpFail); + nodeService.getDeal($routeParams.dealId).then(function (deal) { + return $scope.deal = decorateDeal(deal); + }, handleHttpFail); }); }); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/controllers/Home.js b/samples/irs-demo/web/src/main/resources/static/js/controllers/Home.js index b92d31f1bc..476a2bff9e 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/controllers/Home.js +++ b/samples/irs-demo/web/src/main/resources/static/js/controllers/Home.js @@ -1,23 +1,23 @@ 'use strict'; -define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], (angular, semantic) => { +define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], function (angular, semantic) { angular.module('irsViewer').controller('HomeController', function HomeController($http, $scope, nodeService, httpErrorHandler) { semantic.addLoadingModal($scope, nodeService.isLoading); - let handleHttpFail = httpErrorHandler.createErrorHandler($scope); + var handleHttpFail = httpErrorHandler.createErrorHandler($scope); $scope.infoMsg = ""; $scope.errorText = ""; $scope.date = { "year": "...", "month": "...", "day": "..." }; - $scope.updateDate = (type) => { - nodeService.updateDate(type).then((newDate) => { - $scope.date = newDate + $scope.updateDate = function (type) { + nodeService.updateDate(type).then(function (newDate) { + $scope.date = newDate; }, handleHttpFail); }; /* Extract the common name from an X500 name */ - $scope.renderX500Name = (x500Name) => { - var name = x500Name - x500Name.split(',').forEach(function(element) { + $scope.renderX500Name = function (x500Name) { + var name = x500Name; + x500Name.split(',').forEach(function (element) { var keyValue = element.split('='); if (keyValue[0].toUpperCase() == 'CN') { name = keyValue[1]; @@ -26,7 +26,11 @@ define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHand return name; }; - nodeService.getDate().then((date) => $scope.date = date, handleHttpFail); - nodeService.getDeals().then((deals) => $scope.deals = deals, handleHttpFail); + nodeService.getDate().then(function (date) { + return $scope.date = date; + }, handleHttpFail); + nodeService.getDeals().then(function (deals) { + return $scope.deals = deals; + }, handleHttpFail); }); -}); +}); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/require-config.js b/samples/irs-demo/web/src/main/resources/static/js/require-config.js index 243e207344..85debee88f 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/require-config.js +++ b/samples/irs-demo/web/src/main/resources/static/js/require-config.js @@ -11,18 +11,14 @@ require.config({ maskedInput: 'bower_components/jquery.maskedinput/jquery.maskedinput' }, shim: { - 'angular' : {'exports' : 'angular'}, + 'angular': { 'exports': 'angular' }, 'angularRoute': ['angular'], 'fcsaNumber': ['angular'], 'semantic': ['jquery'], 'maskedInput': ['jquery'] }, - priority: [ - "angular" - ], - baseUrl: 'js', + priority: ["angular"], + baseUrl: 'js' }); -require(['angular', 'app'], (angular, app) => { - -}); \ No newline at end of file +require(['angular', 'app'], function (angular, app) {}); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/routes.js b/samples/irs-demo/web/src/main/resources/static/js/routes.js index 9f99accfbc..e66b100776 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/routes.js +++ b/samples/irs-demo/web/src/main/resources/static/js/routes.js @@ -1,32 +1,22 @@ 'use strict'; -define([ - 'angular', - 'controllers/Home', - 'controllers/Deal', - 'controllers/CreateDeal' -], (angular) => { - angular.module('irsViewer').config(($routeProvider, $locationProvider) => { - $routeProvider - .when('/', { - controller: 'HomeController', - templateUrl: 'view/home.html' - }) - .when('/deal/:dealId', { - controller: 'DealController', - templateUrl: 'view/deal.html' - }) - .when('/party/:partyId', { - templateUrl: 'view/party.html' - }) - .when('/create-deal', { - controller: 'CreateDealController', - templateUrl: 'view/create-deal.html' - }) - .otherwise({redirectTo: '/'}); +define(['angular', 'controllers/Home', 'controllers/Deal', 'controllers/CreateDeal'], function (angular) { + angular.module('irsViewer').config(function ($routeProvider, $locationProvider) { + $routeProvider.when('/', { + controller: 'HomeController', + templateUrl: 'view/home.html' + }).when('/deal/:dealId', { + controller: 'DealController', + templateUrl: 'view/deal.html' + }).when('/party/:partyId', { + templateUrl: 'view/party.html' + }).when('/create-deal', { + controller: 'CreateDealController', + templateUrl: 'view/create-deal.html' + }).otherwise({ redirectTo: '/' }); }); - angular.element().ready(function() { + angular.element().ready(function () { // bootstrap the app manually angular.bootstrap(document, ['irsViewer']); }); diff --git a/samples/irs-demo/web/src/main/resources/static/js/services/HttpErrorHandler.js b/samples/irs-demo/web/src/main/resources/static/js/services/HttpErrorHandler.js index 0ac757b5f6..e25a076df6 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/services/HttpErrorHandler.js +++ b/samples/irs-demo/web/src/main/resources/static/js/services/HttpErrorHandler.js @@ -1,11 +1,11 @@ 'use strict'; -define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _) => { - angular.module('irsViewer').factory('httpErrorHandler', () => { +define(['angular', 'lodash', 'viewmodel/Deal'], function (angular, _) { + angular.module('irsViewer').factory('httpErrorHandler', function () { return { - createErrorHandler: (scope) => { - return (resp) => { - if(resp.status == -1) { + createErrorHandler: function createErrorHandler(scope) { + return function (resp) { + if (resp.status == -1) { scope.httpError = "Could not connect to node web server"; } else { scope.httpError = resp.data; diff --git a/samples/irs-demo/web/src/main/resources/static/js/services/NodeApi.js b/samples/irs-demo/web/src/main/resources/static/js/services/NodeApi.js index 1cfdf401c5..2a068ea7f2 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/services/NodeApi.js +++ b/samples/irs-demo/web/src/main/resources/static/js/services/NodeApi.js @@ -1,44 +1,48 @@ 'use strict'; -define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => { - angular.module('irsViewer').factory('nodeService', ($http) => { - return new (function() { - let date = new Date(2016, 0, 1, 0, 0, 0); - let curLoading = {}; - let serverAddr = ''; // Leave empty to target the same host this page is served from +define(['angular', 'lodash', 'viewmodel/Deal'], function (angular, _, dealViewModel) { + angular.module('irsViewer').factory('nodeService', function ($http) { + return new function () { + var _this = this; - let load = (type, promise) => { + var date = new Date(2016, 0, 1, 0, 0, 0); + var curLoading = {}; + var serverAddr = ''; // Leave empty to target the same host this page is served from + + var load = function load(type, promise) { curLoading[type] = true; - return promise.then((arg) => { + return promise.then(function (arg) { curLoading[type] = false; return arg; - }, (arg) => { + }, function (arg) { curLoading[type] = false; throw arg; }); }; - let endpoint = (target) => serverAddr + target; + var endpoint = function endpoint(target) { + return serverAddr + target; + }; - let changeDateOnNode = (newDate) => { - const dateStr = formatDateForNode(newDate); - return load('date', $http.put(endpoint('/api/irs/demodate'), "\"" + dateStr + "\"")).then((resp) => { + var changeDateOnNode = function changeDateOnNode(newDate) { + var dateStr = formatDateForNode(newDate); + return load('date', $http.put(endpoint('/api/irs/demodate'), "\"" + dateStr + "\"")).then(function (resp) { date = newDate; - return this.getDateModel(date); + return _this.getDateModel(date); }); }; - this.getDate = () => { - return load('date', $http.get(endpoint('/api/irs/demodate'))).then((resp) => { - const dateParts = resp.data; + this.getDate = function () { + return load('date', $http.get(endpoint('/api/irs/demodate'))).then(function (resp) { + var dateParts = resp.data; date = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); // JS uses 0 based months - return this.getDateModel(date); + return _this.getDateModel(date); }); }; - this.updateDate = (type) => { - let newDate = date; - switch(type) { + this.updateDate = function (type) { + var newDate = date; + switch (type) { case "year": newDate.setFullYear(date.getFullYear() + 1); break; @@ -55,22 +59,22 @@ define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => { return changeDateOnNode(newDate); }; - this.getDeals = () => { - return load('deals', $http.get(endpoint('/api/irs/deals'))).then((resp) => { + this.getDeals = function () { + return load('deals', $http.get(endpoint('/api/irs/deals'))).then(function (resp) { return resp.data.reverse(); }); }; - this.getDeal = (dealId) => { - return load('deal' + dealId, $http.get(endpoint('/api/irs/deals/' + dealId))).then((resp) => { + this.getDeal = function (dealId) { + return load('deal' + dealId, $http.get(endpoint('/api/irs/deals/' + dealId))).then(function (resp) { // Do some data modification to simplify the model - let deal = resp.data; + var deal = resp.data; deal.fixedLeg.fixedRate.value = (deal.fixedLeg.fixedRate.ratioUnit.value * 100).toString().slice(0, 6); return deal; }); }; - this.getDateModel = (date) => { + this.getDateModel = function (date) { return { "year": date.getFullYear(), "month": date.getMonth() + 1, // JS uses 0 based months @@ -78,24 +82,23 @@ define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => { }; }; - this.isLoading = () => { - return _.reduce(Object.keys(curLoading), (last, key) => { - return (last || curLoading[key]); + this.isLoading = function () { + return _.reduce(Object.keys(curLoading), function (last, key) { + return last || curLoading[key]; }, false); }; - this.newDeal = () => { + this.newDeal = function () { return dealViewModel; }; - this.createDeal = (deal) => { - return load('create-deal', $http.post(endpoint('/api/irs/deals'), deal.toJson())) - .then((resp) => { + this.createDeal = function (deal) { + return load('create-deal', $http.post(endpoint('/api/irs/deals'), deal.toJson())).then(function (resp) { return deal.tradeId; - }, (resp) => { + }, function (resp) { throw resp; - }) - } - }); + }); + }; + }(); }); }); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/utils/dayCountBasisLookup.js b/samples/irs-demo/web/src/main/resources/static/js/utils/dayCountBasisLookup.js index be80477ac2..18a3f04625 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/utils/dayCountBasisLookup.js +++ b/samples/irs-demo/web/src/main/resources/static/js/utils/dayCountBasisLookup.js @@ -1,6 +1,6 @@ 'use strict'; -define([], () => { +define([], function () { return { "30/360": { "day": "D30", @@ -29,6 +29,6 @@ define([], () => { "ACT/ACT ICMA": { "day": "DActual", "year": "YICMA" - }, + } }; }); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/utils/semantic.js b/samples/irs-demo/web/src/main/resources/static/js/utils/semantic.js index c4874f922b..6b1e9746d1 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/utils/semantic.js +++ b/samples/irs-demo/web/src/main/resources/static/js/utils/semantic.js @@ -1,17 +1,17 @@ 'use strict'; -define(['jquery', 'semantic'], ($, semantic) => { +define(['jquery', 'semantic'], function ($, semantic) { return { - init: function($scope, loadingFunc) { + init: function init($scope, loadingFunc) { $('.ui.accordion').accordion(); $('.ui.dropdown').dropdown(); $('.ui.sticky').sticky(); this.addLoadingModal($scope, loadingFunc); }, - addLoadingModal: ($scope, loadingFunc) => { - $scope.$watch(loadingFunc, (newVal) => { - if(newVal === true) { + addLoadingModal: function addLoadingModal($scope, loadingFunc) { + $scope.$watch(loadingFunc, function (newVal) { + if (newVal === true) { $('#loading').modal('setting', 'closable', false).modal('show'); } else { $('#loading').modal('hide'); diff --git a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Common.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Common.js index c6e58ffbc2..2744d3b926 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Common.js +++ b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Common.js @@ -1,6 +1,6 @@ 'use strict'; -define([], () => { +define([], function () { return { baseCurrency: "USD", effectiveDate: new Date(2016, 2, 11), @@ -31,7 +31,7 @@ define([], () => { }, addressForTransfers: "", exposure: {}, - localBusinessDay: [ "London" , "NewYork" ], + localBusinessDay: ["London", "NewYork"], dailyInterestAmount: "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360", hashLegalDocs: "put hash here" }; diff --git a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Deal.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Deal.js index 54d4e99783..61d61ef326 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Deal.js +++ b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Deal.js @@ -1,6 +1,6 @@ 'use strict'; -define(['viewmodel/FixedLeg', 'viewmodel/FloatingLeg', 'viewmodel/Common'], (fixedLeg, floatingLeg, common) => { +define(['viewmodel/FixedLeg', 'viewmodel/FloatingLeg', 'viewmodel/Common'], function (fixedLeg, floatingLeg, common) { return { fixedLeg: fixedLeg, floatingLeg: floatingLeg, diff --git a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FixedLeg.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FixedLeg.js index 30cfd34650..b143542fd0 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FixedLeg.js +++ b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FixedLeg.js @@ -1,6 +1,6 @@ 'use strict'; -define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => { +define(['utils/dayCountBasisLookup'], function (dayCountBasisLookup) { return { fixedRatePayer: "O=Bank A,L=London,C=GB", notional: 2500000000, @@ -15,4 +15,4 @@ define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => { paymentDelay: "0", interestPeriodAdjustment: "Adjusted" }; -}); +}); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FloatingLeg.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FloatingLeg.js index a2fb24778d..38d0845c5f 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FloatingLeg.js +++ b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FloatingLeg.js @@ -1,6 +1,6 @@ 'use strict'; -define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => { +define(['utils/dayCountBasisLookup'], function (dayCountBasisLookup) { return { floatingRatePayer: "O=Bank B,L=New York,C=US", notional: 2500000000, @@ -20,7 +20,7 @@ define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => { fixingsPerPayment: "Quarterly", indexSource: "Rates Service Provider", indexTenor: { - name: "3M" + name: "3M" } }; -}); +}); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/view/home.html b/samples/irs-demo/web/src/main/resources/static/view/home.html index 65610fd1f4..7dc3f8af57 100644 --- a/samples/irs-demo/web/src/main/resources/static/view/home.html +++ b/samples/irs-demo/web/src/main/resources/static/view/home.html @@ -34,7 +34,7 @@ Recent deals - +
@@ -45,7 +45,7 @@ - +
Trade Id
{{deal.ref}} {{renderX500Name(deal.fixedLeg.fixedRatePayer)}} {{deal.fixedLeg.notional}}