diff --git a/bin/multinodes.sh b/bin/multinodes.sh
new file mode 100755
index 00000000..c5b7219c
--- /dev/null
+++ b/bin/multinodes.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+CURRENT=`pwd`
+GETH_NODE_PORT=30303
+GETH_HTTP_PORT=8102
+CONSTELLATION_PORT=9000
+CAKESHOP_PORT=8080
+NODES=("node1" "node2" "node3" "node4")
+
+
+case "$1" in
+ start)
+ for i in "${NODES[@]}"
+ do :
+ mkdir -p "$CURRENT/$i"
+ cp -n cakeshop.war "$CURRENT/$i"
+ cd "$CURRENT/$i"
+ nohup java -Dserver.port=$CAKESHOP_PORT -Dgeth.url=http://localhost:$GETH_HTTP_PORT -Dgeth.node.port=$GETH_NODE_PORT -Dgeth.constellaiton.url=http://127.0.0.1:$CONSTELLATION_PORT -jar cakeshop.war &>/dev/null &
+ sleep 5
+ GETH_HTTP_PORT=$(( GETH_HTTP_PORT+1 ))
+ GETH_NODE_PORT=$(( GETH_NODE_PORT+1 ))
+ CONSTELLATION_PORT=$(( CONSTELLATION_PORT+1 ))
+ CAKESHOP_PORT=$(( CAKESHOP_PORT+1 ))
+ cd ../
+ done
+ ;;
+
+ stop)
+ killall constellation-node
+ killall java
+ ;;
+
+ esac
diff --git a/cakeshop-abi/pom.xml b/cakeshop-abi/pom.xml
index 4398f1e6..ae4db867 100644
--- a/cakeshop-abi/pom.xml
+++ b/cakeshop-abi/pom.xml
@@ -6,7 +6,7 @@
com.jpmorgancakeshop-parent
- 0.9.1
+ 0.10.0cakeshop-abi
diff --git a/cakeshop-abi/src/main/java/com/jpmorgan/cakeshop/model/ContractABI.java b/cakeshop-abi/src/main/java/com/jpmorgan/cakeshop/model/ContractABI.java
index e2c14900..200bd850 100644
--- a/cakeshop-abi/src/main/java/com/jpmorgan/cakeshop/model/ContractABI.java
+++ b/cakeshop-abi/src/main/java/com/jpmorgan/cakeshop/model/ContractABI.java
@@ -78,8 +78,11 @@ public Constructor getConstructor() {
@Override
public boolean evaluate(Constructor obj) {
return true;
- };
- });
+ }
+ ;
+ }
+
+ );
}
public Function findFunction(Predicate searchPredicate) {
@@ -104,7 +107,6 @@ public String toString() {
return toJson();
}
-
@JsonInclude(Include.NON_NULL)
public static abstract class Entry {
@@ -116,6 +118,7 @@ public enum Type {
@JsonInclude(Include.NON_NULL)
public static class Param {
+
public Boolean indexed;
public String name;
public SolidityType type;
@@ -203,18 +206,21 @@ public SolidityType getType() {
public final List inputs;
public final List outputs;
public final Type type;
+ public final Boolean payable;
- public Entry(Boolean anonymous, Boolean constant, String name, List inputs, List outputs, Type type) {
+ public Entry(Boolean anonymous, Boolean constant, String name, List inputs, List outputs, Type type, Boolean payable) {
this.anonymous = anonymous;
this.constant = constant;
this.name = name;
this.inputs = inputs;
this.outputs = outputs;
this.type = type;
+ this.payable = payable == null ? Boolean.FALSE : payable;
}
/**
* Signature of this entry, before hashing, e.g., "myfunc(uint,bytes32)"
+ *
* @return
*/
public String formatSignature() {
@@ -246,11 +252,11 @@ public byte[] encodeSignature() {
@JsonCreator
public static Entry create(@JsonProperty("anonymous") boolean anonymous,
- @JsonProperty("constant") boolean constant,
- @JsonProperty("name") String name,
- @JsonProperty("inputs") List inputs,
- @JsonProperty("outputs") List outputs,
- @JsonProperty("type") Type type) {
+ @JsonProperty("constant") boolean constant,
+ @JsonProperty("name") String name,
+ @JsonProperty("inputs") List inputs,
+ @JsonProperty("outputs") List outputs,
+ @JsonProperty("type") Type type) {
Entry result = null;
switch (type) {
case constructor:
@@ -295,7 +301,7 @@ public Type getType() {
public static class Constructor extends Entry {
public Constructor(List inputs, List outputs) {
- super(null, null, "", inputs, outputs, Type.constructor);
+ super(null, null, "", inputs, outputs, Type.constructor, false);
}
public List> decode(byte[] encoded) {
@@ -316,7 +322,7 @@ public static class Function extends Entry {
private static final int ENCODED_SIGN_LENGTH = 4;
public Function(boolean constant, String name, List inputs, List outputs) {
- super(null, constant, name, inputs, outputs, Type.function);
+ super(null, constant, name, inputs, outputs, Type.function, false);
}
public String encodeAsHex(Object... args) {
@@ -373,7 +379,7 @@ public String toString() {
public static class Event extends Entry {
public Event(boolean anonymous, String name, List inputs, List outputs) {
- super(anonymous, null, name, inputs, outputs, Type.event);
+ super(anonymous, null, name, inputs, outputs, Type.event, false);
}
public List> decode(byte[] data, byte[][] topics) {
diff --git a/cakeshop-api/pom.xml b/cakeshop-api/pom.xml
index 4c35d6bd..69311b35 100644
--- a/cakeshop-api/pom.xml
+++ b/cakeshop-api/pom.xml
@@ -1,636 +1,663 @@
-
- 4.0.0
-
-
- com.jpmorgan
- cakeshop-parent
- 0.9.1
-
-
- cakeshop
- war
-
- Cakeshop API
-
-
- https://s3.amazonaws.com/com.jpmchase.cakeshop/
-
-
-
-
- windows
-
-
- windows
-
-
-
- geth-1.5.4-b70acf3c-windows.zip
- 4d5bb0b451aa9b59babba6007d56aa1538f5c7fe141ca9071c8e2fcb37e11c136ba3b9d98a810d41a5d39c5e8ad1b0a7bfd1a2aa3fcd2553d6e84b0033c4b652
- win
-
-
-
- mac
-
-
- mac
-
-
-
- geth-1.5.4-b70acf3c-mac.zip
- 0b7ee396d03c557764ea777e55282049bd308809067eea0e2b5e9f35b557f696c1662f400934e72f5cb183d0321545fd28057f7c62df76c7ae19dd6e21007e9d
- mac
-
-
-
- linux
-
-
- linux
-
-
-
- geth-1.5.4-b70acf3c-linux.zip
- 966bb9b476c13f557d33c474ca63ab46146775cc7ed6dfef7b3bae64281a39cccfd2f6647d30d1dbdde39f460771daf11a90d0c1f1cae7f8f5d8fac15232807a
- linux
-
-
-
-
-
-
-
- com.jpmorgan
- cakeshop-abi
- 0.9.1
-
-
-
-
- org.springframework.boot
- spring-boot-starter-log4j
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-starter-logging
-
-
- org.springframework.boot
- spring-boot-starter-tomcat
-
-
-
-
- org.springframework.boot
- spring-boot-starter-actuator
-
-
- org.springframework.hateoas
- spring-hateoas
-
-
- org.springframework.plugin
- spring-plugin-core
-
-
- org.springframework
- spring-context-support
-
-
-
- org.springframework.boot
- spring-boot-starter-jetty
- provided
-
-
-
-
- org.freemarker
- freemarker
-
-
-
-
- org.springframework
- spring-messaging
-
-
- org.springframework
- spring-websocket
-
-
-
-
- org.springframework
- spring-tx
-
-
- org.springframework
- spring-orm
-
-
- org.springframework
- spring-jdbc
-
-
- org.hibernate
- hibernate-core
-
-
- org.hsqldb
- hsqldb
-
-
- mysql
- mysql-connector-java
-
-
- org.postgresql
- postgresql
- ${postgres.version}
-
-
-
-
-
-
- com.fasterxml.jackson.core
- jackson-annotations
-
-
- com.fasterxml.jackson.core
- jackson-core
-
-
- com.fasterxml.jackson.core
- jackson-databind
-
-
- com.google.guava
- guava
- ${guava.version}
-
-
-
-
- org.slf4j
- slf4j-api
- jar
-
-
- org.slf4j
- slf4j-log4j12
-
-
-
- io.dropwizard.metrics
- metrics-core
-
-
-
-
- commons-io
- commons-io
- 2.4
- jar
-
-
- org.apache.commons
- commons-collections4
- 4.1
- jar
-
-
- org.apache.commons
- commons-lang3
- 3.3.2
- jar
-
-
- org.apache.commons
- commons-math3
- 3.6
-
-
- com.squareup.okhttp3
- okhttp
- 3.2.0
-
-
- com.jcabi
- jcabi-manifests
- 1.1
-
-
- com.jcabi
- jcabi-log
-
-
-
-
-
-
- org.bouncycastle
- bcprov-jdk15on
- 1.52
-
-
-
-
-
- net.java.dev.jna
- jna-platform
- 4.1.0
-
-
-
- org.aspectj
- aspectjweaver
-
-
-
-
-
-
-
-
-
-
-
- org.springframework
- spring-test
- provided
- jar
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.boot
- spring-boot-starter-logging
-
-
-
-
- org.testng
- testng
- 6.9.6
- test
-
-
-
- junit
- junit
- jar
- test
-
-
- com.google.code.gson
- gson
- test
-
-
-
-
- org.apache.bval
- org.apache.bval.bundle
- 0.5
- test
-
-
- org.apache.geronimo.specs
- geronimo-validation_1.0_spec
- 1.1
- test
-
-
-
-
- org.springframework.data
- spring-data-jpa
- 1.9.4.RELEASE
- test
- jar
-
-
- javax
- javaee-api
- 6.0
- test
-
-
-
-
-
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ 4.0.0
+
+
+ com.jpmorgan
+ cakeshop-parent
+ 0.10.0
+
+
+ cakeshop
+ war
+
+ Cakeshop API
+
+
+ https://github.com/fixanoid/cakeshop/releases/download/0.10.0/
+
+
+
+
+ windows
+
+
+ windows
+
+
+
+ geth-1.5.4-b70acf3c-windows.zip
+ 4d5bb0b451aa9b59babba6007d56aa1538f5c7fe141ca9071c8e2fcb37e11c136ba3b9d98a810d41a5d39c5e8ad1b0a7bfd1a2aa3fcd2553d6e84b0033c4b652
+ win
+
+
+
+ mac
+
+
+ mac
+
+
+
+ geth-1.5.4-b70acf3c-mac.zip
+ 0b7ee396d03c557764ea777e55282049bd308809067eea0e2b5e9f35b557f696c1662f400934e72f5cb183d0321545fd28057f7c62df76c7ae19dd6e21007e9d
+ mac
+
+
+
+ linux
+
+
+ linux
+
+
+
+ geth-1.5.4-b70acf3c-linux.zip
+ 966bb9b476c13f557d33c474ca63ab46146775cc7ed6dfef7b3bae64281a39cccfd2f6647d30d1dbdde39f460771daf11a90d0c1f1cae7f8f5d8fac15232807a
+ linux
+
+
+
+
-
-
- org.springframework.boot
- spring-boot-dependencies
- 1.3.3.RELEASE
- pom
- import
-
+
+
+ com.jpmorgan
+ cakeshop-abi
+ 0.10.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-log4j2
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
+ org.springframework.hateoas
+ spring-hateoas
+
+
+ org.springframework.plugin
+ spring-plugin-core
+
+
+ org.springframework
+ spring-context-support
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jetty
+ provided
+
+
+
+
+ org.freemarker
+ freemarker
+
+
+
+
+ org.springframework
+ spring-messaging
+
+
+ org.springframework
+ spring-websocket
+
+
+
+
+ org.springframework
+ spring-tx
+
+
+ org.springframework
+ spring-orm
+
+
+ org.springframework
+ spring-jdbc
+
+
+ org.hibernate
+ hibernate-core
+
+
+ org.hsqldb
+ hsqldb
+
+
+ mysql
+ mysql-connector-java
+
+
+ org.postgresql
+ postgresql
+ ${postgres.version}
+
+
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+ io.dropwizard.metrics
+ metrics-core
+
+
+
+
+ commons-io
+ commons-io
+ 2.5
+ jar
+
+
+ org.apache.commons
+ commons-collections4
+ 4.1
+ jar
+
+
+ org.apache.commons
+ commons-lang3
+ 3.3.2
+ jar
+
+
+ org.apache.commons
+ commons-math3
+ 3.6
+
+
+ com.squareup.okhttp3
+ okhttp
+ 3.2.0
+
+
+ com.jcabi
+ jcabi-manifests
+ 1.1
+
+
+ com.jcabi
+ jcabi-log
+
+
+
+
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+ 1.52
+
+
+
+
+
+ net.java.dev.jna
+ jna-platform
+ 4.1.0
+
+
+
+ org.aspectj
+ aspectjweaver
+
+
+ org.thymeleaf
+ thymeleaf-spring4
+ 2.1.4.RELEASE
+
+
+ org.thymeleaf.extras
+ thymeleaf-extras-springsecurity4
+ 2.1.3.RELEASE
+
+
+ nz.net.ultraq.thymeleaf
+ thymeleaf-layout-dialect
+ 2.2.0
+
+
+ io.springfox
+ springfox-swagger2
+ 2.6.1
+
+
+ io.springfox
+ springfox-swagger-ui
+ 2.6.1
+
+
+
+
+
+
+
+ org.springframework
+ spring-test
+ provided
+ jar
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+
+ org.testng
+ testng
+ 6.9.6
+ test
+
+
+
+ junit
+ junit
+ jar
+ test
+
+
+ com.google.code.gson
+ gson
+ test
+
+
+
+
+ org.apache.bval
+ org.apache.bval.bundle
+ 0.5
+ test
+
+
+ org.apache.geronimo.specs
+ geronimo-validation_1.0_spec
+ 1.1
+ test
+
+
+
+
+ org.springframework.data
+ spring-data-jpa
+ 1.9.4.RELEASE
+ test
+ jar
+
+
+ javax
+ javaee-api
+ 6.0
+ test
+
+
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
- ${java.version}
- ${java.version}
-
- ${endorsed.dir}
-
-
-
-
-
- org.apache.maven.plugins
- maven-war-plugin
-
- false
-
-
- true
-
-
- ${project.version}
- ${buildNumber}
- ${timestamp}
-
-
- **/node_modules/
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
- repackage
-
-
-
-
-
-
- org.codehaus.mojo
- buildnumber-maven-plugin
- 1.3
-
-
- validate
-
- create
- create-timestamp
-
-
-
-
- false
- false
- {0,date,yyyy-MM-dd'T'HH:mm:ss.SSSZ}
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
- ${skipTests}
-
-
-
-
- com.github.eirslett
- frontend-maven-plugin
- 1.2
-
-
- src/main/webapp
- target
- v6.9.1
- 3.10.9
-
-
-
-
-
- install node and npm
-
- install-node-and-npm
-
-
-
-
- npm install
-
- npm
-
-
-
-
- webpack build
-
- webpack
-
-
- -p
-
-
-
-
- npm-install-solc
-
- npm
-
-
- src/main/resources/geth/solc
-
-
-
-
-
-
-
- maven-clean-plugin
-
-
-
- src/main/webapp/node_modules
- false
-
-
- src/main/resources/geth/solc/node_modules
- false
-
-
- src/main/webapp/js
-
- index-gen.js*
- *.hot-update.*
- vendor/cakeshop.js
-
- false
-
-
-
-
-
-
- com.googlecode.maven-download-plugin
- download-maven-plugin
- 1.3.0
-
-
- install-geth
- generate-resources
-
- wget
-
-
- ${geth.url.base}/${geth.file}
- true
- ${project.build.directory}/classes/geth/bin/${geth.dir}/
- ${geth.sha512}
-
-
-
-
-
-
- maven-resources-plugin
- 3.0.1
-
-
- copy-nodejs
- generate-resources
-
- copy-resources
-
-
- ${project.build.directory}/classes/geth/bin/${geth.dir}/
-
-
- ${project.build.directory}/node/
-
- node
- node.exe
-
-
-
-
-
-
- copy-cakeshop-js
- generate-sources
-
- copy-resources
-
-
- src/main/webapp/js/vendor/
-
-
- ../cakeshop-client-js/target/dist/
-
- cakeshop.js
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- org.eclipse.m2e
- lifecycle-mapping
- 1.0.0
-
-
-
-
-
- org.codehaus.mojo
- buildnumber-maven-plugin
- [1.3,)
-
- create-timestamp
-
-
-
-
-
-
-
-
- com.googlecode.maven-download-plugin
- download-maven-plugin
- [1.3,)
-
- wget
-
-
-
-
- false
-
-
-
-
-
- com.github.eirslett
- frontend-maven-plugin
- [1.2,)
-
-
- install-node-and-npm
- npm
- webpack
-
-
-
-
- false
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ 1.4.3.RELEASE
+ pom
+ import
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+ ${java.version}
+
+ ${endorsed.dir}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+
+ false
+
+
+ true
+
+
+ ${project.version}
+ ${buildNumber}
+ ${timestamp}
+
+
+ **/node_modules/
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+
+ org.codehaus.mojo
+ buildnumber-maven-plugin
+ 1.3
+
+
+ validate
+
+ create
+ create-timestamp
+
+
+
+
+ false
+ false
+ {0,date,yyyy-MM-dd'T'HH:mm:ss.SSSZ}
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ ${skipTests}
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+ 1.2
+
+
+ src/main/webapp
+ target
+ v6.9.1
+ 3.10.9
+
+
+
+
+
+ install node and npm
+
+ install-node-and-npm
+
+
+
+
+ npm install
+
+ npm
+
+
+
+
+ webpack build
+
+ webpack
+
+
+ -p
+
+
+
+
+ npm-install-solc
+
+ npm
+
+
+ src/main/resources/geth/solc
+
+
+
+
+
+
+
+ maven-clean-plugin
+
+
+
+ src/main/webapp/node_modules
+ false
+
+
+ src/main/resources/geth/solc/node_modules
+ false
+
+
+ src/main/webapp/js
+
+ index-gen.js*
+ *.hot-update.*
+ vendor/cakeshop.js
+
+ false
+
+
+
+
+
+
+ com.googlecode.maven-download-plugin
+ download-maven-plugin
+ 1.3.0
+
+
+ install-geth
+ generate-resources
+
+ wget
+
+
+ ${geth.url.base}/${geth.file}
+ true
+ ${project.build.directory}/classes/geth/bin/${geth.dir}/
+ ${geth.sha512}
+
+
+
+
+
+
+ maven-resources-plugin
+ 3.0.1
+
+
+ copy-nodejs
+ generate-resources
+
+ copy-resources
+
+
+ ${project.build.directory}/classes/geth/bin/${geth.dir}/
+
+
+ ${project.build.directory}/node/
+
+ node
+ node.exe
+
+
+
+
+
+
+ copy-cakeshop-js
+ generate-sources
+
+ copy-resources
+
+
+ src/main/webapp/js/vendor/
+
+
+ ../cakeshop-client-js/target/dist/
+
+ cakeshop.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.eclipse.m2e
+ lifecycle-mapping
+ 1.0.0
+
+
+
+
+
+ org.codehaus.mojo
+ buildnumber-maven-plugin
+ [1.3,)
+
+ create-timestamp
+
+
+
+
+
+
+
+
+ com.googlecode.maven-download-plugin
+ download-maven-plugin
+ [1.3,)
+
+ wget
+
+
+
+
+ false
+
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+ [1.2,)
+
+
+ install-node-and-npm
+ npm
+ webpack
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cakeshop-api/src/main/java/com/jcabi/log/Logger.java b/cakeshop-api/src/main/java/com/jcabi/log/Logger.java
index f2239965..e6c6da33 100644
--- a/cakeshop-api/src/main/java/com/jcabi/log/Logger.java
+++ b/cakeshop-api/src/main/java/com/jcabi/log/Logger.java
@@ -1,8 +1,8 @@
package com.jcabi.log;
/**
- * com.jcabi.log.Logger started causing issues in Spring Boot so we simply
- * stub it out for now.
+ * com.jcabi.log.Logger started causing issues in Spring Boot so we simply stub
+ * it out for now.
*
* @author Chetan Sarva
*/
@@ -17,6 +17,9 @@ public static void info(final Object source, final String msg, Object... args) {
public static void warn(final Object source, final String msg, Object... args) {
}
+ public static void warn(final Object source, final String msg) {
+ }
+
public static void error(final Object source, final String msg, Object... args) {
}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/GethConfigBean.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/GethConfigBean.java
index 7b3b4d13..6c86bfd6 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/GethConfigBean.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/GethConfigBean.java
@@ -2,7 +2,6 @@
import static com.jpmorgan.cakeshop.util.FileUtils.*;
import static com.jpmorgan.cakeshop.util.ProcessUtils.*;
-import static org.apache.commons.io.FileUtils.*;
import com.jpmorgan.cakeshop.util.FileUtils;
import com.jpmorgan.cakeshop.util.SortedProperties;
@@ -14,6 +13,7 @@
import java.io.IOException;
import java.net.URI;
import java.util.Properties;
+import java.util.Scanner;
import javax.annotation.PostConstruct;
@@ -40,6 +40,9 @@ public class GethConfigBean {
@Value("${config.path}")
private String CONFIG_ROOT;
+ @Autowired
+ private QuorumConfigBean quorumConfig;
+
private String configFile;
private String binPath;
@@ -48,6 +51,8 @@ public class GethConfigBean {
private String gethPidFilename;
+ private String constPidFileName;
+
private String gethPasswordFile;
private String genesisBlockFilename;
@@ -58,10 +63,13 @@ public class GethConfigBean {
private String solcPath;
+ private String publicKey;
+
/**
* Whether or not this is a quorum node
*/
private Boolean isQuorum;
+ private boolean isEmbeddedQuorum;
private Properties props;
@@ -76,6 +84,8 @@ public class GethConfigBean {
private final String GETH_AUTO_STOP = "geth.auto.stop";
private final String GETH_START_TIMEOUT = "geth.start.timeout";
private final String GETH_UNLOCK_TIMEOUT = "geth.unlock.timeout";
+ private final String EMBEDDED_NODE = null != System.getProperty("geth.node") ? System.getProperty("geth.node") : null;
+ public final Boolean IS_BOOT_NODE = null != System.getProperty("geth.boot.node");
//geth.db.enabled
private final String GETH_DB_ENABLED = "cakeshop.database.vendor";
@@ -86,6 +96,16 @@ public class GethConfigBean {
private final String GETH_MINING = "geth.mining";
private final String GETH_IDENTITY = "geth.identity";
private final String GETH_EXTRA_PARAMS = "geth.params.extra";
+ // Quorum specific settings
+ private final String GETH_BLOCK_MAKER = "geth.block.maker";
+ private final String GETH_VOTE_ACCOUNT = "geth.vote.account";
+ private final String GETH_BLOCK_MAKER_PASS = "geth.block.maker.pass";
+ private final String GETH_VOTE_ACCOUNT_PASS = "geth.vote.account.pass";
+ private final String GETH_MIN_BLOCKTIME = "geth.min.blocktime";
+ private final String GETH_MAX_BLOCKTIME = "geth.max.blocktime";
+ private final String GETH_VOTE_CONTARCT_ADDRESS = "geth.vote.contract.addr";
+ private final String GETH_CONSTELLATION_ENABLED = "geth.constellation.enabled";
+ private final String GETH_PERMISSIONED = "geth.permissioned";
public GethConfigBean() {
}
@@ -104,17 +124,12 @@ public void initFromVendorConfig() throws IOException {
private void initBean() {
try {
initGethBean();
- } catch (IOException ex) {
+ } catch (IOException | InterruptedException ex) {
LOG.error(ex.getMessage());
}
}
- private void initGethBean() throws IOException {
-
- // load props
- configFile = FileUtils.expandPath(CONFIG_ROOT, "application.properties");
- props = new Properties();
- props.load(new FileInputStream(configFile));
+ private void initGethBean() throws IOException, InterruptedException {
// setup needed paths
String baseResourcePath = System.getProperty("eth.geth.dir");
@@ -122,6 +137,11 @@ private void initGethBean() throws IOException {
baseResourcePath = FileUtils.getClasspathName("geth");
}
+ // load props
+ configFile = FileUtils.expandPath(CONFIG_ROOT, "application.properties");
+ props = new Properties();
+ props.load(new FileInputStream(configFile));
+
// Choose correct geth binary
if (SystemUtils.IS_OS_WINDOWS) {
LOG.debug("Using geth for windows");
@@ -141,10 +161,8 @@ private void initGethBean() throws IOException {
throw new IOException("Path does not exist or is not executable: " + gethPath);
}
binPath = new File(gethPath).getParent();
-
gethPidFilename = expandPath(CONFIG_ROOT, "geth.pid");
-
// init genesis block file (using vendor copy if necessary)
String vendorGenesisDir = expandPath(baseResourcePath, "genesis");
@@ -174,8 +192,8 @@ private void initGethBean() throws IOException {
if (SystemUtils.IS_OS_WINDOWS) {
nodePath = nodePath + ".exe";
}
- solcPath = expandPath(baseResourcePath, "solc", "node_modules", "solc-cli", "bin", "solc");
-
+ solcPath = expandPath(baseResourcePath, "solc", "node_modules", "solc-cakeshop-cli", "bin", "solc");
+ ensureNodeBins(solcPath);
// Clean up data dir path for default config (not an absolute path)
if (getDataDirPath() != null) {
if (getDataDirPath().startsWith("/.ethereum")) {
@@ -208,10 +226,33 @@ private void initGethBean() throws IOException {
if (LOG.isDebugEnabled()) {
LOG.debug(StringUtils.toString(this));
}
+
+ if (StringUtils.isBlank(EMBEDDED_NODE)) {
+ // default to quorum
+ setGethPath(quorumConfig.getQuorumPath());
+
+ String destination = StringUtils.isNotBlank(System.getProperty("spring.config.location"))
+ ? System.getProperty("spring.config.location").replaceAll("file:", "")
+ .replaceAll("application.properties", "").concat("constellation-node/")
+ : getDataDirPath().concat("/constellation/");
+
+ quorumConfig.createKeys("node", destination);
+ quorumConfig.createQuorumConfig("node", destination);
+ setConstPidFileName(expandPath(CONFIG_ROOT, "constellation.pid"));
+ setIsEmbeddedQuorum(true);
+
+ File pubKey = new File(destination.concat("node.pub"));
+ try (Scanner scanner = new Scanner(pubKey)) {
+ while (scanner.hasNext()) {
+ setPublicKey(scanner.nextLine());
+ }
+ }
+ }
}
/**
* Make sure all node bins are executable, both for win & mac/linux
+ *
* @param nodePath
* @param solcPath
*/
@@ -236,6 +277,20 @@ public void setGethPidFilename(String gethPidFilename) {
this.gethPidFilename = gethPidFilename;
}
+ /**
+ * @return the constPidFileName
+ */
+ public String getConstPidFileName() {
+ return constPidFileName;
+ }
+
+ /**
+ * @param constPidFileName the constPidFileName to set
+ */
+ public void setConstPidFileName(String constPidFileName) {
+ this.constPidFileName = constPidFileName;
+ }
+
public String getDataDirPath() {
return props.getProperty(GETH_DATA_DIR);
}
@@ -269,20 +324,36 @@ public void setRpcUrl(String rpcUrl) {
}
public String getRpcPort() {
- String url = getRpcUrl();
- if (StringUtils.isBlank(url)) {
- return null;
- }
- URI uri = URI.create(url);
- return Integer.toString(uri.getPort());
+ String url = getRpcUrl();
+ if (StringUtils.isBlank(url)) {
+ return null;
+ }
+ URI uri = URI.create(url);
+ return Integer.toString(uri.getPort());
}
public String getRpcApiList() {
- return props.getProperty(GETH_RPCAPI_LIST);
+ if (StringUtils.isBlank(EMBEDDED_NODE)) {
+ if (props.getProperty(GETH_RPCAPI_LIST).contains("quorum")) {
+ return props.getProperty(GETH_RPCAPI_LIST);
+ } else {
+ return props.getProperty(GETH_RPCAPI_LIST).concat(",").concat("quorum");
+ }
+ } else {
+ return props.getProperty(GETH_RPCAPI_LIST);
+ }
}
public void setRpcApiList(String rpcApiList) {
- props.setProperty(GETH_RPCAPI_LIST, rpcApiList);
+ if (StringUtils.isBlank(EMBEDDED_NODE)) {
+ if (rpcApiList.contains("quorum")) {
+ props.setProperty(GETH_RPCAPI_LIST, rpcApiList);
+ } else {
+ props.setProperty(GETH_RPCAPI_LIST, rpcApiList.concat(",").concat("quorum"));
+ }
+ } else {
+ props.setProperty(GETH_RPCAPI_LIST, rpcApiList);
+ }
}
public String getGethNodePort() {
@@ -341,6 +412,82 @@ public void setIdentity(String identity) {
props.setProperty(GETH_IDENTITY, identity);
}
+ public String getBlockMaker() {
+ return props.getProperty(GETH_BLOCK_MAKER);
+ }
+
+ public void setBlockMaker(String blockMaker) {
+ props.setProperty(GETH_BLOCK_MAKER, blockMaker);
+ }
+
+ public String getBlockMakerPass() {
+ return props.getProperty(GETH_BLOCK_MAKER_PASS);
+ }
+
+ public void setBlockMakerPass(String pass) {
+ props.setProperty(GETH_BLOCK_MAKER_PASS, pass);
+ }
+
+ public String getVoteAccount() {
+ return props.getProperty(GETH_VOTE_ACCOUNT);
+ }
+
+ public void setVoteAccount(String voteAccount) {
+ props.setProperty(GETH_VOTE_ACCOUNT, voteAccount);
+ }
+
+ public String getVoteAccountPass() {
+ return props.getProperty(GETH_VOTE_ACCOUNT_PASS);
+ }
+
+ public void setVoteAccountPass(String pass) {
+ props.setProperty(GETH_VOTE_ACCOUNT_PASS, pass);
+ }
+
+ public Integer getMinBlockTime() {
+ return StringUtils.isNotBlank(props.getProperty(GETH_MIN_BLOCKTIME)) ? Integer.valueOf(props.getProperty(GETH_MIN_BLOCKTIME)) : 2;
+ }
+
+ public Integer getMaxBlockTime() {
+ return StringUtils.isNotBlank(props.getProperty(GETH_MAX_BLOCKTIME)) ? Integer.valueOf(props.getProperty(GETH_MAX_BLOCKTIME)) : 5;
+ }
+
+ public void setMinBlockTime(Integer minblockTime) {
+ props.setProperty(GETH_MIN_BLOCKTIME, String.valueOf(minblockTime));
+ }
+
+ public void setMaxBlockTime(Integer maxblockTime) {
+ props.setProperty(GETH_MAX_BLOCKTIME, String.valueOf(maxblockTime));
+ }
+
+ public String getVoteContractAddress() {
+ return props.getProperty(GETH_VOTE_CONTARCT_ADDRESS);
+ }
+
+ public void setVoteContractAddress(String address) {
+ props.setProperty(GETH_VOTE_CONTARCT_ADDRESS, address);
+ }
+
+ public Boolean isConstellationEnabled() {
+ return StringUtils.isNotBlank(props.getProperty(GETH_CONSTELLATION_ENABLED))
+ ? Boolean.valueOf(props.getProperty(GETH_CONSTELLATION_ENABLED))
+ : Boolean.TRUE;
+ }
+
+ public void setConstellationEnabled(Boolean isEnabled) {
+ props.setProperty(GETH_CONSTELLATION_ENABLED, String.valueOf(isEnabled));
+ }
+
+ public Boolean isPermissionedNode() {
+ return StringUtils.isNotBlank(props.getProperty(GETH_PERMISSIONED))
+ ? Boolean.valueOf(props.getProperty(GETH_PERMISSIONED))
+ : Boolean.FALSE;
+ }
+
+ public void setPermissionedNode(Boolean isPermissionedNode) {
+ props.setProperty(GETH_PERMISSIONED, String.valueOf(isPermissionedNode));
+ }
+
public String getBinPath() {
return binPath;
}
@@ -429,10 +576,9 @@ public void setQuorum(Boolean isQuorum) {
this.isQuorum = isQuorum;
}
-
-
/**
* Write the underlying config file to disk (persist all properties)
+ *
* @throws IOException
*/
public void save() throws IOException {
@@ -450,8 +596,8 @@ public void setProperty(String key, String val) {
}
/**
- * Simple wrapper around {@link Properties#getProperty(String)} which handles empty strings
- * and nulls properly
+ * Simple wrapper around {@link Properties#getProperty(String)} which
+ * handles empty strings and nulls properly
*
* @param key
* @param defaultStr
@@ -461,4 +607,32 @@ private String get(String key, String defaultStr) {
return StringUtils.defaultIfBlank(props.getProperty(key), defaultStr);
}
+ /**
+ * @return the isEmbeddedQuorum
+ */
+ public boolean isEmbeddedQuorum() {
+ return isEmbeddedQuorum;
+ }
+
+ /**
+ * @param isEmbeddedQuorum the isEmbeddedQuorum to set
+ */
+ public void setIsEmbeddedQuorum(boolean isEmbeddedQuorum) {
+ this.isEmbeddedQuorum = isEmbeddedQuorum;
+ }
+
+ /**
+ * @return the publicKey
+ */
+ public String getPublicKey() {
+ return publicKey;
+ }
+
+ /**
+ * @param publicKey the publicKey to set
+ */
+ public void setPublicKey(String publicKey) {
+ this.publicKey = publicKey;
+ }
+
}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/QuorumConfigBean.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/QuorumConfigBean.java
new file mode 100644
index 00000000..ad46efe5
--- /dev/null
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/QuorumConfigBean.java
@@ -0,0 +1,294 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jpmorgan.cakeshop.bean;
+
+import com.jpmorgan.cakeshop.util.FileUtils;
+import static com.jpmorgan.cakeshop.util.FileUtils.expandPath;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.Scanner;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class QuorumConfigBean implements InitializingBean {
+
+ private static final Logger LOG = LoggerFactory.getLogger(QuorumConfigBean.class);
+
+ private static final String QUORUM_LINUX_COMMAND = "quorum/linux/geth";
+ private static final String QUORUM_MAC_COMMAND = "quorum/mac/geth";
+ private static final String CONSTELLATION_LINUX_COMMAND = "quorum/constellation/linux/constellation-node";
+ private static final String CONSTELLATION_MAC_COMMAND = "quorum/constellation/mac/constellation-node";
+ private static final String CONSTELLATION_LINUX_KEYGEN = "quorum/constellation/linux/constellation-enclave-keygen";
+ private static final String CONSTELLATION_MAC_KEYGEN = "quorum/constellation/mac/constellation-enclave-keygen";
+ private final String CONSTELLATION_URL = StringUtils.isNotBlank(System.getProperty("geth.constellaiton.url"))
+ ? System.getProperty("geth.constellaiton.url") : "http://127.0.0.1:9000";
+
+ private final String EMBEDDED_NODE = null != System.getProperty("geth.node") ? System.getProperty("geth.node") : null;
+
+ private String quorumPath;
+ private String constellationPath;
+ private String keyGen;
+ private String constellationConfig;
+
+ @Value("${geth.bootnodes.list:\"\"}")
+ private String bootNodes;
+ @Value("${geth.bootnode.key:\"\"}")
+ private String bootNodeKey;
+ @Value("${geth.bootnode.address:\"\"}")
+ private String bootNodeAddress;
+ @Value("${geth.boot.node:false}")
+ private Boolean isBootNode;
+
+ /**
+ * @return the quorumPath
+ */
+ public String getQuorumPath() {
+ return quorumPath;
+ }
+
+ /**
+ * @param quorumPath the quorumPath to set
+ */
+ private void setQuorumPath(String quorumPath) {
+ this.quorumPath = quorumPath;
+ }
+
+ /**
+ * @return the constallationPath
+ */
+ public String getConstellationPath() {
+ return constellationPath;
+ }
+
+ /**
+ * @param constellationPath the constallationPath to set
+ */
+ private void setConstellationPath(String constallationPath) {
+ this.constellationPath = constallationPath;
+ }
+
+ /**
+ * @return the keyGen
+ */
+ public String getKeyGen() {
+ return keyGen;
+ }
+
+ /**
+ * @param keyGen the keyGen to set
+ */
+ private void setKeyGen(String keyGen) {
+ this.keyGen = keyGen;
+ }
+
+ public String getConstellationConfigPath() {
+ return constellationConfig;
+ }
+
+ /**
+ * @return the bootNode
+ */
+ public String getBootNodes() {
+ return bootNodes;
+ }
+
+ /**
+ * @return the bootNodeKey
+ */
+ public String getBootNodeKey() {
+ return bootNodeKey;
+ }
+
+ /**
+ * @return the bootNodeAddress
+ */
+ public String getBootNodeAddress() {
+ return bootNodeAddress;
+ }
+
+ /**
+ * @return the isBootNode
+ */
+ public Boolean isBootNode() {
+ return isBootNode;
+ }
+
+ public void createKeys(final String keyName, final String destination) throws IOException, InterruptedException {
+ constellationConfig = destination;
+ File dir = new File(destination);
+ Boolean createKeys = true;
+
+ if (!dir.exists()) {
+ dir.mkdirs();
+ } else {
+ String[] fileNames = dir.list();
+ if (fileNames.length >= 4) {
+ for (String fileName : fileNames) {
+ if (fileName.endsWith(".key") || fileName.endsWith(".pub")) {
+ createKeys = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (createKeys) {
+ //create keys
+ ProcessBuilder pb = new ProcessBuilder(getKeyGen(), destination.concat(keyName));
+ Process process = pb.start();
+ try (Scanner scanner = new Scanner(process.getInputStream())) {
+ boolean flag = scanner.hasNext();
+ try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()))) {
+ while (flag) {
+ String line = scanner.next();
+ if (line.isEmpty()) {
+ continue;
+ }
+ if (line.contains("[none]:")) {
+ writer.newLine();
+ writer.flush();
+ writer.newLine();
+ writer.flush();
+ flag = false;
+ }
+ }
+ }
+ }
+
+ int ret = process.waitFor();
+ if (ret != 0) {
+ LOG.error("Failed to generate keys. Please make sure that berkeley db is installed properly. Version of berkeley db is 6.2.23");
+ } else {
+ //create archive keys
+ pb = new ProcessBuilder(getKeyGen(), destination.concat(keyName.concat("a")));
+ process = pb.start();
+ try (Scanner scanner = new Scanner(process.getInputStream())) {
+ boolean flag = scanner.hasNext();
+ try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()))) {
+ while (flag) {
+ String line = scanner.next();
+ if (line.isEmpty()) {
+ continue;
+ }
+ if (line.contains("[none]:")) {
+ writer.write(" ");
+ writer.flush();
+ writer.newLine();
+ writer.flush();
+ flag = false;
+ }
+ }
+ }
+ }
+
+ ret = process.waitFor();
+ if (ret != 0) {
+ LOG.error("Failed to generate keys. Please make sure that berkeley db is installed properly. Version of berkeley db is 6.2.23");
+ }
+ }
+
+ if (process.isAlive()) {
+ process.destroy();
+ }
+ }
+ }
+
+ public void createQuorumConfig(String keyName, final String destination) throws IOException {
+ File confFile = new File(destination.concat(keyName.concat(".conf")));
+ if (!confFile.exists()) {
+ keyName = destination.concat(keyName);
+ try (FileWriter writer = new FileWriter(confFile)) {
+ String url = CONSTELLATION_URL.endsWith("/") ? CONSTELLATION_URL.replaceFirst("(.*)" + "/" + "$", "$1" + "") : CONSTELLATION_URL;
+ String port = url.substring(url.lastIndexOf(":") + 1, url.length());
+ writer.write("url = \"" + url + "/" + "\"");
+ writer.write("\n");
+ writer.write("port = " + port);
+ writer.write("\n");
+ writer.write("socketPath = \"" + keyName + ".ipc\"");
+ writer.write("\n");
+ writer.write("otherNodeUrls = []");
+ writer.write("\n");
+ writer.write("publicKeyPath = \"" + keyName + ".pub\"");
+ writer.write("\n");
+ writer.write("privateKeyPath = \"" + keyName + ".key\"");
+ writer.write("\n");
+ writer.write("archivalPublicKeyPath = \"" + keyName + "a" + ".pub\"");
+ writer.write("\n");
+ writer.write("archivalPrivateKeyPath = \"" + keyName + "a" + ".key\"");
+ writer.write("\n");
+ writer.write("storagePath = \"" + destination + "constellation\"");
+ writer.flush();
+ }
+ }
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ initQuorumBean();
+ }
+
+ private void initQuorumBean() {
+
+ // setup needed paths
+ String baseResourcePath = System.getProperty("eth.geth.dir");
+ if (StringUtils.isBlank(baseResourcePath)) {
+ baseResourcePath = FileUtils.getClasspathName("geth");
+ }
+
+ if (SystemUtils.IS_OS_LINUX) {
+ LOG.debug("Using quorum for linux");
+ setQuorumPath(expandPath(baseResourcePath, QUORUM_LINUX_COMMAND));
+ setConstellationPath(expandPath(baseResourcePath, CONSTELLATION_LINUX_COMMAND));
+ setKeyGen(expandPath(baseResourcePath, CONSTELLATION_LINUX_KEYGEN));
+
+ } else if (SystemUtils.IS_OS_MAC_OSX) {
+ LOG.debug("Using quorum for mac");
+ setQuorumPath(expandPath(baseResourcePath, QUORUM_MAC_COMMAND));
+ setConstellationPath(expandPath(baseResourcePath, CONSTELLATION_MAC_COMMAND));
+ setKeyGen(expandPath(baseResourcePath, CONSTELLATION_MAC_KEYGEN));
+
+ } else if ( (SystemUtils.IS_OS_WINDOWS) && (StringUtils.equalsIgnoreCase(EMBEDDED_NODE, "geth")) ) {
+ // run GETH
+ return;
+
+ } else if (SystemUtils.IS_OS_WINDOWS) {
+ LOG.error("Running on unsupported OS! Only Linux and Mac OS X are currently supported for Quorum, on Windoze, please run with -Dgeth.node=geth");
+ throw new IllegalArgumentException("Running on unsupported OS! Only Linux and Mac OS X are currently supported for Quorum, on Windoze, please run with -Dgeth.node=geth");
+
+ } else {
+ LOG.error("Running on unsupported OS! Only Linux and Mac OS X are currently supported");
+ throw new IllegalArgumentException("Running on unsupported OS! Only Linux and Mac OS X are currently supported");
+ }
+
+ File quorumExec = new File(getQuorumPath());
+ if (!quorumExec.canExecute()) {
+ quorumExec.setExecutable(true);
+ }
+
+ File constExec = new File(getConstellationPath());
+ if (!constExec.canExecute()) {
+ constExec.setExecutable(true);
+ }
+
+ File keyGenExec = new File(getKeyGen());
+ if (!keyGenExec.canExecute()) {
+ keyGenExec.setExecutable(true);
+ }
+
+ }
+
+}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppConfig.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppConfig.java
index ed533219..79c38272 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppConfig.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppConfig.java
@@ -32,21 +32,21 @@ public class AppConfig implements AsyncConfigurer {
public static final String CONFIG_FILE = "application.properties";
/**
- * Return the configured environment name (via spring.profiles.active system prop)
+ * Return the configured environment name (via spring.profiles.active system
+ * prop)
*
* @return String Environment name
*/
public static String getEnv() {
- return System.getProperty("spring.profiles.active");
+ return System.getProperty("spring.profiles.active");
}
/**
* Return the configured config location
*
- * Search order:
- * - ETH_CONFIG environment variable
- * - eth.config.dir system property (-Deth.config.dir param)
- * - Detect tomcat (container) root relative to classpath
+ * Search order: - ETH_CONFIG environment variable - eth.config.dir system
+ * property (-Deth.config.dir param) - Detect tomcat (container) root
+ * relative to classpath
*
* @return
*/
@@ -99,12 +99,12 @@ public static void initVendorConfig(File configFile) throws IOException {
Properties mergedProps = new Properties();
mergedProps.load(FileUtils.getClasspathStream(getVendorConfigFile()));
mergedProps.load(FileUtils.getClasspathStream(getVendorEnvConfigFile()));
-
+ setSecurity(mergedProps);
SortedProperties.store(mergedProps, new FileOutputStream(configFile));
}
public static PropertySourcesPlaceholderConfigurer createPropConfigurer(String configDir)
- throws IOException {
+ throws IOException {
if (StringUtils.isBlank(getEnv())) {
throw new IOException("System property 'spring.profiles.active' not set; unable to load config");
@@ -116,11 +116,12 @@ public static PropertySourcesPlaceholderConfigurer createPropConfigurer(String c
File configFile = new File(configPath.getPath() + File.separator + CONFIG_FILE);
if (!configPath.exists() || !configFile.exists()) {
+
LOG.debug("Config dir does not exist, will init");
configPath.mkdirs();
if (!configPath.exists()) {
- throw new IOException("Unable to create config dir: " + configPath.getAbsolutePath());
+ throw new IOException("Unable to create config dir: " + configPath.getAbsolutePath());
}
initVendorConfig(configFile);
@@ -129,6 +130,7 @@ public static PropertySourcesPlaceholderConfigurer createPropConfigurer(String c
Properties mergedProps = new Properties();
mergedProps.load(FileUtils.getClasspathStream(getVendorEnvConfigFile()));
mergedProps.load(new FileInputStream(configFile)); // overwrite vendor props with our configs
+ setSecurity(mergedProps);
SortedProperties.store(mergedProps, new FileOutputStream(configFile));
}
@@ -146,7 +148,7 @@ public static PropertySourcesPlaceholderConfigurer createPropConfigurer(String c
return propConfig;
}
- @Bean(name="asyncExecutor")
+ @Bean(name = "asyncExecutor")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
@@ -170,5 +172,38 @@ public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
+ private static void setSecurity(Properties properties) {
+ Boolean securityEnabled
+ = StringUtils.isNotBlank(System.getProperty("cakeshop.security.enabled"))
+ ? Boolean.valueOf(System.getProperty("cakeshop.security.enabled"))
+ : false;
+ if (securityEnabled) {
+ if (StringUtils.isNotBlank(properties.getProperty("management.security.enabled"))) {
+ properties.remove("management.security.enabled");
+ System.setProperty("management.security.enabled", "true");
+ }
+ if (StringUtils.isNotBlank(properties.getProperty("security.basic.enabled"))) {
+ properties.remove("security.basic.enabled");
+ System.setProperty("security.basic.enabled", "true");
+ }
+ if (StringUtils.isNotBlank(properties.getProperty("security.ignored"))) {
+ System.setProperty("security.ignored", "");
+ properties.remove("security.ignored");
+ }
+
+ } else {
+ if (StringUtils.isBlank(properties.getProperty("management.security.enabled"))) {
+ properties.setProperty("management.security.enabled", "false");
+ }
+ if (StringUtils.isBlank(properties.getProperty("security.basic.enabled"))) {
+ properties.setProperty("security.basic.enabled", "false");
+ }
+ if (StringUtils.isBlank(properties.getProperty("security.ignored"))) {
+ properties.setProperty("security.ignored", "/**");
+ }
+ LOG.warn("Authentication disabled.");
+ }
+
+ }
}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppStartup.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppStartup.java
index 9907c145..16ec3133 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppStartup.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppStartup.java
@@ -7,16 +7,16 @@
import com.jpmorgan.cakeshop.service.GethHttpService;
import com.jpmorgan.cakeshop.util.EEUtils;
import com.jpmorgan.cakeshop.util.FileUtils;
+import com.jpmorgan.cakeshop.util.MemoryUtils;
import com.jpmorgan.cakeshop.util.ProcessUtils;
import com.jpmorgan.cakeshop.util.StreamGobbler;
import com.jpmorgan.cakeshop.util.StringUtils;
import java.io.File;
-import java.io.FilenameFilter;
import java.io.IOException;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -34,10 +34,11 @@
import org.springframework.stereotype.Service;
@Order(999999)
-@Service(value="appStartup")
+@Service(value = "appStartup")
public class AppStartup implements ApplicationListener {
private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(AppStartup.class);
+ private final Long REQUIRED_MEMORY = 2000000L;
@Value("${config.path}")
private String CONFIG_ROOT;
@@ -66,15 +67,15 @@ public AppStartup() {
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof EmbeddedServletContainerInitializedEvent) {
- // this event fires after context refresh and after geth has started
- int port = ((EmbeddedServletContainerInitializedEvent) event).getEmbeddedServletContainer().getPort();
- System.out.println(" url: " + getSpringUrl(port));
- System.out.println();
- return;
+ // this event fires after context refresh and after geth has started
+ int port = ((EmbeddedServletContainerInitializedEvent) event).getEmbeddedServletContainer().getPort();
+ System.out.println(" url: " + getSpringUrl(port));
+ System.out.println();
+ return;
}
if (!(event instanceof ContextRefreshedEvent)) {
- return;
+ return;
}
if (autoStartFired) {
@@ -97,10 +98,10 @@ public void onApplicationEvent(ApplicationEvent event) {
gethConfig.setAutoStop(false);
gethConfig.setRpcUrl("http://localhost:22000");
try {
- gethConfig.save();
+ gethConfig.save();
} catch (IOException e) {
- LOG.error("Error writing application.properties: " + e.getMessage());
- System.exit(1);
+ LOG.error("Error writing application.properties: " + e.getMessage());
+ System.exit(1);
}
System.out.println("initialization complete. wrote quorum-example config. exiting.");
System.exit(0);
@@ -108,7 +109,7 @@ public void onApplicationEvent(ApplicationEvent event) {
if (gethConfig.isAutoStart()) {
LOG.info("Autostarting geth node");
- healthy = geth.start();
+ healthy = geth.start();
if (!healthy) {
addError("GETH FAILED TO START");
}
@@ -116,7 +117,6 @@ public void onApplicationEvent(ApplicationEvent event) {
} else {
// run startup tasks only
geth.runPostStartupTasks();
-
}
}
@@ -139,7 +139,6 @@ private void printErrorReport() {
System.out.println();
System.out.println(StringUtils.repeat("*", 80));
-
System.out.println("PRINTING DEBUG INFO");
System.out.println("-------------------");
System.out.println();
@@ -152,7 +151,6 @@ private void printErrorReport() {
System.out.println();
System.out.println(getErrorInfo());
-
System.out.println();
System.out.println(StringUtils.repeat("*", 80));
System.out.println();
@@ -177,53 +175,53 @@ private void printWelcomeMessage() {
// Try to determine listening URL
private String getSpringUrl(int port) {
- String uri = "http://";
- try {
- uri = uri + EEUtils.getAllIPs().get(0).getAddr();
- } catch (APIException e) {
- uri = uri + "localhost";
- }
- return uri + ":" + Integer.toString(port) + "/cakeshop/";
+ String uri = "http://";
+ try {
+ uri = uri + EEUtils.getAllIPs().get(0).getAddr();
+ } catch (APIException e) {
+ uri = uri + "localhost";
+ }
+ return uri + ":" + Integer.toString(port) + "/cakeshop/";
}
public String getDebugInfo(ServletContext servletContext) {
StringBuilder out = new StringBuilder();
- out.append("java.vendor: " + SystemUtils.JAVA_VENDOR + "\n");
- out.append("java.version: " + System.getProperty("java.version") + "\n");
- out.append("java.home: " + SystemUtils.JAVA_HOME + "\n");
- out.append("java.io.tmpdir: " + SystemUtils.JAVA_IO_TMPDIR + "\n");
+ out.append("java.vendor: ").append(SystemUtils.JAVA_VENDOR).append("\n");
+ out.append("java.version: ").append(System.getProperty("java.version")).append("\n");
+ out.append("java.home: ").append(SystemUtils.JAVA_HOME).append("\n");
+ out.append("java.io.tmpdir: ").append(SystemUtils.JAVA_IO_TMPDIR).append("\n");
out.append("\n");
out.append("servlet.container: ");
if (servletContext != null) {
- out.append(servletContext.getServerInfo());
+ out.append(servletContext.getServerInfo());
}
out.append("\n\n");
- out.append("cakeshop.version: " + AppVersion.BUILD_VERSION + "\n");
- out.append("cakeshop.build.id: " + AppVersion.BUILD_ID + "\n");
- out.append("cakeshop.build.date: " + AppVersion.BUILD_DATE + "\n");
+ out.append("cakeshop.version: ").append(AppVersion.BUILD_VERSION).append("\n");
+ out.append("cakeshop.build.id: ").append(AppVersion.BUILD_ID).append("\n");
+ out.append("cakeshop.build.date: ").append(AppVersion.BUILD_DATE).append("\n");
out.append("\n");
- out.append("os.name: " + SystemUtils.OS_NAME + "\n");
- out.append("os.version: " + SystemUtils.OS_VERSION + "\n");
- out.append("os.arch: " + SystemUtils.OS_ARCH + "\n");
+ out.append("os.name: ").append(SystemUtils.OS_NAME).append("\n");
+ out.append("os.version: ").append(SystemUtils.OS_VERSION).append("\n");
+ out.append("os.arch: ").append(SystemUtils.OS_ARCH).append("\n");
out.append("\n");
out.append(getLinuxInfo());
- out.append("user.dir: " + SystemUtils.getUserDir() + "\n");
- out.append("user.home: " + SystemUtils.getUserHome() + "\n");
+ out.append("user.dir: ").append(SystemUtils.getUserDir()).append("\n");
+ out.append("user.home: ").append(SystemUtils.getUserHome()).append("\n");
out.append("\n");
- out.append("app.root: " + FileUtils.getClasspathPath("") + "\n");
- out.append("eth.env: " + System.getProperty("eth.environment") + "\n");
- out.append("eth.config.dir: " + CONFIG_ROOT + "\n");
+ out.append("app.root: ").append(FileUtils.getClasspathPath("")).append("\n");
+ out.append("eth.env: ").append(System.getProperty("eth.environment")).append("\n");
+ out.append("eth.config.dir: ").append(CONFIG_ROOT).append("\n");
out.append("\n");
- out.append("geth.path: " + gethConfig.getGethPath() + "\n");
- out.append("geth.data.dir: " + gethConfig.getDataDirPath() + "\n");
+ out.append("geth.path: ").append(gethConfig.getGethPath()).append("\n");
+ out.append("geth.data.dir: ").append(gethConfig.getDataDirPath()).append("\n");
out.append("geth.version: ");
if (StringUtils.isNotBlank(gethVer)) {
out.append(gethVer);
@@ -232,7 +230,7 @@ public String getDebugInfo(ServletContext servletContext) {
}
out.append("\n\n");
- out.append("solc.path: " + gethConfig.getSolcPath() + "\n");
+ out.append("solc.path: ").append(gethConfig.getSolcPath()).append("\n");
out.append("solc.version: ");
if (StringUtils.isNotBlank(solcVer)) {
out.append(solcVer);
@@ -254,12 +252,7 @@ private String getLinuxInfo() {
List files = new ArrayList<>();
if (dir.exists()) {
- files.addAll(Lists.newArrayList(dir.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String filename) {
- return filename.endsWith("release");
- }
- })));
+ files.addAll(Lists.newArrayList(dir.listFiles((File dir1, String filename) -> filename.endsWith("release"))));
}
// looks for the version file (not all linux distros)
@@ -278,15 +271,15 @@ public boolean accept(File dir, String filename) {
str.append("Linux release info:");
// prints all the version-related files
- for (File f : files) {
+ files.forEach((f) -> {
try {
- String ver = FileUtils.readFileToString(f);
+ String ver = FileUtils.readFileToString(f, Charset.defaultCharset());
if (!StringUtils.isBlank(ver)) {
str.append(ver);
}
} catch (IOException e) {
}
- }
+ });
str.append("\n\n");
return str.toString();
}
@@ -297,17 +290,14 @@ private List getAllErrors() {
allErrors.addAll(errors);
allErrors.addAll(geth.getStartupErrors());
- Collections.sort(allErrors, new Comparator() {
- @Override
- public int compare(ErrorLog o1, ErrorLog o2) {
- long result = o1.nanos - o2.nanos;
- if (result < 0) {
- return -1;
- } else if (result > 0) {
- return 1;
- } else {
- return 0;
- }
+ Collections.sort(allErrors, (ErrorLog o1, ErrorLog o2) -> {
+ long result = o1.nanos - o2.nanos;
+ if (result < 0) {
+ return -1;
+ } else if (result > 0) {
+ return 1;
+ } else {
+ return 0;
}
});
return allErrors;
@@ -320,9 +310,9 @@ public String getErrorInfo() {
}
StringBuilder out = new StringBuilder();
- for (ErrorLog err : allErrors) {
+ allErrors.forEach((err) -> {
out.append(err.toString()).append("\n\n");
- }
+ });
return out.toString();
}
@@ -332,7 +322,7 @@ public String getErrorInfo() {
* @return
*/
private boolean testSystemHealth() {
- boolean healthy = true;
+ boolean isHealthy = true;
System.out.println();
System.out.println();
@@ -348,7 +338,7 @@ private boolean testSystemHealth() {
System.out.println("OK");
} else {
System.out.println("FAILED");
- healthy = false;
+ isHealthy = false;
}
// test config & db data dir
@@ -360,15 +350,15 @@ private boolean testSystemHealth() {
System.out.println("OK");
} else {
System.out.println("FAILED");
- healthy = false;
+ isHealthy = false;
}
// test geth binary
System.out.println();
System.out.println("Testing geth server binary");
String gethOutput = testBinary(gethConfig.getGethPath(), "version");
- if (gethOutput == null || gethOutput.indexOf("Version:") < 0) {
- healthy = false;
+ if (gethOutput == null || !gethOutput.contains("Version:")) {
+ isHealthy = false;
System.out.println("FAILED");
} else {
Matcher matcher = Pattern.compile("^Version: (.*)", Pattern.MULTILINE).matcher(gethOutput);
@@ -382,8 +372,8 @@ private boolean testSystemHealth() {
System.out.println();
System.out.println("Testing solc compiler binary");
String solcOutput = testBinary(gethConfig.getNodePath(), gethConfig.getSolcPath(), "--version");
- if (solcOutput == null || solcOutput.indexOf("Version:") < 0) {
- healthy = false;
+ if (solcOutput == null || !solcOutput.contains("Version:")) {
+ isHealthy = false;
System.out.println("FAILED");
} else {
Matcher matcher = Pattern.compile("^Version: (.*)", Pattern.MULTILINE).matcher(solcOutput);
@@ -394,7 +384,7 @@ private boolean testSystemHealth() {
}
System.out.println();
- if (healthy) {
+ if (isHealthy) {
System.out.println("ALL TESTS PASSED!");
} else {
System.out.println("!!! SYSTEM FAILED SELF-TEST !!!");
@@ -404,7 +394,13 @@ private boolean testSystemHealth() {
System.out.println(StringUtils.repeat("*", 80));
System.out.println();
- return healthy;
+ //Check if total memory or free memory is Less than 2 GB
+ if (MemoryUtils.getMemoryData(false) < REQUIRED_MEMORY && MemoryUtils.getMemoryData(true) < REQUIRED_MEMORY) {
+ errors.add(new ErrorLog("System does not have enough total or free RAM to run cakeshop. Need at least 2 GB of free RAM"));
+ isHealthy = false;
+ }
+
+ return isHealthy;
}
private String testBinary(String... args) {
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/JsonMethodArgumentResolver.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/JsonMethodArgumentResolver.java
deleted file mode 100644
index b9ed240d..00000000
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/JsonMethodArgumentResolver.java
+++ /dev/null
@@ -1,132 +0,0 @@
-package com.jpmorgan.cakeshop.config;
-
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import java.io.BufferedReader;
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.core.MethodParameter;
-import org.springframework.web.bind.annotation.ValueConstants;
-import org.springframework.web.bind.support.WebDataBinderFactory;
-import org.springframework.web.context.request.NativeWebRequest;
-import org.springframework.web.method.support.HandlerMethodArgumentResolver;
-import org.springframework.web.method.support.ModelAndViewContainer;
-
-public class JsonMethodArgumentResolver implements HandlerMethodArgumentResolver {
-
- private static final Logger LOG = LoggerFactory.getLogger(JsonMethodArgumentResolver.class);
-
- private static final ObjectMapper objectMapper = new ObjectMapper();
-
- @Target(ElementType.PARAMETER)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface JsonBodyParam {
-
- /**
- * The name of the request parameter to bind to.
- */
- String value() default "";
-
- /**
- * Whether the parameter is required.
- *
Default is {@code true}, leading to an exception thrown in case
- * of the parameter missing in the request. Switch this to {@code false}
- * if you prefer a {@code null} in case of the parameter missing.
- *
Alternatively, provide a {@link #defaultValue() defaultValue},
- * which implicitly sets this flag to {@code false}.
- */
- boolean required() default true;
-
- /**
- * The default value to use as a fallback when the request parameter value
- * is not provided or empty. Supplying a default value implicitly sets
- * {@link #required()} to false.
- */
- String defaultValue() default ValueConstants.DEFAULT_NONE;
-
- }
-
- @Override
- public boolean supportsParameter(MethodParameter parameter) {
- return (parameter.getParameterAnnotation(JsonBodyParam.class) != null);
- }
-
- @SuppressWarnings({ "unchecked", "rawtypes" })
- @Override
- public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
- NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
-
- if (!mavContainer.getModel().containsAttribute("_json_data")) {
- BufferedReader postReader = ((HttpServletRequest)webRequest.getNativeRequest()).getReader();
- Map data = null;
- try {
- data = objectMapper.readValue(postReader, Map.class);
- } catch (JsonMappingException ex) {
- }
- mavContainer.addAttribute("_json_data", data);
- }
-
-
- Map data = (Map) mavContainer.getModel().get("_json_data");
-
- JsonBodyParam jsonParam = parameter.getParameterAnnotation(JsonBodyParam.class);
- String param = jsonParam.value();
- if (param == null || param.isEmpty() || param.equals(jsonParam.defaultValue())) {
- param = parameter.getParameterName(); // fallback to name of param itself
- }
-
- Class paramType = parameter.getParameterType();
- Object val = data == null ? null : data.get(param);
- if (val == null) {
- // handle null val
- if (!jsonParam.defaultValue().contentEquals(ValueConstants.DEFAULT_NONE)) {
- return jsonParam.defaultValue();
- }
- return val;
- }
- if (paramType == val.getClass() || paramType == Object.class) {
- return val; // val types match exactly or Object type was requested
- }
-
- if ((paramType == Long.class || paramType == Integer.class)
- && (val instanceof Long || val instanceof Integer)) {
-
- // flip type of val
- if (paramType == Long.class) {
- return new Integer((int) val).longValue();
- } else {
- return new Long((long) val).intValue();
- }
- }
-
- if (paramType.isArray()) {
- if (val.getClass().isArray()) {
- return val;
- }
-
- if (val instanceof List) {
- return ((List) val).toArray();
- }
- }
-
- if (paramType == List.class && val instanceof List) {
- return val;
- }
-
- LOG.warn("Param type mismatch for '" + parameter.getParameterName() + "'; got " + val.getClass().toString());
- return null;
- }
-
-}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SecurityConfig.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SecurityConfig.java
new file mode 100644
index 00000000..1054165b
--- /dev/null
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SecurityConfig.java
@@ -0,0 +1,44 @@
+package com.jpmorgan.cakeshop.config;
+
+import com.jpmorgan.cakeshop.service.auth.impl.AuthenticationService;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(SecurityConfig.class);
+
+ @Autowired
+ @Qualifier("authService")
+ private AuthenticationService authService;
+
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+ auth.eraseCredentials(Boolean.FALSE);
+ auth.authenticationProvider(authService);
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+
+ http
+ .authorizeRequests()
+ .antMatchers("/resources/**", "/user").permitAll()
+ .anyRequest().authenticated()
+ .and().formLogin().failureUrl("/login?error")
+ .loginPage("/login").permitAll()
+ .and().httpBasic()
+ .and().logout().logoutSuccessUrl("/login").permitAll();
+
+ http.csrf().disable();
+ }
+
+}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SecurityWebAppInitializer.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SecurityWebAppInitializer.java
new file mode 100644
index 00000000..133579f2
--- /dev/null
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SecurityWebAppInitializer.java
@@ -0,0 +1,8 @@
+package com.jpmorgan.cakeshop.config;
+
+import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
+
+public class SecurityWebAppInitializer {
+ //extends AbstractSecurityWebApplicationInitializer {
+
+}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SpringBootApplication.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SpringBootApplication.java
index f78e5382..c08e9435 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SpringBootApplication.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SpringBootApplication.java
@@ -6,6 +6,7 @@
import java.io.File;
import java.io.IOException;
+import java.net.URL;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -40,7 +41,7 @@ public static void main(String[] args) {
}
if (StringUtils.isNotBlank(System.getProperty("spring.profiles.active"))) {
- System.setProperty("spring.config.location", "file:" + FileUtils.expandPath(AppConfig.getConfigPath(), "application.properties"));
+ System.setProperty("spring.config.location", "file:" + FileUtils.expandPath(AppConfig.getConfigPath(), "application.properties"));
}
// extract geth from WAR (if necessary)
@@ -64,27 +65,35 @@ public static void main(String[] args) {
// boot app
new SpringApplicationBuilder(SpringBootApplication.class)
- .profiles("container", "spring-boot")
- .run(args);
+ .profiles("container", "spring-boot")
+ .run(args);
}
private static void extractGeth(String configDir) throws IOException {
- File war = FileUtils.toFile(GethConfigBean.class.getClassLoader().getResource(""));
- if (!war.toString().endsWith(".war")) {
- return; // no need to copy
- }
+ URL url = GethConfigBean.class.getClassLoader().getResource("");
+ String warUrl = null;
- String gethDir = FileUtils.expandPath(configDir, "geth");
- System.out.println("Extracting geth to " + gethDir);
+ if (url.getProtocol().equals("jar")) {
+ warUrl = url.toString().replaceFirst("jar:", "");
+ warUrl = warUrl.substring(0, warUrl.indexOf("!"));
+ }
+
+ URL newUrl = StringUtils.isNotBlank(warUrl) ? new URL(warUrl) : url;
+ File war = FileUtils.toFile(newUrl);
+
+ if (!war.toString().endsWith(".war")) {
+ return; // no need to copy
+ }
+
+ String gethDir = FileUtils.expandPath(configDir, "geth");
+ System.out.println("Extracting geth to " + gethDir);
- ZipFile warZip = null;
- try {
- warZip = new ZipFile(war);
+ try (ZipFile warZip = new ZipFile(war)) {
Enumeration extends ZipEntry> entries = warZip.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
String file = zipEntry.getName();
- if (zipEntry.isDirectory() || !file.startsWith("WEB-INF/classes/geth")) {
+ if (zipEntry.isDirectory() || !file.startsWith("WEB-INF/classes/geth")) {
continue;
}
@@ -95,13 +104,9 @@ private static void extractGeth(String configDir) throws IOException {
}
FileUtils.copyInputStreamToFile(warZip.getInputStream(zipEntry), target);
}
- } finally {
- if (warZip != null) {
- warZip.close();
- }
- }
+ }
- System.setProperty("eth.geth.dir", gethDir);
+ System.setProperty("eth.geth.dir", gethDir);
}
@Bean
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SwaggerConfig.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SwaggerConfig.java
new file mode 100644
index 00000000..cc02f03d
--- /dev/null
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SwaggerConfig.java
@@ -0,0 +1,41 @@
+package com.jpmorgan.cakeshop.config;
+
+import com.google.common.base.Predicates;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+@Configuration
+@EnableSwagger2
+public class SwaggerConfig {
+
+ @Bean
+ public Docket api() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .select()
+ .apis(RequestHandlerSelectors.basePackage("com.jpmorgan.cakeshop.controller"))
+ .paths(Predicates.not(PathSelectors.regex("/login")))
+ .paths(Predicates.not(PathSelectors.regex("/logout")))
+ .paths(Predicates.not(PathSelectors.regex("/user")))
+ .build()
+ .apiInfo(apiInfo());
+ }
+
+ private ApiInfo apiInfo() {
+ ApiInfo apiInfo = new ApiInfo(
+ "Cakeshop REST API",
+ "Sets of APIs to manage ethereum.",
+ "API 1.0",
+ "Terms of service",
+ ApiInfo.DEFAULT_CONTACT,
+ "License of API",
+ "API license URL");
+ return apiInfo;
+ }
+
+}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebAppInit.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebAppInit.java
index 52798644..591ea435 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebAppInit.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebAppInit.java
@@ -13,37 +13,42 @@
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableAutoConfiguration
+@EnableWebMvc
@ComponentScan(basePackages = "com.jpmorgan.cakeshop")
public class WebAppInit extends SpringBootServletInitializer {
-
-
+
public static void setLoggingPath(boolean isSpringBoot) {
// setup logging path for spring-boot
if (StringUtils.isNotBlank(System.getProperty("logging.path"))) {
return;
}
if (isSpringBoot) {
- System.setProperty("logging.path", ".");
+ System.setProperty("logging.path", "logs");
return;
}
// running in a container, find home path
- if (StringUtils.isNotBlank(System.getProperty("catalina.base"))) {
+ if (StringUtils.isNotBlank(System.getProperty("catalina.base"))
+ && StringUtils.isBlank(System.getProperty("logging.path"))) {
// tomcat
System.setProperty("logging.path", System.getProperty("catalina.base") + "/logs");
- } else if (StringUtils.isNotBlank(System.getProperty("catalina.home"))) {
+ } else if (StringUtils.isNotBlank(System.getProperty("catalina.home"))
+ && StringUtils.isBlank(System.getProperty("logging.path"))) {
// tomcat
System.setProperty("logging.path", System.getProperty("catalina.home") + "/logs");
- } else if (StringUtils.isNotBlank(System.getProperty("jetty.logging.dir"))) {
+ } else if (StringUtils.isNotBlank(System.getProperty("jetty.logging.dir"))
+ && StringUtils.isBlank(System.getProperty("logging.path"))) {
// jetty
System.setProperty("logging.path", System.getProperty("jetty.logging.dir"));
- } else if (StringUtils.isNotBlank(System.getProperty("jetty.home"))) {
+ } else if (StringUtils.isNotBlank(System.getProperty("jetty.home"))
+ && StringUtils.isBlank(System.getProperty("logging.path"))) {
// jetty
System.setProperty("logging.path", System.getProperty("jetty.home") + "/logs");
@@ -57,14 +62,12 @@ public static void setLoggingPath(boolean isSpringBoot) {
System.setProperty("logging.path", "/tmp");
}
-
}
// @Override
// protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
// return builder.profiles(env.getActiveProfiles());
// }
-
@Override
public void onStartup(ServletContext container) throws ServletException {
setLoggingPath(false);
@@ -75,7 +78,7 @@ public void onStartup(ServletContext container) throws ServletException {
}
container.addListener(new SessionListener());
super.onStartup(container);
-
+
}
}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebConfig.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebConfig.java
index a90d458d..b1ed8750 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebConfig.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebConfig.java
@@ -1,9 +1,8 @@
package com.jpmorgan.cakeshop.config;
-import java.util.ArrayList;
-import java.util.List;
+import java.io.FileInputStream;
+import java.util.Properties;
-import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
@@ -13,15 +12,20 @@
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
-import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
-import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import okhttp3.OkHttpClient;
+import org.springframework.web.servlet.ViewResolver;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.thymeleaf.spring4.SpringTemplateEngine;
+import org.thymeleaf.spring4.view.ThymeleafViewResolver;
+import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
+import org.springframework.beans.factory.annotation.Value;
+import com.jpmorgan.cakeshop.util.FileUtils;
/**
*
@@ -31,50 +35,36 @@
@EnableScheduling
public class WebConfig extends WebMvcConfigurerAdapter {
- @Autowired
- private Environment env;
+ private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(WebConfig.class);
+
+ @Value("${config.path}")
+ private String CONFIG_ROOT;
@Autowired
- private RequestMappingHandlerAdapter adapter;
+ private Environment env;
@Autowired
private OkHttpClient okHttpClient;
- @PostConstruct
- public void prioritizeCustomArgumentMethodHandlers() {
- // existing resolvers
- List argumentResolvers
- = new ArrayList<>(adapter.getArgumentResolvers());
-
- // add our resolvers at pos 0
- List customResolvers
- = adapter.getCustomArgumentResolvers();
-
- // empty and re-add our custom list
- argumentResolvers.removeAll(customResolvers);
- argumentResolvers.addAll(0, customResolvers);
-
- adapter.setArgumentResolvers(argumentResolvers);
- }
-
@Override
- public void addArgumentResolvers(List argumentResolvers) {
- super.addArgumentResolvers(argumentResolvers);
- argumentResolvers.add(new JsonMethodArgumentResolver());
- }
-
- @Override
-
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setTaskExecutor(createMvcAsyncExecutor());
}
@Override
public void addCorsMappings(CorsRegistry registry) {
- if (Boolean.valueOf(env.getProperty("geth.cors.enabled:true"))) {
+ String configFile = FileUtils.expandPath(CONFIG_ROOT, "application.properties");
+ Properties props = new Properties();
+ try {
+ props.load(new FileInputStream(configFile));
+ } catch (Exception e) { }
+
+ if (Boolean.valueOf(env.getProperty("geth.cors.enabled"))) {
+ registry.addMapping("/**")
+ .allowedOrigins(env.getProperty("geth.cors.url"));
+ } else if (Boolean.valueOf(props.getProperty("geth.cors.enabled"))) {
registry.addMapping("/**")
- .allowedOrigins(env.getProperty("geth.cors.url"))
- .allowedMethods("POST");
+ .allowedOrigins(props.getProperty("geth.cors.url"));
}
}
@@ -84,6 +74,43 @@ public void configureDefaultServletHandling(DefaultServletHandlerConfigurer conf
configurer.enable();
}
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("swagger-ui.html")
+ .addResourceLocations("classpath:/META-INF/resources/");
+
+ registry.addResourceHandler("/webjars/**")
+ .addResourceLocations("classpath:/META-INF/resources/webjars/");
+ }
+
+ @Bean
+ public ServletContextTemplateResolver templateResolver() {
+ ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
+ templateResolver.setCacheable(false);
+ templateResolver.setTemplateMode("HTML5");
+ templateResolver.setCharacterEncoding("UTF-8");
+ templateResolver.setPrefix("/resources/");
+ templateResolver.setSuffix(".html");
+
+ return templateResolver;
+ }
+
+ @Bean
+ public SpringTemplateEngine templateEngine() {
+ SpringTemplateEngine templateEngine = new SpringTemplateEngine();
+ templateEngine.setTemplateResolver(templateResolver());
+ return templateEngine;
+ }
+
+ @Bean
+ public ViewResolver getViewResolver() {
+ ThymeleafViewResolver resolver = new ThymeleafViewResolver();
+ resolver.setOrder(1);
+ resolver.setViewNames(new String[]{"*.html"});
+ resolver.setTemplateEngine(templateEngine());
+ return resolver;
+ }
+
@PreDestroy
public void shutdown() {
okHttpClient.connectionPool().evictAll();
@@ -95,7 +122,7 @@ public void shutdown() {
*
* @return
*/
- @Bean(name="asyncTaskExecutor")
+ @Bean(name = "asyncTaskExecutor")
public AsyncTaskExecutor createMvcAsyncExecutor() {
ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
exec.setBeanName("asyncTaskExecutor");
@@ -106,7 +133,4 @@ public AsyncTaskExecutor createMvcAsyncExecutor() {
exec.afterPropertiesSet();
return exec;
}
-
-
-
}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/BlockController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/BlockController.java
index 9f1fa9ec..319f2b20 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/BlockController.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/BlockController.java
@@ -1,42 +1,51 @@
package com.jpmorgan.cakeshop.controller;
-import com.jpmorgan.cakeshop.config.JsonMethodArgumentResolver.JsonBodyParam;
import com.jpmorgan.cakeshop.error.APIException;
import com.jpmorgan.cakeshop.model.APIError;
import com.jpmorgan.cakeshop.model.APIResponse;
import com.jpmorgan.cakeshop.model.Block;
+import com.jpmorgan.cakeshop.model.json.BlockPostJsonRequest;
import com.jpmorgan.cakeshop.service.BlockService;
+import com.jpmorgan.cakeshop.util.StringUtils;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/api/block",
- method = RequestMethod.POST,
- consumes = MediaType.APPLICATION_JSON_VALUE,
- produces = MediaType.APPLICATION_JSON_VALUE)
+ method = RequestMethod.POST,
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
public class BlockController extends BaseController {
@Autowired
BlockService blockService;
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "id", required = false, value = "get block by id", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "hash", required = false, value = "get block by hash if id is missing", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "number", required = false, value = "get block by number if id or hash is missing", dataType = "java.lang.Long", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "tag", required = false, value = "get block by tag if id, hash and number are missing", dataType = "java.lang.String", paramType = "body")
+ })
@RequestMapping("/get")
- public ResponseEntity getBlock(
- @JsonBodyParam(required=false) String id,
- @JsonBodyParam(required=false) String hash,
- @JsonBodyParam(required=false) Long number,
- @JsonBodyParam(required=false) String tag) throws APIException {
-
- if (id == null && hash != null) {
- id = hash; // backwards compat
+ public ResponseEntity getBlock(@RequestBody BlockPostJsonRequest jsonRequest) throws APIException {
+
+ if (StringUtils.isBlank(jsonRequest.getId()) && StringUtils.isNotBlank(jsonRequest.getHash())) {
+ jsonRequest.setId(jsonRequest.getHash()); // backwards compat
}
- Block block = blockService.get(id, number, tag);
+ Block block = blockService.get(jsonRequest.getId(), jsonRequest.getNumber(), jsonRequest.getTag());
APIResponse res = new APIResponse();
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/ContractController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/ContractController.java
index 5f274ccf..91a6a7f5 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/ContractController.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/ContractController.java
@@ -1,6 +1,5 @@
package com.jpmorgan.cakeshop.controller;
-import com.jpmorgan.cakeshop.config.JsonMethodArgumentResolver.JsonBodyParam;
import com.jpmorgan.cakeshop.error.APIException;
import com.jpmorgan.cakeshop.model.APIData;
import com.jpmorgan.cakeshop.model.APIError;
@@ -13,9 +12,11 @@
import com.jpmorgan.cakeshop.model.Transaction;
import com.jpmorgan.cakeshop.model.TransactionRequest;
import com.jpmorgan.cakeshop.model.TransactionResult;
-import com.jpmorgan.cakeshop.service.ContractRegistryService;
+import com.jpmorgan.cakeshop.model.json.ContractPostJsonRequest;
import com.jpmorgan.cakeshop.service.ContractService;
import com.jpmorgan.cakeshop.service.ContractService.CodeType;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
import java.util.ArrayList;
import java.util.List;
@@ -26,6 +27,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@@ -33,24 +35,21 @@
@RestController
@RequestMapping(value = "/api/contract",
- method = RequestMethod.POST,
- consumes = MediaType.APPLICATION_JSON_VALUE,
- produces = MediaType.APPLICATION_JSON_VALUE)
+ method = RequestMethod.POST,
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
public class ContractController extends BaseController {
- private static final String DEFAULT_CODE_TYPE = "solidity";
-
@Autowired
private ContractService contractService;
- @Autowired
- private ContractRegistryService contractRegistry;
-
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "address", required = true, value = "Contract address", dataType = "java.lang.String", paramType = "body")
+ })
@RequestMapping("/get")
- public ResponseEntity getContract(
- @JsonBodyParam String address) throws APIException {
+ public ResponseEntity getContract(@RequestBody ContractPostJsonRequest jsonRequest) throws APIException {
- Contract contract = contractService.get(address);
+ Contract contract = contractService.get(jsonRequest.getAddress());
APIResponse res = new APIResponse();
@@ -66,14 +65,18 @@ public ResponseEntity getContract(
return new ResponseEntity<>(res, HttpStatus.NOT_FOUND);
}
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "code", required = false, value = "Required. Contract code", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "code_type", required = false, value = "Required. Only solidity is supported for now.", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "optimize", required = false, value = "Optimize code.", dataType = "java.lang.Boollean", paramType = "body")
+ })
@RequestMapping("/compile")
- public ResponseEntity compile(
- @JsonBodyParam String code,
- @JsonBodyParam(defaultValue=DEFAULT_CODE_TYPE) String code_type,
- @JsonBodyParam(required=false) Boolean optimize) throws APIException {
-
- List contracts = contractService.compile(code, CodeType.valueOf(code_type), optimize);
+ public ResponseEntity compile(@RequestBody ContractPostJsonRequest jsonRequest) throws APIException {
+ List contracts = contractService.compile(jsonRequest.getCode(),
+ CodeType.valueOf(jsonRequest.getCode_type()), jsonRequest.getOptimize());
APIResponse res = new APIResponse();
if (contracts != null) {
@@ -88,19 +91,27 @@ public ResponseEntity compile(
return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST);
}
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "code", required = false, value = "Required. Contract code", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "code_type", required = false, value = "Required. Only solidity is supported for now.", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "from", required = false, value = "Required. Account from address", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "args", required = false, value = "Required. Function arguments to pass", dataType = "java.util.Arrays", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "binary", required = false, value = "Bynary contract code.", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "privateFrom", required = false, value = "Private from Account", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "privateFor", required = false, value = "Private for Account", dataType = "java.lang.String", paramType = "body")
+ })
@RequestMapping("/create")
- public ResponseEntity create(
- @JsonBodyParam String from,
- @JsonBodyParam String code,
- @JsonBodyParam(defaultValue=DEFAULT_CODE_TYPE) String code_type,
- @JsonBodyParam(required=false) Object[] args,
- @JsonBodyParam(required=false) String binary,
- @JsonBodyParam(required=false) Boolean optimize,
- @JsonBodyParam final String privateFrom,
- @JsonBodyParam final List privateFor) throws APIException {
-
- TransactionResult tx = contractService.create(from, code, CodeType.valueOf(code_type), args, binary,
- privateFrom, privateFor);
+ public ResponseEntity create(@RequestBody ContractPostJsonRequest jsonRequest) throws APIException {
+
+ TransactionResult tx = contractService.create(jsonRequest.getFrom(), jsonRequest.getCode(),
+ CodeType.valueOf(jsonRequest.getCode_type()), jsonRequest.getArgs(), jsonRequest.getBinary(),
+ jsonRequest.getPrivateFrom(), jsonRequest.getPrivateFor());
APIResponse res = new APIResponse();
@@ -125,15 +136,20 @@ public ResponseEntity list() throws APIException {
return new ResponseEntity<>(res, HttpStatus.OK);
}
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "from", required = false, value = "Required. Account from address", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "address", required = false, value = "Required. Account address", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "method", required = false, value = "Required. Contract method to execute.", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "args", required = false, value = "Required. Args for contratc method", dataType = "java.util.Arrays", paramType = "body")
+ })
@RequestMapping("/read")
- public ResponseEntity read(
- @JsonBodyParam String from,
- @JsonBodyParam String address,
- @JsonBodyParam String method,
- @JsonBodyParam Object[] args,
- @JsonBodyParam(required=false) Object blockNumber) throws APIException {
-
- Object result = contractService.read(createTransactionRequest(from, address, method, args, true, blockNumber));
+ public ResponseEntity read(@RequestBody ContractPostJsonRequest jsonRequest) throws APIException {
+
+ Object result = contractService.read(createTransactionRequest(jsonRequest.getFrom(), jsonRequest.getAddress(),
+ jsonRequest.getMethod(), jsonRequest.getArgs(), true, jsonRequest.getBlockNumber()));
APIResponse res = new APIResponse();
res.setData(result);
@@ -157,8 +173,8 @@ private TransactionRequest createTransactionRequest(String from, String address,
}
/**
- * Handle Base64 encoded byte/string inputs (byte arrays must be base64 encoded to put them on
- * the wire w/ JSON)
+ * Handle Base64 encoded byte/string inputs (byte arrays must be base64
+ * encoded to put them on the wire w/ JSON)
*
* @param method
* @param args
@@ -182,43 +198,50 @@ private Object[] decodeArgs(Function method, Object[] args) throws APIException
return args;
}
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "from", required = false, value = "Required. Account from address", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "address", required = false, value = "Required. Account address", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "method", required = false, value = "Required. Contract method to execute.", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "args", required = false, value = "Required. Args for contract method", dataType = "java.util.Arrays", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "privateFrom", required = false, value = "Account private from", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "privateFor", required = false, value = "Account private for", dataType = "java.lang.String", paramType = "body")
+ })
@RequestMapping("/transact")
- public WebAsyncTask> transact(
- @JsonBodyParam final String from,
- @JsonBodyParam final String address,
- @JsonBodyParam final String method,
- @JsonBodyParam final Object[] args,
- @JsonBodyParam final String privateFrom,
- @JsonBodyParam final List privateFor) throws APIException {
-
- Callable> callable = new Callable>() {
- @Override
- public ResponseEntity call() throws Exception {
- TransactionRequest req = createTransactionRequest(from, address, method, args, false, null);
- req.setPrivateFrom(privateFrom);
- req.setPrivateFor(privateFor);
-
- TransactionResult tr = contractService.transact(req);
- APIResponse res = new APIResponse();
- res.setData(tr.toAPIData());
- ResponseEntity response = new ResponseEntity<>(res, HttpStatus.OK);
- return response;
- }
+ public WebAsyncTask> transact(@RequestBody ContractPostJsonRequest jsonRequest) throws APIException {
+
+ Callable> callable = () -> {
+ TransactionRequest req = createTransactionRequest(jsonRequest.getFrom(), jsonRequest.getAddress(),
+ jsonRequest.getMethod(), jsonRequest.getArgs(), false, null);
+ req.setPrivateFrom(jsonRequest.getPrivateFrom());
+ req.setPrivateFor(jsonRequest.getPrivateFor());
+
+ TransactionResult tr = contractService.transact(req);
+ APIResponse res = new APIResponse();
+ res.setData(tr.toAPIData());
+ ResponseEntity response = new ResponseEntity<>(res, HttpStatus.OK);
+ return response;
};
WebAsyncTask asyncTask = new WebAsyncTask(callable);
return asyncTask;
}
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "address", required = false, value = "Required. Contract Address", dataType = "java.lang.String", paramType = "body")
+ })
@RequestMapping("/transactions/list")
- public ResponseEntity listTransactions(
- @JsonBodyParam String address) throws APIException {
+ public ResponseEntity listTransactions(@RequestBody final ContractPostJsonRequest jsonRequest) throws APIException {
- List txns = contractService.listTransactions(address);
+ List txns = contractService.listTransactions(jsonRequest.getAddress());
List data = new ArrayList<>();
- for (Transaction tx : txns) {
+ txns.forEach((tx) -> {
data.add(tx.toAPIData());
- }
+ });
APIResponse res = new APIResponse();
res.setData(data);
@@ -226,16 +249,15 @@ public ResponseEntity listTransactions(
return new ResponseEntity<>(res, HttpStatus.OK);
}
-
private APIData toAPIData(Contract c) {
return new APIData(c.getAddress(), Contract.API_DATA_TYPE, c);
}
private List toAPIData(List contracts) {
List data = new ArrayList<>();
- for (Contract c : contracts) {
- data.add(toAPIData(c));
- }
+ contracts.forEach((c) -> {
+ data.add(toAPIData(c));
+ });
return data;
}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/LogViewController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/LogViewController.java
new file mode 100644
index 00000000..4bf3de73
--- /dev/null
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/LogViewController.java
@@ -0,0 +1,84 @@
+package com.jpmorgan.cakeshop.controller;
+
+import com.jpmorgan.cakeshop.bean.GethConfigBean;
+import com.jpmorgan.cakeshop.error.APIException;
+import com.jpmorgan.cakeshop.model.json.LogViewJsonRequest;
+import com.jpmorgan.cakeshop.service.LogViewService;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import java.util.Deque;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+import static org.springframework.http.HttpStatus.OK;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+@Controller
+@RequestMapping(value = "/api/log", method = RequestMethod.POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
+public class LogViewController {
+
+ private final Integer DEFAULT_NUMBER_LINES = 500;
+
+ private final String LOG_PREFIX = System.getProperty("logging.path");
+
+ private final String CONSTELLATION_PATH = com.jpmorgan.cakeshop.util.StringUtils.isNotBlank(System.getProperty("spring.config.location"))
+ ? System.getProperty("spring.config.location").replaceAll("file:", "")
+ .replaceAll("application.properties", "").concat("constellation-node/")
+ : null;
+
+ @Autowired
+ private GethConfigBean gethConfig;
+
+ @Autowired
+ private LogViewService service;
+
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "logFileName", required = false, value = "Required. Name of the log to view", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "logType", required = false, value = "Required. What kind og log", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "numberLines", required = false, value = "Required. Number of last lines to view", dataType = "java.lang.Integer", paramType = "body")
+ })
+ @RequestMapping("/view")
+ public ResponseEntity getLog(@RequestBody LogViewJsonRequest jsonRequest) throws APIException {
+
+ String logPath = getLogPath(jsonRequest.getLogType(), jsonRequest.getLogFileName());
+
+ if (StringUtils.isNotBlank(logPath)) {
+ Deque log = service.getLog(logPath, jsonRequest.getNumberLines() != null ? jsonRequest.getNumberLines() : DEFAULT_NUMBER_LINES);
+ return new ResponseEntity(log, OK);
+ } else {
+ return new ResponseEntity(jsonRequest.getLogType(), BAD_REQUEST);
+ }
+ }
+
+ private String getLogPath(String logType, String logFileName) {
+ String logPath;
+ if (StringUtils.isNotBlank(logType)) {
+ switch (logType) {
+ case "constellation":
+ logPath = StringUtils.isNotBlank(CONSTELLATION_PATH)
+ ? CONSTELLATION_PATH.concat("logs/")
+ .concat(StringUtils.isNotBlank(logFileName) ? logFileName : "constellation.log")
+ : gethConfig.getDataDirPath().concat("/constellation/logs/")
+ .concat(StringUtils.isNotBlank(logFileName) ? logFileName : "constellation.log");
+ break;
+ case "geth":
+ logPath = LOG_PREFIX.concat("/").concat(StringUtils.isNotBlank(logFileName) ? logFileName : "geth.log");
+ break;
+ default:
+ logPath = null;
+ break;
+ }
+
+ } else {
+ return null;
+ }
+ return logPath;
+ }
+}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/LoginController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/LoginController.java
new file mode 100644
index 00000000..6a8f2f02
--- /dev/null
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/LoginController.java
@@ -0,0 +1,66 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jpmorgan.cakeshop.controller;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import static org.springframework.web.bind.annotation.RequestMethod.GET;
+
+@Controller
+public class LoginController {
+
+ @RequestMapping(value = "/login", method = GET)
+ public String loginView() {
+ return "login";
+ }
+
+ @RequestMapping(value = "/logout", method = GET)
+ public String logout(HttpServletRequest request, HttpServletResponse response) {
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+
+ if (auth != null) {
+ new SecurityContextLogoutHandler().logout(request, response, auth);
+ }
+
+ return "login";
+ }
+
+ @RequestMapping(value = "/user", method = GET)
+ public ResponseEntity userInfo(HttpServletRequest request, HttpServletResponse response) {
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+
+ Boolean securityEnabled
+ = StringUtils.isNotBlank(System.getProperty("cakeshop.security.enabled"))
+ ? Boolean.valueOf(System.getProperty("cakeshop.security.enabled"))
+ : false;
+
+ String userName = null;
+ if (null != auth && auth.getCredentials() instanceof String) {
+ userName = auth.getCredentials().toString();
+ }
+
+ Map userInfo = new HashMap<>();
+
+ if (StringUtils.isNotBlank(userName)) {
+ userInfo.put("username", userName);
+ } else {
+ userInfo.put("loggedout", Boolean.TRUE);
+ }
+ userInfo.put("securityEnabled", securityEnabled);
+ return new ResponseEntity(userInfo, HttpStatus.OK);
+ }
+
+}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/NodeController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/NodeController.java
index 1666845c..71ff37f7 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/NodeController.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/NodeController.java
@@ -1,23 +1,36 @@
package com.jpmorgan.cakeshop.controller;
-import com.jpmorgan.cakeshop.config.JsonMethodArgumentResolver.JsonBodyParam;
+import com.jpmorgan.cakeshop.bean.GethConfigBean;
import com.jpmorgan.cakeshop.error.APIException;
import com.jpmorgan.cakeshop.model.APIData;
import com.jpmorgan.cakeshop.model.APIError;
import com.jpmorgan.cakeshop.model.APIResponse;
+import com.jpmorgan.cakeshop.model.ContractABI;
import com.jpmorgan.cakeshop.model.Node;
+import com.jpmorgan.cakeshop.model.NodeSettings;
import com.jpmorgan.cakeshop.model.Peer;
+import com.jpmorgan.cakeshop.model.TransactionRequest;
+import com.jpmorgan.cakeshop.model.json.NodePostJsonRequest;
+import com.jpmorgan.cakeshop.service.ContractService;
import com.jpmorgan.cakeshop.service.GethHttpService;
import com.jpmorgan.cakeshop.service.NodeService;
+import com.jpmorgan.cakeshop.util.FileUtils;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@@ -28,19 +41,29 @@
* @author Samer Falah
*/
@RestController
-@RequestMapping(value = "/api/node",
- method = RequestMethod.POST,
- consumes = MediaType.APPLICATION_JSON_VALUE,
- produces = MediaType.APPLICATION_JSON_VALUE)
+@RequestMapping(value = "/api/node", method = RequestMethod.POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public class NodeController extends BaseController {
+ private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(NodeController.class);
+
@Autowired
private GethHttpService gethService;
@Autowired
private NodeService nodeService;
- @RequestMapping({ "/get" })
+ @Autowired
+ private ContractService contractService;
+ @Autowired
+ private GethConfigBean gethConfig;
+
+ private ContractABI voterAbi;
+
+ public NodeController() throws IOException {
+ voterAbi = ContractABI.fromJson(FileUtils.readClasspathFile("contracts/BlockVoting.sol.json"));
+ }
+
+ @RequestMapping({"/get"})
protected ResponseEntity doGet() throws APIException {
Node node = nodeService.get();
@@ -51,40 +74,72 @@ protected ResponseEntity doGet() throws APIException {
return new ResponseEntity<>(apiResponse, HttpStatus.OK);
}
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "extraParams", required = false, value = "Extra params to start geth", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "genesisBlock", required = false, value = "Genesis block", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "blockMakesAccount", required = false, value = "Block maker account (Quorum only)", dataType = "java.lang.Integer", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "voterAccount", required = false, value = "Voter account (Quorum only)", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "minBlockTime", required = false, value = "Minimial time to generete new Block (Quorum only)", dataType = "java.lang.Integer", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "maxBlockTime", required = false, value = "Maximum time to generete new Block (Quorum only)", dataType = "java.lang.Integer", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "logLevel", required = false, value = "Log verbosity level", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "networkId", required = false, value = "Network Id", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "committingTransactions", required = false, value = "Commit transactions true/false", dataType = "java.lang.Object", paramType = "body")
+ })
@RequestMapping("/update")
- public ResponseEntity update(
- @JsonBodyParam(required = false) String logLevel,
- @JsonBodyParam(required = false) String networkId,
- @JsonBodyParam(required = false) String identity,
- @JsonBodyParam(required = false) Object committingTransactions,
- @JsonBodyParam(required = false) String extraParams,
- @JsonBodyParam(required = false) String genesisBlock) throws APIException {
+ public ResponseEntity update(@RequestBody NodePostJsonRequest jsonRequest) throws APIException {
- Boolean isMining = null;
+ Boolean isMining;
+ NodeSettings nodeSettings = new NodeSettings().extraParams(jsonRequest.getExtraParams()).genesisBlock(jsonRequest.getGenesisBlock())
+ .blockMakerAccount(jsonRequest.getBlockMakerAccount()).voterAccount(jsonRequest.getVoterAccount()).minBlockTime(jsonRequest.getMinBlockTime())
+ .maxBlockTime(jsonRequest.getMaxBlockTime());
try {
- Integer logLevelInt = null,
- networkIDInt = null;
-
- if (!StringUtils.isEmpty(logLevel)) {
- logLevelInt = Integer.parseInt(logLevel);
+ if (!StringUtils.isEmpty(jsonRequest.getLogLevel())) {
+ nodeSettings.logLevel(Integer.parseInt(jsonRequest.getLogLevel()));
}
- if (!StringUtils.isEmpty(networkId)) {
- networkIDInt = Integer.parseInt(networkId);
+ if (!StringUtils.isEmpty(jsonRequest.getNetworkId())) {
+ nodeSettings.networkId(Integer.parseInt(jsonRequest.getNetworkId()));
}
- if (committingTransactions != null) {
- if (committingTransactions instanceof String && StringUtils.isNotBlank((String) committingTransactions)) {
- isMining = Boolean.parseBoolean((String) committingTransactions);
+ if (jsonRequest.getCommittingTransactions() != null) {
+
+ if (jsonRequest.getCommittingTransactions() instanceof String
+ && StringUtils.isNotBlank((String) jsonRequest.getCommittingTransactions())) {
+ isMining = Boolean.parseBoolean((String) jsonRequest.getCommittingTransactions());
} else {
- isMining = (Boolean) committingTransactions;
+ isMining = (Boolean) jsonRequest.getCommittingTransactions();
}
+ nodeSettings.setIsMining(isMining);
+ }
+
+ if (StringUtils.isNotBlank(nodeSettings.getBlockMakerAccount())
+ && (StringUtils.isNotBlank(gethConfig.getBlockMaker())
+ && !nodeSettings.getBlockMakerAccount().contentEquals(gethConfig.getBlockMaker()))) {
+ updateVoteContract(gethConfig.getBlockMaker(), "addBlockMaker",
+ new Object[]{nodeSettings.getBlockMakerAccount()});
+ updateVoteContract(nodeSettings.getBlockMakerAccount(), "removeBlockMaker",
+ new Object[]{gethConfig.getBlockMaker()});
}
- nodeService.update(logLevelInt, networkIDInt, identity, isMining,
- extraParams, genesisBlock);
+ if (StringUtils.isNotBlank(nodeSettings.getVoterAccount())
+ && (StringUtils.isNotBlank(gethConfig.getVoteAccount())
+ && !nodeSettings.getVoterAccount().contentEquals(gethConfig.getVoteAccount()))) {
+ updateVoteContract(gethConfig.getVoteAccount(), "addVoter",
+ new Object[]{nodeSettings.getVoterAccount()});
+ updateVoteContract(nodeSettings.getVoterAccount(), "removeVoter",
+ new Object[]{gethConfig.getVoteAccount()});
+ }
+ nodeService.update(nodeSettings);
return doGet();
@@ -100,14 +155,16 @@ public ResponseEntity update(
}
}
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "address", required = false, value = "Required. External node address to add", dataType = "java.lang.String", paramType = "body")
+ })
@RequestMapping("/peers/add")
- public ResponseEntity addPeer(@JsonBodyParam String address) throws APIException {
- if (StringUtils.isBlank(address)) {
- return new ResponseEntity<>(
- new APIResponse().error(new APIError().title("Missing param 'address'")),
+ public ResponseEntity addPeer(@RequestBody NodePostJsonRequest jsonRequest) throws APIException {
+ if (StringUtils.isBlank(jsonRequest.getAddress())) {
+ return new ResponseEntity<>(new APIResponse().error(new APIError().title("Missing param 'address'")),
HttpStatus.BAD_REQUEST);
}
- boolean added = nodeService.addPeer(address);
+ boolean added = nodeService.addPeer(jsonRequest.getAddress());
return new ResponseEntity<>(APIResponse.newSimpleResponse(added), HttpStatus.OK);
}
@@ -126,20 +183,23 @@ public ResponseEntity peers() throws APIException {
}
@RequestMapping("/start")
- protected @ResponseBody ResponseEntity startGeth() {
+ protected @ResponseBody
+ ResponseEntity startGeth() {
Boolean started = gethService.start();
return new ResponseEntity<>(APIResponse.newSimpleResponse(started), HttpStatus.OK);
}
@RequestMapping("/stop")
- protected @ResponseBody ResponseEntity stopGeth() {
+ protected @ResponseBody
+ ResponseEntity stopGeth() {
Boolean stopped = gethService.stop();
gethService.deletePid();
return new ResponseEntity<>(APIResponse.newSimpleResponse(stopped), HttpStatus.OK);
}
@RequestMapping("/restart")
- protected @ResponseBody ResponseEntity restartGeth() {
+ protected @ResponseBody
+ ResponseEntity restartGeth() {
Boolean stopped = gethService.stop();
Boolean deleted = gethService.deletePid();
Boolean restarted = false;
@@ -150,15 +210,83 @@ public ResponseEntity peers() throws APIException {
}
@RequestMapping("/reset")
- protected @ResponseBody ResponseEntity resetGeth() {
+ protected @ResponseBody
+ ResponseEntity resetGeth() {
Boolean reset = gethService.reset();
return new ResponseEntity<>(APIResponse.newSimpleResponse(reset), HttpStatus.OK);
}
@RequestMapping("/settings/reset")
- protected @ResponseBody ResponseEntity resetNodeInfo() {
+ protected @ResponseBody
+ ResponseEntity resetNodeInfo() {
Boolean reset = nodeService.reset();
return new ResponseEntity<>(APIResponse.newSimpleResponse(reset), HttpStatus.OK);
}
+ @RequestMapping("/constellation/list")
+ protected @ResponseBody
+ ResponseEntity getConstellationList() throws APIException {
+ Map constellations = nodeService.getConstellationNodes();
+ return new ResponseEntity<>(APIResponse.newSimpleResponse(constellations), HttpStatus.OK);
+ }
+
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "constellationNode", required = false, value = "Required. External constellation address(Quorum only)", dataType = "java.lang.String", paramType = "body")
+ })
+ @RequestMapping("/constellation/add")
+ protected @ResponseBody
+ ResponseEntity addConstellation(@RequestBody NodePostJsonRequest jsonRequest)
+ throws APIException {
+ nodeService.addConstellationNode(jsonRequest.getConstellationNode());
+ return doGet();
+ }
+
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "constellationNode", required = false, value = "Required. External constellation address(Quorum only)", dataType = "java.lang.String", paramType = "body")
+ })
+ @RequestMapping("/constellation/remove")
+ protected @ResponseBody
+ ResponseEntity removeConstellation(@RequestBody NodePostJsonRequest jsonRequest)
+ throws APIException {
+ nodeService.removeConstellationNode(jsonRequest.getConstellationNode());
+ return doGet();
+ }
+
+ @RequestMapping("/constellation/stop")
+ protected @ResponseBody
+ ResponseEntity stopConstellation() throws APIException {
+ Boolean stopped = gethService.stopConstellation();
+ gethConfig.setConstellationEnabled(false);
+ try {
+ gethConfig.save();
+ } catch (IOException ex) {
+ throw new APIException(ex);
+ }
+ return new ResponseEntity<>(APIResponse.newSimpleResponse(stopped), HttpStatus.OK);
+ }
+
+ @RequestMapping("/constellation/start")
+ protected @ResponseBody
+ ResponseEntity startConstellation() throws APIException {
+ Boolean started = gethService.startConstellation();
+ gethConfig.setConstellationEnabled(true);
+ try {
+ gethConfig.save();
+ } catch (IOException ex) {
+ throw new APIException(ex);
+ }
+ return new ResponseEntity<>(APIResponse.newSimpleResponse(started), HttpStatus.OK);
+ }
+
+ private void updateVoteContract(String from, String method, Object[] args) throws APIException {
+ String address = gethConfig.getVoteContractAddress();
+ TransactionRequest request = new TransactionRequest(from, address, voterAbi, method, args, false);
+ contractService.transact(request);
+ try {
+ TimeUnit.SECONDS.sleep(gethConfig.getMaxBlockTime());
+ } catch (InterruptedException ex) {
+ LOG.error(from, ex);
+ }
+ }
+
}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/TransactionController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/TransactionController.java
index ee96d241..9d1cd062 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/TransactionController.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/TransactionController.java
@@ -1,6 +1,5 @@
package com.jpmorgan.cakeshop.controller;
-import com.jpmorgan.cakeshop.config.JsonMethodArgumentResolver.JsonBodyParam;
import com.jpmorgan.cakeshop.error.APIException;
import com.jpmorgan.cakeshop.model.APIData;
import com.jpmorgan.cakeshop.model.APIError;
@@ -8,7 +7,10 @@
import com.jpmorgan.cakeshop.model.DirectTransactionRequest;
import com.jpmorgan.cakeshop.model.Transaction;
import com.jpmorgan.cakeshop.model.TransactionResult;
+import com.jpmorgan.cakeshop.model.json.TransPostJsonResquest;
import com.jpmorgan.cakeshop.service.TransactionService;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
import java.util.ArrayList;
import java.util.List;
@@ -18,6 +20,8 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@@ -25,20 +29,27 @@
@RestController
@RequestMapping(value = "/api/transaction",
- method = RequestMethod.POST,
- consumes = MediaType.APPLICATION_JSON_VALUE,
- produces = MediaType.APPLICATION_JSON_VALUE)
+ method = RequestMethod.POST,
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
public class TransactionController extends BaseController {
@Autowired
private TransactionService transactionService;
-
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "id", required = false, value = "Required.Transaction Id", dataType = "java.lang.String", paramType = "body")
+ })
@RequestMapping("/get")
public ResponseEntity getTransaction(
- @JsonBodyParam(required=true) String id) throws APIException {
+ @RequestBody TransPostJsonResquest jsonRequest) throws APIException {
+
+ if (StringUtils.isEmpty(jsonRequest.getId())) {
+ return new ResponseEntity<>(new APIResponse().error(new APIError().title("Missing param 'id'")),
+ HttpStatus.BAD_REQUEST);
+ }
- Transaction tx = transactionService.get(id);
+ Transaction tx = transactionService.get(jsonRequest.getId());
APIResponse res = new APIResponse();
@@ -55,18 +66,25 @@ public ResponseEntity getTransaction(
return new ResponseEntity<>(res, HttpStatus.NOT_FOUND);
}
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "ids", required = false, value = "Required. Hash or Transaction receipt", dataType = "java.util.List", paramType = "body")
+ })
@RequestMapping("/list")
public ResponseEntity getTransactionList(
- @JsonBodyParam(required=true) List ids) throws APIException {
+ @RequestBody TransPostJsonResquest jsonRequest) throws APIException {
- List txns = transactionService.get(ids);
- List data = new ArrayList<>();
+ if (null == jsonRequest.getIds() || jsonRequest.getIds().isEmpty()) {
+ return new ResponseEntity<>(new APIResponse().error(new APIError().title("Missing param 'id'")),
+ HttpStatus.BAD_REQUEST);
+ }
+ List txns = transactionService.get(jsonRequest.getIds());
+ List data = new ArrayList<>();
APIResponse res = new APIResponse();
if (txns != null && !txns.isEmpty()) {
- for (Transaction txn : txns) {
+ txns.forEach((txn) -> {
data.add(txn.toAPIData());
- }
+ });
res.setData(data);
return new ResponseEntity<>(res, HttpStatus.OK);
}
@@ -79,31 +97,35 @@ public ResponseEntity getTransactionList(
return new ResponseEntity<>(res, HttpStatus.NOT_FOUND);
}
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "from", required = false, value = "Required. Account from which transaction initiated", dataType = "java.ulang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "to", required = false, value = "Required. Account to which transaction is going to", dataType = "java.ulang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "data", required = false, value = "Required. Transaction data", dataType = "java.ulang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "privateFrom", required = false, value = "Account transaction private from", dataType = "java.ulang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "privateFor", required = false, value = "Account transsaction private for", dataType = "java.ulang.String", paramType = "body")
+ })
@RequestMapping("/save")
public WebAsyncTask> transact(
- @JsonBodyParam final String from,
- @JsonBodyParam final String to,
- @JsonBodyParam final String data,
- @JsonBodyParam final String privateFrom,
- @JsonBodyParam final List privateFor) throws APIException {
-
- Callable> callable = new Callable>() {
- @Override
- public ResponseEntity call() throws Exception {
- DirectTransactionRequest req = new DirectTransactionRequest(from, to, data, false);
- req.setPrivateFrom(privateFrom);
- req.setPrivateFor(privateFor);
-
- TransactionResult result = transactionService.directTransact(req);
- APIResponse res = new APIResponse();
- res.setData(result.toAPIData());
- ResponseEntity response = new ResponseEntity<>(res, HttpStatus.OK);
- return response;
- }
+ @RequestBody final TransPostJsonResquest jsonRequest) throws APIException {
+
+ Callable> callable = () -> {
+ DirectTransactionRequest req = new DirectTransactionRequest(jsonRequest.getFrom(),
+ jsonRequest.getTo(), jsonRequest.getData(), false);
+ req.setPrivateFrom(jsonRequest.getPrivateFrom());
+ req.setPrivateFor(jsonRequest.getPrivateFor());
+
+ TransactionResult result = transactionService.directTransact(req);
+ APIResponse res = new APIResponse();
+ res.setData(result.toAPIData());
+ ResponseEntity response = new ResponseEntity<>(res, HttpStatus.OK);
+ return response;
};
WebAsyncTask asyncTask = new WebAsyncTask(callable);
return asyncTask;
}
-
}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/WalletController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/WalletController.java
index bf385bd2..889db6fd 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/WalletController.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/WalletController.java
@@ -5,24 +5,29 @@
import com.jpmorgan.cakeshop.model.APIError;
import com.jpmorgan.cakeshop.model.APIResponse;
import com.jpmorgan.cakeshop.model.Account;
+import com.jpmorgan.cakeshop.model.json.WalletPostJsonRequest;
import com.jpmorgan.cakeshop.service.WalletService;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
import java.util.ArrayList;
import java.util.List;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/api/wallet",
- method = RequestMethod.POST,
- consumes = MediaType.APPLICATION_JSON_VALUE,
- produces = MediaType.APPLICATION_JSON_VALUE)
+ method = RequestMethod.POST,
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
public class WalletController extends BaseController {
@Autowired
@@ -36,9 +41,9 @@ public ResponseEntity getAccounts() throws APIException {
List accounts = walletService.list();
if (accounts != null) {
List data = new ArrayList<>();
- for (Account a : accounts) {
- data.add(new APIData(a.getAddress(), "wallet", a));
- }
+ accounts.forEach((account) -> {
+ data.add(new APIData(account.getAddress(), "wallet", account));
+ });
res.setData(data);
return new ResponseEntity<>(res, HttpStatus.OK);
}
@@ -59,4 +64,70 @@ public ResponseEntity create() throws APIException {
return new ResponseEntity<>(res, HttpStatus.OK);
}
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "account", required = false, value = "Required. Account to unlock", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "accountPassword", required = false, value = "Password used to create account. Required only if account was created with one.", dataType = "java.lang.String", paramType = "body")
+ })
+ @RequestMapping("/unlock")
+ public ResponseEntity unlock(@RequestBody WalletPostJsonRequest request) throws APIException {
+ APIResponse res;
+ if (StringUtils.isBlank(request.getAccount())) {
+ res = new APIResponse();
+ APIError err = new APIError();
+ err.setStatus("400");
+ err.setTitle("Bad request");
+ res.addError(err);
+ return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST);
+ } else {
+ res = APIResponse.newSimpleResponse(walletService.unlockAccount(request));
+ return new ResponseEntity<>(res, HttpStatus.OK);
+ }
+ }
+
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "account", required = false, value = "Required. Account to lock", dataType = "java.lang.String", paramType = "body")
+ })
+ @RequestMapping("/lock")
+ public ResponseEntity lock(@RequestBody WalletPostJsonRequest request) throws APIException {
+
+ APIResponse res;
+ if (StringUtils.isBlank(request.getAccount())) {
+ res = new APIResponse();
+ APIError err = new APIError();
+ err.setStatus("400");
+ err.setTitle("Bad request");
+ res.addError(err);
+ return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST);
+ } else {
+ res = APIResponse.newSimpleResponse(walletService.lockAccount(request));
+ return new ResponseEntity<>(res, HttpStatus.OK);
+ }
+ }
+
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "fromAccount", required = false, value = "Required. Account to fund from", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "account", required = false, value = "Required. Account to fund", dataType = "java.lang.String", paramType = "body")
+ ,
+ @ApiImplicitParam(name = "newBalance", required = false, value = "Required. Fund amount", dataType = "java.lang.Long", paramType = "body")
+ })
+ @RequestMapping("/fund")
+ public ResponseEntity fundAccount(@RequestBody WalletPostJsonRequest request) throws APIException {
+
+ APIResponse res;
+ if (StringUtils.isBlank(request.getFromAccount()) || StringUtils.isBlank(request.getAccount())
+ || null == request.getNewBalance()) {
+ res = new APIResponse();
+ APIError err = new APIError();
+ err.setStatus("400");
+ err.setTitle("Bad request");
+ res.addError(err);
+ return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST);
+ } else {
+ res = APIResponse.newSimpleResponse(walletService.fundAccount(request));
+ return new ResponseEntity<>(res, HttpStatus.OK);
+ }
+ }
+
}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/dao/UserDAO.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/dao/UserDAO.java
new file mode 100644
index 00000000..c990341c
--- /dev/null
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/dao/UserDAO.java
@@ -0,0 +1,74 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jpmorgan.cakeshop.dao;
+
+import com.jpmorgan.cakeshop.model.User;
+import org.hibernate.Session;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public class UserDAO extends BaseDAO {
+
+ private final PasswordEncoder ENCODER = new BCryptPasswordEncoder();
+
+ @Transactional
+ public User getUser(String userName) {
+ if (null != getCurrentSession()) {
+ return getCurrentSession().get(User.class, userName);
+ }
+ return null;
+ }
+
+ @Transactional
+ public Boolean save(User user) {
+ if (null != getCurrentSession()) {
+ if (null != getCurrentSession().get(User.class, user.getUserName())) {
+ return false;
+ } else {
+ user.setPassword(ENCODER.encode(user.getPassword()));
+ getCurrentSession().save(user);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Transactional
+ public Boolean authenticate(String userName, String password) {
+ if (null != getCurrentSession()) {
+ User user = getCurrentSession().get(User.class, userName);
+ if (ENCODER.matches(password, user.getPassword())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Transactional
+ public void resetPassword(String userName, String newPassword) {
+ if (null != getCurrentSession()) {
+ User user = getCurrentSession().get(User.class, userName);
+ if (null != user) {
+ user.setPassword(ENCODER.encode(newPassword));
+ getCurrentSession().update(user);
+ }
+ }
+ }
+
+ @Override
+ public void reset() {
+ if (null != getCurrentSession()) {
+ Session session = getCurrentSession();
+ session.createSQLQuery("TRUNCATE TABLE USERS").executeUpdate();
+ session.flush();
+ session.clear();
+ }
+ }
+
+}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/Account.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/Account.java
index ef38fa46..f05ea13b 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/Account.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/Account.java
@@ -5,12 +5,13 @@
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
+import javax.persistence.Transient;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@Entity
-@Table(name="ACCOUNTS")
+@Table(name = "ACCOUNTS")
public class Account implements Serializable {
/**
@@ -22,9 +23,12 @@ public class Account implements Serializable {
* Ethereum account address
*/
@Id
- String address;
+ private String address;
- String balance;
+ private String balance;
+
+ @Transient
+ private Boolean unlocked;
public String getAddress() {
return address;
@@ -46,4 +50,12 @@ public void setBalance(String balance) {
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
+
+ public Boolean isUnlocked() {
+ return unlocked;
+ }
+
+ public void setUnlocked(Boolean unlocked) {
+ this.unlocked = unlocked;
+ }
}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/NodeSettings.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/NodeSettings.java
new file mode 100644
index 00000000..feba7ab3
--- /dev/null
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/NodeSettings.java
@@ -0,0 +1,208 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jpmorgan.cakeshop.model;
+
+public class NodeSettings {
+
+ private String identity, extraParams, genesisBlock, blockMakerAccount, voterAccount;
+ private Integer minBlockTime, maxBlockTime, logLevel, networkId;
+ private Boolean isMining;
+
+ public NodeSettings() {
+
+ }
+
+ /**
+ * @return the logLevel
+ */
+ public Integer getLogLevel() {
+ return logLevel;
+ }
+
+ /**
+ * @param logLevel the logLevel to set
+ */
+ public void setLogLevel(Integer logLevel) {
+ this.logLevel = logLevel;
+ }
+
+ public NodeSettings logLevel(Integer logLevel) {
+ this.logLevel = logLevel;
+ return this;
+ }
+
+ /**
+ * @return the networkId
+ */
+ public Integer getNetworkId() {
+ return networkId;
+ }
+
+ /**
+ * @param networkId the networkId to set
+ */
+ public void setNetworkId(Integer networkId) {
+ this.networkId = networkId;
+ }
+
+ public NodeSettings networkId(Integer networkId) {
+ this.networkId = networkId;
+ return this;
+ }
+
+ /**
+ * @return the identity
+ */
+ public String getIdentity() {
+ return identity;
+ }
+
+ /**
+ * @param identity the identity to set
+ */
+ public void setIdentity(String identity) {
+ this.identity = identity;
+ }
+
+ public NodeSettings identity(String identity) {
+ this.identity = identity;
+ return this;
+ }
+
+ /**
+ * @return the extraParams
+ */
+ public String getExtraParams() {
+ return extraParams;
+ }
+
+ /**
+ * @param extraParams the extraParams to set
+ */
+ public void setExtraParams(String extraParams) {
+ this.extraParams = extraParams;
+ }
+
+ public NodeSettings extraParams(String extraParams) {
+ this.extraParams = extraParams;
+ return this;
+ }
+
+ /**
+ * @return the genesisBlock
+ */
+ public String getGenesisBlock() {
+ return genesisBlock;
+ }
+
+ /**
+ * @param genesisBlock the genesisBlock to set
+ */
+ public void setGenesisBlock(String genesisBlock) {
+ this.genesisBlock = genesisBlock;
+ }
+
+ public NodeSettings genesisBlock(String genesisBlock) {
+ this.genesisBlock = genesisBlock;
+ return this;
+ }
+
+ /**
+ * @return the blockMakerAccount
+ */
+ public String getBlockMakerAccount() {
+ return blockMakerAccount;
+ }
+
+ /**
+ * @param blockMakerAccount the blockMakerAccount to set
+ */
+ public void setBlockMakerAccount(String blockMakerAccount) {
+ this.blockMakerAccount = blockMakerAccount;
+ }
+
+ public NodeSettings blockMakerAccount(String blockMakerAccount) {
+ this.blockMakerAccount = blockMakerAccount;
+ return this;
+ }
+
+ /**
+ * @return the voterAccount
+ */
+ public String getVoterAccount() {
+ return voterAccount;
+ }
+
+ /**
+ * @param voterAccount the voterAccount to set
+ */
+ public void setVoterAccount(String voterAccount) {
+ this.voterAccount = voterAccount;
+ }
+
+ public NodeSettings voterAccount(String voterAccount) {
+ this.voterAccount = voterAccount;
+ return this;
+ }
+
+ /**
+ * @return the minBlockTime
+ */
+ public Integer getMinBlockTime() {
+ return minBlockTime;
+ }
+
+ /**
+ * @param minBlockTime the minBlockTime to set
+ */
+ public void setMinBlockTime(Integer minBlockTime) {
+ this.minBlockTime = minBlockTime;
+ }
+
+ public NodeSettings minBlockTime(Integer minBlockTime) {
+ this.minBlockTime = minBlockTime;
+ return this;
+ }
+
+ /**
+ * @return the maxBlockTime
+ */
+ public Integer getMaxBlockTime() {
+ return maxBlockTime;
+ }
+
+ /**
+ * @param maxBlockTime the maxBlockTime to set
+ */
+ public void setMaxBlockTime(Integer maxBlockTime) {
+ this.maxBlockTime = maxBlockTime;
+ }
+
+ public NodeSettings maxBlockTime(Integer maxBlockTime) {
+ this.maxBlockTime = maxBlockTime;
+ return this;
+ }
+
+ /**
+ * @return the isMining
+ */
+ public Boolean isMining() {
+ return isMining;
+ }
+
+ /**
+ * @param isMining the isMining to set
+ */
+ public void setIsMining(Boolean isMining) {
+ this.isMining = isMining;
+ }
+
+ public NodeSettings isMining(Boolean isMining) {
+ this.isMining = isMining;
+ return this;
+ }
+
+}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/QuorumInfo.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/QuorumInfo.java
index 725a54bb..2e5990ae 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/QuorumInfo.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/QuorumInfo.java
@@ -15,24 +15,31 @@ public BlockMakerStrategy() {
public int getMinBlockTime() {
return minBlockTime;
}
+
public void setMinBlockTime(int minBlockTime) {
this.minBlockTime = minBlockTime;
}
+
public int getMaxBlockTime() {
return maxBlockTime;
}
+
public void setMaxBlockTime(int maxBlockTime) {
this.maxBlockTime = maxBlockTime;
}
+
public String getStatus() {
return status;
}
+
public void setStatus(String status) {
this.status = status;
}
+
public String getType() {
return type;
}
+
public void setType(String type) {
this.type = type;
}
@@ -47,8 +54,12 @@ public void setType(String type) {
private String voteAccount;
+ private String nodeKey;
+
private Boolean canVote;
+ private Boolean isConstellationEnabled;
+
private BlockMakerStrategy blockMakerStrategy;
public QuorumInfo() {
@@ -102,4 +113,20 @@ public void setQuorum(Boolean isQuorum) {
this.isQuorum = isQuorum;
}
+ public String getNodeKey() {
+ return nodeKey;
+ }
+
+ public void setNodeKey(String nodeKey) {
+ this.nodeKey = nodeKey;
+ }
+
+ public Boolean getIsConstellationEnabled() {
+ return isConstellationEnabled;
+ }
+
+ public void setIsConstellationEnabled(Boolean isConstellationEnabled) {
+ this.isConstellationEnabled = isConstellationEnabled;
+ }
+
}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/TransactionRequest.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/TransactionRequest.java
index 633c477b..b244c4fd 100644
--- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/TransactionRequest.java
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/TransactionRequest.java
@@ -50,36 +50,36 @@ public TransactionRequest(String fromAddress, String contractAddress, ContractAB
this.privateFrom = null;
this.privateFor = null;
- this.function = abi.getFunction(method);
- if (this.function == null) {
- throw new APIException("Invalid method '" + method + "'");
- }
+ this.function = abi.getFunction(method);
+ if (this.function == null) {
+ throw new APIException("Invalid method '" + method + "'");
+ }
}
public Object[] toGethArgs() {
Map req = new HashMap<>();
- req.put("from", fromAddress);
- req.put("to", contractAddress);
- req.put("gas", DEFAULT_GAS);
- req.put("data", function.encodeAsHex(args));
+ req.put("from", fromAddress);
+ req.put("to", contractAddress);
+ req.put("gas", DEFAULT_GAS);
+ req.put("data", function.encodeAsHex(args));
- if (StringUtils.isNotBlank(privateFrom)) {
- req.put("privateFrom", privateFrom);
- }
+ if (StringUtils.isNotBlank(privateFrom)) {
+ req.put("privateFrom", privateFrom);
+ }
- if (privateFor != null && !privateFor.isEmpty()) {
+ if (privateFor != null && !privateFor.isEmpty()) {
req.put("privateFor", privateFor);
- }
+ }
if (isRead) {
if (blockNumber == null) {
- return new Object[] { req, BLOCK_LATEST };
+ return new Object[]{req, BLOCK_LATEST};
} else {
- return new Object[] { req, blockNumber };
+ return new Object[]{req, blockNumber};
}
} else {
- return new Object[] { req };
+ return new Object[]{req};
}
}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/User.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/User.java
new file mode 100644
index 00000000..9224ce4d
--- /dev/null
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/User.java
@@ -0,0 +1,81 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jpmorgan.cakeshop.model;
+
+import java.io.Serializable;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "USERS", indexes = {
+ @Index(name = "user_name_idx", columnList = "userName")})
+public class User implements Serializable {
+
+ @Id
+ private String userName;
+ private String password;
+ private String firstName;
+ private String lastName;
+
+ /**
+ * @return the userName
+ */
+ public String getUserName() {
+ return userName;
+ }
+
+ /**
+ * @param userName the userName to set
+ */
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ /**
+ * @return the password
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * @param password the password to set
+ */
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ /**
+ * @return the firstName
+ */
+ public String getFirstName() {
+ return firstName;
+ }
+
+ /**
+ * @param firstName the firstName to set
+ */
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ /**
+ * @return the lastName
+ */
+ public String getLastName() {
+ return lastName;
+ }
+
+ /**
+ * @param lastName the lastName to set
+ */
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/BlockPostJsonRequest.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/BlockPostJsonRequest.java
new file mode 100644
index 00000000..6207f03e
--- /dev/null
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/BlockPostJsonRequest.java
@@ -0,0 +1,68 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jpmorgan.cakeshop.model.json;
+
+public class BlockPostJsonRequest {
+
+ private String id, hash, tag;
+ private Long number;
+
+ /**
+ * @return the id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * @return the hash
+ */
+ public String getHash() {
+ return hash;
+ }
+
+ /**
+ * @param hash the hash to set
+ */
+ public void setHash(String hash) {
+ this.hash = hash;
+ }
+
+ /**
+ * @return the tag
+ */
+ public String getTag() {
+ return tag;
+ }
+
+ /**
+ * @param tag the tag to set
+ */
+ public void setTag(String tag) {
+ this.tag = tag;
+ }
+
+ /**
+ * @return the number
+ */
+ public Long getNumber() {
+ return number;
+ }
+
+ /**
+ * @param number the number to set
+ */
+ public void setNumber(Long number) {
+ this.number = number;
+ }
+}
diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/ContractDeserializer.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/ContractDeserializer.java
new file mode 100644
index 00000000..70eb4d9a
--- /dev/null
+++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/ContractDeserializer.java
@@ -0,0 +1,115 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jpmorgan.cakeshop.model.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.Lists;
+import com.jpmorgan.cakeshop.util.StringUtils;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class ContractDeserializer extends JsonDeserializer {
+
+ @Override
+ public ContractPostJsonRequest deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
+ ContractPostJsonRequest request = new ContractPostJsonRequest();
+ JsonNode node = jp.getCodec().readTree(jp);
+
+ if (null != node.get("privateFor")) {
+ JsonNode privateForNode = node.get("privateFor");
+ List privateFor = null;
+
+ if (privateForNode.isArray()) {
+ privateFor = Lists.newArrayList();
+ for (Iterator iter = privateForNode.elements(); iter.hasNext();) {
+ privateFor.add(iter.next().asText());
+ }
+ } else {
+ if (StringUtils.isNotBlank(node.get("privateFor").textValue())) {
+ privateFor = Lists.newArrayList(node.get("privateFor").textValue());
+ }
+ }
+
+ request.setPrivateFor(privateFor);
+ }
+
+ if (null != node.get("code")) {
+ request.setCode(node.get("code").textValue());
+ }
+
+ if (null != node.get("code_type")) {
+ request.setCode_type(node.get("code_type").textValue());
+ }
+
+ if (null != node.get("from")) {
+ request.setFrom(node.get("from").textValue());
+ }
+
+ if (null != node.get("binary")) {
+ request.setBinary(node.get("binary").textValue());
+ }
+
+ if (null != node.get("privateFrom")) {
+ request.setPrivateFrom(node.get("privateFrom").textValue());
+ }
+
+ if (null != node.get("address")) {
+ request.setAddress(node.get("address").textValue());
+ }
+
+ if (null != node.get("method")) {
+ request.setMethod(node.get("method").textValue());
+ }
+
+ if (null != node.get("blockNumber")) {
+ request.setBlockNumber(node.get("blockNumber").textValue());
+ }
+
+ if (null != node.get("optimize")) {
+ request.setOptimize(node.get("optimize").booleanValue());
+ }
+
+ if (null != node.get("args")) {
+ JsonNode argsNode = node.get("args");
+ List