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