mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 23:45:10 +00:00
FGJ-4 commercial paper client sample
commercial paper sample for Java Gateway SDK (client app) Change-Id: I80c6b9dbc36631004903244a20e6a492138c7751 Signed-off-by: andrew-coleman <andrew_coleman@uk.ibm.com>
This commit is contained in:
parent
8f92861104
commit
c57f10f5f6
14 changed files with 554 additions and 0 deletions
23
commercial-paper/.classpath
Normal file
23
commercial-paper/.classpath
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="organization/digibank/application-java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" path="organization/magnetocorp/application-java"/>
|
||||
<classpathentry kind="src" path="ledger-java-api"/>
|
||||
<classpathentry kind="src" path="organization/shared/lib-java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
2
commercial-paper/.gitignore
vendored
2
commercial-paper/.gitignore
vendored
|
|
@ -6,3 +6,5 @@ organization/digibank/contract/node_modules/
|
|||
organization/digibank/identity/user/
|
||||
package-lock.json
|
||||
.vscode
|
||||
/target/
|
||||
bin/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package org.example.ledger.api;
|
||||
|
||||
public @interface Field {
|
||||
public String name() default "";
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package org.example.ledger.api;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(value=RetentionPolicy.RUNTIME)
|
||||
public @interface Property {
|
||||
public String name() default "";
|
||||
public boolean key() default false;
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.example.ledger.api;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonArray;
|
||||
import javax.json.JsonNumber;
|
||||
import javax.json.JsonObject;
|
||||
import javax.json.JsonReader;
|
||||
import javax.json.JsonString;
|
||||
import javax.json.JsonValue;
|
||||
|
||||
public class State {
|
||||
private final String key;
|
||||
private final Map<String, String> properties;
|
||||
|
||||
protected final Supplier<String> keyBuilder(String[] parts) {
|
||||
return () -> Arrays.asList(parts).stream().map(part -> getProperty(part)).collect(Collectors.joining(":"));
|
||||
};
|
||||
|
||||
public State(byte[] buffer) {
|
||||
this.properties = deserialize(buffer);
|
||||
List<String> keyParts = new ArrayList<String>();
|
||||
for (Field field: getStateFields()) {
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
String value = properties.get(getPropertyName(field));
|
||||
field.set(this, value);
|
||||
boolean isKey = field.getAnnotation(Property.class).key();
|
||||
if(isKey) {
|
||||
keyParts.add(value);
|
||||
}
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
key = String.join(":", keyParts);
|
||||
System.out.println(key);
|
||||
}
|
||||
|
||||
private static Map<String, String> deserialize(byte[] buffer) {
|
||||
JsonObject json = toJSON(buffer);
|
||||
Map<String, String> result = json.entrySet().stream().collect(
|
||||
Collectors.toMap(e -> e.getKey(), e -> jsonString(e.getValue())));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String jsonString(JsonValue value) {
|
||||
if(value.getValueType() == JsonValue.ValueType.STRING) {
|
||||
return ((JsonString)value).getString();
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
private static JsonObject toJSON(byte[] buffer) {
|
||||
String payload = new String(buffer);
|
||||
String payloadData;
|
||||
try (JsonReader reader = Json.createReader(new StringReader(payload))) {
|
||||
JsonObject json = reader.readObject();
|
||||
JsonArray arr = json.getJsonArray("data");
|
||||
byte[] codepoints = new byte[arr.size()];
|
||||
for(int i = 0; i < arr.size(); i++) {
|
||||
codepoints[i] = (byte) ((JsonNumber)arr.get(i)).intValue();
|
||||
}
|
||||
payloadData = new String(codepoints);
|
||||
}
|
||||
try (JsonReader reader = Json.createReader(new StringReader(payloadData))) {
|
||||
JsonObject json = reader.readObject();
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getPropertyName(java.lang.reflect.Field field) {
|
||||
String name = field.getAnnotation(Property.class).name();
|
||||
if (name.isEmpty()) {
|
||||
name = field.getName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private String getProperty(String key) {
|
||||
return properties.get(key);
|
||||
}
|
||||
|
||||
private List<Field> getStateFields() {
|
||||
Class<? extends State> clazz = this.getClass().asSubclass(this.getClass());
|
||||
return Arrays.asList(clazz.getDeclaredFields())
|
||||
.stream()
|
||||
.filter(field -> field.isAnnotationPresent(Property.class))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(this.getClass().getName());
|
||||
builder.append(" [");
|
||||
for (Field field: getStateFields()) {
|
||||
field.setAccessible(true);
|
||||
builder.append(field.getName());
|
||||
builder.append("=");
|
||||
try {
|
||||
builder.append(field.get(this));
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
// ignore for now
|
||||
}
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.setCharAt(builder.length() - 2, ']');
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.digibank;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.hyperledger.fabric.gateway.GatewayException;
|
||||
import org.hyperledger.fabric.gateway.Wallet;
|
||||
import org.hyperledger.fabric.gateway.Wallet.Identity;
|
||||
|
||||
public class AddToWallet {
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// A wallet stores a collection of identities
|
||||
Path walletPath = Paths.get("organization", "digibank", "identity", "user", "balaji", "wallet");
|
||||
Wallet wallet = Wallet.createFileSystemWallet(walletPath);
|
||||
|
||||
// Location of credentials to be stored in the wallet
|
||||
Path credentialPath = Paths.get("..", "basic-network", "crypto-config",
|
||||
"peerOrganizations", "org1.example.com", "users", "Admin@org1.example.com", "msp");
|
||||
Path certificatePem = credentialPath.resolve(Paths.get("signcerts",
|
||||
"Admin@org1.example.com-cert.pem"));
|
||||
Path privateKey = credentialPath.resolve(Paths.get("keystore",
|
||||
"cd96d5260ad4757551ed4a5a991e62130f8008a0bf996e4e4b84cd097a747fec_sk"));
|
||||
|
||||
// Load credentials into wallet
|
||||
String identityLabel = "Admin@org1.example.com";
|
||||
Identity identity = Identity.createIdentity("Org1MSP", Files.newBufferedReader(certificatePem), Files.newBufferedReader(privateKey));
|
||||
|
||||
wallet.put(identityLabel, identity);
|
||||
|
||||
} catch (IOException | GatewayException e) {
|
||||
System.err.println("Error adding to wallet");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.digibank;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.hyperledger.fabric.gateway.Contract;
|
||||
import org.hyperledger.fabric.gateway.Gateway;
|
||||
import org.hyperledger.fabric.gateway.GatewayException;
|
||||
import org.hyperledger.fabric.gateway.Network;
|
||||
import org.hyperledger.fabric.gateway.Wallet;
|
||||
import org.papernet.CommercialPaper;
|
||||
|
||||
public class Buy {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Gateway.Builder builder = Gateway.createBuilder();
|
||||
|
||||
try {
|
||||
// A wallet stores a collection of identities
|
||||
Path walletPath = Paths.get("organization", "digibank", "identity", "user", "balaji", "wallet");
|
||||
Wallet wallet = Wallet.createFileSystemWallet(walletPath);
|
||||
|
||||
String userName = "Admin@org1.example.com";
|
||||
|
||||
Path connectionProfile = Paths.get("organization", "digibank", "gateway", "networkConnection.yaml");
|
||||
|
||||
// Set connection options on the gateway builder
|
||||
builder.identity(wallet, userName).networkConfig(connectionProfile).discovery(false);
|
||||
|
||||
// Connect to gateway using application specified parameters
|
||||
try(Gateway gateway = builder.connect()) {
|
||||
|
||||
// Access PaperNet network
|
||||
System.out.println("Use network channel: mychannel.");
|
||||
Network network = gateway.getNetwork("mychannel");
|
||||
|
||||
// Get addressability to commercial paper contract
|
||||
System.out.println("Use org.papernet.commercialpaper smart contract.");
|
||||
Contract contract = network.getContract("papercontract", "org.papernet.commercialpaper");
|
||||
|
||||
// Buy commercial paper
|
||||
System.out.println("Submit commercial paper buy transaction.");
|
||||
byte[] response = contract.submitTransaction("buy", "MagnetoCorp", "00001", "MagnetoCorp", "DigiBank", "4900000", "2020-05-31");
|
||||
|
||||
// Process response
|
||||
System.out.println("Process buy transaction response.");
|
||||
CommercialPaper paper = CommercialPaper.create(response);
|
||||
System.out.println(paper);
|
||||
}
|
||||
} catch (GatewayException | IOException | TimeoutException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.digibank;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.hyperledger.fabric.gateway.Contract;
|
||||
import org.hyperledger.fabric.gateway.Gateway;
|
||||
import org.hyperledger.fabric.gateway.GatewayException;
|
||||
import org.hyperledger.fabric.gateway.Network;
|
||||
import org.hyperledger.fabric.gateway.Wallet;
|
||||
import org.papernet.CommercialPaper;
|
||||
|
||||
public class Redeem {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Gateway.Builder builder = Gateway.createBuilder();
|
||||
|
||||
try {
|
||||
// A wallet stores a collection of identities
|
||||
Path walletPath = Paths.get("organization", "digibank", "identity", "user", "balaji", "wallet");
|
||||
Wallet wallet = Wallet.createFileSystemWallet(walletPath);
|
||||
|
||||
String userName = "Admin@org1.example.com";
|
||||
|
||||
Path connectionProfile = Paths.get("organization", "digibank", "gateway", "networkConnection.yaml");
|
||||
|
||||
// Set connection options on the gateway builder
|
||||
builder.identity(wallet, userName).networkConfig(connectionProfile).discovery(false);
|
||||
|
||||
// Connect to gateway using application specified parameters
|
||||
try(Gateway gateway = builder.connect()) {
|
||||
|
||||
// Access PaperNet network
|
||||
System.out.println("Use network channel: mychannel.");
|
||||
Network network = gateway.getNetwork("mychannel");
|
||||
|
||||
// Get addressability to commercial paper contract
|
||||
System.out.println("Use org.papernet.commercialpaper smart contract.");
|
||||
Contract contract = network.getContract("papercontract", "org.papernet.commercialpaper");
|
||||
|
||||
// Redeem commercial paper
|
||||
System.out.println("Submit commercial paper redeem transaction.");
|
||||
byte[] response = contract.submitTransaction("redeem", "MagnetoCorp", "00001", "DigiBank", "2020-11-30");
|
||||
|
||||
// Process response
|
||||
System.out.println("Process redeem transaction response.");
|
||||
CommercialPaper paper = CommercialPaper.create(response);
|
||||
System.out.println(paper);
|
||||
}
|
||||
} catch (GatewayException | IOException | TimeoutException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -26,6 +26,9 @@ description: "The basic network"
|
|||
#
|
||||
version: "1.0"
|
||||
|
||||
client:
|
||||
organization: Org1
|
||||
|
||||
#
|
||||
# [Optional]. But most apps would have this section so that channel objects can be constructed
|
||||
# based on the content below. If an app is creating channels, then it likely will not need this
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.magnetocorp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.hyperledger.fabric.gateway.GatewayException;
|
||||
import org.hyperledger.fabric.gateway.Wallet;
|
||||
import org.hyperledger.fabric.gateway.Wallet.Identity;
|
||||
|
||||
public class AddToWallet {
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// A wallet stores a collection of identities
|
||||
Path walletPath = Paths.get("organization", "magnetocorp", "identity", "user", "isabella", "wallet");
|
||||
Wallet wallet = Wallet.createFileSystemWallet(walletPath);
|
||||
|
||||
// Location of credentials to be stored in the wallet
|
||||
Path credentialPath = Paths.get("..", "basic-network", "crypto-config",
|
||||
"peerOrganizations", "org1.example.com", "users", "User1@org1.example.com", "msp");
|
||||
Path certificatePem = credentialPath.resolve(Paths.get("signcerts",
|
||||
"User1@org1.example.com-cert.pem"));
|
||||
Path privateKey = credentialPath.resolve(Paths.get("keystore",
|
||||
"c75bd6911aca808941c3557ee7c97e90f3952e379497dc55eb903f31b50abc83_sk"));
|
||||
|
||||
// Load credentials into wallet
|
||||
String identityLabel = "User1@org1.example.com";
|
||||
Identity identity = Identity.createIdentity("Org1MSP", Files.newBufferedReader(certificatePem), Files.newBufferedReader(privateKey));
|
||||
|
||||
wallet.put(identityLabel, identity);
|
||||
|
||||
} catch (IOException | GatewayException e) {
|
||||
System.err.println("Error adding to wallet");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.magnetocorp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.hyperledger.fabric.gateway.Contract;
|
||||
import org.hyperledger.fabric.gateway.Gateway;
|
||||
import org.hyperledger.fabric.gateway.GatewayException;
|
||||
import org.hyperledger.fabric.gateway.Network;
|
||||
import org.hyperledger.fabric.gateway.Wallet;
|
||||
import org.papernet.CommercialPaper;
|
||||
|
||||
public class Issue {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Gateway.Builder builder = Gateway.createBuilder();
|
||||
|
||||
try {
|
||||
// A wallet stores a collection of identities
|
||||
Path walletPath = Paths.get("organization", "magnetocorp", "identity", "user", "isabella", "wallet");
|
||||
Wallet wallet = Wallet.createFileSystemWallet(walletPath);
|
||||
|
||||
String userName = "User1@org1.example.com";
|
||||
|
||||
Path connectionProfile = Paths.get("organization", "magnetocorp", "gateway", "networkConnection.yaml");
|
||||
|
||||
// Set connection options on the gateway builder
|
||||
builder.identity(wallet, userName).networkConfig(connectionProfile).discovery(false);
|
||||
|
||||
// Connect to gateway using application specified parameters
|
||||
try(Gateway gateway = builder.connect()) {
|
||||
|
||||
// Access PaperNet network
|
||||
System.out.println("Use network channel: mychannel.");
|
||||
Network network = gateway.getNetwork("mychannel");
|
||||
|
||||
// Get addressability to commercial paper contract
|
||||
System.out.println("Use org.papernet.commercialpaper smart contract.");
|
||||
Contract contract = network.getContract("papercontract", "org.papernet.commercialpaper");
|
||||
|
||||
// Issue commercial paper
|
||||
System.out.println("Submit commercial paper issue transaction.");
|
||||
byte[] response = contract.submitTransaction("issue", "MagnetoCorp", "00001", "2020-05-31", "2020-11-30", "5000000");
|
||||
|
||||
// Process response
|
||||
System.out.println("Process issue transaction response.");
|
||||
CommercialPaper paper = CommercialPaper.create(response);
|
||||
System.out.println(paper);
|
||||
}
|
||||
} catch (GatewayException | IOException | TimeoutException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -26,6 +26,9 @@ description: "The basic network"
|
|||
#
|
||||
version: "1.0"
|
||||
|
||||
client:
|
||||
organization: Org1
|
||||
|
||||
#
|
||||
# [Optional]. But most apps would have this section so that channel objects can be constructed
|
||||
# based on the content below. If an app is creating channels, then it likely will not need this
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
package org.papernet;
|
||||
|
||||
import org.example.ledger.api.Property;
|
||||
import org.example.ledger.api.State;
|
||||
|
||||
public class CommercialPaper extends State {
|
||||
private static final String ISSUED = "1";
|
||||
private static final String TRADING = "2";
|
||||
private static final String REDEEMED = "3";
|
||||
|
||||
@Property(key=true)
|
||||
private String issuer;
|
||||
|
||||
@Property
|
||||
private String owner;
|
||||
|
||||
@Property(key=true)
|
||||
private String paperNumber;
|
||||
|
||||
@Property
|
||||
private String issueDateTime;
|
||||
|
||||
@Property
|
||||
private String maturityDateTime;
|
||||
|
||||
@Property
|
||||
private String faceValue;
|
||||
|
||||
@Property(name="currentState")
|
||||
private String status;
|
||||
|
||||
private CommercialPaper(byte[] buffer) {
|
||||
super(buffer);
|
||||
}
|
||||
|
||||
public static CommercialPaper create(byte[] buffer) {
|
||||
return new CommercialPaper(buffer);
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(String owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public String getIssuer() {
|
||||
return issuer;
|
||||
}
|
||||
|
||||
public void setIssued() {
|
||||
status = ISSUED;
|
||||
}
|
||||
|
||||
public void setTrading() {
|
||||
status = TRADING;
|
||||
}
|
||||
|
||||
public void setRedeemed() {
|
||||
status = REDEEMED;
|
||||
}
|
||||
|
||||
public boolean isIssued() {
|
||||
return status == ISSUED;
|
||||
}
|
||||
|
||||
public boolean isTrading() {
|
||||
return status == TRADING;
|
||||
}
|
||||
|
||||
public boolean isRedeemed() {
|
||||
return status == REDEEMED;
|
||||
}
|
||||
|
||||
}
|
||||
33
commercial-paper/pom.xml
Normal file
33
commercial-paper/pom.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>commercial-paper</groupId>
|
||||
<artifactId>commercial-paper</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<build>
|
||||
<sourceDirectory>organization/digibank/application-java</sourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>hyperledger</id>
|
||||
<name>Hyperledger Nexus</name>
|
||||
<url>https://nexus.hyperledger.org/content/repositories/snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.hyperledger.fabric</groupId>
|
||||
<artifactId>fabric-gateway-java</artifactId>
|
||||
<version>1.4.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
Loading…
Reference in a new issue