/*
* Copyright 2011 Future Systems, 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.krakenapps.confdb.file;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.krakenapps.confdb.CommitOp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RevLogWriter {
private static final byte[] MAGIC_STRING = "KRAKEN_CONFDB".getBytes();
private static final int COL_LOG_SIZE = 34;
private final Logger logger = LoggerFactory.getLogger(RevLogReader.class.getName());
/**
* collection log file handle
*/
private FileOutputStream logFileOutput;
private BufferedOutputStream logOutput;
private long logFileLength;
private int logHeaderLength;
/**
* doc file handle
*/
private FileOutputStream datFileOutput;
private BufferedOutputStream datOutput;
private long datFileLength;
private final int datHeaderLength;
/**
* collection log buffer
*/
private byte[] buffer;
public RevLogWriter(File logFile, File datFile) throws IOException {
this(logFile, datFile, null, null);
}
public RevLogWriter(File logFile, File datFile, byte[] logOption, byte[] datOption) throws IOException {
logFile.getParentFile().mkdirs();
datFile.getParentFile().mkdirs();
boolean logExists = logFile.exists();
boolean datExists = datFile.exists();
if (logExists && logOption != null)
logger.info("kraken confdb: log file already exist. ignore log option.");
if (datExists && datOption != null)
logger.info("kraken confdb: dat file already exist. ignore dat option.");
if (logOption == null)
logOption = new byte[0];
if (datOption == null)
datOption = new byte[0];
RandomAccessFile logRaf = new RandomAccessFile(logFile, "rw");
try {
if (!logExists) {
byte[] b = Arrays.copyOf(MAGIC_STRING, 16);
b[13] = 0x2; // version 1, log
b[14] = (byte) ((logOption.length >> 8) & 0xFF);
b[15] = (byte) (logOption.length & 0xFF);
logRaf.write(b);
logRaf.write(logOption);
this.logHeaderLength = 16 + logOption.length;
} else {
logRaf.seek(14);
byte b1 = logRaf.readByte();
byte b2 = logRaf.readByte();
this.logHeaderLength = 16 + ((b1 & 0xFF) << 8) + (b2 & 0xFF);
}
} finally {
logRaf.close();
}
RandomAccessFile datRaf = new RandomAccessFile(datFile, "rw");
try {
if (!datExists) {
byte[] b = Arrays.copyOf(MAGIC_STRING, 16);
b[13] = 0x3; // version 1, dat
b[14] = (byte) ((datOption.length >> 8) & 0xFF);
b[15] = (byte) (datOption.length & 0xFF);
datRaf.write(b);
datRaf.write(datOption);
this.datHeaderLength = 16 + datOption.length;
} else {
datRaf.seek(14);
byte b1 = datRaf.readByte();
byte b2 = datRaf.readByte();
this.datHeaderLength = 16 + ((b1 & 0xFF) << 8) + (b2 & 0xFF);
}
} finally {
datRaf.close();
}
this.buffer = new byte[COL_LOG_SIZE];
this.datFileLength = datFile.length();
this.logFileLength = logFile.length();
this.logFileOutput = new FileOutputStream(logFile, true);
this.datFileOutput = new FileOutputStream(datFile, true);
this.logOutput = new BufferedOutputStream(logFileOutput);
this.datOutput = new BufferedOutputStream(datFileOutput);
// TODO: check signature and collection metadata (e.g. version, name)
}
public int write(RevLog log) throws IOException {
byte[] doc = log.getDoc();
// append doc binary data
ByteBuffer hbb = ByteBuffer.allocate(8);
hbb.putInt(doc == null ? 0 : doc.length);
hbb.putInt(0); // option
datOutput.write(hbb.array());
if (doc != null)
datOutput.write(doc);
// append collection log
log.setDocOffset(datFileLength - datHeaderLength);
log.setDocLength(doc == null ? 0 : doc.length);
if (doc != null)
datFileLength += 8 + doc.length;
if (log.getOperation() == CommitOp.CreateDoc)
log.setDocId((int) ((logFileLength - logHeaderLength) / COL_LOG_SIZE) + 1);
ByteBuffer bb = ByteBuffer.wrap(buffer);
log.serialize(bb);
logOutput.write(bb.array());
logFileLength += COL_LOG_SIZE;
return log.getDocId();
}
/**
* @return the number of revision log items including duplicated updates
*/
public int count() {
return (int) ((logFileLength - logHeaderLength) / COL_LOG_SIZE);
}
public void sync() throws IOException {
datOutput.flush();
logOutput.flush();
datFileOutput.getFD().sync();
logFileOutput.getFD().sync();
}
public void close() {
closeOutput(datOutput, "doc file");
closeOutput(logOutput, "log file");
}
private void closeOutput(OutputStream os, String target) {
try {
os.close();
} catch (IOException e) {
logger.error("kraken confdb: cannot close " + target, e);
}
}
}