Jabit/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java

188 lines
7.1 KiB
Java

/*
* 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(new InventoryVector(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));
}
}
}