1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.springframework.extensions.webscripts.processor;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.concurrent.ConcurrentHashMap;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.mozilla.javascript.Context;
31 import org.mozilla.javascript.ImporterTopLevel;
32 import org.mozilla.javascript.Script;
33 import org.mozilla.javascript.Scriptable;
34 import org.mozilla.javascript.ScriptableObject;
35 import org.mozilla.javascript.WrapFactory;
36 import org.mozilla.javascript.WrappedException;
37 import org.springframework.extensions.surf.core.scripts.ScriptResourceHelper;
38 import org.springframework.extensions.surf.core.scripts.ScriptResourceLoader;
39 import org.springframework.extensions.surf.exception.WebScriptsPlatformException;
40 import org.springframework.extensions.webscripts.NativeMap;
41 import org.springframework.extensions.webscripts.ScriptContent;
42 import org.springframework.extensions.webscripts.ScriptValueConverter;
43 import org.springframework.extensions.webscripts.WebScriptException;
44 import org.springframework.util.FileCopyUtils;
45
46
47
48
49
50
51
52
53 public class JSScriptProcessor extends AbstractScriptProcessor implements ScriptResourceLoader
54 {
55 private static final Log logger = LogFactory.getLog(JSScriptProcessor.class);
56 private static WrapFactory wrapFactory = new PresentationWrapFactory();
57
58 private static final String PATH_CLASSPATH = "classpath:";
59
60
61 private Scriptable secureScope;
62
63
64 private Scriptable nonSecureScope;
65
66
67 private boolean compile = true;
68
69
70 private Map<String, Script> scriptCache = new ConcurrentHashMap<String, Script>(256);
71
72
73
74
75 public void setCompile(boolean compile)
76 {
77 this.compile = compile;
78 }
79
80
81
82
83 public String getExtension()
84 {
85 return "js";
86 }
87
88
89
90
91 public String getName()
92 {
93 return "javascript";
94 }
95
96
97
98
99 public void init()
100 {
101 super.init();
102
103 this.initProcessor();
104 }
105
106
107
108
109 public ScriptContent findScript(String path)
110 {
111 return getScriptLoader().getScript(path);
112 }
113
114
115
116
117 public Object executeScript(String path, Map<String, Object> model)
118 {
119
120 ScriptContent scriptLocation = findScript(path);
121 if (scriptLocation == null)
122 {
123 throw new WebScriptException("Unable to locate script " + path);
124 }
125
126 return executeScript(scriptLocation, model);
127 }
128
129
130
131
132 public Object executeScript(ScriptContent location, Map<String, Object> model)
133 {
134 try
135 {
136
137 String path = location.getPath();
138 Script script = null;
139 if (this.compile && location.isCachable())
140 {
141 script = this.scriptCache.get(path);
142 }
143 if (script == null)
144 {
145 if (logger.isDebugEnabled())
146 logger.debug("Resolving and compiling script path: " + path);
147
148
149 ByteArrayOutputStream os = new ByteArrayOutputStream();
150 FileCopyUtils.copy(location.getInputStream(), os);
151 byte[] bytes = os.toByteArray();
152 String source = new String(bytes, "UTF-8");
153 source = ScriptResourceHelper.resolveScriptImports(source, this, logger);
154
155
156 Context cx = Context.enter();
157 try
158 {
159 script = cx.compileString(source, path, 1, null);
160
161
162
163
164
165
166
167 if (this.compile && location.isCachable())
168 {
169 this.scriptCache.put(path, script);
170 }
171 }
172 finally
173 {
174 Context.exit();
175 }
176 }
177
178 return executeScriptImpl(script, model, location.isSecure());
179 }
180 catch (Throwable e)
181 {
182 throw new WebScriptException("Failed to load script '" + location.toString() + "': " + e.getMessage(), e);
183 }
184 }
185
186
187
188
189
190
191
192
193
194
195
196 public String loadScriptResource(String resource)
197 {
198 if (resource.startsWith(PATH_CLASSPATH))
199 {
200 try
201 {
202
203 String scriptClasspath = resource.substring(PATH_CLASSPATH.length());
204 InputStream stream = getClass().getClassLoader().getResource(scriptClasspath).openStream();
205 if (stream == null)
206 {
207 throw new WebScriptsPlatformException("Unable to load included script classpath resource: " + resource);
208 }
209 ByteArrayOutputStream os = new ByteArrayOutputStream();
210 FileCopyUtils.copy(stream, os);
211 byte[] bytes = os.toByteArray();
212
213 return new String(bytes, "UTF-8");
214 }
215 catch (IOException err)
216 {
217 throw new WebScriptsPlatformException("Unable to load included script classpath resource: " + resource);
218 }
219 }
220 else
221 {
222
223 ScriptContent scriptLocation = findScript(resource);
224 if (scriptLocation == null)
225 {
226 throw new WebScriptException("Unable to locate script " + resource);
227 }
228 try
229 {
230 ByteArrayOutputStream os = new ByteArrayOutputStream();
231 FileCopyUtils.copy(scriptLocation.getInputStream(), os);
232 byte[] bytes = os.toByteArray();
233 return new String(bytes, "UTF-8");
234 }
235 catch (Throwable e)
236 {
237 throw new WebScriptException(
238 "Failed to load script '" + scriptLocation.toString() + "': " + e.getMessage(), e);
239 }
240 }
241 }
242
243
244
245
246
247
248
249
250
251
252
253
254 private Object executeScriptImpl(Script script, Map<String, Object> model, boolean secure)
255 {
256
257 long startTime = 0;
258 if (logger.isDebugEnabled())
259 {
260 startTime = System.nanoTime();
261 }
262
263 Context cx = Context.enter();
264 cx.setOptimizationLevel(1);
265 try
266 {
267
268
269 cx.setWrapFactory(wrapFactory);
270 Scriptable sharedScope = secure ? this.nonSecureScope : this.secureScope;
271 Scriptable scope = cx.newObject(sharedScope);
272 scope.setPrototype(sharedScope);
273 scope.setParentScope(null);
274
275
276 if (model == null)
277 {
278 model = new HashMap<String, Object>();
279 }
280
281
282 addProcessorModelExtensions(model);
283
284
285 for (String key : model.keySet())
286 {
287 Object obj = model.get(key);
288 ScriptableObject.putProperty(scope, key, obj);
289 }
290
291
292 Object result = script.exec(cx, scope);
293 return result;
294 }
295 catch (WrappedException w)
296 {
297 Throwable err = w.getWrappedException();
298 throw new WebScriptException(err.getMessage(), err);
299 }
300 catch (Throwable e)
301 {
302 throw new WebScriptException(e.getMessage(), e);
303 }
304 finally
305 {
306 Context.exit();
307
308 if (logger.isDebugEnabled())
309 {
310 long endTime = System.nanoTime();
311 logger.debug("Time to execute script: " + (endTime - startTime)/1000000f + "ms");
312 }
313 }
314 }
315
316
317
318
319 public Object unwrapValue(Object value)
320 {
321 return ScriptValueConverter.unwrapValue(value);
322 }
323
324
325
326
327 public void reset()
328 {
329 init();
330 this.scriptCache.clear();
331 }
332
333
334
335
336 protected void initProcessor()
337 {
338
339 Context cx = Context.enter();
340 try
341 {
342 cx.setWrapFactory(wrapFactory);
343 this.secureScope = cx.initStandardObjects();
344
345
346
347
348 this.secureScope.delete("Packages");
349 this.secureScope.delete("getClass");
350 this.secureScope.delete("java");
351 }
352 finally
353 {
354 Context.exit();
355 }
356
357
358 cx = Context.enter();
359 try
360 {
361 cx.setWrapFactory(wrapFactory);
362
363
364
365 this.nonSecureScope = new ImporterTopLevel(cx);
366 }
367 finally
368 {
369 Context.exit();
370 }
371 }
372
373
374
375
376
377
378
379 public static class PresentationWrapFactory extends WrapFactory
380 {
381
382
383
384 public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, Object javaObject, Class staticType)
385 {
386 if (javaObject instanceof Map)
387 {
388 return new NativeMap(scope, (Map)javaObject);
389 }
390 return super.wrapAsJavaObject(cx, scope, javaObject, staticType);
391 }
392 }
393 }