src/js/libs/tracemanager.js
changeset 1076 510fd2a482f4
parent 1075 92cb33eb7a75
child 1077 48cdc013fc07
equal deleted inserted replaced
1075:92cb33eb7a75 1076:510fd2a482f4
     1 /*
       
     2  * Modelled Trace API
       
     3  *
       
     4  * This file is part of ktbs4js.
       
     5  *
       
     6  * ktbs4js is free software: you can redistribute it and/or modify it
       
     7  * under the terms of the GNU Lesser General Public License as
       
     8  * published by the Free Software Foundation, either version 3 of the
       
     9  * License, or (at your option) any later version.
       
    10  *
       
    11  * ktbs4js is distributed in the hope that it will be useful,
       
    12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    14  * GNU Lesser General Public License for more details.
       
    15  *
       
    16  * You should have received a copy of the GNU Lesser General Public
       
    17  * License along with ktbs4js.  If not, see <http://www.gnu.org/licenses/>.
       
    18  *
       
    19  */
       
    20 /* FIXME: properly use require.js feature. This will do for debugging in the meantime */
       
    21 window.tracemanager = (function($) {
       
    22      // If there are more than MAX_FAILURE_COUNT synchronisation
       
    23      // failures, then disable synchronisation
       
    24     var MAX_FAILURE_COUNT = 20;
       
    25 
       
    26      // If there are more than MAX_BUFFER_SIZE obsels in the buffer,
       
    27      // then "compress" them as a single "ktbsFullBuffer"
       
    28     var  MAX_BUFFER_SIZE = 500;
       
    29 
       
    30     var _replacement = {
       
    31          ';': '"',
       
    32          '"': ';',
       
    33          '#': '%23',
       
    34          '&': '%26',
       
    35          '?': '%3F'
       
    36      };
       
    37      var BufferedService_prototype = {
       
    38          /*
       
    39           *  Buffered service for traces
       
    40           */
       
    41          // url: "",
       
    42          // buffer: [],
       
    43          // isReady: false,
       
    44          // timer: null,
       
    45          // failureCount: 0,
       
    46 
       
    47          /* Flush buffer */
       
    48          flush: function() {
       
    49              // FIXME: add mutex on this.buffer
       
    50              if (! this.isReady)
       
    51              {
       
    52                  if (window.console) window.console.log("Sync service not ready");
       
    53              } else if (this.failureCount > MAX_FAILURE_COUNT)
       
    54              {
       
    55                  // Disable synchronisation
       
    56                  this.set_sync_mode('none');
       
    57              } else if (this.buffer.length) {
       
    58                  var temp = this.buffer;
       
    59                  this.buffer = [];
       
    60 
       
    61                  if (this.mode == 'GET')
       
    62                  {
       
    63                      // GET mode: do some data mangline. We mark the
       
    64                      // "compressed" nature of the generated JSON by
       
    65                      // prefixing it with c
       
    66                      var data = 'c' + JSON.stringify(temp.map(function (o) { return o.toCompactJSON(); }));
       
    67                      // Swap " (very frequent, which will be
       
    68                      // serialized into %22) and ; (rather rare), this
       
    69                      // saves some bytes
       
    70                      data = data.replace(/[;"#?&]/g, function(s){ return _replacement[s]; });
       
    71                      // FIXME: check data length (< 2K is safe)
       
    72                      var request=$('<img />').error( function() { this.failureCount += 1; })
       
    73                          .load( function() { this.failureCount = 0; })
       
    74                          .attr('src', this.url + 'trace/?data=' + data);
       
    75                  }
       
    76                  else
       
    77                  {
       
    78                      $.ajax({ url: this.url + 'trace/',
       
    79                               type: 'POST',
       
    80                               contentType: 'application/json',
       
    81                               data: JSON.stringify(temp.map(function (o) { return o.toJSON(); })),
       
    82                               processData: false,
       
    83                               // Type of the returned data.
       
    84                               dataType: "html",
       
    85                               error: function(jqXHR, textStatus, errorThrown) {
       
    86                                   if (window.console) window.console.log("Error when sending buffer:", textStatus);
       
    87                                   this.failureCount += 1;
       
    88                               },
       
    89                               success: function(data, textStatus, jqXHR) {
       
    90                                   // Reset failureCount to 0 as soon as there is 1 valid answer
       
    91                                   this.failureCount = 0;
       
    92                               }
       
    93                             });
       
    94                  }
       
    95              }
       
    96          },
       
    97 
       
    98          /* Sync mode: delayed, sync (immediate sync), none (no
       
    99           * synchronisation with server, the trace has to be explicitly saved
       
   100           * if needed */
       
   101          set_sync_mode: function(mode, default_subject) {
       
   102              this.sync_mode = mode;
       
   103              if (! this.isReady && mode !== "none")
       
   104                  this.init(default_subject);
       
   105              if (mode == 'delayed') {
       
   106                  this.start_timer();
       
   107              } else {
       
   108                  this.stop_timer();
       
   109              }
       
   110          },
       
   111 
       
   112          /* Enqueue an obsel */
       
   113          enqueue: function(obsel) {
       
   114              if (this.buffer.length > MAX_BUFFER_SIZE)
       
   115              {
       
   116                  obsel = new Obsel('ktbsFullBuffer', this.buffer[0].begin,
       
   117                                    this.buffer[this.buffer.length - 1].end, this.buffer[0].subject);
       
   118                  obsel.trace = this.buffer[0].trace;
       
   119                  this.buffer = [];
       
   120              }
       
   121              this.buffer.push(obsel);
       
   122              if (this.sync_mode === 'sync') {
       
   123                  // Immediate sync of the obsel.
       
   124                  this.flush();
       
   125              }
       
   126          },
       
   127 
       
   128          start_timer: function() {
       
   129              var self = this;
       
   130              if (this.timer === null) {
       
   131                  this.timer = window.setInterval(function() {
       
   132                                                      self.flush();
       
   133                                                  }, this.timeOut);
       
   134              }
       
   135          },
       
   136 
       
   137          stop_timer: function() {
       
   138              if (this.timer !== null) {
       
   139                  window.clearInterval(this.timer);
       
   140                  this.timer = null;
       
   141              }
       
   142          },
       
   143 
       
   144          /*
       
   145           * Initialize the sync service
       
   146           */
       
   147          init: function(default_subject) {
       
   148              var self = this;
       
   149              if (this.isReady)
       
   150                  /* Already initialized */
       
   151                  return;
       
   152              if (typeof default_subject === 'undefined')
       
   153                  default_subject = 'anonymous';
       
   154              if (this.mode == 'GET')
       
   155              {
       
   156                  var request=$('<img/>').attr('src', this.url + 'login?userinfo={"default_subject": "' + default_subject + '"}');
       
   157                  // Do not wait for the return, assume it is
       
   158                  // initialized. This assumption will not work anymore
       
   159                  // if login returns some necessary information
       
   160                  this.isReady = true;
       
   161              }
       
   162              else
       
   163              {
       
   164                  $.ajax({ url: this.url + 'login',
       
   165                           type: 'POST',
       
   166                           data: 'userinfo={"default_subject":"' + default_subject + '"}',
       
   167                           success: function(data, textStatus, jqXHR) {
       
   168                               self.isReady = true;
       
   169                               if (self.buffer.length) {
       
   170                                   self.flush();
       
   171                               }
       
   172                           }
       
   173                         });
       
   174              }
       
   175          }
       
   176      };
       
   177      var BufferedService = function(url, mode) {
       
   178          this.url = url;
       
   179          this.buffer = [];
       
   180          this.isReady = false;
       
   181          this.timer = null;
       
   182          this.failureCount = 0;
       
   183          // sync_mode is either "none", "sync" or "buffered"
       
   184          this.sync_mode = "none";
       
   185          /* mode can be either POST or GET */
       
   186          if (mode == 'POST' || mode == 'GET')
       
   187              this.mode = mode;
       
   188          else
       
   189              this.mode = 'POST';
       
   190          /* Flush buffer every timeOut ms if the sync_mode is delayed */
       
   191          this.timeOut = 2000;
       
   192      };
       
   193      BufferedService.prototype = BufferedService_prototype;
       
   194 
       
   195      var Trace_prototype = {
       
   196          /* FIXME: We could/should use a sorted list such as
       
   197           http://closure-library.googlecode.com/svn/docs/class_goog_structs_AvlTree.html
       
   198           to speed up queries based on time */
       
   199          obsels: [],
       
   200          /* Trace URI */
       
   201          uri: "",
       
   202          default_subject: "",
       
   203          /* baseuri is used as the base URI to resolve relative
       
   204           * attribute-type names in obsels. Strictly speaking, this
       
   205           * should rather be expressed as a reference to model, or
       
   206           * more generically, as a qname/URI dict */
       
   207          baseuri: "",
       
   208          /* Mapping of obsel type or property name to a compact
       
   209           * representation (shorthands).
       
   210           */
       
   211          shorthands: null,
       
   212          syncservice: null,
       
   213 
       
   214          /* Define the trace URI */
       
   215          set_uri: function(uri) {
       
   216              this.uri = uri;
       
   217          },
       
   218 
       
   219          /* Sync mode: delayed, sync (immediate sync), none (no
       
   220           * synchronisation with server, the trace has to be explicitly saved
       
   221           * if needed */
       
   222          set_sync_mode: function(mode) {
       
   223              if (this.syncservice !== null) {
       
   224                  this.syncservice.set_sync_mode(mode, this.default_subject);
       
   225              }
       
   226          },
       
   227 
       
   228          /*
       
   229           * Return a list of the obsels of this trace matching the parameters
       
   230           */
       
   231          list_obsels: function(_begin, _end, _reverse) {
       
   232              var res;
       
   233              if (typeof _begin !== 'undefined' || typeof _end !== 'undefined') {
       
   234                  /*
       
   235                   * Not optimized yet.
       
   236                   */
       
   237                  res = [];
       
   238                  var l = this.obsels.length;
       
   239                  for (var i = 0; i < l; i++) {
       
   240                      var o = this.obsels[i];
       
   241                      if ((typeof _begin !== 'undefined' && o.begin > _begin) && (typeof _end !== 'undefined' && o.end < _end)) {
       
   242                          res.push(o);
       
   243                      }
       
   244                  }
       
   245              }
       
   246 
       
   247              if (typeof _reverse !== 'undefined') {
       
   248                  if (res !== undefined) {
       
   249                      /* Should reverse the whole list. Make a copy. */
       
   250                      res = this.obsels.slice(0);
       
   251                  }
       
   252                  res.sort(function(a, b) { return b.begin - a.begin; });
       
   253                  return res;
       
   254              }
       
   255 
       
   256              if (res === undefined) {
       
   257                  res = this.obsels;
       
   258              }
       
   259              return res;
       
   260 
       
   261          },
       
   262 
       
   263          /*
       
   264           * Return the obsel of this trace identified by the URI, or undefined
       
   265           */
       
   266          get_obsel: function(id) {
       
   267              for (var i = 0; i < this.obsels.length; i++) {
       
   268                  /* FIXME: should check against variations of id/uri, take this.baseuri into account */
       
   269                  if (this.obsels[i].uri === id) {
       
   270                      return this.obsels[i];
       
   271                  }
       
   272              }
       
   273              return undefined;
       
   274          },
       
   275 
       
   276          set_default_subject: function(subject) {
       
   277              // FIXME: if we call this method after the sync_service
       
   278              // init method, then the default_subject will not be
       
   279              // consistent anymore. Maybe we should then call init() again?
       
   280              this.default_subject = subject;
       
   281          },
       
   282 
       
   283          get_default_subject: function() {
       
   284              return this.default_subject;
       
   285          },
       
   286 
       
   287          /* (type:ObselType, begin:int, end:int?, subject:str?, attributes:[AttributeType=>any]?) */
       
   288          /* Create a new obsel and add it to the trace */
       
   289          create_obsel: function(type, begin, end, subject, _attributes) {
       
   290              var o = new Obsel(type, begin, end, subject);
       
   291              if (typeof _attributes !== 'undefined') {
       
   292                  o.attributes = _attributes;
       
   293              }
       
   294              o.trace = this;
       
   295              this.obsels.push(o);
       
   296              if (this.syncservice !== null)
       
   297                  this.syncservice.enqueue(o);
       
   298          },
       
   299 
       
   300          /* Helper methods */
       
   301 
       
   302          /* Create a new obsel with the given attributes */
       
   303          trace: function(type, _attributes, _begin, _end, _subject) {
       
   304              var t = (new Date()).getTime();
       
   305              if (typeof begin === 'undefined') {
       
   306                  _begin = t;
       
   307              }
       
   308              if (typeof end === 'undefined') {
       
   309                  _end = _begin;
       
   310              }
       
   311              if (typeof subject === 'undefined') {
       
   312                  _subject = this.default_subject;
       
   313              }
       
   314              if (typeof _attributes === 'undefined') {
       
   315                  _attributes = {};
       
   316              }
       
   317              return this.create_obsel(type, _begin, _end, _subject, _attributes);
       
   318          }
       
   319      };
       
   320 
       
   321      var Trace = function(uri, requestmode) {
       
   322          /* FIXME: We could/should use a sorted list such as
       
   323           http://closure-library.googlecode.com/svn/docs/class_goog_structs_AvlTree.html
       
   324           to speed up queries based on time */
       
   325          this.obsels = [];
       
   326          /* Trace URI */
       
   327          if (uri === undefined)
       
   328              uri = "";
       
   329          this.uri = uri;
       
   330          this.sync_mode = "none";
       
   331          this.default_subject = "";
       
   332          this.shorthands = {};
       
   333          /* baseuri is used a the base URI to resolve relative attribute names in obsels */
       
   334          this.baseuri = "";
       
   335 
       
   336          this.syncservice = new BufferedService(uri, requestmode);
       
   337          $(window).unload( function () {
       
   338                                if (this.syncservice && this.sync_mode !== 'none') {
       
   339                                    this.syncservice.flush();
       
   340                                    this.syncservice.stop_timer();
       
   341                                }
       
   342                            });
       
   343      };
       
   344      Trace.prototype = Trace_prototype;
       
   345 
       
   346      var Obsel_prototype = {
       
   347          /* The following attributes are here for documentation
       
   348           * purposes. They MUST be defined in the constructor
       
   349           * function. */
       
   350          trace: undefined,
       
   351          type: undefined,
       
   352          begin: undefined,
       
   353          end: undefined,
       
   354          subject: undefined,
       
   355          /* Dictionary indexed by ObselType URIs */
       
   356          attributes: {},
       
   357 
       
   358          /* Method definitions */
       
   359          get_trace: function() {
       
   360              return this.trace;
       
   361          },
       
   362 
       
   363          get_obsel_type: function() {
       
   364              return this.type;
       
   365          },
       
   366          get_begin: function() {
       
   367              return this.begin;
       
   368          },
       
   369          get_end: function() {
       
   370              return this.end;
       
   371          },
       
   372          get_subject: function() {
       
   373              return this.subject;
       
   374          },
       
   375 
       
   376          list_attribute_types: function() {
       
   377              var result = [];
       
   378              for (var prop in this.attributes) {
       
   379                  if (this.attributes.hasOwnProperty(prop))
       
   380                      result.push(prop);
       
   381              }
       
   382              /* FIXME: we return URIs here instead of AttributeType elements */
       
   383              return result;
       
   384          },
       
   385 
       
   386          list_relation_types: function() {
       
   387              /* FIXME: not implemented yet */
       
   388          },
       
   389 
       
   390          list_related_obsels: function (rt) {
       
   391              /* FIXME: not implemented yet */
       
   392          },
       
   393          list_inverse_relation_types: function () {
       
   394              /* FIXME: not implemented yet */
       
   395          },
       
   396          list_relating_obsels: function (rt) {
       
   397              /* FIXME: not implemented yet */
       
   398          },
       
   399          /*
       
   400           * Return the value of the given attribute type for this obsel
       
   401           */
       
   402          get_attribute_value: function(at) {
       
   403              if (typeof at === "string")
       
   404                  /* It is a URI */
       
   405                  return this.attributes[at];
       
   406              else
       
   407                  /* FIXME: check that at is instance of AttributeType */
       
   408                  return this.attributes[at.uri];
       
   409          },
       
   410 
       
   411 
       
   412          /* obsel modification (trace amendment) */
       
   413 
       
   414          set_attribute_value: function(at, value) {
       
   415              if (typeof at === "string")
       
   416                  /* It is a URI */
       
   417                  this.attributes[at] = value;
       
   418              /* FIXME: check that at is instance of AttributeType */
       
   419              else
       
   420                  this.attributes[at.uri] = value;
       
   421          },
       
   422 
       
   423          del_attribute_value: function(at) {
       
   424              if (typeof at === "string")
       
   425                  /* It is a URI */
       
   426                  delete this.attributes[at];
       
   427              /* FIXME: check that at is instance of AttributeType */
       
   428              else
       
   429                  delete this.attributes[at.uri];
       
   430          },
       
   431 
       
   432          add_related_obsel: function(rt, value) {
       
   433              /* FIXME: not implemented yet */
       
   434          },
       
   435 
       
   436          del_related_obsel: function(rt, value) {
       
   437              /* FIXME: not implemented yet */
       
   438          },
       
   439 
       
   440          /*
       
   441           * Return a JSON representation of the obsel
       
   442           */
       
   443          toJSON: function() {
       
   444              var r = {
       
   445                  "@id": this.id,
       
   446                  "@type": this.type,
       
   447                  "begin": this.begin,
       
   448                  "end": this.end,
       
   449                  "subject": this.subject
       
   450              };
       
   451              for (var prop in this.attributes) {
       
   452                  if (this.attributes.hasOwnProperty(prop))
       
   453                      r[prop] = this.attributes[prop];
       
   454              }
       
   455              return r;
       
   456          },
       
   457 
       
   458          /*
       
   459           * Return a compact JSON representation of the obsel.
       
   460           * Use predefined + custom shorthands for types/properties
       
   461           */
       
   462          toCompactJSON: function() {
       
   463              var r = {
       
   464                  "@t": (this.trace.shorthands.hasOwnProperty(this.type) ? this.trace.shorthands[this.type] : this.type),
       
   465                  "@b": this.begin,
       
   466              };
       
   467              // Transmit subject only if different from default_subject
       
   468              if (this.subject !== this.trace.default_subject)
       
   469                  r["@s"] = this.subject;
       
   470 
       
   471              // Store duration (to save some bytes) and only if it is non-null
       
   472              if (this.begin !== this.end)
       
   473                  r["@d"] = this.end - this.begin;
       
   474 
       
   475              // Store id only if != ""
       
   476              if (this.id !== "")
       
   477                  r["@i"] = this.id;
       
   478 
       
   479              for (var prop in this.attributes) {
       
   480                  if (this.attributes.hasOwnProperty(prop))
       
   481                  {
       
   482                      var v = this.attributes[prop];
       
   483                      r[prop] = this.trace.shorthands.hasOwnProperty(v) ? this.trace.shorthands[v] : v;
       
   484                  }
       
   485              }
       
   486              return r;
       
   487          },
       
   488 
       
   489          toJSONstring: function() {
       
   490              return JSON.stringify(this.toJSON());
       
   491          }
       
   492      };
       
   493 
       
   494      var Obsel = function(type, begin, end, subject, attributes) {
       
   495          this.trace = undefined;
       
   496          this.uri = "";
       
   497          this.id = "";
       
   498          this.type = type;
       
   499          this.begin = begin;
       
   500          this.end = end;
       
   501          this.subject = subject;
       
   502          /* Is the obsel synched with the server ? */
       
   503          this.sync_status = false;
       
   504          /* Dictionary indexed by ObselType URIs */
       
   505          this.attributes = {};
       
   506      };
       
   507      Obsel.prototype = Obsel_prototype;
       
   508 
       
   509      var TraceManager_prototype = {
       
   510          traces: [],
       
   511          /*
       
   512           * Return the trace with id name
       
   513           * If it was not registered, return undefined.
       
   514           */
       
   515          get_trace: function(name) {
       
   516              return this.traces[name];
       
   517          },
       
   518 
       
   519          /*
       
   520           * Explicitly create and initialize a new trace with the given name.
       
   521           * The optional uri parameter allows to initialize the trace URI.
       
   522           *
       
   523           * If another existed with the same name before, then it is replaced by a new one.
       
   524           */
       
   525          init_trace: function(name, params)
       
   526          {
       
   527              if (window.console) window.console.log("init_trace", params);
       
   528              url = params.url ? params.url : "";
       
   529              requestmode = params.requestmode ? params.requestmode : "POST";
       
   530              syncmode = params.syncmode ? params.syncmode : "none";
       
   531              default_subject = params.default_subject ? params.default_subject : "default";
       
   532              var t = new Trace(url, requestmode);
       
   533              t.set_default_subject(default_subject);
       
   534              t.set_sync_mode(syncmode);
       
   535              this.traces[name] = t;
       
   536              return t;
       
   537          }
       
   538      };
       
   539 
       
   540      var TraceManager = function() {
       
   541          this.traces = {};
       
   542      };
       
   543      TraceManager.prototype = TraceManager_prototype;
       
   544 
       
   545      var tracemanager  = new TraceManager();
       
   546      return tracemanager;
       
   547  })(jQuery);