com.almende.eve.state.ConcurrentJsonFileState Maven / Gradle / Ivy
/*
* Copyright: Almende B.V. (2014), Rotterdam, The Netherlands
* License: The Apache Software License, Version 2.0
*/
package com.almende.eve.state;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.almende.eve.rpc.jsonrpc.jackson.JOM;
import com.almende.eve.rpc.jsonrpc.jackson.JsonNullAwareDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.node.NullNode;
/**
* The Class ConcurrentJsonFileState.
*
* @class FileState
*
* A persistent state for an Eve Agent, which stores the data on disk.
* Data is stored in the path provided by the configuration file.
*
* The state provides general information for the agent (about itself,
* the environment, and the system configuration), and the agent can
* store its state in the state. The state extends a standard Java
* Map.
*
* All operations on this FileState are thread-safe. It also provides two
* aditional methods: PutIfNotChanged() and PutAllIfNotChanged().
*
* Usage:
* AgentHost factory = AgentHost.getInstance(config);
* ConcurrentFileState state = new
* ConcurrentFileState("agentId",".eveagents");
* state.put("key", "value");
* System.out.println(state.get("key")); // "value"
* @author jos
* @author ludo
*/
public class ConcurrentJsonFileState extends AbstractState {
private static final Logger LOG = Logger.getLogger("ConcurrentFileState");
private String filename = null;
private FileChannel channel = null;
private FileLock lock = null;
private InputStream fis = null;
private OutputStream fos = null;
private ObjectMapper om = null;
private static Map locked = new ConcurrentHashMap();
private final Map properties = Collections
.synchronizedMap(new HashMap());
/**
* Instantiates a new concurrent json file state.
*
* @param agentId
* the agent id
* @param filename
* the filename
*/
public ConcurrentJsonFileState(final String agentId, final String filename) {
super(agentId);
this.filename = filename;
om = JOM.getInstance();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#finalize()
*/
@Override
public void finalize() throws Throwable {
closeFile();
super.finalize();
}
/**
* Open file.
*
* @throws IOException
* Signals that an I/O exception has occurred.
*/
@SuppressWarnings("resource")
protected void openFile() throws IOException {
synchronized (locked) {
while (locked.containsKey(filename) && locked.get(filename)) {
try {
locked.wait();
} catch (final InterruptedException e) {
}
}
locked.put(filename, true);
final File file = new File(filename);
if (!file.exists()) {
locked.put(filename, false);
locked.notifyAll();
throw new IllegalStateException(
"Warning: File doesn't exist (anymore):'" + filename
+ "'");
}
channel = new RandomAccessFile(file, "rw").getChannel();
try {
// TODO: add support for shared locks, allowing parallel reading
// operations.
lock = channel.lock();
} catch (final Exception e) {
channel.close();
channel = null;
lock = null;
locked.put(filename, false);
locked.notifyAll();
throw new IllegalStateException(
"error, couldn't obtain file lock on:" + filename, e);
}
fis = Channels.newInputStream(channel);
fos = Channels.newOutputStream(channel);
}
}
/**
* Close file.
*/
protected void closeFile() {
synchronized (locked) {
if (lock != null && lock.isValid()) {
try {
lock.release();
} catch (final IOException e) {
LOG.log(Level.WARNING, "", e);
}
}
try {
if (fos != null) {
fos.close();
}
if (fis != null) {
fis.close();
}
if (channel != null) {
channel.close();
}
} catch (final IOException e) {
LOG.log(Level.WARNING, "", e);
}
channel = null;
fis = null;
fos = null;
lock = null;
locked.put(filename, false);
locked.notifyAll();
}
}
/**
* write properties to disk.
*
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private void write() throws IOException {
if (channel != null) {
channel.position(0);
}
om.writeValue(fos, properties);
fos.flush();
if (channel != null) {
channel.truncate(channel.position());
}
}
/**
* read properties from disk.
*
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws ClassNotFoundException
* the class not found exception
*/
@JsonDeserialize(using = JsonNullAwareDeserializer.class)
private void read() throws IOException, ClassNotFoundException {
try {
if (channel != null) {
channel.position(0);
}
properties.clear();
final JsonNode data = om.readTree(fis);
final Iterator> fieldIter = data.fields();
while (fieldIter.hasNext()) {
final Entry item = fieldIter.next();
properties.put(item.getKey(), item.getValue());
}
} catch (final EOFException eof) {
// empty file, new agent?
} catch (final JsonMappingException jme) {
// empty file, new agent?
}
}
/**
* init is executed once before the agent method is invoked.
*/
@Override
public void init() {
}
/**
* destroy is executed once after the agent method is invoked if the
* properties are changed, they will be saved.
*/
@Override
public void destroy() {
}
/*
* (non-Javadoc)
*
* @see com.almende.eve.state.State#clear()
*/
@Override
public synchronized void clear() {
try {
openFile();
final String agentType = properties.get(KEY_AGENT_TYPE).textValue();
properties.clear();
properties.put(KEY_AGENT_TYPE,
JOM.getInstance().valueToTree(agentType));
write();
} catch (final Exception e) {
LOG.log(Level.WARNING, "", e);
}
closeFile();
}
/*
* (non-Javadoc)
*
* @see com.almende.eve.state.State#keySet()
*/
@Override
public synchronized Set keySet() {
Set result = null;
try {
openFile();
read();
result = new HashSet(properties.keySet());
} catch (final Exception e) {
LOG.log(Level.WARNING, "", e);
}
closeFile();
return result;
}
/*
* (non-Javadoc)
*
* @see com.almende.eve.state.State#containsKey(java.lang.String)
*/
@Override
public synchronized boolean containsKey(final String key) {
boolean result = false;
try {
openFile();
read();
result = properties.containsKey(key);
} catch (final Exception e) {
LOG.log(Level.WARNING, "", e);
}
closeFile();
return result;
}
/*
* (non-Javadoc)
*
* @see com.almende.eve.state.AbstractState#get(java.lang.String)
*/
@Override
public synchronized JsonNode get(final String key) {
JsonNode result = NullNode.getInstance();
try {
openFile();
read();
result = properties.get(key);
} catch (final Exception e) {
LOG.log(Level.WARNING, "", e);
}
closeFile();
return result;
}
/*
* (non-Javadoc)
*
* @see com.almende.eve.state.AbstractState#locPut(java.lang.String,
* com.fasterxml.jackson.databind.JsonNode)
*/
@Override
public synchronized JsonNode locPut(final String key, JsonNode value) {
JsonNode result = null;
try {
openFile();
read();
if (value == null) {
value = NullNode.getInstance();
}
result = properties.put(key, value);
write();
} catch (final Exception e) {
LOG.log(Level.WARNING, "", e);
}
closeFile();
return result;
}
/*
* (non-Javadoc)
*
* @see
* com.almende.eve.state.AbstractState#locPutIfUnchanged(java.lang.String,
* com.fasterxml.jackson.databind.JsonNode,
* com.fasterxml.jackson.databind.JsonNode)
*/
@Override
public synchronized boolean locPutIfUnchanged(final String key,
final JsonNode newVal, JsonNode oldVal) {
boolean result = false;
try {
openFile();
read();
JsonNode cur = NullNode.getInstance();
if (properties.containsKey(key)) {
cur = properties.get(key);
}
if (oldVal == null) {
oldVal = NullNode.getInstance();
}
// Poor mans equality as some Numbers are compared incorrectly: e.g.
// IntNode versus LongNode
if (oldVal.equals(cur) || oldVal.toString().equals(cur.toString())) {
properties.put(key, newVal);
write();
result = true;
}
} catch (final Exception e) {
LOG.log(Level.WARNING, "", e);
// Don't let users loop if exception is thrown. They
// would get into a deadlock....
result = true;
}
closeFile();
return result;
}
/*
* (non-Javadoc)
*
* @see com.almende.eve.state.State#remove(java.lang.String)
*/
@Override
public synchronized Object remove(final String key) {
Object result = null;
try {
openFile();
read();
result = properties.remove(key);
write();
} catch (final Exception e) {
LOG.log(Level.WARNING, "", e);
}
closeFile();
return result;
}
/*
* (non-Javadoc)
*
* @see com.almende.eve.state.State#size()
*/
@Override
public synchronized int size() {
int result = -1;
try {
openFile();
read();
result = properties.size();
} catch (final Exception e) {
LOG.log(Level.WARNING, "", e);
}
closeFile();
return result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy