/*
* Copyright 2014 Eediom Inc.
*
* 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.araqne.logstorage.file;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Date;
import org.araqne.storage.api.FilePath;
import org.araqne.storage.api.StorageInputStream;
import org.araqne.storage.api.StorageUtil;
import org.araqne.storage.filepair.CloseableEnumeration;
import org.araqne.storage.filepair.FilePair;
// TODO : handling encryption
public class LogFileV3o extends FilePair<IndexBlockV3Header, LogFileV3o.RawDataBlock> implements Closeable {
private static final short FILE_VERSION = 3;
private StorageInputStream dataStream;
private LogFileHeader ifileHeader;
private LogFileHeader dfileHeader;
private String compression;
private int compressionLevel;
public LogFileV3o(FilePath indexFile, FilePath dataFile) throws IOException {
this(indexFile, dataFile, "deflate");
}
public LogFileV3o(int id, Date day, FilePath basePath) throws IOException {
this(id, day, basePath, "deflate");
}
public LogFileV3o(int id, Date day, FilePath basePath, String compression) throws IOException {
this(DatapathUtil.getIndexFile(id, day, basePath), DatapathUtil.getDataFile(id, day, basePath), compression);
}
public LogFileV3o(FilePath indexFile, FilePath dataFile, String compression) throws IOException {
super(indexFile, dataFile, IndexBlockV3Header.class, RawDataBlock.class);
this.compression = compression;
this.compressionLevel = 3;
}
private StorageInputStream getDataStream() throws IOException {
if (dataStream == null) {
try {
if (dfile.exists())
dataStream = dfile.newInputStream();
} catch (Throwable t) {
StorageUtil.ensureClose(dataStream);
throw new IOException();
}
}
return dataStream;
}
LogFileHeader getIndexFileHeader() throws IOException {
if (ifileHeader == null)
ifileHeader = LogFileHeader.extractHeader(ifile);
return ifileHeader;
}
// for test
void setIndexFileHeader(LogFileHeader header) {
if (ifileHeader != null)
throw new IllegalStateException("IndexFileHeader is already set");
ifileHeader = header;
}
LogFileHeader getDataFileHeader() throws IOException {
if (dfileHeader == null)
dfileHeader = LogFileHeader.extractHeader(dfile);
return dfileHeader;
}
// for test
void setDataFileHeader(LogFileHeader header) {
if (dfileHeader != null)
throw new IllegalStateException("DataFileHeader is already set");
dfileHeader = header;
}
@Override
public void close() throws IOException {
StorageUtil.ensureClose(dataStream);
dataStream = null;
}
@Override
public void writeIndexFileHeader(OutputStream os) throws IOException {
LogFileHeader indexFileHeader = null;
try {
indexFileHeader = getIndexFileHeader();
} catch (IOException e) {
indexFileHeader = new LogFileHeader(FILE_VERSION, LogFileHeader.MAGIC_STRING_INDEX);
}
os.write(indexFileHeader.serialize());
}
@Override
public void writeDataFileHeader(OutputStream os) throws IOException {
LogFileHeader dataFileHeader = null;
try {
dataFileHeader = getDataFileHeader();
} catch (IOException e) {
dataFileHeader = new LogFileHeader(FILE_VERSION, LogFileHeader.MAGIC_STRING_DATA);
byte[] ext = new byte[4];
int flags = 0;
LogFileWriterV3o.prepareInt(flags, ext);
if (compressionLevel > 0) {
byte[] comp = compression.getBytes();
ext = new byte[4 + comp.length];
LogFileWriterV3o.prepareInt(flags, ext);
ByteBuffer bb = ByteBuffer.wrap(ext, 4, comp.length);
bb.put(comp);
}
dataFileHeader.setExtraData(ext);
}
os.write(dataFileHeader.serialize());
}
@Override
public long getIndexFileHeaderLength() throws IOException {
return getIndexFileHeader().size();
}
@Override
public long getDataFileHeaderLength() throws IOException {
return getDataFileHeader().size();
}
@Override
public int getIndexBlockCount() throws IOException {
if (!ifile.exists())
return 0;
return (int) ((getIndexFile().length() - getIndexFileHeaderLength()) / IndexBlockV3Header.ITEM_SIZE);
}
@Override
public IndexBlockV3Header getIndexBlock(int id) throws IOException {
StorageInputStream inputStream = null;
try {
IndexBlockV3Header unserializer = new IndexBlockV3Header();
inputStream = ifile.newInputStream();
inputStream.seek(getIndexFileHeaderLength() + IndexBlockV3Header.ITEM_SIZE * id);
IndexBlockV3Header ret = unserializer.unserialize(id, inputStream);
if (getIndexFileHeaderLength() + IndexBlockV3Header.ITEM_SIZE * (id + 1) >= getIndexFile().length()) {
ret.setDataBlockLen(getDataFile().length() - ret.getPosOnData());
} else {
IndexBlockV3Header next = unserializer.unserialize(id + 1, inputStream);
ret.setDataBlockLen(next.getPosOnData() - ret.getPosOnData());
}
return ret;
} finally {
StorageUtil.ensureClose(inputStream);
}
}
@Override
public RawDataBlock getRawDataBlock(IndexBlockV3Header indexBlock) throws IOException {
ByteBuffer dataBuffer = ByteBuffer.allocate((int) indexBlock.getDataBlockLen());
StorageInputStream stream = getDataStream();
stream.seek(indexBlock.getPosOnData());
stream.readFully(dataBuffer.array());
return new RawDataBlock(indexBlock.getId(), dataBuffer);
}
public CloseableEnumeration<IndexBlockV3Header> getIndexBlocks() throws IOException {
return super.getIndexBlocks();
}
public static class RawDataBlock extends org.araqne.storage.filepair.RawDataBlock<RawDataBlock> {
private int id;
private ByteBuffer blockBuffer;
public RawDataBlock(int id, ByteBuffer blockBuffer) {
this.id = id;
this.blockBuffer = blockBuffer;
}
@Override
public void serialize(OutputStream os) throws IOException {
os.write(blockBuffer.array());
}
public int getId() {
return id;
}
public String getDigest() {
return calcHash(blockBuffer);
}
}
@Override
public int getIndexBlockSize() {
return IndexBlockV3Header.ITEM_SIZE;
}
}