diff --git a/docs/applications/simple-bank-account/README.md b/docs/applications/simple-bank-account/README.md index 36cfc9f1..81548e34 100644 --- a/docs/applications/simple-bank-account/README.md +++ b/docs/applications/simple-bank-account/README.md @@ -20,7 +20,7 @@ This application uses five contracts: - `Transfer.java` - `Withdraw.java` -(which can be found in [`src/main/java/com/scalar/application/bankaccount/contract`](./src/main/java/com/scalar/application/bankaccount/contract)). These contracts will be registered by the bank and will allow the bank to, respectively, view account histories, create accounts, deposit funds to an account, transfer funds between accounts, and withdraw funds from accounts. +(which can be found in [`contract/src/main/java/com/scalar/application/bankaccount/contract`](./contract/src/main/java/com/scalar/application/bankaccount/contract)). These contracts will be registered by the bank and will allow the bank to, respectively, view account histories, create accounts, deposit funds to an account, transfer funds between accounts, and withdraw funds from accounts. The overall architecture of this application can be viewed as follows. (Note again that this use case is for simplicity, and in practice may look a bit different.) @@ -31,6 +31,8 @@ The overall architecture of this application can be viewed as follows. (Note aga Download the [ScalarDL Client SDK](https://github.com/scalar-labs/scalardl-client-sdk). Make sure ScalarDL is running and register all the required contracts by executing ``` +$ ./gradlew build +$ cd contract $ SCALAR_SDK_HOME=/path/to/scalardl-client-sdk ./register ``` Run the application using IntelliJ (or the IDE of your choice), or by executing `gradle bootRun` in the project home directory. It should create a server on `localhost:8080` to which you can send HTTP requests in order to interact with the app. See the [API documentation](./docs/api_endpoints.md) for more information. To create HTTP requests we have found that [Postman](https://www.getpostman.com/) is quite nice. @@ -43,50 +45,52 @@ In this tutorial we will not discuss the detail at the level of web services or ### Contracts -Contracts are Java classes which extend the `Contract` class and override the `invoke` method. Let's take a closer look at the `Deposit.java` contract. +Contracts are Java classes which extend the `JacksonBasedContract` class and override the `invoke` method. Let's take a closer look at the `Deposit.java` contract. ```java package com.scalar.application.bankaccount.contract; -import com.scalar.dl.ledger.asset.Asset; -import com.scalar.dl.ledger.contract.Contract; +import com.fasterxml.jackson.databind.JsonNode; +import com.scalar.dl.ledger.statemachine.Asset; +import com.scalar.dl.ledger.contract.JacksonBasedContract; import com.scalar.dl.ledger.exception.ContractContextException; -import com.scalar.dl.ledger.database.Ledger; +import com.scalar.dl.ledger.statemachine.Ledger; import java.util.Optional; -import javax.json.Json; -import javax.json.JsonObject; +import javax.annotation.Nullable; -public class Deposit extends Contract { +public class Deposit extends JacksonBasedContract { @Override - public JsonObject invoke(Ledger ledger, JsonObject argument, Optional property) { - if (!(argument.containsKey("id") && argument.containsKey("amount"))) { + public JsonNode invoke( + Ledger ledger, JsonNode argument, @Nullable JsonNode properties) { + if (!argument.has("id") || !argument.has("amount")) { throw new ContractContextException("a required key is missing: id and/or amount"); } - String id = argument.getString("id"); - long amount = argument.getJsonNumber("amount").longValue(); + String id = argument.get("id").asText(); + long amount = argument.get("amount").asLong(); if (amount < 0) { throw new ContractContextException("amount is negative"); } - Optional response = ledger.get(id); + Optional> asset = ledger.get(id); - if (!response.isPresent()) { + if (!asset.isPresent()) { throw new ContractContextException("account does not exist"); } - long oldBalance = response.get().data().getInt("balance"); + long oldBalance = asset.get().data().get("balance").asLong(); long newBalance = oldBalance + amount; - ledger.put(id, Json.createObjectBuilder().add("balance", newBalance).build()); - return Json.createObjectBuilder() - .add("status", "succeeded") - .add("old_balance", oldBalance) - .add("new_balance", newBalance) - .build(); + ledger.put(id, getObjectMapper().createObjectNode().put("balance", newBalance)); + return getObjectMapper() + .createObjectNode() + .put("status", "succeeded") + .put("old_balance", oldBalance) + .put("new_balance", newBalance); } } + ``` In order for this contract to function properly the user must supply an account `id` and an `amount`. So the first thing to do is check whether the argument contains these two keys, and if not, throw a `ContractContextException`. @@ -95,15 +99,15 @@ In order for this contract to function properly the user must supply an account So, assuming that we have an `id` and an `amount`, we do a quick non-negative check on `amount` and again throw a `ContractContextException` if it is. Now we are ready to interact with the `ledger`. -There are three methods that can be called on `ledger`: `get(String s)`, `put(String s, JsonObject jsonObject)`, and `scan(AssetFilter assetFilter)`. `get(String s)` will retrieve the asset `s` from the ledger. `put(String s, JsonObject argument)` will associate the asset `s` with the data `jsonObject` and increase the age of the asset. `scan(AssetFilter assetFilter)` will return a version of the history of an asset as specified in the `AssetFilter`. +There are three methods that can be called on `ledger`: `get(String s)`, `put(String s, JsonNode jsonNode)`, and `scan(AssetFilter assetFilter)`. `get(String s)` will retrieve the asset `s` from the ledger. `put(String s, JsonNode jsonNode)` will associate the asset `s` with the data `jsonNode` and increase the age of the asset. `scan(AssetFilter assetFilter)` will return a version of the history of an asset as specified in the `AssetFilter`. **Note:** ledger does not permit blind writes, i.e., before performing a `put` on a particular asset, we must first `get` that asset. Furthermore `scan` is only allowed in read-only contracts, which means a single contract cannot both `scan` and `put`. The rest of the contract proceeds in a straightforward manner. We first `get` the asset from the ledger, retrieve its current balance, add the deposit amount to it, and finally `put` the asset back into the ledger with its new balance. -At the end we must return a `JsonObject`. What the `JsonObject` contains is up to the designer of the contract. Here we have decided to include a `status` message, the `old_balance`, and the `new_balance`. +At the end we must return a `JsonNode`. What the `JsonNode` contains is up to the designer of the contract. Here we have decided to include a `status` message, the `old_balance`, and the `new_balance`. -If you wish, you can view the other contracts that this application uses in [`scr/main/java/com/scalar/application/bankaccount/contract`](./src/main/java/com/scalar/application/bankaccount/contract). +If you wish, you can view the other contracts that this application uses in [`contract/scr/main/java/com/scalar/application/bankaccount/contract`](./contract/src/main/java/com/scalar/application/bankaccount/contract). Once you have written your contracts you will need to compile them, and this can be done as @@ -128,7 +132,8 @@ scalar.dl.client.private_key_path=conf/client-key.pem If everything is set up properly you should be able to register your certificate on the ScalarDL network as ```bash -$ ${SCALAR_SDK_HOME}/client/bin/scalardl register-cert --properties ./conf/client.properties +$ cd contract +$ ${SCALAR_SDK_HOME}/client/bin/scalardl register-cert --properties ../conf/client.properties ``` You should receive status code 200 if successful. @@ -149,7 +154,7 @@ contract-class-file = "build/classes/java/main/com/scalar/application/bankaccoun [[contracts]] contract-id = "transfer" contract-binary-name = "com.scalar.application.bankaccount.contract.Transfer" -contract-class-file = "build/classes/java/main/com/scalar/application/bankaccount/contract/Transfer.class" +contract-class-file = "build/classes/java/main/com/scalar/application/bankaccount/contract/Transfer.class" ``` In this example we will register three contracts: `CreateAccount.java`, `Deposit.java`, and `Transfer.java`. The `contract-binary-name` and `contract-class-file` are determined, but you are free to choose the `contract-id` as you wish. The `contract-id` is how you can refer to a specific contract using `ClientService`, as we will see below. @@ -157,7 +162,7 @@ In this example we will register three contracts: `CreateAccount.java`, `Deposit Once your toml file is written you can register all the specified contracts as ```bash -$ ${SCALAR_SDK_HOME}/client/bin/scalardl register-contracts --properties ./conf/client.properties --contracts-file ./conf/contracts.toml +$ ${SCALAR_SDK_HOME}/client/bin/scalardl register-contracts --properties ../conf/client.properties --contracts-file ../conf/contracts.toml ``` Each successfully registered contract should return status code 200. @@ -169,20 +174,20 @@ You can now execute any registered contracts if you would like. For example, use Create two accounts with ids `a111` and `b222`. (Contract ids can be any string.) ```bash -$ ${SCALAR_SDK_HOME}/client/bin/scalardl execute-contract --properties ./conf/client.properties --contract-id create-account --contract-argument '{"id": "a111"}' -$ ${SCALAR_SDK_HOME}/client/bin/scalardl execute-contract --properties ./conf/client.properties --contract-id create-account --contract-argument '{"id": "b222"}' +$ ${SCALAR_SDK_HOME}/client/bin/scalardl execute-contract --properties ../conf/client.properties --contract-id create-account --contract-argument '{"id": "a111"}' +$ ${SCALAR_SDK_HOME}/client/bin/scalardl execute-contract --properties ../conf/client.properties --contract-id create-account --contract-argument '{"id": "b222"}' ``` Now, deposit 100 into account `a111`: ```bash -$ ${SCALAR_SDK_HOME}/client/bin/scalardl execute-contract --properties ./conf/client.properties --contract-id deposit --contract-argument '{"id": "a111", "amount": 100}' +$ ${SCALAR_SDK_HOME}/client/bin/scalardl execute-contract --properties ../conf/client.properties --contract-id deposit --contract-argument '{"id": "a111", "amount": 100}' ``` Finally, transfer 25 from `a111` to `b222`: ```bash -$ ${SCALAR_SDK_HOME}/client/bin/scalardl execute-contract --properties ./conf/client.properties --contract-id transfer --contract-argument '{"from": "a111", "to": "b222", "amount": 100}' +$ ${SCALAR_SDK_HOME}/client/bin/scalardl execute-contract --properties ../conf/client.properties --contract-id transfer --contract-argument '{"from": "a111", "to": "b222", "amount": 100}' ``` If you were running the application itself, you could execute these commands using the [API endpoints](./docs/api_endpoints.md). @@ -195,18 +200,15 @@ The Client SDK is available on [Maven Central](https://search.maven.org/search?q ```groovy dependencies { - compile group: 'com.scalar-labs', name: 'scalardl-java-client-sdk', version: '2.0.4' + compile group: 'com.scalar-labs', name: 'scalardl-java-client-sdk', version: '3.9.1' } ``` The following snippet shows how you can instantiate a `ClientService` object, where `properties` should be the path to your `client.properties` file. ```java -Injector injector = - Guice.createInjector(new ClientModule(new ClientConfig(new File(properties)))); -try (ClientService clientService = injector.getInstance(ClientService.class)) { - ... -} +ClientServiceFactory factory = new ClientServiceFactory(); +ClientService service = factory.create(new ClientConfig(new File(properties)); ``` `ClientService` contains a method `executeContract(String id, JsonObject argument)` which can be used to, of course, execute a contract. For example: diff --git a/docs/applications/simple-bank-account/app/build.gradle b/docs/applications/simple-bank-account/app/build.gradle new file mode 100644 index 00000000..94e76fe9 --- /dev/null +++ b/docs/applications/simple-bank-account/app/build.gradle @@ -0,0 +1,53 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:3.3.0") + } +} + +plugins { + id 'java' + id 'application' + id 'idea' + id "org.springframework.boot" version "3.3.0" + id "io.spring.dependency-management" version "1.1.5" +} + +application { + mainClass = 'com.scalar.application.bankaccount.Application' +} + +bootJar { + archiveBaseName = 'gs-rest-service' + archiveVersion = '0.1.0' +} + +repositories { + mavenCentral() +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +tasks.named('test') { + useJUnitPlatform() +} + +group = 'com.scalar.application.simple-bank-account' +version = '0.1' + +dependencies { + implementation('org.springframework.boot:spring-boot-starter-web') { + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' + } + implementation 'org.springframework.boot:spring-boot-starter-log4j2' + implementation group: 'com.scalar-labs', name: 'scalardl-java-client-sdk', version: '3.9.1' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testImplementation 'org.assertj:assertj-core:3.26.0' +} diff --git a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/Application.java b/docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/Application.java similarity index 100% rename from docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/Application.java rename to docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/Application.java diff --git a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/controller/AccountController.java b/docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/controller/AccountController.java similarity index 100% rename from docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/controller/AccountController.java rename to docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/controller/AccountController.java diff --git a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/controller/TransferController.java b/docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/controller/TransferController.java similarity index 100% rename from docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/controller/TransferController.java rename to docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/controller/TransferController.java diff --git a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/exception/ResourceNotFoundException.java b/docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/exception/ResourceNotFoundException.java similarity index 100% rename from docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/exception/ResourceNotFoundException.java rename to docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/exception/ResourceNotFoundException.java diff --git a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/model/Account.java b/docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/model/Account.java similarity index 100% rename from docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/model/Account.java rename to docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/model/Account.java diff --git a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/repository/AccountRepository.java b/docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/repository/AccountRepository.java similarity index 80% rename from docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/repository/AccountRepository.java rename to docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/repository/AccountRepository.java index bbca41a3..ccec1800 100644 --- a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/repository/AccountRepository.java +++ b/docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/repository/AccountRepository.java @@ -1,14 +1,11 @@ package com.scalar.application.bankaccount.repository; -import com.google.inject.Guice; -import com.google.inject.Injector; import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.service.ClientModule; import com.scalar.dl.client.service.ClientService; +import com.scalar.dl.client.service.ClientServiceFactory; import com.scalar.dl.ledger.model.ContractExecutionResult; import java.io.File; import java.io.IOException; -import javax.inject.Singleton; import javax.json.JsonObject; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -16,7 +13,6 @@ import org.springframework.stereotype.Repository; @Repository -@Singleton public class AccountRepository { private static final Logger logger = LogManager.getLogger(AccountRepository.class); private static final String ACCOUNT_HISTORY_ID = "account-history"; @@ -28,9 +24,8 @@ public class AccountRepository { public AccountRepository(@Value("${client.properties.path}") String properties) throws IOException { - Injector injector = - Guice.createInjector(new ClientModule(new ClientConfig(new File(properties)))); - this.clientService = injector.getInstance(ClientService.class); + ClientServiceFactory clientServiceFactory = new ClientServiceFactory(); + this.clientService = clientServiceFactory.create(new ClientConfig(new File(properties))); } public ContractExecutionResult create(JsonObject argument) { @@ -40,8 +35,7 @@ public ContractExecutionResult create(JsonObject argument) { } public ContractExecutionResult history(JsonObject argument) { - ContractExecutionResult result = - clientService.executeContract(ACCOUNT_HISTORY_ID, argument); + ContractExecutionResult result = clientService.executeContract(ACCOUNT_HISTORY_ID, argument); logResponse("history", result); return result; } @@ -68,7 +62,7 @@ private void logResponse(String header, ContractExecutionResult result) { logger.info( header + ": (" - + (result.getResult().isPresent() ? result.getResult().get() : "{}") + + (result.getContractResult().isPresent() ? result.getContractResult().get() : "{}") + ")"); } } diff --git a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/service/AccountService.java b/docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/service/AccountService.java similarity index 96% rename from docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/service/AccountService.java rename to docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/service/AccountService.java index b4cc30fc..99b4bacd 100644 --- a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/service/AccountService.java +++ b/docs/applications/simple-bank-account/app/src/main/java/com/scalar/application/bankaccount/service/AccountService.java @@ -71,7 +71,7 @@ private ResponseEntity serve(ThrowableFunction f, JsonObject json) { ContractExecutionResult result = f.apply(json); return ResponseEntity - .ok(result.getResult().isPresent() ? result.getResult().get().toString() : "{}"); + .ok(result.getContractResult().isPresent() ? result.getContractResult().get() : "{}"); } catch (ClientException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Json.createObjectBuilder() diff --git a/docs/applications/simple-bank-account/src/main/resources/application.properties b/docs/applications/simple-bank-account/app/src/main/resources/application.properties similarity index 79% rename from docs/applications/simple-bank-account/src/main/resources/application.properties rename to docs/applications/simple-bank-account/app/src/main/resources/application.properties index 18e68b3b..7b786a6e 100644 --- a/docs/applications/simple-bank-account/src/main/resources/application.properties +++ b/docs/applications/simple-bank-account/app/src/main/resources/application.properties @@ -1,4 +1,4 @@ -client.properties.path=conf/client.properties +client.properties.path=../conf/client.properties # Contract ids contract.id.account-history=account-history diff --git a/docs/applications/simple-bank-account/src/main/resources/log4j.properties b/docs/applications/simple-bank-account/app/src/main/resources/log4j.properties similarity index 100% rename from docs/applications/simple-bank-account/src/main/resources/log4j.properties rename to docs/applications/simple-bank-account/app/src/main/resources/log4j.properties diff --git a/docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/model/AccountTest.java b/docs/applications/simple-bank-account/app/src/test/java/com/scalar/application/bankaccount/model/AccountTest.java similarity index 84% rename from docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/model/AccountTest.java rename to docs/applications/simple-bank-account/app/src/test/java/com/scalar/application/bankaccount/model/AccountTest.java index 840534be..83a7b88d 100644 --- a/docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/model/AccountTest.java +++ b/docs/applications/simple-bank-account/app/src/test/java/com/scalar/application/bankaccount/model/AccountTest.java @@ -1,17 +1,17 @@ package com.scalar.application.bankaccount.model; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Java6Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatCode; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class AccountTest { private static final String ACCOUNT_ID = "account-id"; private static final long BALANCE = 123; private Account account; - @Before + @BeforeEach public void setUp() { account = new Account(ACCOUNT_ID, BALANCE); } diff --git a/docs/applications/simple-bank-account/build.gradle b/docs/applications/simple-bank-account/build.gradle deleted file mode 100644 index f4b492a1..00000000 --- a/docs/applications/simple-bank-account/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.5.RELEASE") - } -} - -plugins { - id 'java' - id 'application' - id 'idea' - id "org.springframework.boot" version "2.1.1.RELEASE" - id "io.spring.dependency-management" version "1.0.6.RELEASE" -} - -bootJar { - baseName = 'gs-rest-service' - version = '0.1.0' -} - -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 -targetCompatibility = 1.8 -group = 'com.scalar.application.simple-bank-account' -version = '0.1' - -dependencies { - compile('org.springframework.boot:spring-boot-starter-web') { - exclude module: 'logback-classic' - } - compile group: 'com.scalar-labs', name: 'scalardl-java-client-sdk', version: '2.0.4' - testCompile 'org.springframework.boot:spring-boot-starter-test' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile 'org.assertj:assertj-core:3.9.1' -} diff --git a/docs/applications/simple-bank-account/conf/client.properties b/docs/applications/simple-bank-account/conf/client.properties index 521dc10a..7c8a5452 100644 --- a/docs/applications/simple-bank-account/conf/client.properties +++ b/docs/applications/simple-bank-account/conf/client.properties @@ -17,12 +17,12 @@ scalar.dl.client.cert_holder_id=user1 #scalar.dl.client.cert_version=1 # Required. The path of the certificate file. -scalar.dl.client.cert_path=./conf/client.pem +scalar.dl.client.cert_path=../conf/client.pem # Required. The path of a corresponding private key file to the certificate. # Exceptionally it can be empty in some requests to privileged services # such as registerCertificate and registerFunction since they don't need a signature. -scalar.dl.client.private_key_path=./conf/client-key.pem +scalar.dl.client.private_key_path=../conf/client-key.pem # Optional. A flag to enable TLS communication. False by default. scalar.dl.client.tls.enabled=false diff --git a/docs/applications/simple-bank-account/contract/build.gradle b/docs/applications/simple-bank-account/contract/build.gradle new file mode 100644 index 00000000..0f3300cf --- /dev/null +++ b/docs/applications/simple-bank-account/contract/build.gradle @@ -0,0 +1,30 @@ +plugins { + id 'java' + id 'idea' +} + +repositories { + mavenCentral() +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} + +tasks.named('test') { + useJUnitPlatform() +} + +group = 'com.scalar.application.simple-bank-account' +version = '0.1' + +dependencies { + implementation group: 'com.scalar-labs', name: 'scalardl-java-client-sdk', version: '3.9.1' + testImplementation group: 'com.scalar-labs', name: 'scalardl-java-client-sdk', version: '3.9.1' + testImplementation group: 'org.mockito', name: 'mockito-core', version: '4.11.0' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testImplementation 'org.assertj:assertj-core:3.26.0' +} diff --git a/docs/applications/simple-bank-account/contract/register b/docs/applications/simple-bank-account/contract/register new file mode 100755 index 00000000..3de3d498 --- /dev/null +++ b/docs/applications/simple-bank-account/contract/register @@ -0,0 +1,4 @@ +#!/bin/bash + +${SCALAR_SDK_HOME}/client/bin/scalardl register-cert --properties ../conf/client.properties +${SCALAR_SDK_HOME}/client/bin/scalardl register-contracts --properties ../conf/client.properties --contracts-file ../conf/contracts.toml diff --git a/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/AccountHistory.java b/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/AccountHistory.java new file mode 100644 index 00000000..2b8e2363 --- /dev/null +++ b/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/AccountHistory.java @@ -0,0 +1,51 @@ +package com.scalar.application.bankaccount.contract; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.scalar.dl.ledger.database.AssetFilter; +import com.scalar.dl.ledger.statemachine.Asset; +import com.scalar.dl.ledger.contract.JacksonBasedContract; +import com.scalar.dl.ledger.exception.ContractContextException; +import com.scalar.dl.ledger.statemachine.Ledger; +import java.util.List; +import javax.annotation.Nullable; + +public class AccountHistory extends JacksonBasedContract { + @Override + public JsonNode invoke(Ledger ledger, JsonNode argument, @Nullable JsonNode property) { + if (!argument.has("id")) { + throw new ContractContextException("a required key is missing: id"); + } + + AssetFilter filter = new AssetFilter(argument.get("id").asText()); + if (argument.has("start")) { + filter.withStartAge(argument.get("start").asInt(), true); + } + if (argument.has("end")) { + filter.withEndAge(argument.get("end").asInt(), false); + } + if (argument.has("limit")) { + filter.withLimit(argument.get("limit").asInt()); + } + if (argument.has("order") && argument.get("order").asText().equals("asc")) { + filter.withAgeOrder(AssetFilter.AgeOrder.ASC); + } + + List> history = ledger.scan(filter); + + ArrayNode result = JsonNodeFactory.instance.arrayNode(); + history.forEach( + asset -> { + JsonNode json = + getObjectMapper() + .createObjectNode() + .put("id", asset.id()) + .put("data", asset.data().asText()) + .put("age", asset.age()); + result.add(json); + }); + + return getObjectMapper().createObjectNode().put("status", "succeeded").set("history", result); + } +} diff --git a/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/CreateAccount.java b/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/CreateAccount.java new file mode 100644 index 00000000..a6dd72af --- /dev/null +++ b/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/CreateAccount.java @@ -0,0 +1,31 @@ +package com.scalar.application.bankaccount.contract; + +import com.fasterxml.jackson.databind.JsonNode; +import com.scalar.dl.ledger.statemachine.Asset; +import com.scalar.dl.ledger.contract.JacksonBasedContract; +import com.scalar.dl.ledger.exception.ContractContextException; +import com.scalar.dl.ledger.statemachine.Ledger; +import java.util.Optional; +import javax.annotation.Nullable; + +public class CreateAccount extends JacksonBasedContract { + @Override + public JsonNode invoke(Ledger ledger, JsonNode argument, @Nullable JsonNode property) { + if (!argument.has("id")) { + throw new ContractContextException("a required key is missing: id"); + } + + String id = argument.get("id").asText(); + Optional> response = ledger.get(id); + + if (response.isPresent()) { + throw new ContractContextException("account already exists"); + } + + ledger.put(id, getObjectMapper().createObjectNode().put("balance", 0)); + return getObjectMapper() + .createObjectNode() + .put("status", "succeeded") + .put("message", "account " + id + " created"); + } +} diff --git a/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/Deposit.java b/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/Deposit.java new file mode 100644 index 00000000..1616a6ec --- /dev/null +++ b/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/Deposit.java @@ -0,0 +1,42 @@ +package com.scalar.application.bankaccount.contract; + +import com.fasterxml.jackson.databind.JsonNode; +import com.scalar.dl.ledger.statemachine.Asset; +import com.scalar.dl.ledger.contract.JacksonBasedContract; +import com.scalar.dl.ledger.exception.ContractContextException; +import com.scalar.dl.ledger.statemachine.Ledger; +import java.util.Optional; +import javax.annotation.Nullable; + +public class Deposit extends JacksonBasedContract { + @Override + public JsonNode invoke( + Ledger ledger, JsonNode argument, @Nullable JsonNode properties) { + if (!argument.has("id") || !argument.has("amount")) { + throw new ContractContextException("a required key is missing: id and/or amount"); + } + + String id = argument.get("id").asText(); + long amount = argument.get("amount").asLong(); + + if (amount < 0) { + throw new ContractContextException("amount is negative"); + } + + Optional> asset = ledger.get(id); + + if (!asset.isPresent()) { + throw new ContractContextException("account does not exist"); + } + + long oldBalance = asset.get().data().get("balance").asLong(); + long newBalance = oldBalance + amount; + + ledger.put(id, getObjectMapper().createObjectNode().put("balance", newBalance)); + return getObjectMapper() + .createObjectNode() + .put("status", "succeeded") + .put("old_balance", oldBalance) + .put("new_balance", newBalance); + } +} diff --git a/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/Transfer.java b/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/Transfer.java new file mode 100644 index 00000000..856637c0 --- /dev/null +++ b/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/Transfer.java @@ -0,0 +1,57 @@ +package com.scalar.application.bankaccount.contract; + +import com.fasterxml.jackson.databind.JsonNode; +import com.scalar.dl.ledger.statemachine.Asset; +import com.scalar.dl.ledger.contract.JacksonBasedContract; +import com.scalar.dl.ledger.exception.ContractContextException; +import com.scalar.dl.ledger.statemachine.Ledger; +import java.util.Optional; +import javax.annotation.Nullable; + +public class Transfer extends JacksonBasedContract { + @Override + public JsonNode invoke(Ledger ledger, JsonNode argument, @Nullable JsonNode property) { + if (!(argument.has("from") && argument.has("to") && argument.has("amount"))) { + throw new ContractContextException("a required key is missing: from, to, and/or amount"); + } + + String fromId = argument.get("from").asText(); + String toId = argument.get("to").asText(); + long amount = argument.get("amount").asLong(); + + if (amount < 0) { + throw new ContractContextException("amount is negative"); + } + + Optional> fromAsset = ledger.get(fromId); + Optional> toAsset = ledger.get(toId); + + if (!fromAsset.isPresent()) { + throw new ContractContextException("from account does not exist"); + } + + if (!toAsset.isPresent()) { + throw new ContractContextException("to account does not exist"); + } + + long fromOldBalance = fromAsset.get().data().get("balance").asLong(); + long fromNewBalance = fromOldBalance - amount; + long toOldBalance = toAsset.get().data().get("balance").asLong(); + long toNewBalance = toOldBalance + amount; + + if (fromNewBalance < 0) { + throw new ContractContextException("insufficient funds"); + } + + ledger.put(fromId, getObjectMapper().createObjectNode().put("balance", fromNewBalance)); + ledger.put(toId, getObjectMapper().createObjectNode().put("balance", toNewBalance)); + + return getObjectMapper() + .createObjectNode() + .put("status", "succeeded") + .put("from_old_balance", fromOldBalance) + .put("from_new_balance", fromNewBalance) + .put("to_old_balance", toOldBalance) + .put("to_new_balance", toNewBalance); + } +} diff --git a/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/Withdraw.java b/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/Withdraw.java new file mode 100644 index 00000000..42595af4 --- /dev/null +++ b/docs/applications/simple-bank-account/contract/src/main/java/com/scalar/application/bankaccount/contract/Withdraw.java @@ -0,0 +1,46 @@ +package com.scalar.application.bankaccount.contract; + +import com.fasterxml.jackson.databind.JsonNode; +import com.scalar.dl.ledger.statemachine.Asset; +import com.scalar.dl.ledger.contract.JacksonBasedContract; +import com.scalar.dl.ledger.exception.ContractContextException; +import com.scalar.dl.ledger.statemachine.Ledger; + +import javax.annotation.Nullable; +import java.util.Optional; + +public class Withdraw extends JacksonBasedContract { + @Override + public JsonNode invoke(Ledger ledger, JsonNode argument, @Nullable JsonNode property) { + if (!(argument.has("id") && argument.has("amount"))) { + throw new ContractContextException("a required key is missing: id and/or amount"); + } + + String id = argument.get("id").asText(); + long amount = argument.get("amount").asLong(); + + if (amount < 0) { + throw new ContractContextException("amount is negative"); + } + + Optional> response = ledger.get(id); + + if (!response.isPresent()) { + throw new ContractContextException("account does not exist"); + } + + long oldBalance = response.get().data().get("balance").asLong(); + long newBalance = oldBalance - amount; + + if (newBalance < 0) { + throw new ContractContextException("insufficient funds"); + } + + ledger.put(id, getObjectMapper().createObjectNode().put("balance", newBalance)); + return getObjectMapper() + .createObjectNode() + .put("status", "succeeded") + .put("old_balance", oldBalance) + .put("new_balance", newBalance); + } +} diff --git a/docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/AccountHistoryTest.java b/docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/AccountHistoryTest.java similarity index 59% rename from docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/AccountHistoryTest.java rename to docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/AccountHistoryTest.java index 3d258a53..2156235a 100644 --- a/docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/AccountHistoryTest.java +++ b/docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/AccountHistoryTest.java @@ -4,16 +4,16 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.verify; -import com.scalar.dl.ledger.contract.Contract; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.scalar.dl.ledger.database.AssetFilter; +import com.scalar.dl.ledger.contract.JacksonBasedContract; import com.scalar.dl.ledger.exception.ContractContextException; -import com.scalar.dl.ledger.database.Ledger; -import java.util.Optional; +import com.scalar.dl.ledger.statemachine.Ledger; import java.util.UUID; -import javax.json.Json; -import javax.json.JsonObject; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -29,21 +29,27 @@ public class AccountHistoryTest { private final String ORDER_KEY = "order"; private final int START = 3; private final String START_KEY = "start"; - private final Contract contract = new AccountHistory(); - @Mock private Ledger ledger; + private final JacksonBasedContract contract = new AccountHistory(); + @Mock private Ledger ledger; + private AutoCloseable closeable; - @Before + @BeforeEach public void setUp() { - MockitoAnnotations.initMocks(this); + closeable = MockitoAnnotations.openMocks(this); + } + + @AfterEach + public void tearDown() throws Exception { + closeable.close(); } @Test public void invoke_EmptyArgument_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().build(); + JsonNode argument = new ObjectMapper().createObjectNode(); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessageStartingWith("a required key is missing:"); } @@ -52,10 +58,10 @@ public void invoke_EmptyArgument_ShouldThrowContractContextException() { public void invoke_ArgumentWithId_ShouldCallScanCorrectly() { // Arrange ArgumentCaptor filter = ArgumentCaptor.forClass(AssetFilter.class); - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID); // Act - contract.invoke(ledger, argument, Optional.empty()); + contract.invoke(ledger, argument, null); // Assert verify(ledger).scan(filter.capture()); @@ -66,15 +72,15 @@ public void invoke_ArgumentWithId_ShouldCallScanCorrectly() { public void invoke_ArgumentWithStartVersion_ShouldCallScanCorrectly() { // Arrange ArgumentCaptor filter = ArgumentCaptor.forClass(AssetFilter.class); - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).add(START_KEY, 3).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID).put(START_KEY, 3); // Act - contract.invoke(ledger, argument, Optional.empty()); + contract.invoke(ledger, argument, null); // Assert verify(ledger).scan(filter.capture()); assertThat(filter.getValue().getId()).isEqualTo(ID); - assertThat(filter.getValue().getStartVersion().get()).isEqualTo(3); + assertThat(filter.getValue().getStartAge().get()).isEqualTo(3); assertThat(filter.getValue().isStartInclusive()).isEqualTo(true); } @@ -82,15 +88,15 @@ public void invoke_ArgumentWithStartVersion_ShouldCallScanCorrectly() { public void invoke_ArgumentWithEndVersion_ShouldCallScanCorrectly() { // Arrange ArgumentCaptor filter = ArgumentCaptor.forClass(AssetFilter.class); - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).add(END_KEY, END).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID).put(END_KEY, END); // Act - contract.invoke(ledger, argument, Optional.empty()); + contract.invoke(ledger, argument, null); // Assert verify(ledger).scan(filter.capture()); assertThat(filter.getValue().getId()).isEqualTo(ID); - assertThat(filter.getValue().getEndVersion().get()).isEqualTo(END); + assertThat(filter.getValue().getEndAge().get()).isEqualTo(END); assertThat(filter.getValue().isEndInclusive()).isEqualTo(false); } @@ -98,10 +104,10 @@ public void invoke_ArgumentWithEndVersion_ShouldCallScanCorrectly() { public void invoke_ArgumentWithLimit_ShouldCallScanCorrectly() { // Arrange ArgumentCaptor filter = ArgumentCaptor.forClass(AssetFilter.class); - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).add(LIMIT_KEY, LIMIT).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID).put(LIMIT_KEY, LIMIT); // Act - contract.invoke(ledger, argument, Optional.empty()); + contract.invoke(ledger, argument, null); // Assert verify(ledger).scan(filter.capture()); @@ -110,44 +116,44 @@ public void invoke_ArgumentWithLimit_ShouldCallScanCorrectly() { } @Test - public void invoke_ArgumentWithAscVersionOrderSpecified_ShouldCallScanCorrectly() { + public void invoke_ArgumentWithAscAgeOrderSpecified_ShouldCallScanCorrectly() { // Arrange ArgumentCaptor filter = ArgumentCaptor.forClass(AssetFilter.class); - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).add(ORDER_KEY, ASC).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID).put(ORDER_KEY, ASC); // Act - contract.invoke(ledger, argument, Optional.empty()); + contract.invoke(ledger, argument, null); // Assert verify(ledger).scan(filter.capture()); assertThat(filter.getValue().getId()).isEqualTo(ID); - assertThat(filter.getValue().getVersionOrder().get()).isEqualTo(AssetFilter.VersionOrder.ASC); + assertThat(filter.getValue().getAgeOrder().get()).isEqualTo(AssetFilter.AgeOrder.ASC); } @Test public void invoke_ArgumentWithEverythingSpecified_ShouldCallScanCorrectly() { // Arrange ArgumentCaptor filter = ArgumentCaptor.forClass(AssetFilter.class); - JsonObject argument = - Json.createObjectBuilder() - .add(ID_KEY, ID) - .add(START_KEY, START) - .add(END_KEY, END) - .add(LIMIT_KEY, LIMIT) - .add(ORDER_KEY, ASC) - .build(); + JsonNode argument = + new ObjectMapper() + .createObjectNode() + .put(ID_KEY, ID) + .put(START_KEY, START) + .put(END_KEY, END) + .put(LIMIT_KEY, LIMIT) + .put(ORDER_KEY, ASC); // Act - contract.invoke(ledger, argument, Optional.empty()); + contract.invoke(ledger, argument, null); // Assert verify(ledger).scan(filter.capture()); assertThat(filter.getValue().getId()).isEqualTo(ID); - assertThat(filter.getValue().getStartVersion().get()).isEqualTo(START); + assertThat(filter.getValue().getStartAge().get()).isEqualTo(START); assertThat(filter.getValue().isStartInclusive()).isEqualTo(true); - assertThat(filter.getValue().getEndVersion().get()).isEqualTo(END); + assertThat(filter.getValue().getEndAge().get()).isEqualTo(END); assertThat(filter.getValue().isEndInclusive()).isEqualTo(false); assertThat(filter.getValue().getLimit()).isEqualTo(LIMIT); - assertThat(filter.getValue().getVersionOrder().get()).isEqualTo(AssetFilter.VersionOrder.ASC); + assertThat(filter.getValue().getAgeOrder().get()).isEqualTo(AssetFilter.AgeOrder.ASC); } } diff --git a/docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/CreateAccountTest.java b/docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/CreateAccountTest.java similarity index 50% rename from docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/CreateAccountTest.java rename to docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/CreateAccountTest.java index d15c6a24..f34be789 100644 --- a/docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/CreateAccountTest.java +++ b/docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/CreateAccountTest.java @@ -4,38 +4,46 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.when; -import com.scalar.dl.ledger.asset.Asset; -import com.scalar.dl.ledger.contract.Contract; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.scalar.dl.ledger.statemachine.Asset; +import com.scalar.dl.ledger.contract.JacksonBasedContract; import com.scalar.dl.ledger.exception.ContractContextException; -import com.scalar.dl.ledger.database.Ledger; +import com.scalar.dl.ledger.statemachine.Ledger; import java.util.Optional; import java.util.UUID; -import javax.json.Json; -import javax.json.JsonObject; -import org.junit.Before; -import org.junit.Test; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class CreateAccountTest { private final String ID = UUID.randomUUID().toString(); private final String ID_KEY = "id"; - private final Contract contract = new CreateAccount(); - @Mock private Ledger ledger; - @Mock private Asset asset; + private final JacksonBasedContract contract = new CreateAccount(); + @Mock private Ledger ledger; + @Mock private Asset asset; + private AutoCloseable closeable; - @Before + @BeforeEach public void setUp() { - MockitoAnnotations.initMocks(this); + closeable = MockitoAnnotations.openMocks(this); + } + + @AfterEach + public void tearDown() throws Exception { + closeable.close(); } @Test public void invoke_EmptyArgument_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().build(); + JsonNode argument = new ObjectMapper().createObjectNode(); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessageStartingWith("a required key is missing:"); } @@ -43,24 +51,24 @@ public void invoke_EmptyArgument_ShouldThrowContractContextException() { @Test public void invoke_AccountDoesNotExist_ShouldReturnSucceeded() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID); when(ledger.get(ID)).thenReturn(Optional.empty()); // Act - JsonObject response = contract.invoke(ledger, argument, Optional.empty()); + JsonNode response = contract.invoke(ledger, argument, null); // Assert - assertThat(response.getString("status")).isEqualTo("succeeded"); + assertThat(response.get("status").asText()).isEqualTo("succeeded"); } @Test public void invoke_AccountExists_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID); when(ledger.get(ID)).thenReturn(Optional.of(asset)); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessage("account already exists"); } diff --git a/docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/DepositTest.java b/docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/DepositTest.java similarity index 53% rename from docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/DepositTest.java rename to docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/DepositTest.java index 27eb2240..badf4136 100644 --- a/docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/DepositTest.java +++ b/docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/DepositTest.java @@ -4,16 +4,18 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.when; -import com.scalar.dl.ledger.asset.Asset; -import com.scalar.dl.ledger.contract.Contract; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.scalar.dl.ledger.statemachine.Asset; +import com.scalar.dl.ledger.contract.JacksonBasedContract; import com.scalar.dl.ledger.exception.ContractContextException; -import com.scalar.dl.ledger.database.Ledger; +import com.scalar.dl.ledger.statemachine.Ledger; import java.util.Optional; import java.util.UUID; -import javax.json.Json; -import javax.json.JsonObject; -import org.junit.Before; -import org.junit.Test; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -23,23 +25,29 @@ public class DepositTest { private final String ID = UUID.randomUUID().toString(); private final String ID_KEY = "id"; private final String STATUS_KEY = "status"; - private final Contract contract = new Deposit(); - @Mock private Ledger ledger; - @Mock private Asset asset; + private final JacksonBasedContract contract = new Deposit(); + @Mock private Ledger ledger; + @Mock private Asset asset; + private AutoCloseable closeable; - @Before + @BeforeEach public void setUp() { - MockitoAnnotations.initMocks(this); - when(asset.data()).thenReturn(Json.createObjectBuilder().add(BALANCE_KEY, 1).build()); + closeable = MockitoAnnotations.openMocks(this); + when(asset.data()).thenReturn(new ObjectMapper().createObjectNode().put(BALANCE_KEY, 1)); + } + + @AfterEach + public void tearDown() throws Exception { + closeable.close(); } @Test public void invoke_EmptyArgument_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().build(); + JsonNode argument = new ObjectMapper().createObjectNode(); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessageStartingWith("a required key is missing:"); } @@ -47,10 +55,10 @@ public void invoke_EmptyArgument_ShouldThrowContractContextException() { @Test public void invoke_IdKeyNotSuppliedInArgument_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(AMOUNT_KEY, 1).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(AMOUNT_KEY, 1); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessageStartingWith("a required key is missing:"); } @@ -58,10 +66,10 @@ public void invoke_IdKeyNotSuppliedInArgument_ShouldThrowContractContextExceptio @Test public void invoke_AmountKeyNotSuppliedInArgument_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, 1).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, 1); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessageStartingWith("a required key is missing:"); } @@ -69,26 +77,26 @@ public void invoke_AmountKeyNotSuppliedInArgument_ShouldThrowContractContextExce @Test public void invoke_AccountExists_ShouldIncreaseBalanceByAppropriateAmount() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).add(AMOUNT_KEY, 1).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID).put(AMOUNT_KEY, 1); when(ledger.get(ID)).thenReturn(Optional.of(asset)); // Act - JsonObject response = contract.invoke(ledger, argument, Optional.empty()); + JsonNode response = contract.invoke(ledger, argument, null); // Arrange - assertThat(response.getString(STATUS_KEY)).isEqualTo("succeeded"); - assertThat(response.getInt("old_balance")).isEqualTo(1); - assertThat(response.getInt("new_balance")).isEqualTo(2); + assertThat(response.get(STATUS_KEY).asText()).isEqualTo("succeeded"); + assertThat(response.get("old_balance").asInt()).isEqualTo(1); + assertThat(response.get("new_balance").asInt()).isEqualTo(2); } @Test public void invoke_AccountDoesNotExist_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).add(AMOUNT_KEY, 1).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID).put(AMOUNT_KEY, 1); when(ledger.get(ID)).thenReturn(Optional.empty()); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessage("account does not exist"); } @@ -96,11 +104,11 @@ public void invoke_AccountDoesNotExist_ShouldThrowContractContextException() { @Test public void invoke_DepositAmountIsNegative_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).add(AMOUNT_KEY, -1).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID).put(AMOUNT_KEY, -1); when(ledger.get(ID)).thenReturn(Optional.of(asset)); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessage("amount is negative"); } diff --git a/docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/TransferTest.java b/docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/TransferTest.java similarity index 52% rename from docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/TransferTest.java rename to docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/TransferTest.java index fcbe2355..0ebbca1c 100644 --- a/docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/TransferTest.java +++ b/docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/TransferTest.java @@ -4,16 +4,17 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.when; -import com.scalar.dl.ledger.asset.Asset; -import com.scalar.dl.ledger.contract.Contract; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.scalar.dl.ledger.statemachine.Asset; +import com.scalar.dl.ledger.contract.JacksonBasedContract; import com.scalar.dl.ledger.exception.ContractContextException; -import com.scalar.dl.ledger.database.Ledger; +import com.scalar.dl.ledger.statemachine.Ledger; import java.util.Optional; import java.util.UUID; -import javax.json.Json; -import javax.json.JsonObject; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -25,25 +26,31 @@ public class TransferTest { private final String STATUS_KEY = "status"; private final String TO = UUID.randomUUID().toString(); private final String TO_KEY = "to"; - private final Contract contract = new Transfer(); - @Mock private Ledger ledger; - @Mock private Asset fromAsset; - @Mock private Asset toAsset; + private final JacksonBasedContract contract = new Transfer(); + @Mock private Ledger ledger; + @Mock private Asset fromAsset; + @Mock private Asset toAsset; + private AutoCloseable closeable; - @Before + @BeforeEach public void setUp() { - MockitoAnnotations.initMocks(this); - when(fromAsset.data()).thenReturn(Json.createObjectBuilder().add(BALANCE_KEY, 1).build()); - when(toAsset.data()).thenReturn(Json.createObjectBuilder().add(BALANCE_KEY, 0).build()); + closeable = MockitoAnnotations.openMocks(this); + when(fromAsset.data()).thenReturn(new ObjectMapper().createObjectNode().put(BALANCE_KEY, 1)); + when(toAsset.data()).thenReturn(new ObjectMapper().createObjectNode().put(BALANCE_KEY, 0)); + } + + @AfterEach + public void tearDown() throws Exception { + closeable.close(); } @Test public void invoke_EmptyArgument_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().build(); + JsonNode argument = new ObjectMapper().createObjectNode(); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessageStartingWith("a required key is missing:"); } @@ -51,10 +58,10 @@ public void invoke_EmptyArgument_ShouldThrowContractContextException() { @Test public void invoke_FromKeyNotSuppliedInArgument_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(TO_KEY, TO).add(AMOUNT_KEY, 1).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(TO_KEY, TO).put(AMOUNT_KEY, 1); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessageStartingWith("a required key is missing:"); } @@ -62,10 +69,11 @@ public void invoke_FromKeyNotSuppliedInArgument_ShouldThrowContractContextExcept @Test public void invoke_ToKeyNotSuppliedInArgument_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(FROM_KEY, FROM).add(AMOUNT_KEY, 1).build(); + JsonNode argument = + new ObjectMapper().createObjectNode().put(FROM_KEY, FROM).put(AMOUNT_KEY, 1); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessageStartingWith("a required key is missing:"); } @@ -73,10 +81,10 @@ public void invoke_ToKeyNotSuppliedInArgument_ShouldThrowContractContextExceptio @Test public void invoke_AmountKeyNotSuppliedInArgument_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(TO_KEY, TO).add(FROM_KEY, FROM).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(TO_KEY, TO).put(FROM_KEY, FROM); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessageStartingWith("a required key is missing:"); } @@ -84,32 +92,40 @@ public void invoke_AmountKeyNotSuppliedInArgument_ShouldThrowContractContextExce @Test public void invoke_AccountExists_ShouldTransferFundsByAppropriateAmount() { // Arrange - JsonObject argument = - Json.createObjectBuilder().add(FROM_KEY, FROM).add(TO_KEY, TO).add(AMOUNT_KEY, 1).build(); + JsonNode argument = + new ObjectMapper() + .createObjectNode() + .put(FROM_KEY, FROM) + .put(TO_KEY, TO) + .put(AMOUNT_KEY, 1); when(ledger.get(FROM)).thenReturn(Optional.of(fromAsset)); when(ledger.get(TO)).thenReturn(Optional.of(toAsset)); // Act - JsonObject response = contract.invoke(ledger, argument, Optional.empty()); + JsonNode response = contract.invoke(ledger, argument, null); // Arrange - assertThat(response.getString(STATUS_KEY)).isEqualTo("succeeded"); - assertThat(response.getInt("from_old_balance")).isEqualTo(1); - assertThat(response.getInt("from_new_balance")).isEqualTo(0); - assertThat(response.getInt("to_old_balance")).isEqualTo(0); - assertThat(response.getInt("to_new_balance")).isEqualTo(1); + assertThat(response.get(STATUS_KEY).asText()).isEqualTo("succeeded"); + assertThat(response.get("from_old_balance").asInt()).isEqualTo(1); + assertThat(response.get("from_new_balance").asInt()).isEqualTo(0); + assertThat(response.get("to_old_balance").asInt()).isEqualTo(0); + assertThat(response.get("to_new_balance").asInt()).isEqualTo(1); } @Test public void invoke_FromAccountDoesNotExist_ShouldThrowContractContextException() { // Arrange - JsonObject argument = - Json.createObjectBuilder().add(FROM_KEY, FROM).add(TO_KEY, TO).add(AMOUNT_KEY, 1).build(); + JsonNode argument = + new ObjectMapper() + .createObjectNode() + .put(FROM_KEY, FROM) + .put(TO_KEY, TO) + .put(AMOUNT_KEY, 1); when(ledger.get(FROM)).thenReturn(Optional.empty()); when(ledger.get(TO)).thenReturn(Optional.of(toAsset)); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessage("from account does not exist"); } @@ -117,13 +133,17 @@ public void invoke_FromAccountDoesNotExist_ShouldThrowContractContextException() @Test public void invoke_ToAccountDoesNotExist_ShouldThrowContractContextException() { // Arrange - JsonObject argument = - Json.createObjectBuilder().add(FROM_KEY, FROM).add(TO_KEY, TO).add(AMOUNT_KEY, 1).build(); + JsonNode argument = + new ObjectMapper() + .createObjectNode() + .put(FROM_KEY, FROM) + .put(TO_KEY, TO) + .put(AMOUNT_KEY, 1); when(ledger.get(FROM)).thenReturn(Optional.of(fromAsset)); when(ledger.get(TO)).thenReturn(Optional.empty()); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessage("to account does not exist"); } @@ -132,13 +152,17 @@ public void invoke_ToAccountDoesNotExist_ShouldThrowContractContextException() { public void invoke_DepositAmountIsNegative_ShouldThrowContractContextException() { // Arrange // Arrange - JsonObject argument = - Json.createObjectBuilder().add(FROM_KEY, FROM).add(TO_KEY, TO).add(AMOUNT_KEY, -1).build(); + JsonNode argument = + new ObjectMapper() + .createObjectNode() + .put(FROM_KEY, FROM) + .put(TO_KEY, TO) + .put(AMOUNT_KEY, -1); when(ledger.get(FROM)).thenReturn(Optional.empty()); when(ledger.get(TO)).thenReturn(Optional.of(toAsset)); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessage("amount is negative"); } @@ -146,13 +170,17 @@ public void invoke_DepositAmountIsNegative_ShouldThrowContractContextException() @Test public void invoke_TransferMoreThanCurrentBalance_ShouldThrowContractContextException() { // Arrange - JsonObject argument = - Json.createObjectBuilder().add(FROM_KEY, FROM).add(TO_KEY, TO).add(AMOUNT_KEY, 2).build(); + JsonNode argument = + new ObjectMapper() + .createObjectNode() + .put(FROM_KEY, FROM) + .put(TO_KEY, TO) + .put(AMOUNT_KEY, 2); when(ledger.get(FROM)).thenReturn(Optional.of(fromAsset)); when(ledger.get(TO)).thenReturn(Optional.of(toAsset)); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessage("insufficient funds"); } diff --git a/docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/WithdrawTest.java b/docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/WithdrawTest.java similarity index 54% rename from docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/WithdrawTest.java rename to docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/WithdrawTest.java index 0523361c..606e7e98 100644 --- a/docs/applications/simple-bank-account/src/test/java/com/scalar/application/bankaccount/contract/WithdrawTest.java +++ b/docs/applications/simple-bank-account/contract/src/test/java/com/scalar/application/bankaccount/contract/WithdrawTest.java @@ -4,16 +4,17 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.when; -import com.scalar.dl.ledger.asset.Asset; -import com.scalar.dl.ledger.contract.Contract; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.scalar.dl.ledger.statemachine.Asset; +import com.scalar.dl.ledger.contract.JacksonBasedContract; import com.scalar.dl.ledger.exception.ContractContextException; -import com.scalar.dl.ledger.database.Ledger; +import com.scalar.dl.ledger.statemachine.Ledger; import java.util.Optional; import java.util.UUID; -import javax.json.Json; -import javax.json.JsonObject; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -23,23 +24,29 @@ public class WithdrawTest { private final String ID = UUID.randomUUID().toString(); private final String ID_KEY = "id"; private final String STATUS_KEY = "status"; - private final Contract contract = new Withdraw(); - @Mock private Ledger ledger; - @Mock private Asset asset; + private final JacksonBasedContract contract = new Withdraw(); + @Mock private Ledger ledger; + @Mock private Asset asset; + private AutoCloseable closeable; - @Before + @BeforeEach public void setUp() { - MockitoAnnotations.initMocks(this); - when(asset.data()).thenReturn(Json.createObjectBuilder().add(BALANCE_KEY, 1).build()); + closeable = MockitoAnnotations.openMocks(this); + when(asset.data()).thenReturn(new ObjectMapper().createObjectNode().put(BALANCE_KEY, 1)); + } + + @AfterEach + public void tearDown() throws Exception { + closeable.close(); } @Test public void invoke_EmptyArgument_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().build(); + JsonNode argument = new ObjectMapper().createObjectNode(); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessageStartingWith("a required key is missing:"); } @@ -47,10 +54,10 @@ public void invoke_EmptyArgument_ShouldThrowContractContextException() { @Test public void invoke_IdKeyNotSuppliedInArgument_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(AMOUNT_KEY, 1).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(AMOUNT_KEY, 1); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessageStartingWith("a required key is missing:"); } @@ -58,10 +65,10 @@ public void invoke_IdKeyNotSuppliedInArgument_ShouldThrowContractContextExceptio @Test public void invoke_AmountKeyNotSuppliedInArgument_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, 1).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, 1); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessageStartingWith("a required key is missing:"); } @@ -69,26 +76,26 @@ public void invoke_AmountKeyNotSuppliedInArgument_ShouldThrowContractContextExce @Test public void invoke_AccountExists_ShouldDecreaseBalanceByAppropriateAmount() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).add(AMOUNT_KEY, 1).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID).put(AMOUNT_KEY, 1); when(ledger.get(ID)).thenReturn(Optional.of(asset)); // Act - JsonObject response = contract.invoke(ledger, argument, Optional.empty()); + JsonNode response = contract.invoke(ledger, argument, null); // Arrange - assertThat(response.getString(STATUS_KEY)).isEqualTo("succeeded"); - assertThat(response.getInt("old_balance")).isEqualTo(1); - assertThat(response.getInt("new_balance")).isEqualTo(0); + assertThat(response.get(STATUS_KEY).asText()).isEqualTo("succeeded"); + assertThat(response.get("old_balance").asInt()).isEqualTo(1); + assertThat(response.get("new_balance").asInt()).isEqualTo(0); } @Test public void invoke_AccountDoesNotExist_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).add(AMOUNT_KEY, 1).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID).put(AMOUNT_KEY, 1); when(ledger.get(ID)).thenReturn(Optional.empty()); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessage("account does not exist"); } @@ -96,11 +103,11 @@ public void invoke_AccountDoesNotExist_ShouldThrowContractContextException() { @Test public void invoke_DepositAmountIsNegative_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).add(AMOUNT_KEY, -1).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID).put(AMOUNT_KEY, -1); when(ledger.get(ID)).thenReturn(Optional.of(asset)); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessage("amount is negative"); } @@ -108,11 +115,11 @@ public void invoke_DepositAmountIsNegative_ShouldThrowContractContextException() @Test public void invoke_WithdrawMoreThanCurrentBalance_ShouldThrowContractContextException() { // Arrange - JsonObject argument = Json.createObjectBuilder().add(ID_KEY, ID).add(AMOUNT_KEY, 2).build(); + JsonNode argument = new ObjectMapper().createObjectNode().put(ID_KEY, ID).put(AMOUNT_KEY, 2); when(ledger.get(ID)).thenReturn(Optional.of(asset)); // Act-assert - assertThatThrownBy(() -> contract.invoke(ledger, argument, Optional.empty())) + assertThatThrownBy(() -> contract.invoke(ledger, argument, null)) .isInstanceOf(ContractContextException.class) .hasMessage("insufficient funds"); } diff --git a/docs/applications/simple-bank-account/gradle/wrapper/gradle-wrapper.jar b/docs/applications/simple-bank-account/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e6441136 Binary files /dev/null and b/docs/applications/simple-bank-account/gradle/wrapper/gradle-wrapper.jar differ diff --git a/docs/applications/simple-bank-account/gradle/wrapper/gradle-wrapper.properties b/docs/applications/simple-bank-account/gradle/wrapper/gradle-wrapper.properties index 290541c7..a4413138 100644 --- a/docs/applications/simple-bank-account/gradle/wrapper/gradle-wrapper.properties +++ b/docs/applications/simple-bank-account/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/docs/applications/simple-bank-account/gradlew b/docs/applications/simple-bank-account/gradlew index cccdd3d5..b740cf13 100755 --- a/docs/applications/simple-bank-account/gradlew +++ b/docs/applications/simple-bank-account/gradlew @@ -1,78 +1,127 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,92 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/docs/applications/simple-bank-account/gradlew.bat b/docs/applications/simple-bank-account/gradlew.bat index e95643d6..7101f8e4 100644 --- a/docs/applications/simple-bank-account/gradlew.bat +++ b/docs/applications/simple-bank-account/gradlew.bat @@ -1,4 +1,20 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -9,25 +25,29 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -35,48 +55,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/docs/applications/simple-bank-account/register b/docs/applications/simple-bank-account/register deleted file mode 100755 index d9d68c11..00000000 --- a/docs/applications/simple-bank-account/register +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -${SCALAR_SDK_HOME}/client/bin/scalardl register-cert -properties ./conf/client.properties -${SCALAR_SDK_HOME}/client/bin/scalardl register-contracts -properties ./conf/client.properties -contracts-toml-file ./conf/contracts.toml diff --git a/docs/applications/simple-bank-account/settings.gradle b/docs/applications/simple-bank-account/settings.gradle index 0ff74490..5486562b 100644 --- a/docs/applications/simple-bank-account/settings.gradle +++ b/docs/applications/simple-bank-account/settings.gradle @@ -1 +1,3 @@ rootProject.name = 'simple-bank-account' +include 'contract' +include 'app' \ No newline at end of file diff --git a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/AccountHistory.java b/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/AccountHistory.java deleted file mode 100644 index 61dc6a4d..00000000 --- a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/AccountHistory.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.scalar.application.bankaccount.contract; - -import com.scalar.dl.ledger.asset.Asset; -import com.scalar.dl.ledger.contract.Contract; -import com.scalar.dl.ledger.database.AssetFilter; -import com.scalar.dl.ledger.exception.ContractContextException; -import com.scalar.dl.ledger.database.Ledger; -import java.util.List; -import java.util.Optional; -import javax.json.Json; -import javax.json.JsonArrayBuilder; -import javax.json.JsonObject; - -public class AccountHistory extends Contract { - @Override - public JsonObject invoke(Ledger ledger, JsonObject argument, Optional property) { - if (!argument.containsKey("id")) { - throw new ContractContextException("a required key is missing: id"); - } - - AssetFilter filter = new AssetFilter(argument.getString("id")); - if (argument.containsKey("start")) { - filter.withStartVersion(argument.getInt("start"), true); - } - if (argument.containsKey("end")) { - filter.withEndVersion(argument.getInt("end"), false); - } - if (argument.containsKey("limit")) { - filter.withLimit(argument.getInt("limit")); - } - if (argument.containsKey("order") && argument.getString("order").equals("asc")) { - filter.withVersionOrder(AssetFilter.VersionOrder.ASC); - } - - List history = ledger.scan(filter); - - JsonArrayBuilder result = Json.createArrayBuilder(); - history.forEach( - asset -> { - JsonObject json = - Json.createObjectBuilder() - .add("id", asset.id()) - .add("data", asset.data()) - .add("age", asset.age()) - .build(); - result.add(json); - }); - - return Json.createObjectBuilder() - .add("status", "succeeded") - .add("history", result.build()) - .build(); - } -} diff --git a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/CreateAccount.java b/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/CreateAccount.java deleted file mode 100644 index 6d9b90b6..00000000 --- a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/CreateAccount.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.scalar.application.bankaccount.contract; - -import com.scalar.dl.ledger.asset.Asset; -import com.scalar.dl.ledger.contract.Contract; -import com.scalar.dl.ledger.exception.ContractContextException; -import com.scalar.dl.ledger.database.Ledger; -import java.util.Optional; -import javax.json.Json; -import javax.json.JsonObject; - -public class CreateAccount extends Contract { - @Override - public JsonObject invoke(Ledger ledger, JsonObject argument, Optional property) { - if (!argument.containsKey("id")) { - throw new ContractContextException("a required key is missing: id"); - } - - String id = argument.getString("id"); - Optional response = ledger.get(id); - - if (response.isPresent()) { - throw new ContractContextException("account already exists"); - } - - ledger.put(id, Json.createObjectBuilder().add("balance", 0).build()); - return Json.createObjectBuilder() - .add("status", "succeeded") - .add("message", "account " + id + " created") - .build(); - } -} diff --git a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/Deposit.java b/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/Deposit.java deleted file mode 100644 index 088995a6..00000000 --- a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/Deposit.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.scalar.application.bankaccount.contract; - -import com.scalar.dl.ledger.asset.Asset; -import com.scalar.dl.ledger.contract.Contract; -import com.scalar.dl.ledger.exception.ContractContextException; -import com.scalar.dl.ledger.database.Ledger; -import java.util.Optional; -import javax.json.Json; -import javax.json.JsonObject; - -public class Deposit extends Contract { - @Override - public JsonObject invoke(Ledger ledger, JsonObject argument, Optional property) { - if (!(argument.containsKey("id") && argument.containsKey("amount"))) { - throw new ContractContextException("a required key is missing: id and/or amount"); - } - - String id = argument.getString("id"); - long amount = argument.getJsonNumber("amount").longValue(); - - if (amount < 0) { - throw new ContractContextException("amount is negative"); - } - - Optional response = ledger.get(id); - - if (!response.isPresent()) { - throw new ContractContextException("account does not exist"); - } - - long oldBalance = response.get().data().getInt("balance"); - long newBalance = oldBalance + amount; - - ledger.put(id, Json.createObjectBuilder().add("balance", newBalance).build()); - return Json.createObjectBuilder() - .add("status", "succeeded") - .add("old_balance", oldBalance) - .add("new_balance", newBalance) - .build(); - } -} diff --git a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/Transfer.java b/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/Transfer.java deleted file mode 100644 index cba5fa0a..00000000 --- a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/Transfer.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.scalar.application.bankaccount.contract; - -import com.scalar.dl.ledger.asset.Asset; -import com.scalar.dl.ledger.contract.Contract; -import com.scalar.dl.ledger.exception.ContractContextException; -import com.scalar.dl.ledger.database.Ledger; -import java.util.Optional; -import javax.json.Json; -import javax.json.JsonObject; - -public class Transfer extends Contract { - @Override - public JsonObject invoke(Ledger ledger, JsonObject argument, Optional property) { - if (!(argument.containsKey("from") - && argument.containsKey("to") - && argument.containsKey("amount"))) { - throw new ContractContextException("a required key is missing: from, to, and/or amount"); - } - - String fromId = argument.getString("from"); - String toId = argument.getString("to"); - long amount = argument.getJsonNumber("amount").longValue(); - - if (amount < 0) { - throw new ContractContextException("amount is negative"); - } - - Optional fromAsset = ledger.get(fromId); - Optional toAsset = ledger.get(toId); - - if (!fromAsset.isPresent()) { - throw new ContractContextException("from account does not exist"); - } - - if (!toAsset.isPresent()) { - throw new ContractContextException("to account does not exist"); - } - - long fromOldBalance = fromAsset.get().data().getInt("balance"); - long fromNewBalance = fromOldBalance - amount; - long toOldBalance = toAsset.get().data().getInt("balance"); - long toNewBalance = toOldBalance + amount; - - if (fromNewBalance < 0) { - throw new ContractContextException("insufficient funds"); - } - - ledger.put(fromId, Json.createObjectBuilder().add("balance", fromNewBalance).build()); - ledger.put(toId, Json.createObjectBuilder().add("balance", toNewBalance).build()); - return Json.createObjectBuilder() - .add("status", "succeeded") - .add("from_old_balance", fromOldBalance) - .add("from_new_balance", fromNewBalance) - .add("to_old_balance", toOldBalance) - .add("to_new_balance", toNewBalance) - .build(); - } -} diff --git a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/Withdraw.java b/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/Withdraw.java deleted file mode 100644 index aba9ccf0..00000000 --- a/docs/applications/simple-bank-account/src/main/java/com/scalar/application/bankaccount/contract/Withdraw.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.scalar.application.bankaccount.contract; - -import com.scalar.dl.ledger.asset.Asset; -import com.scalar.dl.ledger.contract.Contract; -import com.scalar.dl.ledger.exception.ContractContextException; -import com.scalar.dl.ledger.database.Ledger; -import java.util.Optional; -import javax.json.Json; -import javax.json.JsonObject; - -public class Withdraw extends Contract { - @Override - public JsonObject invoke(Ledger ledger, JsonObject argument, Optional property) { - if (!(argument.containsKey("id") && argument.containsKey("amount"))) { - throw new ContractContextException("a required key is missing: id and/or amount"); - } - - String id = argument.getString("id"); - long amount = argument.getJsonNumber("amount").longValue(); - - if (amount < 0) { - throw new ContractContextException("amount is negative"); - } - - Optional response = ledger.get(id); - - if (!response.isPresent()) { - throw new ContractContextException("account does not exist"); - } - - long oldBalance = response.get().data().getInt("balance"); - long newBalance = oldBalance - amount; - - if (newBalance < 0) { - throw new ContractContextException("insufficient funds"); - } - - ledger.put(id, Json.createObjectBuilder().add("balance", newBalance).build()); - return Json.createObjectBuilder() - .add("status", "succeeded") - .add("old_balance", oldBalance) - .add("new_balance", newBalance) - .build(); - } -}