package org.csc.phynixx.loggersystem.logrecord; /* * #%L * phynixx-logger * %% * Copyright (C) 2014 Christoph Schmidt-Casdorff * %% * Licensed 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. * #L% */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import org.apache.commons.io.IOUtils; import org.csc.phynixx.common.exceptions.DelegatedRuntimeException; import org.csc.phynixx.common.exceptions.ExceptionUtils; import org.csc.phynixx.common.logger.IPhynixxLogger; import org.csc.phynixx.common.logger.PhynixxLogManager; import org.csc.phynixx.loggersystem.logger.IDataLogger; import org.csc.phynixx.loggersystem.logger.IDataLoggerReplay; import org.csc.phynixx.loggersystem.logger.channellogger.AccessMode; /** * brings IXADataRecorder and IDataLogger together. * * An instance keeps an {@link IDataLogger} representing the physical logging * strategy. * * Its permitted (but not recommended) to shared or reuse a dataLogger * * Therefore the dataLogger isn't associated to the dataRecorder, but the * xaDataRecorder operates on the current dataLogger. * * * Created by christoph on 10.01.14. */ class XADataLogger { private static final IPhynixxLogger LOGGER = PhynixxLogManager.getLogger(XADataLogger.class); private static final int HEADER_SIZE = 8 + 4; private final IDataLogger dataLogger; XADataLogger(IDataLogger dataLogger) { this.dataLogger = dataLogger; } public boolean isClosed() { return this.dataLogger.isClosed(); } public void destroy() throws IOException { this.dataLogger.destroy(); } /** * callback to recover the content of the xadataRecorder * * @author christoph * */ private class RecoverReplayListener implements IDataLoggerReplay { private int count = 0; private PhynixxXADataRecorder dataRecorder; private RecoverReplayListener(PhynixxXADataRecorder dataRecorder) { this.dataRecorder = dataRecorder; } public int getCountLogRecords() { return count; } @Override public void onRecord(XALogRecordType recordType, byte[][] fieldData) { if (count == 0) { // recovers the message sequence id dataRecorder.setMessageSequenceId(XADataLogger.this.recoverMessageSequenceId(fieldData[0])); } else { short typeId = recordType.getType(); switch (typeId) { case XALogRecordType.XA_START_TYPE: case XALogRecordType.XA_PREPARED_TYPE: case XALogRecordType.ROLLFORWARD_DATA_TYPE: case XALogRecordType.XA_DONE_TYPE: case XALogRecordType.USER_TYPE: case XALogRecordType.ROLLBACK_DATA_TYPE: XADataLogger.this.recoverData(dataRecorder, recordType, fieldData); break; default: LOGGER.error("Unknown LogRecordtype " + recordType); break; } } count++; } } /** * prepares the Logger for writing. The current content is removed. * * @param dataRecorder * @throws IOException * @throws InterruptedException */ void prepareForWrite(long xaDataRecorderId) throws IOException, InterruptedException { this.dataLogger.reopen(AccessMode.WRITE); this.writeStartSequence(xaDataRecorderId); } /** * prepares the Logger for writing. * * @throws IOException * @throws InterruptedException */ void prepareForAppend() throws IOException, InterruptedException { this.dataLogger.reopen(AccessMode.APPEND); } /** * prepares the Logger for writing. * * @throws IOException * @throws InterruptedException */ void prepareForRead() throws IOException, InterruptedException { this.dataLogger.reopen(AccessMode.READ); } /** * * * @param message * message to be written * @throws IOException */ void writeData(IDataRecord message) throws IOException { DataOutputStream io = null; try { ByteArrayOutputStream byteIO = new ByteArrayOutputStream(HEADER_SIZE); io = new DataOutputStream(byteIO); io.writeLong(message.getXADataRecorderId()); io.writeInt(message.getOrdinal().intValue()); byte[] header = byteIO.toByteArray(); byte[][] data = message.getData(); byte[][] content = null; if (data == null) { content = new byte[][] { header }; } else { content = new byte[data.length + 1][]; content[0] = header; for (int i = 0; i < data.length; i++) { content[i + 1] = data[i]; } } try { this.dataLogger.write(message.getLogRecordType().getType(), content); } catch (Exception e) { throw new DelegatedRuntimeException("writing message " + message + "\n" + ExceptionUtils.getStackTrace(e), e); } } finally { if (io != null) { io.close(); } } } /** * * * @param dataRecorder * DataRecorder that uses /operates on the current physical logger * * @throws IOException * @throws InterruptedException */ void recover(PhynixxXADataRecorder dataRecorder) throws IOException, InterruptedException { RecoverReplayListener listener = new RecoverReplayListener(dataRecorder); dataRecorder.rewind(); this.dataLogger.replay(listener); if (LOGGER.isDebugEnabled()) { LOGGER.debug("# Records=" + listener.getCountLogRecords()); } } /** * * a new data record is created an added to dataRecorder. It's not checked if * the record is permissable * * @param dataRecorder * DataRecorder that uses /operates on the current physical logger * * @param logRecordType * @param fieldData * * */ private void recoverData(PhynixxXADataRecorder dataRecorder, XALogRecordType logRecordType, byte[][] fieldData) { if (LOGGER.isDebugEnabled()) { if (fieldData == null || fieldData.length == 0) { throw new IllegalArgumentException("Record fields are empty"); } } // field 0 is header byte[] headerData = fieldData[0]; DataInputStream io = null; try { io = new DataInputStream(new ByteArrayInputStream(headerData)); // redundant , just read it an skip io.readLong(); int ordinal = io.readInt(); byte[][] content = null; if (fieldData.length > 1) { content = new byte[fieldData.length - 1][]; for (int i = 0; i < fieldData.length - 1; i++) { content[i] = fieldData[i + 1]; } } else { content = new byte[][] {}; } PhynixxDataRecord msg = new PhynixxDataRecord(dataRecorder.getXADataRecorderId(), ordinal, logRecordType, content); dataRecorder.recoverMessage(msg); } catch (Exception e) { throw new DelegatedRuntimeException(e); } finally { if (io != null) { IOUtils.closeQuietly(io); } } } void close() { try { this.dataLogger.close(); } catch (Exception e) { throw new DelegatedRuntimeException(e); } } /** * start sequence writes the ID of the XADataLogger to identify the content * of the logger * * @param dataRecorder * DataRecorder that uses /operates on the current physical logger * */ private void writeStartSequence(long xaDataRecorderId) throws IOException, InterruptedException { ByteArrayOutputStream byteOut = null; try { byteOut = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(byteOut); dos.writeLong(xaDataRecorderId); dos.flush(); } finally { if (byteOut != null) { IOUtils.closeQuietly(byteOut); } } byte[][] startSequence = new byte[1][]; startSequence[0] = byteOut.toByteArray(); this.dataLogger.write(XALogRecordType.USER.getType(), startSequence); } private long recoverMessageSequenceId(byte[] bytes) { byte[] headerData = bytes; DataInputStream io = null; try { io = new DataInputStream(new ByteArrayInputStream(headerData)); return io.readLong(); } catch (IOException e) { throw new DelegatedRuntimeException(e); } finally { if (io != null) { IOUtils.closeQuietly(io); } } } }