/*
* 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;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.apache.felix.ipojo.annotations.Validate;
import org.krakenapps.api.DateFormat;
import org.krakenapps.confdb.ConfigService;
import org.krakenapps.eventstorage.Event;
import org.krakenapps.eventstorage.EventRecord;
import org.krakenapps.eventstorage.EventStorage;
import org.krakenapps.eventstorage.EventStorageStatus;
import org.krakenapps.eventstorage.EventTableNotFoundException;
import org.krakenapps.eventstorage.EventTableRegistry;
import org.krakenapps.eventstorage.engine.GlobalConfig.Key;
import org.krakenapps.eventstorage.engine.file.EventPointerFile;
import org.krakenapps.eventstorage.engine.file.EventReader;
import org.krakenapps.eventstorage.engine.file.EventWriter;
import org.krakenapps.eventstorage.engine.file.FileHandlerManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(name = "eventstorage-engine")
@Provides
public class EventStorageEngine implements EventStorage {
private Logger logger = LoggerFactory.getLogger(EventStorageEngine.class);
@Requires
private ConfigService confsvc;
@Requires
private EventTableRegistry tableRegistry;
private File dir;
private EventStorageStatus status;
private Map<Integer, AtomicLong> nextIds;
private FileHandlerManager fileman = new FileHandlerManager(confsvc);
private Thread filemanThread = new Thread(fileman);
@Validate
@Override
public void start() {
if (status != null && status != EventStorageStatus.Stopped)
throw new IllegalStateException("storage not stopped");
status = EventStorageStatus.Starting;
String pathname = (String) GlobalConfig.get(confsvc, Key.StorageDirectory);
setDirectory(new File(pathname));
this.nextIds = new HashMap<Integer, AtomicLong>();
@SuppressWarnings("unchecked")
Map<String, String> savedIds = (Map<String, String>) GlobalConfig.get(confsvc, Key.NextEventId);
for (String key : savedIds.keySet())
nextIds.put(Integer.parseInt(key), new AtomicLong(Long.parseLong(savedIds.get(key))));
filemanThread.start();
status = EventStorageStatus.Started;
}
@Invalidate
@Override
public void stop() {
if (status != EventStorageStatus.Started)
throw new IllegalStateException("storage not started");
status = EventStorageStatus.Stopping;
fileman.setDoStop(true);
fileman.close();
filemanThread.interrupt();
Map<String, String> saveIds = new HashMap<String, String>();
for (Integer key : nextIds.keySet())
saveIds.put(key.toString(), String.valueOf(nextIds.get(key).get()));
GlobalConfig.set(confsvc, Key.NextEventId, saveIds, true);
try {
for (int i = 0; i < 25; i++) {
if (!fileman.isRunning())
break;
Thread.sleep(200);
}
} catch (InterruptedException e) {
}
status = EventStorageStatus.Stopped;
}
@Override
public EventStorageStatus getStatus() {
return status;
}
@Override
public File getDirectory() {
return dir;
}
@Override
public File getTableDirectory(String tableName) {
if (!tableRegistry.exists(tableName))
throw new EventTableNotFoundException(tableName);
int tableId = tableRegistry.getTableId(tableName);
return new File(dir, Integer.toString(tableId));
}
@Override
public void setDirectory(File dir) {
if (dir == null)
throw new IllegalArgumentException("storage path should be not null");
if (!dir.exists())
dir.mkdirs();
if (!dir.isDirectory())
throw new IllegalArgumentException("storage path should be directory");
GlobalConfig.set(confsvc, Key.StorageDirectory, dir.getAbsolutePath());
DatapathUtil.setLogDir(dir);
this.dir = dir;
}
@Override
public void createTable(String tableName) {
createTable(tableName, null);
}
@Override
public void createTable(String tableName, Map<String, String> tableMetadata) {
int id = tableRegistry.createTable(tableName, tableMetadata);
nextIds.put(id, new AtomicLong(1));
}
@Override
public void dropTable(String tableName) {
int tableId = tableRegistry.getTableId(tableName);
fileman.close(tableId);
tableRegistry.dropTable(tableName);
File dir = DatapathUtil.getDirPath(tableId);
File[] files = dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return DatapathUtil.FileType.isValidFilename(name);
}
});
for (File f : files) {
if (!f.delete())
logger.warn("kraken eventstorage: cannot delete event table [" + tableName + "] file [" + f.getAbsolutePath()
+ "]");
}
if (dir.listFiles().length == 0)
dir.delete();
nextIds.remove(tableId);
}
@Override
public long getNextId(String tableName) {
int tableId = tableRegistry.getTableId(tableName);
AtomicLong id = nextIds.get(tableId);
if (id == null) {
id = new AtomicLong(1L);
nextIds.put(tableId, id);
}
return id.getAndIncrement();
}
@Override
public void write(String tableName, EventRecord record) {
verify();
try {
int tableId = tableRegistry.getTableId(tableName);
AtomicLong id = nextIds.get(tableId);
if (id == null || id.get() <= record.getId()) {
id = new AtomicLong(record.getId() + 1);
nextIds.put(tableId, id);
}
EventWriter writer = fileman.getWriter(tableId, record.getDate(), true);
writer.write(record);
} catch (IOException e) {
logger.debug("kraken eventstorage: cannot write event record. table [" + tableName + "]", e);
throw new IllegalStateException(e);
}
}
@Override
public void write(String tableName, Collection<EventRecord> records) {
verify();
for (EventRecord record : records)
write(tableName, record);
}
@Override
public void remove(String tableName, long id) {
try {
int tableId = tableRegistry.getTableId(tableName);
EventPointerFile ptr = fileman.getPointerFile(tableId, id, true);
ptr.remove(id);
} catch (IOException e) {
logger.debug("kraken eventstorage: cannot remove event. table [" + tableName + "], id [" + id + "]", e);
throw new IllegalStateException(e);
}
}
@Override
public void remove(String tableName, Date day) {
try {
int tableId = tableRegistry.getTableId(tableName);
List<Date> dates = DatapathUtil.getLogDates(tableId);
Collections.reverse(dates);
for (Date date : dates) {
if (date.after(day))
break;
fileman.delete(tableId, date);
}
} catch (IOException e) {
logger.debug(
"kraken eventstorage: cannot remove event. table [" + tableName + "], day ["
+ DateFormat.format("yyyy-MM-dd", day) + "]", e);
throw new IllegalStateException(e);
}
}
@Override
public void flush() {
fileman.setForceFlush(true);
filemanThread.interrupt();
}
@Override
public Event getEvent(String tableName, long id) {
verify();
int tableId = tableRegistry.getTableId(tableName);
Iterator<EventReader> it = new EventFileReaderIterator(tableId);
while (it.hasNext()) {
EventReader reader = it.next();
try {
Event event = reader.read(id);
if (event != null)
return event;
} catch (IOException e) {
String day = DateFormat.format("yyyy-MM-dd", reader.getDay());
logger.error("kraken eventstorage: event read error. table [" + tableName + "] day " + day, e);
} finally {
if (reader != null)
reader.close();
}
}
return null;
}
@Override
public Collection<Event> getEvents(String tableName, int limit) {
verify();
return getEvents(tableName, 0, limit);
}
@Override
public Collection<Event> getEvents(String tableName, int offset, int limit) {
verify();
int tableId = tableRegistry.getTableId(tableName);
EventReadHelper helper = null;
try {
helper = new EventReadHelper(offset, limit);
Iterator<EventReader> it = new EventFileReaderIterator(tableId);
while (it.hasNext()) {
EventReader reader = it.next();
try {
if (reader.read(helper))
break;
} catch (IOException e) {
String day = DateFormat.format("yyyy-MM-dd", reader.getDay());
logger.error("kraken eventstorage: event read error. table [" + tableName + "] day " + day, e);
} finally {
if (reader != null)
reader.close();
}
}
} finally {
if (helper != null)
helper.close();
}
return helper.getResult();
}
private void verify() {
if (status != EventStorageStatus.Started)
throw new IllegalStateException("storage not started");
}
private class EventFileReaderIterator implements Iterator<EventReader> {
private int tableId;
private Iterator<Date> it;
public EventFileReaderIterator(int tableId) {
this.tableId = tableId;
this.it = DatapathUtil.getLogDates(tableId).iterator();
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public EventReader next() {
try {
return new EventReader(tableId, it.next(), fileman);
} catch (IOException e) {
logger.debug("kraken eventstorage: file open failed", e);
throw new IllegalStateException(e);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}