/******************************************************************************
* Copyright © 2013-2016 The Nxt Core Developers. *
* *
* See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at *
* the top-level directory of this distribution for the individual copyright *
* holder information and the developer policies on copyright and licensing. *
* *
* Unless otherwise agreed in a custom licensing agreement, no part of the *
* Nxt software, including this file, may be copied, modified, propagated, *
* or distributed except according to the terms contained in the LICENSE.txt *
* file. *
* *
* Removal or modification of this copyright notice is prohibited. *
* *
******************************************************************************/
package nxt;
import nxt.crypto.EncryptedData;
import nxt.db.DbIterator;
import nxt.db.DbKey;
import nxt.db.DbUtils;
import nxt.db.PrunableDbTable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public final class PrunableMessage {
private static final DbKey.LongKeyFactory<PrunableMessage> prunableMessageKeyFactory = new DbKey.LongKeyFactory<PrunableMessage>("id") {
@Override
public DbKey newKey(PrunableMessage prunableMessage) {
return prunableMessage.dbKey;
}
};
private static final PrunableDbTable<PrunableMessage> prunableMessageTable = new PrunableDbTable<PrunableMessage>("prunable_message", prunableMessageKeyFactory) {
@Override
protected PrunableMessage load(Connection con, ResultSet rs) throws SQLException {
return new PrunableMessage(rs);
}
@Override
protected void save(Connection con, PrunableMessage prunableMessage) throws SQLException {
prunableMessage.save(con);
}
@Override
protected String defaultSort() {
return " ORDER BY block_timestamp DESC, db_id DESC ";
}
};
public static int getCount() {
return prunableMessageTable.getCount();
}
public static DbIterator<PrunableMessage> getAll(int from, int to) {
return prunableMessageTable.getAll(from, to);
}
public static PrunableMessage getPrunableMessage(long transactionId) {
return prunableMessageTable.get(prunableMessageKeyFactory.newKey(transactionId));
}
public static DbIterator<PrunableMessage> getPrunableMessages(long accountId, int from, int to) {
Connection con = null;
try {
con = Db.db.getConnection();
PreparedStatement pstmt = con.prepareStatement("SELECT * FROM prunable_message WHERE sender_id = ?"
+ " UNION ALL SELECT * FROM prunable_message WHERE recipient_id = ? AND sender_id <> ? ORDER BY block_timestamp DESC, db_id DESC "
+ DbUtils.limitsClause(from, to));
int i = 0;
pstmt.setLong(++i, accountId);
pstmt.setLong(++i, accountId);
pstmt.setLong(++i, accountId);
DbUtils.setLimits(++i, pstmt, from, to);
return prunableMessageTable.getManyBy(con, pstmt, false);
} catch (SQLException e) {
DbUtils.close(con);
throw new RuntimeException(e.toString(), e);
}
}
public static DbIterator<PrunableMessage> getPrunableMessages(long accountId, long otherAccountId, int from, int to) {
Connection con = null;
try {
con = Db.db.getConnection();
PreparedStatement pstmt = con.prepareStatement("SELECT * FROM prunable_message WHERE sender_id = ? AND recipient_id = ? "
+ "UNION ALL SELECT * FROM prunable_message WHERE sender_id = ? AND recipient_id = ? AND sender_id <> recipient_id "
+ "ORDER BY block_timestamp DESC, db_id DESC "
+ DbUtils.limitsClause(from, to));
int i = 0;
pstmt.setLong(++i, accountId);
pstmt.setLong(++i, otherAccountId);
pstmt.setLong(++i, otherAccountId);
pstmt.setLong(++i, accountId);
DbUtils.setLimits(++i, pstmt, from, to);
return prunableMessageTable.getManyBy(con, pstmt, false);
} catch (SQLException e) {
DbUtils.close(con);
throw new RuntimeException(e.toString(), e);
}
}
static void init() {}
private final long id;
private final DbKey dbKey;
private final long senderId;
private final long recipientId;
private byte[] message;
private EncryptedData encryptedData;
private boolean messageIsText;
private boolean encryptedMessageIsText;
private boolean isCompressed;
private final int transactionTimestamp;
private final int blockTimestamp;
private final int height;
private PrunableMessage(Transaction transaction, int blockTimestamp, int height) {
this.id = transaction.getId();
this.dbKey = prunableMessageKeyFactory.newKey(this.id);
this.senderId = transaction.getSenderId();
this.recipientId = transaction.getRecipientId();
this.blockTimestamp = blockTimestamp;
this.height = height;
this.transactionTimestamp = transaction.getTimestamp();
}
private void setPlain(Appendix.PrunablePlainMessage appendix) {
this.message = appendix.getMessage();
this.messageIsText = appendix.isText();
}
private void setEncrypted(Appendix.PrunableEncryptedMessage appendix) {
this.encryptedData = appendix.getEncryptedData();
this.encryptedMessageIsText = appendix.isText();
this.isCompressed = appendix.isCompressed();
}
private PrunableMessage(ResultSet rs) throws SQLException {
this.id = rs.getLong("id");
this.dbKey = prunableMessageKeyFactory.newKey(this.id);
this.senderId = rs.getLong("sender_id");
this.recipientId = rs.getLong("recipient_id");
this.message = rs.getBytes("message");
if (this.message != null) {
this.messageIsText = rs.getBoolean("message_is_text");
}
byte[] encryptedMessage = rs.getBytes("encrypted_message");
if (encryptedMessage != null) {
this.encryptedData = EncryptedData.readEncryptedData(encryptedMessage);
this.encryptedMessageIsText = rs.getBoolean("encrypted_is_text");
this.isCompressed = rs.getBoolean("is_compressed");
}
this.blockTimestamp = rs.getInt("block_timestamp");
this.transactionTimestamp = rs.getInt("transaction_timestamp");
this.height = rs.getInt("height");
}
private void save(Connection con) throws SQLException {
if (message == null && encryptedData == null) {
throw new IllegalStateException("Prunable message not fully initialized");
}
try (PreparedStatement pstmt = con.prepareStatement("MERGE INTO prunable_message (id, sender_id, recipient_id, "
+ "message, encrypted_message, message_is_text, encrypted_is_text, is_compressed, block_timestamp, transaction_timestamp, height) "
+ "KEY (id) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")) {
int i = 0;
pstmt.setLong(++i, this.id);
pstmt.setLong(++i, this.senderId);
DbUtils.setLongZeroToNull(pstmt, ++i, this.recipientId);
DbUtils.setBytes(pstmt, ++i, this.message);
DbUtils.setBytes(pstmt, ++i, this.encryptedData == null ? null : this.encryptedData.getBytes());
pstmt.setBoolean(++i, this.messageIsText);
pstmt.setBoolean(++i, this.encryptedMessageIsText);
pstmt.setBoolean(++i, this.isCompressed);
pstmt.setInt(++i, this.blockTimestamp);
pstmt.setInt(++i, this.transactionTimestamp);
pstmt.setInt(++i, this.height);
pstmt.executeUpdate();
}
}
public byte[] getMessage() {
return message;
}
public EncryptedData getEncryptedData() {
return encryptedData;
}
public boolean messageIsText() {
return messageIsText;
}
public boolean encryptedMessageIsText() {
return encryptedMessageIsText;
}
public boolean isCompressed() {
return isCompressed;
}
public long getId() {
return id;
}
public long getSenderId() {
return senderId;
}
public long getRecipientId() {
return recipientId;
}
public int getTransactionTimestamp() {
return transactionTimestamp;
}
public int getBlockTimestamp() {
return blockTimestamp;
}
public int getHeight() {
return height;
}
static void add(Transaction transaction, Appendix.PrunablePlainMessage appendix) {
add(transaction, appendix, Nxt.getBlockchain().getLastBlockTimestamp(), Nxt.getBlockchain().getHeight());
}
static void add(Transaction transaction, Appendix.PrunablePlainMessage appendix, int blockTimestamp, int height) {
if (appendix.getMessage() != null) {
PrunableMessage prunableMessage = prunableMessageTable.get(prunableMessageKeyFactory.newKey(transaction.getId()));
if (prunableMessage == null) {
prunableMessage = new PrunableMessage(transaction, blockTimestamp, height);
} else if (prunableMessage.height != height) {
throw new RuntimeException("Attempt to modify prunable message from height " + prunableMessage.height + " at height " + height);
}
if (prunableMessage.getMessage() == null) {
prunableMessage.setPlain(appendix);
prunableMessageTable.insert(prunableMessage);
}
}
}
static void add(Transaction transaction, Appendix.PrunableEncryptedMessage appendix) {
add(transaction, appendix, Nxt.getBlockchain().getLastBlockTimestamp(), Nxt.getBlockchain().getHeight());
}
static void add(Transaction transaction, Appendix.PrunableEncryptedMessage appendix, int blockTimestamp, int height) {
if (appendix.getEncryptedData() != null) {
PrunableMessage prunableMessage = prunableMessageTable.get(prunableMessageKeyFactory.newKey(transaction.getId()));
if (prunableMessage == null) {
prunableMessage = new PrunableMessage(transaction, blockTimestamp, height);
} else if (prunableMessage.height != height) {
throw new RuntimeException("Attempt to modify prunable message from height " + prunableMessage.height + " at height " + height);
}
if (prunableMessage.getEncryptedData() == null) {
prunableMessage.setEncrypted(appendix);
prunableMessageTable.insert(prunableMessage);
}
}
}
static boolean isPruned(long transactionId, boolean hasPrunablePlainMessage, boolean hasPrunableEncryptedMessage) {
if (!hasPrunablePlainMessage && !hasPrunableEncryptedMessage) {
return false;
}
try (Connection con = Db.db.getConnection();
PreparedStatement pstmt = con.prepareStatement("SELECT message, encrypted_message FROM prunable_message WHERE id = ?")) {
pstmt.setLong(1, transactionId);
try (ResultSet rs = pstmt.executeQuery()) {
return !rs.next()
|| (hasPrunablePlainMessage && rs.getBytes("message") == null)
|| (hasPrunableEncryptedMessage && rs.getBytes("encrypted_message") == null);
}
} catch (SQLException e) {
throw new RuntimeException(e.toString(), e);
}
}
}