fixing update management
[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; //XXX Should be harmonized with query state
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         if (!update_query_ext)
1084             return;
1085
1086         var update_query = update_query_ext.query;
1087         var update_query_ext = query_ext.update_query_ext;
1088         var update_query_orig = query_ext.update_query_orig_ext.query;
1089
1090         // Testing whether the result has subqueries (one level deep only)
1091         // iif the query has subqueries
1092         var count = 0;
1093         var obj = query.analyzed_query.subqueries;
1094         for (method in obj) {
1095             if (obj.hasOwnProperty(method)) {
1096                 var key = manifold.metadata.get_key(method);
1097                 if (!key)
1098                     continue;
1099                 var sq_keys = [];
1100                 var subrecords = record[method];
1101                 if (!subrecords)
1102                     continue
1103                 $.each(subrecords, function (i, subrecord) {
1104                     sq_keys.push(manifold.record_get_value(subrecord, key));
1105                 });
1106                 update_query.params[method] = sq_keys;
1107                 update_query_orig.params[method] = sq_keys.slice();
1108                 count++;
1109             }
1110         }
1111
1112         if (count > 0) {
1113             update_query_ext.disabled = false;
1114             update_query_orig_ext.disabled = false;
1115         }
1116     },
1117
1118     process_get_query_records: function(query, records) {
1119         this.setup_update_query(query, records);
1120         
1121         var query_ext = manifold.query_store.find_query_ext(query.query_uuid);
1122         query_ext.query_state = QUERY_STATE_DONE;
1123
1124         /* Publish full results */
1125         var tmp_query = manifold.query_store.find_analyzed_query(query.query_uuid);
1126         manifold.publish_result_rec(tmp_query, records);
1127     },
1128
1129     make_records: function(object, records)
1130     {
1131         $.each(records, function(i, record) {
1132             manifold.make_record(object, record);
1133         });
1134     },
1135
1136     make_record: function(object, record)
1137     {
1138         // To make an object a record, we just add the hash function
1139         var key = manifold.metadata.get_key(object);
1140         record.hashCode = manifold.record_hashcode(key.sort());
1141         record.equals   = manifold.record_equals(key);
1142
1143         // Looking after subrecords
1144         for (var field in record) {
1145             var result_value = record[field];
1146
1147             switch (this.get_type(result_value)) {
1148                 case TYPE_RECORD:
1149                     var subobject = manifold.metadata.get_type(object, field);
1150                     // if (subobject) XXX Bugs with fields declared string while they are not : network.version is a dict in fact
1151                     if (subobject && subobject != 'string')
1152                         manifold.make_record(subobject, result_value);
1153                     break;
1154                 case TYPE_LIST_OF_RECORDS:
1155                     var subobject = manifold.metadata.get_type(object, field);
1156                     if (subobject)
1157                         manifold.make_records(subobject, result_value);
1158                     break;
1159             }
1160         }
1161     },
1162
1163     /**
1164      * 
1165      * What we need to do when receiving results from an update query:
1166      * - differences between what we had, what we requested, and what we obtained
1167      *    . what we had : update_query_orig (simple fields and set fields managed differently)
1168      *    . what we requested : update_query
1169      *    . what we received : records
1170      * - raise appropriate events
1171      *
1172      * The normal process is that results similar to Get will be pushed in the
1173      * pubsub mechanism, thus repopulating everything while we only need
1174      * diff's. This means we need to move the publish functionalities in the
1175      * previous 'process_get_query_records' function.
1176      */
1177     process_update_query_records: function(query, records) {
1178         // First issue: we request everything, and not only what we modify, so will will have to ignore some fields
1179         var query_uuid        = query.query_uuid;
1180         var query_ext         = manifold.query_store.find_analyzed_query_ext(query_uuid);
1181         var update_query      = query_ext.main_query_ext.update_query_ext.query;
1182         var update_query_orig = query_ext.main_query_ext.update_query_orig_ext.query;
1183         
1184         // Since we update objects one at a time, we can get the first record
1185         var record = records[0];
1186
1187         // Let's iterate over the object properties
1188         for (var field in record) {
1189             var result_value = record[field];
1190             switch (this.get_type(result_value)) {
1191                 case TYPE_VALUE:
1192                     // Did we ask for a change ?
1193                     var update_value = update_query[field];
1194                     if (!update_value)
1195                         // Not requested, if it has changed: OUT OF SYNC
1196                         // How we can know ?
1197                         // We assume it won't have changed
1198                         continue;
1199
1200                     if (!result_value)
1201                         throw "Internal error";
1202
1203                     data = {
1204                         state : STATE_SET,
1205                         key   : field,
1206                         op    : update_value,
1207                         value : (update_value == result_value) ? STATE_VALUE_CHANGE_SUCCESS : STATE_VALUE_CHANGE_FAILURE,
1208                     }
1209                     manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data);
1210
1211                     break;
1212                 case TYPE_RECORD:
1213                     throw "Not implemented";
1214                     break;
1215
1216                 case TYPE_LIST_OF_VALUES:
1217                     // Same as list of records, but we don't have to extract keys
1218                     
1219                     // The rest of exactly the same (XXX factorize)
1220                     var update_keys  = update_query_orig.params[field];
1221                     var query_keys   = update_query.params[field];
1222                     var added_keys   = $.grep(query_keys, function (x) { return $.inArray(x, update_keys) == -1 });
1223                     var removed_keys = $.grep(update_keys, function (x) { return $.inArray(x, query_keys) == -1 });
1224
1225
1226                     $.each(added_keys, function(i, key) {
1227                         if ($.inArray(key, result_value) == -1) {
1228                             data = {
1229                                 request: FIELD_REQUEST_ADD,
1230                                 key   : field,
1231                                 value : key,
1232                                 status: FIELD_REQUEST_FAILURE,
1233                             }
1234                         } else {
1235                             data = {
1236                                 request: FIELD_REQUEST_ADD,
1237                                 key   : field,
1238                                 value : key,
1239                                 status: FIELD_REQUEST_SUCCESS,
1240                             }
1241                         }
1242                         manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data);
1243                     });
1244                     $.each(removed_keys, function(i, key) {
1245                         if ($.inArray(key, result_keys) == -1) {
1246                             data = {
1247                                 request: FIELD_REQUEST_REMOVE,
1248                                 key   : field,
1249                                 value : key,
1250                                 status: FIELD_REQUEST_SUCCESS,
1251                             }
1252                         } else {
1253                             data = {
1254                                 request: FIELD_REQUEST_REMOVE,
1255                                 key   : field,
1256                                 value : key,
1257                                 status: FIELD_REQUEST_FAILURE,
1258                             }
1259                         }
1260                         manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data);
1261                     });
1262
1263
1264                     break;
1265                 case TYPE_LIST_OF_RECORDS:
1266                     // example: slice.resource
1267                     //  - update_query_orig.params.resource = resources in slice before update
1268                     //  - update_query.params.resource = resource requested in slice
1269                     //  - keys from field = resources obtained
1270                     var key = manifold.metadata.get_key(field);
1271                     if (!key)
1272                         continue;
1273                     if (key.length > 1) {
1274                         throw "Not implemented";
1275                         continue;
1276                     }
1277                     key = key[0];
1278
1279                     /* XXX should be modified for multiple keys */
1280                     var result_keys  = $.map(record[field], function(x) { return manifold.record_get_value(x, key); });
1281
1282                     var update_keys  = update_query_orig.params[field];
1283                     var query_keys   = update_query.params[field];
1284                     var added_keys   = $.grep(query_keys, function (x) { return $.inArray(x, update_keys) == -1 });
1285                     var removed_keys = $.grep(update_keys, function (x) { return $.inArray(x, query_keys) == -1 });
1286
1287
1288                     $.each(added_keys, function(i, key) {
1289                         if ($.inArray(key, result_keys) == -1) {
1290                             data = {
1291                                 state: STATE_SET,
1292                                 key  : field,
1293                                 op   : STATE_SET_IN_FAILURE,
1294                                 value: key
1295                             }
1296                         } else {
1297                             data = {
1298                                 state: STATE_SET,
1299                                 key  : field,
1300                                 op   : STATE_SET_IN_SUCCESS,
1301                                 value: key
1302                             }
1303                         }
1304                         manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data);
1305                     });
1306                     $.each(removed_keys, function(i, key) {
1307                         if ($.inArray(key, result_keys) == -1) {
1308                             data = {
1309                                 state: STATE_SET,
1310                                 key  : field,
1311                                 op   : STATE_SET_OUT_SUCCESS,
1312                                 value: key
1313                             }
1314                         } else {
1315                             data = {
1316                                 state: STATE_SET,
1317                                 key  : field,
1318                                 op   : STATE_SET_OUT_FAILURE,
1319                                 value: key
1320                             }
1321                         }
1322                         manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data);
1323                     });
1324
1325
1326                     break;
1327             }
1328         }
1329         
1330         // XXX Now we need to adapt 'update' and 'update_orig' queries as if we had done a get
1331         this.setup_update_query(query, records);
1332
1333         var query_ext = manifold.query_store.find_query_ext(query.query_uuid);
1334         query_ext.query_state = QUERY_STATE_DONE;
1335
1336         // Send DONE message to plugins
1337         manifold.raise_record_event(query.query_uuid, DONE);
1338
1339     },
1340
1341     process_query_records: function(query, records) {
1342         if (query.action == 'get') {
1343             this.process_get_query_records(query, records);
1344         } else if (query.action == 'update') {
1345             this.process_update_query_records(query, records);
1346         }
1347     },
1348
1349     // if set callback is provided it is called
1350     // most of the time publish_uuid will be query.query_uuid
1351     // however in some cases we wish to publish the result under a different uuid
1352     // e.g. an updater wants to publish its result as if from the original (get) query
1353     asynchroneous_success : function (data, query, publish_uuid, callback) {
1354         // xxx should have a nicer declaration of that enum in sync with the python code somehow
1355         
1356         var start = new Date();
1357         if (manifold.asynchroneous_debug)
1358             messages.debug(">>>>>>>>>> asynchroneous_success query.object=" + query.object);
1359
1360         if (data.code == 2) { // ERROR
1361             // We need to make sense of error codes here
1362             alert("Your session has expired, please log in again");
1363             localStorage.removeItem('user');
1364             window.location="/logout/";
1365             if (manifold.asynchroneous_debug) {
1366                 duration=new Date()-start;
1367                 messages.debug ("<<<<<<<<<< asynchroneous_success " + query.object + " -- error returned - logging out " + duration + " ms");
1368             }
1369             return;
1370         }
1371         if (data.code == 1) { // WARNING
1372             messages.error("Some errors have been received from the manifold backend at " + MANIFOLD_URL + " [" + data.description + "]");
1373             // publish error code and text message on a separate channel for whoever is interested
1374             if (publish_uuid)
1375                 $.publish("/results/" + publish_uuid + "/failed", [data.code, data.description] );
1376
1377         }
1378
1379         // If a callback has been specified, we redirect results to it 
1380         if (!!callback) { 
1381             callback(data); 
1382             if (manifold.asynchroneous_debug) {
1383                 duration=new Date()-start;
1384                 messages.debug ("<<<<<<<<<< asynchroneous_success " + query.object + " -- callback ended " + duration + " ms");
1385             }
1386             return; 
1387         }
1388
1389         if (manifold.asynchroneous_debug) 
1390             messages.debug ("========== asynchroneous_success " + query.object + " -- before process_query_records [" + query.query_uuid +"]");
1391
1392         // once everything is checked we can use the 'value' part of the manifoldresult
1393         var result=data.value;
1394         if (result) {
1395             /* Eventually update the content of related queries (update, etc) */
1396             manifold.make_records(query.object, result);
1397             this.process_query_records(query, result);
1398
1399             /* Publish results: disabled here, done in the previous call */
1400             //tmp_query = manifold.find_query(query.query_uuid);
1401             //manifold.publish_result_rec(tmp_query.analyzed_query, result);
1402         }
1403         if (manifold.asynchroneous_debug) {
1404             duration=new Date()-start;
1405             messages.debug ("<<<<<<<<<< asynchroneous_success " + query.object + " -- done " + duration + " ms");
1406         }
1407
1408     },
1409
1410     /************************************************************************** 
1411      * Plugin API helpers
1412      **************************************************************************/ 
1413
1414     raise_event_handler: function(type, query_uuid, event_type, value) {
1415         if (manifold.pubsub_debug)
1416             messages.debug("raise_event_handler, quuid="+query_uuid+" type="+type+" event_type="+event_type);
1417         if ((type != 'query') && (type != 'record'))
1418             throw 'Incorrect type for manifold.raise_event()';
1419         // xxx we observe quite a lot of incoming calls with an undefined query_uuid
1420         // this should be fixed upstream in manifold I expect
1421         if (query_uuid === undefined) {
1422             messages.warning("undefined query in raise_event_handler");
1423             return;
1424         }
1425
1426         // notify the change to objects that either listen to this channel specifically,
1427         // or to the wildcard channel
1428         var channels = [ manifold.get_channel(type, query_uuid), manifold.get_channel(type, '*') ];
1429
1430         $.each(channels, function(i, channel) {
1431             if (value === undefined) {
1432                 if (manifold.pubsub_debug) messages.debug("triggering [no value] on channel="+channel+" and event_type="+event_type);
1433                 $('.pubsub').trigger(channel, [event_type]);
1434             } else {
1435                 if (manifold.pubsub_debug) messages.debug("triggering [value="+value+"] on channel="+channel+" and event_type="+event_type);
1436                 $('.pubsub').trigger(channel, [event_type, value]);
1437             }
1438         });
1439     },
1440
1441     raise_query_event: function(query_uuid, event_type, value) {
1442         manifold.raise_event_handler('query', query_uuid, event_type, value);
1443     },
1444
1445     raise_record_event: function(query_uuid, event_type, value) {
1446         manifold.raise_event_handler('record', query_uuid, event_type, value);
1447     },
1448
1449     /**
1450      * Event handler helpers
1451      */
1452     _get_next_state_add: function(prev_state, event_type)
1453     {
1454         switch (prev_state) {
1455             case STATE_SET_OUT:
1456             case STATE_SET_OUT_SUCCESS:
1457             case STATE_SET_IN_FAILURE:
1458                 new_state = STATE_SET_IN_PENDING;
1459                 break;
1460
1461             case STATE_SET_OUT_PENDING:
1462                 new_state = STATE_SET_IN;
1463                 break;
1464
1465             case STATE_SET_IN:
1466             case STATE_SET_IN_PENDING:
1467             case STATE_SET_IN_SUCCESS:
1468             case STATE_SET_OUT_FAILURE:
1469                 console.log("Inconsistent state: already in");
1470                 return;
1471         }
1472         return new_state;
1473     },
1474
1475     _get_next_state_remove: function(prev_state, event_type)
1476     {
1477         switch (prev_state) {
1478             case STATE_SET_IN:
1479             case STATE_SET_IN_SUCCESS:
1480             case STATE_SET_OUT_FAILURE:
1481                 new_state = STATE_SET_OUT_PENDING;
1482                 break;
1483
1484             case STATE_SET_IN_PENDING:
1485                 new_state = STATE_SET_OUT;
1486                 break;  
1487
1488             case STATE_SET_OUT:
1489             case STATE_SET_OUT_PENDING:
1490             case STATE_SET_OUT_SUCCESS:
1491             case STATE_SET_IN_FAILURE:
1492                 console.log("Inconsistent state: already out");
1493                 return;
1494         }
1495         return new_state;
1496     },
1497
1498     _grep_active_lease_callback: function(lease_query, resource_key) {
1499         return function(lease_key_lease) {
1500             var state, lease_key, lease;
1501
1502             lease_key = lease_key_lease[0];
1503             lease = lease_key_lease[1];
1504
1505             if (lease['resource'] != resource_key)
1506                 return false;
1507
1508             state = manifold.query_store.get_record_state(lease_query.query_uuid, lease_key, STATE_SET);;
1509             switch(state) {
1510                 case STATE_SET_IN:
1511                 case STATE_SET_IN_PENDING:
1512                 case STATE_SET_IN_SUCCESS:
1513                 case STATE_SET_OUT_FAILURE:
1514                     return true;
1515                 case STATE_SET_OUT:
1516                 case STATE_SET_OUT_PENDING:
1517                 case STATE_SET_OUT_SUCCESS:
1518                 case STATE_SET_IN_FAILURE:
1519                     return false;
1520             }
1521         }
1522     },
1523
1524     _enforce_constraints: function(query_ext, record, resource_key, event_type)
1525     {
1526         var query, data;
1527
1528         query = query_ext.query;
1529
1530         switch(query.object) {
1531
1532             case 'resource':
1533                 // CONSTRAINT_RESERVABLE_LEASE
1534                 // 
1535                 // +) If a reservable node is added to the slice, then it should have a corresponding lease
1536                 // XXX Not always a resource
1537                 var is_reservable = (record.exclusive == true);
1538                 if (is_reservable) {
1539                     var warnings = manifold.query_store.get_record_state(query.query_uuid, resource_key, STATE_WARNINGS);
1540
1541                     if (event_type == STATE_SET_ADD) {
1542                         // We should have a lease_query associated
1543                         var lease_query = query_ext.parent_query_ext.query.subqueries['lease']; // in  options
1544                         var lease_query_ext = manifold.query_store.find_analyzed_query_ext(lease_query.query_uuid);
1545                         // Do we have lease records (in) with this resource
1546                         var lease_records = $.grep(lease_query_ext.records.entries(), this._grep_active_lease_callback(lease_query, resource_key));
1547                         if (lease_records.length == 0) {
1548                             // Sets a warning
1549                             // XXX Need for a better function to manage warnings
1550                             var warn = CONSTRAINT_RESERVABLE_LEASE_MSG;
1551                             warnings[CONSTRAINT_RESERVABLE_LEASE] = warn;
1552                         } else {
1553                             // Lease are defined, delete the warning in case it was set previously
1554                             delete warnings[CONSTRAINT_RESERVABLE_LEASE];
1555                         }
1556                     } else {
1557                         // Remove warnings attached to this resource
1558                         delete warnings[CONSTRAINT_RESERVABLE_LEASE];
1559                     }
1560
1561                     manifold.query_store.set_record_state(query.query_uuid, resource_key, STATE_WARNINGS, warnings);
1562                 }
1563
1564                 /* This was redundant */
1565                 // manifold.query_store.recount(query.query_uuid); 
1566
1567                 // Signal the change to plugins (even if the constraint does not apply, so that the plugin can display a checkmark)
1568                 data = {
1569                     state:  STATE_WARNINGS,
1570                     key   : resource_key,
1571                     op    : null,
1572                     value : warnings
1573                 }
1574                 manifold.raise_record_event(query.query_uuid, FIELD_STATE_CHANGED, data);
1575                 break;
1576
1577             case 'lease':
1578                 var resource_key = record.resource;
1579                 var resource_query = query_ext.parent_query_ext.query.subqueries['resource'];
1580                 var warnings = manifold.query_store.get_record_state(resource_query.query_uuid, resource_key, STATE_WARNINGS);
1581
1582                 if (event_type == STATE_SET_ADD) {
1583                      // A lease is added, it removes the constraint
1584                     delete warnings[CONSTRAINT_RESERVABLE_LEASE];
1585                 } else {
1586                     // A lease is removed, it might trigger the warning
1587                     var lease_records = $.grep(query_ext.records.entries(), this._grep_active_lease_callback(query, resource_key));
1588                     if (lease_records.length == 0) { // XXX redundant cases
1589                         // Sets a warning
1590                         // XXX Need for a better function to manage warnings
1591                         var warn = CONSTRAINT_RESERVABLE_LEASE_MSG;
1592                         warnings[CONSTRAINT_RESERVABLE_LEASE] = warn;
1593                     } else {
1594                         // Lease are defined, delete the warning in case it was set previously
1595                         delete warnings[CONSTRAINT_RESERVABLE_LEASE];
1596                     }
1597                     
1598                 }
1599
1600                 manifold.query_store.recount(resource_query.query_uuid); 
1601
1602                 // Signal the change to plugins (even if the constraint does not apply, so that the plugin can display a checkmark)
1603                 data = {
1604                     state:  STATE_WARNINGS,
1605                     key   : resource_key,
1606                     op    : null,
1607                     value : warnings
1608                 }
1609                 manifold.raise_record_event(resource_query.query_uuid, FIELD_STATE_CHANGED, data);
1610                 break;
1611         }
1612
1613         // -) When a lease is added, it might remove the warning associated to a reservable node
1614
1615         // If a NITOS node is reserved, then at least a NITOS channel should be reserved
1616         // - When a NITOS channel is added, it might remove a warning associated to all NITOS nodes
1617
1618         // If a NITOS channel is reserved, then at least a NITOS node should be reserved
1619         // - When a NITOS node is added, it might remove a warning associated to all NITOS channels
1620
1621         // A lease is present while the resource has been removed => Require warnings on nodes not in set !
1622
1623     },
1624
1625     _get_query_path: function(query_ext) {
1626         var path = "";
1627         var sq = query_ext;
1628         while (sq.parent_query_ext) {
1629             if (path != "")
1630                 path = '.' + path;
1631             path = sq.query.object + path;
1632             sq = sq.parent_query_ext;
1633         }
1634         return path;
1635     },
1636
1637
1638     /**
1639      * Handling events raised by plugins
1640      */
1641     raise_event: function(query_uuid, event_type, data) 
1642     {
1643         var query, query_ext;
1644
1645         // Query uuid has been updated with the key of a new element
1646         query_ext    = manifold.query_store.find_analyzed_query_ext(query_uuid);
1647         query = query_ext.query;
1648
1649         switch(event_type) {
1650
1651             // XXX At some point, should be renamed to RECORD_STATE_CHANGED
1652             case FIELD_STATE_CHANGED:
1653
1654                 // value is an object (request, key, value, status)
1655                 // update is only possible is the query is not pending, etc
1656                 // SET_ADD is on a subquery, FIELD_STATE_CHANGED on the query itself
1657                 // we should map SET_ADD on this...
1658
1659                 // 1. Update internal query store about the change in status
1660
1661                 // 2. Update the update query
1662                 update_query      = query_ext.main_query_ext.update_query_ext.query;
1663                 update_query_orig = query_ext.main_query_ext.update_query_orig_ext.query;
1664
1665                 switch(data.state) {
1666             
1667                     case STATE_VALUE:
1668                         switch(data.op) {
1669                             case STATE_CHANGE:
1670                                 /* Set parameter data.key in the update_query to VALUE */
1671                                 if (update_query.params[data.key] === undefined)
1672                                     update_query.params[data.key] = Array();
1673                                 update_query.params[data.key] = value.value;
1674                                 break;
1675
1676                         }
1677                         break;
1678
1679                     case STATE_SET:
1680
1681                         switch(data.op) {
1682                             case STATE_SET_ADD:
1683                                 var prev_state, new_state;
1684                                 var main_query, record, new_data;
1685                 
1686                                 prev_state = manifold.query_store.get_record_state(query_uuid, data.value, STATE_SET);
1687                                 if (prev_state === null)
1688                                     prev_state = STATE_SET_OUT;
1689                                 new_state = this._get_next_state_add(prev_state, data.state);
1690                     
1691                                 if (!data.key) {
1692                                     /* data.value containts the resource key */
1693                                     manifold.query_store.add_record(query_uuid, data.value, new_state);
1694                                     record = manifold.query_store.get_record(query_uuid, data.value);
1695                                     this._enforce_constraints(query_ext, record, data.value, STATE_SET_ADD);
1696                     
1697                                     /* Inform the parent query: important for update */
1698                                     new_data = {
1699                                         state : STATE_SET,
1700                                         key   : this._get_query_path(query_ext),
1701                                         op    : STATE_SET_ADD,
1702                                         value : data.value,
1703                                     };
1704                                     main_query = query_ext.main_query_ext.query;
1705                                     this.raise_event(main_query.query_uuid, FIELD_STATE_CHANGED, new_data);
1706
1707                                     new_data.op = new_state;
1708                     
1709                                 } else {
1710                                     // mainquery: proceed to update
1711
1712                                     //if ($.inArray(data.value, update_query_orig.params[data.key]) != -1)
1713                                     //    value.request = FIELD_REQUEST_ADD_RESET;
1714
1715                                     if (update_query.params[data.key] === undefined)
1716                                         update_query.params[data.key] = Array();
1717                                     update_query.params[data.key].push(data.value);
1718
1719                                     new_data = {
1720                                         state : STATE_SET,
1721                                         key   : this._get_query_path(query_ext),// null???
1722                                         op    : new_state,
1723                                         value : data.value,
1724                                     }
1725                                 }
1726
1727                                 /* Propagate the event to other plugins subscribed to the query */
1728                                 manifold.raise_query_event(query_uuid, event_type, new_data);
1729
1730                                 break;
1731
1732                             case STATE_SET_REMOVE:
1733                                 var prev_state, new_state;
1734                                 var main_query, record, new_data;
1735                 
1736                                 prev_state = manifold.query_store.get_record_state(query_uuid, data.value, STATE_SET);
1737                                 if (prev_state === null)
1738                                     prev_state = STATE_SET_OUT;
1739                                 new_state = this._get_next_state_remove(prev_state, data.state);
1740                     
1741                                 if (!data.key) {
1742                                     /* data.value contains the resource key */
1743                                     manifold.query_store.remove_record(query_uuid, data.value, new_state);
1744                                     record = manifold.query_store.get_record(query_uuid, data.value);
1745                                     this._enforce_constraints(query_ext, record, data.value, STATE_SET_REMOVE);
1746                     
1747                                     /* Inform the parent query: important for update */
1748                                     new_data = {
1749                                         state : STATE_SET,
1750                                         key   : this._get_query_path(query_ext),
1751                                         op    : STATE_SET_REMOVE,
1752                                         value : data.value,
1753                                     };
1754                                     main_query = query_ext.main_query_ext.query;
1755                                     this.raise_event(main_query.query_uuid, FIELD_STATE_CHANGED, new_data);
1756                     
1757                                     new_data.op = new_state
1758                     
1759                                 } else {
1760                                     // main query: proceed to update
1761
1762                                     //if ($.inArray(data.value, update_query_orig.params[data.key]) == -1)
1763                                     //    value.request = FIELD_REQUEST_REMOVE_RESET;
1764
1765                                     var arr = update_query.params[data.key];
1766                                     arr = $.grep(arr, function(x) { return x != data.value; });
1767                                     if (update_query.params[data.key] === undefined)
1768                                         update_query.params[data.key] = Array();
1769                                     update_query.params[data.key] = arr;
1770
1771                                     new_data = {
1772                                         state : STATE_SET,
1773                                         key   : this._get_query_path(query_ext),// null???
1774                                         op    : new_state,
1775                                         value : data.value,
1776                                     }
1777                                 }
1778
1779                                 /* Propagate the event to other plugins subscribed to the query */
1780                                 manifold.raise_query_event(query_uuid, event_type, new_data);
1781                                 break;
1782                         }
1783                         break;
1784                 }
1785
1786                 // 3. Inform others about the change
1787                 // a) the main query...
1788                 manifold.raise_record_event(query_uuid, event_type, data);
1789
1790                 // b) subqueries eventually (dot in the key)
1791                 // Let's unfold 
1792
1793                 var cur_query = query;
1794                 if (cur_query.analyzed_query)
1795                     cur_query = cur_query.analyzed_query;
1796
1797                 if (data.key) {
1798                     var path_array = data.key.split('.');
1799                     var value_key = data.key.split('.');
1800                     $.each(path_array, function(i, method) {
1801                         cur_query = cur_query.subqueries[method];
1802                         value_key.shift(); // XXX check that method is indeed shifted
1803                     });
1804                     data.key = value_key;
1805                 }
1806
1807                 manifold.query_store.recount(cur_query.query_uuid);
1808                 manifold.raise_record_event(cur_query.query_uuid, event_type, data);
1809
1810                 break;
1811
1812             case RUN_UPDATE:
1813                 manifold.run_query(query_ext.main_query_ext.update_query_ext.query);
1814                 break;
1815
1816             /* QUERY STATE CHANGED */
1817             
1818             // FILTERS
1819
1820             case FILTER_ADDED: 
1821                 /* Update internal record state */
1822                 manifold.query_store.add_filter(query_uuid, data);
1823
1824                 /* Propagate the message to plugins */
1825                 manifold.raise_query_event(query_uuid, event_type, data);
1826
1827                 break;
1828
1829             case FILTER_REMOVED:
1830                 /* Update internal record state */
1831                 manifold.query_store.remove_filter(query_uuid, data);
1832
1833                 /* Propagate the message to plugins */
1834                 manifold.raise_query_event(query_uuid, event_type, data);
1835
1836                 break;
1837
1838             case FIELD_ADDED:
1839                 main_query = query_ext.main_query_ext.query;
1840                 main_update_query = query_ext.main_query_ext.update_query;
1841                 query.select(data);
1842
1843                 // Here we need the full path through all subqueries
1844                 path = ""
1845                 // XXX We might need the query name in the QueryExt structure
1846                 main_query.select(data);
1847
1848                 // XXX When is an update query associated ?
1849                 // XXX main_update_query.select(value);
1850
1851                 manifold.raise_query_event(query_uuid, event_type, data);
1852                 break;
1853
1854             case FIELD_REMOVED:
1855                 query = query_ext.query;
1856                 main_query = query_ext.main_query_ext.query;
1857                 main_update_query = query_ext.main_query_ext.update_query;
1858                 query.unselect(data);
1859                 main_query.unselect(data);
1860
1861                 // We need to inform about changes in these queries to the respective plugins
1862                 // Note: query & main_query have the same UUID
1863                 manifold.raise_query_event(query_uuid, event_type, data);
1864                 break;
1865         }
1866         // We need to inform about changes in these queries to the respective plugins
1867         // Note: query, main_query & update_query have the same UUID
1868
1869         // http://trac.myslice.info/ticket/32
1870         // Avoid multiple calls to the same event
1871         //manifold.raise_query_event(query_uuid, event_type, value);
1872
1873         // We are targeting the same object with get and update
1874         // The notion of query is bad, we should have a notion of destination, and issue queries on the destination
1875         // NOTE: Editing a subquery == editing a local view on the destination
1876
1877         // XXX We might need to run the new query again and manage the plugins in the meantime with spinners...
1878         // For the time being, we will collect all columns during the first query
1879     },
1880
1881     /* Publish/subscribe channels for internal use */
1882     get_channel: function(type, query_uuid) {
1883         if ((type !== 'query') && (type != 'record'))
1884             return null;
1885         return '/' + type + '/' + query_uuid;
1886     },
1887
1888 }; // manifold object
1889 /* ------------------------------------------------------------ */
1890
1891 (function($) {
1892
1893     // OLD PLUGIN API: extend jQuery/$ with pubsub capabilities
1894     // https://gist.github.com/661855
1895     var o = $({});
1896     $.subscribe = function( channel, selector, data, fn) {
1897       /* borrowed from jQuery */
1898       if ( data == null && fn == null ) {
1899           // ( channel, fn )
1900           fn = selector;
1901           data = selector = undefined;
1902       } else if ( fn == null ) {
1903           if ( typeof selector === "string" ) {
1904               // ( channel, selector, fn )
1905               fn = data;
1906               data = undefined;
1907           } else {
1908               // ( channel, data, fn )
1909               fn = data;
1910               data = selector;
1911               selector = undefined;
1912           }
1913       }
1914       /* </ugly> */
1915   
1916       /* We use an indirection function that will clone the object passed in
1917        * parameter to the subscribe callback 
1918        * 
1919        * FIXME currently we only clone query objects which are the only ones
1920        * supported and editable, we might have the same issue with results but
1921        * the page load time will be severely affected...
1922        */
1923       o.on.apply(o, [channel, selector, data, function() { 
1924           for(i = 1; i < arguments.length; i++) {
1925               if ( arguments[i].constructor.name == 'ManifoldQuery' )
1926                   arguments[i] = arguments[i].clone();
1927           }
1928           fn.apply(o, arguments);
1929       }]);
1930     };
1931   
1932     $.unsubscribe = function() {
1933       o.off.apply(o, arguments);
1934     };
1935   
1936     $.publish = function() {
1937       o.trigger.apply(o, arguments);
1938     };
1939   
1940 }(jQuery));
1941
1942 /* ------------------------------------------------------------ */
1943
1944 //http://stackoverflow.com/questions/5100539/django-csrf-check-failing-with-an-ajax-post-request
1945 //make sure to expose csrf in our outcoming ajax/post requests
1946 $.ajaxSetup({ 
1947      beforeSend: function(xhr, settings) {
1948          function getCookie(name) {
1949              var cookieValue = null;
1950              if (document.cookie && document.cookie != '') {
1951                  var cookies = document.cookie.split(';');
1952                  for (var i = 0; i < cookies.length; i++) {
1953                      var cookie = jQuery.trim(cookies[i]);
1954                      // Does this cookie string begin with the name we want?
1955                  if (cookie.substring(0, name.length + 1) == (name + '=')) {
1956                      cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
1957                      break;
1958                  }
1959              }
1960          }
1961          return cookieValue;
1962          }
1963          if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
1964              // Only send the token to relative URLs i.e. locally.
1965              xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
1966          }
1967      } 
1968 });