|
1 | 1 | package com.couchbase.lite.javascript;
|
2 | 2 |
|
3 |
| -import com.couchbase.lite.Database; |
4 |
| -import com.couchbase.lite.Emitter; |
5 | 3 | import com.couchbase.lite.Mapper;
|
6 | 4 | import com.couchbase.lite.Reducer;
|
7 | 5 | import com.couchbase.lite.ViewCompiler;
|
8 |
| -import com.couchbase.lite.util.Log; |
9 |
| -import com.fasterxml.jackson.databind.ObjectMapper; |
10 |
| - |
11 |
| -import org.elasticsearch.script.javascript.support.NativeList; |
12 |
| -import org.elasticsearch.script.javascript.support.NativeMap; |
13 |
| -import org.mozilla.javascript.Context; |
14 |
| -import org.mozilla.javascript.Function; |
15 |
| -import org.mozilla.javascript.NativeArray; |
16 |
| -import org.mozilla.javascript.Scriptable; |
17 |
| -import org.mozilla.javascript.WrapFactory; |
18 |
| - |
19 |
| -import java.io.IOException; |
20 |
| -import java.util.List; |
21 |
| -import java.util.Map; |
22 | 6 |
|
23 | 7 | public class JavaScriptViewCompiler implements ViewCompiler {
|
24 |
| - |
25 |
| - @Override |
26 |
| - public Mapper compileMap(String source, String language) { |
| 8 | + @Override |
| 9 | + public Mapper compileMap(String source, String language) { |
27 | 10 | if (language.equals("javascript")) {
|
28 | 11 | return new ViewMapBlockRhino(source);
|
29 | 12 | }
|
30 | 13 | throw new IllegalArgumentException(language + " is not supported");
|
31 |
| - } |
| 14 | + } |
32 | 15 |
|
33 |
| - @Override |
34 |
| - public Reducer compileReduce(String source, String language) { |
| 16 | + @Override |
| 17 | + public Reducer compileReduce(String source, String language) { |
35 | 18 | if (language.equals("javascript")) {
|
36 | 19 | return new ViewReduceBlockRhino(source);
|
37 | 20 | }
|
38 | 21 | throw new IllegalArgumentException(language + " is not supported");
|
39 |
| - } |
40 |
| - |
41 |
| -} |
42 |
| - |
43 |
| -/** |
44 |
| - * Wrap Factory for Rhino Script Engine |
45 |
| - */ |
46 |
| -class CustomWrapFactory extends WrapFactory { |
47 |
| - |
48 |
| - public CustomWrapFactory() { |
49 |
| - setJavaPrimitiveWrap(false); // RingoJS does that..., claims its annoying... |
50 |
| - } |
51 |
| - |
52 |
| - @Override |
53 |
| - public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, Object javaObject, Class staticType) { |
54 |
| - if (javaObject instanceof Map) { |
55 |
| - return new NativeMap(scope, (Map) javaObject); |
56 |
| - } |
57 |
| - else if(javaObject instanceof List) { |
58 |
| - return new NativeList(scope, (List<Object>)javaObject); |
59 |
| - } |
60 |
| - |
61 |
| - return super.wrapAsJavaObject(cx, scope, javaObject, staticType); |
62 |
| - } |
63 |
| -} |
64 |
| - |
65 |
| -// REFACT: Extract superview for both the map and reduce blocks as they do pretty much the same thing |
66 |
| - |
67 |
| -class ViewMapBlockRhino implements Mapper { |
68 |
| - |
69 |
| - private static WrapFactory wrapFactory = new CustomWrapFactory(); |
70 |
| - private Scriptable globalScope; |
71 |
| - private String src; |
72 |
| - |
73 |
| - public ViewMapBlockRhino(String src) { |
74 |
| - this.src = src; |
75 |
| - Context ctx = Context.enter(); |
76 |
| - try { |
77 |
| - ctx.setOptimizationLevel(-1); |
78 |
| - ctx.setWrapFactory(wrapFactory); |
79 |
| - globalScope = ctx.initStandardObjects(null, true); |
80 |
| - } finally { |
81 |
| - Context.exit(); |
82 |
| - } |
83 | 22 | }
|
84 |
| - |
85 |
| - @Override |
86 |
| - public void map(Map<String, Object> document, Emitter emitter) { |
87 |
| - Context ctx = Context.enter(); |
88 |
| - try { |
89 |
| - ctx.setOptimizationLevel(-1); |
90 |
| - ctx.setWrapFactory(wrapFactory); |
91 |
| - |
92 |
| - //create a place to hold results |
93 |
| - String placeHolder = "var map_results = [];"; |
94 |
| - ctx.evaluateString(globalScope, placeHolder, "placeHolder", 1, null); |
95 |
| - |
96 |
| - //register the emit function |
97 |
| - String emitFunction = "var emit = function(key, value) { map_results.push([key, value]); };"; |
98 |
| - ctx.evaluateString(globalScope, emitFunction, "emit", 1, null); |
99 |
| - |
100 |
| - //register the map function |
101 |
| - String mapSrc = "var map = " + src + ";"; |
102 |
| - try { |
103 |
| - ctx.evaluateString(globalScope, mapSrc, "map", 1, null); |
104 |
| - } catch(org.mozilla.javascript.EvaluatorException e) { |
105 |
| - // Error in the JavaScript view - CouchDB swallows the error and tries the next document |
106 |
| - // REFACT: would be nice to check this in the constructor so we don't have to reparse every time |
107 |
| - // should also be much faster if we can insert the map function into this objects globals |
108 |
| - Log.e(Database.TAG, "Javascript syntax error in view:\n" + src, e); |
109 |
| - return; |
110 |
| - } |
111 |
| - |
112 |
| - // Need to stringify the json tree, as the ContextWrapper is unable |
113 |
| - // to correctly convert nested json to their js representation. |
114 |
| - // More specifically, if a dictionary is included that contains an array as a value |
115 |
| - // that array will not be wrapped correctly but you'll get the plain |
116 |
| - // java.util.ArrayList instead - and then an error. |
117 |
| - ObjectMapper mapper = new ObjectMapper(); |
118 |
| - String json = null; |
119 |
| - try { |
120 |
| - json = mapper.writeValueAsString(document); |
121 |
| - } catch (IOException e) { |
122 |
| - // Can thrown different subclasses of IOException- but we really do not care, |
123 |
| - // as this document was unserialized from JSON, so Jackson should be able to serialize it. |
124 |
| - Log.e(Database.TAG, "Error reserializing json from the db: " + document, e); |
125 |
| - return; |
126 |
| - } |
127 |
| - |
128 |
| - String mapInvocation = "map(" + json + ");"; |
129 |
| - try { |
130 |
| - ctx.evaluateString(globalScope, mapInvocation, "map invocation", 1, null); |
131 |
| - } |
132 |
| - catch (org.mozilla.javascript.RhinoException e) { |
133 |
| - // Error in the JavaScript view - CouchDB swallows the error and tries the next document |
134 |
| - Log.e(Database.TAG, "Error in javascript view:\n" + src + "\n with document:\n" + document, e); |
135 |
| - return; |
136 |
| - } |
137 |
| - |
138 |
| - //now pull values out of the place holder and emit them |
139 |
| - NativeArray mapResults = (NativeArray)globalScope.get("map_results", globalScope); |
140 |
| - for(int i=0; i<mapResults.getLength(); i++) { |
141 |
| - NativeArray mapResultItem = (NativeArray)mapResults.get(i); |
142 |
| - if(mapResultItem.getLength() == 2) { |
143 |
| - Object key = mapResultItem.get(0); |
144 |
| - Object value = mapResultItem.get(1); |
145 |
| - emitter.emit(key, value); |
146 |
| - } else { |
147 |
| - Log.e(Database.TAG, String.format("Expected 2 element array with key and value. Got: %s", mapResultItem)); |
148 |
| - } |
149 |
| - |
150 |
| - } |
151 |
| - } finally { |
152 |
| - Context.exit(); |
153 |
| - } |
154 |
| - |
155 |
| - } |
156 |
| - |
157 | 23 | }
|
158 | 24 |
|
159 |
| -class ViewReduceBlockRhino implements Reducer { |
160 |
| - |
161 |
| - private static WrapFactory wrapFactory = new CustomWrapFactory(); |
162 |
| - private Scriptable globalScope; |
163 |
| - private String src; |
164 | 25 |
|
165 |
| - public ViewReduceBlockRhino(String src) { |
166 |
| - this.src = src; |
167 |
| - Context ctx = Context.enter(); |
168 |
| - try { |
169 |
| - ctx.setOptimizationLevel(-1); |
170 |
| - ctx.setWrapFactory(wrapFactory); |
171 |
| - globalScope = ctx.initStandardObjects(null, true); |
172 |
| - } finally { |
173 |
| - Context.exit(); |
174 |
| - } |
175 |
| - } |
176 |
| - |
177 |
| - @Override |
178 |
| - public Object reduce(List<Object> keys, List<Object> values, boolean rereduce) { |
179 |
| - Context ctx = Context.enter(); |
180 |
| - try { |
181 |
| - ctx.setOptimizationLevel(-1); |
182 |
| - ctx.setWrapFactory(wrapFactory); |
183 |
| - |
184 |
| - //register the reduce function |
185 |
| - String reduceSrc = "var reduce = " + src + ";"; |
186 |
| - ctx.evaluateString(globalScope, reduceSrc, "reduce", 1, null); |
187 |
| - |
188 |
| - //find the reduce function and execute it |
189 |
| - Function reduceFun = (Function)globalScope.get("reduce", globalScope); |
190 |
| - Object[] functionArgs = { keys, values, rereduce }; |
191 |
| - Object result = reduceFun.call(ctx, globalScope, globalScope, functionArgs); |
192 | 26 |
|
193 |
| - return result; |
194 |
| - |
195 |
| - } finally { |
196 |
| - Context.exit(); |
197 |
| - } |
198 |
| - |
199 |
| - } |
200 |
| - |
201 |
| -} |
0 commit comments