package com.limegroup.gnutella;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.limewire.collection.FixedsizeForgetfulHashMap;
import org.limewire.core.settings.SecuritySettings;
import org.limewire.inject.EagerSingleton;
import org.limewire.io.GUID;
import org.limewire.lifecycle.Asynchronous;
import org.limewire.lifecycle.Service;
import org.limewire.lifecycle.ServiceRegistry;
import org.limewire.lifecycle.ServiceStage;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import org.limewire.security.id.SecureIdStore;
import org.limewire.util.Base32;
import org.limewire.util.Clock;
import org.limewire.util.CommonUtils;
import com.google.inject.Inject;
@EagerSingleton
public class SecureIdDatabaseStore implements SecureIdStore, Service {
private static final Log LOG = LogFactory.getLog(SecureIdDatabaseStore.class);
private volatile DbStore store;
private final Map<GUID, byte[]> cache = new FixedsizeForgetfulHashMap<GUID, byte[]>(100);
private final Clock clock;
private volatile boolean resetDatabase;
@Inject
public SecureIdDatabaseStore(Clock clock) {
this.clock = clock;
}
@Inject
void register(ServiceRegistry serviceRegistry) {
serviceRegistry.register(this).in(ServiceStage.VERY_LATE);
}
@Override
public byte[] getLocalData() {
String value = SecuritySettings.SECURE_IDENTITY.get();
if (value.isEmpty()) {
return null;
}
return Base32.decode(value);
}
@Override
public void setLocalData(byte[] value) {
SecuritySettings.SECURE_IDENTITY.set(Base32.encode(value));
resetDatabase = true;
}
@Override
public String getServiceName() {
return "id db store";
}
@Override
public void initialize() {
}
@Override
@Asynchronous
public void start() {
try {
store = new DbStore(resetDatabase);
long aYearAgo = clock.now() - TimeUnit.DAYS.toMillis(365);
synchronized (store) {
store.deleteOlderThan(aYearAgo);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public void stop() {
store.stop();
}
@Override
public byte[] get(GUID key) {
synchronized (store) {
byte[] value = cache.get(key);
if (value != null) {
return value;
}
value = store.get(key);
if (value != null) {
// only store non-null values in memory cache to avoid valid
// items from being expelled, we might want to change this based
// on usage
cache.put(key, value);
}
return value;
}
}
@Override
public void put(GUID key, byte[] value) {
synchronized (store) {
boolean stored = store.put(key, value);
if (stored) {
cache.put(key, value);
}
}
}
class DbStore {
private final Connection connection;
private final PreparedStatement getStatement;
private final PreparedStatement putStatement;
private final PreparedStatement deleteStatement;
private final PreparedStatement updateStatement;
public DbStore(boolean dropDb) throws SQLException {
try {
Class.forName("org.hsqldb.jdbcDriver");
} catch (ClassNotFoundException e1) {
throw new RuntimeException(e1);
}
File dbFile = new File(CommonUtils.getUserSettingsDir(), "secure-ids");
String connectionUrl = "jdbc:hsqldb:file:" + dbFile.getAbsolutePath();
connection = DriverManager.getConnection(connectionUrl, "sa", "");
Statement statement = connection.createStatement();
try {
if (dropDb) {
statement.execute("drop table ids if exists");
}
statement.execute("create cached table ids (guid binary(16) primary key, timestamp bigint, data varbinary(250))");
} catch (SQLException se) {
LOG.debug("table already exists", se);
}
getStatement = connection.prepareStatement("select data from ids where guid = ?");
updateStatement = connection.prepareStatement("update ids set timestamp = ? where guid = ?");
putStatement = connection.prepareStatement("insert into ids values (?, ?, ?)");
deleteStatement = connection.prepareStatement("delete from ids where timestamp < ?");
}
public synchronized byte[] get(GUID key) {
try {
getStatement.setBytes(1, key.bytes());
ResultSet resultSet = getStatement.executeQuery();
while (resultSet.next()) {
byte[] value = resultSet.getBytes(1);
if (value != null) {
updateStatement.setLong(1, clock.now());
updateStatement.setBytes(2, key.bytes());
updateStatement.execute();
}
return value;
}
} catch (SQLException e) {
LOG.debug("error getting value", e);
}
return null;
}
public synchronized boolean put(GUID key, byte[] value) {
try {
putStatement.setBytes(1, key.bytes());
putStatement.setLong(2, clock.now());
putStatement.setBytes(3, value);
putStatement.execute();
return true;
} catch (SQLException e) {
LOG.debug("error putting value", e);
}
return false;
}
public synchronized void stop() {
try {
Statement statement = connection.createStatement();
statement.execute("SHUTDOWN");
} catch (SQLException e) {
LOG.debug("error shutting down", e);
}
}
public synchronized void deleteOlderThan(long timestamp) {
try {
deleteStatement.setLong(1, timestamp);
deleteStatement.execute();
} catch (SQLException e) {
LOG.debug("error deleting old entries", e);
}
}
}
}