package org.kairosdb.rollup;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import org.apache.commons.io.FileUtils;
import org.kairosdb.core.http.rest.QueryException;
import org.kairosdb.core.http.rest.json.QueryParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.kairosdb.rollup.RollupTaskChangeListener.Action;
import static org.kairosdb.util.Preconditions.checkNotNullOrEmpty;
/**
Manages access to the roll up task store
*/
public class RollUpTasksFileStore implements RollUpTasksStore
{
private static final Logger logger = LoggerFactory.getLogger(RollUpTasksFileStore.class);
private static final String FILE_NAME = "rollup.config";
private final ReentrantLock lock = new ReentrantLock();
private final QueryParser parser;
private final File configFile;
private final Map<String, RollupTask> rollups = new HashMap<String, RollupTask>();
private final CopyOnWriteArrayList<RollupTaskChangeListener> listenerList = new CopyOnWriteArrayList<RollupTaskChangeListener>();
@SuppressWarnings("ResultOfMethodCallIgnored")
@Inject
public RollUpTasksFileStore(@Named("STORE_DIRECTORY") String storeDirectory,
QueryParser parser) throws IOException, RollUpException
{
checkNotNullOrEmpty(storeDirectory);
checkNotNull(parser);
createStoreDirectory(storeDirectory);
configFile = new File(storeDirectory, FILE_NAME);
configFile.createNewFile();
this.parser = parser;
readFromFile();
}
@Override
public void write(List<RollupTask> tasks) throws RollUpException
{
checkNotNull(tasks);
List<RollupTask> added = new ArrayList<RollupTask>();
List<RollupTask> changed = new ArrayList<RollupTask>();
lock.lock();
try
{
for (RollupTask task : tasks)
{
if (rollups.containsKey(task.getId()))
{
changed.add(task);
}
else
{
added.add(task);
}
rollups.put(task.getId(), task);
}
writeTasks();
}
catch (IOException e)
{
throw new RollUpException("Failed to write roll-up tasks to " + configFile.getAbsolutePath(), e);
}
finally
{
lock.unlock();
}
notifyListeners(added, Action.ADDED);
notifyListeners(changed, Action.CHANGED);
}
private void writeTasks() throws IOException
{
FileUtils.deleteQuietly(configFile);
for (RollupTask task : rollups.values())
{
FileUtils.writeLines(configFile, ImmutableList.of(task.getJson()), true);
}
}
@Override
public List<RollupTask> read() throws RollUpException
{
lock.lock();
try
{
return new ArrayList<RollupTask>(rollups.values());
}
finally
{
lock.unlock();
}
}
private void readFromFile() throws RollUpException
{
lock.lock();
try
{
List<String> taskJson = FileUtils.readLines(configFile, Charset.forName("UTF-8"));
for (String json : taskJson)
{
try
{
RollupTask task = parser.parseRollupTask(json);
if (task != null)
rollups.put(task.getId(), task);
}
catch (QueryException e)
{
logger.error("Could no parse rollup task from json: " + json);
}
}
}
catch (IOException e)
{
throw new RollUpException("Failed to read roll-up tasks from " + configFile.getAbsolutePath(), e);
}
finally
{
lock.unlock();
}
}
@Override
public void remove(String id) throws RollUpException
{
checkNotNullOrEmpty(id);
RollupTask removed = null;
lock.lock();
try
{
removed = rollups.get(id);
if (removed != null)
{
rollups.remove(id);
writeTasks();
}
}
catch (IOException e)
{
throw new RollUpException("Failed to remove roll-up task", e);
}
finally
{
lock.unlock();
}
if (removed != null)
{
notifyListeners(Collections.singletonList(removed), Action.REMOVED);
}
}
public void addListener(RollupTaskChangeListener listener)
{
listenerList.add(listener);
}
public void notifyListeners(List<RollupTask> tasks, Action action)
{
for (RollupTask task : tasks)
{
for (RollupTaskChangeListener listener : listenerList)
{
listener.change(task, action);
}
}
}
private void createStoreDirectory(String storeDirectory) throws IOException
{
try
{
Files.createDirectory(Paths.get(storeDirectory));
}
catch (FileAlreadyExistsException ignore)
{
}
}
}