package org.araqne.logdb.impl;
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.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.apache.felix.ipojo.annotations.Validate;
import org.araqne.confdb.ConfigDatabase;
import org.araqne.confdb.ConfigIterator;
import org.araqne.confdb.ConfigService;
import org.araqne.logdb.SavedResult;
import org.araqne.logdb.SavedResultManager;
import org.araqne.logdb.query.command.IoHelper;
import org.araqne.logstorage.Log;
import org.araqne.logstorage.LogCursor;
import org.araqne.logstorage.LogFileService;
import org.araqne.logstorage.LogFileServiceRegistry;
import org.araqne.logstorage.LogMarshaler;
import org.araqne.logstorage.file.LogFileReader;
import org.araqne.logstorage.file.LogRecord;
import org.araqne.logstorage.file.LogRecordCursor;
import org.araqne.storage.api.FilePath;
import org.araqne.storage.api.StorageInputStream;
import org.araqne.storage.api.StorageOutputStream;
import org.araqne.storage.localfile.LocalFilePath;
import org.json.JSONConverter;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(name = "saved-result-manager")
@Provides
public class SavedResultManagerImpl implements SavedResultManager {
private static final String FILE_NAME = "saved_results.json";
private final Logger slog = LoggerFactory.getLogger(SavedResultManagerImpl.class);
@Requires
private ConfigService conf;
@Requires
private LogFileServiceRegistry fileServiceRegistry;
private FilePath baseDir;
// guid to saved result
private ConcurrentHashMap<String, SavedResult> cachedResults = new ConcurrentHashMap<String, SavedResult>();
@Validate
public void start() {
baseDir = new LocalFilePath(System.getProperty("araqne.data.dir")).newFilePath("araqne-logdb/saved");
baseDir.mkdirs();
cachedResults = readSavedResults();
// migrate
if (cachedResults == null) {
cachedResults = new ConcurrentHashMap<String, SavedResult>();
ConfigDatabase db = conf.ensureDatabase("araqne-logdb");
ConfigIterator it = db.findAll(SavedResult.class);
Collection<SavedResult> docs = it.getDocuments(SavedResult.class);
for (SavedResult doc : docs)
cachedResults.put(doc.getGuid(), doc);
writeSavedResults();
}
}
@Override
public List<SavedResult> getResultList(String owner) {
List<SavedResult> results = new ArrayList<SavedResult>();
for (SavedResult result : cachedResults.values())
if (result.getOwner().equals(owner))
results.add(result);
Collections.sort(results);
return results;
}
@Override
public SavedResult getResult(String guid) {
return cachedResults.get(guid);
}
@Override
public void saveResult(SavedResult result) throws IOException {
SavedResult old = cachedResults.putIfAbsent(result.getGuid(), result);
if (old != null)
throw new IllegalStateException("duplicated guid of saved result: " + result.getGuid());
FilePath fromIndexPath = new LocalFilePath(result.getIndexPath());
FilePath toIndexPath = baseDir.newFilePath(result.getGuid() + ".idx");
FilePath fromDataPath = new LocalFilePath(result.getDataPath());
FilePath toDataPath = baseDir.newFilePath(result.getGuid() + ".dat");
try {
copy(fromIndexPath, toIndexPath);
copy(fromDataPath, toDataPath);
writeSavedResults();
} catch (IOException e) {
toIndexPath.delete();
toDataPath.delete();
throw e;
}
}
private ConcurrentHashMap<String, SavedResult> readSavedResults() {
File resultFile = new File(((LocalFilePath) baseDir).getFile(), FILE_NAME);
if (!resultFile.exists()) {
slog.debug("araqne logdb: saved result file [{}] does not exist", resultFile.getAbsolutePath());
return null;
}
FileInputStream fis = null;
BufferedReader br = null;
try {
ConcurrentHashMap<String, SavedResult> results = new ConcurrentHashMap<String, SavedResult>();
fis = new FileInputStream(resultFile);
br = new BufferedReader(new InputStreamReader(fis, "utf-8"));
String line = null;
while ((line = br.readLine()) != null) {
Map<String, Object> m = JSONConverter.parse(new JSONObject(line));
SavedResult result = SavedResult.parse(m);
results.put(result.getGuid(), result);
}
return results;
} catch (Throwable t) {
slog.error("araqne logdb: cannot read saved result file [" + resultFile.getAbsolutePath() + "]", t);
return new ConcurrentHashMap<String, SavedResult>();
} finally {
IoHelper.close(br);
IoHelper.close(fis);
}
}
private synchronized void writeSavedResults() {
File resultFile = new File(((LocalFilePath) baseDir).getFile(), FILE_NAME);
File tmpFile = new File(((LocalFilePath) baseDir).getFile(), FILE_NAME + ".tmp");
FileOutputStream fos = null;
BufferedWriter bw = null;
try {
fos = new FileOutputStream(tmpFile);
bw = new BufferedWriter(new OutputStreamWriter(fos, "utf-8"));
for (SavedResult result : cachedResults.values()) {
String json = JSONConverter.jsonize(result.marshal());
bw.write(json);
bw.newLine();
}
bw.flush();
fos.flush();
fos.getFD().sync();
} catch (Throwable t) {
slog.warn("araqne logdb: cannot write saved results. file path [" + tmpFile.getAbsolutePath() + "]", t);
return;
} finally {
IoHelper.close(bw);
IoHelper.close(fos);
}
if (resultFile.exists()) {
if (!resultFile.delete()) {
slog.error("araqne logdb: cannot delete old saved results [{}]", resultFile.getAbsolutePath());
}
}
tmpFile.renameTo(resultFile);
}
private void copy(FilePath from, FilePath to) throws IOException {
StorageInputStream src = null;
StorageOutputStream dst = null;
try {
src = from.newInputStream();
dst = to.newOutputStream(false);
long length = src.length();
long copied = 0;
ByteBuffer bb = ByteBuffer.allocate(8192);
while (true) {
int read = src.read(bb.array(), 0, bb.limit());
dst.write(bb.array(), 0, read);
copied += read;
if (read != bb.capacity())
// eof
break;
}
if (copied != length)
throw new IOException("copied size is not equal with length of source file");
} finally {
if (src != null) {
try {
src.close();
} catch (IOException e) {
}
}
if (dst != null) {
try {
dst.close();
} catch (IOException e) {
}
}
}
}
@Override
public void deleteResult(String guid) {
SavedResult old = cachedResults.remove(guid);
if (old == null)
throw new IllegalStateException("saved result not found: " + guid);
writeSavedResults();
FilePath indexPath = baseDir.newFilePath(guid + ".idx");
FilePath dataPath = baseDir.newFilePath(guid + ".dat");
if (!indexPath.delete())
slog.error("araqne logdb: cannot delete saved result [{}]", indexPath.getAbsolutePath());
if (!dataPath.delete())
slog.error("araqne logdb: cannot delete saved result [{}]", dataPath.getAbsolutePath());
}
@Override
public LogCursor getCursor(String guid) throws IOException {
SavedResult sr = getResult(guid);
if (sr == null)
throw new IllegalStateException("saved result not found: " + guid);
LogFileService lfs = fileServiceRegistry.getLogFileService(sr.getStorageName());
Map<String, Object> options = new HashMap<String, Object>();
options.put("indexPath", baseDir.newFilePath(guid + ".idx"));
options.put("dataPath", baseDir.newFilePath(guid + ".dat"));
LogFileReader reader = lfs.newReader("result-" + guid, options);
return new LogCursorImpl(sr, reader);
}
private static class LogCursorImpl implements LogCursor {
private String guid;
private LogFileReader reader;
private LogRecordCursor c;
public LogCursorImpl(SavedResult result, LogFileReader reader) throws IOException {
this.guid = result.getGuid();
this.reader = reader;
this.c = reader.getCursor(true);
}
@Override
public boolean hasNext() {
return c.hasNext();
}
@Override
public Log next() {
LogRecord next = c.next();
return LogMarshaler.convert(guid, next);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void close() {
reader.close();
}
}
}