/*
* Copyright 2011 Future Systems
*
* 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.logdb.query.command;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Comparator;
import java.util.Date;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import org.krakenapps.codec.EncodingRule;
import org.krakenapps.codec.FastEncodingRule;
import org.krakenapps.logdb.LogQueryCallback;
import org.krakenapps.logdb.LogQueryCommand;
import org.krakenapps.logdb.LogResultSet;
import org.krakenapps.logstorage.file.LogFileReaderV2;
import org.krakenapps.logstorage.file.LogFileWriterV2;
import org.krakenapps.logstorage.file.LogRecord;
import org.krakenapps.logstorage.file.LogRecordCursor;
public class Result extends LogQueryCommand {
private static File BASE_DIR = new File(System.getProperty("kraken.data.dir"), "kraken-logdb/query/");
private LogFileWriterV2 writer;
private File indexPath;
private File dataPath;
private long count;
private Set<LogQueryCallback> callbacks;
private Queue<LogQueryCallbackInfo> callbackQueue;
private Integer nextCallback;
private long nextStatusChangeCallback;
/**
* index and data file is deleted by user request
*/
private volatile boolean purged;
public Result() throws IOException {
callbacks = new CopyOnWriteArraySet<LogQueryCallback>();
callbackQueue = new PriorityQueue<Result.LogQueryCallbackInfo>(11, new CallbackInfoComparator());
indexPath = File.createTempFile("result", ".idx", BASE_DIR);
dataPath = File.createTempFile("result", ".dat", BASE_DIR);
writer = new LogFileWriterV2(indexPath, dataPath, 1024 * 1024, 1);
BASE_DIR.mkdirs();
}
private class LogQueryCallbackInfo {
private int size;
private LogQueryCallback callback;
public LogQueryCallbackInfo(LogQueryCallback callback) {
this.size = callback.offset() + callback.limit();
this.callback = callback;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + ((callback == null) ? 0 : callback.hashCode());
result = prime * result + size;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LogQueryCallbackInfo other = (LogQueryCallbackInfo) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (callback == null) {
if (other.callback != null)
return false;
} else if (!callback.equals(other.callback))
return false;
if (size != other.size)
return false;
return true;
}
private Result getOuterType() {
return Result.this;
}
}
private class CallbackInfoComparator implements Comparator<LogQueryCallbackInfo> {
@Override
public int compare(LogQueryCallbackInfo o1, LogQueryCallbackInfo o2) {
return o1.size - o2.size;
}
}
@Override
public void push(LogMap m) {
try {
synchronized (writer) {
writer.write(convert(m.map()));
}
} catch (IOException e) {
throw new IllegalStateException(e);
}
count++;
while (nextCallback != null && count >= nextCallback) {
LogQueryCallback callback = callbackQueue.poll().callback;
callback.onPageLoaded();
if (callbackQueue.isEmpty())
nextCallback = null;
else
nextCallback = callbackQueue.peek().size;
}
if (nextStatusChangeCallback < System.currentTimeMillis()) {
for (LogQueryCallback callback : logQuery.getLogQueryCallback())
callback.onQueryStatusChange();
nextStatusChangeCallback = System.currentTimeMillis() + 2000;
}
}
private LogRecord convert(Map<String, Object> m) {
FastEncodingRule enc = new FastEncodingRule();
ByteBuffer bb = enc.encode(m);
LogRecord logdata = new LogRecord(new Date(), count + 1, bb);
return logdata;
}
@Override
public boolean isReducer() {
return false;
}
public void registerCallback(LogQueryCallback callback) {
callbacks.add(callback);
callbackQueue.add(new LogQueryCallbackInfo(callback));
nextCallback = this.callbackQueue.peek().size;
}
public void unregisterCallback(LogQueryCallback callback) {
callbacks.remove(callback);
callbackQueue.remove(new LogQueryCallbackInfo(callback));
nextCallback = null;
if (!this.callbackQueue.isEmpty())
nextCallback = this.callbackQueue.peek().size;
}
public LogResultSet getResult() throws IOException {
if (purged) {
String msg = "query result file is already purged, index=" + indexPath.getAbsolutePath() + ", data="
+ dataPath.getAbsolutePath();
throw new IOException(msg);
}
synchronized (writer) {
writer.flush();
writer.sync();
}
LogFileReaderV2 reader = new LogFileReaderV2(indexPath, dataPath);
return new LogResultSetImpl(reader);
}
@Override
public void eof() {
this.status = Status.Finalizing;
try {
synchronized (writer) {
writer.close();
}
} catch (IOException e) {
}
super.eof();
for (LogQueryCallback callback : callbacks)
callback.onEof();
}
public void purge() {
purged = true;
// clear query callbacks
callbacks.clear();
callbackQueue.clear();
nextCallback = null;
// delete files
indexPath.delete();
dataPath.delete();
}
private static class LogResultSetImpl implements LogResultSet {
private LogFileReaderV2 reader;
private LogRecordCursor cursor;
public LogResultSetImpl(LogFileReaderV2 reader) {
this.reader = reader;
this.cursor = reader.getCursor(true);
}
@Override
public long size() {
return reader.count();
}
@Override
public boolean hasNext() {
return cursor.hasNext();
}
@Override
public Map<String, Object> next() {
LogRecord next = cursor.next();
return EncodingRule.decodeMap(next.getData());
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void skip(long n) {
cursor.skip(n);
}
@Override
public void close() {
try {
reader.close();
} catch (IOException e) {
}
}
}
}