Merge branch 'onelab' of ssh://git.onelab.eu/git/myslice into onelab
[myslice.git] / manifoldapi / static / js / manifold.js
1 // utilities 
2 function debug_dict_keys (msg, o) {
3     var keys=[];
4     for (var k in o) keys.push(k);
5     messages.debug ("debug_dict_keys: " + msg + " keys= " + keys);
6 }
7 function debug_dict (msg, o) {
8     for (var k in o) messages.debug ("debug_dict: " + msg + " [" + k + "]=" + o[k]);
9 }
10 function debug_value (msg, value) {
11     messages.debug ("debug_value: " + msg + " " + value);
12 }
13 function debug_query (msg, query) {
14     if (query === undefined) messages.debug ("debug_query: " + msg + " -> undefined");
15     else if (query == null) messages.debug ("debug_query: " + msg + " -> null");
16     else if ('query_uuid' in query) messages.debug ("debug_query: " + msg + query.__repr());
17     else messages.debug ("debug_query: " + msg + " query= " + query);
18 }
19
20 // http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
21 Object.toType = (function toType(global) {
22   return function(obj) {
23     if (obj === global) {
24       return "global";
25     }
26     return ({}).toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
27   }
28 })(this);
29
30 /* ------------------------------------------------------------ */
31
32 // Constants that should be somehow moved to a plugin.js file
33 var FILTER_ADDED   = 1;
34 var FILTER_REMOVED = 2;
35 var CLEAR_FILTERS  = 3;
36 var FIELD_ADDED    = 4;
37 var FIELD_REMOVED  = 5;
38 var CLEAR_FIELDS   = 6;
39 var NEW_RECORD     = 7;
40 var CLEAR_RECORDS  = 8;
41
42 /**
43  * event: FIELD_STATE_CHANGED
44  *
45  * Parameters:
46  *   dict :
47  *      .state      : ???? used to be FIELD_REQUEST_ADD / FIELD_REQUEST_REMOVE
48  *      .key        : ??? the key fields of the record
49  *      .op         : the key of the record who has received an update
50  *      .value      : the new state of the record
51  */
52 var FIELD_STATE_CHANGED = 9;
53
54 var IN_PROGRESS    = 101;
55 var DONE           = 102;
56
57 /* Update requests related to subqueries */
58
59 /*
60 var SET_ADD        = 201;
61 var SET_REMOVED    = 202;
62 */
63
64 // request
65 /*
66 var FIELD_REQUEST_CHANGE  = 301;
67 var FIELD_REQUEST_ADD     = 302;
68 var FIELD_REQUEST_REMOVE  = 303;
69 var FIELD_REQUEST_ADD_RESET = 304;
70 var FIELD_REQUEST_REMOVE_RESET = 305;
71 */
72 // status (XXX Should be deprecated)
73 var FIELD_REQUEST_PENDING = 401;
74 var FIELD_REQUEST_SUCCESS = 402;
75 var FIELD_REQUEST_FAILURE = 403;
76 var STATUS_OKAY           = 404;
77 var STATUS_SET_WARNING    = 405;
78 var STATUS_ADD_WARNING    = 406;
79 var STATUS_REMOVE_WARNING = 407;
80 var STATUS_RESET          = 408;
81
82 /* Requests for query cycle */
83 var RUN_UPDATE     = 601;
84
85 /* MANIFOLD types */
86 var TYPE_VALUE  = 1;
87 var TYPE_RECORD = 2;
88 var TYPE_LIST_OF_VALUES = 3;
89 var TYPE_LIST_OF_RECORDS = 4;
90
91 /******************************************************************************
92  * QUERY STATUS (for manifold events)
93  ******************************************************************************/
94
95 var STATUS_NONE               = 500; // Query has not been started yet
96 var STATUS_GET_IN_PROGRESS    = 501; // Query has been sent, no result has been received
97 var STATUS_GET_RECEIVED       = 502; // Success
98 var STATUS_GET_ERROR          = 503; // Error
99 var STATUS_UPDATE_PENDING     = 504;
100 var STATUS_UPDATE_IN_PROGRESS = 505;
101 var STATUS_UPDATE_RECEIVED    = 506;
102 var STATUS_UPDATE_ERROR       = 507;
103
104 /******************************************************************************
105  * QUERY STATE (for query_store)
106  ******************************************************************************/
107
108 // XXX Rendundant with query status ?
109
110 var QUERY_STATE_INIT        = 0;
111 var QUERY_STATE_INPROGRESS  = 1;
112 var QUERY_STATE_DONE        = 2;
113
114 /******************************************************************************
115  * RECORD STATES (for query_store)
116  ******************************************************************************/
117
118 var STATE_SET       = 0;
119 var STATE_VALUE     = 1;
120 var STATE_WARNINGS  = 2;
121 var STATE_VISIBLE   = 3;
122
123 // ACTIONS
124 var STATE_SET_CHANGE = 0;
125 var STATE_SET_ADD    = 1;
126 var STATE_SET_REMOVE = 2;
127 var STATE_SET_CLEAR  = 3;
128
129 // STATE_SET : enum
130 var STATE_SET_IN            = 0;
131 var STATE_SET_OUT           = 1;
132 var STATE_SET_IN_PENDING    = 2;
133 var STATE_SET_OUT_PENDING   = 3;
134 var STATE_SET_IN_SUCCESS    = 4;
135 var STATE_SET_OUT_SUCCESS   = 5;
136 var STATE_SET_IN_FAILURE    = 6;
137 var STATE_SET_OUT_FAILURE   = 7;
138 var STATE_VALUE_CHANGE_PENDING    = 8;
139 var STATE_VALUE_CHANGE_SUCCESS    = 9;
140 var STATE_VALUE_CHANGE_FAILURE    = 10;
141
142 // STATE_WARNINGS : dict
143
144 // STATE_VISIBLE : boolean
145
146 /******************************************************************************
147  * CONSTRAINTS
148  ******************************************************************************/
149
150 var CONSTRAINT_RESERVABLE_LEASE     = 0;
151
152 var CONSTRAINT_RESERVABLE_LEASE_MSG = "Configuration required: this resource needs to be scheduled";
153
154 // A structure for storing queries
155
156 function QueryExt(query, parent_query_ext, main_query_ext, update_query_ext, disabled, domain_query_ext) {
157
158     /* Constructor */
159     if (typeof query == "undefined")
160         throw "Must pass a query in QueryExt constructor";
161     this.query                 = query;
162     this.parent_query_ext      = (typeof parent_query_ext      == "undefined") ? null  : parent_query_ext;
163     this.main_query_ext        = (typeof main_query_ext        == "undefined") ? null  : main_query_ext;
164     this.update_query_ext      = (typeof update_query_ext      == "undefined") ? null  : update_query_ext;
165     this.update_query_orig_ext = (typeof update_query_orig_ext == "undefined") ? null  : update_query_orig_ext;
166     this.disabled              = (typeof disabled              == "undefined") ? false : disabled;
167
168     // A domain query is a query that is issued to retrieve all possible values for a set
169     // eg. all resources that can be attached to a slice
170     // It is null unless we are a subquery for which a domain query has been issued
171     this.domain_query_ext      = (typeof domain_query_ext      == "undefined") ? null  : domain_query_ext;
172
173     // Set members to buffer until the domain query is completed
174     // A list of keys
175     this.set_members = [];
176
177     // The set query is the query for which the domain query has been issued.
178     // It is null unless the query is a domain query
179     this.set_query_ext         = (typeof set_query_ext         == "undefined") ? null  : domain_query_ext;
180     
181     this.query_state = QUERY_STATE_INIT;
182
183     // Results from a query consists in a dict that maps keys to records
184     this.records = new Hashtable();
185
186     // Status is a dict that maps keys to record status
187     this.state = new Hashtable();
188
189     // Filters that impact visibility in the local interface
190     this.filters = [];
191
192     // XXX Until we find a better solution
193     this.num_pending = 0;
194     this.num_unconfigured = 0;
195
196     // update_query null unless we are a main_query (aka parent_query == null); only main_query_fields can be updated...
197 }
198
199 function QueryStore() {
200
201     this.main_queries     = {};
202     this.analyzed_queries = {};
203
204     /* Insertion */
205
206     this.insert = function(query) {
207         // We expect only main_queries are inserted
208         
209         /* If the query has not been analyzed, then we analyze it */
210         if (query.analyzed_query == null) {
211             query.analyze_subqueries();
212         }
213
214         /* We prepare the update query corresponding to the main query and store both */
215         /* Note: they have the same UUID */
216
217         // XXX query.change_action() should become deprecated
218         update_query = query.clone();
219         update_query.action = 'update';
220         update_query.analyzed_query.action = 'update';
221         update_query.params = {};
222         update_query_ext = new QueryExt(update_query);
223
224         /* We remember the original query to be able to reset it */
225         update_query_orig_ext = new QueryExt(update_query.clone());
226
227
228         /* We store the main query */
229         query_ext = new QueryExt(query, null, null, update_query_ext, update_query_orig_ext, false);
230         manifold.query_store.main_queries[query.query_uuid] = query_ext;
231         /* Note: the update query does not have an entry! */
232
233
234         // The query is disabled; since it is incomplete until we know the content of the set of subqueries
235         // XXX unless we have no subqueries ???
236         // we will complete with params when records are received... this has to be done by the manager
237         // SET_ADD, SET_REMOVE will change the status of the elements of the set
238         // UPDATE will change also, etc.
239         // XXX We need a proper structure to store this information...
240
241         // We also need to insert all queries and subqueries from the analyzed_query
242         // XXX We need the root of all subqueries
243         query.iter_subqueries(function(sq, data, parent_query) {
244             var parent_query_ext;
245             if (parent_query) {
246                 parent_query_ext = manifold.query_store.find_analyzed_query_ext(parent_query.query_uuid);
247             } else {
248                 parent_query_ext = null;
249             }
250             // XXX parent_query_ext == false
251             // XXX main.subqueries = {} # Normal, we need analyzed_query
252             sq_ext = new QueryExt(sq, parent_query_ext, query_ext)
253
254             if (parent_query) {
255                 /* Let's issue a query for the subquery domain. This query will not need any update etc.
256                    eg. for resources in a slice, we also query all resources */
257                 var all_fields = manifold.metadata.get_field_names(sq.object);
258                 var domain_query = new ManifoldQuery('get', sq.object, 'now', [], {}, all_fields); 
259                 //var domain_query = new ManifoldQuery('get', sq.object); 
260
261                 console.log("Created domain query", domain_query);
262                 var domain_query_ext = new QueryExt(domain_query);
263
264                 domain_query_ext.set_query_ext = sq_ext;
265                 sq_ext.domain_query_ext = domain_query_ext;
266
267                 // One of these two is useless ?
268                 manifold.query_store.main_queries[domain_query.query_uuid] = domain_query_ext;
269                 manifold.query_store.analyzed_queries[domain_query.query_uuid] = domain_query_ext;
270
271                 // XXX This query is run before the plugins are initialized and listening
272                 manifold.run_query(domain_query);
273             }
274
275             manifold.query_store.analyzed_queries[sq.query_uuid] = sq_ext;
276         });
277
278         // XXX We have spurious update queries...
279     }
280
281     /* Searching */
282
283     this.find_query_ext = function(query_uuid)
284     {
285         return this.main_queries[query_uuid];
286     }
287
288     this.find_query = function(query_uuid) 
289     {
290         return this.find_query_ext(query_uuid).query;
291     }
292
293     this.find_analyzed_query_ext = function(query_uuid)
294     {
295         return this.analyzed_queries[query_uuid];
296     }
297
298     this.find_analyzed_query = function(query_uuid) 
299     {
300         return this.find_analyzed_query_ext(query_uuid).query;
301     }
302
303     this.state_dict_create = function(default_set)
304     {
305         default_set = (default_set === undefined) ? STATE_SET_OUT : default_set;
306         var state_dict = {};
307         // We cannot use constants in literal definition, so...
308         state_dict[STATE_WARNINGS] = {};
309         state_dict[STATE_SET] = default_set;
310         state_dict[STATE_VISIBLE] = true;
311         return state_dict;
312     }
313
314     // RECORDS
315
316     this.set_records = function(query_uuid, records, default_set)
317     {
318         default_set = (default_set === undefined) ? STATE_SET_OUT : default_set;
319
320         var self = this;
321         var query_ext = this.find_analyzed_query_ext(query_uuid);
322         var record_key = manifold.metadata.get_key(query_ext.query.object);
323         $.each(records, function(i, record) {
324             var key = manifold.metadata.get_key(query_ext.query.object);
325             // ["start_time", "resource", "end_time"]
326             // ["urn"]
327             
328             var record_key_value = manifold.record_get_value(record, record_key);
329             query_ext.records.put(record_key_value, record);
330
331             if (!(query_ext.state.get(record_key_value)))
332                 query_ext.state.put(record_key_value, self.state_dict_create(default_set));
333         });
334     }
335
336     this.get_records = function(query_uuid)
337     {
338         var query_ext = this.find_analyzed_query_ext(query_uuid);
339         return query_ext.records.values();
340     }
341
342     this.get_record = function(query_uuid, record_key)
343     {
344         var query_ext = this.find_analyzed_query_ext(query_uuid);
345         return query_ext.records.get(record_key);
346     }
347
348     this.add_record = function(query_uuid, record, new_state)
349     {
350         var query_ext, key, record_key;
351         query_ext = this.find_analyzed_query_ext(query_uuid);
352         
353         if (typeof(record) == 'object') {
354             key = manifold.metadata.get_key(query_ext.query.object);
355             record_key = manifold.record_get_value(record, key);
356         } else {
357             record_key = record;
358         }
359
360         var record_entry = query_ext.records.get(record_key);
361         if (!record_entry)
362             query_ext.records.put(record_key, record);
363
364         manifold.query_store.set_record_state(query_uuid, record_key, STATE_SET, new_state);
365     }
366
367     this.remove_record = function(query_uuid, record, new_state)
368     {
369         var query_ext, key, record_key;
370         query_ext = this.find_analyzed_query_ext(query_uuid);
371         
372         if (typeof(record) == 'object') {
373             key = manifold.metadata.get_key(query_ext.query.object);
374             record_key = manifold.record_get_value(record, key);
375         } else {
376             record_key = record;
377         }
378
379         manifold.query_store.set_record_state(query_uuid, record_key, STATE_SET, new_state);
380     }
381
382     this.iter_records = function(query_uuid, callback)
383     {
384         var query_ext = this.find_analyzed_query_ext(query_uuid);
385         query_ext.records.each(callback);
386         //callback = function(record_key, record)
387     }
388
389     this.iter_visible_records = function(query_uuid, callback)
390     {
391         var query_ext = this.find_analyzed_query_ext(query_uuid);
392         query_ext.records.each(function(record_key, record) {
393             if (query_ext.state.get(record_key)[STATE_VISIBLE]) // .STATE_VISIBLE would be for the string key
394                 callback(record_key, record);
395         });
396         //callback = function(record_key, record)
397
398     }
399
400     // STATE
401
402     this.set_record_state = function(query_uuid, result_key, state, value)
403     {
404         var query_ext = this.find_analyzed_query_ext(query_uuid);
405         var state_dict = query_ext.state.get(result_key);
406         if (!state_dict)
407             state_dict = this.state_dict_create();
408
409         state_dict[state] = value;
410
411         query_ext.state.put(result_key, state_dict);
412     }
413
414     this.get_record_state = function(query_uuid, result_key, state)
415     {
416         var query_ext = this.find_analyzed_query_ext(query_uuid);
417         var state_dict = query_ext.state.get(result_key);
418         if (!state_dict)
419             return null;
420         return state_dict[state];
421     }
422
423     // FILTERS
424
425     this.add_filter = function(query_uuid, filter)
426     {
427         var query_ext = this.find_analyzed_query_ext(query_uuid);
428         // XXX When we update a filter
429         query_ext.filters.push(filter);
430
431         this.apply_filters(query_uuid);
432
433     }
434
435     this.update_filter = function(query_uuid, filter)
436     {
437         // XXX
438
439         this.apply_filters(query_uuid);
440     }
441
442     this.remove_filter = function(query_uuid, filter)
443     {
444         var query_ext = this.find_analyzed_query_ext(query_uuid);
445         query_ext.filters = $.grep(query_ext.filters, function(x) {
446             return x == filter;
447         });
448
449         this.apply_filters(query_uuid);
450     }
451
452     this.get_filters = function(query_uuid)
453     {
454         var query_ext = this.find_analyzed_query_ext(query_uuid);
455         return query_ext.filters;
456     }
457
458     this.recount = function(query_uuid)
459     {
460         var query_ext;
461         var is_reserved, is_pending, in_set,  is_unconfigured;
462
463         query_ext = manifold.query_store.find_analyzed_query_ext(query_uuid);
464         query_ext.num_pending = 0;
465         query_ext.num_unconfigured = 0;
466
467         this.iter_records(query_uuid, function(record_key, record) {
468             var record_state = manifold.query_store.get_record_state(query_uuid, record_key, STATE_SET);
469             var record_warnings = manifold.query_store.get_record_state(query_uuid, record_key, STATE_WARNINGS);
470
471             is_reserved = (record_state == STATE_SET_IN) 
472                        || (record_state == STATE_SET_OUT_PENDING)
473                        || (record_state == STATE_SET_IN_SUCCESS)
474                        || (record_state == STATE_SET_OUT_FAILURE);
475
476             is_pending = (record_state == STATE_SET_IN_PENDING) 
477                       || (record_state == STATE_SET_OUT_PENDING);
478
479             in_set = (record_state == STATE_SET_IN) // should not have warnings
480                   || (record_state == STATE_SET_IN_PENDING)
481                   || (record_state == STATE_SET_IN_SUCCESS)
482                   || (record_state == STATE_SET_OUT_FAILURE); // should not have warnings
483
484             is_unconfigured = (in_set && !$.isEmptyObject(record_warnings));
485
486             /* Let's update num_pending and num_unconfigured at this stage */
487             if (is_pending)
488                 query_ext.num_pending++;
489             if (is_unconfigured)
490                 query_ext.num_unconfigured++;
491         });
492
493     }
494
495     this.apply_filters = function(query_uuid)
496     {
497         var start = new Date().getTime();
498
499         // Toggle visibility of records according to the different filters.
500
501         var self = this;
502         var filters = this.get_filters(query_uuid);
503         var col_value;
504         /* Let's update num_pending and num_unconfigured at this stage */
505
506         // Adapted from querytable._querytable_filter()
507
508         this.iter_records(query_uuid, function(record_key, record) {
509             var is_reserved, is_pending, in_set,  is_unconfigured;
510             var visible = true;
511
512             var record_state = manifold.query_store.get_record_state(query_uuid, record_key, STATE_SET);
513             var record_warnings = manifold.query_store.get_record_state(query_uuid, record_key, STATE_WARNINGS);
514
515             is_reserved = (record_state == STATE_SET_IN) 
516                        || (record_state == STATE_SET_OUT_PENDING)
517                        || (record_state == STATE_SET_IN_SUCCESS)
518                        || (record_state == STATE_SET_OUT_FAILURE);
519
520             is_pending = (record_state == STATE_SET_IN_PENDING) 
521                       || (record_state == STATE_SET_OUT_PENDING);
522
523             in_set = (record_state == STATE_SET_IN) // should not have warnings
524                   || (record_state == STATE_SET_IN_PENDING)
525                   || (record_state == STATE_SET_IN_SUCCESS)
526                   || (record_state == STATE_SET_OUT_FAILURE); // should not have warnings
527
528             is_unconfigured = (in_set && !$.isEmptyObject(record_warnings));
529
530             // We go through each filter and decide whether it affects the visibility of the record
531             $.each(filters, function(index, filter) {
532                 var key = filter[0];
533                 var op = filter[1];
534                 var value = filter[2];
535
536
537                 /* We do some special handling for the manifold:status filter
538                  * predicates. */
539
540                 if (key == 'manifold:status') {
541                     if (op != '=' && op != '==') {
542                         // Unsupported filter, let's ignore it
543                         console.log("Unsupported filter on manifold:status. Should be EQUAL only.");
544                         return true; // ~ continue
545                     }
546
547                     switch (value) {
548                         case 'reserved':
549                             // true  => ~ continue
550                             // false => ~ break
551                             visible = is_reserved;
552                             return visible;
553                         case 'unconfigured':
554                             visible = is_unconfigured;
555                             return visible;
556                         case 'pending':
557                             visible = is_pending;
558                             return visible;
559                     }
560                     return false; // ~ break
561                 }
562
563                 /* Normal filtering behaviour (according to the record content) follows... */
564                 col_value = manifold.record_get_value(record, key);
565
566                 // When the filter does not match, we hide the column by default
567                 if (col_value === 'undefined') {
568                     visible = false;
569                     return false; // ~ break
570                 }
571
572                 // XXX This should accept pluggable filtering functions.
573
574
575                 /* Test whether current filter is compatible with the column */
576                 if (op == '=' || op == '==') {
577                     if ( col_value != value || col_value==null || col_value=="" || col_value=="n/a")
578                         visible = false;
579                 }else if (op == 'included') {
580                     $.each(value, function(i,x) {
581                       if(x == col_value){
582                           visible = true;
583                           return false; // ~ break
584                       }else{
585                           visible = false;
586                       }
587                     });
588                 }else if (op == '!=') {
589                     if ( col_value == value || col_value==null || col_value=="" || col_value=="n/a")
590                         visible = false;
591                 } else if(op=='<') {
592                     if ( parseFloat(col_value) >= value || col_value==null || col_value=="" || col_value=="n/a")
593                         visible = false;
594                 } else if(op=='>') {
595                     if ( parseFloat(col_value) <= value || col_value==null || col_value=="" || col_value=="n/a")
596                         visible = false;
597                 } else if(op=='<=' || op=='≤') {
598                     if ( parseFloat(col_value) > value || col_value==null || col_value=="" || col_value=="n/a")
599                         visible = false;
600                 } else if(op=='>=' || op=='≥') {
601                     if ( parseFloat(col_value) < value || col_value==null || col_value=="" || col_value=="n/a")
602                         visible = false;
603                 }else{
604                     // How to break out of a loop ?
605                     alert("filter not supported");
606                     return false; // break
607                 }
608
609             });
610
611             // Set the visibility status in the query store
612             self.set_record_state(query_uuid, record_key, STATE_VISIBLE, visible);
613         });
614
615         var end = new Date().getTime();
616         console.log("APPLY FILTERS took", end - start, "ms");
617
618     }
619
620 }
621
622 /*!
623  * This namespace holds functions for globally managing query objects
624  * \Class Manifold
625  */
626 var manifold = {
627
628     /************************************************************************** 
629      * Helper functions
630      **************************************************************************/ 
631
632     separator: '__',
633
634     get_type: function(variable) {
635         switch(Object.toType(variable)) {
636             case 'number':
637             case 'string':
638                 return TYPE_VALUE;
639             case 'object':
640                 return TYPE_RECORD;
641             case 'array':
642                 if ((variable.length > 0) && (Object.toType(variable[0]) === 'object'))
643                     return TYPE_LIST_OF_RECORDS;
644                 else
645                     return TYPE_LIST_OF_VALUES;
646         }
647     },
648
649     /**
650      *  Args:
651      *      fields: A String instance (field name), or a set of String instances
652      *          (field names) # XXX tuple !!
653      *  Returns:
654      *      If fields is a String,  return the corresponding value.
655      *      If fields is a set, return a tuple of corresponding value.
656      *
657      *  Raises:
658      *      KeyError if at least one of the fields is not found
659      */
660     record_get_value: function(record, fields) 
661     {
662         if (typeof(fields) === 'string') {
663             if (fields.indexOf('.') != -1) {
664                 key_subkey = key.split('.', 2);
665                 key     = key_subkey[0]; 
666                 subkey  = key_subkey[1];
667
668                 if (record.indexOf(key) == -1) {
669                     return null;
670                 }
671                 // Tests if the following is an array (typeof would give object)
672                 if (Object.prototype.toString.call(record[key]) === '[object Array]') {
673                     // Records
674                     return $.map(record[key], function(subrecord) { return manifold.record_get_value(subrecord, subkey) });
675                 } else if (typeof(record) == 'object') {
676                     // Record
677                     return manifold.record_get_value(record[key], subkey);
678                 } else {
679                     console.log('Unknown field');
680                 }
681             } else {
682                 return record[fields];
683             }
684         } else {
685             // see. get_map_entries
686             if (fields.length == 1)
687                 return manifold.record_get_value(record, fields[0])
688
689             // Build a new record
690             var ret = {};
691             $.each(fields, function(i, field) {
692                 ret[field] = manifold.record_get_value(record, field);
693             });
694             ret.hashCode = record.hashCode;
695             ret.equals = record.equals;
696             return ret;
697             // this was an array, we want a dictionary
698             //return $.map(fields, function(x) { manifold.record_get_value(record, x) });
699                 
700         }
701     },
702
703     record_hashcode: function(key_fields)
704     {
705         return function() {
706             ret = "";
707             for (i=0; i < key_fields.length; i++)
708                 ret += "@@" + this[key_fields[i]];
709             return ret;
710         };
711     },
712
713     record_equals: function(key_fields)
714     {
715         var self = this;
716
717         return function(other) {
718             for (i=0; i < key_fields.length; i++) {
719                 var this_value  = this[key_fields[i]];
720                 var other_value = other[key_fields[i]];
721
722                 var this_type = self.get_type(this_value);
723                 var other_type = self.get_type(other_value);
724                 if (this_type != other_type)
725                     return false;
726
727                 switch (this_type) {
728                     case TYPE_VALUE:
729                     case TYPE_LIST_OF_VALUES:
730                         if (this_value != other_value)
731                             return false;
732                         break;
733                     case TYPE_RECORD:
734                         if (!(record_equals(this_value, other_value)))
735                             return false;
736                         break;
737                     case TYPE_LIST_OF_RECORDS:
738                         if (this_value.length != other_value.length)
739                             return false;
740                         for (i = 0; i < this_value.length; i++)
741                             if (!(record_equals(this_value, other_value)))
742                                 return false;
743                         break;
744                 }
745             }
746             return true;
747         };
748     },
749
750
751     /************************************************************************** 
752      * Metadata management
753      **************************************************************************/ 
754
755      metadata: {
756
757         get_table: function(method) {
758             var table = MANIFOLD_METADATA[method];
759             return (typeof table === 'undefined') ? null : table;
760         },
761
762         get_columns: function(method) {
763             var table = this.get_table(method);
764             if (!table) {
765                 return null;
766             }
767
768             return (typeof table.column === 'undefined') ? null : table.column;
769         },
770
771         get_field_names: function(method)
772         {
773             var columns = this.get_columns(method);
774             if (!columns)
775                 return null;
776             return $.map(columns, function (x) { return x.name });
777         },
778
779         get_key: function(method) {
780             var table = this.get_table(method);
781             if (!table)
782                 return null;
783
784             return (typeof table.key === 'undefined') ? null : table.key;
785         },
786
787
788         get_column: function(method, name) {
789             var columns = this.get_columns(method);
790             if (!columns)
791                 return null;
792
793             $.each(columns, function(i, c) {
794                 if (c.name == name)
795                     return c
796             });
797             return null;
798         },
799
800         get_type: function(method, name) {
801             var table = this.get_table(method);
802             if (!table)
803                 return null;
804
805             var match = $.grep(table.column, function(x) { return x.name == name });
806             if (match.length == 0) {
807                 return undefined;
808             } else {
809                 return match[0].type;
810             }
811             return (typeof table.type === 'undefined') ? null : table.type;
812         }
813
814      },
815
816     /************************************************************************** 
817      * Query management
818      **************************************************************************/ 
819
820     query_store: new QueryStore(),
821
822     // XXX Remaining functions are deprecated since they are replaced by the query store
823
824     /*!
825      * Associative array storing the set of queries active on the page
826      * \memberof Manifold
827      */
828     all_queries: {},
829
830     /*!
831      * Insert a query in the global hash table associating uuids to queries.
832      * If the query has no been analyzed yet, let's do it.
833      * \fn insert_query(query)
834      * \memberof Manifold
835      * \param ManifoldQuery query Query to be added
836      */
837     insert_query : function (query) { 
838         // NEW API
839         manifold.query_store.insert(query);
840
841         // Run
842         $(document).ready(function() {
843         manifold.run_query(query);
844         });
845
846         // FORMER API
847         if (query.analyzed_query == null) {
848             query.analyze_subqueries();
849         }
850         manifold.all_queries[query.query_uuid]=query;
851     },
852
853     /*!
854      * Returns the query associated to a UUID
855      * \fn find_query(query_uuid)
856      * \memberof Manifold
857      * \param string query_uuid The UUID of the query to be returned
858      */
859     find_query : function (query_uuid) { 
860         return manifold.all_queries[query_uuid];
861     },
862
863     /************************************************************************** 
864      * Query execution
865      **************************************************************************/ 
866
867     // trigger a query asynchroneously
868     proxy_url : '/manifold/proxy/json/',
869
870     // reasonably low-noise, shows manifold requests coming in and out
871     asynchroneous_debug : true,
872     // print our more details on result publication and related callbacks
873     pubsub_debug : false,
874
875     /**
876      * \brief We use js function closure to be able to pass the query (array)
877      * to the callback function used when data is received
878      */
879     success_closure: function(query, publish_uuid, callback) {
880         return function(data, textStatus) {
881             manifold.asynchroneous_success(data, query, publish_uuid, callback);
882         }
883     },
884
885     run_query: function(query, callback)
886         {
887         // default value for callback = null
888         if (typeof callback === 'undefined')
889             callback = null; 
890
891         var query_ext = manifold.query_store.find_query_ext(query.query_uuid);
892         query_ext.query_state = QUERY_STATE_INPROGRESS;
893
894         var query_json = JSON.stringify(query);
895
896         // Inform plugins about the progress
897         query.iter_subqueries(function (sq) {
898             var sq_query_ext = manifold.query_store.find_analyzed_query_ext(sq.query_uuid);
899             sq_query_ext.query_state = QUERY_STATE_INPROGRESS;
900
901             manifold.raise_record_event(sq.query_uuid, IN_PROGRESS);
902         });
903
904
905         $.post(manifold.proxy_url, {'json': query_json} , manifold.success_closure(query, null, callback));
906     },
907
908     // XXX DEPRECATED
909     // Executes all async. queries - intended for the javascript header to initialize queries
910     // input queries are specified as a list of {'query_uuid': <query_uuid> }
911     // each plugin is responsible for managing its spinner through on_query_in_progress
912     asynchroneous_exec : function (query_exec_tuples) {
913         
914         // Loop through input array, and use publish_uuid to publish back results
915         $.each(query_exec_tuples, function(index, tuple) {
916             var query=manifold.find_query(tuple.query_uuid);
917             var query_json=JSON.stringify (query);
918             var publish_uuid=tuple.publish_uuid;
919             // by default we publish using the same uuid of course
920             if (publish_uuid==undefined) publish_uuid=query.query_uuid;
921             if (manifold.pubsub_debug) {
922                 messages.debug("sending POST on " + manifold.proxy_url + query.__repr());
923             }
924
925             query.iter_subqueries(function (sq) {
926                 manifold.raise_record_event(sq.query_uuid, IN_PROGRESS);
927             });
928
929             // not quite sure what happens if we send a string directly, as POST data is named..
930             // this gets reconstructed on the proxy side with ManifoldQuery.fill_from_POST
931             $.post(manifold.proxy_url, {'json':query_json}, 
932                    manifold.success_closure(query, publish_uuid, tuple.callback));
933         })
934     },
935
936     /**
937      * \brief Forward a query to the manifold backend
938      * \param query (dict) the query to be executed asynchronously
939      * \param callback (function) the function to be called when the query terminates
940      */
941     forward: function(query, callback) {
942         var query_json = JSON.stringify(query);
943         $.post(manifold.proxy_url, {'json': query_json} , 
944                manifold.success_closure(query, query.query_uuid, callback));
945     },
946
947     /*!
948      * Returns whether a query expects a unique results.
949      * This is the case when the filters contain a key of the object
950      * \fn query_expects_unique_result(query)
951      * \memberof Manifold
952      * \param ManifoldQuery query Query for which we are testing whether it expects a unique result
953      */
954     query_expects_unique_result: function(query) {
955         /* XXX we need functions to query metadata */
956         //var keys = MANIFOLD_METADATA[query.object]['keys']; /* array of array of field names */
957         /* TODO requires keys in metadata */
958         return true;
959     },
960
961     /*!
962      * Publish result
963      * \fn publish_result(query, results)
964      * \memberof Manifold
965      * \param ManifoldQuery query Query which has received results
966      * \param array results results corresponding to query
967      */
968     publish_result: function(query, result) {
969         if (typeof result === 'undefined')
970             result = [];
971
972         // NEW PLUGIN API
973         manifold.raise_record_event(query.query_uuid, CLEAR_RECORDS);
974         if (manifold.pubsub_debug)
975             messages.debug(".. publish_result (1) ");
976         var count=0;
977         $.each(result, function(i, record) {
978             manifold.raise_record_event(query.query_uuid, NEW_RECORD, record);
979             count += 1;
980         });
981         if (manifold.pubsub_debug) 
982             messages.debug(".. publish_result (2) has used NEW API on " + count + " records");
983         manifold.raise_record_event(query.query_uuid, DONE);
984         if (manifold.pubsub_debug) 
985             messages.debug(".. publish_result (3) has used NEW API to say DONE");
986
987         // OLD PLUGIN API BELOW
988         /* Publish an update announce */
989         var channel="/results/" + query.query_uuid + "/changed";
990         if (manifold.pubsub_debug) 
991             messages.debug(".. publish_result (4) OLD API on channel" + channel);
992
993         $.publish(channel, [result, query]);
994
995         if (manifold.pubsub_debug) 
996             messages.debug(".. publish_result (5) END q=" + query.__repr());
997     },
998
999     store_records: function(query, records) {
1000         // Store records
1001         var query_ext = manifold.query_store.find_analyzed_query_ext(query.query_uuid);
1002         if (query_ext.set_query_ext) {
1003             // We have a domain query
1004             // The results are stored in the corresponding set_query
1005             manifold.query_store.set_records(query_ext.set_query_ext.query.query_uuid, records)
1006             
1007         } else if (query_ext.domain_query_ext) {
1008             // We have a set query, it is only used to determine which objects are in the set, we should only retrieve the key
1009             // Has it a domain query, and has it completed ?
1010             $.each(records, function(i, record) {
1011                 var key = manifold.metadata.get_key(query.object);
1012                 var record_key = manifold.record_get_value(record, key);
1013                 manifold.query_store.set_record_state(query.query_uuid, record_key, STATE_SET, STATE_SET_IN);
1014             });
1015
1016         } else {
1017             // We have a normal query
1018             manifold.query_store.set_records(query.query_uuid, records, STATE_SET_IN);
1019         }
1020     },
1021
1022     /*!
1023      * Recursively publish result
1024      * \fn publish_result_rec(query, result)
1025      * \memberof Manifold
1026      * \param ManifoldQuery query Query which has received result
1027      * \param array result result corresponding to query
1028      *
1029      * Note: this function works on the analyzed query
1030      */
1031     publish_result_rec: function(query, records) {
1032         /* If the result is not unique, only publish the top query;
1033          * otherwise, publish the main object as well as subqueries
1034          * XXX how much recursive are we ?
1035          */
1036         if (manifold.pubsub_debug)
1037              messages.debug (">>>>> publish_result_rec " + query.object);
1038         if (manifold.query_expects_unique_result(query)) {
1039             /* Also publish subqueries */
1040             $.each(query.subqueries, function(object, subquery) {
1041                 manifold.publish_result_rec(subquery, records[0][object]);
1042                 /* TODO remove object from result */
1043             });
1044         }
1045         if (manifold.pubsub_debug) 
1046             messages.debug ("===== publish_result_rec " + query.object);
1047
1048         var query_ext = manifold.query_store.find_analyzed_query_ext(query.query_uuid);
1049         query_ext.query_state = QUERY_STATE_DONE;
1050
1051         this.store_records(query, records);
1052
1053         var pub_query;
1054
1055         if (query_ext.set_query_ext) {
1056             if (query_ext.set_query_ext.query_state != QUERY_STATE_DONE)
1057                 return;
1058             pub_query = query_ext.set_query_ext.query;
1059         } else if (query_ext.domain_query_ext) {
1060             if (query_ext.domain_query_ext.query_state != QUERY_STATE_DONE)
1061                 return;
1062             pub_query = query;
1063         } else {
1064             pub_query = query;
1065         }
1066         // We can only publish results if the query (and its related domain query) is complete
1067         manifold.publish_result(pub_query, records);
1068
1069         if (manifold.pubsub_debug) 
1070             messages.debug ("<<<<< publish_result_rec " + query.object);
1071     },
1072
1073     setup_update_query: function(query, records) {
1074         // We don't prepare an update query if the result has more than 1 entry
1075         if (records.length != 1)
1076             return;
1077         var query_ext = manifold.query_store.find_query_ext(query.query_uuid);
1078
1079         var record = records[0];
1080
1081         var update_query_ext = query_ext.update_query_ext;
1082
1083         console.log("Update case not handled yet!");
1084         if (!update_query_ext)
1085             return;
1086
1087         var update_query = update_query_ext.query;
1088         var update_query_ext = query_ext.update_query_ext;
1089         var update_query_orig = query_ext.update_query_orig_ext.query;
1090
1091         // Testing whether the result has subqueries (one level deep only)
1092         // iif the query has subqueries
1093         var count = 0;
1094         var obj = query.analyzed_query.subqueries;
1095         for (method in obj) {
1096             if (obj.hasOwnProperty(method)) {
1097                 var key = manifold.metadata.get_key(method);
1098                 if (!key)
1099                     continue;
1100                 var sq_keys = [];
1101                 var subrecords = record[method];
1102                 if (!subrecords)
1103                     continue
1104                 $.each(subrecords, function (i, subrecord) {
1105                     sq_keys.push(manifold.record_get_value(subrecord, key));
1106                 });
1107                 update_query.params[method] = sq_keys;
1108                 update_query_orig.params[method] = sq_keys.slice();
1109                 count++;
1110             }
1111         }
1112
1113         if (count > 0) {
1114             update_query_ext.disabled = false;
1115             update_query_orig_ext.disabled = false;
1116         }
1117     },
1118
1119     process_get_query_records: function(query, records) {
1120         this.setup_update_query(query, records);
1121         
1122         var query_ext = manifold.query_store.find_query_ext(query.query_uuid);
1123         query_ext.query_state = QUERY_STATE_DONE;
1124
1125         /* Publish full results */
1126         var tmp_query = manifold.query_store.find_analyzed_query(query.query_uuid);
1127         manifold.publish_result_rec(tmp_query, records);
1128     },
1129
1130     make_records: function(object, records)
1131     {
1132         $.each(records, function(i, record) {
1133             manifold.make_record(object, record);
1134         });
1135     },
1136
1137     make_record: function(object, record)
1138     {
1139         // To make an object a record, we just add the hash function
1140         var key = manifold.metadata.get_key(object);
1141         record.hashCode = manifold.record_hashcode(key.sort());
1142         record.equals   = manifold.record_equals(key);
1143
1144         // Looking after subrecords
1145         for (var field in record) {
1146             var result_value = record[field];
1147
1148             switch (this.get_type(result_value)) {
1149                 case TYPE_RECORD:
1150                     var subobject = manifold.metadata.get_type(object, field);
1151                     // if (subobject) XXX Bugs with fields declared string while they are not : network.version is a dict in fact
1152                     if (subobject && subobject != 'string')
1153                         manifold.make_record(subobject, result_value);
1154                     break;
1155                 case TYPE_LIST_OF_RECORDS:
1156                     var subobject = manifold.metadata.get_type(object, field);
1157                     if (subobject)
1158                         manifold.make_records(subobject, result_value);
1159                     break;
1160             }
1161         }
1162     },
1163
1164     /**
1165      * 
1166      * What we need to do when receiving results from an update query:
1167      * - differences between what we had, what we requested, and what we obtained
1168      *    . what we had : update_query_orig (simple fields and set fields managed differently)
1169      *    . what we requested : update_query
1170      *    . what we received : records
1171      * - raise appropriate events
1172      *
1173      * The normal process is that results similar to Get will be pushed in the
1174      * pubsub mechanism, thus repopulating everything while we only need
1175      * diff's. This means we need to move the publish functionalities in the
1176      * previous 'process_get_query_records' function.
1177      */
1178     process_update_query_records: function(query, records) {
1179         // XXX XXX XXX XXX
1180         // XXX XXX XXX XXX
1181         // XXX XXX XXX XXX
1182         // XXX XXX XXX XXX
1183         // XXX XXX XXX XXX
1184         // XXX XXX XXX XXX
1185         // XXX XXX XXX XXX
1186         // XXX XXX XXX XXX
1187         // XXX XXX XXX XXX
1188         // XXX XXX XXX XXX
1189         // XXX XXX XXX XXX
1190         // XXX XXX XXX XXX
1191         // XXX XXX XXX XXX
1192         // XXX XXX XXX XXX
1193         // XXX XXX XXX XXX
1194         // First issue: we request everything, and not only what we modify, so will will have to ignore some fields
1195         var query_uuid        = query.query_uuid;
1196         var query_ext         = manifold.query_store.find_analyzed_query_ext(query_uuid);
1197         var update_query      = query_ext.main_query_ext.update_query_ext.query;
1198         var update_query_orig = query_ext.main_query_ext.update_query_orig_ext.query;
1199         
1200         // Since we update objects one at a time, we can get the first record
1201         var record = records[0];
1202
1203         // Let's iterate over the object properties
1204         for (var field in record) {
1205             var result_value = record[field];
1206             switch (this.get_type(result_value)) {
1207                 case TYPE_VALUE:
1208                     // Did we ask for a change ?
1209                     var update_value = update_query[field];
1210                     if (!update_value)
1211                         // Not requested, if it has changed: OUT OF SYNC
1212                         // How we can know ?
1213                         // We assume it won't have changed
1214                         continue;
1215
1216                     if (!result_value)
1217                         throw "Internal error";
1218
1219                     data = {
1220                         state : STATE_SET,
1221                         key   : field,
1222                         op    : update_value,
1223                         value : (update_value == result_value) ? STATE_VALUE_CHANGE_SUCCESS : STATE_VALUE_CHANGE_FAILURE,
1224                     }
1225                     manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data);
1226
1227                     break;
1228                 case TYPE_RECORD:
1229                     throw "Not implemented";
1230                     break;
1231
1232                 case TYPE_LIST_OF_VALUES:
1233                     // Same as list of records, but we don't have to extract keys
1234                     
1235                     // The rest of exactly the same (XXX factorize)
1236                     var update_keys  = update_query_orig.params[field];
1237                     var query_keys   = update_query.params[field];
1238                     var added_keys   = $.grep(query_keys, function (x) { return $.inArray(x, update_keys) == -1 });
1239                     var removed_keys = $.grep(update_keys, function (x) { return $.inArray(x, query_keys) == -1 });
1240
1241
1242                     $.each(added_keys, function(i, key) {
1243                         if ($.inArray(key, result_value) == -1) {
1244                             data = {
1245                                 request: FIELD_REQUEST_ADD,
1246                                 key   : field,
1247                                 value : key,
1248                                 status: FIELD_REQUEST_FAILURE,
1249                             }
1250                         } else {
1251                             data = {
1252                                 request: FIELD_REQUEST_ADD,
1253                                 key   : field,
1254                                 value : key,
1255                                 status: FIELD_REQUEST_SUCCESS,
1256                             }
1257                         }
1258                         manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data);
1259                     });
1260                     $.each(removed_keys, function(i, key) {
1261                         if ($.inArray(key, result_keys) == -1) {
1262                             data = {
1263                                 request: FIELD_REQUEST_REMOVE,
1264                                 key   : field,
1265                                 value : key,
1266                                 status: FIELD_REQUEST_SUCCESS,
1267                             }
1268                         } else {
1269                             data = {
1270                                 request: FIELD_REQUEST_REMOVE,
1271                                 key   : field,
1272                                 value : key,
1273                                 status: FIELD_REQUEST_FAILURE,
1274                             }
1275                         }
1276                         manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data);
1277                     });
1278
1279
1280                     break;
1281                 case TYPE_LIST_OF_RECORDS:
1282                     // example: slice.resource
1283                     //  - update_query_orig.params.resource = resources in slice before update
1284                     //  - update_query.params.resource = resource requested in slice
1285                     //  - keys from field = resources obtained
1286                     var key = manifold.metadata.get_key(field);
1287                     if (!key)
1288                         continue;
1289                     if (key.length > 1) {
1290                         throw "Not implemented";
1291                         continue;
1292                     }
1293                     key = key[0];
1294
1295                     /* XXX should be modified for multiple keys */
1296                     var result_keys  = $.map(record[field], function(x) { return manifold.record_get_value(x, key); });
1297
1298                     var update_keys  = update_query_orig.params[field];
1299                     var query_keys   = update_query.params[field];
1300                     var added_keys   = $.grep(query_keys, function (x) { return $.inArray(x, update_keys) == -1 });
1301                     var removed_keys = $.grep(update_keys, function (x) { return $.inArray(x, query_keys) == -1 });
1302
1303
1304                     $.each(added_keys, function(i, key) {
1305                         if ($.inArray(key, result_keys) == -1) {
1306                             data = {
1307                                 request: FIELD_REQUEST_ADD,
1308                                 key   : field,
1309                                 value : key,
1310                                 status: FIELD_REQUEST_FAILURE,
1311                             }
1312                         } else {
1313                             data = {
1314                                 request: FIELD_REQUEST_ADD,
1315                                 key   : field,
1316                                 value : key,
1317                                 status: FIELD_REQUEST_SUCCESS,
1318                             }
1319                         }
1320                         manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data);
1321                     });
1322                     $.each(removed_keys, function(i, key) {
1323                         if ($.inArray(key, result_keys) == -1) {
1324                             data = {
1325                                 request: FIELD_REQUEST_REMOVE,
1326                                 key   : field,
1327                                 value : key,
1328                                 status: FIELD_REQUEST_SUCCESS,
1329                             }
1330                         } else {
1331                             data = {
1332                                 request: FIELD_REQUEST_REMOVE,
1333                                 key   : field,
1334                                 value : key,
1335                                 status: FIELD_REQUEST_FAILURE,
1336                             }
1337                         }
1338                         manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data);
1339                     });
1340
1341
1342                     break;
1343             }
1344         }
1345         
1346         // XXX Now we need to adapt 'update' and 'update_orig' queries as if we had done a get
1347         this.setup_update_query(query, records);
1348     },
1349
1350     process_query_records: function(query, records) {
1351         if (query.action == 'get') {
1352             this.process_get_query_records(query, records);
1353         } else if (query.action == 'update') {
1354             this.process_update_query_records(query, records);
1355         }
1356     },
1357
1358     // if set callback is provided it is called
1359     // most of the time publish_uuid will be query.query_uuid
1360     // however in some cases we wish to publish the result under a different uuid
1361     // e.g. an updater wants to publish its result as if from the original (get) query
1362     asynchroneous_success : function (data, query, publish_uuid, callback) {
1363         // xxx should have a nicer declaration of that enum in sync with the python code somehow
1364         
1365         var start = new Date();
1366         if (manifold.asynchroneous_debug)
1367             messages.debug(">>>>>>>>>> asynchroneous_success query.object=" + query.object);
1368
1369         if (data.code == 2) { // ERROR
1370             // We need to make sense of error codes here
1371             alert("Your session has expired, please log in again");
1372             localStorage.removeItem('user');
1373             window.location="/logout/";
1374             if (manifold.asynchroneous_debug) {
1375                 duration=new Date()-start;
1376                 messages.debug ("<<<<<<<<<< asynchroneous_success " + query.object + " -- error returned - logging out " + duration + " ms");
1377             }
1378             return;
1379         }
1380         if (data.code == 1) { // WARNING
1381             messages.error("Some errors have been received from the manifold backend at " + MANIFOLD_URL + " [" + data.description + "]");
1382             // publish error code and text message on a separate channel for whoever is interested
1383             if (publish_uuid)
1384                 $.publish("/results/" + publish_uuid + "/failed", [data.code, data.description] );
1385
1386         }
1387
1388         // If a callback has been specified, we redirect results to it 
1389         if (!!callback) { 
1390             callback(data); 
1391             if (manifold.asynchroneous_debug) {
1392                 duration=new Date()-start;
1393                 messages.debug ("<<<<<<<<<< asynchroneous_success " + query.object + " -- callback ended " + duration + " ms");
1394             }
1395             return; 
1396         }
1397
1398         if (manifold.asynchroneous_debug) 
1399             messages.debug ("========== asynchroneous_success " + query.object + " -- before process_query_records [" + query.query_uuid +"]");
1400
1401         // once everything is checked we can use the 'value' part of the manifoldresult
1402         var result=data.value;
1403         if (result) {
1404             /* Eventually update the content of related queries (update, etc) */
1405             manifold.make_records(query.object, result);
1406             this.process_query_records(query, result);
1407
1408             /* Publish results: disabled here, done in the previous call */
1409             //tmp_query = manifold.find_query(query.query_uuid);
1410             //manifold.publish_result_rec(tmp_query.analyzed_query, result);
1411         }
1412         if (manifold.asynchroneous_debug) {
1413             duration=new Date()-start;
1414             messages.debug ("<<<<<<<<<< asynchroneous_success " + query.object + " -- done " + duration + " ms");
1415         }
1416
1417     },
1418
1419     /************************************************************************** 
1420      * Plugin API helpers
1421      **************************************************************************/ 
1422
1423     raise_event_handler: function(type, query_uuid, event_type, value) {
1424         if (manifold.pubsub_debug)
1425             messages.debug("raise_event_handler, quuid="+query_uuid+" type="+type+" event_type="+event_type);
1426         if ((type != 'query') && (type != 'record'))
1427             throw 'Incorrect type for manifold.raise_event()';
1428         // xxx we observe quite a lot of incoming calls with an undefined query_uuid
1429         // this should be fixed upstream in manifold I expect
1430         if (query_uuid === undefined) {
1431             messages.warning("undefined query in raise_event_handler");
1432             return;
1433         }
1434
1435         // notify the change to objects that either listen to this channel specifically,
1436         // or to the wildcard channel
1437         var channels = [ manifold.get_channel(type, query_uuid), manifold.get_channel(type, '*') ];
1438
1439         $.each(channels, function(i, channel) {
1440             if (value === undefined) {
1441                 if (manifold.pubsub_debug) messages.debug("triggering [no value] on channel="+channel+" and event_type="+event_type);
1442                 $('.pubsub').trigger(channel, [event_type]);
1443             } else {
1444                 if (manifold.pubsub_debug) messages.debug("triggering [value="+value+"] on channel="+channel+" and event_type="+event_type);
1445                 $('.pubsub').trigger(channel, [event_type, value]);
1446             }
1447         });
1448     },
1449
1450     raise_query_event: function(query_uuid, event_type, value) {
1451         manifold.raise_event_handler('query', query_uuid, event_type, value);
1452     },
1453
1454     raise_record_event: function(query_uuid, event_type, value) {
1455         manifold.raise_event_handler('record', query_uuid, event_type, value);
1456     },
1457
1458     /**
1459      * Event handler helpers
1460      */
1461     _get_next_state_add: function(prev_state, event_type)
1462     {
1463         switch (prev_state) {
1464             case STATE_SET_OUT:
1465             case STATE_SET_OUT_SUCCESS:
1466             case STATE_SET_IN_FAILURE:
1467                 new_state = STATE_SET_IN_PENDING;
1468                 break;
1469
1470             case STATE_SET_OUT_PENDING:
1471                 new_state = STATE_SET_IN;
1472                 break;
1473
1474             case STATE_SET_IN:
1475             case STATE_SET_IN_PENDING:
1476             case STATE_SET_IN_SUCCESS:
1477             case STATE_SET_OUT_FAILURE:
1478                 console.log("Inconsistent state: already in");
1479                 return;
1480         }
1481         return new_state;
1482     },
1483
1484     _get_next_state_remove: function(prev_state, event_type)
1485     {
1486         switch (prev_state) {
1487             case STATE_SET_IN:
1488             case STATE_SET_IN_SUCCESS:
1489             case STATE_SET_OUT_FAILURE:
1490                 new_state = STATE_SET_OUT_PENDING;
1491                 break;
1492
1493             case STATE_SET_IN_PENDING:
1494                 new_state = STATE_SET_OUT;
1495                 break;  
1496
1497             case STATE_SET_OUT:
1498             case STATE_SET_OUT_PENDING:
1499             case STATE_SET_OUT_SUCCESS:
1500             case STATE_SET_IN_FAILURE:
1501                 console.log("Inconsistent state: already out");
1502                 return;
1503         }
1504         return new_state;
1505     },
1506
1507     _grep_active_lease_callback: function(lease_query, resource_key) {
1508         return function(lease_key_lease) {
1509             var state, lease_key, lease;
1510
1511             lease_key = lease_key_lease[0];
1512             lease = lease_key_lease[1];
1513
1514             if (lease['resource'] != resource_key)
1515                 return false;
1516
1517             state = manifold.query_store.get_record_state(lease_query.query_uuid, lease_key, STATE_SET);;
1518             switch(state) {
1519                 case STATE_SET_IN:
1520                 case STATE_SET_IN_PENDING:
1521                 case STATE_SET_IN_SUCCESS:
1522                 case STATE_SET_OUT_FAILURE:
1523                     return true;
1524                 case STATE_SET_OUT:
1525                 case STATE_SET_OUT_PENDING:
1526                 case STATE_SET_OUT_SUCCESS:
1527                 case STATE_SET_IN_FAILURE:
1528                     return false;
1529             }
1530         }
1531     },
1532
1533     _enforce_constraints: function(query_ext, record, resource_key, event_type)
1534     {
1535         var query, data;
1536
1537         query = query_ext.query;
1538
1539         switch(query.object) {
1540
1541             case 'resource':
1542                 // CONSTRAINT_RESERVABLE_LEASE
1543                 // 
1544                 // +) If a reservable node is added to the slice, then it should have a corresponding lease
1545                 // XXX Not always a resource
1546                 var is_reservable = (record.exclusive == true);
1547                 if (is_reservable) {
1548                     var warnings = manifold.query_store.get_record_state(query.query_uuid, resource_key, STATE_WARNINGS);
1549
1550                     if (event_type == STATE_SET_ADD) {
1551                         // We should have a lease_query associated
1552                         var lease_query = query_ext.parent_query_ext.query.subqueries['lease']; // in  options
1553                         var lease_query_ext = manifold.query_store.find_analyzed_query_ext(lease_query.query_uuid);
1554                         // Do we have lease records (in) with this resource
1555                         var lease_records = $.grep(lease_query_ext.records.entries(), this._grep_active_lease_callback(lease_query, resource_key));
1556                         if (lease_records.length == 0) {
1557                             // Sets a warning
1558                             // XXX Need for a better function to manage warnings
1559                             var warn = CONSTRAINT_RESERVABLE_LEASE_MSG;
1560                             warnings[CONSTRAINT_RESERVABLE_LEASE] = warn;
1561                         } else {
1562                             // Lease are defined, delete the warning in case it was set previously
1563                             delete warnings[CONSTRAINT_RESERVABLE_LEASE];
1564                         }
1565                     } else {
1566                         // Remove warnings attached to this resource
1567                         delete warnings[CONSTRAINT_RESERVABLE_LEASE];
1568                     }
1569
1570                     manifold.query_store.set_record_state(query.query_uuid, resource_key, STATE_WARNINGS, warnings);
1571                 }
1572
1573                 manifold.query_store.recount(query.query_uuid); 
1574                 manifold.query_store.recount(lease_query.query_uuid); 
1575
1576                 // Signal the change to plugins (even if the constraint does not apply, so that the plugin can display a checkmark)
1577                 data = {
1578                     state:  STATE_WARNINGS,
1579                     key   : resource_key,
1580                     op    : null,
1581                     value : warnings
1582                 }
1583                 manifold.raise_record_event(query.query_uuid, FIELD_STATE_CHANGED, data);
1584                 break;
1585
1586             case 'lease':
1587                 var resource_key = record.resource;
1588                 var resource_query = query_ext.parent_query_ext.query.subqueries['resource'];
1589                 var warnings = manifold.query_store.get_record_state(resource_query.query_uuid, resource_key, STATE_WARNINGS);
1590
1591                 if (event_type == STATE_SET_ADD) {
1592                      // A lease is added, it removes the constraint
1593                     delete warnings[CONSTRAINT_RESERVABLE_LEASE];
1594                 } else {
1595                     // A lease is removed, it might trigger the warning
1596                     var lease_records = $.grep(query_ext.records.entries(), this._grep_active_lease_callback(query, resource_key));
1597                     if (lease_records.length == 0) { // XXX redundant cases
1598                         // Sets a warning
1599                         // XXX Need for a better function to manage warnings
1600                         var warn = CONSTRAINT_RESERVABLE_LEASE_MSG;
1601                         warnings[CONSTRAINT_RESERVABLE_LEASE] = warn;
1602                     } else {
1603                         // Lease are defined, delete the warning in case it was set previously
1604                         delete warnings[CONSTRAINT_RESERVABLE_LEASE];
1605                     }
1606                     
1607                 }
1608
1609                 manifold.query_store.recount(query.query_uuid); 
1610                 manifold.query_store.recount(resource_query.query_uuid); 
1611
1612                 // Signal the change to plugins (even if the constraint does not apply, so that the plugin can display a checkmark)
1613                 data = {
1614                     state:  STATE_WARNINGS,
1615                     key   : resource_key,
1616                     op    : null,
1617                     value : warnings
1618                 }
1619                 manifold.raise_record_event(resource_query.query_uuid, FIELD_STATE_CHANGED, data);
1620                 break;
1621         }
1622
1623         // -) When a lease is added, it might remove the warning associated to a reservable node
1624
1625         // If a NITOS node is reserved, then at least a NITOS channel should be reserved
1626         // - When a NITOS channel is added, it might remove a warning associated to all NITOS nodes
1627
1628         // If a NITOS channel is reserved, then at least a NITOS node should be reserved
1629         // - When a NITOS node is added, it might remove a warning associated to all NITOS channels
1630
1631         // A lease is present while the resource has been removed => Require warnings on nodes not in set !
1632
1633     },
1634
1635     _get_query_path: function(query_ext) {
1636         var path = "";
1637         var sq = query_ext;
1638         while (sq.parent_query_ext) {
1639             if (path != "")
1640                 path = '.' + path;
1641             path = sq.query.object + path;
1642             sq = sq.parent_query_ext;
1643         }
1644         return path;
1645     },
1646
1647
1648     /**
1649      * Handling events raised by plugins
1650      */
1651     raise_event: function(query_uuid, event_type, data) 
1652     {
1653         var query, query_ext;
1654
1655         // Query uuid has been updated with the key of a new element
1656         query_ext    = manifold.query_store.find_analyzed_query_ext(query_uuid);
1657         query = query_ext.query;
1658
1659         switch(event_type) {
1660
1661             // XXX At some point, should be renamed to RECORD_STATE_CHANGED
1662             case FIELD_STATE_CHANGED:
1663
1664                 // value is an object (request, key, value, status)
1665                 // update is only possible is the query is not pending, etc
1666                 // SET_ADD is on a subquery, FIELD_STATE_CHANGED on the query itself
1667                 // we should map SET_ADD on this...
1668
1669                 // 1. Update internal query store about the change in status
1670
1671                 // 2. Update the update query
1672                 update_query      = query_ext.main_query_ext.update_query_ext.query;
1673                 update_query_orig = query_ext.main_query_ext.update_query_orig_ext.query;
1674
1675                 switch(data.state) {
1676             
1677                     case STATE_VALUE:
1678                         switch(data.op) {
1679                             case STATE_CHANGE:
1680                                 /* Set parameter data.key in the update_query to VALUE */
1681                                 if (update_query.params[data.key] === undefined)
1682                                     update_query.params[data.key] = Array();
1683                                 update_query.params[data.key] = value.value;
1684                                 break;
1685
1686                         }
1687                         break;
1688
1689                     case STATE_SET:
1690
1691                         switch(data.op) {
1692                             case STATE_SET_ADD:
1693                                 if (!data.key) {
1694                                     var prev_state, new_state;
1695                                     var main_query, record, new_data;
1696                     
1697                                     prev_state = manifold.query_store.get_record_state(query_uuid, data.value, STATE_SET);
1698                                     if (prev_state === null)
1699                                         prev_state = STATE_SET_OUT;
1700                                     new_state = this._get_next_state_add(prev_state, data.state);
1701                     
1702                                     /* data.value containts the resource key */
1703                                     manifold.query_store.add_record(query_uuid, data.value, new_state);
1704                                     record = manifold.query_store.get_record(query_uuid, data.value);
1705                                     this._enforce_constraints(query_ext, record, data.value, STATE_SET_ADD);
1706                     
1707                                     /* Inform the parent query: important for update */
1708                                     new_data = {
1709                                         state : STATE_SET,
1710                                         key   : this._get_query_path(query_ext),
1711                                         op    : STATE_SET_IN_PENDING,
1712                                         value : data.value,
1713                                     };
1714                                     main_query = query_ext.main_query_ext.query;
1715                                     this.raise_event(main_query.query_uuid, FIELD_STATE_CHANGED, new_data);
1716                     
1717                                     /*
1718                                      * Propagate the event to other plugins subscribed to the query
1719                                      */
1720                                     manifold.raise_query_event(query_uuid, event_type, data);
1721                                 } else {
1722                                     // mainquery: proceed to update
1723
1724                                     //if ($.inArray(data.value, update_query_orig.params[data.key]) != -1)
1725                                     //    value.request = FIELD_REQUEST_ADD_RESET;
1726
1727                                     if (update_query.params[data.key] === undefined)
1728                                         update_query.params[data.key] = Array();
1729                                     update_query.params[data.key].push(data.value);
1730                                 }
1731                                 break;
1732
1733                             case STATE_SET_REMOVE:
1734                                 if (!data.key) {
1735                                     var prev_state, new_state;
1736                                     var main_query, record, new_data;
1737                     
1738                                     prev_state = manifold.query_store.get_record_state(query_uuid, data.value, STATE_SET);
1739                                     if (prev_state === null)
1740                                         prev_state = STATE_SET_OUT;
1741                                     new_state = this._get_next_state_remove(prev_state, data.state);
1742                     
1743                                     /* data.value contains the resource key */
1744                                     manifold.query_store.remove_record(query_uuid, data.value, new_state);
1745                                     record = manifold.query_store.get_record(query_uuid, data.value);
1746                                     this._enforce_constraints(query_ext, record, data.value, STATE_SET_REMOVE);
1747                     
1748                                     /* Inform the parent query: important for update */
1749                                     new_data = {
1750                                         state : STATE_SET,
1751                                         key   : this._get_query_path(query_ext),
1752                                         op    : STATE_SET_OUT_PENDING,
1753                                         value : data.value,
1754                                     };
1755                                     main_query = query_ext.main_query_ext.query;
1756                                     this.raise_event(main_query.query_uuid, FIELD_STATE_CHANGED, new_data);
1757                     
1758                                     /* Propagate the event to other plugins subscribed to the query */
1759                                     manifold.raise_query_event(query_uuid, event_type, data);
1760                     
1761                                 } else {
1762                                     // main query: proceed to update
1763
1764                                     //if ($.inArray(data.value, update_query_orig.params[data.key]) == -1)
1765                                     //    value.request = FIELD_REQUEST_REMOVE_RESET;
1766
1767                                     var arr = update_query.params[data.key];
1768                                     arr = $.grep(arr, function(x) { return x != data.value; });
1769                                     if (update_query.params[data.key] === undefined)
1770                                         update_query.params[data.key] = Array();
1771                                     update_query.params[data.key] = arr;
1772                                 }
1773                                 break;
1774                         }
1775                         break;
1776                 }
1777
1778                 // 3. Inform others about the change
1779                 // a) the main query...
1780                 manifold.raise_record_event(query_uuid, event_type, data);
1781
1782                 // b) subqueries eventually (dot in the key)
1783                 // Let's unfold 
1784
1785                 var cur_query = query;
1786                 if (cur_query.analyzed_query)
1787                     cur_query = cur_query.analyzed_query;
1788
1789                 if (data.key) {
1790                     var path_array = data.key.split('.');
1791                     var value_key = data.key.split('.');
1792                     $.each(path_array, function(i, method) {
1793                         cur_query = cur_query.subqueries[method];
1794                         value_key.shift(); // XXX check that method is indeed shifted
1795                     });
1796                     data.key = value_key;
1797                 }
1798
1799                 manifold.query_store.recount(cur_query.query_uuid);
1800                 manifold.raise_record_event(cur_query.query_uuid, event_type, data);
1801
1802                 break;
1803
1804             case RUN_UPDATE:
1805                 manifold.run_query(query_ext.main_query_ext.update_query_ext.query);
1806                 break;
1807
1808             /* QUERY STATE CHANGED */
1809             
1810             // FILTERS
1811
1812             case FILTER_ADDED: 
1813                 /* Update internal record state */
1814                 manifold.query_store.add_filter(query_uuid, data);
1815
1816                 /* Propagate the message to plugins */
1817                 manifold.raise_query_event(query_uuid, event_type, data);
1818
1819                 break;
1820
1821             case FILTER_REMOVED:
1822                 /* Update internal record state */
1823                 manifold.query_store.remove_filter(query_uuid, data);
1824
1825                 /* Propagate the message to plugins */
1826                 manifold.raise_query_event(query_uuid, event_type, data);
1827
1828                 break;
1829
1830             case FIELD_ADDED:
1831                 main_query = query_ext.main_query_ext.query;
1832                 main_update_query = query_ext.main_query_ext.update_query;
1833                 query.select(data);
1834
1835                 // Here we need the full path through all subqueries
1836                 path = ""
1837                 // XXX We might need the query name in the QueryExt structure
1838                 main_query.select(data);
1839
1840                 // XXX When is an update query associated ?
1841                 // XXX main_update_query.select(value);
1842
1843                 manifold.raise_query_event(query_uuid, event_type, data);
1844                 break;
1845
1846             case FIELD_REMOVED:
1847                 query = query_ext.query;
1848                 main_query = query_ext.main_query_ext.query;
1849                 main_update_query = query_ext.main_query_ext.update_query;
1850                 query.unselect(data);
1851                 main_query.unselect(data);
1852
1853                 // We need to inform about changes in these queries to the respective plugins
1854                 // Note: query & main_query have the same UUID
1855                 manifold.raise_query_event(query_uuid, event_type, data);
1856                 break;
1857         }
1858         // We need to inform about changes in these queries to the respective plugins
1859         // Note: query, main_query & update_query have the same UUID
1860
1861         // http://trac.myslice.info/ticket/32
1862         // Avoid multiple calls to the same event
1863         //manifold.raise_query_event(query_uuid, event_type, value);
1864
1865         // We are targeting the same object with get and update
1866         // The notion of query is bad, we should have a notion of destination, and issue queries on the destination
1867         // NOTE: Editing a subquery == editing a local view on the destination
1868
1869         // XXX We might need to run the new query again and manage the plugins in the meantime with spinners...
1870         // For the time being, we will collect all columns during the first query
1871     },
1872
1873     /* Publish/subscribe channels for internal use */
1874     get_channel: function(type, query_uuid) {
1875         if ((type !== 'query') && (type != 'record'))
1876             return null;
1877         return '/' + type + '/' + query_uuid;
1878     },
1879
1880 }; // manifold object
1881 /* ------------------------------------------------------------ */
1882
1883 (function($) {
1884
1885     // OLD PLUGIN API: extend jQuery/$ with pubsub capabilities
1886     // https://gist.github.com/661855
1887     var o = $({});
1888     $.subscribe = function( channel, selector, data, fn) {
1889       /* borrowed from jQuery */
1890       if ( data == null && fn == null ) {
1891           // ( channel, fn )
1892           fn = selector;
1893           data = selector = undefined;
1894       } else if ( fn == null ) {
1895           if ( typeof selector === "string" ) {
1896               // ( channel, selector, fn )
1897               fn = data;
1898               data = undefined;
1899           } else {
1900               // ( channel, data, fn )
1901               fn = data;
1902               data = selector;
1903               selector = undefined;
1904           }
1905       }
1906       /* </ugly> */
1907   
1908       /* We use an indirection function that will clone the object passed in
1909        * parameter to the subscribe callback 
1910        * 
1911        * FIXME currently we only clone query objects which are the only ones
1912        * supported and editable, we might have the same issue with results but
1913        * the page load time will be severely affected...
1914        */
1915       o.on.apply(o, [channel, selector, data, function() { 
1916           for(i = 1; i < arguments.length; i++) {
1917               if ( arguments[i].constructor.name == 'ManifoldQuery' )
1918                   arguments[i] = arguments[i].clone();
1919           }
1920           fn.apply(o, arguments);
1921       }]);
1922     };
1923   
1924     $.unsubscribe = function() {
1925       o.off.apply(o, arguments);
1926     };
1927   
1928     $.publish = function() {
1929       o.trigger.apply(o, arguments);
1930     };
1931   
1932 }(jQuery));
1933
1934 /* ------------------------------------------------------------ */
1935
1936 //http://stackoverflow.com/questions/5100539/django-csrf-check-failing-with-an-ajax-post-request
1937 //make sure to expose csrf in our outcoming ajax/post requests
1938 $.ajaxSetup({ 
1939      beforeSend: function(xhr, settings) {
1940          function getCookie(name) {
1941              var cookieValue = null;
1942              if (document.cookie && document.cookie != '') {
1943                  var cookies = document.cookie.split(';');
1944                  for (var i = 0; i < cookies.length; i++) {
1945                      var cookie = jQuery.trim(cookies[i]);
1946                      // Does this cookie string begin with the name we want?
1947                  if (cookie.substring(0, name.length + 1) == (name + '=')) {
1948                      cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
1949                      break;
1950                  }
1951              }
1952          }
1953          return cookieValue;
1954          }
1955          if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
1956              // Only send the token to relative URLs i.e. locally.
1957              xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
1958          }
1959      } 
1960 });