From 3d77780200930a0ce4c54e425238147e3b59d7f9 Mon Sep 17 00:00:00 2001 From: blavenie <benoit.lavenier@e-is.pro> Date: Thu, 8 Dec 2016 19:29:10 +0100 Subject: [PATCH] - Use Jackson for JSON blockchain deserialization - Add user event on blockchain changes --- LICENSE | 2 +- duniter4j-core-client/LICENSE | 2 +- duniter4j-core-client/pom.xml | 80 +++---- .../client/model/bma/BlockchainBlock.java | 154 +++++++++++-- .../duniter/core/client/model/bma/Blocks.java | 54 +++++ .../core/client/model/bma/NetworkPeering.java | 63 +++++- .../core/client/model/bma/NetworkPeers.java | 92 ++++++++ .../core/client/model/bma/Protocol.java | 2 + .../core/client/model/bma/TxSource.java | 9 +- .../client/model/bma/WotCertification.java | 61 +++++- .../core/client/model/bma/WotLookup.java | 149 ++++++++++--- .../model/bma/gson/JoinerTypeAdapter.java | 8 +- .../model/bma/gson/RevokedTypeAdapter.java | 2 +- .../bma/jackson/EndpointDeserializer.java | 51 +++++ .../bma/jackson/IdentityDeserializer.java | 40 ++++ .../model/bma/jackson/JacksonUtils.java | 27 +++ .../model/bma/jackson/JoinerDeserializer.java | 40 ++++ .../bma/jackson/RevokedDeserializer.java | 36 +++ .../client/model/elasticsearch/Record.java | 6 +- .../core/client/service/HttpServiceImpl.java | 42 ++-- .../service/bma/BlockchainRemoteService.java | 12 +- .../bma/BlockchainRemoteServiceImpl.java | 24 +- .../bma/TransactionRemoteServiceImpl.java | 206 ++++++++++++++---- .../service/bma/WotRemoteServiceImpl.java | 18 +- .../CurrencyRegistryRemoteServiceImpl.java | 3 +- .../duniter4j-core-client_fr_FR.properties | 6 +- .../core/client/service/HttpServiceTest.java | 73 +++++++ .../bma/BlockchainRemoteServiceTest.java | 30 ++- .../bma/TransactionRemoteServiceTest.java | 2 - .../service/bma/WotRemoteServiceTest.java | 4 +- .../duniter4j-core-client-test.properties | 8 +- .../src/test/resources/log4j.properties | 2 +- duniter4j-core-shared/LICENSE | 2 +- duniter4j-es-core/LICENSE | 2 +- .../duniter/elasticsearch/PluginSettings.java | 4 + .../service/CurrencyService.java | 2 +- .../service/changes/ChangeEvent.java | 58 +++++ .../service/changes/ChangeListener.java | 6 - .../service/changes/ChangeService.java | 130 ++++++----- .../service/changes/ChangeSource.java | 61 ++++++ .../service/changes/ChangeUtils.java | 65 ------ ...int.java => WebSocketChangesEndPoint.java} | 33 ++- .../websocket/WebSocketModule.java | 3 +- .../websocket/WebSocketServer.java | 1 - .../gchange/service/MarketService.java | 2 +- .../elasticsearch/user/model/UserEvent.java | 105 +++++++-- .../user/model/UserEventCodes.java | 11 +- .../user/model/UserEventLink.java | 74 ------- .../service/BlockchainUserEventService.java | 190 ++++++++++++++++ .../user/service/ServiceModule.java | 5 +- .../user/service/UserEventService.java | 92 +++++--- .../user/service/UserService.java | 6 +- .../i18n/duniter4j-es-user_en_GB.properties | 11 +- .../i18n/duniter4j-es-user_fr_FR.properties | 8 +- 54 files changed, 1680 insertions(+), 499 deletions(-) create mode 100644 duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Blocks.java create mode 100644 duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/EndpointDeserializer.java create mode 100644 duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/IdentityDeserializer.java create mode 100644 duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/JacksonUtils.java create mode 100644 duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/JoinerDeserializer.java create mode 100644 duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/RevokedDeserializer.java create mode 100644 duniter4j-core-client/src/test/java/org/duniter/core/client/service/HttpServiceTest.java delete mode 100644 duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeListener.java delete mode 100644 duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeUtils.java rename duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/{changes/WebSocketChangeEndPoint.java => WebSocketChangesEndPoint.java} (70%) delete mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEventLink.java create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/BlockchainUserEventService.java diff --git a/LICENSE b/LICENSE index 94a9ed02..6df63bc4 100644 --- a/LICENSE +++ b/LICENSE @@ -552,7 +552,7 @@ License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed +permission to reference or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, diff --git a/duniter4j-core-client/LICENSE b/duniter4j-core-client/LICENSE index 94a9ed02..6df63bc4 100644 --- a/duniter4j-core-client/LICENSE +++ b/duniter4j-core-client/LICENSE @@ -552,7 +552,7 @@ License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed +permission to reference or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, diff --git a/duniter4j-core-client/pom.xml b/duniter4j-core-client/pom.xml index 4cdc3b20..d71653b9 100644 --- a/duniter4j-core-client/pom.xml +++ b/duniter4j-core-client/pom.xml @@ -39,50 +39,54 @@ <scope>runtime</scope> </dependency> - <dependency> - <groupId>org.glassfish.tyrus</groupId> - <artifactId>tyrus-client</artifactId> - <version>1.12</version> - </dependency> + <dependency> + <groupId>org.glassfish.tyrus</groupId> + <artifactId>tyrus-client</artifactId> + <version>1.12</version> + </dependency> - <dependency> - <groupId>org.glassfish.tyrus</groupId> - <artifactId>tyrus-container-grizzly-client</artifactId> - <version>1.12</version> - </dependency> + <dependency> + <groupId>org.glassfish.tyrus</groupId> + <artifactId>tyrus-container-grizzly-client</artifactId> + <version>1.12</version> + </dependency> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> - </dependency> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-collections4</artifactId> - </dependency> - <dependency> - <groupId>org.apache.httpcomponents</groupId> - <artifactId>httpclient</artifactId> - </dependency> - <dependency> - <groupId>org.apache.httpcomponents</groupId> - <artifactId>httpcore</artifactId> - </dependency> - <dependency> - <groupId>org.apache.httpcomponents</groupId> - <artifactId>httpmime</artifactId> - </dependency> - <dependency> - <groupId>org.nuiton</groupId> - <artifactId>nuiton-config</artifactId> - </dependency> - <dependency> - <groupId>org.nuiton.i18n</groupId> - <artifactId>nuiton-i18n</artifactId> - </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-collections4</artifactId> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpcore</artifactId> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpmime</artifactId> + </dependency> + <dependency> + <groupId>org.nuiton</groupId> + <artifactId>nuiton-config</artifactId> + </dependency> + <dependency> + <groupId>org.nuiton.i18n</groupId> + <artifactId>nuiton-i18n</artifactId> + </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> <!-- Unit test --> <dependency> diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/BlockchainBlock.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/BlockchainBlock.java index 2da348bd..74f7c52c 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/BlockchainBlock.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/BlockchainBlock.java @@ -24,6 +24,12 @@ package org.duniter.core.client.model.bma; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + import java.io.Serializable; import java.math.BigInteger; @@ -45,14 +51,17 @@ public class BlockchainBlock implements Serializable { private Long medianTime; private Integer membersCount; private BigInteger monetaryMass; - private Integer unitBase; + private Integer unitbase; + private Integer issuersCount; + private Integer issuersFrame; + private Integer issuersFrameVar; private String currency; private String issuer; private String hash; private String parameters; private String previousHash; private String previousIssuer; - private String inner_hash; + private String innerHash; private BigInteger dividend; private Identity[] identities; private Joiner[] joiners; @@ -66,7 +75,7 @@ public class BlockchainBlock implements Serializable { // raw": "Version: 1\nType: Block\nCurrency: zeta_brouzouf\nNonce: 8233\nNumber: 1\nDate: 1416589860\nConfirmedDate: 1416589860\nIssuer: HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk\nPreviousHash: 00006CD96A01378465318E48310118AC6B2F3625\nPreviousIssuer: HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk\nMembersCount: 4\nIdentities:\nJoiners:\nActives:\nLeavers:\nExcluded:\nCertifications:\nTransactions:\n" - //private String raw; + private String raw; public String getVersion() { return version; @@ -183,20 +192,22 @@ public class BlockchainBlock implements Serializable { this.joiners = joiners; } - public Integer getUnitBase() { - return unitBase; + public Integer getUnitbase() { + return unitbase; } - public void setUnitBase(Integer unitBase) { - this.unitBase = unitBase; + public void setUnitbase(Integer unitbase) { + this.unitbase = unitbase; } + @JsonGetter("inner_hash") public String getInnerHash() { - return inner_hash; + return innerHash; } + @JsonSetter("inner_hash") public void setInnerHash(String inner_hash) { - this.inner_hash = inner_hash; + this.innerHash = inner_hash; } public Joiner[] getLeavers() { @@ -247,10 +258,42 @@ public class BlockchainBlock implements Serializable { this.transactions = transactions; } + public Integer getIssuersCount() { + return issuersCount; + } + + public void setIssuersCount(Integer issuersCount) { + this.issuersCount = issuersCount; + } + + public Integer getIssuersFrame() { + return issuersFrame; + } + + public void setIssuersFrame(Integer issuersFrame) { + this.issuersFrame = issuersFrame; + } + + public Integer getIssuersFrameVar() { + return issuersFrameVar; + } + + public void setIssuersFrameVar(Integer issuersFrameVar) { + this.issuersFrameVar = issuersFrameVar; + } + + public String getRaw() { + return raw; + } + + public void setRaw(String raw) { + this.raw = raw; + } + public String toString() { String s = "version=" + version; s += "\nnonce=" + nonce; - s += "\ninner_hash=" + inner_hash; + s += "\ninnerHash=" + innerHash; s += "\nnumber=" + number; s += "\npowMin" + powMin; s += "\ntime=" + time; @@ -312,6 +355,7 @@ public class BlockchainBlock implements Serializable { return s; } + @JsonDeserialize public static class Identity implements Serializable { private static final long serialVersionUID = 8080689271400316984L; @@ -379,9 +423,9 @@ public class BlockchainBlock implements Serializable { private String userId; - private String mBlockUid; + private String membershipBlockUid; - private String iBlockUid; + private String idtyBlockUid; public String getPublicKey() { return publicKey; @@ -407,20 +451,20 @@ public class BlockchainBlock implements Serializable { this.userId = uid; } - public String getMBlockUid() { - return mBlockUid; + public String getMembershipBlockUid() { + return membershipBlockUid; } - public void setMBlockUid(String mBlockUid) { - this.mBlockUid = mBlockUid; + public void setMembershipBlockUid(String membershipBlockUid) { + this.membershipBlockUid = membershipBlockUid; } - public String getIBlockUid() { - return iBlockUid; + public String getIdtyBlockUid() { + return idtyBlockUid; } - public void setIBlockUid(String iBlockUid) { - this.iBlockUid = iBlockUid; + public void setIdtyBlockUid(String idtyBlockUid) { + this.idtyBlockUid = idtyBlockUid; } @Override @@ -429,8 +473,8 @@ public class BlockchainBlock implements Serializable { StringBuilder sb = new StringBuilder() .append(":").append(publicKey) .append(":").append(signature) - .append(":").append(mBlockUid) - .append(":").append(iBlockUid) + .append(":").append(membershipBlockUid) + .append(":").append(idtyBlockUid) .append(":").append(userId); return sb.toString(); @@ -468,7 +512,9 @@ public class BlockchainBlock implements Serializable { } } - public class Transaction implements Serializable { + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Transaction implements Serializable { private static final long serialVersionUID = 1L; private String[] signatures; @@ -485,6 +531,18 @@ public class BlockchainBlock implements Serializable { private String[] outputs; + private long time; + + private long locktime; + + private String blockstamp; + + private long blockstampTime; + + private String comment; + + private long blockNumber; + public String[] getSignatures() { return signatures; } @@ -541,6 +599,56 @@ public class BlockchainBlock implements Serializable { this.outputs = outputs; } + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } + + public long getBlockstampTime() { + return blockstampTime; + } + + public void setBlockstampTime(long blockstampTime) { + this.blockstampTime = blockstampTime; + } + + public long getLocktime() { + return locktime; + } + + public void setLocktime(long locktime) { + this.locktime = locktime; + } + + public String getBlockstamp() { + return blockstamp; + } + + public void setBlockstamp(String blockstamp) { + this.blockstamp = blockstamp; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + @JsonGetter("block_number") + public long getBlockNumber() { + return blockNumber; + } + + @JsonSetter("block_number") + public void setBlockNumber(long blockNumber) { + this.blockNumber = blockNumber; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Blocks.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Blocks.java new file mode 100644 index 00000000..25e4874f --- /dev/null +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Blocks.java @@ -0,0 +1,54 @@ +package org.duniter.core.client.model.bma; + +/* + * #%L + * UCoin Java Client :: Core API + * %% + * Copyright (C) 2014 - 2015 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + + +import java.io.Serializable; + +public class Blocks { + + private Result result; + + public Result getResult() { + return result; + } + + public void setResult(Result result) { + this.result = result; + } + + public static class Result implements Serializable { + + private static final long serialVersionUID = -39452685440482106L; + + private long[] blocks; + + public long[] getBlocks() { + return blocks; + } + + public void setBlocks(long[] blocks) { + this.blocks = blocks; + } + } +} diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeering.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeering.java index 3fc7e3e9..1d24f9c5 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeering.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeering.java @@ -22,6 +22,8 @@ package org.duniter.core.client.model.bma; * #L% */ +import com.fasterxml.jackson.annotation.JsonIgnore; + import java.io.Serializable; /** @@ -32,7 +34,9 @@ public class NetworkPeering implements Serializable { private String currency; private String block; private String signature; - // not need : private String raw + + private String raw; + private String pubkey; public Endpoint[] endpoints; @@ -77,6 +81,23 @@ public class NetworkPeering implements Serializable { this.pubkey = pubkey; } + @JsonIgnore + public String getRaw() { + return raw; + } + + public void setRaw(String raw) { + this.raw = raw; + } + + public Endpoint[] getEndpoints() { + return endpoints; + } + + public void setEndpoints(Endpoint[] endpoints) { + this.endpoints = endpoints; + } + public String toString() { String s = "version=" + version + "\n" + "currency=" + currency + "\n" + @@ -97,6 +118,46 @@ public class NetworkPeering implements Serializable { public String ipv6; public Integer port; + public EndpointProtocol getProtocol() { + return protocol; + } + + public void setProtocol(EndpointProtocol protocol) { + this.protocol = protocol; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getIpv4() { + return ipv4; + } + + public void setIpv4(String ipv4) { + this.ipv4 = ipv4; + } + + public String getIpv6() { + return ipv6; + } + + public void setIpv6(String ipv6) { + this.ipv6 = ipv6; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + @Override public String toString() { String s = "protocol=" + protocol.name() + "\n" + diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeers.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeers.java index 76f11af0..90cbf2cf 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeers.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeers.java @@ -22,6 +22,10 @@ package org.duniter.core.client.model.bma; * #L% */ +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonSetter; + import java.io.Serializable; /** @@ -46,8 +50,96 @@ public class NetworkPeers implements Serializable { public String block; public String signature; public String pubkey; + public Long firstDown; + public Long lastTry; + public String raw; public NetworkPeering.Endpoint[] endpoints; + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getBlock() { + return block; + } + + public void setBlock(String block) { + this.block = block; + } + + public String getSignature() { + return signature; + } + + public void setSignature(String signature) { + this.signature = signature; + } + + public String getPubkey() { + return pubkey; + } + + public void setPubkey(String pubkey) { + this.pubkey = pubkey; + } + + @JsonGetter("first_down") + public Long getFirstDown() { + return firstDown; + } + + @JsonSetter("first_down") + public void setFirstDown(Long firstDown) { + this.firstDown = firstDown; + } + + @JsonGetter("last_try") + public Long getLastTry() { + return lastTry; + } + + @JsonSetter("last_try") + public void setLastTry(Long lastTry) { + this.lastTry = lastTry; + } + + public NetworkPeering.Endpoint[] getEndpoints() { + return endpoints; + } + + public void setEndpoints(NetworkPeering.Endpoint[] endpoints) { + this.endpoints = endpoints; + } + + @JsonIgnore + public String getRaw() { + return raw; + } + + public void setRaw(String raw) { + this.raw = raw; + } + @Override public String toString() { String s = "version=" + version + "\n" + diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Protocol.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Protocol.java index 016b7edc..20653d2d 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Protocol.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Protocol.java @@ -29,6 +29,8 @@ public interface Protocol { String VERSION = "2"; + String TX_VERSION = "3"; + String TYPE_IDENTITY = "Identity"; String TYPE_MEMBERSHIP = "Membership"; diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/TxSource.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/TxSource.java index e1eaf7ef..36e4901a 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/TxSource.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/TxSource.java @@ -23,6 +23,8 @@ package org.duniter.core.client.model.bma; */ +import com.fasterxml.jackson.annotation.JsonCreator; + import java.io.Serializable; import java.util.List; @@ -34,6 +36,9 @@ public class TxSource { private Source[] sources; + public TxSource() { + } + public String getCurrency() { return currency; } @@ -58,7 +63,7 @@ public class TxSource { this.sources = sources; } - public class Source implements Serializable, Cloneable { + public static class Source implements Serializable, Cloneable { private static final long serialVersionUID = 8084087351543574142L; @@ -68,6 +73,8 @@ public class TxSource { private long amount; private int base; + public Source() { + } @Override public Object clone() throws CloneNotSupportedException { diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/WotCertification.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/WotCertification.java index ea0c259c..6ded2537 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/WotCertification.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/WotCertification.java @@ -23,6 +23,8 @@ package org.duniter.core.client.model.bma; */ +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonSetter; import org.duniter.core.client.model.local.Identity; import java.io.Serializable; @@ -41,6 +43,10 @@ public class WotCertification implements Serializable{ private String uid; + private String sigDate; + + private boolean isMember; + private Certification[] certifications; public Certification[] getCertifications() { @@ -67,23 +73,46 @@ public class WotCertification implements Serializable{ this.uid = uid; } - public class Certification extends Identity { + @JsonGetter("is_member") + public boolean isMember() { + return isMember; + } + + public void setIsMember(boolean isMember) { + this.isMember = isMember; + } + + public String getSigDate() { + return sigDate; + } + + public void setSigDate(String sigDate) { + this.sigDate = sigDate; + } + + public static class Certification extends Identity { private static final long serialVersionUID = 2204517069552693026L; - public CertTime cert_time; + private CertTime certTime; + + private String sigDate; /** * Indicate whether the certification is written in the blockchain or not. */ private Written written; - public CertTime getCert_time() { - return cert_time; + private boolean wasMember; + + @JsonGetter("cert_time") + public CertTime getCertTime() { + return certTime; } - public void setCert_time(CertTime cert_time) { - this.cert_time = cert_time; + @JsonSetter("cert_time") + public void setCertTime(CertTime certTime) { + this.certTime = certTime; } /** @@ -97,9 +126,25 @@ public class WotCertification implements Serializable{ this.written = written; } + public String getSigDate() { + return sigDate; + } + + public void setSigDate(String sigDate) { + this.sigDate = sigDate; + } + + @JsonGetter("wasMember") + public boolean wasMember() { + return wasMember; + } + + public void setWasMember(boolean wasMember) { + this.wasMember = wasMember; + } } - public class CertTime implements Serializable { + public static class CertTime implements Serializable { private static final long serialVersionUID = -358639516878884523L; @@ -125,7 +170,7 @@ public class WotCertification implements Serializable{ } - public class Written implements Serializable{ + public static class Written implements Serializable{ private long number = -1; diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/WotLookup.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/WotLookup.java index 8772e350..691a1b28 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/WotLookup.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/WotLookup.java @@ -23,12 +23,15 @@ package org.duniter.core.client.model.bma; */ +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonSetter; + import java.io.Serializable; public class WotLookup { - public boolean partial; - public Result[] results; + private boolean partial; + private Result[] results; public boolean isPartial() { return partial; @@ -63,9 +66,9 @@ public class WotLookup { private static final long serialVersionUID = -39452685440482106L; - public String pubkey; - public Uid[] uids; - public SignedSignature[] signed; + private String pubkey; + private Uid[] uids; + private SignedSignature[] signed; public String getPubkey() { return pubkey; @@ -92,14 +95,19 @@ public class WotLookup { } } - public class Uid { + public static class Uid { + + private String uid; + private Meta meta; + private String self; + private Boolean revoked; + private Long revokedOn; + private String revocationSig; + private OtherSignature[] others; - public String uid; - public Meta meta; - public String self; - public Boolean revoked; - public String revocation_sig; - public OtherSignature[] others; + public Uid(){ + + } public String getUid() { return uid; @@ -141,19 +149,32 @@ public class WotLookup { this.revoked = revoked; } + @JsonGetter("revocation_sig") public String getRevocationSig() { - return revocation_sig; + return revocationSig; } + @JsonSetter("revocation_sig") public void setRevocationSig(String revocationSig) { - this.revocation_sig = revocationSig; + this.revocationSig = revocationSig; + } + + @JsonGetter("revoked_on") + public Long getRevokedOn() { + return revokedOn; + } + + @JsonSetter("revoked_on") + public void setRevokedOn(Long revokedOn) { + this.revokedOn = revokedOn; } } - public class Meta implements Serializable { - public String timestamp; - public Long block_number; + public static class Meta implements Serializable { + private String timestamp; + private String blockHash; + private Long blockNumber; public String getTimestamp() { return timestamp; @@ -162,16 +183,36 @@ public class WotLookup { public void setTimestamp(String timestamp) { this.timestamp = timestamp; } + + @JsonGetter("block_hash") + public String getBlockHash() { + return blockHash; + } + + @JsonSetter("block_hash") + public void setBlockHash(String blockHash) { + this.blockHash = blockHash; + } + @JsonGetter("block_number") + public Long getBlockNumber() { + return blockNumber; + } + + @JsonSetter("block_number") + public void setBlockNumberH(Long blockNumber) { + this.blockNumber = blockNumber; + } + } - public class OtherSignature { + public static class OtherSignature { - public String pubkey; - public Meta meta; - public String signature; - public String[] uids; - public boolean isMember; - public boolean wasMember; + private String pubkey; + private Meta meta; + private String signature; + private String[] uids; + private boolean isMember; + private boolean wasMember; public String getPubkey() { return pubkey; @@ -205,15 +246,18 @@ public class WotLookup { this.uids = uids; } + @JsonGetter("isMember") public boolean isMember() { return isMember; } + @JsonSetter("isMember") public void setMember(boolean member) { isMember = member; } - public boolean isWasMember() { + @JsonGetter("wasMember") + public boolean wasMember() { return wasMember; } @@ -222,14 +266,15 @@ public class WotLookup { } } - public class SignedSignature { + public static class SignedSignature { - public String uid; - public String pubkey; - public Meta meta; - public String signature; - public boolean isMember; - public boolean wasMember; + private String uid; + private String pubkey; + private Meta meta; + private CertTime cerTime; + private String signature; + private boolean isMember; + private boolean wasMember; public String getUid() { return uid; @@ -263,21 +308,55 @@ public class WotLookup { this.signature = signature; } + @JsonGetter("isMember") public boolean isMember() { return isMember; } - public void setMember(boolean member) { - isMember = member; + public void setIsMember(boolean isMember) { + this.isMember = isMember; } - public boolean isWasMember() { + @JsonGetter("wasMember") + public boolean wasMember() { return wasMember; } public void setWasMember(boolean wasMember) { this.wasMember = wasMember; } + + @JsonGetter("cert_time") + public CertTime getCerTime() { + return cerTime; + } + + @JsonSetter("cert_time") + public void setCerTime(CertTime cerTime) { + this.cerTime = cerTime; + } } + public static class CertTime implements Serializable { + private Long block; + private String blockHash; + + public Long getBlock() { + return block; + } + + public void setBlock(Long block) { + this.block = block; + } + + @JsonGetter("block_hash") + public String getBlockHash() { + return blockHash; + } + + @JsonSetter("block_hash") + public void setBlockHash(String blockHash) { + this.blockHash = blockHash; + } + } } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/JoinerTypeAdapter.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/JoinerTypeAdapter.java index 33af16e3..cb574675 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/JoinerTypeAdapter.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/JoinerTypeAdapter.java @@ -48,8 +48,8 @@ public class JoinerTypeAdapter implements JsonDeserializer<BlockchainBlock.Joine result.setPublicKey(identityParts[i++]); result.setSignature(identityParts[i++]); - result.setMBlockUid(identityParts[i++]); - result.setIBlockUid(identityParts[i++]); + result.setMembershipBlockUid(identityParts[i++]); + result.setIdtyBlockUid(identityParts[i++]); result.setUserId(identityParts[i++]); return result; @@ -60,8 +60,8 @@ public class JoinerTypeAdapter implements JsonDeserializer<BlockchainBlock.Joine String result = new StringBuilder() .append(member.getPublicKey()).append(":") .append(member.getSignature()).append(":") - .append(member.getMBlockUid()).append(":") - .append(member.getIBlockUid()).append(":") + .append(member.getMembershipBlockUid()).append(":") + .append(member.getIdtyBlockUid()).append(":") .append(member.getUserId()).toString(); return context.serialize(result.toString(), String.class); diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/RevokedTypeAdapter.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/RevokedTypeAdapter.java index 4f49b139..1948ecd8 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/RevokedTypeAdapter.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/RevokedTypeAdapter.java @@ -39,7 +39,7 @@ public class RevokedTypeAdapter implements JsonDeserializer<BlockchainBlock.Revo } String[] identityParts = identityStr.split(":"); - if (identityParts.length != 4) { + if (identityParts.length != 2) { throw new JsonParseException(String.format("Bad format for BlockchainBlock.Revoked. Should have 4 parts, but found %s.", identityParts.length)); } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/EndpointDeserializer.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/EndpointDeserializer.java new file mode 100644 index 00000000..e67c1d2b --- /dev/null +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/EndpointDeserializer.java @@ -0,0 +1,51 @@ +package org.duniter.core.client.model.bma.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.google.gson.JsonParseException; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.conn.util.InetAddressUtils; +import org.duniter.core.client.model.bma.BlockchainBlock; +import org.duniter.core.client.model.bma.EndpointProtocol; +import org.duniter.core.client.model.bma.NetworkPeering; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Created by blavenie on 07/12/16. + */ +public class EndpointDeserializer extends JsonDeserializer<NetworkPeering.Endpoint> { + @Override + public NetworkPeering.Endpoint deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + + String ept = jp.getText(); + ArrayList<String> parts = new ArrayList<>(Arrays.asList(ept.split(" "))); + NetworkPeering.Endpoint endpoint = new NetworkPeering.Endpoint(); + endpoint.port = Integer.parseInt(parts.remove(parts.size() - 1)); + for (String word : parts) { + if (InetAddressUtils.isIPv4Address(word)) { + endpoint.ipv4 = word; + } else if (InetAddressUtils.isIPv6Address(word)) { + endpoint.ipv6 = word; + } else if (word.startsWith("http")) { + endpoint.url = word; + } else { + try { + endpoint.protocol = EndpointProtocol.valueOf(word); + } catch (IllegalArgumentException e) { + // skip this part + } + } + } + + if (endpoint.protocol == null) { + endpoint.protocol = EndpointProtocol.UNDEFINED; + } + + return endpoint; + } +} \ No newline at end of file diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/IdentityDeserializer.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/IdentityDeserializer.java new file mode 100644 index 00000000..49dd54b7 --- /dev/null +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/IdentityDeserializer.java @@ -0,0 +1,40 @@ +package org.duniter.core.client.model.bma.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.google.gson.JsonParseException; +import org.apache.commons.lang3.StringUtils; +import org.duniter.core.client.model.bma.BlockchainBlock; + +import java.io.IOException; + +/** + * Created by blavenie on 07/12/16. + */ +public class IdentityDeserializer extends JsonDeserializer<BlockchainBlock.Identity> { + @Override + public BlockchainBlock.Identity deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + + String identityStr = jp.getText(); + if (StringUtils.isBlank(identityStr)) { + return null; + } + + String[] identityParts = identityStr.split(":"); + if (identityParts.length != 4) { + throw new JsonParseException(String.format("Bad format for BlockchainBlock.Identity. Should have 4 parts, but found %s.", identityParts.length)); + } + + BlockchainBlock.Identity result = new BlockchainBlock.Identity(); + int i = 0; + + result.setPublicKey(identityParts[i++]); + result.setSignature(identityParts[i++]); + result.setBlockUid(identityParts[i++]); + result.setUserId(identityParts[i++]); + + return result; + } +} \ No newline at end of file diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/JacksonUtils.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/JacksonUtils.java new file mode 100644 index 00000000..dfc44541 --- /dev/null +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/JacksonUtils.java @@ -0,0 +1,27 @@ +package org.duniter.core.client.model.bma.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.duniter.core.client.model.bma.BlockchainBlock; +import org.duniter.core.client.model.bma.NetworkPeering; + +/** + * Created by blavenie on 07/12/16. + */ +public abstract class JacksonUtils extends SimpleModule { + + public static ObjectMapper newObjectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + + // Configure deserializer + SimpleModule module = new SimpleModule(); + module.addDeserializer(BlockchainBlock.Identity.class, new IdentityDeserializer()); + module.addDeserializer(BlockchainBlock.Joiner.class, new JoinerDeserializer()); + module.addDeserializer(BlockchainBlock.Revoked.class, new RevokedDeserializer()); + module.addDeserializer(NetworkPeering.Endpoint.class, new EndpointDeserializer()); + + objectMapper.registerModule(module); + + return objectMapper; + } +} diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/JoinerDeserializer.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/JoinerDeserializer.java new file mode 100644 index 00000000..2cfa2a1e --- /dev/null +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/JoinerDeserializer.java @@ -0,0 +1,40 @@ +package org.duniter.core.client.model.bma.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.google.gson.JsonParseException; +import org.apache.commons.lang3.StringUtils; +import org.duniter.core.client.model.bma.BlockchainBlock; + +import java.io.IOException; + +/** + * Created by blavenie on 07/12/16. + */ +public class JoinerDeserializer extends JsonDeserializer<BlockchainBlock.Joiner> { + @Override + public BlockchainBlock.Joiner deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + String identityStr = jp.getText(); + if (StringUtils.isBlank(identityStr)) { + return null; + } + + String[] identityParts = identityStr.split(":"); + if (identityParts.length != 5) { + throw new JsonParseException(String.format("Bad format for BlockchainBlock.Identity. Should have 5 parts, but found %s.", identityParts.length)); + } + + BlockchainBlock.Joiner result = new BlockchainBlock.Joiner(); + int i = 0; + + result.setPublicKey(identityParts[i++]); + result.setSignature(identityParts[i++]); + result.setMembershipBlockUid(identityParts[i++]); + result.setIdtyBlockUid(identityParts[i++]); + result.setUserId(identityParts[i++]); + + return result; + } +} \ No newline at end of file diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/RevokedDeserializer.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/RevokedDeserializer.java new file mode 100644 index 00000000..ab7661f5 --- /dev/null +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/RevokedDeserializer.java @@ -0,0 +1,36 @@ +package org.duniter.core.client.model.bma.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.google.gson.JsonParseException; +import org.apache.commons.lang3.StringUtils; +import org.duniter.core.client.model.bma.BlockchainBlock; + +import java.io.IOException; + +/** + * Created by blavenie on 07/12/16. + */ +public class RevokedDeserializer extends JsonDeserializer<BlockchainBlock.Revoked> { + @Override + public BlockchainBlock.Revoked deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + String identityStr = jp.getText(); + if (StringUtils.isBlank(identityStr)) { + return null; + } + + String[] identityParts = identityStr.split(":"); + if (identityParts.length != 2) { + throw new JsonParseException(String.format("Bad format for BlockchainBlock.Revoked. Should have 4 parts, but found %s.", identityParts.length)); + } + + BlockchainBlock.Revoked result = new BlockchainBlock.Revoked(); + int i = 0; + + result.setSignature(identityParts[i++]); + result.setUserId(identityParts[i++]); + + return result; + } +} \ No newline at end of file diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/Record.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/Record.java index 67d8cf86..65ad743c 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/Record.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/Record.java @@ -35,7 +35,7 @@ public class Record { private String issuer; private String hash; private String signature; - private Integer time; + private Long time; public Record() { } @@ -72,11 +72,11 @@ public class Record { } - public Integer getTime() { + public Long getTime() { return time; } - public void setTime(Integer time) { + public void setTime(Long time) { this.time = time; } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java index 07480993..54aedaa3 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java @@ -22,16 +22,8 @@ package org.duniter.core.client.service; * #L% */ +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Joiner; -import com.google.gson.Gson; -import org.apache.http.client.utils.URIBuilder; -import org.duniter.core.beans.InitializingBean; -import org.duniter.core.client.config.Configuration; -import org.duniter.core.client.model.bma.Error; -import org.duniter.core.client.model.bma.gson.GsonUtils; -import org.duniter.core.client.model.local.Peer; -import org.duniter.core.client.service.exception.*; -import org.duniter.core.exception.TechnicalException; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; @@ -39,8 +31,19 @@ import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import org.duniter.core.beans.InitializingBean; +import org.duniter.core.client.config.Configuration; +import org.duniter.core.client.model.bma.Error; +import org.duniter.core.client.model.bma.jackson.JacksonUtils; +import org.duniter.core.client.model.local.Peer; +import org.duniter.core.client.service.exception.HttpBadRequestException; +import org.duniter.core.client.service.exception.HttpConnectException; +import org.duniter.core.client.service.exception.HttpNotFoundException; +import org.duniter.core.client.service.exception.PeerConnectionException; +import org.duniter.core.exception.TechnicalException; import org.duniter.core.util.StringUtils; import org.nuiton.i18n.I18n; import org.slf4j.Logger; @@ -62,7 +65,7 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean public static final String URL_PEER_ALIVE = "/blockchain/parameters"; protected Integer baseTimeOut; - protected Gson gson; + protected ObjectMapper objectMapper; protected HttpClient httpClient; protected Peer defaultPeer; private boolean debug; @@ -75,7 +78,7 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean @Override public void afterPropertiesSet() throws Exception { Configuration config = Configuration.instance(); - this.gson = GsonUtils.newBuilder().create(); + this.objectMapper = JacksonUtils.newObjectMapper(); this.baseTimeOut = config.getNetworkTimeout(); this.httpClient = createHttpClient(); } @@ -294,22 +297,7 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean // deserialize using gson else { try { - Reader reader = new InputStreamReader(content, StandardCharsets.UTF_8); - if (ResultClass != null) { - result = gson.fromJson(reader, ResultClass); - } - else { - result = null; - } - } - catch (com.google.gson.JsonSyntaxException e) { - if (content != null) { - log.warn("Error while parsing JSON response: " + getContentAsString(content), e); - } - else { - log.warn("Error while parsing JSON response", e); - } - throw new JsonSyntaxException(I18n.t("duniter4j.client.core.invalidResponse"), e); + result = objectMapper.readValue(content, ResultClass); } catch (Exception e) { throw new TechnicalException(I18n.t("duniter4j.client.core.invalidResponse"), e); diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteService.java index 8516a025..d3ccd755 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteService.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteService.java @@ -91,7 +91,15 @@ public interface BlockchainRemoteService extends Service { * @param number the block number * @return */ - BlockchainBlock getBlock(Peer peer, int number) throws BlockNotFoundException; + BlockchainBlock getBlock(Peer peer, long number) throws BlockNotFoundException; + + /** + * Get block with TX + * + * @param peer the peer to use for request + * @return + */ + long[] getBlocksWithTx(Peer peer); /** * Retrieve a block, by id (from 0 to current) as JSON string @@ -100,7 +108,7 @@ public interface BlockchainRemoteService extends Service { * @param number the block number * @return */ - String getBlockAsJson(Peer peer, int number) throws BlockNotFoundException; + String getBlockAsJson(Peer peer, long number) throws BlockNotFoundException; /** * Retrieve a block, by id (from 0 to current) as JSON string diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceImpl.java index f277e5c7..966c112e 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceImpl.java @@ -23,10 +23,7 @@ package org.duniter.core.client.service.bma; */ import org.duniter.core.client.config.Configuration; -import org.duniter.core.client.model.bma.BlockchainBlock; -import org.duniter.core.client.model.bma.BlockchainMemberships; -import org.duniter.core.client.model.bma.BlockchainParameters; -import org.duniter.core.client.model.bma.Protocol; +import org.duniter.core.client.model.bma.*; import org.duniter.core.client.model.bma.gson.JsonArrayParser; import org.duniter.core.client.model.local.Identity; import org.duniter.core.client.model.local.Peer; @@ -66,10 +63,13 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement public static final String URL_BLOCK = URL_BASE + "/block/%s"; + public static final String URL_BLOCKS_FROM = URL_BASE + "/blocks/%s/%s"; public static final String URL_BLOCK_CURRENT = URL_BASE + "/current"; + public static final String URL_BLOCK_WITH_TX = URL_BASE + "/with/tx"; + public static final String URL_BLOCK_WITH_UD = URL_BASE + "/with/ud"; public static final String URL_MEMBERSHIP = URL_BASE + "/membership"; @@ -164,7 +164,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement @Override - public BlockchainBlock getBlock(Peer peer, int number) throws BlockNotFoundException { + public BlockchainBlock getBlock(Peer peer, long number) throws BlockNotFoundException { // Get block from number String path = String.format(URL_BLOCK, number); try { @@ -176,7 +176,19 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement } @Override - public String getBlockAsJson(Peer peer, int number) { + public long[] getBlocksWithTx(Peer peer) { + try { + Blocks blocks = executeRequest(peer, URL_BLOCK_WITH_TX, Blocks.class); + return (blocks == null || blocks.getResult() == null) ? new long[0] : blocks.getResult().getBlocks(); + } + catch(HttpNotFoundException e) { + throw new TechnicalException(String.format("Error while getting blocks with TX on peer [%s]", peer)); + } + } + + + @Override + public String getBlockAsJson(Peer peer, long number) { // get blockchain parameter String path = String.format(URL_BLOCK, number); try { diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteServiceImpl.java index 0548f391..5125270f 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteServiceImpl.java @@ -23,7 +23,9 @@ package org.duniter.core.client.service.bma; */ +import com.google.common.base.Joiner; import org.duniter.core.client.model.TxOutput; +import org.duniter.core.client.model.bma.BlockchainBlock; import org.duniter.core.client.model.bma.Protocol; import org.duniter.core.client.model.bma.TxHistory; import org.duniter.core.client.model.bma.TxSource; @@ -76,13 +78,16 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen public String transfer(Wallet wallet, String destPubKey, long amount, String comment) throws InsufficientCreditException { - + + // Get current block + BlockchainBlock currentBlock = executeRequest(wallet.getCurrencyId(), BlockchainRemoteServiceImpl.URL_BLOCK_CURRENT, BlockchainBlock.class); + // http post /tx/process HttpPost httpPost = new HttpPost( getPath(wallet.getCurrencyId(), URL_TX_PROCESS)); // compute transaction - String transaction = getSignedTransaction(wallet, destPubKey, 0, amount, + String transaction = getSignedTransaction(wallet, currentBlock, destPubKey, 0, amount, comment); if (log.isDebugEnabled()) { @@ -212,6 +217,7 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen /* -- internal methods -- */ public String getSignedTransaction(Wallet wallet, + BlockchainBlock block, String destPubKey, int locktime, long amount, @@ -229,16 +235,18 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen TxSource.Source[] sources = sourceResults.getSources(); if (CollectionUtils.isEmpty(sources)) { throw new InsufficientCreditException( - "Insufficient credit : no credit found."); + "Insufficient credit: no credit found."); } - List<TxSource.Source> txInputs = new ArrayList<TxSource.Source>(); - List<TxOutput> txOutputs = new ArrayList<TxOutput>(); - computeTransactionInputsAndOuputs(wallet.getPubKeyHash(), destPubKey, + List<TxSource.Source> txInputs = new ArrayList<>(); + List<TxOutput> txOutputs = new ArrayList<>(); + computeTransactionInputsAndOuputs(block.getUnitbase(), + wallet.getPubKeyHash(), destPubKey, sources, amount, txInputs, txOutputs); String transaction = getTransaction(wallet.getCurrency(), - wallet.getPubKeyHash(), destPubKey, locktime, txInputs, txOutputs, + block.getNumber(), block.getHash(), + wallet.getPubKeyHash(), locktime, txInputs, txOutputs, comment); String signature = cryptoService.sign(transaction, wallet.getSecKey()); @@ -248,16 +256,18 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen } public String getTransaction(String currency, + long blockNumber, + String blockHash, String srcPubKey, - String destPubKey, int locktime, List<TxSource.Source> inputs, List<TxOutput> outputs, String comments) { StringBuilder sb = new StringBuilder(); - sb.append("Version: ").append(Protocol.VERSION).append("\n") + sb.append("Version: ").append(Protocol.TX_VERSION).append("\n") .append("Type: ").append(Protocol.TYPE_TRANSACTION).append("\n") .append("Currency: ").append(currency).append('\n') + .append("Blockstamp: ").append(blockNumber).append('-').append(blockHash).append("\n") .append("Locktime: ").append(locktime).append('\n') .append("Issuers:\n") // add issuer pubkey @@ -265,12 +275,18 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen // Inputs coins sb.append("Inputs:\n"); + Joiner joiner = Joiner.on(':'); for (TxSource.Source input : inputs) { - // if D : D:PUBLIC_KEY:BLOCK_ID - // if T : T:T_HASH:T_INDEX - sb.append(input.getType()).append(':') - .append(input.getIdentifier()).append(':') - .append(input.getNoffset()).append('\n'); + // if D : AMOUNT:BASE:D:PUBLIC_KEY:BLOCK_ID + // if T : AMOUNT:BASE:T:T_HASH:T_INDEX + joiner.appendTo(sb, new String[]{ + String.valueOf(input.getAmount()), + String.valueOf(input.getBase()), + input.getType(), + input.getIdentifier(), + input.getNoffset() + }); + sb.append('\n'); } // Unlocks @@ -296,15 +312,15 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen return sb.toString(); } - public String getCompactTransaction(String currency, String srcPubKey, - String destPubKey, List<TxSource.Source> inputs, List<TxOutput> outputs, + public String getCompactTransaction(String srcPubKey, + List<TxSource.Source> inputs, List<TxOutput> outputs, String comments) { boolean hasComment = comments != null && comments.length() > 0; StringBuilder sb = new StringBuilder(); sb.append("TX:") // VERSION - .append(Protocol.VERSION).append(':') + .append(Protocol.TX_VERSION).append(':') // NB_ISSUERS .append("1:") // NB_INPUTS @@ -339,42 +355,144 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen return sb.toString(); } - public void computeTransactionInputsAndOuputs(String srcPubKey, + public void computeTransactionInputsAndOuputs(int currentUnitBase, + String srcPubKey, String destPubKey, TxSource.Source[] sources, long amount, - List<TxSource.Source> inputs, List<TxOutput> outputs) throws InsufficientCreditException{ + List<TxSource.Source> resultInputs, List<TxOutput> resultOutputs) throws InsufficientCreditException{ + + TxInputs inputs = new TxInputs(); + inputs.amount = 0; + inputs.minBase = currentUnitBase; + inputs.maxBase = currentUnitBase + 1; + + // Get inputs, starting to use current base sources + int amountBase = 0; + while (inputs.amount < amount && amountBase <= currentUnitBase) { + inputs = getInputs(sources, amount, currentUnitBase, currentUnitBase); + + if (inputs.amount < amount) { + // try to reduce amount (replace last digits to zero) + amountBase++; + if (amountBase <= currentUnitBase) { + amount = truncBase(amount, amountBase); + } + } + } + + if (inputs.amount < amount) { + throw new InsufficientCreditException("Insufficient credit"); + } + + // Avoid to get outputs on lower base + if (amountBase < inputs.minBase && !isBase(amount, inputs.minBase)) { + amount = truncBase(amount, inputs.minBase); + log.debug("TX Amount has been truncate to " + amount); + } + else if (amountBase > 0) { + log.debug("TX Amount has been truncate to " + amount); + } + resultInputs.addAll(inputs.sources); long rest = amount; - long restForHimSelf = 0; - - for (TxSource.Source source : sources) { - long srcAmount = source.getAmount(); - inputs.add(source); - if (srcAmount >= rest) { - restForHimSelf = srcAmount - rest; - rest = 0; - break; + int outputBase = inputs.maxBase; + long outputAmount; + while(rest > 0) { + outputAmount = truncBase(rest, outputBase); + rest -= outputAmount; + if (outputAmount > 0) { + outputAmount = inversePowBase(outputAmount, outputBase); + TxOutput output = new TxOutput(); + output.setAmount(outputAmount); + output.setBase(outputBase); + output.setPubKey(destPubKey); + resultOutputs.add(output); } - rest -= srcAmount; + outputBase--; } - - if (rest > 0) { - throw new InsufficientCreditException(String.format( - "Insufficient credit. Need %s more units.", rest)); + rest = inputs.amount - amount; + outputBase = inputs.maxBase; + while(rest > 0) { + outputAmount = truncBase(rest, outputBase); + rest -= outputAmount; + if (outputAmount > 0) { + outputAmount = inversePowBase(outputAmount, outputBase); + TxOutput output = new TxOutput(); + output.setAmount(outputAmount); + output.setBase(outputBase); + output.setPubKey(srcPubKey); + resultOutputs.add(output); + } + outputBase--; } + } + + private long truncBase(long amount, int base) { + long pow = (long)Math.pow(10, base); + if (amount < pow) return pow; + return (long)(Math.floor(amount / pow ) * pow); + } - // outputs - { - TxOutput output = new TxOutput(); - output.setPubKey(destPubKey); - output.setAmount(amount); - outputs.add(output); + private long powBase(long amount, int base) { + if (base <= 0) return amount; + return (long) (amount * Math.pow(10, base)); + } + + private long inversePowBase(long amount, int base) { + if (base <= 0) return amount; + return (long) (amount / Math.pow(10, base)); + } + + private boolean isBase(long amount, int base) { + if (base <= 0) return true; + if (amount < Math.pow(10, base)) return false; + String rest = "00000000" + amount; + long lastDigits = Integer.parseInt(rest.substring(rest.length()-base)); + return lastDigits == 0; // no rest + } + + private TxInputs getInputs(TxSource.Source[] availableSources, long amount, int outputBase, int filterBase) { + if (filterBase < 0) { + filterBase = outputBase; } - if (restForHimSelf > 0) { - TxOutput output = new TxOutput(); - output.setPubKey(srcPubKey); - output.setAmount(restForHimSelf); - outputs.add(output); + long sourcesAmount = 0; + TxInputs result = new TxInputs(); + result.minBase = filterBase; + result.maxBase = filterBase; + for (TxSource.Source source: availableSources) { + if (source.getBase() == filterBase){ + sourcesAmount += powBase(source.getAmount(), source.getBase()); + result.sources.add(source); + // Stop if enough sources + if (sourcesAmount >= amount) { + break; + } + } } + + // IF not enough sources, get add inputs from lower base (recursively) + if (sourcesAmount < amount && filterBase > 0) { + filterBase -= 1; + long missingAmount = amount - sourcesAmount; + TxInputs lowerInputs = getInputs(availableSources, missingAmount, outputBase, filterBase); + + // Add lower base inputs to result + if (lowerInputs.amount > 0) { + result.minBase = lowerInputs.minBase; + sourcesAmount += lowerInputs.amount; + result.sources.addAll(lowerInputs.sources); + } + } + + result.amount = sourcesAmount; + + return result; + } + + private class TxInputs { + long amount; + int minBase; + int maxBase; + List<TxSource.Source> sources = new ArrayList<>(); } } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteServiceImpl.java index 2f762ca7..97829efe 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteServiceImpl.java @@ -422,8 +422,8 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe target.setUid(source.getUid()); target.setSelf(source.getSelf()); - String timestamp = source.getMeta() != null ? source.getMeta().timestamp : null; - //FIXME target.setTimestamp(timestamp); + String timestamp = source.getMeta() != null ? source.getMeta().getTimestamp() : null; + target.setTimestamp(timestamp); } public String getSignedIdentity(String currency, byte[] pubKey, byte[] secKey, String uid, String blockUid) { @@ -552,8 +552,8 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe // Read certified-by if (CollectionUtils.isNotEmpty(lookupResults.getResults())) { for (WotLookup.Result lookupResult: lookupResults.getResults()) { - if (lookupResult.signed != null) { - for(WotLookup.SignedSignature lookupSignature : lookupResult.signed) { + if (lookupResult.getSigned() != null) { + for(WotLookup.SignedSignature lookupSignature : lookupResult.getSigned()) { Certification certifiedBy = toCertifiedByCerticication(lookupSignature); // Set the currency Id @@ -778,25 +778,25 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe Certification target = new Certification(); // uid - target.setUid(source.uid); + target.setUid(source.getUid()); // certifieb by target.setCertifiedBy(true); - if (source.meta != null) { + if (source.getMeta() != null) { // timestamp - String timestamp = source.meta != null ? source.meta.timestamp : null; + String timestamp = source.getMeta() != null ? source.getMeta().getTimestamp() : null; if (timestamp != null) { //FIXME target.setTimestamp(timestamp.longValue()); } } // Pubkey - target.setPubkey(source.pubkey); + target.setPubkey(source.getPubkey()); // Is member - target.setMember(source.isMember); + target.setMember(source.isMember()); // add to result list return target; diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/elasticsearch/CurrencyRegistryRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/elasticsearch/CurrencyRegistryRemoteServiceImpl.java index f9ce3c7e..a34d8bea 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/elasticsearch/CurrencyRegistryRemoteServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/elasticsearch/CurrencyRegistryRemoteServiceImpl.java @@ -96,8 +96,7 @@ public class CurrencyRegistryRemoteServiceImpl extends BaseRemoteServiceImpl imp // get currency String jsonResponse; try { - String path = getPath(peer, URL_STATUS); - jsonResponse = executeRequest(peer, path, String.class); + jsonResponse = executeRequest(peer, URL_STATUS, String.class); int statusCode = GsonUtils.getValueFromJSONAsInt(jsonResponse, "status"); return statusCode == HttpStatus.SC_OK; } diff --git a/duniter4j-core-client/src/main/resources/i18n/duniter4j-core-client_fr_FR.properties b/duniter4j-core-client/src/main/resources/i18n/duniter4j-core-client_fr_FR.properties index 94c99f6e..8e18b6c8 100644 --- a/duniter4j-core-client/src/main/resources/i18n/duniter4j-core-client_fr_FR.properties +++ b/duniter4j-core-client/src/main/resources/i18n/duniter4j-core-client_fr_FR.properties @@ -1,8 +1,8 @@ duniter4j.client.authentication=Echec de la requete (Accès interdit ou non autorisé). duniter4j.client.core.connect=Echec de la connection au noeud Duniter [%s] -duniter4j.client.core.emptyResponse= -duniter4j.client.core.invalidResponse= -duniter4j.client.core.timeout= +duniter4j.client.core.emptyResponse=Réponse HTTP vide +duniter4j.client.core.invalidResponse=Réponse HTTP non valide +duniter4j.client.core.timeout=Délai d'attente de la requête dépassé duniter4j.client.notFound=Ressource non trouvée [%s] duniter4j.client.status=Echec de requete HTTP [%s] \: %s duniter4j.config= diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/HttpServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/HttpServiceTest.java new file mode 100644 index 00000000..0f96bc8b --- /dev/null +++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/HttpServiceTest.java @@ -0,0 +1,73 @@ +package org.duniter.core.client.service; + +/* + * #%L + * UCoin Java Client :: Core API + * %% + * Copyright (C) 2014 - 2015 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + + +import org.duniter.core.client.TestResource; +import org.duniter.core.client.config.Configuration; +import org.duniter.core.client.model.bma.EndpointProtocol; +import org.duniter.core.client.model.bma.NetworkPeering; +import org.duniter.core.client.model.local.Peer; +import org.duniter.core.client.service.bma.NetworkRemoteService; +import org.junit.Assert; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class HttpServiceTest { + + private static final Logger log = LoggerFactory.getLogger(HttpServiceTest.class); + + @ClassRule + public static final TestResource resource = TestResource.create(); + + private HttpService service; + private Peer peer; + + @Before + public void setUp() { + service = ServiceLocator.instance().getHttpService(); + peer = createTestPeer(); + } + + @Test + public void connect() throws Exception { + + service.connect(peer); + } + + + /* -- internal methods */ + + protected Peer createTestPeer() { + Peer peer = new Peer( + Configuration.instance().getNodeHost(), + Configuration.instance().getNodePort()); + + return peer; + } +} diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceTest.java index 6cc37273..67ac086e 100644 --- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceTest.java +++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceTest.java @@ -86,6 +86,25 @@ public class BlockchainRemoteServiceTest { } } + @Test + public void getBlockWithTx() throws Exception { + + long[] blocks = service.getBlocksWithTx(createTestPeer()); + if (blocks == null) return; + + // Check first block with TX + BlockchainBlock result = service.getBlock(createTestPeer(), blocks[0]); + Assert.assertNotNull(result); + Assert.assertNotNull(result.getTransactions()); + Assert.assertTrue(result.getTransactions().length > 0); + + // Check last block with TX + result = service.getBlock(createTestPeer(), blocks[blocks.length-1]); + Assert.assertNotNull(result); + Assert.assertNotNull(result.getTransactions()); + Assert.assertTrue(result.getTransactions().length > 0); + } + @Test public void getBlocksAsJson() throws Exception { @@ -115,13 +134,10 @@ public class BlockchainRemoteServiceTest { isWebSocketNewBlockReceived = false; - service.addNewBlockListener(createTestPeer(), new WebsocketClientEndpoint.MessageHandler() { - @Override - public void handleMessage(String message) { - BlockchainBlock block = GsonUtils.newBuilder().create().fromJson(message, BlockchainBlock.class); - log.debug("Received block #" + block.getNumber()); - isWebSocketNewBlockReceived = true; - } + service.addNewBlockListener(createTestPeer(), (message) -> { + BlockchainBlock block = GsonUtils.newBuilder().create().fromJson(message, BlockchainBlock.class); + log.debug("Received block #" + block.getNumber()); + isWebSocketNewBlockReceived = true; }); int count = 0; diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/TransactionRemoteServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/TransactionRemoteServiceTest.java index 8f0f5fcb..8d8950c6 100644 --- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/TransactionRemoteServiceTest.java +++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/TransactionRemoteServiceTest.java @@ -49,8 +49,6 @@ public class TransactionRemoteServiceTest { } @Test - @Ignore - //FIXME : get Wrong unit base for outputs - see implementation on Cesium public void transfer() throws Exception { service.transfer( diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/WotRemoteServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/WotRemoteServiceTest.java index db373205..5cbbd36e 100644 --- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/WotRemoteServiceTest.java +++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/WotRemoteServiceTest.java @@ -97,7 +97,7 @@ public class WotRemoteServiceTest { for (WotCertification.Certification cert : result.getCertifications()) { Assert.assertNotNull(cert.getUid()); - WotCertification.CertTime certTime = cert.getCert_time(); + WotCertification.CertTime certTime = cert.getCertTime(); Assert.assertNotNull(certTime); Assert.assertTrue(certTime.getBlock() >= 0); Assert.assertNotNull(certTime.getMedianTime() >= 0); @@ -124,7 +124,7 @@ public class WotRemoteServiceTest { for (WotCertification.Certification cert : result.getCertifications()) { Assert.assertNotNull(cert.getUid()); - WotCertification.CertTime certTime = cert.getCert_time(); + WotCertification.CertTime certTime = cert.getCertTime(); Assert.assertNotNull(certTime); Assert.assertTrue(certTime.getBlock() >= 0); Assert.assertNotNull(certTime.getMedianTime() >= 0); diff --git a/duniter4j-core-client/src/test/resources/duniter4j-core-client-test.properties b/duniter4j-core-client/src/test/resources/duniter4j-core-client-test.properties index 4eae98cf..a420819a 100644 --- a/duniter4j-core-client/src/test/resources/duniter4j-core-client-test.properties +++ b/duniter4j-core-client/src/test/resources/duniter4j-core-client-test.properties @@ -1,11 +1,11 @@ #duniter4j.node.host=metab.ucoin.io #duniter4j.node.host=metab.ucoin.fr -#duniter4j.node.host=192.168.0.28 -#duniter4j.node.port=9202 +duniter4j.node.host=test-net.duniter.fr +duniter4j.node.port=9201 -duniter4j.node.host=cgeek.fr -duniter4j.node.port=9330 +#duniter4j.node.host=cgeek.fr +#duniter4j.node.port=9330 duniter4j.node.elasticsearch.host=localhost diff --git a/duniter4j-core-client/src/test/resources/log4j.properties b/duniter4j-core-client/src/test/resources/log4j.properties index ba8baa96..a3e37387 100644 --- a/duniter4j-core-client/src/test/resources/log4j.properties +++ b/duniter4j-core-client/src/test/resources/log4j.properties @@ -9,7 +9,7 @@ log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %5p (%c:%L) - %m%n # ucoin levels log4j.logger.org.duniter=INFO -#log4j.logger.org.duniter.core.client.service=DEBUG +log4j.logger.org.duniter.core.client.service=DEBUG log4j.logger.org.duniter.core.client.service.bma=DEBUG log4j.logger.org.duniter.core.beans=WARN diff --git a/duniter4j-core-shared/LICENSE b/duniter4j-core-shared/LICENSE index 94a9ed02..6df63bc4 100644 --- a/duniter4j-core-shared/LICENSE +++ b/duniter4j-core-shared/LICENSE @@ -552,7 +552,7 @@ License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed +permission to reference or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, diff --git a/duniter4j-es-core/LICENSE b/duniter4j-es-core/LICENSE index 94a9ed02..6df63bc4 100644 --- a/duniter4j-es-core/LICENSE +++ b/duniter4j-es-core/LICENSE @@ -552,7 +552,7 @@ License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed +permission to reference or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java index fe53f1c7..1931d541 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java @@ -285,6 +285,10 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> { return settings.getAsBoolean("duniter.ws.enable", Boolean.TRUE); } + public String[] getWebSocketChangesListenSource() { + return settings.getAsArray("duniter.ws.changes.listenSource", new String[]{"*"}); + } + /* protected methods */ protected void initI18n() throws IOException { diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java index 0cd8989c..1ab515e3 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java @@ -167,7 +167,7 @@ public class CurrencyService extends AbstractService { */ public Currency indexCurrencyFromPeer(Peer peer) { BlockchainParameters parameters = blockchainRemoteService.getParameters(peer); - BlockchainBlock firstBlock = blockchainRemoteService.getBlock(peer, 0); + BlockchainBlock firstBlock = blockchainRemoteService.getBlock(peer, 0l); BlockchainBlock currentBlock = blockchainRemoteService.getCurrentBlock(peer); long lastUD = blockchainRemoteService.getLastUD(peer); diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeEvent.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeEvent.java index 99d6fd7a..2b26e303 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeEvent.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeEvent.java @@ -38,9 +38,20 @@ package org.duniter.elasticsearch.service.changes; limitations under the License. */ +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.JsonSyntaxException; +import org.duniter.core.exception.TechnicalException; +import org.duniter.elasticsearch.exception.InvalidFormatException; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.json.JsonXContent; import org.joda.time.DateTime; +import java.io.IOException; + public class ChangeEvent { private final String id; private final String index; @@ -93,4 +104,51 @@ public class ChangeEvent { } + public String toJson() { + try { + XContentBuilder builder = new XContentBuilder(JsonXContent.jsonXContent, new BytesStreamOutput()); + builder.startObject() + .field("_index", getIndex()) + .field("_type", getType()) + .field("_id", getId()) + .field("_timestamp", getTimestamp()) + .field("_version", getVersion()) + .field("_operation", getOperation().toString()); + if (getSource() != null) { + builder.rawField("_source", getSource()); + } + builder.endObject(); + + return builder.string(); + } catch (IOException e) { + throw new TechnicalException("Error while generating JSON from change event", e); + } + } + + public static ChangeEvent fromJson(String json) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode actualObj = objectMapper.readTree(json); + String index = actualObj.get("_index").asText(); + String type = actualObj.get("_type").asText(); + String id = actualObj.get("_id").asText(); + DateTime timestamp = new DateTime(actualObj.get("_timestamp").asLong()); + ChangeEvent.Operation operation = ChangeEvent.Operation.valueOf(actualObj.get("_operation").asText()); + long version = actualObj.get("_version").asLong(); + + JsonNode sourceNode = actualObj.get("_source"); + BytesReference source = null; + if (sourceNode != null) { + // TODO : fill bytes reference from source + //source = sourceNode. + } + + ChangeEvent event = new ChangeEvent(index, type, id, timestamp, operation, version, source); + return event; + } catch (IOException | JsonSyntaxException e) { + throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e); + } + } + + } diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeListener.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeListener.java deleted file mode 100644 index af8ba091..00000000 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.duniter.elasticsearch.service.changes; - -public interface ChangeListener { - String getId(); - void onChanges(String message); -} \ No newline at end of file diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeService.java index 56487c38..7b5d3bc4 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeService.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeService.java @@ -38,13 +38,11 @@ package org.duniter.elasticsearch.service.changes; limitations under the License. */ +import org.duniter.core.util.CollectionUtils; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.indexing.IndexingOperationListener; import org.elasticsearch.index.shard.IndexShard; @@ -52,14 +50,16 @@ import org.elasticsearch.indices.IndicesLifecycle; import org.elasticsearch.indices.IndicesService; import org.joda.time.DateTime; -import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; public class ChangeService { + public interface ChangeListener { + String getId(); + void onChange(ChangeEvent change); + Collection<ChangeSource> getChangeSources(); + } + private static final String SETTING_PRIMARY_SHARD_ONLY = "duniter.changes.primaryShardOnly"; private static final String SETTING_LISTEN_SOURCE = "duniter.changes.listenSource"; @@ -67,14 +67,13 @@ public class ChangeService { private static final Map<String, ChangeListener> LISTENERS = new HashMap<>(); + private static Map<String, ChangeSource> LISTENERS_SOURCES = new HashMap<>(); + private static Map<String, Integer> LISTENERS_SOURCES_USAGE_COUNT = new HashMap<>(); + @Inject public ChangeService(final Settings settings, IndicesService indicesService) { final boolean allShards = !settings.getAsBoolean(SETTING_PRIMARY_SHARD_ONLY, Boolean.FALSE); - final String[] sourcesStr = settings.getAsArray(SETTING_LISTEN_SOURCE, new String[]{"*"}); - final Set<ChangeSource> sources = new HashSet<>(); - for(String sourceStr : sourcesStr) { - sources.add(new ChangeSource(sourceStr)); - } + indicesService.indicesLifecycle().addListener(new IndicesLifecycle.Listener() { @Override @@ -85,6 +84,10 @@ public class ChangeService { indexShard.indexingService().addListener(new IndexingOperationListener() { @Override public void postCreate(Engine.Create create) { + if (!hasListener(indexName, create.type(), create.id())) { + return; + } + ChangeEvent change=new ChangeEvent( indexName, create.type(), @@ -100,6 +103,10 @@ public class ChangeService { @Override public void postDelete(Engine.Delete delete) { + if (!hasListener(indexName, delete.type(), delete.id())) { + return; + } + ChangeEvent change=new ChangeEvent( indexName, delete.type(), @@ -115,6 +122,9 @@ public class ChangeService { @Override public void postIndex(Engine.Index index) { + if (!hasListener(indexName, index.type(), index.id())) { + return; + } ChangeEvent change=new ChangeEvent( indexName, @@ -129,25 +139,24 @@ public class ChangeService { addChange(change); } - private boolean filter(String index, String type, String id, ChangeSource source) { - if (source.getIndices() != null && !source.getIndices().contains(index)) { - return false; - } - - if (source.getTypes() != null && !source.getTypes().contains(type)) { - return false; - } + private boolean hasListener(String index, String type, String id) { + if (LISTENERS_SOURCES.isEmpty()) return false; - if (source.getIds() != null && !source.getIds().contains(id)) { - return false; + for (ChangeSource source : LISTENERS_SOURCES.values()) { + if (source.apply(index, type, id)) { + return true; + } } - return true; + return false; } - private boolean filter(String index, ChangeEvent change) { + private boolean apply(ChangeListener listener, ChangeEvent change) { + Collection<ChangeSource> sources = listener.getChangeSources(); + if (CollectionUtils.isEmpty(sources)) return true; + for (ChangeSource source : sources) { - if (filter(index, change.getType(), change.getId(), source)) { + if (source.apply(change.getIndex(), change.getType(), change.getId())) { return true; } } @@ -156,40 +165,14 @@ public class ChangeService { } private void addChange(ChangeEvent change) { - - if (!filter(indexName, change)) { - return; - } - - - String message; - try { - XContentBuilder builder = new XContentBuilder(JsonXContent.jsonXContent, new BytesStreamOutput()); - builder.startObject() - .field("_index", indexName) - .field("_type", change.getType()) - .field("_id", change.getId()) - .field("_timestamp", change.getTimestamp()) - .field("_version", change.getVersion()) - .field("_operation", change.getOperation().toString()); - if (change.getSource() != null) { - builder.rawField("_source", change.getSource()); - } - builder.endObject(); - - message = builder.string(); - } catch (IOException e) { - log.error("Failed to write JSON", e); - return; - } - for (ChangeListener listener : LISTENERS.values()) { - try { - listener.onChanges(message); - } catch (Exception e) { - log.error("Failed to send message", e); + if (apply(listener, change)) { + try { + listener.onChange(change); + } catch (Exception e) { + log.error("Failed to send message", e); + } } - } } @@ -202,10 +185,41 @@ public class ChangeService { public static void registerListener(ChangeListener listener) { LISTENERS.put(listener.getId(), listener); + + // Update sources + if (CollectionUtils.isNotEmpty(listener.getChangeSources())) { + for (ChangeSource source: listener.getChangeSources()) { + String sourceKey = source.toString(); + if (!LISTENERS_SOURCES.containsKey(sourceKey)) { + LISTENERS_SOURCES.put(sourceKey, source); + LISTENERS_SOURCES_USAGE_COUNT.put(sourceKey, 1); + } + else { + LISTENERS_SOURCES_USAGE_COUNT.put(sourceKey, LISTENERS_SOURCES_USAGE_COUNT.get(sourceKey)+1); + } + } + } } public static void unregisterListener(ChangeListener listener) { LISTENERS.remove(listener.getId()); + + // Update sources + if (CollectionUtils.isNotEmpty(listener.getChangeSources())) { + for (ChangeSource source: listener.getChangeSources()) { + String sourceKey = source.toString(); + if (LISTENERS_SOURCES.containsKey(sourceKey)) { + int usageCount = LISTENERS_SOURCES_USAGE_COUNT.get(sourceKey) - 1; + if (usageCount > 0) { + LISTENERS_SOURCES_USAGE_COUNT.put(sourceKey, usageCount); + } + else { + LISTENERS_SOURCES.remove(sourceKey); + LISTENERS_SOURCES_USAGE_COUNT.remove(sourceKey); + } + } + } + } } diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeSource.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeSource.java index 6f79a507..982a3454 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeSource.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeSource.java @@ -38,7 +38,10 @@ package org.duniter.elasticsearch.service.changes; limitations under the License. */ +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; +import org.duniter.core.util.StringUtils; import java.util.Set; @@ -65,6 +68,17 @@ public class ChangeSource { } } + public ChangeSource(String index, String type) { + this(index, type, null); + } + + public ChangeSource(String index, String type, String id) { + Preconditions.checkArgument(StringUtils.isNotBlank(index)); + indices = index.equals("*") ? null : ImmutableSet.of(index); + types = StringUtils.isBlank(type) || type.equals("*") ? null : ImmutableSet.of(type); + ids = StringUtils.isBlank(id) || id.equals("*") ? null : ImmutableSet.of(id); + } + public Set<String> getIds() { return ids; } @@ -77,4 +91,51 @@ public class ChangeSource { return types; } + public String toString() { + StringBuilder sb = new StringBuilder(); + + // Add indices + Joiner joiner = Joiner.on(','); + if (indices == null) { + sb.append('*'); + } + else { + joiner.appendTo(sb, indices); + } + + // Add types + if (types == null) { + if (ids != null) { + sb.append("/*"); + } + } + else { + sb.append('/'); + joiner.appendTo(sb, types); + } + + // Add ids + if (ids != null) { + sb.append('/'); + joiner.appendTo(sb, ids); + } + return sb.toString(); + } + + public boolean apply(String index, String type, String id) { + if (indices != null && !indices.contains(index)) { + return false; + } + + if (types != null && !types.contains(type)) { + return false; + } + + if (ids != null && !ids.contains(id)) { + return false; + } + + return true; + } + } diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeUtils.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeUtils.java deleted file mode 100644 index cfe6a16c..00000000 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeUtils.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.duniter.elasticsearch.service.changes; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.JsonSyntaxException; -import org.duniter.core.exception.TechnicalException; -import org.duniter.elasticsearch.exception.InvalidFormatException; -import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.io.stream.BytesStreamOutput; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.json.JsonXContent; -import org.joda.time.DateTime; - -import java.io.IOException; - -/** - * Created by blavenie on 30/11/16. - */ -public class ChangeUtils { - - public static ChangeEvent fromJson(ObjectMapper objectMapper, String json) { - try { - JsonNode actualObj = objectMapper.readTree(json); - String index = actualObj.get("_index").asText(); - String type = actualObj.get("_type").asText(); - String id = actualObj.get("_id").asText(); - DateTime timestamp = new DateTime(actualObj.get("_timestamp").asLong()); - ChangeEvent.Operation operation = ChangeEvent.Operation.valueOf(actualObj.get("_operation").asText()); - long version = actualObj.get("_version").asLong(); - - JsonNode sourceNode = actualObj.get("_source"); - BytesReference source = null; - if (sourceNode != null) { - // TODO : fill bytes reference from source - //source = sourceNode. - } - - ChangeEvent event = new ChangeEvent(index, type, id, timestamp, operation, version, source); - return event; - } catch (IOException | JsonSyntaxException e) { - throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e); - } - } - - public static String toJson(ChangeEvent change) { - try { - XContentBuilder builder = new XContentBuilder(JsonXContent.jsonXContent, new BytesStreamOutput()); - builder.startObject() - .field("_index", change.getIndex()) - .field("_type", change.getType()) - .field("_id", change.getId()) - .field("_timestamp", change.getTimestamp()) - .field("_version", change.getVersion()) - .field("_operation", change.getOperation().toString()); - if (change.getSource() != null) { - builder.rawField("_source", change.getSource()); - } - builder.endObject(); - - return builder.string(); - } catch (IOException e) { - throw new TechnicalException("Error while generating JSON from change event", e); - } - } -} diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/changes/WebSocketChangeEndPoint.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketChangesEndPoint.java similarity index 70% rename from duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/changes/WebSocketChangeEndPoint.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketChangesEndPoint.java index b99e117e..8d7b02f6 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/changes/WebSocketChangeEndPoint.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketChangesEndPoint.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.websocket.changes; +package org.duniter.elasticsearch.websocket; /* * #%L @@ -38,24 +38,36 @@ package org.duniter.elasticsearch.websocket.changes; limitations under the License. */ -import org.duniter.elasticsearch.service.changes.ChangeListener; +import org.duniter.elasticsearch.PluginSettings; +import org.duniter.elasticsearch.service.changes.ChangeEvent; import org.duniter.elasticsearch.service.changes.ChangeService; -import org.duniter.elasticsearch.websocket.WebSocketServer; +import org.duniter.elasticsearch.service.changes.ChangeSource; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.Loggers; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; @ServerEndpoint(value = "/_changes") -public class WebSocketChangeEndPoint implements ChangeListener{ +public class WebSocketChangesEndPoint implements ChangeService.ChangeListener{ + + public static Collection<ChangeSource> SOURCES = null; public static class Init { @Inject - public Init(WebSocketServer webSocketServer) { - webSocketServer.addEndPoint(WebSocketChangeEndPoint.class); + public Init(WebSocketServer webSocketServer, PluginSettings pluginSettings) { + webSocketServer.addEndPoint(WebSocketChangesEndPoint.class); + final String[] sourcesStr = pluginSettings.getWebSocketChangesListenSource(); + List<ChangeSource> sources = new ArrayList<>(); + for(String sourceStr : sourcesStr) { + sources.add(new ChangeSource(sourceStr)); + } + SOURCES = sources; } } @@ -70,8 +82,8 @@ public class WebSocketChangeEndPoint implements ChangeListener{ } @Override - public void onChanges(String message) { - session.getAsyncRemote().sendText(message); + public void onChange(ChangeEvent changeEvent) { + session.getAsyncRemote().sendText(changeEvent.toJson()); } @Override @@ -79,6 +91,11 @@ public class WebSocketChangeEndPoint implements ChangeListener{ return session == null ? null : session.getId(); } + @Override + public Collection<ChangeSource> getChangeSources() { + return SOURCES; + } + @OnMessage public void onMessage(String message) { log.debug("Received message: "+message); diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketModule.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketModule.java index 717933a1..7152c435 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketModule.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketModule.java @@ -38,13 +38,12 @@ package org.duniter.elasticsearch.websocket; limitations under the License. */ -import org.duniter.elasticsearch.websocket.changes.WebSocketChangeEndPoint; import org.elasticsearch.common.inject.AbstractModule; public class WebSocketModule extends AbstractModule { @Override protected void configure() { bind(WebSocketServer.class).asEagerSingleton(); - bind(WebSocketChangeEndPoint.Init.class).asEagerSingleton(); + bind(WebSocketChangesEndPoint.Init.class).asEagerSingleton(); } } diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServer.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServer.java index 598fc81f..c0ac3524 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServer.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServer.java @@ -41,7 +41,6 @@ package org.duniter.elasticsearch.websocket; import org.duniter.core.exception.TechnicalException; import org.duniter.elasticsearch.PluginSettings; import org.duniter.elasticsearch.threadpool.ThreadPool; -import org.duniter.elasticsearch.websocket.changes.WebSocketChangeEndPoint; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.Loggers; diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/MarketService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/MarketService.java index 1236558c..2e2a4a3b 100644 --- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/MarketService.java +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/MarketService.java @@ -438,7 +438,7 @@ public class MarketService extends AbstractService { issuerTitle != null ? issuerTitle : issuer.substring(0, 8), recordTitle) .setRecipient(recordIssuer) - .setLink(INDEX, RECORD_TYPE, recordId) + .setReference(INDEX, RECORD_TYPE, recordId) .build()); } } diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEvent.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEvent.java index 5e8e4921..601c819d 100644 --- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEvent.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEvent.java @@ -23,7 +23,9 @@ package org.duniter.elasticsearch.user.model; */ import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Preconditions; import org.duniter.core.client.model.elasticsearch.Record; import org.duniter.core.exception.TechnicalException; import org.nuiton.i18n.I18n; @@ -57,7 +59,7 @@ public class UserEvent extends Record { public static final String PROPERTY_CODE="code"; public static final String PROPERTY_MESSAGE="message"; public static final String PROPERTY_PARAMS="params"; - public static final String PROPERTY_LINK="link"; + public static final String PROPERTY_LINK="reference"; public static final String PROPERTY_RECIPIENT="recipient"; @@ -71,7 +73,7 @@ public class UserEvent extends Record { private String[] params; - private UserEventLink link; + private Reference reference; public UserEvent() { super(); @@ -91,7 +93,7 @@ public class UserEvent extends Record { this.type = another.getType(); this.code = another.getCode(); this.params = another.getParams(); - this.link = (another.getLink() != null) ? new UserEventLink(another.getLink()) : null; + this.reference = (another.getReference() != null) ? new Reference(another.getReference()) : null; this.message = another.getMessage(); this.recipient = another.getRecipient(); } @@ -112,8 +114,8 @@ public class UserEvent extends Record { return params; } - public UserEventLink getLink() { - return link; + public Reference getReference() { + return reference; } public String getLocalizedMessage(Locale locale) { @@ -136,8 +138,8 @@ public class UserEvent extends Record { this.params = params; } - public void setLink(UserEventLink link) { - this.link = link; + public void setReference(Reference reference) { + this.reference = reference; } public String getRecipient() { @@ -152,6 +154,7 @@ public class UserEvent extends Record { public String toJson() { try { ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return mapper.writeValueAsString(this); } catch(Exception e) { throw new TechnicalException(e); @@ -165,6 +168,10 @@ public class UserEvent extends Record { return copy.toJson(); } + private static long getDefaultTime() { + return Math.round(1d * System.currentTimeMillis() / 1000); + } + public static class Builder { private UserEvent result; @@ -193,13 +200,24 @@ public class UserEvent extends Record { return this; } - public Builder setLink(String index, String type, String id) { - result.setLink(new UserEventLink(index, type, id)); + public Builder setReference(String index, String type, String id) { + result.setReference(new Reference(index, type, id)); + return this; + } + + public Builder setReferenceHash(String hash) { + Preconditions.checkNotNull(result.getReference(), "No reference set. Please call setReference() first"); + result.getReference().setHash(hash); return this; } - public Builder setLink(String index, String type, String id, String anchor) { - result.setLink(new UserEventLink(index, type, id, anchor)); + public Builder setReference(String index, String type, String id, String anchor) { + result.setReference(new Reference(index, type, id, anchor)); + return this; + } + + public Builder setTime(long time) { + result.setTime(time); return this; } @@ -211,7 +229,68 @@ public class UserEvent extends Record { } } - private static int getDefaultTime() { - return Math.round(1f * System.currentTimeMillis() / 1000); + + + public static class Reference { + + private String index; + + private String type; + + private String id; + + private String anchor; + + private String hash; + + public Reference() { + } + + public Reference(String index, String type, String id) { + this(index, type, id, null); + } + + public Reference(String index, String type, String id, String anchor) { + this.index = index; + this.type = type; + this.id = id; + this.anchor = anchor; + } + + public Reference(Reference another) { + this.index = another.getIndex(); + this.type = another.getType(); + this.id = another.getId(); + this.hash = another.getHash(); + this.anchor = another.getAnchor(); + } + + public String getIndex() { + return index; + } + + public String getType() { + return type; + } + + public String getId() { + return id; + } + + public String getAnchor() { + return anchor; + } + + public void setAnchor(String anchor) { + this.anchor = anchor; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } } } diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEventCodes.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEventCodes.java index 39e4d4e0..fbb06fab 100644 --- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEventCodes.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEventCodes.java @@ -28,6 +28,15 @@ package org.duniter.elasticsearch.user.model; public enum UserEventCodes { NODE_STARTED, - CREATE_DOC + CREATE_DOC, + + // Membership state + MEMBER_JOIN, + MEMBER_LEAVE, + MEMBER_ACTIVE, + + // TX + TX_SENT, + TX_RECEIVED } diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEventLink.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEventLink.java deleted file mode 100644 index fb03cd71..00000000 --- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEventLink.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.duniter.elasticsearch.user.model; - -/* - * #%L - * Duniter4j :: ElasticSearch Plugin - * %% - * Copyright (C) 2014 - 2016 EIS - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * <http://www.gnu.org/licenses/gpl-3.0.html>. - * #L% - */ - -/** - * Created by blavenie on 29/11/16. - */ -public class UserEventLink { - - private String index; - - private String type; - - private String id; - - private String anchor; - - public UserEventLink() { - } - - public UserEventLink(String index, String type, String id) { - this(index, type, id, null); - } - - public UserEventLink(String index, String type, String id, String anchor) { - this.index = index; - this.type = type; - this.id = id; - this.anchor = anchor; - } - - public UserEventLink(UserEventLink another) { - this.index = another.getIndex(); - this.type = another.getType(); - this.id = another.getId(); - this.anchor = another.getAnchor(); - } - - public String getIndex() { - return index; - } - - public String getType() { - return type; - } - - public String getId() { - return id; - } - - public String getAnchor() { - return anchor; - } -} diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/BlockchainUserEventService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/BlockchainUserEventService.java new file mode 100644 index 00000000..1b2976c2 --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/BlockchainUserEventService.java @@ -0,0 +1,190 @@ +package org.duniter.elasticsearch.user.service; + +/* + * #%L + * UCoin Java Client :: Core API + * %% + * Copyright (C) 2014 - 2015 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import org.duniter.core.client.model.bma.BlockchainBlock; +import org.duniter.core.client.model.bma.jackson.JacksonUtils; +import org.duniter.core.exception.TechnicalException; +import org.duniter.core.service.CryptoService; +import org.duniter.core.util.CollectionUtils; +import org.duniter.elasticsearch.PluginSettings; +import org.duniter.elasticsearch.service.AbstractService; +import org.duniter.elasticsearch.service.BlockchainService; +import org.duniter.elasticsearch.service.changes.ChangeEvent; +import org.duniter.elasticsearch.service.changes.ChangeService; +import org.duniter.elasticsearch.service.changes.ChangeSource; +import org.duniter.elasticsearch.user.model.UserEvent; +import org.duniter.elasticsearch.user.model.UserEventCodes; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.nuiton.i18n.I18n; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Created by Benoit on 30/03/2015. + */ +public class BlockchainUserEventService extends AbstractService implements ChangeService.ChangeListener { + + public final UserEventService userEventService; + + public final ObjectMapper objectMapper; + + public final List<ChangeSource> changeListenSources; + + public final Joiner simpleJoiner = Joiner.on(','); + + @Inject + public BlockchainUserEventService(Client client, PluginSettings settings, CryptoService cryptoService, + UserEventService userEventService) { + super("duniter.user.event.blockchain", client, settings, cryptoService); + this.userEventService = userEventService; + this.objectMapper = JacksonUtils.newObjectMapper(); + this.changeListenSources = ImmutableList.of(new ChangeSource("*", BlockchainService.BLOCK_TYPE)); + ChangeService.registerListener(this); + } + + @Override + public String getId() { + return "duniter.user.event.blockchain"; + } + + @Override + public void onChange(ChangeEvent change) { + if (change.getSource() == null) return; + + try { + BlockchainBlock block = objectMapper.readValue(change.getSource().streamInput(), BlockchainBlock.class); + + switch (change.getOperation()) { + case INDEX: + processBlockIndex(block); + break; + + // on DELETE : remove user event on block (using link + case DELETE: + processBlockDelete(block); + break; + } + + } + catch(IOException e) { + throw new TechnicalException(String.format("Unable to parse received block %s", change.getId()), e); + } + + //logger.info("receiveing block change: " + change.toJson()); + } + + @Override + public Collection<ChangeSource> getChangeSources() { + return changeListenSources; + } + + /* -- internal method -- */ + + private void processBlockIndex(BlockchainBlock block) { + // Joiners + if (CollectionUtils.isNotEmpty(block.getJoiners())) { + for (BlockchainBlock.Joiner joiner: block.getJoiners()) { + notifyUserEvent(block, joiner.getPublicKey(), UserEventCodes.MEMBER_JOIN, I18n.n("duniter.user.event.ms.join"), block.getCurrency()); + } + } + + // Leavers + if (CollectionUtils.isNotEmpty(block.getLeavers())) { + for (BlockchainBlock.Joiner leaver: block.getJoiners()) { + notifyUserEvent(block, leaver.getPublicKey(), UserEventCodes.MEMBER_LEAVE, I18n.n("duniter.user.event.ms.leave"), block.getCurrency()); + } + } + + // Actives + if (CollectionUtils.isNotEmpty(block.getActives())) { + for (BlockchainBlock.Joiner active: block.getActives()) { + notifyUserEvent(block, active.getPublicKey(), UserEventCodes.MEMBER_ACTIVE, I18n.n("duniter.user.event.ms.active"), block.getCurrency()); + } + } + + // Tx + if (CollectionUtils.isNotEmpty(block.getTransactions())) { + for (BlockchainBlock.Transaction tx: block.getTransactions()) { + processTx(block, tx); + } + } + } + + private void processTx(BlockchainBlock block, BlockchainBlock.Transaction tx) { + Set<String> senders = ImmutableSet.copyOf(tx.getIssuers()); + + + // Received + // TODO get profile name + String sendersStr = simpleJoiner.join(senders); + Set<String> receivers = new HashSet<>(); + for (String output : tx.getOutputs()) { + String[] parts = output.split(":"); + if (parts.length >= 3 && parts[2].startsWith("SIG(")) { + String receiver = parts[2].substring(4, parts[2].length() - 1); + if (!senders.contains(receiver) && !receivers.contains(receiver)) { + notifyUserEvent(block, receiver, UserEventCodes.TX_RECEIVED, I18n.n("duniter.user.event.tx.received"), sendersStr); + receivers.add(receiver); + } + } + } + + + // Sent + // TODO get profile name + String receiverStr = simpleJoiner.join(receivers); + for (String sender:senders) { + notifyUserEvent(block, sender, UserEventCodes.TX_SENT, I18n.n("duniter.user.event.tx.sent"), receiverStr); + } + + // TODO : index this TX in a special index ? + } + + private void notifyUserEvent(BlockchainBlock block, String pubkey, UserEventCodes code, String message, String... params) { + UserEvent event = UserEvent.newBuilder(UserEvent.EventType.INFO, code.name()) + .setRecipient(pubkey) + .setMessage(message, params) + .setTime(block.getMedianTime()) + .setReference(block.getCurrency(), BlockchainService.BLOCK_TYPE, String.valueOf(block.getNumber())) + .setReferenceHash(block.getHash()) + .build(); + userEventService.notifyUser(event); + } + + private void processBlockDelete(BlockchainBlock block) { + + + //userEventService.deleteUserEventByReference() + } +} diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/ServiceModule.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/ServiceModule.java index 30e1b815..bcb7164e 100644 --- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/ServiceModule.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/ServiceModule.java @@ -28,10 +28,13 @@ import org.elasticsearch.common.inject.Module; public class ServiceModule extends AbstractModule implements Module { @Override protected void configure() { - bind(MessageService.class).asEagerSingleton(); bind(HistoryService.class).asEagerSingleton(); + + bind(MessageService.class).asEagerSingleton(); bind(UserService.class).asEagerSingleton(); bind(UserEventService.class).asEagerSingleton(); + bind(BlockchainUserEventService.class).asEagerSingleton(); + bind(SynchroService.class).asEagerSingleton(); } diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserEventService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserEventService.java index 1f002846..c961281e 100644 --- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserEventService.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserEventService.java @@ -87,7 +87,7 @@ public class UserEventService extends AbstractService { public UserEventService(Client client, PluginSettings settings, CryptoService cryptoService, MailService mailService, ThreadPool threadPool) { - super("duniter.event." + INDEX, client, settings, cryptoService); + super("duniter.user.event", client, settings, cryptoService); this.mailService = mailService; this.threadPool = threadPool; this.nodeKeyPair = getNodeKeyPairOrNull(pluginSettings); @@ -102,44 +102,20 @@ public class UserEventService extends AbstractService { * Notify cluster admin */ public void notifyAdmin(UserEvent event) { - - UserProfile adminProfile; - if (StringUtils.isNotBlank(nodePubkey)) { - adminProfile = getUserProfile(nodePubkey, UserProfile.PROPERTY_EMAIL, UserProfile.PROPERTY_LOCALE); - } - else { - adminProfile = new UserProfile(); - } - - // Add new event to index - Locale locale = StringUtils.isNotBlank(adminProfile.getLocale()) ? - new Locale(adminProfile.getLocale()) : - I18n.getDefaultLocale(); - if (StringUtils.isNotBlank(nodePubkey)) { - event.setRecipient(nodePubkey); - indexEvent(locale, event); - } - - // Send email to admin - String adminEmail = StringUtils.isNotBlank(adminProfile.getEmail()) ? - adminProfile.getEmail() : - pluginSettings.getMailAdmin(); - if (StringUtils.isNotBlank(adminEmail)) { - String subjectPrefix = pluginSettings.getMailSubjectPrefix(); - sendEmail(adminEmail, - I18n.l(locale, "duniter4j.event.subject."+event.getType().name(), subjectPrefix), - event.getLocalizedMessage(locale)); - } + // async + //threadPool.schedule(() -> { + doNotifyAdmin(event); + //}); } /** * Notify a user */ public void notifyUser(UserEvent event) { - // Notify user + // async threadPool.schedule(() -> { doNotifyUser(event); - }, TimeValue.timeValueMillis(100)); + }, TimeValue.timeValueMillis(500)); } public String indexEvent(Locale locale, UserEvent event) { @@ -235,8 +211,8 @@ public class UserEventService extends AbstractService { .field("index", "not_analyzed") .endObject() - // link - .startObject("link") + // reference + .startObject("reference") .field("type", "nested") .field("dynamic", "false") .startObject("properties") @@ -252,6 +228,10 @@ public class UserEventService extends AbstractService { .field("type", "string") .field("index", "not_analyzed") .endObject() + .startObject("hash") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() .endObject() .endObject() @@ -265,6 +245,18 @@ public class UserEventService extends AbstractService { .field("type", "string") .endObject() + // hash + .startObject("hash") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + // signature + .startObject("signature") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + .endObject() .endObject().endObject(); @@ -312,6 +304,40 @@ public class UserEventService extends AbstractService { return CryptoUtils.encodeBase58(nodeKeyPair.getPubKey()); } + /** + * Notify cluster admin + */ + public void doNotifyAdmin(UserEvent event) { + + UserProfile adminProfile; + if (StringUtils.isNotBlank(nodePubkey)) { + adminProfile = getUserProfile(nodePubkey, UserProfile.PROPERTY_EMAIL, UserProfile.PROPERTY_LOCALE); + } + else { + adminProfile = new UserProfile(); + } + + // Add new event to index + Locale locale = StringUtils.isNotBlank(adminProfile.getLocale()) ? + new Locale(adminProfile.getLocale()) : + I18n.getDefaultLocale(); + if (StringUtils.isNotBlank(nodePubkey)) { + event.setRecipient(nodePubkey); + indexEvent(locale, event); + } + + // Send email to admin + String adminEmail = StringUtils.isNotBlank(adminProfile.getEmail()) ? + adminProfile.getEmail() : + pluginSettings.getMailAdmin(); + if (StringUtils.isNotBlank(adminEmail)) { + String subjectPrefix = pluginSettings.getMailSubjectPrefix(); + sendEmail(adminEmail, + I18n.l(locale, "duniter4j.event.subject."+event.getType().name(), subjectPrefix), + event.getLocalizedMessage(locale)); + } + } + /** * Notify a user */ diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserService.java index 0cfbf566..5333f4e4 100644 --- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserService.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserService.java @@ -50,15 +50,13 @@ public class UserService extends AbstractService { public static final String INDEX = "user"; public static final String PROFILE_TYPE = "profile"; - public static final String EVENT_TYPE = "profile"; public static final String SETTINGS_TYPE = "settings"; @Inject public UserService(Client client, PluginSettings settings, - CryptoService cryptoService, - MailService mailService) { - super("gchange." + INDEX, client, settings,cryptoService); + CryptoService cryptoService) { + super("duniter." + INDEX, client, settings,cryptoService); } /** diff --git a/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_en_GB.properties b/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_en_GB.properties index d884f6ba..f5143c94 100644 --- a/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_en_GB.properties +++ b/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_en_GB.properties @@ -1,5 +1,12 @@ -duniter.event.NODE_STARTED= -duniter4j.event.NODE_STARTED=Node started on cluster Duniter4j ES [%s] +duniter.event.NODE_STARTED=Node started on cluster Duniter4j ES [%s] +duniter.user.event.active= +duniter.user.event.join= +duniter.user.event.leave= +duniter.user.event.ms.active= +duniter.user.event.ms.join= +duniter.user.event.ms.leave= +duniter.user.event.tx.received= +duniter.user.event.tx.sent= duniter4j.event.subject.ERROR=[%s] Error message duniter4j.event.subject.INFO=[%s] Information message duniter4j.event.subject.WARN=[%s] Warning message diff --git a/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_fr_FR.properties b/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_fr_FR.properties index e179adfd..57a43dce 100644 --- a/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_fr_FR.properties +++ b/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_fr_FR.properties @@ -1,5 +1,9 @@ -duniter.event.NODE_STARTED= -duniter4j.event.NODE_STARTED=Noeud démarré sur le cluster Duniter4j ES [%s] +duniter.event.NODE_STARTED=Noeud démarré sur le cluster Duniter4j ES [%s] +duniter.user.event.ms.active=Votre adhésion comme membre a bien été renouvellée +duniter.user.event.ms.join=Vous êtes maintenant membre de la monnaie +duniter.user.event.ms.leave=Votre adhésion comme membre à expirée +duniter.user.event.tx.received=Vous avez recu un paiement de %s +duniter.user.event.tx.sent=Votre paiement à %s a bien été executé duniter4j.event.subject.ERROR=%s Message d'erreur duniter4j.event.subject.INFO=%s Message d'information duniter4j.event.subject.WARN=%s Message d'avertissement -- GitLab