/*
* Copyright 2012 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.eventstorage.engine.file;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.krakenapps.confdb.ConfigService;
import org.krakenapps.eventstorage.engine.DatapathUtil;
import org.krakenapps.eventstorage.engine.DatapathUtil.FileType;
import org.krakenapps.eventstorage.engine.GlobalConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FileHandlerManager implements Runnable {
private static final long TIMEZONE_OFFSET = Calendar.getInstance().getTimeZone().getRawOffset();
private Logger logger = LoggerFactory.getLogger(FileHandlerManager.class);
private ConcurrentMap<DayKey, EventWriter> idxs;
private ConcurrentMap<IntKey, EventPointerFile> ptrs;
private ConcurrentMap<DayKey, EventDataFile> dats;
private ConfigService confsvc;
private volatile int checkInterval;
private volatile int flushInterval;
private volatile int maxIdleTime;
private volatile boolean doStop;
private volatile boolean isRunning;
private volatile boolean forceFlush;
public FileHandlerManager(ConfigService confsvc) {
this.idxs = new ConcurrentHashMap<DayKey, EventWriter>();
this.ptrs = new ConcurrentHashMap<IntKey, EventPointerFile>();
this.dats = new ConcurrentHashMap<DayKey, EventDataFile>();
this.confsvc = confsvc;
}
@Override
public void run() {
try {
isRunning = true;
this.checkInterval = Integer.parseInt(GlobalConfig.get(confsvc, GlobalConfig.Key.CheckInterval).toString());
this.flushInterval = Integer.parseInt(GlobalConfig.get(confsvc, GlobalConfig.Key.FlushInterval).toString());
this.maxIdleTime = Integer.parseInt(GlobalConfig.get(confsvc, GlobalConfig.Key.MaxIdleTime).toString());
while (true) {
try {
try {
if (doStop)
break;
Thread.sleep(checkInterval);
sweep(false);
} catch (InterruptedException e) {
if (forceFlush) {
sweep(true);
forceFlush = false;
}
}
} catch (Throwable e) {
logger.error("kraken eventstorage: sweeper error", e);
}
}
} finally {
doStop = false;
isRunning = false;
}
logger.info("kraken eventstorage: writer sweeper stopped");
}
private void sweep(boolean force) {
Iterator<DayKey> datit = dats.keySet().iterator();
while (datit.hasNext())
sweep(force, dats.get(datit.next()), datit);
Iterator<IntKey> ptrit = ptrs.keySet().iterator();
while (ptrit.hasNext())
sweep(force, ptrs.get(ptrit.next()), ptrit);
Iterator<DayKey> idxit = idxs.keySet().iterator();
while (idxit.hasNext())
sweep(force, idxs.get(idxit.next()), idxit);
}
private void sweep(boolean force, FileWriter writer, Iterator<?> it) {
long current = System.currentTimeMillis();
if (!writer.isClosed() && (force || current - writer.getLastFlushTime().getTime() > flushInterval)) {
try {
writer.flush(true);
} catch (IOException e) {
logger.warn("kraken eventstorage: event flush failed", e);
}
}
if (writer.isClosed() || current - writer.getLastWriteTime().getTime() > maxIdleTime) {
it.remove();
writer.close();
}
}
public EventWriter getWriter(int tableId, Date date, boolean create) {
long time = date.getTime();
date = new Date(time - ((time + TIMEZONE_OFFSET) % 86400000L));
DayKey key = new DayKey(tableId, date);
EventWriter writer = idxs.get(key);
if (!create)
return (writer == null || writer.isClosed()) ? null : writer;
if (writer == null || writer.isClosed()) {
try {
writer = new EventWriter(tableId, date, this);
EventWriter old = idxs.putIfAbsent(key, writer);
if (old != null && !old.isClosed()) {
writer.close();
return old;
}
} catch (IOException e) {
logger.debug("kraken eventstorage: cannot create event file writer.", e);
throw new IllegalStateException(e);
}
}
return writer;
}
public EventPointerFile getPointerFile(int tableId, long id, boolean write) {
int key = (int) ((id >>> 24) & 0xFFFFFF);
IntKey k = new IntKey(tableId, key);
EventPointerFile ptr = ptrs.get(k);
if (ptr == null || ptr.isClosed()) {
try {
ptr = new EventPointerFile(k.tableId, k.key, this, write);
EventPointerFile old = ptrs.putIfAbsent(k, ptr);
if (old != null && !old.isClosed()) {
ptr.close();
return old;
}
} catch (FileNotFoundException e) {
return null;
} catch (IOException e) {
logger.debug("kraken eventstorage: cannot create event file writer.", e);
throw new IllegalStateException(e);
}
}
return ptr;
}
public EventDataFile getDataFile(int tableId, Date day, boolean write) {
long time = day.getTime();
day = new Date(time - ((time + TIMEZONE_OFFSET) % 86400000L));
DayKey key = new DayKey(tableId, day);
EventDataFile dat = dats.get(key);
if (dat == null || dat.isClosed()) {
try {
dat = new EventDataFile(key.tableId, key.day, write);
EventDataFile old = dats.putIfAbsent(key, dat);
if (old != null && !dat.isClosed()) {
dat.close();
return old;
}
} catch (FileNotFoundException e) {
return null;
} catch (IOException e) {
logger.debug("kraken eventstorage: cannot create event file writer.", e);
throw new IllegalStateException(e);
}
}
return dat;
}
public void delete(int tableId, Date day) throws IOException {
long time = day.getTime();
day = new Date(time - ((time + TIMEZONE_OFFSET) % 86400000L));
DayKey key = new DayKey(tableId, day);
EventWriter idx = idxs.remove(key);
if (idx != null)
idx.close();
DatapathUtil.getFilePath(tableId, day, FileType.Index).delete();
EventDataFile dat = dats.remove(key);
if (dat == null)
dat = new EventDataFile(tableId, day, false);
Set<Long> ids = dat.getIds();
dat.close();
DatapathUtil.getFilePath(tableId, day, FileType.Data).delete();
for (long id : ids) {
EventPointerFile ptr = getPointerFile(tableId, (int) ((id >>> 24) & 0xFFFFFF), true);
ptr.remove(id, (short) ((day.getTime() + TIMEZONE_OFFSET) / 86400000L));
}
}
public void close(int tableId) {
Iterator<DayKey> datit = dats.keySet().iterator();
while (datit.hasNext()) {
DayKey key = datit.next();
if (key.tableId == tableId) {
EventDataFile dat = dats.get(key);
datit.remove();
dat.close();
}
}
Iterator<IntKey> ptrit = ptrs.keySet().iterator();
while (ptrit.hasNext()) {
IntKey key = ptrit.next();
if (key.tableId == tableId) {
EventPointerFile ptr = ptrs.get(key);
ptrit.remove();
ptr.close();
}
}
Iterator<DayKey> idxit = idxs.keySet().iterator();
while (idxit.hasNext()) {
DayKey key = idxit.next();
if (key.tableId == tableId) {
EventWriter idx = idxs.get(key);
idxit.remove();
idx.close();
}
}
}
public void close() {
Collection<EventDataFile> datfiles = new ArrayList<EventDataFile>(dats.values());
dats.clear();
for (EventDataFile dat : datfiles)
dat.close();
Collection<EventPointerFile> ptrfiles = new ArrayList<EventPointerFile>(ptrs.values());
ptrs.clear();
for (EventPointerFile ptr : ptrfiles)
ptr.close();
Collection<EventWriter> idxfiles = new ArrayList<EventWriter>(idxs.values());
idxs.clear();
for (EventWriter idx : idxfiles)
idx.close();
}
public int getCheckInterval() {
return checkInterval;
}
public void setCheckInterval(int checkInterval) {
GlobalConfig.set(confsvc, GlobalConfig.Key.CheckInterval, checkInterval);
this.checkInterval = checkInterval;
}
public int getFlushInterval() {
return flushInterval;
}
public void setFlushInterval(int flushInterval) {
GlobalConfig.set(confsvc, GlobalConfig.Key.FlushInterval, flushInterval);
this.flushInterval = flushInterval;
}
public int getMaxIdleTime() {
return maxIdleTime;
}
public void setMaxIdleTime(int maxIdleTime) {
GlobalConfig.set(confsvc, GlobalConfig.Key.MaxIdleTime, maxIdleTime);
this.maxIdleTime = maxIdleTime;
}
public boolean isRunning() {
return isRunning;
}
public void setDoStop(boolean doStop) {
this.doStop = doStop;
}
public void setForceFlush(boolean forceFlush) {
this.forceFlush = forceFlush;
}
private class DayKey {
private int tableId;
private Date day;
public DayKey(int tableId, Date day) {
this.tableId = tableId;
this.day = day;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + ((day == null) ? 0 : day.hashCode());
result = prime * result + tableId;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DayKey other = (DayKey) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (day == null) {
if (other.day != null)
return false;
} else if (!day.equals(other.day))
return false;
if (tableId != other.tableId)
return false;
return true;
}
private FileHandlerManager getOuterType() {
return FileHandlerManager.this;
}
}
private class IntKey {
private int tableId;
private int key;
private IntKey(int tableId, int key) {
this.tableId = tableId;
this.key = key;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + key;
result = prime * result + tableId;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
IntKey other = (IntKey) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (key != other.key)
return false;
if (tableId != other.tableId)
return false;
return true;
}
private FileHandlerManager getOuterType() {
return FileHandlerManager.this;
}
}
}