/*
* Copyright 2010 NCHOVY
*
* 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.
*/
package org.krakenapps.logstorage.file;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogFileReaderV1 extends LogFileReader {
private Logger logger = LoggerFactory.getLogger(LogFileReaderV1.class);
private static final int INDEX_ITEM_SIZE = 16;
private BufferedRandomAccessFileReader indexFile;
private BufferedRandomAccessFileReader dataFile;
private List<BlockHeader> blockHeaders = new ArrayList<BlockHeader>();
public LogFileReaderV1(File indexPath, File dataPath) throws IOException, InvalidLogFileHeaderException {
this.indexFile = new BufferedRandomAccessFileReader(indexPath);
LogFileHeader indexFileHeader = LogFileHeader.extractHeader(indexFile, indexPath);
if (indexFileHeader.version() != 1)
throw new InvalidLogFileHeaderException("version not match");
RandomAccessFile f = new RandomAccessFile(indexPath, "r");
long length = f.length();
long pos = indexFileHeader.size();
while (pos < length) {
f.seek(pos);
BlockHeader header = new BlockHeader(f);
header.fp = pos;
blockHeaders.add(header);
if (header.blockLength == 0)
break;
pos += 18 + header.blockLength;
}
f.close();
logger.trace("kraken logstorage: {} has {} blocks.", indexPath.getName(), blockHeaders.size());
this.dataFile = new BufferedRandomAccessFileReader(dataPath);
LogFileHeader dataFileHeader = LogFileHeader.extractHeader(dataFile, dataPath);
if (dataFileHeader.version() != 1)
throw new InvalidLogFileHeaderException("version not match");
}
@Override
public LogRecord find(long id) throws IOException {
Long pos = null;
for (BlockHeader header : blockHeaders) {
if (id < header.firstId + header.blockLength / INDEX_ITEM_SIZE) {
// fp + header size + offset + (id + date) index
long indexPos = header.fp + 18 + (id - header.firstId) * INDEX_ITEM_SIZE + 10;
indexFile.seek(indexPos);
pos = read6Bytes(indexFile);
break;
}
}
if (pos == null)
return null;
// read data length
dataFile.seek(pos);
int key = dataFile.readInt();
Date date = new Date(dataFile.readLong());
int dataLen = dataFile.readInt();
// read block
byte[] block = new byte[dataLen];
dataFile.readFully(block);
ByteBuffer bb = ByteBuffer.wrap(block);
return new LogRecord(date, key, bb);
}
@Override
public void traverse(long limit, LogRecordCallback callback) throws IOException, InterruptedException {
traverse(0, limit, callback);
}
@Override
public void traverse(long offset, long limit, LogRecordCallback callback) throws IOException, InterruptedException {
traverse(null, null, offset, limit, callback);
}
@Override
public void traverse(Date from, Date to, long limit, LogRecordCallback callback) throws IOException, InterruptedException {
traverse(from, to, 0, limit, callback);
}
@Override
public void traverse(Date from, Date to, long offset, long limit, LogRecordCallback callback) throws IOException, InterruptedException {
int matched = 0;
int block = blockHeaders.size() - 1;
BlockHeader header = blockHeaders.get(block);
long blockLogNum = header.blockLength / INDEX_ITEM_SIZE;
if (header.endTime == 0)
blockLogNum = (indexFile.length() - (header.fp + 18)) / INDEX_ITEM_SIZE;
// block validate
while ((from != null && header.endTime != 0L && header.endTime < from.getTime()) || (to != null && header.startTime > to.getTime())) {
if (--block < 0)
return;
header = blockHeaders.get(block);
blockLogNum = header.blockLength / INDEX_ITEM_SIZE;
}
while (true) {
if (--blockLogNum < 0) {
do {
if (--block < 0)
return;
header = blockHeaders.get(block);
blockLogNum = header.blockLength / INDEX_ITEM_SIZE - 1;
} while ((from != null && header.endTime < from.getTime()) || (to != null && header.startTime > to.getTime()));
}
// begin of item (ignore id)
indexFile.seek(header.fp + 18 + INDEX_ITEM_SIZE * blockLogNum + 4);
// read index
Date indexDate = new Date(read6Bytes(indexFile));
if (from != null && indexDate.before(from))
continue;
if (to != null && indexDate.after(to))
continue;
// read data file fp
long pos = read6Bytes(indexFile);
// read data
dataFile.seek(pos);
int dataId = dataFile.readInt();
Date dataDate = new Date(dataFile.readLong());
int dataLen = dataFile.readInt();
byte[] data = new byte[dataLen];
dataFile.readFully(data);
if (offset > matched) {
matched++;
continue;
}
ByteBuffer bb = ByteBuffer.wrap(data, 0, dataLen);
if (callback.onLog(new LogRecord(dataDate, dataId, bb))) {
if (++matched == offset + limit)
return;
}
}
}
private long read6Bytes(BufferedRandomAccessFileReader f) throws IOException {
return ((long) f.readInt() << 16) | (f.readShort() & 0xFFFF);
}
@Override
public void close() throws IOException {
indexFile.close();
dataFile.close();
}
private static class BlockHeader {
private static Integer NEXT_ID = 1;
private long fp;
private long startTime;
private long endTime;
private long blockLength;
private int firstId;
private BlockHeader(RandomAccessFile f) throws IOException {
this.startTime = read6Bytes(f);
this.endTime = read6Bytes(f);
this.blockLength = read6Bytes(f);
this.firstId = NEXT_ID;
NEXT_ID += (int) this.blockLength / INDEX_ITEM_SIZE;
}
private long read6Bytes(RandomAccessFile f) throws IOException {
return ((long) f.readInt() << 16) | (f.readShort() & 0xFFFF);
}
}
}