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