/*
* Copyright 2012-2015, the original author or authors.
*
* 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 com.flipkart.aesop.serializer.stateengine;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import org.apache.avro.generic.GenericRecord;
import org.trpr.platform.core.impl.logging.LogFactory;
import org.trpr.platform.core.spi.logging.Logger;
import com.flipkart.aesop.serializer.SerializerConstants;
import com.netflix.zeno.fastblob.FastBlobStateEngine;
import com.netflix.zeno.fastblob.io.FastBlobReader;
/**
* The <code>DailyDiffInterpreter</code> is a sub-type of the {@link DiffInterpreter} that interprets change events from daily snapshots followed by
* any number of deltas for the day.
*
* @author Regunath B
* @version 1.0, 19 March 2014
*/
public class DailyDiffInterpreter<T, S extends GenericRecord> extends DiffInterpreter<T,S> {
/** The Logger interface*/
private static final Logger LOGGER = LogFactory.getLogger(DailyDiffInterpreter.class);
/**
* Overriden superclass method. Reads daily snapshot and associated deltas to update the state engine to a state identified by the SCN specified
* @see com.flipkart.aesop.serializer.stateengine.DiffInterpreter#readSnapshotAndDeltasForSCN(com.netflix.zeno.fastblob.FastBlobStateEngine, long)
*/
protected void readSnapshotAndDeltasForSCN(FastBlobStateEngine stateEngine,long sinceSCN) {
this.readSnapshotAndDeltas(stateEngine, sinceSCN, true);
}
protected void readSnapshotAndDeltasAfterSCN(FastBlobStateEngine stateEngine, long sinceSCN) {
this.readSnapshotAndDeltas(stateEngine, sinceSCN, false);
}
/**
* Reads snapshots and deltas for the specified SCN.
* @param stateEngine the FastBlobStateEngine to append snapshots and deltas to
* @param sinceSCN the SCN for identifying snapshots and deltas
* @param limitToSCN boolean true if the read should stop at the snapshot and related deltas or proceed to all available later state changes
*/
private void readSnapshotAndDeltas(FastBlobStateEngine stateEngine, final long sinceSCN, final boolean limitToSCN) {
File[] stateDirs = this.serializedDataLocationDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
if (new File(dir,name).isDirectory()) {
if (limitToSCN) {
return Long.valueOf(name) <= getSCNDay(sinceSCN); // we are interested only in directories that are older than SCN/state engine version
} else {
return Long.valueOf(name) >= getSCNDay(sinceSCN); // we are interested only in directories that are newer than SCN/state engine version
}
}
return false;
}
});
if (stateDirs.length == 0) { // no state files found and this is an initialization load
return;
}
Arrays.sort(stateDirs, Collections.reverseOrder(new Comparator<File>() { // sort descending to get latest available state
public int compare(File file1, File file2) {
return (int)(file1.lastModified() - file2.lastModified());
}
}));
FastBlobReader stateReader = new FastBlobReader(stateEngine);
for (File stateDir : stateDirs) {
File[] stateFiles = stateDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
if (name.endsWith(SerializerConstants.SNAPSHOT_FILE) || name.endsWith(SerializerConstants.DELTA_FILE)) {
LOGGER.debug("State file version : SCN [" + getStateFileVersion(new File(dir,name)) + " : " +sinceSCN + "]");
if (limitToSCN) {
return getStateFileVersion(new File(dir,name)) <= sinceSCN; // we are interested only in files that are older than SCN/state engine version
} else {
return getStateFileVersion(new File(dir,name)) >= sinceSCN; // we are interested only in files that are newer than SCN/state engine version
}
}
return false;
}
});
for (File stateFile : stateFiles) {
try {
LOGGER.debug("State file version : engine version [" + this.getStateFileVersion(stateFile) + " : " + this.getStateEngineVersion(stateEngine) +
"] State file is " + stateFile.getAbsolutePath());
if (this.getStateFileVersion(stateFile) > this.getStateEngineVersion(stateEngine)) { // we want to load the state data only if the engine is not at this version already
if (stateFile.getName().endsWith(SerializerConstants.SNAPSHOT_FILE)) {
stateReader.readSnapshot(new DataInputStream(new BufferedInputStream(new FileInputStream(stateFile))));
} else {
stateReader.readDelta(new DataInputStream(new BufferedInputStream(new FileInputStream(stateFile))));
}
stateEngine.setLatestVersion(String.valueOf(this.getStateFileVersion(stateFile)));
LOGGER.info("State engine initialized from state file : " + stateFile.getAbsolutePath());
}
} catch (Exception e) { // The state data read has failed. Proceed with empty state or next available set of state files
LOGGER.warn("Error reading state file : " + stateFile.getAbsolutePath() + " Proceeding with next file. Error message is : " + e.getMessage(), e);
}
}
}
LOGGER.debug(this.getStateEngineVersion(stateEngine) != 0 ? "State engine is set to version : " + stateEngine.getLatestVersion() :
"State engine not initialized from any existing snapshot");
}
/** Helper method to get state file version of the form yyyyMMdd*/
private long getStateFileVersion(File stateFile) {
return Long.valueOf(stateFile.getName().substring(0, SerializerConstants.DAILY_FILE_FORMAT_STRING.length()));
}
/** Helper method to get only the day portion of the SCN of the form yyyyMMdd*/
private long getSCNDay(long sinceSCN) {
return sinceSCN == 0L ? sinceSCN :
Long.valueOf(String.valueOf(sinceSCN).substring(0, SerializerConstants.DAILY_DIR_FORMAT_STRING.length()));
}
/** Helper method to get the engine version*/
private long getStateEngineVersion(FastBlobStateEngine stateEngine) {
return Long.valueOf(((stateEngine.getLatestVersion() == null || stateEngine.getLatestVersion().trim().length() == 0) ?
"0" : stateEngine.getLatestVersion()));
}
}