/* * This file is part of Bitsquare. * * Bitsquare is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * Bitsquare 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 Affero General Public * License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. */ package io.bitsquare.arbitration; import io.bitsquare.app.Version; import io.bitsquare.arbitration.messages.DisputeCommunicationMessage; import io.bitsquare.common.crypto.PubKeyRing; import io.bitsquare.common.util.Utilities; import io.bitsquare.common.wire.Payload; import io.bitsquare.storage.Storage; import io.bitsquare.trade.Contract; import javafx.beans.property.*; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.IOException; import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; public final class Dispute implements Payload { // That object is sent over the wire, so we need to take care of version compatibility. private static final long serialVersionUID = Version.P2P_NETWORK_VERSION; private static final Logger log = LoggerFactory.getLogger(Dispute.class); /////////////////////////////////////////////////////////////////////////////////////////// // Fields /////////////////////////////////////////////////////////////////////////////////////////// private final String tradeId; private final String id; private final int traderId; private final boolean disputeOpenerIsBuyer; private final boolean disputeOpenerIsOfferer; private final long openingDate; private final PubKeyRing traderPubKeyRing; private final long tradeDate; private final Contract contract; private final byte[] contractHash; @Nullable private final byte[] depositTxSerialized; @Nullable private final byte[] payoutTxSerialized; @Nullable private final String depositTxId; @Nullable private final String payoutTxId; private final String contractAsJson; private final String offererContractSignature; private final String takerContractSignature; private final PubKeyRing arbitratorPubKeyRing; private final boolean isSupportTicket; private final ArrayList<DisputeCommunicationMessage> disputeCommunicationMessages = new ArrayList<>(); private boolean isClosed; private DisputeResult disputeResult; @Nullable private String disputePayoutTxId; transient private Storage<DisputeList<Dispute>> storage; transient private ObservableList<DisputeCommunicationMessage> disputeCommunicationMessagesAsObservableList = FXCollections.observableArrayList(disputeCommunicationMessages); transient private BooleanProperty isClosedProperty = new SimpleBooleanProperty(isClosed); transient private ObjectProperty<DisputeResult> disputeResultProperty = new SimpleObjectProperty<>(disputeResult); /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// public Dispute(Storage<DisputeList<Dispute>> storage, String tradeId, int traderId, boolean disputeOpenerIsBuyer, boolean disputeOpenerIsOfferer, PubKeyRing traderPubKeyRing, Date tradeDate, Contract contract, byte[] contractHash, @Nullable byte[] depositTxSerialized, @Nullable byte[] payoutTxSerialized, @Nullable String depositTxId, @Nullable String payoutTxId, String contractAsJson, String offererContractSignature, String takerContractSignature, PubKeyRing arbitratorPubKeyRing, boolean isSupportTicket) { this.storage = storage; this.tradeId = tradeId; this.traderId = traderId; this.disputeOpenerIsBuyer = disputeOpenerIsBuyer; this.disputeOpenerIsOfferer = disputeOpenerIsOfferer; this.traderPubKeyRing = traderPubKeyRing; this.tradeDate = tradeDate.getTime(); this.contract = contract; this.contractHash = contractHash; this.depositTxSerialized = depositTxSerialized; this.payoutTxSerialized = payoutTxSerialized; this.depositTxId = depositTxId; this.payoutTxId = payoutTxId; this.contractAsJson = contractAsJson; this.offererContractSignature = offererContractSignature; this.takerContractSignature = takerContractSignature; this.arbitratorPubKeyRing = arbitratorPubKeyRing; this.isSupportTicket = isSupportTicket; this.openingDate = new Date().getTime(); id = tradeId + "_" + traderId; } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { try { in.defaultReadObject(); disputeCommunicationMessagesAsObservableList = FXCollections.observableArrayList(disputeCommunicationMessages); disputeResultProperty = new SimpleObjectProperty<>(disputeResult); isClosedProperty = new SimpleBooleanProperty(isClosed); } catch (Throwable t) { log.warn("Cannot be deserialized." + t.getMessage()); } } public void addDisputeMessage(DisputeCommunicationMessage disputeCommunicationMessage) { if (!disputeCommunicationMessages.contains(disputeCommunicationMessage)) { disputeCommunicationMessages.add(disputeCommunicationMessage); disputeCommunicationMessagesAsObservableList.add(disputeCommunicationMessage); storage.queueUpForSave(); } else { log.error("disputeDirectMessage already exists"); } } /////////////////////////////////////////////////////////////////////////////////////////// // Setters /////////////////////////////////////////////////////////////////////////////////////////// // In case we get the object via the network storage is not set as its transient, so we need to set it. public void setStorage(Storage<DisputeList<Dispute>> storage) { this.storage = storage; } public void setIsClosed(boolean isClosed) { boolean changed = this.isClosed != isClosed; this.isClosed = isClosed; isClosedProperty.set(isClosed); if (changed) storage.queueUpForSave(); } public void setDisputeResult(DisputeResult disputeResult) { boolean changed = this.disputeResult == null || !this.disputeResult.equals(disputeResult); this.disputeResult = disputeResult; disputeResultProperty.set(disputeResult); if (changed) storage.queueUpForSave(); } public void setDisputePayoutTxId(String disputePayoutTxId) { boolean changed = this.disputePayoutTxId == null || !this.disputePayoutTxId.equals(disputePayoutTxId); this.disputePayoutTxId = disputePayoutTxId; if (changed) storage.queueUpForSave(); } /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// public String getId() { return id; } public String getTradeId() { return tradeId; } public String getShortTradeId() { return Utilities.getShortId(tradeId); } public int getTraderId() { return traderId; } public boolean isDisputeOpenerIsBuyer() { return disputeOpenerIsBuyer; } public boolean isDisputeOpenerIsOfferer() { return disputeOpenerIsOfferer; } public Date getOpeningDate() { return new Date(openingDate); } public PubKeyRing getTraderPubKeyRing() { return traderPubKeyRing; } public Contract getContract() { return contract; } @Nullable public byte[] getDepositTxSerialized() { return depositTxSerialized; } @Nullable public byte[] getPayoutTxSerialized() { return payoutTxSerialized; } @Nullable public String getDepositTxId() { return depositTxId; } @Nullable public String getPayoutTxId() { return payoutTxId; } public String getContractAsJson() { return contractAsJson; } public String getOffererContractSignature() { return offererContractSignature; } public String getTakerContractSignature() { return takerContractSignature; } public ObservableList<DisputeCommunicationMessage> getDisputeCommunicationMessagesAsObservableList() { return disputeCommunicationMessagesAsObservableList; } public boolean isClosed() { return isClosedProperty.get(); } public ReadOnlyBooleanProperty isClosedProperty() { return isClosedProperty; } public PubKeyRing getArbitratorPubKeyRing() { return arbitratorPubKeyRing; } public ObjectProperty<DisputeResult> disputeResultProperty() { return disputeResultProperty; } public boolean isSupportTicket() { return isSupportTicket; } public byte[] getContractHash() { return contractHash; } public Date getTradeDate() { return new Date(tradeDate); } @org.jetbrains.annotations.Nullable public String getDisputePayoutTxId() { return disputePayoutTxId; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Dispute)) return false; Dispute dispute = (Dispute) o; if (traderId != dispute.traderId) return false; if (disputeOpenerIsBuyer != dispute.disputeOpenerIsBuyer) return false; if (disputeOpenerIsOfferer != dispute.disputeOpenerIsOfferer) return false; if (openingDate != dispute.openingDate) return false; if (tradeDate != dispute.tradeDate) return false; if (isSupportTicket != dispute.isSupportTicket) return false; if (isClosed != dispute.isClosed) return false; if (tradeId != null ? !tradeId.equals(dispute.tradeId) : dispute.tradeId != null) return false; if (id != null ? !id.equals(dispute.id) : dispute.id != null) return false; if (traderPubKeyRing != null ? !traderPubKeyRing.equals(dispute.traderPubKeyRing) : dispute.traderPubKeyRing != null) return false; if (contract != null ? !contract.equals(dispute.contract) : dispute.contract != null) return false; if (!Arrays.equals(contractHash, dispute.contractHash)) return false; if (!Arrays.equals(depositTxSerialized, dispute.depositTxSerialized)) return false; if (!Arrays.equals(payoutTxSerialized, dispute.payoutTxSerialized)) return false; if (depositTxId != null ? !depositTxId.equals(dispute.depositTxId) : dispute.depositTxId != null) return false; if (payoutTxId != null ? !payoutTxId.equals(dispute.payoutTxId) : dispute.payoutTxId != null) return false; if (contractAsJson != null ? !contractAsJson.equals(dispute.contractAsJson) : dispute.contractAsJson != null) return false; if (offererContractSignature != null ? !offererContractSignature.equals(dispute.offererContractSignature) : dispute.offererContractSignature != null) return false; if (takerContractSignature != null ? !takerContractSignature.equals(dispute.takerContractSignature) : dispute.takerContractSignature != null) return false; if (arbitratorPubKeyRing != null ? !arbitratorPubKeyRing.equals(dispute.arbitratorPubKeyRing) : dispute.arbitratorPubKeyRing != null) return false; if (disputeCommunicationMessages != null ? !disputeCommunicationMessages.equals(dispute.disputeCommunicationMessages) : dispute.disputeCommunicationMessages != null) return false; if (disputeResult != null ? !disputeResult.equals(dispute.disputeResult) : dispute.disputeResult != null) return false; if (disputePayoutTxId != null ? !disputePayoutTxId.equals(dispute.disputePayoutTxId) : dispute.disputePayoutTxId != null) return false; return !(storage != null ? !storage.equals(dispute.storage) : dispute.storage != null); } @Override public int hashCode() { int result = tradeId != null ? tradeId.hashCode() : 0; result = 31 * result + (id != null ? id.hashCode() : 0); result = 31 * result + traderId; result = 31 * result + (disputeOpenerIsBuyer ? 1 : 0); result = 31 * result + (disputeOpenerIsOfferer ? 1 : 0); result = 31 * result + (int) (openingDate ^ (openingDate >>> 32)); result = 31 * result + (traderPubKeyRing != null ? traderPubKeyRing.hashCode() : 0); result = 31 * result + (int) (tradeDate ^ (tradeDate >>> 32)); result = 31 * result + (contract != null ? contract.hashCode() : 0); result = 31 * result + (contractHash != null ? Arrays.hashCode(contractHash) : 0); result = 31 * result + (depositTxSerialized != null ? Arrays.hashCode(depositTxSerialized) : 0); result = 31 * result + (payoutTxSerialized != null ? Arrays.hashCode(payoutTxSerialized) : 0); result = 31 * result + (depositTxId != null ? depositTxId.hashCode() : 0); result = 31 * result + (payoutTxId != null ? payoutTxId.hashCode() : 0); result = 31 * result + (contractAsJson != null ? contractAsJson.hashCode() : 0); result = 31 * result + (offererContractSignature != null ? offererContractSignature.hashCode() : 0); result = 31 * result + (takerContractSignature != null ? takerContractSignature.hashCode() : 0); result = 31 * result + (arbitratorPubKeyRing != null ? arbitratorPubKeyRing.hashCode() : 0); result = 31 * result + (isSupportTicket ? 1 : 0); result = 31 * result + (disputeCommunicationMessages != null ? disputeCommunicationMessages.hashCode() : 0); result = 31 * result + (isClosed ? 1 : 0); result = 31 * result + (disputeResult != null ? disputeResult.hashCode() : 0); result = 31 * result + (disputePayoutTxId != null ? disputePayoutTxId.hashCode() : 0); result = 31 * result + (storage != null ? storage.hashCode() : 0); return result; } @Override public String toString() { return "Dispute{" + "tradeId='" + tradeId + '\'' + ", id='" + id + '\'' + ", traderId=" + traderId + ", disputeOpenerIsBuyer=" + disputeOpenerIsBuyer + ", disputeOpenerIsOfferer=" + disputeOpenerIsOfferer + ", openingDate=" + openingDate + ", traderPubKeyRing=" + traderPubKeyRing + ", tradeDate=" + tradeDate + ", contract=" + contract + ", contractHash=" + Arrays.toString(contractHash) + ", depositTxSerialized=" + Arrays.toString(depositTxSerialized) + ", payoutTxSerialized=" + Arrays.toString(payoutTxSerialized) + ", depositTxId='" + depositTxId + '\'' + ", payoutTxId='" + payoutTxId + '\'' + ", contractAsJson='" + contractAsJson + '\'' + ", offererContractSignature='" + offererContractSignature + '\'' + ", takerContractSignature='" + takerContractSignature + '\'' + ", arbitratorPubKeyRing=" + arbitratorPubKeyRing + ", isSupportTicket=" + isSupportTicket + ", disputeCommunicationMessages=" + disputeCommunicationMessages + ", isClosed=" + isClosed + ", disputeResult=" + disputeResult + ", disputePayoutTxId='" + disputePayoutTxId + '\'' + ", disputeCommunicationMessagesAsObservableList=" + disputeCommunicationMessagesAsObservableList + ", isClosedProperty=" + isClosedProperty + ", disputeResultProperty=" + disputeResultProperty + '}'; } }