/* * 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 java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQBuffers; import org.apache.activemq.artemis.api.core.ActiveMQExceptionType; import org.apache.activemq.artemis.core.journal.EncodingSupport; import org.apache.activemq.artemis.core.journal.IOCompletion; import org.apache.activemq.artemis.core.persistence.Persister; import org.apache.activemq.artemis.core.journal.RecordInfo; import org.apache.activemq.artemis.journal.ActiveMQJournalLogger; import org.apache.activemq.artemis.utils.ActiveMQBufferInputStream; class JDBCJournalRecord { /* Database Table Schema: id BIGINT (long) recordType SMALLINT (byte) compactCount SMALLINT (byte) txId BIGINT (long) userRecordType SMALLINT (byte) variableSize INT (int) record BLOB (InputStream) txDataSize INT (int) txData BLOB (InputStream) txCheckNoRecords INT (int) */ // Record types taken from Journal Impl static final byte ADD_RECORD = 11; static final byte UPDATE_RECORD = 12; static final byte ADD_RECORD_TX = 13; static final byte UPDATE_RECORD_TX = 14; static final byte DELETE_RECORD_TX = 15; static final byte DELETE_RECORD = 16; static final byte PREPARE_RECORD = 17; static final byte COMMIT_RECORD = 18; static final byte ROLLBACK_RECORD = 19; // Callback and sync operations private IOCompletion ioCompletion = null; private boolean storeLineUp = true; private boolean sync = false; // DB Fields for all records private Long id; private byte recordType; private byte compactCount; private long txId; // DB fields for ADD_RECORD(TX), UPDATE_RECORD(TX), private int variableSize; protected byte userRecordType; private InputStream record; // DB Fields for PREPARE_RECORD private int txDataSize; private InputStream txData; // DB Fields for COMMIT_RECORD and PREPARE_RECORD private int txCheckNoRecords; private boolean isUpdate; private boolean isTransactional; private long seq; JDBCJournalRecord(long id, byte recordType, long seq) { this.id = id; this.recordType = recordType; isUpdate = recordType == UPDATE_RECORD || recordType == UPDATE_RECORD_TX; isTransactional = recordType == UPDATE_RECORD_TX || recordType == ADD_RECORD_TX || recordType == DELETE_RECORD_TX; // set defaults compactCount = 0; txId = 0; variableSize = 0; userRecordType = -1; record = new ByteArrayInputStream(new byte[0]); txDataSize = 0; txData = new ByteArrayInputStream(new byte[0]); txCheckNoRecords = 0; this.seq = seq; } public void complete(boolean success) { if (ioCompletion != null) { if (success) { ioCompletion.done(); } else { ioCompletion.onError(ActiveMQExceptionType.IO_ERROR.getCode(), "JDBC Transaction failed."); } } } public void storeLineUp() { if (storeLineUp && ioCompletion != null) { ioCompletion.storeLineUp(); } } void writeRecord(PreparedStatement statement) throws Exception { byte[] recordBytes = new byte[variableSize]; byte[] txDataBytes = new byte[txDataSize]; try { record.read(recordBytes); txData.read(txDataBytes); } catch (IOException e) { ActiveMQJournalLogger.LOGGER.error("Error occurred whilst reading Journal Record", e); throw e; } statement.setLong(1, id); statement.setByte(2, recordType); statement.setByte(3, compactCount); statement.setLong(4, txId); statement.setByte(5, userRecordType); statement.setInt(6, variableSize); statement.setBytes(7, recordBytes); statement.setInt(8, txDataSize); statement.setBytes(9, txDataBytes); statement.setInt(10, txCheckNoRecords); statement.setLong(11, seq); statement.addBatch(); } void writeDeleteRecord(PreparedStatement deleteStatement) throws SQLException { deleteStatement.setLong(1, id); deleteStatement.addBatch(); } static JDBCJournalRecord readRecord(ResultSet rs) throws SQLException { JDBCJournalRecord record = new JDBCJournalRecord(rs.getLong(1), (byte) rs.getShort(2), rs.getLong(11)); record.setCompactCount((byte) rs.getShort(3)); record.setTxId(rs.getLong(4)); record.setUserRecordType((byte) rs.getShort(5)); record.setVariableSize(rs.getInt(6)); record.setRecord(rs.getBytes(7)); record.setTxDataSize(rs.getInt(8)); record.setTxData(rs.getBytes(9)); record.setTxCheckNoRecords(rs.getInt(10)); return record; } IOCompletion getIoCompletion() { return ioCompletion; } void setIoCompletion(IOCompletion ioCompletion) { this.ioCompletion = ioCompletion; } public void setStoreLineUp(boolean storeLineUp) { this.storeLineUp = storeLineUp; } public boolean isSync() { return sync; } public void setSync(boolean sync) { this.sync = sync; } public Long getId() { return id; } public byte getRecordType() { return recordType; } byte getCompactCount() { return compactCount; } private void setCompactCount(byte compactCount) { this.compactCount = compactCount; } long getTxId() { return txId; } void setTxId(long txId) { this.txId = txId; } private void setVariableSize(int variableSize) { this.variableSize = variableSize; } public byte getUserRecordType() { return userRecordType; } public void setUserRecordType(byte userRecordType) { this.userRecordType = userRecordType; } public void setRecord(byte[] record) { if (record != null) { this.variableSize = record.length; this.record = new ByteArrayInputStream(record); } } public void setRecord(InputStream record) { this.record = record; } public void setRecord(Persister persister, Object record) { this.variableSize = persister.getEncodeSize(record); ActiveMQBuffer encodedBuffer = ActiveMQBuffers.fixedBuffer(variableSize); persister.encode(encodedBuffer, record); this.record = new ActiveMQBufferInputStream(encodedBuffer); } public InputStream getRecord() { return record; } int getTxCheckNoRecords() { return txCheckNoRecords; } private void setTxCheckNoRecords(int txCheckNoRecords) { this.txCheckNoRecords = txCheckNoRecords; } private void setTxDataSize(int txDataSize) { this.txDataSize = txDataSize; } void setTxData(EncodingSupport txData) { this.txDataSize = txData.getEncodeSize(); ActiveMQBuffer encodedBuffer = ActiveMQBuffers.fixedBuffer(txDataSize); txData.encode(encodedBuffer); this.txData = new ActiveMQBufferInputStream(encodedBuffer); } void setTxData(byte[] txData) { if (txData != null) { this.txDataSize = txData.length; this.txData = new ByteArrayInputStream(txData); } } public boolean isUpdate() { return isUpdate; } private byte[] getRecordData() throws IOException { byte[] data = new byte[variableSize]; record.read(data); return data; } byte[] getTxDataAsByteArray() throws IOException { byte[] data = new byte[txDataSize]; txData.read(data); return data; } RecordInfo toRecordInfo() throws IOException { return new RecordInfo(getId(), getUserRecordType(), getRecordData(), isUpdate(), getCompactCount()); } public boolean isTransactional() { return isTransactional; } long getSeq() { return seq; } @Override public String toString() { return "JDBCJournalRecord{" + "compactCount=" + compactCount + ", id=" + id + ", isTransactional=" + isTransactional + ", isUpdate=" + isUpdate + ", recordType=" + recordType + ", seq=" + seq + ", storeLineUp=" + storeLineUp + ", sync=" + sync + ", txCheckNoRecords=" + txCheckNoRecords + ", txDataSize=" + txDataSize + ", txId=" + txId + ", userRecordType=" + userRecordType + ", variableSize=" + variableSize + '}'; } }