/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.activemq.artemis.jdbc.store.journal;
import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
import org.apache.activemq.artemis.core.io.IOCriticalErrorListener;
import org.apache.activemq.artemis.core.io.SequentialFileFactory;
import org.apache.activemq.artemis.core.journal.EncoderPersister;
import org.apache.activemq.artemis.core.journal.EncodingSupport;
import org.apache.activemq.artemis.core.journal.IOCompletion;
import org.apache.activemq.artemis.core.journal.Journal;
import org.apache.activemq.artemis.core.journal.JournalLoadInformation;
import org.apache.activemq.artemis.core.journal.LoaderCallback;
import org.apache.activemq.artemis.core.persistence.Persister;
import org.apache.activemq.artemis.core.journal.PreparedTransactionInfo;
import org.apache.activemq.artemis.core.journal.RecordInfo;
import org.apache.activemq.artemis.core.journal.TransactionFailureCallback;
import org.apache.activemq.artemis.core.journal.impl.JournalFile;
import org.apache.activemq.artemis.core.journal.impl.SimpleWaitIOCallback;
import org.apache.activemq.artemis.core.server.ActiveMQScheduledComponent;
import org.apache.activemq.artemis.jdbc.store.drivers.AbstractJDBCDriver;
import org.apache.activemq.artemis.jdbc.store.sql.SQLProvider;
import org.jboss.logging.Logger;
public class JDBCJournalImpl extends AbstractJDBCDriver implements Journal {
private static final Logger logger = Logger.getLogger(JDBCJournalImpl.class);
// Sync Delay in ms
private static final int SYNC_DELAY = 5;
private static int USER_VERSION = 1;
private final List<JDBCJournalRecord> records;
private PreparedStatement insertJournalRecords;
private PreparedStatement selectJournalRecords;
private PreparedStatement countJournalRecords;
private PreparedStatement deleteJournalRecords;
private PreparedStatement deleteJournalTxRecords;
private boolean started;
private AtomicBoolean failed = new AtomicBoolean(false);
private JDBCJournalSync syncTimer;
private final Executor completeExecutor;
private final ScheduledExecutorService scheduledExecutorService;
// Track Tx Records
private Map<Long, TransactionHolder> transactions = new ConcurrentHashMap<>();
// Sequence ID for journal records
private final AtomicLong seq = new AtomicLong(0);
private final IOCriticalErrorListener criticalIOErrorListener;
public JDBCJournalImpl(DataSource dataSource,
SQLProvider provider,
String tableName,
ScheduledExecutorService scheduledExecutorService,
Executor completeExecutor,
IOCriticalErrorListener criticalIOErrorListener) {
super(dataSource, provider);
records = new ArrayList<>();
this.scheduledExecutorService = scheduledExecutorService;
this.completeExecutor = completeExecutor;
this.criticalIOErrorListener = criticalIOErrorListener;
}
public JDBCJournalImpl(String jdbcUrl,
String jdbcDriverClass,
SQLProvider sqlProvider,
ScheduledExecutorService scheduledExecutorService,
Executor completeExecutor,
IOCriticalErrorListener criticalIOErrorListener) {
super(sqlProvider, jdbcUrl, jdbcDriverClass);
records = new ArrayList<>();
this.scheduledExecutorService = scheduledExecutorService;
this.completeExecutor = completeExecutor;
this.criticalIOErrorListener = criticalIOErrorListener;
}
@Override
public void start() throws SQLException {
super.start();
syncTimer = new JDBCJournalSync(scheduledExecutorService, completeExecutor, SYNC_DELAY, TimeUnit.MILLISECONDS, this);
started = true;
}
@Override
public void flush() throws Exception {
}
@Override
protected void createSchema() throws SQLException {
createTable(sqlProvider.getCreateJournalTableSQL());
}
@Override
protected void prepareStatements() throws SQLException {
logger.tracef("preparing statements");
insertJournalRecords = connection.prepareStatement(sqlProvider.getInsertJournalRecordsSQL());
selectJournalRecords = connection.prepareStatement(sqlProvider.getSelectJournalRecordsSQL());
countJournalRecords = connection.prepareStatement(sqlProvider.getCountJournalRecordsSQL());
deleteJournalRecords = connection.prepareStatement(sqlProvider.getDeleteJournalRecordsSQL());
deleteJournalTxRecords = connection.prepareStatement(sqlProvider.getDeleteJournalTxRecordsSQL());
}
@Override
public void stop() throws SQLException {
stop(true);
}
public synchronized void stop(boolean sync) throws SQLException {
if (started) {
if (sync)
sync();
started = false;
super.stop();
}
}
@Override
public synchronized void destroy() throws Exception {
super.destroy();
stop();
}
public synchronized int sync() {
List<JDBCJournalRecord> recordRef;
synchronized (records) {
if (records.isEmpty()) {
return 0;
}
recordRef = new ArrayList<>(records);
records.clear();
}
if (!started || failed.get()) {
executeCallbacks(recordRef, false);
return 0;
}
// We keep a list of deleted records and committed tx (used for cleaning up old transaction data).
List<Long> deletedRecords = new ArrayList<>();
List<Long> committedTransactions = new ArrayList<>();
TransactionHolder holder;
try {
connection.setAutoCommit(false);
for (JDBCJournalRecord record : recordRef) {
if (logger.isTraceEnabled()) {
logger.trace("sync::preparing JDBC statment for " + record);
}
switch (record.getRecordType()) {
case JDBCJournalRecord.DELETE_RECORD:
// Standard SQL Delete Record, Non transactional delete
deletedRecords.add(record.getId());
record.writeDeleteRecord(deleteJournalRecords);
break;
case JDBCJournalRecord.ROLLBACK_RECORD:
// Roll back we remove all records associated with this TX ID. This query is always performed last.
deleteJournalTxRecords.setLong(1, record.getTxId());
deleteJournalTxRecords.addBatch();
break;
case JDBCJournalRecord.COMMIT_RECORD:
// We perform all the deletes and add the commit record in the same Database TX
holder = transactions.get(record.getTxId());
for (RecordInfo info : holder.recordsToDelete) {
deletedRecords.add(record.getId());
deleteJournalRecords.setLong(1, info.id);
deleteJournalRecords.addBatch();
}
record.writeRecord(insertJournalRecords);
committedTransactions.add(record.getTxId());
break;
default:
// Default we add a new record to the DB
record.writeRecord(insertJournalRecords);
break;
}
}
insertJournalRecords.executeBatch();
deleteJournalRecords.executeBatch();
deleteJournalTxRecords.executeBatch();
connection.commit();
if (logger.isTraceEnabled()) {
logger.trace("JDBC commit worked");
}
cleanupTxRecords(deletedRecords, committedTransactions);
executeCallbacks(recordRef, true);
return recordRef.size();
} catch (Exception e) {
handleException(recordRef, e);
return 0;
}
}
/** public for tests only, not through API */
public void handleException(List<JDBCJournalRecord> recordRef, Throwable e) {
logger.warn(e.getMessage(), e);
failed.set(true);
criticalIOErrorListener.onIOException(e, "Critical IO Error. Failed to process JDBC Record statements", null);
if (logger.isTraceEnabled()) {
logger.trace("Rolling back Transaction, just in case");
}
try {
connection.rollback();
} catch (Throwable rollback) {
logger.warn(rollback);
}
try {
connection.close();
} catch (Throwable rollback) {
logger.warn(rollback);
}
if (recordRef != null) {
executeCallbacks(recordRef, false);
}
}
/* We store Transaction reference in memory (once all records associated with a Tranascation are Deleted,
we remove the Tx Records (i.e. PREPARE, COMMIT). */
private synchronized void cleanupTxRecords(List<Long> deletedRecords, List<Long> committedTx) throws SQLException {
List<RecordInfo> iterableCopy;
List<TransactionHolder> iterableCopyTx = new ArrayList<>();
iterableCopyTx.addAll(transactions.values());
for (Long txId : committedTx) {
transactions.get(txId).committed = true;
}
// TODO (mtaylor) perhaps we could store a reverse mapping of IDs to prevent this O(n) loop
for (TransactionHolder h : iterableCopyTx) {
iterableCopy = new ArrayList<>();
iterableCopy.addAll(h.recordInfos);
for (RecordInfo info : iterableCopy) {
if (deletedRecords.contains(info.id)) {
h.recordInfos.remove(info);
}
}
if (h.recordInfos.isEmpty() && h.committed) {
deleteJournalTxRecords.setLong(1, h.transactionID);
deleteJournalTxRecords.addBatch();
transactions.remove(h.transactionID);
}
}
}
private void executeCallbacks(final List<JDBCJournalRecord> records, final boolean success) {
Runnable r = new Runnable() {
@Override
public void run() {
for (JDBCJournalRecord record : records) {
if (logger.isTraceEnabled()) {
logger.trace("Calling callback " + record + " with success = " + success);
}
record.complete(success);
}
}
};
completeExecutor.execute(r);
}
private void checkStatus() {
checkStatus(null);
}
private void checkStatus(IOCompletion callback) {
if (!started) {
if (callback != null) callback.onError(-1, "JDBC Journal is not loaded");
throw new IllegalStateException("JDBCJournal is not loaded");
}
if (failed.get()) {
if (callback != null) callback.onError(-1, "JDBC Journal failed");
throw new IllegalStateException("JDBCJournal Failed");
}
}
private void appendRecord(JDBCJournalRecord record) throws Exception {
// extra measure I know, as all the callers are also checking for this..
// better to be safe ;)
checkStatus();
if (logger.isTraceEnabled()) {
logger.trace("appendRecord " + record);
}
record.storeLineUp();
if (!started) {
if (record.getIoCompletion() != null) {
record.getIoCompletion().onError(ActiveMQExceptionType.IO_ERROR.getCode(), "JDBC Journal not started");
}
}
SimpleWaitIOCallback callback = null;
if (record.isSync() && record.getIoCompletion() == null) {
callback = new SimpleWaitIOCallback();
record.setIoCompletion(callback);
}
synchronized (this) {
if (record.isTransactional() || record.getRecordType() == JDBCJournalRecord.PREPARE_RECORD) {
addTxRecord(record);
}
synchronized (records) {
records.add(record);
}
}
syncTimer.delay();
if (callback != null) callback.waitCompletion();
}
private synchronized void addTxRecord(JDBCJournalRecord record) {
if (logger.isTraceEnabled()) {
logger.trace("addTxRecord " + record + ", started=" + started + ", failed=" + failed);
}
checkStatus();
TransactionHolder txHolder = transactions.get(record.getTxId());
if (txHolder == null) {
txHolder = new TransactionHolder(record.getTxId());
transactions.put(record.getTxId(), txHolder);
}
// We actually only need the record ID in this instance.
if (record.isTransactional()) {
RecordInfo info = new RecordInfo(record.getId(), record.getRecordType(), new byte[0], record.isUpdate(), record.getCompactCount());
if (record.getRecordType() == JDBCJournalRecord.DELETE_RECORD_TX) {
txHolder.recordsToDelete.add(info);
} else {
txHolder.recordInfos.add(info);
}
} else {
txHolder.prepared = true;
}
}
@Override
public void appendAddRecord(long id, byte recordType, byte[] record, boolean sync) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.ADD_RECORD, seq.incrementAndGet());
r.setUserRecordType(recordType);
r.setRecord(record);
r.setSync(sync);
if (logger.isTraceEnabled()) {
logger.trace("appendAddRecord bytes[] " + r);
}
appendRecord(r);
}
@Override
public void appendAddRecord(long id, byte recordType, Persister persister, Object record, boolean sync) throws Exception {
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.ADD_RECORD, seq.incrementAndGet());
r.setUserRecordType(recordType);
r.setRecord(persister, record);
r.setSync(sync);
if (logger.isTraceEnabled()) {
logger.trace("appendAddRecord (encoding) " + r + " with record = " + record);
}
appendRecord(r);
}
@Override
public void appendAddRecord(long id,
byte recordType,
Persister persister,
Object record,
boolean sync,
IOCompletion completionCallback) throws Exception {
checkStatus(completionCallback);
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.ADD_RECORD, seq.incrementAndGet());
r.setUserRecordType(recordType);
r.setRecord(persister, record);
r.setSync(sync);
r.setIoCompletion(completionCallback);
if (logger.isTraceEnabled()) {
logger.trace("appendAddRecord (completionCallback & encoding) " + r + " with record = " + record);
}
appendRecord(r);
}
@Override
public void appendUpdateRecord(long id, byte recordType, byte[] record, boolean sync) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.UPDATE_RECORD, seq.incrementAndGet());
r.setUserRecordType(recordType);
r.setRecord(record);
r.setSync(sync);
if (logger.isTraceEnabled()) {
logger.trace("appendUpdateRecord (bytes)) " + r);
}
appendRecord(r);
}
@Override
public void appendUpdateRecord(long id, byte recordType, Persister persister, Object record, boolean sync) throws Exception {
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.UPDATE_RECORD, seq.incrementAndGet());
r.setUserRecordType(recordType);
r.setRecord(persister, record);
r.setSync(sync);
if (logger.isTraceEnabled()) {
logger.trace("appendUpdateRecord (encoding)) " + r + " with record " + record);
}
appendRecord(r);
}
@Override
public void appendUpdateRecord(long id,
byte recordType,
Persister persister,
Object record,
boolean sync,
IOCompletion completionCallback) throws Exception {
checkStatus(completionCallback);
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.ADD_RECORD, seq.incrementAndGet());
r.setUserRecordType(recordType);
r.setRecord(persister, record);
r.setSync(sync);
r.setIoCompletion(completionCallback);
if (logger.isTraceEnabled()) {
logger.trace("appendUpdateRecord (encoding & completioncallback)) " + r + " with record " + record);
}
appendRecord(r);
}
@Override
public void appendDeleteRecord(long id, boolean sync) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.DELETE_RECORD, seq.incrementAndGet());
r.setSync(sync);
if (logger.isTraceEnabled()) {
logger.trace("appendDeleteRecord id=" + id + " sync=" + sync);
}
appendRecord(r);
}
@Override
public void appendDeleteRecord(long id, boolean sync, IOCompletion completionCallback) throws Exception {
checkStatus(completionCallback);
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.DELETE_RECORD, seq.incrementAndGet());
r.setSync(sync);
r.setIoCompletion(completionCallback);
if (logger.isTraceEnabled()) {
logger.trace("appendDeleteRecord id=" + id + " sync=" + sync + " with completionCallback");
}
appendRecord(r);
}
@Override
public void appendAddRecordTransactional(long txID, long id, byte recordType, byte[] record) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.ADD_RECORD_TX, seq.incrementAndGet());
r.setUserRecordType(recordType);
r.setRecord(record);
r.setTxId(txID);
appendRecord(r);
if (logger.isTraceEnabled()) {
logger.trace("appendAddRecordTransactional txID=" + txID + " id=" + id + " using bytes[] r=" + r);
}
}
@Override
public void appendAddRecordTransactional(long txID,
long id,
byte recordType,
Persister persister,
Object record) throws Exception {
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.ADD_RECORD_TX, seq.incrementAndGet());
r.setUserRecordType(recordType);
r.setRecord(persister, record);
r.setTxId(txID);
if (logger.isTraceEnabled()) {
logger.trace("appendAddRecordTransactional txID=" + txID + " id=" + id + " using encoding=" + record + " and r=" + r);
}
appendRecord(r);
}
@Override
public void appendUpdateRecordTransactional(long txID, long id, byte recordType, byte[] record) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.UPDATE_RECORD_TX, seq.incrementAndGet());
r.setUserRecordType(recordType);
r.setRecord(record);
r.setTxId(txID);
if (logger.isTraceEnabled()) {
logger.trace("appendUpdateRecordTransactional txID=" + txID + " id=" + id + " using bytes and r=" + r);
}
appendRecord(r);
}
@Override
public void appendUpdateRecordTransactional(long txID,
long id,
byte recordType,
Persister persister,
Object record) throws Exception {
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.UPDATE_RECORD_TX, seq.incrementAndGet());
r.setUserRecordType(recordType);
r.setRecord(persister, record);
r.setTxId(txID);
if (logger.isTraceEnabled()) {
logger.trace("appendUpdateRecordTransactional txID=" + txID + " id=" + id + " using encoding=" + record + " and r=" + r);
}
appendRecord(r);
}
@Override
public void appendDeleteRecordTransactional(long txID, long id, byte[] record) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.DELETE_RECORD_TX, seq.incrementAndGet());
r.setRecord(record);
r.setTxId(txID);
if (logger.isTraceEnabled()) {
logger.trace("appendDeleteRecordTransactional txID=" + txID + " id=" + id + " using bytes and r=" + r);
}
appendRecord(r);
}
@Override
public void appendDeleteRecordTransactional(long txID, long id, EncodingSupport record) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.DELETE_RECORD_TX, seq.incrementAndGet());
r.setRecord(EncoderPersister.getInstance(), record);
r.setTxId(txID);
if (logger.isTraceEnabled()) {
logger.trace("appendDeleteRecordTransactional txID=" + txID + " id=" + id + " using encoding=" + record + " and r=" + r);
}
appendRecord(r);
}
@Override
public void appendDeleteRecordTransactional(long txID, long id) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(id, JDBCJournalRecord.DELETE_RECORD_TX, seq.incrementAndGet());
r.setTxId(txID);
if (logger.isTraceEnabled()) {
logger.trace("appendDeleteRecordTransactional txID=" + txID + " id=" + id);
}
appendRecord(r);
}
@Override
public void appendCommitRecord(long txID, boolean sync) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(-1, JDBCJournalRecord.COMMIT_RECORD, seq.incrementAndGet());
r.setTxId(txID);
r.setSync(sync);
if (logger.isTraceEnabled()) {
logger.trace("appendCommitRecord txID=" + txID + " sync=" + sync);
}
appendRecord(r);
}
@Override
public void appendCommitRecord(long txID, boolean sync, IOCompletion callback) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(-1, JDBCJournalRecord.COMMIT_RECORD, seq.incrementAndGet());
r.setTxId(txID);
r.setSync(sync);
r.setIoCompletion(callback);
if (logger.isTraceEnabled()) {
logger.trace("appendCommitRecord txID=" + txID + " callback=" + callback);
}
appendRecord(r);
}
@Override
public void appendCommitRecord(long txID,
boolean sync,
IOCompletion callback,
boolean lineUpContext) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(-1, JDBCJournalRecord.COMMIT_RECORD, seq.incrementAndGet());
r.setTxId(txID);
r.setStoreLineUp(lineUpContext);
r.setIoCompletion(callback);
r.setSync(sync);
if (logger.isTraceEnabled()) {
logger.trace("appendCommitRecord txID=" + txID + " using callback, lineup=" + lineUpContext);
}
appendRecord(r);
}
@Override
public void appendPrepareRecord(long txID, EncodingSupport transactionData, boolean sync) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(-1, JDBCJournalRecord.PREPARE_RECORD, seq.incrementAndGet());
r.setTxId(txID);
r.setTxData(transactionData);
r.setSync(sync);
if (logger.isTraceEnabled()) {
logger.trace("appendPrepareRecord txID=" + txID + " using sync=" + sync);
}
appendRecord(r);
}
@Override
public void appendPrepareRecord(long txID,
EncodingSupport transactionData,
boolean sync,
IOCompletion callback) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(0, JDBCJournalRecord.PREPARE_RECORD, seq.incrementAndGet());
r.setTxId(txID);
r.setTxData(transactionData);
r.setTxData(transactionData);
r.setSync(sync);
r.setIoCompletion(callback);
if (logger.isTraceEnabled()) {
logger.trace("appendPrepareRecord txID=" + txID + " using callback, sync=" + sync);
}
appendRecord(r);
}
@Override
public void appendPrepareRecord(long txID, byte[] transactionData, boolean sync) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(0, JDBCJournalRecord.PREPARE_RECORD, seq.incrementAndGet());
r.setTxId(txID);
r.setTxData(transactionData);
r.setSync(sync);
if (logger.isTraceEnabled()) {
logger.trace("appendPrepareRecord txID=" + txID + " transactionData, sync=" + sync);
}
appendRecord(r);
}
@Override
public void appendRollbackRecord(long txID, boolean sync) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(0, JDBCJournalRecord.ROLLBACK_RECORD, seq.incrementAndGet());
r.setTxId(txID);
r.setSync(sync);
if (logger.isTraceEnabled()) {
logger.trace("appendRollbackRecord txID=" + txID + " sync=" + sync);
}
appendRecord(r);
}
@Override
public void appendRollbackRecord(long txID, boolean sync, IOCompletion callback) throws Exception {
checkStatus();
JDBCJournalRecord r = new JDBCJournalRecord(0, JDBCJournalRecord.ROLLBACK_RECORD, seq.incrementAndGet());
r.setTxId(txID);
r.setSync(sync);
r.setIoCompletion(callback);
if (logger.isTraceEnabled()) {
logger.trace("appendRollbackRecord txID=" + txID + " sync=" + sync + " using callback");
}
appendRecord(r);
}
@Override
public synchronized JournalLoadInformation load(LoaderCallback reloadManager) {
JournalLoadInformation jli = new JournalLoadInformation();
JDBCJournalReaderCallback jrc = new JDBCJournalReaderCallback(reloadManager);
JDBCJournalRecord r;
try (ResultSet rs = selectJournalRecords.executeQuery()) {
int noRecords = 0;
while (rs.next()) {
r = JDBCJournalRecord.readRecord(rs);
switch (r.getRecordType()) {
case JDBCJournalRecord.ADD_RECORD:
jrc.onReadAddRecord(r.toRecordInfo());
break;
case JDBCJournalRecord.UPDATE_RECORD:
jrc.onReadUpdateRecord(r.toRecordInfo());
break;
case JDBCJournalRecord.DELETE_RECORD:
jrc.onReadDeleteRecord(r.getId());
break;
case JDBCJournalRecord.ADD_RECORD_TX:
jrc.onReadAddRecordTX(r.getTxId(), r.toRecordInfo());
break;
case JDBCJournalRecord.UPDATE_RECORD_TX:
jrc.onReadUpdateRecordTX(r.getTxId(), r.toRecordInfo());
break;
case JDBCJournalRecord.DELETE_RECORD_TX:
jrc.onReadDeleteRecordTX(r.getTxId(), r.toRecordInfo());
break;
case JDBCJournalRecord.PREPARE_RECORD:
jrc.onReadPrepareRecord(r.getTxId(), r.getTxDataAsByteArray(), r.getTxCheckNoRecords());
break;
case JDBCJournalRecord.COMMIT_RECORD:
jrc.onReadCommitRecord(r.getTxId(), r.getTxCheckNoRecords());
break;
case JDBCJournalRecord.ROLLBACK_RECORD:
jrc.onReadRollbackRecord(r.getTxId());
break;
default:
throw new Exception("Error Reading Journal, Unknown Record Type: " + r.getRecordType());
}
noRecords++;
if (r.getSeq() > seq.longValue()) {
seq.set(r.getSeq());
}
}
jrc.checkPreparedTx();
jli.setMaxID(((JDBCJournalLoaderCallback) reloadManager).getMaxId());
jli.setNumberOfRecords(noRecords);
transactions = jrc.getTransactions();
} catch (Throwable e) {
handleException(null, e);
}
return jli;
}
@Override
public JournalLoadInformation loadInternalOnly() throws Exception {
return null;
}
@Override
public JournalLoadInformation loadSyncOnly(JournalState state) throws Exception {
return null;
}
@Override
public void lineUpContext(IOCompletion callback) {
callback.storeLineUp();
}
@Override
public JournalLoadInformation load(List<RecordInfo> committedRecords,
List<PreparedTransactionInfo> preparedTransactions,
TransactionFailureCallback transactionFailure) throws Exception {
return load(committedRecords, preparedTransactions, transactionFailure, true);
}
public synchronized JournalLoadInformation load(final List<RecordInfo> committedRecords,
final List<PreparedTransactionInfo> preparedTransactions,
final TransactionFailureCallback failureCallback,
final boolean fixBadTX) throws Exception {
JDBCJournalLoaderCallback lc = new JDBCJournalLoaderCallback(committedRecords, preparedTransactions, failureCallback, fixBadTX);
return load(lc);
}
@Override
public int getAlignment() throws Exception {
return 0;
}
@Override
public int getNumberOfRecords() {
int count = 0;
try (ResultSet rs = countJournalRecords.executeQuery()) {
rs.next();
count = rs.getInt(1);
} catch (SQLException e) {
logger.warn(e.getMessage(), e);
return -1;
}
return count;
}
@Override
public int getUserVersion() {
return USER_VERSION;
}
@Override
public void runDirectJournalBlast() throws Exception {
}
@Override
public Map<Long, JournalFile> createFilesForBackupSync(long[] fileIds) throws Exception {
return null;
}
@Override
public final void synchronizationLock() {
logger.error("Replication is not supported with JDBC Store", new Exception("trace"));
}
@Override
public final void synchronizationUnlock() {
logger.error("Replication is not supported with JDBC Store", new Exception("trace"));
}
@Override
public void forceMoveNextFile() throws Exception {
}
@Override
public JournalFile[] getDataFiles() {
return new JournalFile[0];
}
@Override
public SequentialFileFactory getFileFactory() {
return null;
}
@Override
public int getFileSize() {
return 0;
}
@Override
public void scheduleCompactAndBlock(int timeout) throws Exception {
}
@Override
public void replicationSyncPreserveOldFiles() {
}
@Override
public void replicationSyncFinished() {
}
@Override
public boolean isStarted() {
return started;
}
private static class JDBCJournalSync extends ActiveMQScheduledComponent {
private final JDBCJournalImpl journal;
JDBCJournalSync(ScheduledExecutorService scheduledExecutorService,
Executor executor,
long checkPeriod,
TimeUnit timeUnit,
JDBCJournalImpl journal) {
super(scheduledExecutorService, executor, checkPeriod, timeUnit, true);
this.journal = journal;
}
@Override
public void run() {
if (journal.isStarted()) {
journal.sync();
}
}
}
}