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