package com.fourspaces.featherdb.views; import java.io.InputStreamReader; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import org.json.JSONException; import org.json.JSONObject; import com.fourspaces.featherdb.backend.Backend; import com.fourspaces.featherdb.document.Document; import com.fourspaces.featherdb.document.JSONDocument; import com.fourspaces.featherdb.utils.Logger; /** * * Creates new JavaScript Views... * * This will create a new javascript engine for each view instance (not optimal, but without proper nested * ScriptContext's I'm not sure how else to do it). This is <b>not thread-safe</b>. It must be called from * a single ViewRunner to lower the overhead of having a JS Engine instance for each JavaScriptView instance. * <p> * The alternative is to have an engine (and initialize it) for each document... and that's even worse. * */ @ViewType("text/javascript") public class JavaScriptView implements View { /** * */ private static final long serialVersionUID = 713522274681368650L; transient protected ScriptEngineManager manager = new ScriptEngineManager(); transient protected ScriptEngine engine = null; transient protected Backend backend; transient protected Logger log = Logger.get(JavaScriptView.class); protected String db; protected String src; public JavaScriptView(String db,String src) throws ViewException { this.db=db; this.src=src; setupEngine(); } protected void setupEngine() throws ViewException { if (engine == null) { engine = manager.getEngineByName("js"); try { engine.eval(new InputStreamReader(getClass().getResourceAsStream("json.js"))); engine.eval("function map(key,val) { _FeatherDB_retval = { 'key':key,'value':val }; }"); engine.eval("function get(id,revision,db) { return eval('('+_FeatherDB_JSVIEW.get(id,revision,db)+')'); }"); engine.eval("function toJSON(obj) { if (obj!=null && typeof obj != 'undefined') { return obj.toJSONString(); } } "); engine.eval("_FeatherDB_filter="+src); engine.put("_FeatherDB_JSVIEW",this); } catch (ScriptException e) { log.error(e); throw new ViewException(e); } } } public void setBackend(Backend backend) { this.backend=backend; } public JSONObject get(String id, String rev, String mydb) { if (mydb==null) { mydb = this.db; } Document d = null; d = backend.getDocument(mydb, id,rev); if (d!=null) { if (d instanceof JSONDocument) { return new JSONObject(d.toString()); } return new JSONObject().put("error", mydb+"/"+id+" is not a JSONDocument"); } return null; } public JSONObject filter(Document doc1) { JSONDocument doc = (JSONDocument) doc1; try { setupEngine(); } catch (ViewException e1) { log.error("Error filtering object",e1); return null; } try { engine.eval("_FeatherDB_retval = '' "); Object docJSObject = engine.eval("_FeatherDB_doc = eval('('+'"+ doc.toString()+"'+')');"); Invocable invocable = (Invocable) engine; Object retval = invocable.invokeFunction("_FeatherDB_filter",docJSObject); String json=null; if (retval != null) { json = (String) invocable.invokeFunction("toJSON",new Object[]{retval}); } else { json = (String) engine.eval("_FeatherDB_retval.toJSONString();"); } if (json!=null && !json.equals("") && !json.equals("\"\"")) { return new JSONObject(json); } } catch (ScriptException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } return null; } }