/*
- Class com.neocoretechs.arieslogger.core.FlushedScan
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 com.neocoretechs.arieslogger.core.impl;
import com.neocoretechs.arieslogger.core.LogInstance;
import com.neocoretechs.arieslogger.core.StreamLogScan;
import com.neocoretechs.bigsack.io.pooled.GlobalDBIO;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
/**
Scan the the log which is implemented by a series of log files.n
This log scan knows how to move across log file if it is positioned at
the boundary of a log file and needs to getNextRecord.
<PRE>
4 bytes - length of user data, i.e. N
8 bytes - long representing log instance
N bytes of supplied data
4 bytes - length of user data, i.e. N
</PRE>
*/
public class FlushedScan implements StreamLogScan {
private RandomAccessFile scan; // an output stream to the log file
private LogToFile logFactory; // log factory knows how to to skip
// from log file to log file
private boolean open; // true if the scan is open
private long currentLogFileNumber; // the log file the scan is currently on
private long currentLogFileFirstUnflushedPosition;
// The length of the unflushed portion
// of the current log file. This is the
// length of the file for all but the
// last log file.
private long currentInstance; // the log instance the scan is
// currently on - only valid after a
// successful getNextRecord
private long firstUnflushed = -1; // scan until we reach the first
// unflushed byte in the log.
private long firstUnflushedFileNumber;
private long firstUnflushedFilePosition;
/**
The length of the next record. Read from scan and set by
currentLogFileHasUnflushedRecord. This is used to retain the length of a
log record in the case currentLogFileHasUnflushedRecord reads the length
and determines that some bytes in the log record are not yet flushed.
*/
int nextRecordLength;
/**
Flag to indicate that the length of the next log record has been read by
currentLogFileHasUnflushedRecord.
This flag gets reset in two ways:
<OL>
<LI> currentLogFileHasUnflushedRecord determines that the entire log
record is flushed and returns true. In this case getNextRecord reads and
returns the log record.
<LI> we switch log files --due to a partial log record at the end of an
old log file.
</OL>
*/
boolean readNextRecordLength;
//RESOLVE: This belongs in a shared place.
static final int LOG_REC_LEN_BYTE_LENGTH = 4;
private static final boolean DEBUG = false;
public FlushedScan(LogToFile logFactory, long startAt) throws IOException {
if (DEBUG)
{
assert(startAt != LogCounter.INVALID_LOG_INSTANCE) : "cannot start scan on an invalid log instance";
}
setCurrentLogFileNumber(LogCounter.getLogFileNumber(startAt));
this.setLogFactory(logFactory);
scan = logFactory.getLogFileAtPosition(startAt);
setFirstUnflushed();
setOpen(true);
setCurrentInstance(LogCounter.INVALID_LOG_INSTANCE); // set at getNextRecord
}
/*
** Methods of LogScan
*/
/**
Read a log record into the byte array provided. Resize the input
stream byte array if necessary.
@return the length of the data written into data, or -1 if the end of the
scan has been reached.
@exception IOException
*/
public HashMap<LogInstance, LogRecord> getNextRecord(int groupmask) throws IOException {
boolean candidate;
LogRecord lr;
do
{
if (!isOpen() || !positionToNextRecord())
return null;
// this log record is a candidate unless proven otherwise
lr = null;
candidate = true;
setCurrentInstance(scan.readLong());
byte[] data = new byte[nextRecordLength];
scan.readFully(data, 0, nextRecordLength);
// put the data to 'input'
ByteBuffer input = ByteBuffer.wrap(data);
lr = (LogRecord)(GlobalDBIO.deserializeObject(input));
if (Scan.multiTrans && groupmask != 0 && (groupmask & lr.group()) == 0)
candidate = false; // no match, throw this log record out
if (!candidate)
{
// the starting record position is in the currentInstance,
// calculate the next record starting position using that
// and the nextRecordLength
long nextRecordStartPosition =
LogCounter.getLogFilePosition(getCurrentInstance()) +
nextRecordLength + LogToFile.LOG_RECORD_OVERHEAD;
scan.seek(nextRecordStartPosition);
}
} while (candidate == false);
HashMap<LogInstance, LogRecord> retLog = new HashMap<LogInstance, LogRecord>();
retLog.put(new LogCounter(currentInstance), lr);
return retLog;
}
/**
Reset the scan to the given LogInstance.
@param instance the position to reset to
@exception IOException scan cannot access the log at the new position.
*/
public void resetPosition(LogInstance instance) throws IOException
{
if (DEBUG)
{
throw new IOException("Unsupported feature");
}
}
/**
Get the log instance that is right after the record just retrived
@return INVALID_LOG_INSTANCE if this is not a FORWARD scan or, no
record have been returned yet or the scan has completed.
*/
public long getLogRecordEnd()
{
return LogCounter.INVALID_LOG_INSTANCE;
}
/**
returns true if there is partially writen log records before the crash
in the last log file. Partiall wrires are identified during forward
scans for log recovery.
*/
public boolean isLogEndFuzzy()
{
return false;
}
/**
Return the log instance (as an integer) the scan is currently on - this is the log
instance of the log record that was returned by getNextRecord.
*/
public long getLogInstanceAsLong()
{
return getCurrentInstance();
}
/**
Return the log instance the scan is currently on - this is the log
instance of the log record that was returned by getNextRecord.
*/
public LogInstance getLogInstance()
{
if (getCurrentInstance() == LogCounter.INVALID_LOG_INSTANCE)
return null;
else
return new LogCounter(getCurrentInstance());
}
/**
Close the scan.
*/
public void close()
{
if (scan != null)
{
try
{
scan.close();
}
catch (IOException ioe)
{}
scan = null;
}
setCurrentInstance(LogCounter.INVALID_LOG_INSTANCE);
setOpen(false);
}
/*
Private methods.
*/
private void setFirstUnflushed() throws IOException
{
LogInstance firstUnflushedInstant = getLogFactory().getFirstUnflushedInstance();
firstUnflushed = ((LogCounter)firstUnflushedInstant).getValueAsLong();
setFirstUnflushedFileNumber(LogCounter.getLogFileNumber(firstUnflushed));
setFirstUnflushedFilePosition(LogCounter.getLogFilePosition(firstUnflushed));
setCurrentLogFileFirstUnflushedPosition();
}
private void setCurrentLogFileFirstUnflushedPosition() throws IOException
{
/*
Note we get the currentLogFileLength without synchronization.
This is safe because one of the following cases apply:
<OL>
<LI> The end of the flushed section of the log is in another file.
In this case the end of the current file will not change.
<LI> The end of the log is in this file. In this case we
end our scan at the firstUnflushedInstant and do not use
currentLogFileLength.
</OL>
*/
if (getCurrentLogFileNumber() == getFirstUnflushedFileNumber())
currentLogFileFirstUnflushedPosition = getFirstUnflushedFilePosition();
else if (getCurrentLogFileNumber() < getFirstUnflushedFileNumber())
currentLogFileFirstUnflushedPosition = scan.length();
else
{
// RESOLVE
throw new IOException("Log bad start");
}
}
private void switchLogFile() throws IOException {
readNextRecordLength = false;
scan.close();
scan = null;
scan = getLogFactory().getLogFileAtBeginning(setCurrentLogFileNumber(getCurrentLogFileNumber() + 1));
setCurrentLogFileFirstUnflushedPosition();
}
private boolean currentLogFileHasUnflushedRecord() throws IOException
{
if (DEBUG)
assert(scan != null);//, "scan is null");
long curPos = scan.getFilePointer();
if (!readNextRecordLength)
{
if (curPos + LOG_REC_LEN_BYTE_LENGTH >
getCurrentLogFileFirstUnflushedPosition())
return false;
nextRecordLength = scan.readInt();
curPos+=4;
readNextRecordLength = true;
}
if (nextRecordLength==0) return false;
int bytesNeeded =
nextRecordLength + LOG_REC_LEN_BYTE_LENGTH;
if (curPos + bytesNeeded > getCurrentLogFileFirstUnflushedPosition())
{
return false;
}
else
{
readNextRecordLength = false;
return true;
}
}
private boolean positionToNextRecord() throws IOException
{
//If the flushed section of the current log file contains our record we
//simply return.
if (currentLogFileHasUnflushedRecord()) return true;
//Update our cached copy of the first unflushed instance.
setFirstUnflushed();
//In the call to setFirstUnflushed, we may have noticed that the current
//log file really does contain our record. If so we simply return.
if (currentLogFileHasUnflushedRecord()) return true;
//Our final chance of finding a record is if we are not scanning the log
//file with the last flushed instance we can switch logfiles. Note that
//we do this in a loop to cope with empty log files.
while(getCurrentLogFileNumber() < getFirstUnflushedFileNumber())
{
switchLogFile();
if (currentLogFileHasUnflushedRecord()) return true;
}
//The log contains no more flushed log records so we return false.
setCurrentInstance(LogCounter.INVALID_LOG_INSTANCE);
return false;
}
public long getCurrentLogFileNumber() {
return currentLogFileNumber;
}
public long setCurrentLogFileNumber(long currentLogFileNumber) {
this.currentLogFileNumber = currentLogFileNumber;
return currentLogFileNumber;
}
public boolean isOpen() {
return open;
}
public void setOpen(boolean open) {
this.open = open;
}
public long getCurrentLogFileFirstUnflushedPosition() {
return currentLogFileFirstUnflushedPosition;
}
public void setCurrentLogFileFirstUnflushedPosition(
long currentLogFileFirstUnflushedPosition) {
this.currentLogFileFirstUnflushedPosition = currentLogFileFirstUnflushedPosition;
}
public long getCurrentInstance() {
return currentInstance;
}
public void setCurrentInstance(long currentInstance) {
this.currentInstance = currentInstance;
}
public long getFirstUnflushed() {
return firstUnflushed;
}
public void setFirstUnflushed(long firstUnflushed) {
this.firstUnflushed = firstUnflushed;
}
public long getFirstUnflushedFileNumber() {
return firstUnflushedFileNumber;
}
public void setFirstUnflushedFileNumber(long firstUnflushedFileNumber) {
this.firstUnflushedFileNumber = firstUnflushedFileNumber;
}
public long getFirstUnflushedFilePosition() {
return firstUnflushedFilePosition;
}
public void setFirstUnflushedFilePosition(long firstUnflushedFilePosition) {
this.firstUnflushedFilePosition = firstUnflushedFilePosition;
}
public LogToFile getLogFactory() {
return logFactory;
}
public void setLogFactory(LogToFile logFactory) {
this.logFactory = logFactory;
}
@Override
public void checkFuzzyLogEnd() throws IOException {
// TODO Auto-generated method stub
}
}