package net.sf.hajdbc.state.leveldb; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.iq80.leveldb.DB; import org.iq80.leveldb.DBFactory; import org.iq80.leveldb.DBIterator; import org.iq80.leveldb.Options; import org.iq80.leveldb.WriteBatch; import net.sf.hajdbc.DatabaseCluster; import net.sf.hajdbc.durability.DurabilityEvent; import net.sf.hajdbc.durability.DurabilityEventFactory; import net.sf.hajdbc.durability.InvocationEvent; import net.sf.hajdbc.durability.InvokerEvent; import net.sf.hajdbc.durability.InvokerResult; import net.sf.hajdbc.logging.Level; import net.sf.hajdbc.logging.Logger; import net.sf.hajdbc.logging.LoggerFactory; import net.sf.hajdbc.state.DatabaseEvent; import net.sf.hajdbc.state.DurabilityListenerAdapter; import net.sf.hajdbc.state.SerializedDurabilityListener; import net.sf.hajdbc.state.StateManager; import net.sf.hajdbc.util.Objects; public class LevelDBStateManager implements StateManager, SerializedDurabilityListener { private static final Logger logger = LoggerFactory.getLogger(LevelDBStateManager.class); private final DBFactory factory; private final File file; private final Options options; private final DurabilityEventFactory eventFactory; private final DurabilityListenerAdapter listener; private volatile DB stateDatabase; private volatile DB invokerDatabase; private volatile DB invocationDatabase; public LevelDBStateManager(DatabaseCluster<?, ?> cluster, DBFactory factory, File file, Options options) { this.factory = factory; this.file = file; this.options = options; this.eventFactory = cluster.getDurability(); this.listener = new DurabilityListenerAdapter(this, cluster.getTransactionIdentifierFactory(), this.eventFactory); } @Override public void start() throws SQLException { try { this.stateDatabase = this.factory.open(this.file, this.options); this.invokerDatabase = this.factory.open(this.file, this.options); this.invocationDatabase = this.factory.open(this.file, this.options); } catch (IOException e) { throw new SQLException(e); } } @Override public void stop() { try { this.stateDatabase.close(); this.invokerDatabase.close(); this.invocationDatabase.close(); } catch (IOException e) { logger.log(Level.WARN, e); } } @Override public boolean isEnabled() { return true; } @Override public void activated(DatabaseEvent event) { this.stateDatabase.put(event.getSource().getBytes(StandardCharsets.UTF_8), null); } @Override public void deactivated(DatabaseEvent event) { this.stateDatabase.delete(event.getSource().getBytes(StandardCharsets.UTF_8)); } @Override public Set<String> getActiveDatabases() { try (DBIterator entries = this.stateDatabase.iterator()) { Set<String> databases = new TreeSet<>(); while (entries.hasNext()) { databases.add(new String(entries.next().getKey(), StandardCharsets.UTF_8)); } return databases; } catch (IOException e) { throw new IllegalStateException(e); } } @Override public void setActiveDatabases(Set<String> databases) { try (WriteBatch batch = this.stateDatabase.createWriteBatch()) { for (Map.Entry<byte[], byte[]> entry: this.stateDatabase) { batch.delete(entry.getKey()); } for (String database: databases) { batch.put(database.getBytes(StandardCharsets.UTF_8), null); } this.stateDatabase.write(batch); } catch (IOException e) { throw new IllegalStateException(e); } } @Override public void beforeInvocation(byte[] transactionId, byte phase, byte exceptionType) { this.invocationDatabase.put(createKey(transactionId, phase), new byte[] { exceptionType }); } @Override public void afterInvocation(byte[] transactionId, byte phase) { try (WriteBatch batch = this.invokerDatabase.createWriteBatch()) { for (String database: new String[] { }) { batch.delete(createKey(transactionId, phase, database)); } this.invocationDatabase.delete(createKey(transactionId, phase)); } catch (IOException e) { throw new IllegalStateException(e); } } private static byte[] createKey(byte[] transactionId, byte phase) { byte[] key = Arrays.copyOf(transactionId, transactionId.length + 1); key[transactionId.length] = phase; return key; } @Override public void beforeInvoker(byte[] transactionId, byte phase, String databaseId) { this.invokerDatabase.put(createKey(transactionId, phase, databaseId), null); } @Override public void afterInvoker(byte[] transactionId, byte phase, String databaseId, byte[] result) { this.invokerDatabase.put(createKey(transactionId, phase, databaseId), result); } private static byte[] createKey(byte[] transactionId, byte phase, String database) { byte[] databaseBytes = database.getBytes(StandardCharsets.UTF_8); byte[] key = new byte[transactionId.length + 2 + databaseBytes.length]; key[0] = (byte) transactionId.length; for (int i = 0; i < transactionId.length; ++i) { key[1 + i] = databaseBytes[i]; } key[transactionId.length + 1] = phase; for (int i = 0; i < databaseBytes.length; ++i) { key[transactionId.length + 2 + i] = databaseBytes[i]; } return key; } @Override public Map<InvocationEvent, Map<String, InvokerEvent>> recover() { Map<InvocationEvent, Map<String, InvokerEvent>> map = new HashMap<>(); try (DBIterator entries = this.invocationDatabase.iterator()) { while (entries.hasNext()) { Map.Entry<byte[], byte[]> entry = entries.next(); byte[] key = entry.getKey(); byte[] txId = Arrays.copyOf(key, key.length - 1); byte phase = key[key.length - 1]; byte exceptionType = entry.getValue()[0]; map.put(this.listener.createInvocationEvent(txId, phase, exceptionType), new HashMap<String, InvokerEvent>()); } } catch (IOException e) { throw new IllegalStateException(e); } try (DBIterator entries = this.invokerDatabase.iterator()) { while (entries.hasNext()) { Map.Entry<byte[], byte[]> entry = entries.next(); byte[] key = entry.getKey(); byte[] txId = Arrays.copyOfRange(key, 1, key[0] + 1); byte phase = key[txId.length + 1]; String databaseId = new String(Arrays.copyOfRange(key, txId.length + 2, key.length), StandardCharsets.UTF_8); DurabilityEvent event = this.listener.createEvent(txId, phase); Map<String, InvokerEvent> invokers = map.get(event); if (invokers != null) { InvokerEvent invokerEvent = this.eventFactory.createInvokerEvent(event.getTransactionId(), event.getPhase(), databaseId); byte[] value = entry.getValue(); if (value != null) { invokerEvent.setResult(Objects.deserialize(value, InvokerResult.class)); } invokers.put(databaseId, invokerEvent); } } } catch (IOException e) { throw new IllegalStateException(e); } return map; } @Override public void beforeInvocation(InvocationEvent event) { this.listener.beforeInvocation(event); } @Override public void afterInvocation(InvocationEvent event) { this.listener.afterInvocation(event); } @Override public void beforeInvoker(InvokerEvent event) { this.listener.beforeInvoker(event); } @Override public void afterInvoker(InvokerEvent event) { this.listener.afterInvoker(event); } }