Migrated networking and fixed networking tests
This commit is contained in:
@ -1,238 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.AddressRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.sql.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class JdbcAddressRepository extends JdbcHelper implements AddressRepository {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JdbcAddressRepository.class);
|
||||
|
||||
public JdbcAddressRepository(JdbcConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitmessageAddress findContact(byte[] ripeOrTag) {
|
||||
for (BitmessageAddress address : find("public_key is null")) {
|
||||
if (address.getVersion() > 3) {
|
||||
if (Arrays.equals(ripeOrTag, address.getTag())) return address;
|
||||
} else {
|
||||
if (Arrays.equals(ripeOrTag, address.getRipe())) return address;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitmessageAddress findIdentity(byte[] ripeOrTag) {
|
||||
for (BitmessageAddress address : find("private_key is not null")) {
|
||||
if (address.getVersion() > 3) {
|
||||
if (Arrays.equals(ripeOrTag, address.getTag())) return address;
|
||||
} else {
|
||||
if (Arrays.equals(ripeOrTag, address.getRipe())) return address;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BitmessageAddress> getIdentities() {
|
||||
return find("private_key IS NOT NULL");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BitmessageAddress> getChans() {
|
||||
return find("chan = '1'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BitmessageAddress> getSubscriptions() {
|
||||
return find("subscribed = '1'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BitmessageAddress> getSubscriptions(long broadcastVersion) {
|
||||
if (broadcastVersion > 4) {
|
||||
return find("subscribed = '1' AND version > 3");
|
||||
} else {
|
||||
return find("subscribed = '1' AND version <= 3");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BitmessageAddress> getContacts() {
|
||||
return find("private_key IS NULL OR chan = '1'");
|
||||
}
|
||||
|
||||
private List<BitmessageAddress> find(String where) {
|
||||
List<BitmessageAddress> result = new LinkedList<>();
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT address, alias, public_key, private_key, subscribed, chan " +
|
||||
"FROM Address WHERE " + where)
|
||||
) {
|
||||
while (rs.next()) {
|
||||
BitmessageAddress address;
|
||||
|
||||
InputStream privateKeyStream = rs.getBinaryStream("private_key");
|
||||
if (privateKeyStream == null) {
|
||||
address = new BitmessageAddress(rs.getString("address"));
|
||||
Blob publicKeyBlob = rs.getBlob("public_key");
|
||||
if (publicKeyBlob != null) {
|
||||
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(),
|
||||
publicKeyBlob.getBinaryStream(), (int) publicKeyBlob.length(), false);
|
||||
if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) {
|
||||
pubkey = new V4Pubkey((V3Pubkey) pubkey);
|
||||
}
|
||||
address.setPubkey(pubkey);
|
||||
}
|
||||
} else {
|
||||
PrivateKey privateKey = PrivateKey.read(privateKeyStream);
|
||||
address = new BitmessageAddress(privateKey);
|
||||
}
|
||||
address.setAlias(rs.getString("alias"));
|
||||
address.setSubscribed(rs.getBoolean("subscribed"));
|
||||
address.setChan(rs.getBoolean("chan"));
|
||||
|
||||
result.add(address);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean exists(BitmessageAddress address) {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT '1' FROM Address " +
|
||||
"WHERE address='" + address.getAddress() + "'")
|
||||
) {
|
||||
return rs.next();
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(BitmessageAddress address) {
|
||||
try {
|
||||
if (exists(address)) {
|
||||
update(address);
|
||||
} else {
|
||||
insert(address);
|
||||
}
|
||||
} catch (IOException | SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void update(BitmessageAddress address) throws IOException, SQLException {
|
||||
StringBuilder statement = new StringBuilder("UPDATE Address SET alias=?");
|
||||
if (address.getPubkey() != null) {
|
||||
statement.append(", public_key=?");
|
||||
}
|
||||
if (address.getPrivateKey() != null) {
|
||||
statement.append(", private_key=?");
|
||||
}
|
||||
statement.append(", subscribed=?, chan=? WHERE address=?");
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
PreparedStatement ps = connection.prepareStatement(statement.toString())
|
||||
) {
|
||||
int i = 0;
|
||||
ps.setString(++i, address.getAlias());
|
||||
if (address.getPubkey() != null) {
|
||||
writePubkey(ps, ++i, address.getPubkey());
|
||||
}
|
||||
if (address.getPrivateKey() != null) {
|
||||
writeBlob(ps, ++i, address.getPrivateKey());
|
||||
}
|
||||
ps.setBoolean(++i, address.isSubscribed());
|
||||
ps.setBoolean(++i, address.isChan());
|
||||
ps.setString(++i, address.getAddress());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private void insert(BitmessageAddress address) throws IOException, SQLException {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
PreparedStatement ps = connection.prepareStatement(
|
||||
"INSERT INTO Address (address, version, alias, public_key, private_key, subscribed, chan) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?)")
|
||||
) {
|
||||
ps.setString(1, address.getAddress());
|
||||
ps.setLong(2, address.getVersion());
|
||||
ps.setString(3, address.getAlias());
|
||||
writePubkey(ps, 4, address.getPubkey());
|
||||
writeBlob(ps, 5, address.getPrivateKey());
|
||||
ps.setBoolean(6, address.isSubscribed());
|
||||
ps.setBoolean(7, address.isChan());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
protected void writePubkey(PreparedStatement ps, int parameterIndex, Pubkey data) throws SQLException, IOException {
|
||||
if (data != null) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
data.writeUnencrypted(out);
|
||||
ps.setBytes(parameterIndex, out.toByteArray());
|
||||
} else {
|
||||
ps.setBytes(parameterIndex, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(BitmessageAddress address) {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement()
|
||||
) {
|
||||
stmt.executeUpdate("DELETE FROM Address WHERE address = '" + address.getAddress() + "'");
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitmessageAddress getAddress(String address) {
|
||||
List<BitmessageAddress> result = find("address = '" + address + "'");
|
||||
if (result.size() > 0) return result.get(0);
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository;
|
||||
|
||||
import org.flywaydb.core.Flyway;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* The base configuration for all JDBC based repositories. You should only make one instance,
|
||||
* as flyway initializes/updates the database at object creation.
|
||||
*/
|
||||
public class JdbcConfig {
|
||||
protected final Flyway flyway;
|
||||
protected final String dbUrl;
|
||||
protected final String dbUser;
|
||||
protected final String dbPassword;
|
||||
|
||||
public JdbcConfig(String dbUrl, String dbUser, String dbPassword) {
|
||||
this.dbUrl = dbUrl;
|
||||
this.dbUser = dbUser;
|
||||
this.dbPassword = dbPassword;
|
||||
this.flyway = new Flyway();
|
||||
flyway.setDataSource(dbUrl, dbUser, dbPassword);
|
||||
flyway.migrate();
|
||||
}
|
||||
|
||||
public JdbcConfig() {
|
||||
this("jdbc:h2:~/jabit;AUTO_SERVER=TRUE", "sa", null);
|
||||
}
|
||||
|
||||
public Connection getConnection() throws SQLException {
|
||||
return DriverManager.getConnection(dbUrl, dbUser, dbPassword);
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Helper class that does Flyway migration, provides JDBC connections and some helper methods.
|
||||
*/
|
||||
public abstract class JdbcHelper {
|
||||
|
||||
protected final JdbcConfig config;
|
||||
|
||||
protected JdbcHelper(JdbcConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public static void writeBlob(PreparedStatement ps, int parameterIndex, Streamable data) throws SQLException, IOException {
|
||||
if (data == null) {
|
||||
ps.setBytes(parameterIndex, null);
|
||||
} else {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
data.write(os);
|
||||
ps.setBytes(parameterIndex, os.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.Inventory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.SqlStrings.join;
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.now;
|
||||
|
||||
public class JdbcInventory extends JdbcHelper implements Inventory {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JdbcInventory.class);
|
||||
|
||||
private final Map<Long, Map<InventoryVector, Long>> cache = new ConcurrentHashMap<>();
|
||||
|
||||
public JdbcInventory(JdbcConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InventoryVector> getInventory(long... streams) {
|
||||
List<InventoryVector> result = new LinkedList<>();
|
||||
for (long stream : streams) {
|
||||
getCache(stream).entrySet().stream()
|
||||
.filter(e -> e.getValue() > now())
|
||||
.forEach(e -> result.add(e.getKey()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<InventoryVector, Long> getCache(long stream) {
|
||||
Map<InventoryVector, Long> result = cache.get(stream);
|
||||
if (result == null) {
|
||||
synchronized (cache) {
|
||||
if (cache.get(stream) == null) {
|
||||
result = new ConcurrentHashMap<>();
|
||||
cache.put(stream, result);
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT hash, expires FROM Inventory " +
|
||||
"WHERE expires > " + (now() - 5 * MINUTE) + " AND stream = " + stream)
|
||||
) {
|
||||
while (rs.next()) {
|
||||
result.put(InventoryVector.fromHash(rs.getBytes("hash")), rs.getLong("expires"));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InventoryVector> getMissing(List<InventoryVector> offer, long... streams) {
|
||||
for (long stream : streams) {
|
||||
offer.removeAll(getCache(stream).keySet());
|
||||
}
|
||||
return offer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectMessage getObject(InventoryVector vector) {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = X'" + vector + "'")
|
||||
) {
|
||||
if (rs.next()) {
|
||||
Blob data = rs.getBlob("data");
|
||||
return Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length());
|
||||
} else {
|
||||
LOG.info("Object requested that we don't have. IV: " + vector);
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ObjectMessage> getObjects(long stream, long version, ObjectType... types) {
|
||||
StringBuilder query = new StringBuilder("SELECT data, version FROM Inventory WHERE 1=1");
|
||||
if (stream > 0) {
|
||||
query.append(" AND stream = ").append(stream);
|
||||
}
|
||||
if (version > 0) {
|
||||
query.append(" AND version = ").append(version);
|
||||
}
|
||||
if (types.length > 0) {
|
||||
query.append(" AND type IN (").append(join(types)).append(')');
|
||||
}
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(query.toString())
|
||||
) {
|
||||
List<ObjectMessage> result = new LinkedList<>();
|
||||
while (rs.next()) {
|
||||
Blob data = rs.getBlob("data");
|
||||
result.add(Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()));
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeObject(ObjectMessage object) {
|
||||
if (getCache(object.getStream()).containsKey(object.getInventoryVector()))
|
||||
return;
|
||||
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
PreparedStatement ps = connection.prepareStatement("INSERT INTO Inventory " +
|
||||
"(hash, stream, expires, data, type, version) VALUES (?, ?, ?, ?, ?, ?)")
|
||||
) {
|
||||
InventoryVector iv = object.getInventoryVector();
|
||||
LOG.trace("Storing object " + iv);
|
||||
ps.setBytes(1, iv.getHash());
|
||||
ps.setLong(2, object.getStream());
|
||||
ps.setLong(3, object.getExpiresTime());
|
||||
writeBlob(ps, 4, object);
|
||||
ps.setLong(5, object.getType());
|
||||
ps.setLong(6, object.getVersion());
|
||||
ps.executeUpdate();
|
||||
getCache(object.getStream()).put(iv, object.getExpiresTime());
|
||||
} catch (SQLException e) {
|
||||
LOG.debug("Error storing object of type " + object.getPayload().getClass().getSimpleName(), e);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(ObjectMessage object) {
|
||||
return getCache(object.getStream()).entrySet().stream()
|
||||
.anyMatch(x -> x.getKey().equals(object.getInventoryVector()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement()
|
||||
) {
|
||||
stmt.executeUpdate("DELETE FROM Inventory WHERE expires < " + (now() - 5 * MINUTE));
|
||||
} catch (SQLException e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
for (Map<InventoryVector, Long> c : cache.values()) {
|
||||
c.entrySet().removeIf(e -> e.getValue() < (now() - 5 * MINUTE));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,201 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||
import ch.dissem.bitmessage.ports.NodeRegistry;
|
||||
import ch.dissem.bitmessage.utils.Collections;
|
||||
import ch.dissem.bitmessage.utils.SqlStrings;
|
||||
import ch.dissem.bitmessage.utils.Strings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static ch.dissem.bitmessage.ports.NodeRegistryHelper.loadStableNodes;
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.*;
|
||||
|
||||
public class JdbcNodeRegistry extends JdbcHelper implements NodeRegistry {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JdbcNodeRegistry.class);
|
||||
private Map<Long, Set<NetworkAddress>> stableNodes;
|
||||
|
||||
public JdbcNodeRegistry(JdbcConfig config) {
|
||||
super(config);
|
||||
cleanUp();
|
||||
}
|
||||
|
||||
private void cleanUp() {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
PreparedStatement ps = connection.prepareStatement(
|
||||
"DELETE FROM Node WHERE time<?")
|
||||
) {
|
||||
ps.setLong(1, now() - 28 * DAY);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private NetworkAddress loadExisting(NetworkAddress node) {
|
||||
String query =
|
||||
"SELECT stream, address, port, services, time" +
|
||||
" FROM Node" +
|
||||
" WHERE stream = " + node.getStream() +
|
||||
" AND address = X'" + Strings.hex(node.getIPv6()) + "'" +
|
||||
" AND port = " + node.getPort();
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(query)
|
||||
) {
|
||||
if (rs.next()) {
|
||||
return new NetworkAddress.Builder()
|
||||
.stream(rs.getLong("stream"))
|
||||
.ipv6(rs.getBytes("address"))
|
||||
.port(rs.getInt("port"))
|
||||
.services(rs.getLong("services"))
|
||||
.time(rs.getLong("time"))
|
||||
.build();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
PreparedStatement ps = connection.prepareStatement(
|
||||
"DELETE FROM Node")
|
||||
) {
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
|
||||
List<NetworkAddress> result = new LinkedList<>();
|
||||
String query =
|
||||
"SELECT stream, address, port, services, time" +
|
||||
" FROM Node WHERE stream IN (" + SqlStrings.join(streams) + ")" +
|
||||
" ORDER BY TIME DESC" +
|
||||
" LIMIT " + limit;
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(query)
|
||||
) {
|
||||
while (rs.next()) {
|
||||
result.add(
|
||||
new NetworkAddress.Builder()
|
||||
.stream(rs.getLong("stream"))
|
||||
.ipv6(rs.getBytes("address"))
|
||||
.port(rs.getInt("port"))
|
||||
.services(rs.getLong("services"))
|
||||
.time(rs.getLong("time"))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
if (result.isEmpty()) {
|
||||
synchronized (this) {
|
||||
if (stableNodes == null) {
|
||||
stableNodes = loadStableNodes();
|
||||
}
|
||||
}
|
||||
for (long stream : streams) {
|
||||
Set<NetworkAddress> nodes = stableNodes.get(stream);
|
||||
if (nodes != null && !nodes.isEmpty()) {
|
||||
result.add(Collections.selectRandom(nodes));
|
||||
}
|
||||
}
|
||||
if (result.isEmpty()) {
|
||||
// There might have been an error resolving domain names due to a missing internet exception.
|
||||
// Try to load the stable nodes again next time.
|
||||
stableNodes = null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offerAddresses(List<NetworkAddress> nodes) {
|
||||
cleanUp();
|
||||
nodes.stream()
|
||||
.filter(node -> node.getTime() < now() + 2 * MINUTE && node.getTime() > now() - 28 * DAY)
|
||||
.forEach(node -> {
|
||||
synchronized (this) {
|
||||
NetworkAddress existing = loadExisting(node);
|
||||
if (existing == null) {
|
||||
insert(node);
|
||||
} else if (node.getTime() > existing.getTime()) {
|
||||
update(node);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void insert(NetworkAddress node) {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
PreparedStatement ps = connection.prepareStatement(
|
||||
"INSERT INTO Node (stream, address, port, services, time) " +
|
||||
"VALUES (?, ?, ?, ?, ?)")
|
||||
) {
|
||||
ps.setLong(1, node.getStream());
|
||||
ps.setBytes(2, node.getIPv6());
|
||||
ps.setInt(3, node.getPort());
|
||||
ps.setLong(4, node.getServices());
|
||||
ps.setLong(5, node.getTime());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void update(NetworkAddress node) {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
PreparedStatement ps = connection.prepareStatement(
|
||||
"UPDATE Node SET services=?, time=? WHERE stream=? AND address=? AND port=?")
|
||||
) {
|
||||
ps.setLong(1, node.getServices());
|
||||
ps.setLong(2, node.getTime());
|
||||
ps.setLong(3, node.getStream());
|
||||
ps.setBytes(4, node.getIPv6());
|
||||
ps.setInt(5, node.getPort());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,150 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository;
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
|
||||
import ch.dissem.bitmessage.utils.Strings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWorkRepository, InternalContext.ContextHolder {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JdbcProofOfWorkRepository.class);
|
||||
private InternalContext ctx;
|
||||
|
||||
public JdbcProofOfWorkRepository(JdbcConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item getItem(byte[] initialHash) {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
PreparedStatement ps = connection.prepareStatement("SELECT data, version, nonce_trials_per_byte, " +
|
||||
"extra_bytes, expiration_time, message_id FROM POW WHERE initial_hash=?")
|
||||
) {
|
||||
ps.setBytes(1, initialHash);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
Blob data = rs.getBlob("data");
|
||||
if (rs.getObject("message_id") == null) {
|
||||
return new Item(
|
||||
Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()),
|
||||
rs.getLong("nonce_trials_per_byte"),
|
||||
rs.getLong("extra_bytes")
|
||||
);
|
||||
} else {
|
||||
return new Item(
|
||||
Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()),
|
||||
rs.getLong("nonce_trials_per_byte"),
|
||||
rs.getLong("extra_bytes"),
|
||||
rs.getLong("expiration_time"),
|
||||
ctx.getMessageRepository().getMessage(rs.getLong("message_id"))
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Object requested that we don't have. Initial hash: " + Strings.hex(initialHash));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<byte[]> getItems() {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT initial_hash FROM POW")
|
||||
) {
|
||||
List<byte[]> result = new LinkedList<>();
|
||||
while (rs.next()) {
|
||||
result.add(rs.getBytes("initial_hash"));
|
||||
}
|
||||
return result;
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putObject(Item item) {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
PreparedStatement ps = connection.prepareStatement("INSERT INTO POW (initial_hash, data, version, " +
|
||||
"nonce_trials_per_byte, extra_bytes, expiration_time, message_id) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?)")
|
||||
) {
|
||||
ps.setBytes(1, cryptography().getInitialHash(item.getObjectMessage()));
|
||||
writeBlob(ps, 2, item.getObjectMessage());
|
||||
ps.setLong(3, item.getObjectMessage().getVersion());
|
||||
ps.setLong(4, item.getNonceTrialsPerByte());
|
||||
ps.setLong(5, item.getExtraBytes());
|
||||
|
||||
if (item.getMessage() == null) {
|
||||
ps.setObject(6, null);
|
||||
ps.setObject(7, null);
|
||||
} else {
|
||||
ps.setLong(6, item.getExpirationTime());
|
||||
ps.setLong(7, (Long) item.getMessage().getId());
|
||||
}
|
||||
ps.executeUpdate();
|
||||
} catch (IOException | SQLException e) {
|
||||
LOG.debug("Error storing object of type " + item.getObjectMessage().getPayload().getClass().getSimpleName(), e);
|
||||
throw new ApplicationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
|
||||
putObject(new Item(object, nonceTrialsPerByte, extraBytes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeObject(byte[] initialHash) {
|
||||
try (
|
||||
Connection connection = config.getConnection();
|
||||
PreparedStatement ps = connection.prepareStatement("DELETE FROM POW WHERE initial_hash=?")
|
||||
) {
|
||||
ps.setBytes(1, initialHash);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(InternalContext context) {
|
||||
this.ctx = context;
|
||||
}
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||
import ch.dissem.bitmessage.entity.payload.V3Pubkey
|
||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||
import ch.dissem.bitmessage.factory.Factory
|
||||
import ch.dissem.bitmessage.ports.AddressRepository
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.sql.PreparedStatement
|
||||
import java.sql.SQLException
|
||||
import java.util.*
|
||||
|
||||
class JdbcAddressRepository(config: JdbcConfig) : JdbcHelper(config), AddressRepository {
|
||||
|
||||
override fun findContact(ripeOrTag: ByteArray) = find("private_key is null").firstOrNull {
|
||||
if (it.version > 3) {
|
||||
Arrays.equals(ripeOrTag, it.tag)
|
||||
} else {
|
||||
Arrays.equals(ripeOrTag, it.ripe)
|
||||
}
|
||||
}
|
||||
|
||||
override fun findIdentity(ripeOrTag: ByteArray) = find("private_key is not null").firstOrNull {
|
||||
if (it.version > 3) {
|
||||
Arrays.equals(ripeOrTag, it.tag)
|
||||
} else {
|
||||
Arrays.equals(ripeOrTag, it.ripe)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIdentities() = find("private_key IS NOT NULL")
|
||||
|
||||
override fun getChans() = find("chan = '1'")
|
||||
|
||||
override fun getSubscriptions() = find("subscribed = '1'")
|
||||
|
||||
override fun getSubscriptions(broadcastVersion: Long): List<BitmessageAddress> = if (broadcastVersion > 4) {
|
||||
find("subscribed = '1' AND version > 3")
|
||||
} else {
|
||||
find("subscribed = '1' AND version <= 3")
|
||||
}
|
||||
|
||||
override fun getContacts() = find("private_key IS NULL OR chan = '1'")
|
||||
|
||||
private fun find(where: String): List<BitmessageAddress> {
|
||||
val result = LinkedList<BitmessageAddress>()
|
||||
try {
|
||||
config.getConnection().use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery("""
|
||||
SELECT address, alias, public_key, private_key, subscribed, chan
|
||||
FROM Address
|
||||
WHERE $where
|
||||
""").use { rs ->
|
||||
while (rs.next()) {
|
||||
val address: BitmessageAddress
|
||||
|
||||
val privateKeyStream = rs.getBinaryStream("private_key")
|
||||
if (privateKeyStream == null) {
|
||||
address = BitmessageAddress(rs.getString("address"))
|
||||
rs.getBlob("public_key")?.let { publicKeyBlob ->
|
||||
var pubkey: Pubkey = Factory.readPubkey(address.version, address.stream,
|
||||
publicKeyBlob.binaryStream, publicKeyBlob.length().toInt(), false)!!
|
||||
if (address.version == 4L && pubkey is V3Pubkey) {
|
||||
pubkey = V4Pubkey(pubkey)
|
||||
}
|
||||
address.pubkey = pubkey
|
||||
}
|
||||
} else {
|
||||
val privateKey = PrivateKey.read(privateKeyStream)
|
||||
address = BitmessageAddress(privateKey)
|
||||
}
|
||||
address.alias = rs.getString("alias")
|
||||
address.isSubscribed = rs.getBoolean("subscribed")
|
||||
address.isChan = rs.getBoolean("chan")
|
||||
|
||||
result.add(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun exists(address: BitmessageAddress): Boolean {
|
||||
config.getConnection().use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery("SELECT '1' FROM Address " +
|
||||
"WHERE address='" + address.address + "'").use { rs -> return rs.next() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun save(address: BitmessageAddress) {
|
||||
try {
|
||||
if (exists(address)) {
|
||||
update(address)
|
||||
} else {
|
||||
insert(address)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
LOG.error(e.message, e)
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun update(address: BitmessageAddress) {
|
||||
val statement = StringBuilder("UPDATE Address SET alias=?")
|
||||
if (address.pubkey != null) {
|
||||
statement.append(", public_key=?")
|
||||
}
|
||||
if (address.privateKey != null) {
|
||||
statement.append(", private_key=?")
|
||||
}
|
||||
statement.append(", subscribed=?, chan=? WHERE address=?")
|
||||
config.getConnection().use { connection ->
|
||||
connection.prepareStatement(statement.toString()).use { ps ->
|
||||
var i = 0
|
||||
ps.setString(++i, address.alias)
|
||||
if (address.pubkey != null) {
|
||||
writePubkey(ps, ++i, address.pubkey)
|
||||
}
|
||||
if (address.privateKey != null) {
|
||||
JdbcHelper.writeBlob(ps, ++i, address.privateKey)
|
||||
}
|
||||
ps.setBoolean(++i, address.isSubscribed)
|
||||
ps.setBoolean(++i, address.isChan)
|
||||
ps.setString(++i, address.address)
|
||||
ps.executeUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun insert(address: BitmessageAddress) {
|
||||
config.getConnection().use { connection ->
|
||||
connection.prepareStatement(
|
||||
"INSERT INTO Address (address, version, alias, public_key, private_key, subscribed, chan) " + "VALUES (?, ?, ?, ?, ?, ?, ?)").use { ps ->
|
||||
ps.setString(1, address.address)
|
||||
ps.setLong(2, address.version)
|
||||
ps.setString(3, address.alias)
|
||||
writePubkey(ps, 4, address.pubkey)
|
||||
JdbcHelper.writeBlob(ps, 5, address.privateKey)
|
||||
ps.setBoolean(6, address.isSubscribed)
|
||||
ps.setBoolean(7, address.isChan)
|
||||
ps.executeUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun writePubkey(ps: PreparedStatement, parameterIndex: Int, data: Pubkey?) {
|
||||
if (data != null) {
|
||||
val out = ByteArrayOutputStream()
|
||||
data.writeUnencrypted(out)
|
||||
ps.setBytes(parameterIndex, out.toByteArray())
|
||||
} else {
|
||||
ps.setBytes(parameterIndex, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun remove(address: BitmessageAddress) {
|
||||
try {
|
||||
config.getConnection().use { connection -> connection.createStatement().use { stmt -> stmt.executeUpdate("DELETE FROM Address WHERE address = '" + address.address + "'") } }
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun getAddress(address: String): BitmessageAddress? {
|
||||
val result = find("address = '$address'")
|
||||
if (result.isNotEmpty()) return result[0]
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(JdbcAddressRepository::class.java)
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository
|
||||
|
||||
import org.flywaydb.core.Flyway
|
||||
import java.sql.DriverManager
|
||||
|
||||
/**
|
||||
* The base configuration for all JDBC based repositories. You should only make one instance,
|
||||
* as flyway initializes/updates the database at object creation.
|
||||
*/
|
||||
open class JdbcConfig @JvmOverloads constructor(
|
||||
protected val dbUrl: String = "jdbc:h2:~/jabit;AUTO_SERVER=TRUE",
|
||||
protected val dbUser: String = "sa",
|
||||
protected val dbPassword: String? = null
|
||||
) {
|
||||
protected val flyway = Flyway()
|
||||
|
||||
init {
|
||||
flyway.setDataSource(dbUrl, dbUser, dbPassword)
|
||||
flyway.migrate()
|
||||
}
|
||||
|
||||
fun getConnection() = DriverManager.getConnection(dbUrl, dbUser, dbPassword)
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.sql.PreparedStatement
|
||||
|
||||
/**
|
||||
* Helper class that does Flyway migration, provides JDBC connections and some helper methods.
|
||||
*/
|
||||
abstract class JdbcHelper protected constructor(protected val config: JdbcConfig) {
|
||||
companion object {
|
||||
@JvmStatic fun writeBlob(ps: PreparedStatement, parameterIndex: Int, data: Streamable?) {
|
||||
if (data == null) {
|
||||
ps.setBytes(parameterIndex, null)
|
||||
} else {
|
||||
val os = ByteArrayOutputStream()
|
||||
data.write(os)
|
||||
ps.setBytes(parameterIndex, os.toByteArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.factory.Factory
|
||||
import ch.dissem.bitmessage.ports.Inventory
|
||||
import ch.dissem.bitmessage.utils.SqlStrings.join
|
||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.sql.SQLException
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class JdbcInventory(config: JdbcConfig) : JdbcHelper(config), Inventory {
|
||||
|
||||
private val cache = ConcurrentHashMap<Long, MutableMap<InventoryVector, Long>>()
|
||||
|
||||
override fun getInventory(vararg streams: Long): List<InventoryVector> {
|
||||
val result = LinkedList<InventoryVector>()
|
||||
for (stream in streams) {
|
||||
getCache(stream).entries.stream()
|
||||
.filter { e -> e.value > now }
|
||||
.forEach { e -> result.add(e.key) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getCache(stream: Long): MutableMap<InventoryVector, Long> {
|
||||
var result: MutableMap<InventoryVector, Long>? = cache[stream]
|
||||
if (result == null) {
|
||||
synchronized(cache) {
|
||||
if (cache[stream] == null) {
|
||||
val map = ConcurrentHashMap<InventoryVector, Long>()
|
||||
cache.put(stream, map)
|
||||
result = map
|
||||
try {
|
||||
config.getConnection().use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery("SELECT hash, expires FROM Inventory " +
|
||||
"WHERE expires > " + (now - 5 * MINUTE) + " AND stream = " + stream).use { rs ->
|
||||
while (rs.next()) {
|
||||
map.put(InventoryVector(rs.getBytes("hash")), rs.getLong("expires"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result!!
|
||||
}
|
||||
|
||||
override fun getMissing(offer: List<InventoryVector>, vararg streams: Long): List<InventoryVector> = offer - streams.flatMap { getCache(it).keys }
|
||||
|
||||
override fun getObject(vector: InventoryVector): ObjectMessage? {
|
||||
config.getConnection().use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = X'$vector'").use { rs ->
|
||||
if (rs.next()) {
|
||||
val data = rs.getBlob("data")
|
||||
return Factory.getObjectMessage(rs.getInt("version"), data.binaryStream, data.length().toInt())
|
||||
} else {
|
||||
LOG.info("Object requested that we don't have. IV: " + vector)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getObjects(stream: Long, version: Long, vararg types: ObjectType): List<ObjectMessage> {
|
||||
val query = StringBuilder("SELECT data, version FROM Inventory WHERE 1=1")
|
||||
if (stream > 0) {
|
||||
query.append(" AND stream = ").append(stream)
|
||||
}
|
||||
if (version > 0) {
|
||||
query.append(" AND version = ").append(version)
|
||||
}
|
||||
if (types.isNotEmpty()) {
|
||||
query.append(" AND type IN (").append(join(*types)).append(')')
|
||||
}
|
||||
config.getConnection().use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery(query.toString()).use { rs ->
|
||||
val result = LinkedList<ObjectMessage>()
|
||||
while (rs.next()) {
|
||||
val data = rs.getBlob("data")
|
||||
result.add(Factory.getObjectMessage(rs.getInt("version"), data.binaryStream, data.length().toInt())!!)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun storeObject(objectMessage: ObjectMessage) {
|
||||
if (getCache(objectMessage.stream).containsKey(objectMessage.inventoryVector))
|
||||
return
|
||||
|
||||
try {
|
||||
config.getConnection().use { connection ->
|
||||
connection.prepareStatement("INSERT INTO Inventory " + "(hash, stream, expires, data, type, version) VALUES (?, ?, ?, ?, ?, ?)").use { ps ->
|
||||
val iv = objectMessage.inventoryVector
|
||||
LOG.trace("Storing object " + iv)
|
||||
ps.setBytes(1, iv.hash)
|
||||
ps.setLong(2, objectMessage.stream)
|
||||
ps.setLong(3, objectMessage.expiresTime)
|
||||
JdbcHelper.Companion.writeBlob(ps, 4, objectMessage)
|
||||
ps.setLong(5, objectMessage.type)
|
||||
ps.setLong(6, objectMessage.version)
|
||||
ps.executeUpdate()
|
||||
getCache(objectMessage.stream).put(iv, objectMessage.expiresTime)
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.debug("Error storing object of type " + objectMessage.payload.javaClass.simpleName, e)
|
||||
} catch (e: Exception) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun contains(objectMessage: ObjectMessage): Boolean {
|
||||
return getCache(objectMessage.stream).any { (key, _) -> key == objectMessage.inventoryVector }
|
||||
}
|
||||
|
||||
override fun cleanup() {
|
||||
try {
|
||||
config.getConnection().use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeUpdate("DELETE FROM Inventory WHERE expires < " + (now - 5 * MINUTE))
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.debug(e.message, e)
|
||||
}
|
||||
|
||||
for (c in cache.values) {
|
||||
c.entries.removeIf { e -> e.value < now - 5 * MINUTE }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(JdbcInventory::class.java)
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||
import ch.dissem.bitmessage.ports.AbstractMessageRepository
|
||||
import ch.dissem.bitmessage.ports.MessageRepository
|
||||
import ch.dissem.bitmessage.repository.JdbcHelper.writeBlob
|
||||
import ch.dissem.bitmessage.repository.JdbcHelper.Companion.writeBlob
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.sql.Connection
|
||||
@ -34,7 +34,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep
|
||||
|
||||
override fun findLabels(where: String): List<Label> {
|
||||
try {
|
||||
config.connection.use {
|
||||
config.getConnection().use {
|
||||
connection ->
|
||||
return findLabels(connection, where)
|
||||
}
|
||||
@ -66,7 +66,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep
|
||||
"SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name + "'))"
|
||||
|
||||
try {
|
||||
config.connection.use { connection ->
|
||||
config.getConnection().use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery("SELECT count(*) FROM Message WHERE $where").use { rs ->
|
||||
if (rs.next()) {
|
||||
@ -84,7 +84,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep
|
||||
override fun find(where: String): List<Plaintext> {
|
||||
val result = LinkedList<Plaintext>()
|
||||
try {
|
||||
config.connection.use { connection ->
|
||||
config.getConnection().use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery(
|
||||
"""SELECT id, iv, type, sender, recipient, data, ack_data, sent, received, initial_hash, status, ttl, retries, next_try, conversation
|
||||
@ -100,13 +100,13 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep
|
||||
builder.from(ctx.addressRepository.getAddress(rs.getString("sender"))!!)
|
||||
builder.to(ctx.addressRepository.getAddress(rs.getString("recipient")))
|
||||
builder.ackData(rs.getBytes("ack_data"))
|
||||
builder.sent(rs.getObject("sent", Long::class.java))
|
||||
builder.received(rs.getObject("received", Long::class.java))
|
||||
builder.sent(rs.getObject("sent") as Long?)
|
||||
builder.received(rs.getObject("received") as Long?)
|
||||
builder.status(Plaintext.Status.valueOf(rs.getString("status")))
|
||||
builder.ttl(rs.getLong("ttl"))
|
||||
builder.retries(rs.getInt("retries"))
|
||||
builder.nextTry(rs.getObject("next_try", Long::class.java))
|
||||
builder.conversation(rs.getObject("conversation", UUID::class.java))
|
||||
builder.nextTry(rs.getObject("next_try") as Long?)
|
||||
builder.conversation(rs.getObject("conversation") as UUID? ?: UUID.randomUUID())
|
||||
builder.labels(findLabels(connection,
|
||||
"id IN (SELECT label_id FROM Message_Label WHERE message_id=$id) ORDER BY ord"))
|
||||
val message = builder.build()
|
||||
@ -144,7 +144,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep
|
||||
saveContactIfNecessary(message.from)
|
||||
saveContactIfNecessary(message.to)
|
||||
|
||||
config.connection.use { connection ->
|
||||
config.getConnection().use { connection ->
|
||||
try {
|
||||
connection.autoCommit = false
|
||||
save(connection, message)
|
||||
@ -261,7 +261,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep
|
||||
|
||||
override fun remove(message: Plaintext) {
|
||||
try {
|
||||
config.connection.use { connection ->
|
||||
config.getConnection().use { connection ->
|
||||
connection.autoCommit = false
|
||||
try {
|
||||
connection.createStatement().use { stmt ->
|
||||
@ -294,7 +294,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep
|
||||
}
|
||||
val result = LinkedList<UUID>()
|
||||
try {
|
||||
config.connection.use { connection ->
|
||||
config.getConnection().use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery(
|
||||
"SELECT DISTINCT conversation FROM Message WHERE " + where).use { rs ->
|
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2016 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
|
||||
import ch.dissem.bitmessage.ports.NodeRegistry
|
||||
import ch.dissem.bitmessage.ports.NodeRegistryHelper.loadStableNodes
|
||||
import ch.dissem.bitmessage.utils.Collections
|
||||
import ch.dissem.bitmessage.utils.SqlStrings
|
||||
import ch.dissem.bitmessage.utils.Strings
|
||||
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.sql.SQLException
|
||||
import java.util.*
|
||||
|
||||
class JdbcNodeRegistry(config: JdbcConfig) : JdbcHelper(config), NodeRegistry {
|
||||
private var stableNodes: Map<Long, Set<NetworkAddress>> = emptyMap()
|
||||
get() {
|
||||
if (field.isEmpty())
|
||||
field = loadStableNodes()
|
||||
return field
|
||||
}
|
||||
|
||||
init {
|
||||
cleanUp()
|
||||
}
|
||||
|
||||
private fun cleanUp() {
|
||||
try {
|
||||
config.getConnection().use { connection ->
|
||||
connection.prepareStatement("DELETE FROM Node WHERE time<?").use { ps ->
|
||||
ps.setLong(1, now - 28 * DAY)
|
||||
ps.executeUpdate()
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadExisting(node: NetworkAddress): NetworkAddress? {
|
||||
val query = """
|
||||
SELECT stream, address, port, services, time
|
||||
FROM Node
|
||||
WHERE stream = ${node.stream} AND address = X'${Strings.hex(node.IPv6)}' AND port = ${node.port}
|
||||
"""
|
||||
config.getConnection().use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery(query).use { rs ->
|
||||
if (rs.next()) {
|
||||
return NetworkAddress.Builder()
|
||||
.stream(rs.getLong("stream"))
|
||||
.ipv6(rs.getBytes("address"))
|
||||
.port(rs.getInt("port"))
|
||||
.services(rs.getLong("services"))
|
||||
.time(rs.getLong("time"))
|
||||
.build()
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
try {
|
||||
config.getConnection().use { connection ->
|
||||
connection.prepareStatement("DELETE FROM Node").use { ps -> ps.executeUpdate() }
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress> {
|
||||
val result = LinkedList<NetworkAddress>()
|
||||
val query = """
|
||||
SELECT stream, address, port, services, time
|
||||
FROM Node
|
||||
WHERE stream IN (${SqlStrings.join(*streams)})
|
||||
ORDER BY TIME DESC LIMIT $limit
|
||||
"""
|
||||
config.getConnection().use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery(query).use { rs ->
|
||||
while (rs.next()) {
|
||||
result.add(
|
||||
NetworkAddress.Builder()
|
||||
.stream(rs.getLong("stream"))
|
||||
.ipv6(rs.getBytes("address"))
|
||||
.port(rs.getInt("port"))
|
||||
.services(rs.getLong("services"))
|
||||
.time(rs.getLong("time"))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.isEmpty()) {
|
||||
streams
|
||||
.asSequence()
|
||||
.mapNotNull { stableNodes[it] }
|
||||
.filter { it.isNotEmpty() }
|
||||
.mapTo(result) { Collections.selectRandom(it) }
|
||||
if (result.isEmpty()) {
|
||||
// There might have been an error resolving domain names due to a missing internet connection.
|
||||
// Try to load the stable nodes again next time.
|
||||
stableNodes = emptyMap()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun offerAddresses(nodes: List<NetworkAddress>) {
|
||||
cleanUp()
|
||||
nodes.stream()
|
||||
.filter { (time) -> time < now + 2 * MINUTE && time > now - 28 * DAY }
|
||||
.forEach { node ->
|
||||
synchronized(this) {
|
||||
val existing = loadExisting(node)
|
||||
if (existing == null) {
|
||||
insert(node)
|
||||
} else if (node.time > existing.time) {
|
||||
update(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun insert(node: NetworkAddress) {
|
||||
try {
|
||||
config.getConnection().use { connection ->
|
||||
connection.prepareStatement(
|
||||
"INSERT INTO Node (stream, address, port, services, time) VALUES (?, ?, ?, ?, ?)").use { ps ->
|
||||
ps.setLong(1, node.stream)
|
||||
ps.setBytes(2, node.IPv6)
|
||||
ps.setInt(3, node.port)
|
||||
ps.setLong(4, node.services)
|
||||
ps.setLong(5, node.time)
|
||||
ps.executeUpdate()
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun update(node: NetworkAddress) {
|
||||
try {
|
||||
config.getConnection().use { connection ->
|
||||
connection.prepareStatement(
|
||||
"UPDATE Node SET services=?, time=? WHERE stream=? AND address=? AND port=?").use { ps ->
|
||||
ps.setLong(1, node.services)
|
||||
ps.setLong(2, node.time)
|
||||
ps.setLong(3, node.stream)
|
||||
ps.setBytes(4, node.IPv6)
|
||||
ps.setInt(5, node.port)
|
||||
ps.executeUpdate()
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(JdbcNodeRegistry::class.java)
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2016 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||
import ch.dissem.bitmessage.factory.Factory
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import ch.dissem.bitmessage.utils.Strings
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.sql.SQLException
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
class JdbcProofOfWorkRepository(config: JdbcConfig) : JdbcHelper(config), ProofOfWorkRepository, InternalContext.ContextHolder {
|
||||
private lateinit var ctx: InternalContext
|
||||
|
||||
override fun getItem(initialHash: ByteArray): ProofOfWorkRepository.Item {
|
||||
config.getConnection().use { connection ->
|
||||
connection.prepareStatement("""
|
||||
SELECT data, version, nonce_trials_per_byte, extra_bytes, expiration_time, message_id
|
||||
FROM POW
|
||||
WHERE initial_hash=?
|
||||
""").use { ps ->
|
||||
ps.setBytes(1, initialHash)
|
||||
ps.executeQuery().use { rs ->
|
||||
if (rs.next()) {
|
||||
val data = rs.getBlob("data")
|
||||
if (rs.getObject("message_id") == null) {
|
||||
return ProofOfWorkRepository.Item(
|
||||
Factory.getObjectMessage(rs.getInt("version"), data.binaryStream, data.length().toInt())!!,
|
||||
rs.getLong("nonce_trials_per_byte"),
|
||||
rs.getLong("extra_bytes")
|
||||
)
|
||||
} else {
|
||||
return ProofOfWorkRepository.Item(
|
||||
Factory.getObjectMessage(rs.getInt("version"), data.binaryStream, data.length().toInt())!!,
|
||||
rs.getLong("nonce_trials_per_byte"),
|
||||
rs.getLong("extra_bytes"),
|
||||
rs.getLong("expiration_time"),
|
||||
ctx.messageRepository.getMessage(rs.getLong("message_id"))
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw IllegalArgumentException("Object requested that we don't have. Initial hash: " + Strings.hex(initialHash))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItems(): List<ByteArray> {
|
||||
config.getConnection().use { connection ->
|
||||
connection.createStatement().use { stmt ->
|
||||
stmt.executeQuery("SELECT initial_hash FROM POW").use { rs ->
|
||||
val result = mutableListOf<ByteArray>()
|
||||
while (rs.next()) {
|
||||
result.add(rs.getBytes("initial_hash"))
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun putObject(item: ProofOfWorkRepository.Item) {
|
||||
config.getConnection().use { connection ->
|
||||
connection.prepareStatement("""
|
||||
INSERT INTO
|
||||
POW (initial_hash, data, version, nonce_trials_per_byte, extra_bytes, expiration_time, message_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)""").use { ps ->
|
||||
ps.setBytes(1, cryptography().getInitialHash(item.objectMessage))
|
||||
JdbcHelper.Companion.writeBlob(ps, 2, item.objectMessage)
|
||||
ps.setLong(3, item.objectMessage.version)
|
||||
ps.setLong(4, item.nonceTrialsPerByte)
|
||||
ps.setLong(5, item.extraBytes)
|
||||
|
||||
if (item.message == null) {
|
||||
ps.setObject(6, null)
|
||||
ps.setObject(7, null)
|
||||
} else {
|
||||
ps.setLong(6, item.expirationTime!!)
|
||||
ps.setLong(7, item.message!!.id as Long)
|
||||
}
|
||||
ps.executeUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
|
||||
putObject(ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes))
|
||||
}
|
||||
|
||||
override fun removeObject(initialHash: ByteArray) {
|
||||
try {
|
||||
config.getConnection().use { connection ->
|
||||
connection.prepareStatement("DELETE FROM POW WHERE initial_hash=?").use { ps ->
|
||||
ps.setBytes(1, initialHash)
|
||||
ps.executeUpdate()
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
LOG.debug(e.message, e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setContext(context: InternalContext) {
|
||||
ctx = context
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(JdbcProofOfWorkRepository::class.java)
|
||||
}
|
||||
}
|
@ -42,7 +42,7 @@ public class TestJdbcConfig extends JdbcConfig {
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
flyway.clean();
|
||||
flyway.migrate();
|
||||
getFlyway().clean();
|
||||
getFlyway().migrate();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user