package com.linkedin.databus.core;
/*
*
* Copyright 2013 LinkedIn Corp. All rights reserved
*
* 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.
*
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
public class DbusEventBufferMetaInfo {
public static final Logger LOG = Logger.getLogger(DbusEventBufferMetaInfo.class);
// these are keys in meta file info. Each value matches the corresponding setting
// in the DbusEventBuf.
public static final String EVENT_STATE = "eventState";
public static final String TIMESTAMP_OF_LATEST_DATA_EVENT = "timestampOfLatestDataEvent";
public static final String TIMESTAMP_OF_FIRST_EVENT = "timestampOfFirstEvent";
public static final String PREV_SCN = "prevScn";
public static final String SEEN_END_OF_PERIOD_SCN = "seenEndOfPeriodScn";
public static final String LAST_WRITTEN_SEQUENCE = "lastWrittenSequence";
public static final String NUM_EVENTS_IN_WINDOW = "numEventsInWindow";
public static final String EVENT_START_INDEX = "eventStartIndex";
public static final String ALLOCATED_SIZE = "allocatedSize";
public static final String BUFFER_EMPTY = "empty";
public static final String BUFFER_TAIL = "tail";
public static final String BUFFER_HEAD = "head";
public static final String MAX_BUFFER_SIZE = "maxBufferSize";
public static final String CURRENT_WRITE_POSITION = "currentWritePosition";
public static final String BYTE_BUFFER_INFO = "ByteBufferInfo";
public static final String NUM_BYTE_BUFFER = "ByteBufferNum";
/**
* helper class for buffer serialization
*/
public static class BufferInfo {
public static final String DELIMITER = ",";
public int _pos;
public int _limit;
public int _cap;
BufferInfo(int pos, int limit, int cap) {
_pos = pos; _limit = limit; _cap = cap;
}
BufferInfo(String fromString) throws DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException { // string format is "pos,limit,capacity"
String [] info = fromString.split(DELIMITER);
if(info.length != 3)
throw new DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException("parsing BufferInfo failed for " + fromString);
try {
_pos = Integer.parseInt(info[0]);
_limit = Integer.parseInt(info[1]);
_cap = Integer.parseInt(info[2]);
} catch (NumberFormatException e) {
throw new DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException(
"parsing BufferInfo failed for " + fromString + " " + e.getLocalizedMessage());
}
}
public int getLimit() { return _limit;}
public int getPos() { return _pos; }
public int getCapacity() { return _cap;}
@Override
public String toString() {
return _pos + DELIMITER + _limit + DELIMITER + _cap;
}
}
public static class DbusEventBufferMetaInfoException extends IOException {
public DbusEventBufferMetaInfoException(String string) {
super(string);
}
public DbusEventBufferMetaInfoException(DbusEventBufferMetaInfo mi, String string) {
super("[" + mi.toString() + "]:" + string);
}
private static final long serialVersionUID = 1L;
}
private static final int META_INFO_VERSION = 1;
private static final char KEY_VALUE_SEP = ' ';
private final Map<String, String> _info = new HashMap<String, String>(100);
private boolean _valid = false;
private final File _file;
public DbusEventBufferMetaInfo(File metaFile) {
_file = metaFile;
}
public boolean isValid() {
return _valid;
}
public String getSessionId() {
return getVal("sessionId");
}
public void setSessionId(String sid) {
setVal("sessionId", sid);
}
public DbusEventBufferMetaInfo.BufferInfo getReadBufferInfo() throws DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException {
return new DbusEventBufferMetaInfo.BufferInfo(getVal("readBufInfo"));
}
public DbusEventBufferMetaInfo.BufferInfo getScnIndexBufferInfo() throws DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException {
return new DbusEventBufferMetaInfo.BufferInfo(getVal("scnIndexBufferInfo"));
}
public void setScnIndexBufferInfo(DbusEventBufferMetaInfo.BufferInfo bi) {
setVal("scnIndexBufferInfo", bi.toString());
}
public void setReadBufferInfo(DbusEventBufferMetaInfo.BufferInfo bi) {
setVal("readBufInfo", bi.toString());
}
public void setVal(String key, String val) {
_info.put(key, val);
}
String getVal(String key) {
String val = _info.get(key);
// if(val == null)
// return "";
return val;
}
public long getLong(String key) throws DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException {
long l;
try {
l = Long.parseLong(getVal(key));
} catch (NumberFormatException e) {
throw new DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException(this, "key="+key + " msg= " + e.getLocalizedMessage());
}
return l;
}
public int getInt(String key) throws DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException {
int i;
try {
i = Integer.parseInt(getVal(key));
} catch (NumberFormatException e) {
throw new DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException(this, e.getLocalizedMessage());
}
return i;
}
public boolean getBool(String key) {
return Boolean.parseBoolean(getVal(key));
}
/**
* load key/values form the file
* line is separated by the 'space'
* @throws DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException
*/
public boolean loadMetaInfo() throws DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException{
//
BufferedReader br = null;
_valid = false;
boolean debugEnabled = DbusEventBuffer.LOG.isDebugEnabled();
try {
InputStreamReader isr = new InputStreamReader(new FileInputStream(_file), "UTF-8");
br = new BufferedReader(isr);
DbusEventBuffer.LOG.info("loading metaInfoFile " + _file);
_info.clear();
String line = br.readLine();
while(line != null) {
if(line.isEmpty() || line.charAt(0) == '#')
continue;
// format is key' 'val
int idx = line.indexOf(KEY_VALUE_SEP);
if(idx < 0) {
DbusEventBuffer.LOG.warn("illegal line in metaInfoFile. line=" + line);
continue;
}
String key = line.substring(0, idx);
String val = line.substring(idx+1);
_info.put(key, val);
if(debugEnabled)
DbusEventBuffer.LOG.debug("\tkey=" + key + "; val=" + val);
line = br.readLine();
_valid = true;
}
} catch (IOException e) {
throw new DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException(this, "cannot read metaInfoFile: " + e.getLocalizedMessage());
} finally {
if(br != null)
try {
br.close();
} catch (IOException e) {
DbusEventBuffer.LOG.warn("faild to close " + _file);
}
}
int version = getInt("version");
if(version != META_INFO_VERSION)
throw new DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException(this,
"metaInfoFile version doesn't match. Please remove the metafile and restart");
if(isMetaFileOlderThenMMappedFiles(getSessionId())) {
// we do not act on this, but keep it as a warning in the logs
//_valid = false; //not valid file - don't use
}
return _valid;
}
/**
*
* @param sessionId
* @return true if mmaped fiels have changed after metaInfo file
*/
private boolean isMetaFileOlderThenMMappedFiles(String sessionId) {
if (sessionId == null)
return false;// valid case, no session is ok
// one extra check is to verify that the session directory that contains the actual buffers
// was not modified after the file was saved
long metaFileModTime = _file.lastModified();
LOG.debug(_file + " mod time: " + metaFileModTime);
// check the directory first
File sessionDir = new File(_file.getParent(), sessionId);
long sessionDirModTime = sessionDir.lastModified();
if(sessionDirModTime > metaFileModTime) {
LOG.error("Session dir " + sessionDir +
" seemed to be modified AFTER metaFile " + _file +
" dirModTime=" + sessionDirModTime + "; metaFileModTime=" + metaFileModTime);
return true;
}
// check each file in the directory
String mmappedFiles[] = sessionDir.list();
if(mmappedFiles == null) {
LOG.error("There are no mmaped files in the session directory: " + sessionDir);
return true;
}
for(String fName : mmappedFiles) {
File f = new File(sessionDir, fName);
long modTime = f.lastModified();
LOG.debug(f + " mod time: " + modTime);
if(modTime > metaFileModTime) {
LOG.error("MMapped file " + f + "(" + modTime + ") seemed to be modified AFTER metaFile "
+ _file + "(" + metaFileModTime + ")");
return true;
}
}
return false;// valid file - go ahead and use it
}
/**
* reads cap, pos and limit for each buffer
* @return
* @throws DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException
*/
public DbusEventBufferMetaInfo.BufferInfo[] getBuffersInfo() throws DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException {
int bufNum = 0;
try {
bufNum = Integer.parseInt(_info.get("ByteBufferNum"));
} catch (NumberFormatException e) {
throw new DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException(this, e.getLocalizedMessage());
}
String bufInfoAll = _info.get("ByteBufferInfo");
String [] buffersInfo = bufInfoAll.split(" ");
if(buffersInfo.length != bufNum) {
throw new DbusEventBufferMetaInfo.DbusEventBufferMetaInfoException(this, "bufNum " + bufNum + " doesn't match bufInfo size [" + bufInfoAll + "]");
}
DbusEventBufferMetaInfo.BufferInfo [] bInfos = new DbusEventBufferMetaInfo.BufferInfo[bufNum];
for(int i=0; i<buffersInfo.length; i++) {
bInfos[i] = new DbusEventBufferMetaInfo.BufferInfo(buffersInfo[i]);
}
return bInfos;
}
public void saveAndClose() throws IOException {
// update version
setVal("version", Integer.toString(META_INFO_VERSION));
if(_file.exists()) {
File renameTo = new File(_file.getAbsoluteFile() + "." + System.currentTimeMillis());
if(! _file.renameTo(renameTo)) {
DbusEventBufferMetaInfo.LOG.warn("failed to rename " + _file + " to " + renameTo);
}
DbusEventBuffer.LOG.warn("metaInfoFile " + _file + " exists. it is renambed to " + renameTo);
}
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(_file), "UTF-8");
BufferedWriter bw = new BufferedWriter(osw);
try {
for(Map.Entry<String, String> e : _info.entrySet()) {
bw.write(e.getKey() + KEY_VALUE_SEP + e.getValue());
bw.newLine();
}
} finally {
if(bw != null)
bw.close();
}
}
@Override
public String toString() {
return _file.getAbsolutePath();
}
}