1 /**********************************************************************
2 *
3 * Project: MapServer
4 * Purpose: V8 JavaScript Engine Support
5 * Author: Alan Boudreault (aboudreault@mapgears.com)
6 *
7 **********************************************************************
8 * Copyright (c) 2013, Alan Boudreault, MapGears
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a
11 * copy of this software and associated documentation files (the "Software"),
12 * to deal in the Software without restriction, including without limitation
13 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 * and/or sell copies of the Software, and to permit persons to whom the
15 * Software is furnished to do so, subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be included in
18 * all copies of this Software or works derived from this Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 * DEALINGS IN THE SOFTWARE.
27 **********************************************************************/
28
29 #include "mapserver-config.h"
30 #ifdef USE_V8_MAPSCRIPT
31
32 #include "mapserver.h"
33 #include "v8_mapscript.h"
34
35 /* This file could be refactored in the future to encapsulate the global
36 functions and internal use functions in a class. */
37
38 /* Handler for Javascript Exceptions. Not exposed to JavaScript, used internally.
39 Most of the code from v8 shell example.
40 */
msV8ReportException(TryCatch * try_catch,const char * msg="")41 void msV8ReportException(TryCatch* try_catch, const char *msg = "")
42 {
43 HandleScope handle_scope;
44
45 if (!try_catch || !try_catch->HasCaught()) {
46 msSetError(MS_V8ERR, "%s.", "msV8ReportException()", msg);
47 return;
48 }
49
50 String::Utf8Value exception(try_catch->Exception());
51 const char* exception_string = *exception;
52 Handle<Message> message = try_catch->Message();
53 if (message.IsEmpty()) {
54 msSetError(MS_V8ERR, "Javascript Exception: %s.", "msV8ReportException()",
55 exception_string);
56 } else {
57 String::Utf8Value filename(message->GetScriptResourceName());
58 const char* filename_string = *filename;
59 int linenum = message->GetLineNumber();
60 msSetError(MS_V8ERR, "Javascript Exception: %s:%i: %s", "msV8ReportException()",
61 filename_string, linenum, exception_string);
62 String::Utf8Value sourceline(message->GetSourceLine());
63 const char* sourceline_string = *sourceline;
64 msSetError(MS_V8ERR, "Exception source line: %s", "msV8ReportException()",
65 sourceline_string);
66 String::Utf8Value stack_trace(try_catch->StackTrace());
67 if (stack_trace.length() > 0) {
68 const char* stack_trace_string = *stack_trace;
69 msSetError(MS_V8ERR, "Exception StackTrace: %s", "msV8ReportException()",
70 stack_trace_string);
71 }
72 }
73 }
74
75 /* This function load a javascript file in memory. */
msV8ReadFile(V8Context * v8context,const char * path)76 static Handle<Value> msV8ReadFile(V8Context *v8context, const char *path)
77 {
78 FILE* file = fopen(path, "rb");
79 if (file == NULL) {
80 char err[MS_MAXPATHLEN+21];
81 sprintf(err, "Error opening file: %s", path);
82 msV8ReportException(NULL, err);
83 return Handle<String>(String::New(""));
84 }
85
86 fseek(file, 0, SEEK_END);
87 int size = ftell(file);
88 rewind(file);
89
90 char* chars = new char[size + 1];
91 chars[size] = '\0';
92 for (int i = 0; i < size;) {
93 int read = static_cast<int>(fread(&chars[i], 1, size - i, file));
94 if (read == 0) {
95 delete [] chars;
96 fclose(file);
97 msDebug("msV8ReadFile: error while reading file '%s'\n", path);
98 return Undefined();
99 }
100 i += read;
101 }
102
103 fclose(file);
104 Handle<String> result = String::New(chars, size);
105 delete[] chars;
106 return result;
107 }
108
109 /* Returns a compiled javascript script. */
msV8CompileScript(Handle<String> source,Handle<String> script_name)110 static Handle<Script> msV8CompileScript(Handle<String> source, Handle<String> script_name)
111 {
112 TryCatch try_catch;
113
114 if (source.IsEmpty() || source->Length() == 0) {
115 msV8ReportException(NULL, "No source to compile");
116 return Handle<Script>();
117 }
118
119 Handle<Script> script = Script::Compile(source, script_name);
120 if (script.IsEmpty() && try_catch.HasCaught()) {
121 msV8ReportException(&try_catch);
122 }
123
124 return script;
125 }
126
127 /* Runs a compiled script */
msV8RunScript(Handle<Script> script)128 static Handle<Value> msV8RunScript(Handle<Script> script)
129 {
130 if (script.IsEmpty()) {
131 ThrowException(String::New("No script to run"));
132 return Handle<Value>();
133 }
134
135 TryCatch try_catch;
136 Handle<Value> result = script->Run();
137
138 if (result.IsEmpty() && try_catch.HasCaught()) {
139 msV8ReportException(&try_catch);
140 }
141
142 return result;
143 }
144
145 /* Execute a javascript file */
msV8ExecuteScript(const char * path,int throw_exception=MS_FALSE)146 static Handle<Value> msV8ExecuteScript(const char *path, int throw_exception = MS_FALSE)
147 {
148 char fullpath[MS_MAXPATHLEN];
149 map<string, Persistent<Script> >::iterator it;
150 Isolate *isolate = Isolate::GetCurrent();
151 V8Context *v8context = (V8Context*)isolate->GetData();
152
153 /* construct the path */
154 msBuildPath(fullpath, v8context->paths.top().c_str(), path);
155 char *filepath = msGetPath((char*)fullpath);
156 v8context->paths.push(filepath);
157 free(filepath);
158
159 Handle<Script> script;
160 it = v8context->scripts.find(fullpath);
161 if (it == v8context->scripts.end()) {
162 Handle<Value> source = msV8ReadFile(v8context, fullpath);
163 Handle<String> script_name = String::New(msStripPath((char*)path));
164 script = msV8CompileScript(source->ToString(), script_name);
165 if (script.IsEmpty()) {
166 v8context->paths.pop();
167 if (throw_exception) {
168 return ThrowException(String::New("Error compiling script"));
169 }
170 }
171 /* cache the compiled script */
172 Persistent<Script> pscript;
173 pscript.Reset(isolate, script);
174 v8context->scripts[fullpath] = pscript;
175 } else {
176 script = v8context->scripts[fullpath];
177 }
178
179 Handle<Value> result = msV8RunScript(script);
180 v8context->paths.pop();
181 if (result.IsEmpty() && throw_exception) {
182 return ThrowException(String::New("Error running script"));
183 }
184
185 return result;
186 }
187
188 /* END OF INTERNAL JAVASCRIPT FUNCTIONS */
189
190 /* JAVASCRIPT EXPOSED FUNCTIONS */
191
192 /* JavaScript Function to load javascript dependencies.
193 Exposed to JavaScript as 'require()'. */
msV8Require(const Arguments & args)194 static Handle<Value> msV8Require(const Arguments& args)
195 {
196 TryCatch try_catch;
197
198 for (int i = 0; i < args.Length(); i++) {
199 String::Utf8Value filename(args[i]);
200 msV8ExecuteScript(*filename, MS_TRUE);
201 if (try_catch.HasCaught()) {
202 return try_catch.ReThrow();
203 }
204 }
205
206 return Undefined();
207 }
208
209 /* Javascript Function print: print to debug file.
210 Exposed to JavaScript as 'print()'. */
msV8Print(const Arguments & args)211 static Handle<Value> msV8Print(const Arguments& args)
212 {
213 for (int i = 0; i < args.Length(); i++) {
214 String::Utf8Value str(args[i]);
215 msDebug("msV8Print: %s\n", *str);
216 }
217
218 return Undefined();
219 }
220
221 /* END OF JAVASCRIPT EXPOSED FUNCTIONS */
222
223 /* Create and return a v8 context. Thread safe. */
msV8CreateContext(mapObj * map)224 void msV8CreateContext(mapObj *map)
225 {
226
227 Isolate *isolate = Isolate::GetCurrent();
228 Isolate::Scope isolate_scope(isolate);
229 HandleScope handle_scope(isolate);
230
231 V8Context *v8context = new V8Context(isolate);
232
233 Handle<ObjectTemplate> global_templ = ObjectTemplate::New();
234 global_templ->Set(String::New("require"), FunctionTemplate::New(msV8Require));
235 global_templ->Set(String::New("print"), FunctionTemplate::New(msV8Print));
236 global_templ->Set(String::New("alert"), FunctionTemplate::New(msV8Print));
237
238 Handle<Context> context_ = Context::New(v8context->isolate, NULL, global_templ);
239 v8context->context.Reset(v8context->isolate, context_);
240
241 /* we have to enter the context before getting global instance */
242 Context::Scope context_scope(context_);
243 Handle<Object> global = context_->Global();
244 Shape::Initialize(global);
245 Point::Initialize(global);
246 Line::Initialize(global);
247
248 v8context->paths.push(map->mappath);
249 v8context->isolate->SetData(v8context);
250 v8context->layer = NULL;
251
252 map->v8context = (void*)v8context;
253 }
254
msV8ContextSetLayer(mapObj * map,layerObj * layer)255 void msV8ContextSetLayer(mapObj *map, layerObj *layer)
256 {
257 V8Context* v8context = V8CONTEXT(map);
258
259 if (!v8context) {
260 msSetError(MS_V8ERR, "V8 Persistent Context is not created.", "msV8ContextSetLayer()");
261 return;
262 }
263
264 v8context->layer = layer;
265 }
266
msV8FreeContextScripts(V8Context * v8context)267 static void msV8FreeContextScripts(V8Context *v8context)
268 {
269 map<string, Persistent<Script> >::iterator it;
270 for(it=v8context->scripts.begin(); it!=v8context->scripts.end(); ++it)
271 {
272 ((*it).second).Dispose();
273 }
274 }
275
msV8FreeContext(mapObj * map)276 void msV8FreeContext(mapObj *map)
277 {
278 V8Context* v8context = V8CONTEXT(map);
279 Shape::Dispose();
280 Point::Dispose();
281 Line::Dispose();
282 msV8FreeContextScripts(v8context);
283 v8context->context.Dispose();
284 delete v8context;
285 }
286
287 /* Create a V8 execution context, execute a script and return the feature
288 * style. */
msV8GetFeatureStyle(mapObj * map,const char * filename,layerObj * layer,shapeObj * shape)289 char* msV8GetFeatureStyle(mapObj *map, const char *filename, layerObj *layer, shapeObj *shape)
290 {
291 TryCatch try_catch;
292 V8Context* v8context = V8CONTEXT(map);
293 char *ret = NULL;
294
295 if (!v8context) {
296 msSetError(MS_V8ERR, "V8 Persistent Context is not created.", "msV8ReportException()");
297 return NULL;
298 }
299
300 Isolate::Scope isolate_scope(v8context->isolate);
301 HandleScope handle_scope(v8context->isolate);
302
303 /* execution context */
304 Local<Context> context = Local<Context>::New(v8context->isolate, v8context->context);
305 Context::Scope context_scope(context);
306 Handle<Object> global = context->Global();
307
308 Shape *shape_ = new Shape(shape);
309 shape_->setLayer(layer); // hack to set the attribute names, should change in future.
310 shape_->disableMemoryHandler(); /* the internal object should not be freed by the v8 GC */
311 Handle<Value> ext = External::New(shape_);
312 global->Set(String::New("shape"),
313 Shape::Constructor()->NewInstance(1, &ext));
314
315 msV8ExecuteScript(filename);
316 Handle<Value> value = global->Get(String::New("styleitem"));
317 if (value->IsUndefined()) {
318 msDebug("msV8GetFeatureStyle: Function 'styleitem' is missing.\n");
319 return ret;
320 }
321 Handle<Function> func = Handle<Function>::Cast(value);
322 Handle<Value> result = func->Call(global, 0, 0);
323 if (result.IsEmpty() && try_catch.HasCaught()) {
324 msV8ReportException(&try_catch);
325 }
326
327 if (!result.IsEmpty() && !result->IsUndefined()) {
328 String::AsciiValue ascii(result);
329 ret = msStrdup(*ascii);
330 }
331
332 return ret;
333 }
334
335 /* for geomtransform, we don't have the mapObj */
msV8TransformShape(shapeObj * shape,const char * filename)336 shapeObj *msV8TransformShape(shapeObj *shape, const char* filename)
337 {
338 TryCatch try_catch;
339 Isolate *isolate = Isolate::GetCurrent();
340 V8Context *v8context = (V8Context*)isolate->GetData();
341
342 HandleScope handle_scope(v8context->isolate);
343
344 /* execution context */
345 Local<Context> context = Local<Context>::New(v8context->isolate, v8context->context);
346 Context::Scope context_scope(context);
347 Handle<Object> global = context->Global();
348
349 Shape* shape_ = new Shape(shape);
350 shape_->setLayer(v8context->layer);
351 shape_->disableMemoryHandler();
352 Handle<Value> ext = External::New(shape_);
353 global->Set(String::New("shape"),
354 Shape::Constructor()->NewInstance(1, &ext));
355
356 msV8ExecuteScript(filename);
357 Handle<Value> value = global->Get(String::New("geomtransform"));
358 if (value->IsUndefined()) {
359 msDebug("msV8TransformShape: Function 'geomtransform' is missing.\n");
360 return NULL;
361 }
362 Handle<Function> func = Handle<Function>::Cast(value);
363 Handle<Value> result = func->Call(global, 0, 0);
364 if (result.IsEmpty() && try_catch.HasCaught()) {
365 msV8ReportException(&try_catch);
366 }
367
368 if (!result.IsEmpty() && result->IsObject()) {
369 Handle<Object> obj = result->ToObject();
370 if (obj->GetConstructorName()->Equals(String::New("shapeObj"))) {
371 Shape* new_shape = ObjectWrap::Unwrap<Shape>(result->ToObject());
372 if (shape == new_shape->get()) {
373 shapeObj *new_shape_ = (shapeObj *)msSmallMalloc(sizeof(shapeObj));
374 msInitShape(new_shape_);
375 msCopyShape(shape, new_shape_);
376 return new_shape_;
377 }
378 else {
379 new_shape->disableMemoryHandler();
380 return new_shape->get();
381 }
382 }
383 }
384
385 return NULL;
386 }
387
388 #endif /* USE_V8_MAPSCRIPT */
389