      1 /*
      2     Copyright 2008,2009
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Bianca Valentin,
      7         Alfred Wassermann,
      8         Peter Wilfahrt
      9
     10     This file is part of JSXGraph.
     11
     12     JSXGraph is free software: you can redistribute it and/or modify
     13     it under the terms of the GNU Lesser General Public License as published by
     14     the Free Software Foundation, either version 3 of the License, or
     15     (at your option) any later version.
     16
     17     JSXGraph is distributed in the hope that it will be useful,
     18     but WITHOUT ANY WARRANTY; without even the implied warranty of
     19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     20     GNU Lesser General Public License for more details.
     21
     22     You should have received a copy of the GNU Lesser General Public License
     23     along with JSXGraph. If not, see <http://www.gnu.org/licenses/>.
     24 */
     25
     26 /**
     27  * @fileoverview The JXG.Server is a wrapper for a smoother integration of server side calculations. on the
     28  * server side a python plugin system is used.
     29  */
     30
     31 /**
     32  * @namespace
     33  * JXG.Server namespace holding functions to load JXG server modules.
     34  */
     35 JXG.Server = function(){};
     36
     37 /**
     38  * This is where all of a module's handlers are accessed from. If you're loading a module named JXGModule which
     39  * provides a handler called ImaHandler, then this handler can be called by invoking JXG.Server.modules.JXGModule.ImaHandler().
     40  * @namespace
     41  */
     42 JXG.Server.modules = function(){};
     43
     44 /**
     45  * Stores all asynchronous calls to server which aren't finished yet.
     46  * @private
     47  */
     48 JXG.Server.runningCalls = {};
     49
     50 /**
     51  * Handles errors, just a default implementation, can be overwritten by you, if you want to handle errors by yourself.
     52  * @param {object} data An object holding a field of type string named message handling the error described in the message string.
     53  */
     54 JXG.Server.handleError = function(data) {
     55 	alert('error occured, server says: ' + data.message);
     56 };
     57
     58 /**
     59  * The main method of JXG.Server. Actually makes the calls to the server and parses the feedback.
     60  * @param {string} action Can be 'load' or 'exec'.
     61  * @param {function} callback Function pointer or anonymous function which takes as it's only argument an
     62  * object containing the data from the server. The fields of this object depend on the reply of the server
     63  * module. See the correspondings server module readme.
     64  * @param {object} data What is to be sent to the server.
     65  * @param {boolean} sync If the call should be synchronous or not.
     66  */
     67 JXG.Server.callServer = function(action, callback, data, sync) {
     68 	var fileurl, passdata, AJAX,
     69 	params, id, dataJSONStr,
     70 	k;
     71
     72     sync = sync || false;
     73
     74 	params = '';
     75 	for(k in data) {
     76 		params += '&' + escape(k) + '=' + escape(data[k]);
     77 	}
     78
     79 	dataJSONStr = JXG.toJSON(data);
     80
     81 	// generate id
     82 	do {
     83 		id = action + Math.floor(Math.random()*4096);
     84 	} while(typeof this.runningCalls[id] != 'undefined');
     85
     86 	// store information about the calls
     87 	this.runningCalls[id] = {action: action};
     88 	if(typeof data.module != 'undefined')
     89 		this.runningCalls[id].module = data.module;
     90
     91 	fileurl = JXG.serverBase + 'JXGServer.py';
     92     passdata = 'action=' + escape(action) + '&id=' + id + '&dataJSON=' + escape(JXG.Util.Base64.encode(dataJSONStr));
     93
     94 	this.cbp = function(d) {
     95 		var str, data,
     96 		tmp, inject, paramlist, id,
     97 		i, j;
     98
     99 		str = (new JXG.Util.Unzip(JXG.Util.Base64.decodeAsArray(d))).unzip();
    100 		if(JXG.isArray(str) && str.length > 0)
    101 			str = str[0][0];
    102
    103         if(typeof str != 'string')
    104             return;
    105
    106         data = window.JSON && window.JSON.parse ? window.JSON.parse(str) : (new Function('return ' + str))();
    107 		//data = eval("(" + str + ")");
    108
    109 		if(data.type == 'error') {
    110 			this.handleError(data);
    111 		} else if (data.type == 'response') {
    112 			id = data.id;
    113
    114 			// inject fields
    115 			for(i=0; i<data.fields.length; i++) {
    116 				tmp = data.fields[i];
    117 				//inject = tmp.namespace + ( typeof eval(tmp.namespace) == 'object' ? '.' : '.prototype.') + tmp.name + ' = ' + tmp.value;
    118                 inject = tmp.namespace + ( typeof ((new Function('return ' + tmp.namespace))()) == 'object' ? '.' : '.prototype.') + tmp.name + ' = ' + tmp.value;
    119 				//eval(inject);
    120                 (new Function(inject))();
    121 			}
    122
    123 			// inject handlers
    124 			for(i=0; i<data.handler.length; i++) {
    125 				tmp = data.handler[i];
    126 				paramlist = [];
    127
    128 				for(j=0; j<tmp.parameters.length; j++) {
    129 					paramlist[j] = '"' + tmp.parameters[j] + '": ' + tmp.parameters[j];
    130 				}
    131 				// insert subnamespace named after module.
    132 				inject = 'if(typeof JXG.Server.modules.' + this.runningCalls[id].module + ' == "undefined")' +
    133 				'JXG.Server.modules.' + this.runningCalls[id].module + ' = {};';
    134
    135 				// insert callback method which fetches and uses the server's data for calculation in JavaScript
    136 				inject += 'JXG.Server.modules.' + this.runningCalls[id].module + '.' + tmp.name + '_cb = ' + tmp.callback + ';';
    137
    138 				// insert handler as JXG.Server.modules.<module name>.<handler name>
    139 				inject += 'JXG.Server.modules.' + this.runningCalls[id].module + '.' + tmp.name + ' = function (' + tmp.parameters.join(',') + ', __JXGSERVER_CB__, __JXGSERVER_SYNC) {' +
    140 				'if(typeof __JXGSERVER_CB__ == "undefined") __JXGSERVER_CB__ = JXG.Server.modules.' + this.runningCalls[id].module + '.' + tmp.name + '_cb;' +
    141 				'var __JXGSERVER_PAR__ = {' + paramlist.join(',') + ', "module": "' + this.runningCalls[id].module + '", "handler": "' + tmp.name + '" };' +
    142 				'JXG.Server.callServer("exec", __JXGSERVER_CB__, __JXGSERVER_PAR__, __JXGSERVER_SYNC);' +
    143 				'};';
    144 				//eval(inject);
    145                 (new Function(inject))();
    146 			}
    147
    148 			delete this.runningCalls[id];
    149
    150 			// handle data
    151 			callback(data.data);
    152 		}
    153 	};
    154
    155 	// bind cbp callback method to JXG.Server to get access to JXG.Server fields from within cpb
    156 	this.cb = JXG.bind(this.cbp, this);
    157
    158     // we're using our own XMLHttpRequest object in here because of a/sync and POST
    159     if (window.XMLHttpRequest) {
    160         AJAX = new XMLHttpRequest();
    161         AJAX.overrideMimeType('text/plain; charset=iso-8859-1');
    162     } else {
    163         AJAX = new ActiveXObject("Microsoft.XMLHTTP");
    164     }
    165     if (AJAX) {
    166         // POST is required if data sent to server is too long for a url.
    167         // some browsers/http servers don't accept long urls.
    168         AJAX.open("POST", fileurl, !sync);
    169         AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    170
    171         if(!sync) {
    172             // Define function to fetch data received from server
    173             // that function returning a function is required to make this.cb known to the function.
    174             AJAX.onreadystatechange = (function(cb){ return function () {
    175                 switch(AJAX.readyState) {
    176                     // server is ready for take-off
    177                     case 4:
    178                         if(AJAX.status != 200) { }
    179                             //alert("Fehler:" + AJAX.status);
    180                         else  { // grab it and call the server callback to debase64, unzip, and parse the data
    181                             cb(AJAX.responseText);
    182                         }
    183                     break;
    184                     default:
    185                         return false;
    186                     break;
    187                 }
    188             }})(this.cb);
    189         }
    190
    191         // send the data
    192         AJAX.send(passdata);
    193         if(sync)
    194             this.cb(AJAX.responseText);
    195     } else {
    196         return false;
    197     }
    198
    199 //	JXG.FileReader.parseFileContent(fileurl, this.cb, 'raw', !sync);
    200 };
    201
    202 /**
    203  * Callback for the default action 'load'.
    204  */
    205 JXG.Server.loadModule_cb = function(data) {
    206 	var i;
    207 	for(i=0; i<data.length; i++)
    208 		alert(data[i].name + ': ' + data[i].value);
    209 };
    210
    211 /**
    212  * Loads a module from the server.
    213  * @param {string} module A string containing the module. Has to match the filename of the Python module on the server exactly including
    214  * lower and upper case letters without the file ending .py.
    215  */
    216 JXG.Server.loadModule = function(module) {
    217 	return JXG.Server.callServer('load', JXG.Server.loadModule_cb, {'module': module}, true);
    218 };
    219
    220 JXG.Server.load = JXG.Server.loadModule;
    221
    222