package dovetaildb.servlet;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import dovetaildb.apiservice.ApiException;
import dovetaildb.apiservice.ApiService;
import dovetaildb.dbrepository.DbRepository;
import dovetaildb.dbrepository.ParsedRequest;
import dovetaildb.dbrepository.RequestAcceptor;
import dovetaildb.scriptbridge.ScriptFunction;
import dovetaildb.scriptbridge.UniversalScriptBridge;
import dovetaildb.util.Pair;
import dovetaildb.util.Util;
public class DovetaildbServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
private static final long serialVersionUID = -8232011496563375902L;
protected DbRepository repo;
public DbRepository getRepo() { return repo; }
public void setRepo(DbRepository repo) { this.repo = repo; }
ScriptFunction globalAcceptFn = null;
public ScriptFunction getGlobalAcceptFn() { return globalAcceptFn; }
public void setGlobalAcceptFn(ScriptFunction globalAcceptFn) { this.globalAcceptFn = globalAcceptFn; }
public interface Host {
void ddbStopListening();
void ddbShutdown();
}
Host host = null;
public void setHost(Host host) {this.host = host; }
public Host getHost() { return host; }
public DovetaildbServlet() { super(); }
boolean isLoggingRequests = false;
public void setIsLoggingRequests(boolean isLoggingRequests) {
this.isLoggingRequests = isLoggingRequests;
}
public boolean isLoggingRequests() { return isLoggingRequests; }
boolean isAllowingIdParameters = false;
public void setIsAllowingIdParameters(boolean isAllowingIdParameters) {
this.isAllowingIdParameters = isAllowingIdParameters;
}
public boolean isAllowingIdParameters() { return isAllowingIdParameters; }
final int MAX_RESPONSE_LOG_LENGTH = 1024;
protected Object handle(String url, boolean insertOrUpdate, HttpServletRequest request, HttpServletResponse response) {
ParsedRequest req = new ParsedRequest(url, insertOrUpdate, request, response);
if (req.db == null) req.db = "_metadata";
RequestAcceptor acceptor = repo.getRequestAcceptor("_metadata");
if (! acceptor.accept(req) ) {
throw new ApiException("PermissionDenied","Permission denied");
// if (! req.response.isCommitted()) {
// try {
// req.response.sendError(HttpServletResponse.SC_FORBIDDEN);
// return;
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// }
}
Object returnVal = null;
ApiService api = repo.newSession(req.db);
if (req.action == "call") { // double equals ok here (both sides are from literal strings)
List arguments = (List)Util.jsonDecode(req.request.getParameter("arguments"));
returnVal = repo.invokeFunction(req.db, req.methodName, arguments.toArray());
} else if (req.action == "execute") {
String code = req.request.getParameter("code");
ArrayList<Pair<String,String>> codeFiles = new ArrayList<Pair<String,String>>(1);
codeFiles.set(0, new Pair<String, String>("<execute code>", code));
Map<String,Object> env = Util.literalMap().p("dovetaildb", api);
returnVal = (new UniversalScriptBridge()).evaluateExpression(req.scriptLanguage+":"+codeFiles, env);
} else if (req.action == "query") {
String query = req.request.getParameter("query");
returnVal = api.query(req.bagName, (List)Util.jsonDecode(query), null);
} else if (req.action == "insert") {
Map<String,Object> entry = (Map<String,Object>)Util.jsonDecode(req.request.getParameter("entry"));
if (req.id != null) {
entry.put("id", req.id);
}
api.insert(req.bagName, entry);
} else if (req.action == "update") {
Map<String,Object> entry = (Map<String,Object>)Util.jsonDecode(req.request.getParameter("entry"));
api.update(req.bagName, req.id, entry);
} else if (req.action == "remove") {
api.remove(req.bagName, req.id);
}
return returnVal;
}
@Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
shipResponse(request, response, handle(request.getPathInfo()+"/remove", false, request, response));
}
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
shipResponse(request, response, handle(request.getPathInfo()+"/update", true, request, response));
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
shipResponse(request, response, handle(request.getPathInfo()+"/query", false, request, response));
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
shipResponse(request, response, handle(request.getPathInfo(), false, request, response));
}
/*
{
if (isLoggingRequests) {
String url = request.getRequestURL().toString();
String queryString = request.getQueryString();
if (queryString != null) url += "?"+queryString;
this.log("--> "+url);
}
doIt(request, response);
}
{
if (isLoggingRequests) {
StringBuffer buf = new StringBuffer();
OUTER : for(Object eObj : request.getParameterMap().entrySet()) {
Map.Entry entry = ((Map.Entry)eObj);
for(String v : (String[])entry.getValue()) {
buf.append('&');
buf.append(URLEncoder.encode((String)entry.getKey(), "UTF-8"));
buf.append('=');
buf.append(URLEncoder.encode(v, "UTF-8"));
if (buf.length() > MAX_RESPONSE_LOG_LENGTH) {
buf.delete(MAX_RESPONSE_LOG_LENGTH, buf.length());
buf.append("...");
break OUTER;
}
}
}
String url = request.getRequestURL().toString();
buf.setCharAt(0, '?');
this.log("--> " + url + buf);
}
doIt(request, response);
}
*/
public static JSONObject toJsonError(Exception e) {
Throwable cause = e.getCause();
if (cause == null) cause = e;
JSONObject error = new JSONObject();
if (e instanceof ApiException) {
ApiException apiException = (ApiException)e;
error.put("name", apiException.exName);
error.put("message", apiException.getMessage());
if (apiException.stackTrace != null)
error.put("stacktrace", apiException.stackTrace);
} else {
error.put("name", e.getClass().toString());
error.put("message", e.toString());
}
org.json.simple.JSONArray jsonTrace = new org.json.simple.JSONArray();
for(StackTraceElement elem: cause.getStackTrace()) {
jsonTrace.add(elem.toString());
}
error.put("traceback", jsonTrace);
return error;
}
protected void shipResponse(HttpServletRequest request, HttpServletResponse response, Object results) throws ServletException, IOException {
long t0 = System.currentTimeMillis();
Map<String,Object> jsonResponse = new HashMap<String,Object>();
jsonResponse.put("version","1.1");
String txnId = request.getParameter("reqid");
if (txnId != null)
jsonResponse.put("id", txnId);
try {
jsonResponse.put("result", results);
} catch(Exception e) {
JSONObject error = toJsonError(e);
jsonResponse.put("error", error);
}
ServletOutputStream os = response.getOutputStream();
String jsonCallback = request.getParameter("callback");
os.flush();
final Writer coreWriter = new BufferedWriter(new OutputStreamWriter(os));
Writer wtr = coreWriter;
if (jsonCallback != null) {
wtr.write(jsonCallback);
wtr.write("(");
}
if (! isLoggingRequests) {
Util.jsonEncode(wtr, jsonResponse);
} else {
final StringBuffer logBuffer = new StringBuffer();
wtr = new Writer() {
public void close() throws IOException {coreWriter.close();}
public void flush() throws IOException {coreWriter.flush();}
public void write(char[] arg0, int arg1, int arg2) throws IOException {
coreWriter.write(arg0, arg1, arg2);
if (logBuffer.length() < MAX_RESPONSE_LOG_LENGTH) {
logBuffer.append(arg0, arg1, arg2);
}
}
};
Util.jsonEncode(wtr, jsonResponse);
String ret = (logBuffer.length() > MAX_RESPONSE_LOG_LENGTH) ?
logBuffer.substring(0,MAX_RESPONSE_LOG_LENGTH)+"..." : logBuffer.toString();
t0 = System.currentTimeMillis() - t0;
this.log("<-- ("+t0+" ms) "+ret);
}
if (jsonCallback != null) {
wtr.write(")");
}
wtr.flush();
}
@Override
public void destroy() {
this.repo.close();
}
}