Commit 7f5f4e44 authored by Tristan Cavelier's avatar Tristan Cavelier

gidstorage added + tests (wip)

parent a68b440b
...@@ -164,6 +164,8 @@ var clearlog = function () { ...@@ -164,6 +164,8 @@ var clearlog = function () {
</script> </script>
<script type="text/javascript" src="../src/jio.storage/splitstorage.js"> <script type="text/javascript" src="../src/jio.storage/splitstorage.js">
</script> </script>
<script type="text/javascript" src="../src/jio.storage/gidstorage.js">
</script>
<script type="text/javascript" src="../lib/sjcl/sjcl.min.js"></script> <script type="text/javascript" src="../lib/sjcl/sjcl.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
<!-- <!--
......
/*
* JIO extension for resource global identifier management.
* Copyright (C) 2013 Nexedi SA
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global jIO: true, localStorage: true, setTimeout: true,
complex_queries: true */
/**
* JIO GID Storage. Type = 'gid'.
* Identifies document with their global identifier représentation
*
* Sub storages must support complex queries.
*
* Storage Description:
*
* {
* "type": "gid",
* "sub_storage": {<storage description>},
* "constraints": {
* "default": {
* "identifier": "list", // ['a', 1]
* "type": "DCMIType", // 'Text'
* "title": "string" // 'something blue'
* },
* "Text": {
* "format": "contentType" // contains 'text/plain;charset=utf-8'
* },
* "Image": {
* "version": "json" // value as is
* }
* }
* }
*/
(function () {
var dcmi_types, metadata_actions, content_type_re
dcmi_types = {
'Collection': 'Collection',
'Dataset': 'Dataset',
'Event': 'Event',
'Image': 'Image',
'InteractiveResource': 'InteractiveResource',
'MovingImage': 'MovingImage',
'PhysicalObject': 'PhysicalObject',
'Service': 'Service',
'Software': 'Software',
'Sound': 'Sound',
'StillImage': 'StillImage',
'Text': 'Text'
};
metadata_actions = {
json: function (value) {
return value;
},
string: function (value) {
if (typeof value === 'string') {
return value;
}
},
list: function (value) {
var i, new_value = [];
if (Array.isArray(value)) {
for (i = 0; i < value.length; i += 1) {
if (typeof value[i] === 'object') {
new_value[new_value.length] = value[i].content;
} else {
new_value[new_value.length] = value[i];
}
}
} else if (value !== undefined) {
value = [value];
}
return value;
},
DCMIType: function (value) {
return dcmi_types[value];
},
contentType: function (value) {
var i;
if (!Array.isArray(value)) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
if (value[i] === 'object') {
if (content_type_re.test(value[i].content)) {
return value[i].content;
}
} else {
if (content_type_re.test(value[i])) {
return value[i];
}
}
}
}
};
content_type_re =
/^([a-z]+\/[a-zA-Z0-9\+\-\.]+)(?:\s*;\s*charset\s*=\s*([a-zA-Z0-9\-]+))?$/;
function Metadata(metadata) {
if (typeof metadata === 'object' && !Array.isArray(metadata)) {
this.metadata = metadata;
} else {
this.metadata = {};
}
Metadata.prototype.update.call(this, metadata);
}
Metadata.prototype.update = function (metadata) {
var k;
for (k in metadata) {
if (metadata.hasOwnProperty(k)) {
if (metadata[k] !== undefined) {
if (k[0] === '_') {
this.metadata[k] = JSON.parse(JSON.stringify(metadata[k]));
} else {
this.metadata[k] = Metadata.normalizeValue(metadata[k]);
}
}
}
}
return this;
};
Metadata.prototype.get = function (key) {
return this.metadata[key];
};
Metadata.prototype.set = function (key, value) {
if (value === undefined) {
delete this.metadata[key];
return this;
}
if (key[0] === '_') {
this.metadata[key] = value;
} else {
this.metadata[key] = Metadata.normalizeValue(value);
}
return this;
};
Metadata.normalizeArray = function (value) {
var i;
if (value.length === 0) {
return;
}
value = value.slice();
i = 0;
while (i < value.length) {
if (typeof value[i] === 'object' && !Array.isArray(value[i])) {
value[i] = Metadata.normalizeObject(value[i]);
if (value[i] === undefined) {
value.splice(i, 1);
} else {
i += 1;
}
} else if ((typeof value[i] === 'string') ||
(isNaN(value[i]) && typeof value[i] === 'number')) {
i += 1;
} else {
value.splice(i, 1);
}
}
if (value.length === 1) {
return value[0];
}
return value;
};
Metadata.normalizeObject = function (value) {
var i, count = 0, new_value = {};
for (i in value) {
if (value.hasOwnProperty(i)) {
if ((typeof value[i] === 'string') ||
(isNaN(value[i]) && typeof value[i] === 'number')) {
new_value[i] = value[i];
count += 1;
}
}
}
if (new_value.content === undefined) {
return;
}
if (count === 1) {
return new_value.content;
}
return new_value;
};
Metadata.normalizeValue = function (value) {
if ((typeof value === 'string') ||
(isNaN(value) && typeof value === 'number')) {
return value;
}
if (Array.isArray(value)) {
return Metadata.normalizeArray(value);
}
if (typeof value === 'object') {
return Metadata.normalizeObject(value);
}
};
function gidFormat(metadata, constraints) {
var types, i, meta_key, result = {}, tmp;
types = ['default', metadata.type];
for (i = 0; i < types.length; i += 1) {
for (meta_key in constraints[types[i]]) {
if (constraints[types[i]].hasOwnProperty(meta_key)) {
tmp = metadata_actions[
constraints[types[i]][meta_key]
](metadata[meta_key]);
if (tmp === undefined) {
return;
}
result[meta_key] = tmp;
}
}
}
return JSON.stringify(result);
}
function gidToComplexQuery(gid, contraints) {
var k, i, result = [], meta, content;
if (typeof gid === 'string') {
gid = JSON.parse(gid);
}
for (k in gid) {
if (gid.hasOwnProperty(k)) {
meta = gid[k];
if (!Array.isArray(meta)) {
meta = [meta];
}
for (i = 0; i < meta.length; i += 1) {
content = meta[i];
if (typeof content === 'object') {
content = content.content;
}
result[result.length] = {
"type": "simple",
"operator": "=",
"key": k,
"value": content
};
}
}
}
return {
"type": "complex",
"operator": "AND",
"query_list": result
};
}
function gidParse(gid, constraints) {
var object;
try {
object = JSON.parse(gid);
} catch (e) {
return;
}
if (gid !== gidFormat(object, constraints)) {
return;
}
return object;
}
function gidStorage(spec, my) {
var that = my.basicStorage(spec, my), priv = {};
priv.sub_storage = spec.sub_storage;
priv.constraints = spec.constraints || {
"default": {
"identifier": "list",
"type": "DCMIType"
}
};
that.specToStore = function () {
return {
"sub_storage": priv.sub_storage,
"constraints": priv.constraints
};
};
that.post = function (command) {
setTimeout(function () {
var gid, complex_query, doc = command.cloneDoc();
gid = gidFormat(doc, priv.constraints);
if (gid === undefined || (doc._id && gid !== doc._id)) {
return that.error({
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": "Cannot post document",
"reason": "metadata should respect constraints"
});
}
complex_query = gidToComplexQuery(gid);
that.addJob('allDocs', priv.sub_storage, {}, {
"query": complex_query,
"wildcard_character": null
}, function (response) {
var doc;
if (response.total_rows !== 0) {
return that.error({
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": "Cannot post document",
"reason": "Document already exist"
});
}
doc = command.cloneDoc();
delete doc._id;
that.addJob('post', priv.sub_storage, doc, {
}, function (response) {
response.id = gid;
that.success(response);
}, function (err) {
err.message = "Cannot post document";
that.error(err);
});
}, function (err) {
err.message = "Cannot post document";
that.error(err);
});
});
};
that.get = function (command) {
setTimeout(function () {
var gid_object, complex_query;
gid_object = gidParse(command.cloneDoc()._id, priv.constraints);
if (gid_object === undefined) {
return that.error({
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": "Cannot post document",
"reason": "metadata should respect constraints"
});
}
complex_query = gidToComplexQuery(gid_object);
that.addJob('allDocs', priv.sub_storage, {}, {
"query": complex_query,
"wildcard_character": null
}, function (response) {
if (response.total_rows === 0) {
return that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot get document",
"reason": "missing"
});
}
// XXX
}, function (err) {
err.message = "Cannot post document";
that.error(err);
});
});
};
return that;
}
jIO.addStorageType('gid', gidStorage);
}());
...@@ -7963,6 +7963,97 @@ test("AllDocs", function () { ...@@ -7963,6 +7963,97 @@ test("AllDocs", function () {
o.jio.stop(); o.jio.stop();
}); });
module("JIO GID Storage");
test("Post", function () {
var o = generateTools(this);
o.localstorage_spec = {
"type": "local",
"username": "one",
"application_name": "gid storage post test"
};
o.local_jio = JIO.newJio(o.localstorage_spec);
o.jio = JIO.newJio({
"type": "gid",
"sub_storage": o.localstorage_spec,
"constraints": {
"default": {
"identifier": "list"
}
}
});
o.local_jio.put({"_id": "blue", "identifier": "a"});
o.local_jio.put({"_id": "green", "identifier": ["ac", "b"]});
o.clock.tick(2000);
o.local_jio.stop();
o.spy(o, 'status', 409, 'Post document without respecting constraints ' +
'-> conflicts');
o.jio.post({}, o.f);
o.tick(o);
o.spy(o, 'status', 409, 'Post existent document -> conflict');
o.jio.post({"identifier": "a"}, o.f);
o.tick(o);
o.spy(o, 'value', {
"id": "{\"identifier\":[\"a%\"]}",
"ok": true
}, 'Post respecting constraints');
o.jio.post({"identifier": "a%"}, o.f);
o.tick(o);
o.spy(o, 'status', 409, 'Post same document respecting constraints ' +
'-> conflicts');
o.jio.post({"identifier": "a%"}, o.f);
o.tick(o);
o.jio.stop();
});
test("Get", function () {
var o = generateTools(this);
o.localstorage_spec = {
"type": "local",
"username": "one",
"application_name": "gid storage get test"
};
o.local_jio = JIO.newJio(o.localstorage_spec);
o.jio = JIO.newJio({
"type": "gid",
"sub_storage": o.localstorage_spec,
"constraints": {
"default": {
"identifier": "list"
}
}
});
o.local_jio.put({"_id": "blue", "identifier": "a"});
o.local_jio.put({"_id": "green", "identifier": ["ac", "b"]});
o.clock.tick(2000);
o.local_jio.stop();
o.spy(o, 'status', 404, 'Get inexistent document');
o.jio.get({"_id": "{\"identifier\":[\"b\"]}"}, o.f);
o.tick(o);
o.spy(o, 'value', {"_id": "blue", "identifier": "a"}, 'Get document');
o.jio.get({"_id": "{\"identifier\":[\"a\"]}"}, o.f);
o.tick(o);
o.jio.stop();
});
}; // end thisfun }; // end thisfun
if (window.requirejs) { if (window.requirejs) {
......
...@@ -40,6 +40,8 @@ ...@@ -40,6 +40,8 @@
</script> </script>
<script type="text/javascript" src="../src/jio.storage/splitstorage.js"> <script type="text/javascript" src="../src/jio.storage/splitstorage.js">
</script> </script>
<script type="text/javascript" src="../src/jio.storage/gidstorage.js">
</script>
<script type="text/javascript" src="./jiotests.js"></script> <script type="text/javascript" src="./jiotests.js"></script>
</body> </body>
</html> </html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment