/*
* Copyright (C) 2015 Alexander Christian <alex(at)root1.de>. All rights reserved.
*
* This file is part of KnxAutomationDaemon (KAD).
*
* KAD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KAD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KAD. If not, see <http://www.gnu.org/licenses/>.
*/
package de.root1.kad.knxservice;
import de.root1.kad.utils.Utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import java.util.WeakHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author achristian
*/
public class KnxCache {
private final Logger log = LoggerFactory.getLogger(getClass());
private int purgetime;
private final Map<String, CacheEntry> cache = new WeakHashMap<>();
private TimerTask taskCacheClean = new TimerTask() {
@Override
public void run() {
synchronized (cache) {
Iterator<String> iterator = cache.keySet().iterator();
while (iterator.hasNext()) {
String address = iterator.next();
CacheEntry cacheEntry = cache.get(address);
if (System.currentTimeMillis() - cacheEntry.getLastUpdate() > purgetime) {
log.debug("Removing from cache: {}", cacheEntry);
iterator.remove();
}
}
}
}
};
private TimerTask taskCachePersist = new TimerTask() {
@Override
public void run() {
synchronized (cache) {
try {
log.debug("Persisting cache. Number of items: {}", cache.size());
if (cacheFile.exists()) {
cacheFile.delete();
}
FileOutputStream fos = new FileOutputStream(cacheFile);
ObjectOutputStream oos = new ObjectOutputStream(fos);
Collection<CacheEntry> values = cache.values();
oos.writeLong(values.size());
for (CacheEntry ce : values) {
oos.writeObject(ce);
}
oos.close();
log.debug("Persisting cache... *done*");
} catch (IOException ex) {
log.error("Error persisting cache. Deleting cache.", ex);
if (cacheFile.exists()) {
cacheFile.delete();
}
}
}
}
};
private Timer timerCacheClean = new Timer("KnxCache Cleaner");
private Timer timerPersistCache;
private boolean persist;
private int interval;
private File cacheFile = new File(Utils.getCacheDir(), "KnxCache.dat");
public KnxCache(Properties configProperties) {
purgetime = Integer.parseInt(configProperties.getProperty("knx.cache.purgetime", "480" /*8 hrs*/));
persist = Boolean.parseBoolean(configProperties.getProperty("knx.cache.persist", "false"));
/*
0 = every update
1..n == every n minute
*/
interval = Integer.parseInt(configProperties.getProperty("knx.cache.persist.interval", "15" /* 15min*/));
log.info("cache purge time: {}min", purgetime);
log.info("cache persist: {}", persist);
log.info("cache persist interval: {}min", interval);
interval *= 1000 * 60; // calc minutes to milliseconds
purgetime *= 1000 * 60;
if (persist) {
if (interval > 0) {
timerPersistCache = new Timer("KnxCache Persist");
timerPersistCache.schedule(taskCachePersist, interval, interval);
log.info("Started persistence timer");
} else {
log.info("Will persist cache on every change");
}
}
// start with 5sec delay
timerCacheClean.schedule(taskCacheClean, 5000, purgetime);
if (persist) {
try {
FileInputStream fis = new FileInputStream(cacheFile);
ObjectInputStream ois = new ObjectInputStream(fis);
long count = ois.readLong();
log.info("Filling cache with persisted data. Reading {} items from cache.", count);
for (int i = 0; i < count; i++) {
CacheEntry ce = (CacheEntry) ois.readObject();
cache.put(ce.getAddress(), ce);
}
log.debug("Done filling cache");
} catch (FileNotFoundException ex) {
log.info("No cache file present. Skip.");
} catch (ClassNotFoundException | IOException ex) {
log.error("Error reading cache. Will delete cache file", ex);
}
}
}
/**
* Get cached value
*
* @param ga groupaddress to query for
* @return cached value, or null if not found in cache
*/
public String get(String ga) {
CacheEntry ce;
synchronized (cache) {
ce = cache.get(ga);
if (ce != null && System.currentTimeMillis() - ce.getLastUpdate() < purgetime) {
log.debug("got value from cache: {}", ce);
return ce.getValue();
} else {
cache.remove(ga);
return null;
}
}
}
/**
* Update value in cache
*
* @param ga
* @param value
*/
void update(String ga, String value) {
synchronized (cache) {
CacheEntry ce = cache.get(ga);
if (ce == null) {
ce = new CacheEntry(ga, value);
cache.put(ga, ce);
}
log.debug("Updating cache({}): {} -> '{}'", cache.size(), ga, value);
ce.setValue(value);
if (persist && interval == 0) {
taskCachePersist.run();
}
}
}
}