erp5storage.js 12.8 KB
Newer Older
Tristan Cavelier's avatar
Tristan Cavelier committed
1
/*
Tristan Cavelier's avatar
Tristan Cavelier committed
2 3 4 5
 * Copyright 2013, Nexedi SA
 * Released under the LGPL license.
 * http://www.gnu.org/licenses/lgpl.html
 */
Romain Courteaud's avatar
Romain Courteaud committed
6
// JIO ERP5 Storage Description :
Tristan Cavelier's avatar
Tristan Cavelier committed
7
// {
Tristan Cavelier's avatar
Tristan Cavelier committed
8
//   type: "erp5"
Tristan Cavelier's avatar
Tristan Cavelier committed
9 10
//   url: {string}
// }
11

12
/*jslint nomen: true, unparam: true */
Romain Courteaud's avatar
Romain Courteaud committed
13
/*global jIO, UriTemplate, FormData, RSVP, URI, Blob*/
14

Romain Courteaud's avatar
Romain Courteaud committed
15
(function (jIO, UriTemplate, FormData, RSVP, URI, Blob) {
16
  "use strict";
17

18 19 20 21 22 23 24 25
  function getSiteDocument(storage) {
    return new RSVP.Queue()
      .push(function () {
        return jIO.util.ajax({
          "type": "GET",
          "url": storage._url,
          "xhrFields": {
            withCredentials: true
26
          }
27 28 29 30 31
        });
      })
      .push(function (event) {
        return JSON.parse(event.target.responseText);
      });
32 33
  }

34
  function getDocumentAndHateoas(storage, id, options) {
35 36
    if (options === undefined) {
      options = {};
37
    }
38 39
    return getSiteDocument(storage)
      .push(function (site_hal) {
40
        // XXX need to get modified metadata
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
        return new RSVP.Queue()
          .push(function () {
            return jIO.util.ajax({
              "type": "GET",
              "url": UriTemplate.parse(site_hal._links.traverse.href)
                                .expand({
                  relative_url: id,
                  view: options._view
                }),
              "xhrFields": {
                withCredentials: true
              }
            });
          })
          .push(undefined, function (error) {
            if ((error.target !== undefined) &&
                (error.target.status === 404)) {
              throw new jIO.util.jIOError("Cannot find document: " + id, 404);
            }
            throw error;
          });
62 63 64
      });
  }

65 66
  var allowed_field_dict = {
    "StringField": null,
67
    "EmailField": null,
68 69 70 71 72
    "IntegerField": null,
    "FloatField": null,
    "TextAreaField": null
  };

73 74 75
  function extractPropertyFromFormJSON(json) {
    return new RSVP.Queue()
      .push(function () {
76 77 78 79 80 81
        var form = json._embedded._view,
          converted_json = {
            portal_type: json.portal_type
          },
          form_data_json = {},
          field,
82 83
          key,
          prefix_length;
84 85 86 87 88 89 90 91 92

        form_data_json.form_id = {
          "key": [form.form_id.key],
          "default": form.form_id["default"]
        };
        // XXX How to store datetime
        for (key in form) {
          if (form.hasOwnProperty(key)) {
            field = form[key];
93 94 95 96 97 98 99 100
            prefix_length = 0;
            if (key.indexOf('my_') === 0 && field.editable) {
              prefix_length = 3;
            }
            if (key.indexOf('your_') === 0) {
              prefix_length = 5;
            }
            if ((prefix_length !== 0) &&
101
                (allowed_field_dict.hasOwnProperty(field.type))) {
102
              form_data_json[key.substring(prefix_length)] = {
103 104 105
                "default": field["default"],
                "key": field.key
              };
106
              converted_json[key.substring(prefix_length)] = field["default"];
107 108 109 110 111 112 113 114 115 116 117 118
            }
          }
        }

        return {
          action_href: form._actions.put.href,
          data: converted_json,
          form_data: form_data_json
        };
      });
  }

119 120 121 122 123 124 125 126 127 128 129 130 131
  function extractPropertyFromForm(context, id) {
    return context.getAttachment(id, "view")
      .push(function (blob) {
        return jIO.util.readBlobAsText(blob);
      })
      .push(function (evt) {
        return JSON.parse(evt.target.result);
      })
      .push(function (json) {
        return extractPropertyFromFormJSON(json);
      });
  }

132
  // XXX docstring
133 134 135 136 137 138
  function ERP5Storage(spec) {
    if (typeof spec.url !== "string" || !spec.url) {
      throw new TypeError("ERP5 'url' must be a string " +
                          "which contains more than one character.");
    }
    this._url = spec.url;
139
    this._default_view_reference = spec.default_view_reference;
140 141
  }

142 143 144 145 146 147 148 149 150 151 152 153 154 155
  function convertJSONToGet(json) {
    var key,
      result = json.data;
    // Remove all ERP5 hateoas links / convert them into jIO ID
    for (key in result) {
      if (result.hasOwnProperty(key)) {
        if (!result[key]) {
          delete result[key];
        }
      }
    }
    return result;
  }

156
  ERP5Storage.prototype.get = function (id) {
157 158
    return extractPropertyFromForm(this, id)
      .push(function (result) {
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
        return convertJSONToGet(result);
      });
  };

  ERP5Storage.prototype.bulk = function (request_list) {
    var i,
      storage = this,
      bulk_list = [];


    for (i = 0; i < request_list.length; i += 1) {
      if (request_list[i].method !== "get") {
        throw new Error("ERP5Storage: not supported " +
                        request_list[i].method + " in bulk");
      }
      bulk_list.push({
        relative_url: request_list[i].parameter_list[0],
        view: storage._default_view_reference
      });
    }
    return getSiteDocument(storage)
      .push(function (site_hal) {
        var form_data = new FormData();
        form_data.append("bulk_list", JSON.stringify(bulk_list));
        return jIO.util.ajax({
          "type": "POST",
          "url": site_hal._actions.bulk.href,
          "data": form_data,
//           "headers": {
//             "Content-Type": "application/json"
//           },
          "xhrFields": {
            withCredentials: true
192
          }
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
        });
      })
      .push(function (response) {
        var result_list = [],
          hateoas = JSON.parse(response.target.responseText);

        function pushResult(json) {
          json.portal_type = json._links.type.name;
          return extractPropertyFromFormJSON(json)
            .push(function (json2) {
              return convertJSONToGet(json2);
            });
        }

        for (i = 0; i < hateoas.result_list.length; i += 1) {
          result_list.push(pushResult(hateoas.result_list[i]));
209
        }
210
        return RSVP.all(result_list);
Romain Courteaud's avatar
Romain Courteaud committed
211
      });
212
  };
Romain Courteaud's avatar
Romain Courteaud committed
213

Romain Courteaud's avatar
Romain Courteaud committed
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
  ERP5Storage.prototype.post = function (data) {
    var context = this,
      new_id;

    return getSiteDocument(this)
      .push(function (site_hal) {
        var form_data = new FormData();
        form_data.append("portal_type", data.portal_type);
        form_data.append("parent_relative_url", data.parent_relative_url);
        return jIO.util.ajax({
          type: "POST",
          url: site_hal._actions.add.href,
          data: form_data,
          xhrFields: {
            withCredentials: true
          }
        });
      })
      .push(function (evt) {
        var location = evt.target.getResponseHeader("X-Location"),
          uri = new URI(location);
        new_id = uri.segment(2);
        return context.put(new_id, data);
      })
      .push(function () {
        return new_id;
      });
  };

Romain Courteaud's avatar
Romain Courteaud committed
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
  ERP5Storage.prototype.put = function (id, data) {
    var context = this;

    return extractPropertyFromForm(context, id)
      .push(function (result) {
        var key,
          json = result.form_data,
          form_data = {};
        form_data[json.form_id.key] = json.form_id["default"];

        // XXX How to store datetime:!!!!!
        for (key in data) {
          if (data.hasOwnProperty(key)) {
            if (key === "form_id") {
              throw new jIO.util.jIOError(
                "ERP5: forbidden property: " + key,
                400
              );
            }
            if ((key !== "portal_type") && (key !== "parent_relative_url")) {
              if (!json.hasOwnProperty(key)) {
                throw new jIO.util.jIOError(
                  "ERP5: can not store property: " + key,
                  400
                );
              }
              form_data[json[key].key] = data[key];
            }
          }
        }
        return context.putAttachment(
          id,
          result.action_href,
          new Blob([JSON.stringify(form_data)], {type: "application/json"})
        );
      });
  };

281
  ERP5Storage.prototype.allAttachments = function (id) {
282
    var context = this;
283 284
    return getDocumentAndHateoas(this, id)
      .push(function () {
285 286 287 288 289
        if (context._default_view_reference === undefined) {
          return {
            links: {}
          };
        }
290 291 292 293 294 295 296
        return {
          view: {},
          links: {}
        };
      });
  };

297
  ERP5Storage.prototype.getAttachment = function (id, action) {
298 299

    if (action === "view") {
300 301 302 303 304 305
      if (this._default_view_reference === undefined) {
        throw new jIO.util.jIOError(
          "Cannot find attachment view for: " + id,
          404
        );
      }
306
      return getDocumentAndHateoas(this, id,
307
                                   {"_view": this._default_view_reference})
308 309
        .push(function (response) {
          var result = JSON.parse(response.target.responseText);
310
          result._id = id;
311 312 313 314 315 316 317
          result.portal_type = result._links.type.name;
          // Remove all ERP5 hateoas links / convert them into jIO ID

          // XXX Change default action to an jio urn with attachment name inside
          // if Base_edit, do put URN
          // if others, do post URN (ie, unique new attachment name)
          // XXX Except this attachment name should be generated when
318
          return new Blob(
319 320
            [JSON.stringify(result)],
            {"type": 'application/hal+json'}
321
          );
322 323 324
        });
    }
    if (action === "links") {
325
      return getDocumentAndHateoas(this, id)
326
        .push(function (response) {
327
          return new Blob(
328 329
            [JSON.stringify(JSON.parse(response.target.responseText))],
            {"type": 'application/hal+json'}
330
          );
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
        });
    }
    if (action.indexOf(this._url) === 0) {
      return new RSVP.Queue()
        .push(function () {
          return jIO.util.ajax({
            "type": "GET",
            "url": action,
            "xhrFields": {
              withCredentials: true
            }
          });
        })
        .push(function (evt) {
          var result = JSON.parse(evt.target.responseText);
346
          result._id = id;
347
          return new Blob(
348 349
            [JSON.stringify(result)],
            {"type": evt.target.getResponseHeader("Content-Type")}
350
          );
351 352
        });
    }
Romain Courteaud's avatar
Romain Courteaud committed
353 354
    throw new jIO.util.jIOError("ERP5: not support get attachment: " + action,
                                400);
355 356
  };

357
  ERP5Storage.prototype.putAttachment = function (id, name, blob) {
358
    // Assert we use a callable on a document from the ERP5 site
359
    if (name.indexOf(this._url) !== 0) {
Romain Courteaud's avatar
Romain Courteaud committed
360
      throw new jIO.util.jIOError("Can not store outside ERP5: " +
361
                                  name, 400);
362 363 364 365
    }

    return new RSVP.Queue()
      .push(function () {
366
        return jIO.util.readBlobAsText(blob);
367 368 369
      })
      .push(function (evt) {
        var form_data = JSON.parse(evt.target.result),
Romain Courteaud's avatar
Romain Courteaud committed
370
          data = new FormData(),
371
          i,
Romain Courteaud's avatar
Romain Courteaud committed
372
          key;
373 374
        for (key in form_data) {
          if (form_data.hasOwnProperty(key)) {
375 376 377 378 379 380 381
            if (Array.isArray(form_data[key])) {
              for (i = 0; i < form_data[key].length; i += 1) {
                data.append(key, form_data[key][i]);
              }
            } else {
              data.append(key, form_data[key]);
            }
382
          }
383
        }
Romain Courteaud's avatar
Romain Courteaud committed
384
        return jIO.util.ajax({
385
          "type": "POST",
386
          "url": name,
Romain Courteaud's avatar
Romain Courteaud committed
387 388 389
          "data": data,
          "xhrFields": {
            withCredentials: true
390
          }
Romain Courteaud's avatar
Romain Courteaud committed
391
        });
392
      });
Romain Courteaud's avatar
Romain Courteaud committed
393 394
  };

395 396 397
  ERP5Storage.prototype.hasCapacity = function (name) {
    return ((name === "list") || (name === "query") ||
            (name === "select") || (name === "limit"));
398 399
  };

400 401 402 403 404 405 406 407
  ERP5Storage.prototype.buildQuery = function (options) {
//     if (typeof options.query !== "string") {
//       options.query = (options.query ?
//                        jIO.Query.objectToSearchText(options.query) :
//                        undefined);
//     }
    return getSiteDocument(this)
      .push(function (site_hal) {
Romain Courteaud's avatar
Romain Courteaud committed
408 409 410 411 412 413 414 415 416 417 418 419 420 421
        return jIO.util.ajax({
          "type": "GET",
          "url": UriTemplate.parse(site_hal._links.raw_search.href)
                            .expand({
              query: options.query,
              // XXX Force erp5 to return embedded document
              select_list: options.select_list || ["title", "reference"],
              limit: options.limit
            }),
          "xhrFields": {
            withCredentials: true
          }
        });
      })
422
      .push(function (response) {
Romain Courteaud's avatar
Romain Courteaud committed
423 424
        return JSON.parse(response.target.responseText);
      })
425
      .push(function (catalog_json) {
Romain Courteaud's avatar
Romain Courteaud committed
426 427 428
        var data = catalog_json._embedded.contents,
          count = data.length,
          i,
429
          uri,
Romain Courteaud's avatar
Romain Courteaud committed
430
          item,
431
          result = [];
Romain Courteaud's avatar
Romain Courteaud committed
432 433
        for (i = 0; i < count; i += 1) {
          item = data[i];
434
          uri = new URI(item._links.self.href);
435
          delete item._links;
Romain Courteaud's avatar
Romain Courteaud committed
436
          result.push({
437
            id: uri.segment(2),
Romain Courteaud's avatar
Romain Courteaud committed
438 439
            value: item
          });
440
        }
441
        return result;
442
      });
Romain Courteaud's avatar
Romain Courteaud committed
443
  };
Tristan Cavelier's avatar
Tristan Cavelier committed
444

445
  jIO.addStorage("erp5", ERP5Storage);
Tristan Cavelier's avatar
Tristan Cavelier committed
446

Romain Courteaud's avatar
Romain Courteaud committed
447
}(jIO, UriTemplate, FormData, RSVP, URI, Blob));