/**
* diqube: Distributed Query Base.
*
* Copyright (C) 2015 Bastian Gloeckle
*
* This file is part of diqube.
*
* diqube is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diqube.file.internaldb;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.OptionalLong;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.apache.thrift.TBase;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.transport.TIOStreamTransport;
import org.diqube.file.internaldb.v1.SInternalDbFileHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Reads "internal db" files which actually contain a collection of arbitrary thrift {@link TBase}s.
*
* <p>
* InternalDb files do not only maintain a common file content, but also a common filename layout:
* prefix-commitIndex-suffix. The commitIndex is the index of the consensus cluster commit that initiated this write.
* This commitIndex is therefore increasing always, means higher index is newer file. The {@link InternalDbFileReader}
* reads the newest available file.
*
* @author Bastian Gloeckle
*/
public class InternalDbFileReader<T extends TBase<?, ?>> {
private static final Logger logger = LoggerFactory.getLogger(InternalDbFileReader.class);
private String dataType;
private String filenamePrefix;
private File inputDir;
private Supplier<T> factory;
public InternalDbFileReader(String dataType, String filenamePrefix, File inputDir, Supplier<T> factory) {
this.dataType = dataType;
this.filenamePrefix = filenamePrefix;
this.inputDir = inputDir;
this.factory = factory;
}
/**
* Read the newest file.
*
* @return <code>null</code> if no file is available.
* @throws ReadException
* If file cannot be read.
*/
public List<T> readNewest() throws ReadException {
File inputFile = findFileToReadFrom();
if (inputFile == null)
return null;
logger.info("Loading {} entities from '{}'...", dataType, inputFile.getAbsolutePath());
try (FileInputStream fis = new FileInputStream(inputFile)) {
try (TIOStreamTransport transport = new TIOStreamTransport(fis)) {
TCompactProtocol protocol = new TCompactProtocol(transport);
SInternalDbFileHeader header = new SInternalDbFileHeader();
header.read(protocol);
if (header.getVersion() != InternalDbFileWriter.VERSION)
throw new ReadException("Bad version number: " + header.getVersion());
if (!header.getDataType().equals(dataType))
throw new ReadException("Bad data type. Expected: " + dataType + " but got: " + header.getDataType());
List<T> res = new ArrayList<>();
long size = header.getSize();
while (size-- > 0) {
T newObj = factory.get();
newObj.read(protocol);
res.add(newObj);
}
logger.info("Loaded {} entities from '{}'.", dataType, inputFile.getAbsolutePath());
return res;
} catch (TException e) {
throw new ReadException("Could not read identities from " + inputFile.getAbsolutePath(), e);
}
} catch (IOException e1) {
throw new ReadException("Could not read identities from " + inputFile.getAbsolutePath(), e1);
}
}
private File findFileToReadFrom() throws ReadException {
File[] allDbFiles = inputDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return dir.equals(inputDir) && name.startsWith(filenamePrefix)
&& name.endsWith(InternalDbFileWriter.FILENAME_SUFFIX);
}
});
if (allDbFiles.length == 0)
return null;
if (allDbFiles.length == 1) {
return allDbFiles[0];
} else {
OptionalLong maxId = Stream.of(allDbFiles)
.mapToLong(f -> InternalDbFileUtil.parseCommitIndex(f, filenamePrefix, InternalDbFileWriter.FILENAME_SUFFIX))
.max();
if (!maxId.isPresent())
throw new ReadException("Could not identify maximum internaldb file of type " + dataType);
File dbFile = Stream.of(allDbFiles)
.filter(f -> f.getName().endsWith(maxId + InternalDbFileWriter.FILENAME_SUFFIX)).findAny().get();
return dbFile;
}
}
public static class ReadException extends Exception {
private static final long serialVersionUID = 1L;
/* package */ ReadException(String msg) {
super(msg);
}
/* package */ ReadException(String msg, Throwable cause) {
super(msg, cause);
}
}
}