package dovetaildb.dbrepository;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import dovetaildb.apiservice.ApiService;
import dovetaildb.apiservice.ChangesetBuffer;
import dovetaildb.coordinator.Coordinator;
import dovetaildb.dbservice.DbService;
public abstract class StandardDbRepository implements DbRepository {
static class ScriptFile {
String code;
String language = "ECMAScript";
static final Pattern propRegex = Pattern.compile("^\\bDDB_(\\w+)\\s*=\\s*(\\S+)\\b");
static final Pattern nlRegex = Pattern.compile("[\\r\\n]+");
public ScriptFile(String code) {
this.code = code;
Matcher nlMatcher = nlRegex.matcher(code);
int firstNewline;
if (nlMatcher.find()) {
firstNewline = nlMatcher.start();
if (nlMatcher.find()) { //in the first two lines
firstNewline = nlMatcher.start();
}
} else {
firstNewline = code.length();
}
Matcher matcher = propRegex.matcher(code);
while(matcher.find()) {
if (matcher.start() > firstNewline) break;
String key = matcher.group(1);
String val = matcher.group(2);
if (key.equals("language")) language = val;
else throw new RuntimeException("Unsupported code parameter: \""+key+"\"");
}
}
}
static class ScriptEnv {
public ScriptEngineManager manager = new ScriptEngineManager();
public ArrayList<ScriptEngine> engines = new ArrayList<ScriptEngine>();
void ingestScript(String code) {
ScriptFile file = new ScriptFile(code);
ScriptEngine engine = manager.getEngineByName(file.language);
if (engine == null) throw new RuntimeException("Unsupported script language: "+file.language);
try {
engine.eval(code);
} catch (ScriptException e) {
throw new RuntimeException(e);
}
}
<T>T getInterface(Class<T> iface, Object... args) {
T impl = null;
for(ScriptEngine engine : engines) {
if (engine instanceof Invocable) {
T curImpl = ((Invocable)engine).getInterface(iface);
if (impl != null) throw new RuntimeException("The "+iface.getSimpleName()+" interface is implemented multiple times in different scripting languages.");
impl = curImpl;
}
}
return impl;
}
public Object invokeFunction(String functionName, Object[] args) {
for(ScriptEngine engine : engines) {
if (engine instanceof Invocable) {
try {
return ((Invocable)engine).invokeFunction(functionName, args);
} catch (ScriptException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
}
}
}
throw new RuntimeException("Function not defined: \""+functionName+"\"");
}
}
static ScriptEnv buildScriptEnv(Collection<String> codeFiles) {
ScriptEnv env = new ScriptEnv();
for(String code : codeFiles) {
env.ingestScript(code);
}
return env;
}
static interface WrapApiService {
public ApiService wrapApiService(ApiService apiService);
}
static class DbRecord implements Serializable {
private static final long serialVersionUID = -1307143029380559827L;
DbService db;
Map<String,String> code;
Coordinator coordinator;
transient ScriptEnv scriptEnv;
transient WrapApiService apiServiceWrapperFn;
transient RequestAcceptor requestAcceptor;
public void signalCodeChange() {
ScriptEnv scriptEnv = buildScriptEnv(code.values());
apiServiceWrapperFn = scriptEnv.getInterface(WrapApiService.class);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
signalCodeChange();
}
}
ConcurrentHashMap<String, DbRecord> repo;
public ApiService newSession(String db) {
DbRecord rec = repo.get(db);
ApiService api = new ChangesetBuffer(rec.db, rec.coordinator);
if (rec.apiServiceWrapperFn != null)
api = rec.apiServiceWrapperFn.wrapApiService(api);
return api;
}
public RequestAcceptor getRequestAcceptor(String dbName) {
DbRecord rec = repo.get(dbName);
return rec.requestAcceptor;
}
public Object invokeFunction(String dbName, String functionName, Object[] args) {
DbRecord rec = repo.get(dbName);
return rec.scriptEnv.invokeFunction(functionName, args);
}
public DbService getDbService(String db) {
return repo.get(db).db;
}
public void setDbService(String dbName, DbService dbService, Coordinator coordinator) {
DbRecord rec = new DbRecord();
rec.db = dbService;
rec.coordinator = coordinator;
repo.put(dbName, rec);
}
public boolean deleteDbService(String dbName) {
if (!repo.containsKey(dbName)) return false;
DbRecord rec = repo.get(dbName);
rec.db.drop();
DbRecord last = repo.remove(dbName);
return (last != null);
}
public void addCodeFile(String dbName, String fileName, String content) {
DbRecord rec = repo.get(dbName);
rec.code.put(fileName, content);
rec.signalCodeChange();
}
public boolean deleteCodeFile(String dbName, String fileName) {
DbRecord rec = repo.get(dbName);
boolean removed = (rec.code.remove(fileName) != null);
rec.signalCodeChange();
return removed;
}
private static final String[] EMPTY_STRING_ARRAY = new String[]{};
public String[] getCodeFilenames(String dbName) {
DbRecord rec = repo.get(dbName);
return rec.code.keySet().toArray(EMPTY_STRING_ARRAY);
}
public void close() {}
}