Commit 33814b87 authored by Tristan Cavelier's avatar Tristan Cavelier

built complex_queries.js and jio.js added

parent a50c31db
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/**
* Provides some function to use complex queries with item list
*
* @module complex_queries
*/
// define([module_name], [dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(exports);
}
window.complex_queries = {};
module(window.complex_queries);
}(['exports'], function (to_export) {
"use strict";
/**
* Add a secured (write permission denied) property to an object.
*
* @param {Object} object The object to fill
* @param {String} key The object key where to store the property
* @param {Any} value The value to store
*/
function _export(key, value) {
Object.defineProperty(to_export, key, {
"configurable": false,
"enumerable": true,
"writable": false,
"value": value
});
}
/**
* Parse a text request to a json query object tree
*
* @param {String} string The string to parse
* @return {Object} The json query tree
*/
function parseStringToObject(string) {
/*
Default template driver for JS/CC generated parsers running as
browser-based JavaScript/ECMAScript applications.
WARNING: This parser template will not run as console and has lesser
features for debugging than the console derivates for the
various JavaScript platforms.
Features:
- Parser trace messages
- Integrated panic-mode error recovery
Written 2007, 2008 by Jan Max Meyer, J.M.K S.F. Software Technologies
This is in the public domain.
*/
var NODEJS__dbg_withtrace = false;
var NODEJS__dbg_string = new String();
function __NODEJS_dbg_print( text )
{
NODEJS__dbg_string += text + "\n";
}
function __NODEJS_lex( info )
{
var state = 0;
var match = -1;
var match_pos = 0;
var start = 0;
var pos = info.offset + 1;
do
{
pos--;
state = 0;
match = -2;
start = pos;
if( info.src.length <= start )
return 19;
do
{
switch( state )
{
case 0:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 8 ) || ( info.src.charCodeAt( pos ) >= 10 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || info.src.charCodeAt( pos ) == 59 || ( info.src.charCodeAt( pos ) >= 63 && info.src.charCodeAt( pos ) <= 64 ) || ( info.src.charCodeAt( pos ) >= 66 && info.src.charCodeAt( pos ) <= 77 ) || ( info.src.charCodeAt( pos ) >= 80 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 9 ) state = 2;
else if( info.src.charCodeAt( pos ) == 40 ) state = 3;
else if( info.src.charCodeAt( pos ) == 41 ) state = 4;
else if( info.src.charCodeAt( pos ) == 60 || info.src.charCodeAt( pos ) == 62 ) state = 5;
else if( info.src.charCodeAt( pos ) == 34 ) state = 11;
else if( info.src.charCodeAt( pos ) == 79 ) state = 12;
else if( info.src.charCodeAt( pos ) == 32 ) state = 13;
else if( info.src.charCodeAt( pos ) == 61 ) state = 14;
else if( info.src.charCodeAt( pos ) == 65 ) state = 18;
else if( info.src.charCodeAt( pos ) == 78 ) state = 19;
else state = -1;
break;
case 1:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else state = -1;
match = 10;
match_pos = pos;
break;
case 2:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else state = -1;
match = 1;
match_pos = pos;
break;
case 3:
state = -1;
match = 3;
match_pos = pos;
break;
case 4:
state = -1;
match = 4;
match_pos = pos;
break;
case 5:
if( info.src.charCodeAt( pos ) == 61 ) state = 14;
else state = -1;
match = 11;
match_pos = pos;
break;
case 6:
state = -1;
match = 8;
match_pos = pos;
break;
case 7:
state = -1;
match = 9;
match_pos = pos;
break;
case 8:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else state = -1;
match = 6;
match_pos = pos;
break;
case 9:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else state = -1;
match = 5;
match_pos = pos;
break;
case 10:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else state = -1;
match = 7;
match_pos = pos;
break;
case 11:
if( info.src.charCodeAt( pos ) == 34 ) state = 7;
else if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 33 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) ) state = 11;
else if( info.src.charCodeAt( pos ) == 92 ) state = 15;
else state = -1;
break;
case 12:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 81 ) || ( info.src.charCodeAt( pos ) >= 83 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else if( info.src.charCodeAt( pos ) == 82 ) state = 8;
else state = -1;
match = 10;
match_pos = pos;
break;
case 13:
state = -1;
match = 1;
match_pos = pos;
break;
case 14:
state = -1;
match = 11;
match_pos = pos;
break;
case 15:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 254 ) ) state = 11;
else state = -1;
break;
case 16:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 67 ) || ( info.src.charCodeAt( pos ) >= 69 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else if( info.src.charCodeAt( pos ) == 68 ) state = 9;
else state = -1;
match = 10;
match_pos = pos;
break;
case 17:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 83 ) || ( info.src.charCodeAt( pos ) >= 85 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else if( info.src.charCodeAt( pos ) == 84 ) state = 10;
else state = -1;
match = 10;
match_pos = pos;
break;
case 18:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 77 ) || ( info.src.charCodeAt( pos ) >= 79 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else if( info.src.charCodeAt( pos ) == 78 ) state = 16;
else state = -1;
match = 10;
match_pos = pos;
break;
case 19:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 78 ) || ( info.src.charCodeAt( pos ) >= 80 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else if( info.src.charCodeAt( pos ) == 79 ) state = 17;
else state = -1;
match = 10;
match_pos = pos;
break;
}
pos++;
}
while( state > -1 );
}
while( 1 > -1 && match == 1 );
if( match > -1 )
{
info.att = info.src.substr( start, match_pos - start );
info.offset = match_pos;
}
else
{
info.att = new String();
match = -1;
}
return match;
}
function __NODEJS_parse( src, err_off, err_la )
{
var sstack = new Array();
var vstack = new Array();
var err_cnt = 0;
var act;
var go;
var la;
var rval;
var parseinfo = new Function( "", "var offset; var src; var att;" );
var info = new parseinfo();
/* Pop-Table */
var pop_tab = new Array(
new Array( 0/* begin' */, 1 ),
new Array( 13/* begin */, 1 ),
new Array( 12/* search_text */, 1 ),
new Array( 12/* search_text */, 2 ),
new Array( 12/* search_text */, 3 ),
new Array( 14/* and_expression */, 1 ),
new Array( 14/* and_expression */, 3 ),
new Array( 15/* boolean_expression */, 2 ),
new Array( 15/* boolean_expression */, 1 ),
new Array( 16/* expression */, 3 ),
new Array( 16/* expression */, 2 ),
new Array( 16/* expression */, 1 ),
new Array( 17/* value */, 2 ),
new Array( 17/* value */, 1 ),
new Array( 18/* string */, 1 ),
new Array( 18/* string */, 1 )
);
/* Action-Table */
var act_tab = new Array(
/* State 0 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 1 */ new Array( 19/* "$" */,0 ),
/* State 2 */ new Array( 19/* "$" */,-1 ),
/* State 3 */ new Array( 6/* "OR" */,14 , 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 , 19/* "$" */,-2 , 4/* "RIGHT_PARENTHESE" */,-2 ),
/* State 4 */ new Array( 5/* "AND" */,16 , 19/* "$" */,-5 , 7/* "NOT" */,-5 , 3/* "LEFT_PARENTHESE" */,-5 , 8/* "COLUMN" */,-5 , 11/* "OPERATOR" */,-5 , 10/* "WORD" */,-5 , 9/* "STRING" */,-5 , 6/* "OR" */,-5 , 4/* "RIGHT_PARENTHESE" */,-5 ),
/* State 5 */ new Array( 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 6 */ new Array( 19/* "$" */,-8 , 7/* "NOT" */,-8 , 3/* "LEFT_PARENTHESE" */,-8 , 8/* "COLUMN" */,-8 , 11/* "OPERATOR" */,-8 , 10/* "WORD" */,-8 , 9/* "STRING" */,-8 , 6/* "OR" */,-8 , 5/* "AND" */,-8 , 4/* "RIGHT_PARENTHESE" */,-8 ),
/* State 7 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 8 */ new Array( 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 9 */ new Array( 19/* "$" */,-11 , 7/* "NOT" */,-11 , 3/* "LEFT_PARENTHESE" */,-11 , 8/* "COLUMN" */,-11 , 11/* "OPERATOR" */,-11 , 10/* "WORD" */,-11 , 9/* "STRING" */,-11 , 6/* "OR" */,-11 , 5/* "AND" */,-11 , 4/* "RIGHT_PARENTHESE" */,-11 ),
/* State 10 */ new Array( 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 11 */ new Array( 19/* "$" */,-13 , 7/* "NOT" */,-13 , 3/* "LEFT_PARENTHESE" */,-13 , 8/* "COLUMN" */,-13 , 11/* "OPERATOR" */,-13 , 10/* "WORD" */,-13 , 9/* "STRING" */,-13 , 6/* "OR" */,-13 , 5/* "AND" */,-13 , 4/* "RIGHT_PARENTHESE" */,-13 ),
/* State 12 */ new Array( 19/* "$" */,-14 , 7/* "NOT" */,-14 , 3/* "LEFT_PARENTHESE" */,-14 , 8/* "COLUMN" */,-14 , 11/* "OPERATOR" */,-14 , 10/* "WORD" */,-14 , 9/* "STRING" */,-14 , 6/* "OR" */,-14 , 5/* "AND" */,-14 , 4/* "RIGHT_PARENTHESE" */,-14 ),
/* State 13 */ new Array( 19/* "$" */,-15 , 7/* "NOT" */,-15 , 3/* "LEFT_PARENTHESE" */,-15 , 8/* "COLUMN" */,-15 , 11/* "OPERATOR" */,-15 , 10/* "WORD" */,-15 , 9/* "STRING" */,-15 , 6/* "OR" */,-15 , 5/* "AND" */,-15 , 4/* "RIGHT_PARENTHESE" */,-15 ),
/* State 14 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 15 */ new Array( 19/* "$" */,-3 , 4/* "RIGHT_PARENTHESE" */,-3 ),
/* State 16 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 17 */ new Array( 19/* "$" */,-7 , 7/* "NOT" */,-7 , 3/* "LEFT_PARENTHESE" */,-7 , 8/* "COLUMN" */,-7 , 11/* "OPERATOR" */,-7 , 10/* "WORD" */,-7 , 9/* "STRING" */,-7 , 6/* "OR" */,-7 , 5/* "AND" */,-7 , 4/* "RIGHT_PARENTHESE" */,-7 ),
/* State 18 */ new Array( 4/* "RIGHT_PARENTHESE" */,23 ),
/* State 19 */ new Array( 19/* "$" */,-10 , 7/* "NOT" */,-10 , 3/* "LEFT_PARENTHESE" */,-10 , 8/* "COLUMN" */,-10 , 11/* "OPERATOR" */,-10 , 10/* "WORD" */,-10 , 9/* "STRING" */,-10 , 6/* "OR" */,-10 , 5/* "AND" */,-10 , 4/* "RIGHT_PARENTHESE" */,-10 ),
/* State 20 */ new Array( 19/* "$" */,-12 , 7/* "NOT" */,-12 , 3/* "LEFT_PARENTHESE" */,-12 , 8/* "COLUMN" */,-12 , 11/* "OPERATOR" */,-12 , 10/* "WORD" */,-12 , 9/* "STRING" */,-12 , 6/* "OR" */,-12 , 5/* "AND" */,-12 , 4/* "RIGHT_PARENTHESE" */,-12 ),
/* State 21 */ new Array( 19/* "$" */,-4 , 4/* "RIGHT_PARENTHESE" */,-4 ),
/* State 22 */ new Array( 19/* "$" */,-6 , 7/* "NOT" */,-6 , 3/* "LEFT_PARENTHESE" */,-6 , 8/* "COLUMN" */,-6 , 11/* "OPERATOR" */,-6 , 10/* "WORD" */,-6 , 9/* "STRING" */,-6 , 6/* "OR" */,-6 , 4/* "RIGHT_PARENTHESE" */,-6 ),
/* State 23 */ new Array( 19/* "$" */,-9 , 7/* "NOT" */,-9 , 3/* "LEFT_PARENTHESE" */,-9 , 8/* "COLUMN" */,-9 , 11/* "OPERATOR" */,-9 , 10/* "WORD" */,-9 , 9/* "STRING" */,-9 , 6/* "OR" */,-9 , 5/* "AND" */,-9 , 4/* "RIGHT_PARENTHESE" */,-9 )
);
/* Goto-Table */
var goto_tab = new Array(
/* State 0 */ new Array( 13/* begin */,1 , 12/* search_text */,2 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
/* State 1 */ new Array( ),
/* State 2 */ new Array( ),
/* State 3 */ new Array( 12/* search_text */,15 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
/* State 4 */ new Array( ),
/* State 5 */ new Array( 16/* expression */,17 , 17/* value */,9 , 18/* string */,11 ),
/* State 6 */ new Array( ),
/* State 7 */ new Array( 12/* search_text */,18 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
/* State 8 */ new Array( 16/* expression */,19 , 17/* value */,9 , 18/* string */,11 ),
/* State 9 */ new Array( ),
/* State 10 */ new Array( 18/* string */,20 ),
/* State 11 */ new Array( ),
/* State 12 */ new Array( ),
/* State 13 */ new Array( ),
/* State 14 */ new Array( 12/* search_text */,21 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
/* State 15 */ new Array( ),
/* State 16 */ new Array( 14/* and_expression */,22 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
/* State 17 */ new Array( ),
/* State 18 */ new Array( ),
/* State 19 */ new Array( ),
/* State 20 */ new Array( ),
/* State 21 */ new Array( ),
/* State 22 */ new Array( ),
/* State 23 */ new Array( )
);
/* Symbol labels */
var labels = new Array(
"begin'" /* Non-terminal symbol */,
"WHITESPACE" /* Terminal symbol */,
"WHITESPACE" /* Terminal symbol */,
"LEFT_PARENTHESE" /* Terminal symbol */,
"RIGHT_PARENTHESE" /* Terminal symbol */,
"AND" /* Terminal symbol */,
"OR" /* Terminal symbol */,
"NOT" /* Terminal symbol */,
"COLUMN" /* Terminal symbol */,
"STRING" /* Terminal symbol */,
"WORD" /* Terminal symbol */,
"OPERATOR" /* Terminal symbol */,
"search_text" /* Non-terminal symbol */,
"begin" /* Non-terminal symbol */,
"and_expression" /* Non-terminal symbol */,
"boolean_expression" /* Non-terminal symbol */,
"expression" /* Non-terminal symbol */,
"value" /* Non-terminal symbol */,
"string" /* Non-terminal symbol */,
"$" /* Terminal symbol */
);
info.offset = 0;
info.src = src;
info.att = new String();
if( !err_off )
err_off = new Array();
if( !err_la )
err_la = new Array();
sstack.push( 0 );
vstack.push( 0 );
la = __NODEJS_lex( info );
while( true )
{
act = 25;
for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
{
if( act_tab[sstack[sstack.length-1]][i] == la )
{
act = act_tab[sstack[sstack.length-1]][i+1];
break;
}
}
if( NODEJS__dbg_withtrace && sstack.length > 0 )
{
__NODEJS_dbg_print( "\nState " + sstack[sstack.length-1] + "\n" +
"\tLookahead: " + labels[la] + " (\"" + info.att + "\")\n" +
"\tAction: " + act + "\n" +
"\tSource: \"" + info.src.substr( info.offset, 30 ) + ( ( info.offset + 30 < info.src.length ) ?
"..." : "" ) + "\"\n" +
"\tStack: " + sstack.join() + "\n" +
"\tValue stack: " + vstack.join() + "\n" );
}
//Panic-mode: Try recovery when parse-error occurs!
if( act == 25 )
{
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "Error detected: There is no reduce or shift on the symbol " + labels[la] );
err_cnt++;
err_off.push( info.offset - info.att.length );
err_la.push( new Array() );
for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
err_la[err_la.length-1].push( labels[act_tab[sstack[sstack.length-1]][i]] );
//Remember the original stack!
var rsstack = new Array();
var rvstack = new Array();
for( var i = 0; i < sstack.length; i++ )
{
rsstack[i] = sstack[i];
rvstack[i] = vstack[i];
}
while( act == 25 && la != 19 )
{
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tError recovery\n" +
"Current lookahead: " + labels[la] + " (" + info.att + ")\n" +
"Action: " + act + "\n\n" );
if( la == -1 )
info.offset++;
while( act == 25 && sstack.length > 0 )
{
sstack.pop();
vstack.pop();
if( sstack.length == 0 )
break;
act = 25;
for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
{
if( act_tab[sstack[sstack.length-1]][i] == la )
{
act = act_tab[sstack[sstack.length-1]][i+1];
break;
}
}
}
if( act != 25 )
break;
for( var i = 0; i < rsstack.length; i++ )
{
sstack.push( rsstack[i] );
vstack.push( rvstack[i] );
}
la = __NODEJS_lex( info );
}
if( act == 25 )
{
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tError recovery failed, terminating parse process..." );
break;
}
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tError recovery succeeded, continuing" );
}
/*
if( act == 25 )
break;
*/
//Shift
if( act > 0 )
{
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "Shifting symbol: " + labels[la] + " (" + info.att + ")" );
sstack.push( act );
vstack.push( info.att );
la = __NODEJS_lex( info );
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tNew lookahead symbol: " + labels[la] + " (" + info.att + ")" );
}
//Reduce
else
{
act *= -1;
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "Reducing by producution: " + act );
rval = void(0);
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tPerforming semantic action..." );
switch( act )
{
case 0:
{
rval = vstack[ vstack.length - 1 ];
}
break;
case 1:
{
result = vstack[ vstack.length - 1 ];
}
break;
case 2:
{
rval = vstack[ vstack.length - 1 ];
}
break;
case 3:
{
rval = mkComplexQuery('OR',[vstack[ vstack.length - 2 ],vstack[ vstack.length - 1 ]]);
}
break;
case 4:
{
rval = mkComplexQuery('OR',[vstack[ vstack.length - 3 ],vstack[ vstack.length - 1 ]]);
}
break;
case 5:
{
rval = vstack[ vstack.length - 1 ] ;
}
break;
case 6:
{
rval = mkComplexQuery('AND',[vstack[ vstack.length - 3 ],vstack[ vstack.length - 1 ]]);
}
break;
case 7:
{
rval = mkNotQuery(vstack[ vstack.length - 1 ]);
}
break;
case 8:
{
rval = vstack[ vstack.length - 1 ];
}
break;
case 9:
{
rval = vstack[ vstack.length - 2 ];
}
break;
case 10:
{
simpleQuerySetKey(vstack[ vstack.length - 1 ],vstack[ vstack.length - 2 ].split(':').slice(0,-1).join(':')); rval = vstack[ vstack.length - 1 ];
}
break;
case 11:
{
rval = vstack[ vstack.length - 1 ];
}
break;
case 12:
{
vstack[ vstack.length - 1 ].operator = vstack[ vstack.length - 2 ] ; rval = vstack[ vstack.length - 1 ];
}
break;
case 13:
{
rval = vstack[ vstack.length - 1 ];
}
break;
case 14:
{
rval = mkSimpleQuery('',vstack[ vstack.length - 1 ]);
}
break;
case 15:
{
rval = mkSimpleQuery('',vstack[ vstack.length - 1 ].split('"').slice(1,-1).join('"'));
}
break;
}
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tPopping " + pop_tab[act][1] + " off the stack..." );
for( var i = 0; i < pop_tab[act][1]; i++ )
{
sstack.pop();
vstack.pop();
}
go = -1;
for( var i = 0; i < goto_tab[sstack[sstack.length-1]].length; i+=2 )
{
if( goto_tab[sstack[sstack.length-1]][i] == pop_tab[act][0] )
{
go = goto_tab[sstack[sstack.length-1]][i+1];
break;
}
}
if( act == 0 )
break;
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tPushing non-terminal " + labels[ pop_tab[act][0] ] );
sstack.push( go );
vstack.push( rval );
}
if( NODEJS__dbg_withtrace )
{
alert( NODEJS__dbg_string );
NODEJS__dbg_string = new String();
}
}
if( NODEJS__dbg_withtrace )
{
__NODEJS_dbg_print( "\nParse complete." );
alert( NODEJS__dbg_string );
}
return err_cnt;
}
var arrayExtend = function () {
var j, i, newlist = [], list_list = arguments;
for (j = 0; j < list_list.length; j += 1) {
for (i = 0; i < list_list[j].length; i += 1) {
newlist.push(list_list[j][i]);
}
}
return newlist;
}, mkSimpleQuery = function (key, value, operator) {
return {"type": "simple", "operator": "=", "key": key, "value": value};
}, mkNotQuery = function (query) {
if (query.operator === "NOT") {
return query.query_list[0];
}
return {"type": "complex", "operator": "NOT", "query_list": [query]};
}, mkComplexQuery = function (operator, query_list) {
var i, query_list2 = [];
for (i = 0; i < query_list.length; i += 1) {
if (query_list[i].operator === operator) {
query_list2 = arrayExtend(query_list2, query_list[i].query_list);
} else {
query_list2.push(query_list[i]);
}
}
return {type:"complex",operator:operator,query_list:query_list2};
}, simpleQuerySetKey = function (query, key) {
var i;
if (query.type === "complex") {
for (i = 0; i < query.query_list.length; ++i) {
simpleQuerySetKey (query.query_list[i],key);
}
return true;
}
if (query.type === "simple" && !query.key) {
query.key = key;
return true;
}
return false;
},
error_offsets = [],
error_lookaheads = [],
error_count = 0,
result;
if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) {
var i;
for (i = 0; i < error_count; i += 1) {
throw new Error("Parse error near \"" +
string.substr(error_offsets[i]) +
"\", expecting \"" +
error_lookaheads[i].join() + "\"");
}
}
return result;
} // parseStringToObject
_export('parseStringToObject', parseStringToObject);
/*jslint indent: 2, maxlen: 80, sloppy: true */
var query_class_dict = {};
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query: true, query_class_dict: true, inherits: true,
_export: true, QueryFactory: true */
/**
* The ComplexQuery inherits from Query, and compares one or several metadata
* values.
*
* @class ComplexQuery
* @extends Query
* @param {Object} [spec={}] The specifications
* @param {String} [spec.operator="AND"] The compare method to use
* @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare
*/
function ComplexQuery(spec) {
Query.call(this);
/**
* Logical operator to use to compare object values
*
* @attribute operator
* @type String
* @default "AND"
* @optional
*/
this.operator = spec.operator || "AND";
/**
* The sub Query list which are used to query an item.
*
* @attribute query_list
* @type Array
* @default []
* @optional
*/
this.query_list = spec.query_list || [];
this.query_list = this.query_list.map(QueryFactory.create);
}
inherits(ComplexQuery, Query);
/**
* #crossLink "Query/match:method"
*/
ComplexQuery.prototype.match = function (item, wildcard_character) {
return this[this.operator](item, wildcard_character);
};
/**
* #crossLink "Query/toString:method"
*/
ComplexQuery.prototype.toString = function () {
var str_list = ["("], this_operator = this.operator;
this.query_list.forEach(function (query) {
str_list.push(query.toString());
str_list.push(this_operator);
});
str_list.pop(); // remove last operator
str_list.push(")");
return str_list.join(" ");
};
/**
* #crossLink "Query/serialized:method"
*/
ComplexQuery.prototype.serialized = function () {
var s = {
"type": "complex",
"operator": this.operator,
"query_list": []
};
this.query_list.forEach(function (query) {
s.query_list.push(query.serialized());
});
return s;
};
/**
* Comparison operator, test if all sub queries match the
* item value
*
* @method AND
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if all match, false otherwise
*/
ComplexQuery.prototype.AND = function (item, wildcard_character) {
var i;
for (i = 0; i < this.query_list.length; i += 1) {
if (!this.query_list[i].match(item, wildcard_character)) {
return false;
}
}
return true;
};
/**
* Comparison operator, test if one of the sub queries matches the
* item value
*
* @method OR
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if one match, false otherwise
*/
ComplexQuery.prototype.OR = function (item, wildcard_character) {
var i;
for (i = 0; i < this.query_list.length; i += 1) {
if (this.query_list[i].match(item, wildcard_character)) {
return true;
}
}
return false;
};
/**
* Comparison operator, test if the sub query does not match the
* item value
*
* @method NOT
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if one match, false otherwise
*/
ComplexQuery.prototype.NOT = function (item, wildcard_character) {
return !this.query_list[0].match(item, wildcard_character);
};
query_class_dict.complex = ComplexQuery;
_export("ComplexQuery", ComplexQuery);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global parseStringToObject: true, emptyFunction: true, sortOn: true, limit:
true, select: true, _export: true, stringEscapeRegexpCharacters: true,
deepClone: true */
/**
* The query to use to filter a list of objects.
* This is an abstract class.
*
* @class Query
* @constructor
*/
function Query() {
/**
* Called before parsing the query. Must be overridden!
*
* @method onParseStart
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseStart = emptyFunction;
/**
* Called when parsing a simple query. Must be overridden!
*
* @method onParseSimpleQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseSimpleQuery = emptyFunction;
/**
* Called when parsing a complex query. Must be overridden!
*
* @method onParseComplexQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseComplexQuery = emptyFunction;
/**
* Called after parsing the query. Must be overridden!
*
* @method onParseEnd
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseEnd = emptyFunction;
}
/**
* Filter the item list with matching item only
*
* @method exec
* @param {Array} item_list The list of object
* @param {Object} [option] Some operation option
* @param {String} [option.wildcard_character="%"] The wildcard character
* @param {Array} [option.select_list] A object keys to retrieve
* @param {Array} [option.sort_on] Couples of object keys and "ascending"
* or "descending"
* @param {Array} [option.limit] Couple of integer, first is an index and
* second is the length.
*/
Query.prototype.exec = function (item_list, option) {
var i = 0;
if (!Array.isArray(item_list)) {
throw new TypeError("Query().exec(): Argument 1 is not of type 'array'");
}
if (option === undefined) {
option = {};
}
if (typeof option !== 'object') {
throw new TypeError("Query().exec(): " +
"Optional argument 2 is not of type 'object'");
}
if (option.wildcard_character === undefined) {
option.wildcard_character = '%';
}
while (i < item_list.length) {
if (!this.match(item_list[i], option.wildcard_character)) {
item_list.splice(i, 1);
} else {
i += 1;
}
}
if (option.sort_on) {
sortOn(option.sort_on, item_list);
}
if (option.limit) {
limit(option.limit, item_list);
}
select(option.select_list || [], item_list);
};
/**
* Test if an item matches this query
*
* @method match
* @param {Object} item The object to test
* @param {String} wildcard_character The wildcard character to use
* @return {Boolean} true if match, false otherwise
*/
Query.prototype.match = function () {
return true;
};
/**
* Browse the Query in deep calling parser method in each step.
*
* `onParseStart` is called first, on end `onParseEnd` is called.
* It starts from the simple queries at the bottom of the tree calling the
* parser method `onParseSimpleQuery`, and go up calling the
* `onParseComplexQuery` method.
*
* @method parse
* @param {Object} option Any options you want (except 'parsed')
* @return {Any} The parse result
*/
Query.prototype.parse = function (option) {
var that = this, object;
/**
* The recursive parser.
*
* @param {Object} object The object shared in the parse process
* @param {Object} options Some options usable in the parseMethods
* @return {Any} The parser result
*/
function recParse(object, option) {
var i, query = object.parsed;
if (query.type === "complex") {
for (i = 0; i < query.query_list.length; i += 1) {
object.parsed = query.query_list[i];
recParse(object, option);
query.query_list[i] = object.parsed;
}
object.parsed = query;
that.onParseComplexQuery(object, option);
} else if (query.type === "simple") {
that.onParseSimpleQuery(object, option);
}
}
object = {"parsed": JSON.parse(JSON.stringify(that.serialized()))};
that.onParseStart(object, option);
recParse(object, option);
that.onParseEnd(object, option);
return object.parsed;
};
/**
* Convert this query to a parsable string.
*
* @method toString
* @return {String} The string version of this query
*/
Query.prototype.toString = function () {
return "";
};
/**
* Convert this query to an jsonable object in order to be remake thanks to
* QueryFactory class.
*
* @method serialized
* @return {Object} The jsonable object
*/
Query.prototype.serialized = function () {
return undefined;
};
_export("Query", Query);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global _export, ComplexQuery, SimpleQuery, Query, parseStringToObject,
query_class_dict */
/**
* Provides static methods to create Query object
*
* @class QueryFactory
*/
function QueryFactory() {
return;
}
/**
* Creates Query object from a search text string or a serialized version
* of a Query.
*
* @method create
* @static
* @param {Object,String} object The search text or the serialized version
* of a Query
* @return {Query} A Query object
*/
QueryFactory.create = function (object) {
if (object === "") {
return new Query();
}
if (typeof object === "string") {
object = parseStringToObject(object);
}
if (typeof (object || {}).type === "string" &&
query_class_dict[object.type]) {
return new query_class_dict[object.type](object);
}
throw new TypeError("QueryFactory.create(): " +
"Argument 1 is not a search text or a parsable object");
};
_export("QueryFactory", QueryFactory);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global _export: true, to_export: true */
function objectToSearchText(query) {
var str_list = [];
if (query.type === "complex") {
str_list.push("(");
(query.query_list || []).forEach(function (sub_query) {
str_list.push(objectToSearchText(sub_query));
str_list.push(query.operator);
});
str_list.length -= 1;
str_list.push(")");
return str_list.join(" ");
}
if (query.type === "simple") {
return query.id + (query.id ? ": " : "") + (query.operator || "=") + ' "' +
query.value + '"';
}
throw new TypeError("This object is not a query");
}
_export("objectToSearchText", objectToSearchText);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query: true, inherits: true, query_class_dict: true, _export: true,
convertStringToRegExp: true */
/**
* The SimpleQuery inherits from Query, and compares one metadata value
*
* @class SimpleQuery
* @extends Query
* @param {Object} [spec={}] The specifications
* @param {String} [spec.operator="="] The compare method to use
* @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare
*/
function SimpleQuery(spec) {
Query.call(this);
/**
* Operator to use to compare object values
*
* @attribute operator
* @type String
* @default "="
* @optional
*/
this.operator = spec.operator || "=";
/**
* Key of the object which refers to the value to compare
*
* @attribute key
* @type String
*/
this.key = spec.key;
/**
* Value is used to do the comparison with the object value
*
* @attribute value
* @type String
*/
this.value = spec.value;
}
inherits(SimpleQuery, Query);
/**
* #crossLink "Query/match:method"
*/
SimpleQuery.prototype.match = function (item, wildcard_character) {
return this[this.operator](item[this.key], this.value, wildcard_character);
};
/**
* #crossLink "Query/toString:method"
*/
SimpleQuery.prototype.toString = function () {
return (this.key ? this.key + ": " : "") + (this.operator || "=") + ' "' +
this.value + '"';
};
/**
* #crossLink "Query/serialized:method"
*/
SimpleQuery.prototype.serialized = function () {
return {
"type": "simple",
"operator": this.operator,
"key": this.key,
"value": this.value
};
};
/**
* Comparison operator, test if this query value matches the item value
*
* @method =
* @param {String} object_value The value to compare
* @param {String} comparison_value The comparison value
* @param {String} wildcard_character The wildcard_character
* @return {Boolean} true if match, false otherwise
*/
SimpleQuery.prototype["="] = function (object_value, comparison_value,
wildcard_character) {
var value, i;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
for (i = 0; i < object_value.length; i += 1) {
value = object_value[i];
if (typeof value === 'object') {
value = value.content;
}
if (comparison_value === undefined) {
if (value === undefined) {
return true;
}
return false;
}
if (value === undefined) {
return false;
}
if (
convertStringToRegExp(
comparison_value.toString(),
wildcard_character
).test(value.toString())
) {
return true;
}
}
return false;
};
/**
* Comparison operator, test if this query value does not match the item value
*
* @method !=
* @param {String} object_value The value to compare
* @param {String} comparison_value The comparison value
* @param {String} wildcard_character The wildcard_character
* @return {Boolean} true if not match, false otherwise
*/
SimpleQuery.prototype["!="] = function (object_value, comparison_value,
wildcard_character) {
var value, i;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
for (i = 0; i < object_value.length; i += 1) {
value = object_value[i];
if (typeof value === 'object') {
value = value.content;
}
if (comparison_value === undefined) {
if (value === undefined) {
return false;
}
return true;
}
if (value === undefined) {
return true;
}
if (
convertStringToRegExp(
comparison_value.toString(),
wildcard_character
).test(value.toString())
) {
return false;
}
}
return true;
};
/**
* Comparison operator, test if this query value is lower than the item value
*
* @method <
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if lower, false otherwise
*/
SimpleQuery.prototype["<"] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object') {
value = value.content;
}
return value < comparison_value;
};
/**
* Comparison operator, test if this query value is equal or lower than the
* item value
*
* @method <=
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if equal or lower, false otherwise
*/
SimpleQuery.prototype["<="] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object') {
value = value.content;
}
return value <= comparison_value;
};
/**
* Comparison operator, test if this query value is greater than the item
* value
*
* @method >
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if greater, false otherwise
*/
SimpleQuery.prototype[">"] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object') {
value = value.content;
}
return value > comparison_value;
};
/**
* Comparison operator, test if this query value is equal or greater than the
* item value
*
* @method >=
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if equal or greater, false otherwise
*/
SimpleQuery.prototype[">="] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object') {
value = value.content;
}
return value >= comparison_value;
};
query_class_dict.simple = SimpleQuery;
_export("SimpleQuery", SimpleQuery);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global _export: true */
/**
* Escapes regexp special chars from a string.
*
* @param {String} string The string to escape
* @return {String} The escaped string
*/
function stringEscapeRegexpCharacters(string) {
if (typeof string === "string") {
return string.replace(/([\\\.\$\[\]\(\)\{\}\^\?\*\+\-])/g, "\\$1");
}
throw new TypeError("complex_queries.stringEscapeRegexpCharacters(): " +
"Argument no 1 is not of type 'string'");
}
_export("stringEscapeRegexpCharacters", stringEscapeRegexpCharacters);
/**
* Convert metadata values to array of strings. ex:
*
* "a" -> ["a"],
* {"content": "a"} -> ["a"]
*
* @param {Any} value The metadata value
* @return {Array} The value in string array format
*/
function metadataValueToStringArray(value) {
var i, new_value = [];
if (value === undefined) {
return undefined;
}
if (!Array.isArray(value)) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
if (typeof value[i] === 'object') {
new_value[i] = value[i].content;
} else {
new_value[i] = value[i];
}
}
return new_value;
}
/**
* A sort function to sort items by key
*
* @param {String} key The key to sort on
* @param {String} [way="ascending"] 'ascending' or 'descending'
* @return {Function} The sort function
*/
function sortFunction(key, way) {
if (way === 'descending') {
return function (a, b) {
// this comparison is 5 times faster than json comparison
var i, l;
a = metadataValueToStringArray(a[key]) || [];
b = metadataValueToStringArray(b[key]) || [];
l = a.length > b.length ? a.length : b.length;
for (i = 0; i < l; i += 1) {
if (a[i] === undefined) {
return 1;
}
if (b[i] === undefined) {
return -1;
}
if (a[i] > b[i]) {
return -1;
}
if (a[i] < b[i]) {
return 1;
}
}
return 0;
};
}
if (way === 'ascending') {
return function (a, b) {
// this comparison is 5 times faster than json comparison
var i, l;
a = metadataValueToStringArray(a[key]) || [];
b = metadataValueToStringArray(b[key]) || [];
l = a.length > b.length ? a.length : b.length;
for (i = 0; i < l; i += 1) {
if (a[i] === undefined) {
return -1;
}
if (b[i] === undefined) {
return 1;
}
if (a[i] > b[i]) {
return 1;
}
if (a[i] < b[i]) {
return -1;
}
}
return 0;
};
}
throw new TypeError("complex_queries.sortFunction(): " +
"Argument 2 must be 'ascending' or 'descending'");
}
/**
* Clones all native object in deep. Managed types: Object, Array, String,
* Number, Boolean, null.
*
* @param {A} object The object to clone
* @return {A} The cloned object
*/
function deepClone(object) {
var i, cloned;
if (Array.isArray(object)) {
cloned = [];
for (i = 0; i < object.length; i += 1) {
cloned[i] = deepClone(object[i]);
}
return cloned;
}
if (typeof object === "object") {
cloned = {};
for (i in object) {
if (object.hasOwnProperty(i)) {
cloned[i] = deepClone(object[i]);
}
}
return cloned;
}
return object;
}
/**
* Inherits the prototype methods from one constructor into another. The
* prototype of `constructor` will be set to a new object created from
* `superConstructor`.
*
* @param {Function} constructor The constructor which inherits the super one
* @param {Function} superConstructor The super constructor
*/
function inherits(constructor, superConstructor) {
constructor.super_ = superConstructor;
constructor.prototype = Object.create(superConstructor.prototype, {
"constructor": {
"configurable": true,
"enumerable": false,
"writable": true,
"value": constructor
}
});
}
/**
* Does nothing
*/
function emptyFunction() {
return;
}
/**
* Filter a list of items, modifying them to select only wanted keys. If
* `clone` is true, then the method will act on a cloned list.
*
* @param {Array} select_option Key list to keep
* @param {Array} list The item list to filter
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function select(select_option, list, clone) {
var i, j, new_item;
if (!Array.isArray(select_option)) {
throw new TypeError("complex_queries.select(): " +
"Argument 1 is not of type Array");
}
if (!Array.isArray(list)) {
throw new TypeError("complex_queries.select(): " +
"Argument 2 is not of type Array");
}
if (clone === true) {
list = deepClone(list);
}
for (i = 0; i < list.length; i += 1) {
new_item = {};
for (j = 0; j < select_option.length; j += 1) {
new_item[select_option[j]] = list[i][select_option[j]];
}
for (j in new_item) {
if (new_item.hasOwnProperty(j)) {
list[i] = new_item;
break;
}
}
}
return list;
}
_export('select', select);
/**
* Sort a list of items, according to keys and directions. If `clone` is true,
* then the method will act on a cloned list.
*
* @param {Array} sort_on_option List of couples [key, direction]
* @param {Array} list The item list to sort
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function sortOn(sort_on_option, list, clone) {
var sort_index;
if (!Array.isArray(sort_on_option)) {
throw new TypeError("complex_queries.sortOn(): " +
"Argument 1 is not of type 'array'");
}
if (clone) {
list = deepClone(list);
}
for (sort_index = sort_on_option.length - 1; sort_index >= 0;
sort_index -= 1) {
list.sort(sortFunction(
sort_on_option[sort_index][0],
sort_on_option[sort_index][1]
));
}
return list;
}
_export('sortOn', sortOn);
/**
* Limit a list of items, according to index and length. If `clone` is true,
* then the method will act on a cloned list.
*
* @param {Array} limit_option A couple [from, length]
* @param {Array} list The item list to limit
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function limit(limit_option, list, clone) {
if (!Array.isArray(limit_option)) {
throw new TypeError("complex_queries.limit(): " +
"Argument 1 is not of type 'array'");
}
if (!Array.isArray(list)) {
throw new TypeError("complex_queries.limit(): " +
"Argument 2 is not of type 'array'");
}
if (clone) {
list = deepClone(list);
}
list.splice(0, limit_option[0]);
if (limit_option[1]) {
list.splice(limit_option[1]);
}
return list;
}
_export('limit', limit);
/**
* Convert a search text to a regexp.
*
* @param {String} string The string to convert
* @param {String} [wildcard_character=undefined] The wildcard chararter
* @return {RegExp} The search text regexp
*/
function convertStringToRegExp(string, wildcard_character) {
if (typeof string !== 'string') {
throw new TypeError("complex_queries.convertStringToRegExp(): " +
"Argument 1 is not of type 'string'");
}
if (wildcard_character === undefined ||
wildcard_character === null || wildcard_character === '') {
return new RegExp("^" + stringEscapeRegexpCharacters(string) + "$");
}
if (typeof wildcard_character !== 'string' || wildcard_character.length > 1) {
throw new TypeError("complex_queries.convertStringToRegExp(): " +
"Optional argument 2 must be a string of length <= 1");
}
return new RegExp("^" + stringEscapeRegexpCharacters(string).replace(
new RegExp(stringEscapeRegexpCharacters(wildcard_character), 'g'),
'.*'
) + "$");
}
_export('convertStringToRegExp', convertStringToRegExp);
return to_export;
}));
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(exports, require('rsvp'), require('sha256'));
}
window.jIO = {};
module(window.jIO, RSVP, {hex_sha256: hex_sha256});
}(['exports', 'rsvp', 'sha256'], function (exports, RSVP, sha256) {
"use strict";
var hex_sha256 = sha256.hex_sha256;
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global uniqueJSONStringify, methodType */
var defaults = {}, constants = {};
defaults.storage_types = {};
constants.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'
};
// if (dcmi_types.Collection === 'Collection') { is a DCMI type }
// if (typeof dcmi_types[name] === 'string') { is a DCMI type }
constants.http_status_text = {
"0": "Unknown",
"550": "Internal JIO Error",
"551": "Internal Storage Error",
"Unknown": "Unknown",
"Internal JIO Error": "Internal JIO Error",
"Internal Storage Error": "Internal Storage Error",
"unknown": "Unknown",
"internal_jio_error": "Internal JIO Error",
"internal_storage_error": "Internal Storage Error",
"200": "Ok",
"201": "Created",
"204": "No Content",
"205": "Reset Content",
"206": "Partial Content",
"400": "Bad Request",
"401": "Unauthorized",
"402": "Payment Required",
"403": "Forbidden",
"404": "Not Found",
"405": "Method Not Allowed",
"406": "Not Acceptable",
"407": "Proxy Authentication Required",
"408": "Request Timeout",
"409": "Conflict",
"410": "Gone",
"411": "Length Required",
"412": "Precondition Failed",
"413": "Request Entity Too Large",
"414": "Request-URI Too Long",
"415": "Unsupported Media Type",
"416": "Requested Range Not Satisfiable",
"417": "Expectation Failed",
"418": "I'm a teapot",
"419": "Authentication Timeout",
"500": "Internal Server Error",
"501": "Not Implemented",
"502": "Bad Gateway",
"503": "Service Unavailable",
"504": "Gateway Timeout",
"507": "Insufficient Storage",
"Ok": "Ok",
"Created": "Created",
"No Content": "No Content",
"Reset Content": "Reset Content",
"Partial Content": "Partial Content",
"Bad Request": "Bad Request",
"Unauthorized": "Unauthorized",
"Payment Required": "Payment Required",
"Forbidden": "Forbidden",
"Not Found": "Not Found",
"Method Not Allowed": "Method Not Allowed",
"Not Acceptable": "Not Acceptable",
"Proxy Authentication Required": "Proxy Authentication Required",
"Request Timeout": "Request Timeout",
"Conflict": "Conflict",
"Gone": "Gone",
"Length Required": "Length Required",
"Precondition Failed": "Precondition Failed",
"Request Entity Too Large": "Request Entity Too Large",
"Request-URI Too Long": "Request-URI Too Long",
"Unsupported Media Type": "Unsupported Media Type",
"Requested Range Not Satisfiable": "Requested Range Not Satisfiable",
"Expectation Failed": "Expectation Failed",
"I'm a teapot": "I'm a teapot",
"Authentication Timeout": "Authentication Timeout",
"Internal Server Error": "Internal Server Error",
"Not Implemented": "Not Implemented",
"Bad Gateway": "Bad Gateway",
"Service Unavailable": "Service Unavailable",
"Gateway Timeout": "Gateway Timeout",
"Insufficient Storage": "Insufficient Storage",
"ok": "Ok",
"created": "Created",
"no_content": "No Content",
"reset_content": "Reset Content",
"partial_content": "Partial Content",
"bad_request": "Bad Request",
"unauthorized": "Unauthorized",
"payment_required": "Payment Required",
"forbidden": "Forbidden",
"not_found": "Not Found",
"method_not_allowed": "Method Not Allowed",
"not_acceptable": "Not Acceptable",
"proxy_authentication_required": "Proxy Authentication Required",
"request_timeout": "Request Timeout",
"conflict": "Conflict",
"gone": "Gone",
"length_required": "Length Required",
"precondition_failed": "Precondition Failed",
"request_entity_too_large": "Request Entity Too Large",
"request-uri_too_long": "Request-URI Too Long",
"unsupported_media_type": "Unsupported Media Type",
"requested_range_not_satisfiable": "Requested Range Not Satisfiable",
"expectation_failed": "Expectation Failed",
"im_a_teapot": "I'm a teapot",
"authentication_timeout": "Authentication Timeout",
"internal_server_error": "Internal Server Error",
"not_implemented": "Not Implemented",
"bad_gateway": "Bad Gateway",
"service_unavailable": "Service Unavailable",
"gateway_timeout": "Gateway Timeout",
"insufficient_storage": "Insufficient Storage"
};
constants.http_status = {
"0": 0,
"550": 550,
"551": 551,
"Unknown": 0,
"Internal JIO Error": 550,
"Internal Storage Error": 551,
"unknown": 0,
"internal_jio_error": 550,
"internal_storage_error": 551,
"200": 200,
"201": 201,
"204": 204,
"205": 205,
"206": 206,
"400": 400,
"401": 401,
"402": 402,
"403": 403,
"404": 404,
"405": 405,
"406": 406,
"407": 407,
"408": 408,
"409": 409,
"410": 410,
"411": 411,
"412": 412,
"413": 413,
"414": 414,
"415": 415,
"416": 416,
"417": 417,
"418": 418,
"419": 419,
"500": 500,
"501": 501,
"502": 502,
"503": 503,
"504": 504,
"507": 507,
"Ok": 200,
"Created": 201,
"No Content": 204,
"Reset Content": 205,
"Partial Content": 206,
"Bad Request": 400,
"Unauthorized": 401,
"Payment Required": 402,
"Forbidden": 403,
"Not Found": 404,
"Method Not Allowed": 405,
"Not Acceptable": 406,
"Proxy Authentication Required": 407,
"Request Timeout": 408,
"Conflict": 409,
"Gone": 410,
"Length Required": 411,
"Precondition Failed": 412,
"Request Entity Too Large": 413,
"Request-URI Too Long": 414,
"Unsupported Media Type": 415,
"Requested Range Not Satisfiable": 416,
"Expectation Failed": 417,
"I'm a teapot": 418,
"Authentication Timeout": 419,
"Internal Server Error": 500,
"Not Implemented": 501,
"Bad Gateway": 502,
"Service Unavailable": 503,
"Gateway Timeout": 504,
"Insufficient Storage": 507,
"ok": 200,
"created": 201,
"no_content": 204,
"reset_content": 205,
"partial_content": 206,
"bad_request": 400,
"unauthorized": 401,
"payment_required": 402,
"forbidden": 403,
"not_found": 404,
"method_not_allowed": 405,
"not_acceptable": 406,
"proxy_authentication_required": 407,
"request_timeout": 408,
"conflict": 409,
"gone": 410,
"length_required": 411,
"precondition_failed": 412,
"request_entity_too_large": 413,
"request-uri_too_long": 414,
"unsupported_media_type": 415,
"requested_range_not_satisfiable": 416,
"expectation_failed": 417,
"im_a_teapot": 418,
"authentication_timeout": 419,
"internal_server_error": 500,
"not_implemented": 501,
"bad_gateway": 502,
"service_unavailable": 503,
"gateway_timeout": 504,
"insufficient_storage": 507
};
constants.http_action = {
"0": "error",
"550": "error",
"551": "error",
"Unknown": "error",
"Internal JIO Error": "error",
"Internal Storage Error": "error",
"unknown": "error",
"internal_jio_error": "error",
"internal_storage_error": "error",
"200": "success",
"201": "success",
"204": "success",
"205": "success",
"206": "success",
"400": "error",
"401": "error",
"402": "error",
"403": "error",
"404": "error",
"405": "error",
"406": "error",
"407": "error",
"408": "error",
"409": "error",
"410": "error",
"411": "error",
"412": "error",
"413": "error",
"414": "error",
"415": "error",
"416": "error",
"417": "error",
"418": "error",
"419": "retry",
"500": "retry",
"501": "error",
"502": "error",
"503": "retry",
"504": "retry",
"507": "error",
"Ok": "success",
"Created": "success",
"No Content": "success",
"Reset Content": "success",
"Partial Content": "success",
"Bad Request": "error",
"Unauthorized": "error",
"Payment Required": "error",
"Forbidden": "error",
"Not Found": "error",
"Method Not Allowed": "error",
"Not Acceptable": "error",
"Proxy Authentication Required": "error",
"Request Timeout": "error",
"Conflict": "error",
"Gone": "error",
"Length Required": "error",
"Precondition Failed": "error",
"Request Entity Too Large": "error",
"Request-URI Too Long": "error",
"Unsupported Media Type": "error",
"Requested Range Not Satisfiable": "error",
"Expectation Failed": "error",
"I'm a teapot": "error",
"Authentication Timeout": "retry",
"Internal Server Error": "retry",
"Not Implemented": "error",
"Bad Gateway": "error",
"Service Unavailable": "retry",
"Gateway Timeout": "retry",
"Insufficient Storage": "error",
"ok": "success",
"created": "success",
"no_content": "success",
"reset_content": "success",
"partial_content": "success",
"bad_request": "error",
"unauthorized": "error",
"payment_required": "error",
"forbidden": "error",
"not_found": "error",
"method_not_allowed": "error",
"not_acceptable": "error",
"proxy_authentication_required": "error",
"request_timeout": "error",
"conflict": "error",
"gone": "error",
"length_required": "error",
"precondition_failed": "error",
"request_entity_too_large": "error",
"request-uri_too_long": "error",
"unsupported_media_type": "error",
"requested_range_not_satisfiable": "error",
"expectation_failed": "error",
"im_a_teapot": "error",
"authentication_timeout": "retry",
"internal_server_error": "retry",
"not_implemented": "error",
"bad_gateway": "error",
"service_unavailable": "retry",
"gateway_timeout": "retry",
"insufficient_storage": "error"
};
constants.content_type_re =
/^([a-z]+\/[a-zA-Z0-9\+\-\.]+)(?:\s*;\s*charset\s*=\s*([a-zA-Z0-9\-]+))?$/;
/**
* Function that does nothing
*/
constants.emptyFunction = function () {
return;
};
defaults.job_rule_conditions = {};
/**
* Adds some job rule conditions
*/
(function () {
/**
* Compare two jobs and test if they use the same storage description
*
* @param {Object} a The first job to compare
* @param {Object} b The second job to compare
* @return {Boolean} True if equal, else false
*/
function sameStorageDescription(a, b) {
return uniqueJSONStringify(a.storage_spec) ===
uniqueJSONStringify(b.storage_spec);
}
/**
* Compare two jobs and test if they are writers
*
* @param {Object} a The first job to compare
* @param {Object} b The second job to compare
* @return {Boolean} True if equal, else false
*/
function areWriters(a, b) {
return methodType(a.method) === 'writer' &&
methodType(b.method) === 'writer';
}
/**
* Compare two jobs and test if they are readers
*
* @param {Object} a The first job to compare
* @param {Object} b The second job to compare
* @return {Boolean} True if equal, else false
*/
function areReaders(a, b) {
return methodType(a.method) === 'reader' &&
methodType(b.method) === 'reader';
}
/**
* Compare two jobs and test if their methods are the same
*
* @param {Object} a The first job to compare
* @param {Object} b The second job to compare
* @return {Boolean} True if equal, else false
*/
function sameMethod(a, b) {
return a.method === b.method;
}
/**
* Compare two jobs and test if their document ids are the same
*
* @param {Object} a The first job to compare
* @param {Object} b The second job to compare
* @return {Boolean} True if equal, else false
*/
function sameDocumentId(a, b) {
return a.kwargs._id === b.kwargs._id;
}
/**
* Compare two jobs and test if their kwargs are equal
*
* @param {Object} a The first job to compare
* @param {Object} b The second job to compare
* @return {Boolean} True if equal, else false
*/
function sameParameters(a, b) {
return uniqueJSONStringify(a.kwargs) ===
uniqueJSONStringify(b.kwargs);
}
/**
* Compare two jobs and test if their options are equal
*
* @param {Object} a The first job to compare
* @param {Object} b The second job to compare
* @return {Boolean} True if equal, else false
*/
function sameOptions(a, b) {
return uniqueJSONStringify(a.options) ===
uniqueJSONStringify(b.options);
}
defaults.job_rule_conditions = {
"sameStorageDescription": sameStorageDescription,
"areWriters": areWriters,
"areReaders": areReaders,
"sameMethod": sameMethod,
"sameDocumentId": sameDocumentId,
"sameParameters": sameParameters,
"sameOptions": sameOptions
};
}());
/*jslint indent: 2, maxlen: 80, nomen: true, sloppy: true */
/*global exports, Blob, FileReader, RSVP, hex_sha256, XMLHttpRequest,
constants */
/**
* Do not exports these tools unless they are not writable, not configurable.
*/
exports.util = {};
/**
* Inherits the prototype methods from one constructor into another. The
* prototype of `constructor` will be set to a new object created from
* `superConstructor`.
*
* @param {Function} constructor The constructor which inherits the super
* one
* @param {Function} superConstructor The super constructor
*/
function inherits(constructor, superConstructor) {
constructor.super_ = superConstructor;
constructor.prototype = Object.create(superConstructor.prototype, {
"constructor": {
"configurable": true,
"enumerable": false,
"writable": true,
"value": constructor
}
});
}
/**
* Clones jsonable object in deep
*
* @param {A} object The jsonable object to clone
* @return {A} The cloned object
*/
function jsonDeepClone(object) {
var tmp = JSON.stringify(object);
if (tmp === undefined) {
return undefined;
}
return JSON.parse(tmp);
}
exports.util.jsonDeepClone = jsonDeepClone;
/**
* Clones all native object in deep. Managed types: Object, Array, String,
* Number, Boolean, Function, null.
*
* It can also clone object which are serializable, like Date.
*
* To make a class serializable, you need to implement the `toJSON` function
* which returns a JSON representation of the object. The return value is used
* as first parameter of the object constructor.
*
* @param {A} object The object to clone
* @return {A} The cloned object
*/
function deepClone(object) {
var i, cloned;
if (Array.isArray(object)) {
cloned = [];
for (i = 0; i < object.length; i += 1) {
cloned[i] = deepClone(object[i]);
}
return cloned;
}
if (object === null) {
return null;
}
if (typeof object === 'object') {
if (Object.getPrototypeOf(object) === Object.prototype) {
cloned = {};
for (i in object) {
if (object.hasOwnProperty(i)) {
cloned[i] = deepClone(object[i]);
}
}
return cloned;
}
if (object instanceof Date) {
// XXX this block is to enable phantomjs and browsers compatibility with
// Date.prototype.toJSON when it is a invalid date. In phantomjs, it
// returns `"Invalid Date"` but in browsers it returns `null`. In
// browsers, give `null` as parameter to `new Date()` doesn't return an
// invalid date.
// Clonning date with `return new Date(object)` make problems on Firefox.
// I don't know why... (Tested on Firefox 23)
if (isFinite(object.getTime())) {
return new Date(object.toJSON());
}
return new Date("Invalid Date");
}
// clone serializable objects
if (typeof object.toJSON === 'function') {
return new (Object.getPrototypeOf(object).constructor)(object.toJSON());
}
// cannot clone
return object;
}
return object;
}
exports.util.deepClone = deepClone;
/**
* Update a dictionnary by adding/replacing key values from another dict.
* Enumerable values equal to undefined are also used.
*
* @param {Object} original The dict to update
* @param {Object} other The other dict
* @return {Object} The updated original dict
*/
function dictUpdate(original, other) {
var k;
for (k in other) {
if (other.hasOwnProperty(k)) {
original[k] = other[k];
}
}
return original;
}
exports.util.dictUpdate = dictUpdate;
/**
* Like 'dict.clear()' in python. Delete all dict entries.
*
* @method dictClear
* @param {Object} self The dict to clear
*/
function dictClear(dict) {
var i;
for (i in dict) {
if (dict.hasOwnProperty(i)) {
delete dict[i];
// dictClear(dict);
// break;
}
}
}
exports.util.dictClear = dictClear;
/**
* Filter a dict to keep only values which keys are in `keys` list.
*
* @param {Object} dict The dict to filter
* @param {Array} keys The key list to keep
*/
function dictFilter(dict, keys) {
var i, buffer = [];
for (i = 0; i < keys.length; i += 1) {
buffer[i] = dict[keys[i]];
}
dictClear(dict);
for (i = 0; i < buffer.length; i += 1) {
dict[keys[i]] = buffer[i];
}
}
exports.util.dictFilter = dictFilter;
/**
* A faster version of `array.indexOf(value)` -> `indexOf(value, array)`
*
* @param {Any} value The value to search for
* @param {Array} array The array to browse
* @return {Number} index of value, -1 otherwise
*/
function indexOf(value, array) {
var i;
for (i = 0; i < array.length; i += 1) {
if (array[i] === value) {
return i;
}
}
return -1;
}
exports.util.indexOf = indexOf;
/**
* Gets all elements of an array and classifies them in a dict of array.
* Dict keys are element types, and values are list of element of type 'key'.
*
* @param {Array} array The array of elements to pop
* @return {Object} The type dict
*/
function arrayValuesToTypeDict(array) {
var i, type, types = {};
for (i = 0; i < array.length; i += 1) {
type = Array.isArray(array[i]) ? 'array' : typeof array[i];
if (!types[type]) {
types[type] = [array[i]];
} else {
types[type][types[type].length] = array[i];
}
}
return types;
}
/**
* An Universal Unique ID generator
*
* @return {String} The new UUID.
*/
function generateUuid() {
function S4() {
return ('0000' + Math.floor(
Math.random() * 0x10000 /* 65536 */
).toString(16)).slice(-4);
}
return S4() + S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + S4() + S4();
}
exports.util.generateUuid = generateUuid;
/**
* Returns the number with the lowest value
*
* @param {Number} *values The values to compare
* @return {Number} The minimum
*/
function min() {
var i, val;
for (i = 1; i < arguments.length; i += 1) {
if (val === undefined || val > arguments[i]) {
val = arguments[i];
}
}
return val;
}
exports.util.min = min;
/**
* Returns the number with the greatest value
*
* @param {Number} *values The values to compare
* @return {Number} The maximum
*/
function max() {
var i, val;
for (i = 1; i < arguments.length; i += 1) {
if (val === undefined || val < arguments[i]) {
val = arguments[i];
}
}
return val;
}
exports.util.max = max;
/**
* JSON stringify a value. Dict keys are sorted in order to make a kind of
* deepEqual thanks to a simple strict equal string comparison.
*
* JSON.stringify({"a": "b", "c": "d"}) ===
* JSON.stringify({"c": "d", "a": "b"}) // false
*
* deepEqual({"a": "b", "c": "d"}, {"c": "d", "a": "b"}); // true
*
* uniqueJSONStringify({"a": "b", "c": "d"}) ===
* uniqueJSONStringify({"c": "d", "a": "b"}) // true
*
* @param {Any} value The value to stringify
* @param {Function} [replacer] A function to replace values during parse
*/
function uniqueJSONStringify(value, replacer) {
function subStringify(value, key) {
var i, res;
if (typeof replacer === 'function') {
value = replacer(key, value);
}
if (Array.isArray(value)) {
res = [];
for (i = 0; i < value.length; i += 1) {
res[res.length] = subStringify(value[i], i);
if (res[res.length - 1] === undefined) {
res[res.length - 1] = 'null';
}
}
return '[' + res.join(',') + ']';
}
if (typeof value === 'object' && value !== null &&
typeof value.toJSON !== 'function') {
res = [];
for (i in value) {
if (value.hasOwnProperty(i)) {
res[res.length] = subStringify(value[i], i);
if (res[res.length - 1] !== undefined) {
res[res.length - 1] = JSON.stringify(i) + ":" + res[res.length - 1];
} else {
res.length -= 1;
}
}
}
res.sort();
return '{' + res.join(',') + '}';
}
return JSON.stringify(value);
}
return subStringify(value, '');
}
exports.util.uniqueJSONStringify = uniqueJSONStringify;
function makeBinaryStringDigest(string) {
return 'sha256-' + hex_sha256(string);
}
exports.util.makeBinaryStringDigest = makeBinaryStringDigest;
function readBlobAsBinaryString(blob) {
var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) {
fr.addEventListener("load", resolve);
fr.addEventListener("error", reject);
fr.addEventListener("progress", notify);
fr.readAsBinaryString(blob);
}, function () {
fr.abort();
});
}
exports.util.readBlobAsBinaryString = readBlobAsBinaryString;
function readBlobAsArrayBuffer(blob) {
var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) {
fr.addEventListener("load", resolve);
fr.addEventListener("error", reject);
fr.addEventListener("progress", notify);
fr.readAsArrayBuffer(blob);
}, function () {
fr.abort();
});
}
exports.util.readBlobAsArrayBuffer = readBlobAsArrayBuffer;
function readBlobAsText(blob) {
var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) {
fr.addEventListener("load", resolve);
fr.addEventListener("error", reject);
fr.addEventListener("progress", notify);
fr.readAsText(blob);
}, function () {
fr.abort();
});
}
exports.util.readBlobAsText = readBlobAsText;
/**
* Send request with XHR and return a promise. xhr.onload: The promise is
* resolve when the status code is lower than 400 with the xhr object as first
* parameter. xhr.onerror: reject with xhr object as first
* parameter. xhr.onprogress: notifies the xhr object.
*
* @param {Object} param The parameters
* @param {String} [param.type="GET"] The request method
* @param {String} [param.dataType=""] The data type to retrieve
* @param {String} param.url The url
* @param {Any} [param.data] The data to send
* @param {Function} [param.beforeSend] A function called just before send
* request. The first parameter of this function is the XHR object.
* @return {Promise} The promise
*/
function ajax(param) {
var xhr = new XMLHttpRequest();
return new RSVP.Promise(function (resolve, reject, notify) {
var k;
xhr.open(param.type || "GET", param.url, true);
xhr.responseType = param.dataType || "";
if (typeof param.headers === 'object' && param.headers !== null) {
for (k in param.headers) {
if (param.headers.hasOwnProperty(k)) {
xhr.setRequestHeader(k, param.headers[k]);
}
}
}
xhr.addEventListener("load", function (e) {
if (e.target.status >= 400) {
return reject(e);
}
resolve(e);
});
xhr.addEventListener("error", reject);
xhr.addEventListener("progress", notify);
if (typeof param.beforeSend === 'function') {
param.beforeSend(xhr);
}
xhr.send(param.data);
}, function () {
xhr.abort();
});
}
exports.util.ajax = ajax;
/**
* Acts like `Array.prototype.concat` but does not create a copy of the original
* array. It extends the original array and return it.
*
* @param {Array} array The array to extend
* @param {Any} [args]* Values to add in the array
* @return {Array} The original array
*/
function arrayExtend(array) { // args*
var i, j;
for (i = 1; i < arguments.length; i += 1) {
if (Array.isArray(arguments[i])) {
for (j = 0; j < arguments[i].length; j += 1) {
array[array.length] = arguments[i][j];
}
} else {
array[array.length] = arguments[i];
}
}
return array;
}
exports.util.arrayExtend = arrayExtend;
/**
* Acts like `Array.prototype.concat` but does not create a copy of the original
* array. It extends the original array from a specific position and return it.
*
* @param {Array} array The array to extend
* @param {Number} position The position where to extend
* @param {Any} [args]* Values to add in the array
* @return {Array} The original array
*/
function arrayInsert(array, position) { // args*
var array_part = array.splice(position, array.length - position);
arrayExtend.apply(null, arrayExtend([
], [array], Array.prototype.slice.call(arguments, 2)));
return arrayExtend(array, array_part);
}
exports.util.arrayInsert = arrayInsert;
/**
* Guess if the method is a writer or a reader.
*
* @param {String} method The method name
* @return {String} "writer", "reader" or "unknown"
*/
function methodType(method) {
switch (method) {
case "post":
case "put":
case "putAttachment":
case "remove":
case "removeAttachment":
case "repair":
return 'writer';
case "get":
case "getAttachment":
case "allDocs":
case "check":
return 'reader';
default:
return 'unknown';
}
}
/*jslint indent: 2, maxlen: 80, nomen: true, sloppy: true */
/*global secureMethods, exports, console */
/**
* Inspired by nodejs EventEmitter class
* http://nodejs.org/api/events.html
*
* When an EventEmitter instance experiences an error, the typical action is
* to emit an 'error' event. Error events are treated as a special case in
* node. If there is no listener for it, then the default action throws the
* exception again.
*
* All EventEmitters emit the event 'newListener' when new listeners are added
* and 'removeListener' when a listener is removed.
*
* @class EventEmitter
* @constructor
*/
function EventEmitter() {
this._events = {};
this._maxListeners = 10;
}
/**
* Adds a listener to the end of the listeners array for the specified
* event.
*
* @method addListener
* @param {String} event The event name
* @param {Function} listener The listener callback
* @return {EventEmitter} This emitter
*/
EventEmitter.prototype.addListener = function (event, listener) {
var listener_list;
if (typeof listener !== "function") {
return this;
}
this.emit("newListener", event, listener);
listener_list = this._events[event];
if (listener_list === undefined) {
this._events[event] = listener;
listener_list = listener;
} else if (typeof listener_list === "function") {
this._events[event] = [listener_list, listener];
listener_list = this._events[event];
} else {
listener_list[listener_list.length] = listener;
}
if (this._maxListeners > 0 &&
typeof listener_list !== "function" &&
listener_list.length > this._maxListeners &&
listener_list.warned !== true) {
console.warn("warning: possible EventEmitter memory leak detected. " +
listener_list.length + " listeners added. " +
"Use emitter.setMaxListeners() to increase limit.");
listener_list.warned = true;
}
return this;
};
/**
* #crossLink "EventEmitter/addListener:method"
*
* @method on
*/
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
/**
* Adds a one time listener for the event. This listener is invoked only the
* next time the event is fired, after which it is removed.
*
* @method once
* @param {String} event The event name
* @param {Function} listener The listener callback
* @return {EventEmitter} This emitter
*/
EventEmitter.prototype.once = function (event, listener) {
var that = this, wrapper = function () {
that.removeListener(event, wrapper);
listener.apply(that, arguments);
};
wrapper.original = listener;
return that.on(event, wrapper);
};
/**
* Remove a listener from the listener array for the specified event.
* Caution: changes array indices in the listener array behind the listener
*
* @method removeListener
* @param {String} event The event name
* @param {Function} listener The listener callback
* @return {EventEmitter} This emitter
*/
EventEmitter.prototype.removeListener = function (event, listener) {
var listener_list = this._events[event], i;
if (listener_list) {
if (typeof listener_list === "function") {
if (listener_list === listener || listener_list.original === listener) {
delete this._events[event];
}
return this;
}
for (i = 0; i < listener_list.length; i += 1) {
if (listener_list[i] === listener ||
listener_list[i].original === listener) {
listener_list.splice(i, 1);
this.emit("removeListener", event, listener);
break;
}
}
if (listener_list.length === 1) {
this._events[event] = listener_list[0];
}
if (listener_list.length === 0) {
this._events[event] = undefined;
}
}
return this;
};
/**
* Removes all listeners, or those of the specified event.
*
* @method removeAllListeners
* @param {String} event The event name (optional)
* @return {EventEmitter} This emitter
*/
EventEmitter.prototype.removeAllListeners = function (event) {
var key;
if (event === undefined) {
for (key in this._events) {
if (this._events.hasOwnProperty(key)) {
delete this._events[key];
}
}
return this;
}
delete this._events[event];
return this;
};
/**
* By default EventEmitters will print a warning if more than 10 listeners
* are added for a particular event. This is a useful default which helps
* finding memory leaks. Obviously not all Emitters should be limited to 10.
* This function allows that to be increased. Set to zero for unlimited.
*
* @method setMaxListeners
* @param {Number} max_listeners The maximum of listeners
*/
EventEmitter.prototype.setMaxListeners = function (max_listeners) {
this._maxListeners = max_listeners;
};
/**
* Execute each of the listeners in order with the supplied arguments.
*
* @method emit
* @param {String} event The event name
* @param {Any} [args]* The listener argument to give
* @return {Boolean} true if event had listeners, false otherwise.
*/
EventEmitter.prototype.emit = function (event) {
var i, argument_list, listener_list;
listener_list = this._events[event];
if (typeof listener_list === 'function') {
listener_list = [listener_list];
} else if (Array.isArray(listener_list)) {
listener_list = listener_list.slice();
} else {
return false;
}
argument_list = Array.prototype.slice.call(arguments, 1);
for (i = 0; i < listener_list.length; i += 1) {
try {
listener_list[i].apply(this, argument_list);
} catch (e) {
if (this.listeners("error").length > 0) {
this.emit("error", e);
break;
}
throw e;
}
}
return true;
};
/**
* Returns an array of listeners for the specified event.
*
* @method listeners
* @param {String} event The event name
* @return {Array} The array of listeners
*/
EventEmitter.prototype.listeners = function (event) {
return (typeof this._events[event] === 'function' ?
[this._events[event]] : (this._events[event] || []).slice());
};
/**
* Static method; Return the number of listeners for a given event.
*
* @method listenerCount
* @static
* @param {EventEmitter} emitter The event emitter
* @param {String} event The event name
* @return {Number} The number of listener
*/
EventEmitter.listenerCount = function (emitter, event) {
return emitter.listeners(event).length;
};
exports.EventEmitter = EventEmitter;
/*jslint indent: 2, maxlen: 80, nomen: true, sloppy: true */
/*global EventEmitter, deepClone, inherits, exports */
/*global enableRestAPI, enableRestParamChecker, enableJobMaker, enableJobRetry,
enableJobReference, enableJobChecker, enableJobQueue, enableJobRecovery,
enableJobTimeout, enableJobExecuter */
function JIO(storage_spec, options) {
JIO.super_.call(this);
var shared = new EventEmitter();
shared.storage_spec = deepClone(storage_spec);
if (options === undefined) {
options = {};
} else if (typeof options !== 'object' || Array.isArray(options)) {
throw new TypeError("JIO(): Optional argument 2 is not of type 'object'");
}
enableRestAPI(this, shared, options);
enableRestParamChecker(this, shared, options);
enableJobMaker(this, shared, options);
enableJobReference(this, shared, options);
enableJobRetry(this, shared, options);
enableJobChecker(this, shared, options);
enableJobQueue(this, shared, options);
enableJobRecovery(this, shared, options);
enableJobTimeout(this, shared, options);
enableJobExecuter(this, shared, options);
shared.emit('load');
}
inherits(JIO, EventEmitter);
JIO.createInstance = function (storage_spec, options) {
return new JIO(storage_spec, options);
};
exports.JIO = JIO;
exports.createJIO = JIO.createInstance;
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global deepClone, dictFilter, uniqueJSONStringify */
/**
* Tool to manipulate a list of object containing at least one property: 'id'.
* Id must be a number > 0.
*
* @class JobQueue
* @constructor
* @param {Workspace} workspace The workspace where to store
* @param {String} namespace The namespace to use in the workspace
* @param {Array} job_keys An array of job keys to store
* @param {Array} [array] An array of object
*/
function JobQueue(workspace, namespace, job_keys, array) {
this._workspace = workspace;
this._namespace = namespace;
this._job_keys = job_keys;
if (Array.isArray(array)) {
this._array = array;
} else {
this._array = [];
}
}
/**
* Store the job queue into the workspace.
*
* @method save
*/
JobQueue.prototype.save = function () {
var i, job_queue = deepClone(this._array);
for (i = 0; i < job_queue.length; i += 1) {
dictFilter(job_queue[i], this._job_keys);
}
if (this._array.length === 0) {
this._workspace.removeItem(this._namespace);
} else {
this._workspace.setItem(
this._namespace,
uniqueJSONStringify(job_queue)
);
}
return this;
};
/**
* Loads the job queue from the workspace.
*
* @method load
*/
JobQueue.prototype.load = function () {
var job_list;
try {
job_list = JSON.parse(this._workspace.getItem(this._namespace));
} catch (ignore) {}
if (!Array.isArray(job_list)) {
job_list = [];
}
this.clear();
new JobQueue(job_list).repair();
this.update(job_list);
return this;
};
/**
* Returns the array version of the job queue
*
* @method asArray
* @return {Array} The job queue as array
*/
JobQueue.prototype.asArray = function () {
return this._array;
};
/**
* Removes elements which are not objects containing at least 'id' property.
*
* @method repair
*/
JobQueue.prototype.repair = function () {
var i, job;
for (i = 0; i < this._array.length; i += 1) {
job = this._array[i];
if (typeof job !== 'object' || Array.isArray(job) ||
typeof job.id !== 'number' || job.id <= 0) {
this._array.splice(i, 1);
i -= 1;
}
}
};
/**
* Post an object and generate an id
*
* @method post
* @param {Object} job The job object
* @return {Number} The generated id
*/
JobQueue.prototype.post = function (job) {
var i, next = 1;
// get next id
for (i = 0; i < this._array.length; i += 1) {
if (this._array[i].id >= next) {
next = this._array[i].id + 1;
}
}
job.id = next;
this._array[this._array.length] = deepClone(job);
return this;
};
/**
* Put an object to the list. If an object contains the same id, it is replaced
* by the new one.
*
* @method put
* @param {Object} job The job object with an id
*/
JobQueue.prototype.put = function (job) {
var i;
if (typeof job.id !== 'number' || job.id <= 0) {
throw new TypeError("JobQueue().put(): Job id should be a positive number");
}
for (i = 0; i < this._array.length; i += 1) {
if (this._array[i].id === job.id) {
break;
}
}
this._array[i] = deepClone(job);
return this;
};
/**
* Puts some object into the list. Update object with the same id, and add
* unreferenced one.
*
* @method update
* @param {Array} job_list A list of new jobs
*/
JobQueue.prototype.update = function (job_list) {
var i, j = 0, j_max, index = {}, next = 1, job, post_list = [];
j_max = this._array.length;
for (i = 0; i < job_list.length; i += 1) {
if (typeof job_list[i].id !== 'number' || job_list[i].id <= 0) {
// this job has no id, it has to be post
post_list[post_list.length] = job_list[i];
} else {
job = deepClone(job_list[i]);
if (index[job.id] !== undefined) {
// this job is on the list, update
this._array[index[job.id]] = job;
} else if (j === j_max) {
// this job is not on the list, update
this._array[this._array.length] = job;
} else {
// don't if the job is there or not
// searching same job in the original list
while (j < j_max) {
// references visited job
index[this._array[j].id] = j;
if (this._array[j].id >= next) {
next = this._array[j].id + 1;
}
if (this._array[j].id === job.id) {
// found on the list, just update
this._array[j] = job;
break;
}
j += 1;
}
if (j === j_max) {
// not found on the list, add to the end
this._array[this._array.length] = job;
} else {
// found on the list, already updated
j += 1;
}
}
if (job.id >= next) {
next = job.id + 1;
}
}
}
for (i = 0; i < post_list.length; i += 1) {
// adding job without id
post_list[i].id = next;
next += 1;
this._array[this._array.length] = deepClone(post_list[i]);
}
return this;
};
/**
* Get an object from an id. Returns undefined if not found
*
* @method get
* @param {Number} id The job id
* @return {Object} The job or undefined
*/
JobQueue.prototype.get = function (id) {
var i;
for (i = 0; i < this._array.length; i += 1) {
if (this._array[i].id === id) {
return deepClone(this._array[i]);
}
}
};
/**
* Removes an object from an id
*
* @method remove
* @param {Number} id The job id
*/
JobQueue.prototype.remove = function (id) {
var i;
for (i = 0; i < this._array.length; i += 1) {
if (this._array[i].id === id) {
this._array.splice(i, 1);
return true;
}
}
return false;
};
/**
* Clears the list.
*
* @method clear
*/
JobQueue.prototype.clear = function () {
this._array.length = 0;
return this;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global localStorage */
// keywords: js, javascript, store on local storage as array
function LocalStorageArray(namespace) {
var index, next;
function nextId() {
var i = next;
next += 1;
return i;
}
this.length = function () {
return index.length;
};
this.truncate = function (length) {
var i;
if (length === index.length) {
return this;
}
if (length > index.length) {
index.length = length;
localStorage[namespace + '.index'] = JSON.stringify(index);
return this;
}
while (length < index.length) {
i = index.pop();
if (i !== undefined && i !== null) {
delete localStorage[namespace + '.' + i];
}
}
localStorage[namespace + '.index'] = JSON.stringify(index);
return this;
};
this.get = function (i) {
return JSON.parse(localStorage[namespace + '.' + index[i]] || 'null');
};
this.set = function (i, value) {
if (index[i] === undefined || index[i] === null) {
index[i] = nextId();
localStorage[namespace + '.' + index[i]] = JSON.stringify(value);
localStorage[namespace + '.index'] = JSON.stringify(index);
} else {
localStorage[namespace + '.' + index[i]] = JSON.stringify(value);
}
return this;
};
this.append = function (value) {
index[index.length] = nextId();
localStorage[namespace + '.' + index[index.length - 1]] =
JSON.stringify(value);
localStorage[namespace + '.index'] = JSON.stringify(index);
return this;
};
this.pop = function (i) {
var value, key;
if (i === undefined || i === null) {
key = namespace + '.' + index[index.length - 1];
index.pop();
} else {
if (i < 0 || i >= index.length) {
return null;
}
key = namespace + '.' + i;
index.splice(i, 1);
}
value = localStorage[key];
if (index.length === 0) {
delete localStorage[namespace + '.index'];
} else {
localStorage[namespace + '.index'] = JSON.stringify(index);
}
delete localStorage[key];
return JSON.parse(value || 'null');
};
this.clear = function () {
var i;
for (i = 0; i < index.length; i += 1) {
delete localStorage[namespace + '.' + index[i]];
}
index = [];
delete localStorage[namespace + '.index'];
return this;
};
this.reload = function () {
var i;
index = JSON.parse(localStorage[namespace + '.index'] || '[]');
next = 0;
for (i = 0; i < index.length; i += 1) {
if (next < index[i]) {
next = index[i];
}
}
return this;
};
this.toArray = function () {
var i, list = [];
for (i = 0; i < index.length; i += 1) {
list[list.length] = this.get(i);
}
return list;
};
this.update = function (list) {
if (!Array.isArray(list)) {
throw new TypeError("LocalStorageArray().saveArray(): " +
"Argument 1 is not of type 'array'");
}
var i, location;
// update previous values
for (i = 0; i < list.length; i += 1) {
location = index[i];
if (location === undefined || location === null) {
location = nextId();
index[i] = location;
}
localStorage[namespace + '.' + location] =
JSON.stringify(list[i]);
}
// remove last ones
while (list.length < index.length) {
location = index.pop();
if (location !== undefined && location !== null) {
delete localStorage[namespace + '.' + location];
}
}
// store index
localStorage[namespace + '.index'] = JSON.stringify(index);
return this;
};
this.reload();
}
LocalStorageArray.saveArray = function (namespace, list) {
if (!Array.isArray(list)) {
throw new TypeError("LocalStorageArray.saveArray(): " +
"Argument 2 is not of type 'array'");
}
var local_storage_array = new LocalStorageArray(namespace).clear(), i;
for (i = 0; i < list.length; i += 1) {
local_storage_array.append(list[i]);
}
};
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global exports, deepClone, jsonDeepClone */
/**
* A class to manipulate metadata
*
* @class Metadata
* @constructor
*/
function Metadata(metadata) {
if (arguments.length > 0) {
if (typeof metadata !== 'object' ||
Object.getPrototypeOf(metadata || []) !== Object.prototype) {
throw new TypeError("Metadata(): Optional argument 1 is not an object");
}
this._dict = metadata;
} else {
this._dict = {};
}
}
Metadata.prototype.format = function () {
return this.update(this._dict);
};
Metadata.prototype.check = function () {
var k;
for (k in this._dict) {
if (this._dict.hasOwnProperty(k)) {
if (k[0] !== '_') {
if (!Metadata.checkValue(this._dict[k])) {
return false;
}
}
}
}
return true;
};
Metadata.prototype.update = function (metadata) {
var k;
for (k in metadata) {
if (metadata.hasOwnProperty(k)) {
if (k[0] === '_') {
this._dict[k] = jsonDeepClone(metadata[k]);
} else {
this._dict[k] = Metadata.normalizeValue(metadata[k]);
}
if (this._dict[k] === undefined) {
delete this._dict[k];
}
}
}
return this;
};
Metadata.prototype.get = function (key) {
return this._dict[key];
};
Metadata.prototype.add = function (key, value) {
var i;
if (key[0] === '_') {
return this;
}
if (this._dict[key] === undefined) {
this._dict[key] = Metadata.normalizeValue(value);
if (this._dict[key] === undefined) {
delete this._dict[key];
}
return this;
}
if (!Array.isArray(this._dict[key])) {
this._dict[key] = [this._dict[key]];
}
value = Metadata.normalizeValue(value);
if (value === undefined) {
return this;
}
if (!Array.isArray(value)) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
this._dict[key][this._dict[key].length] = value[i];
}
return this;
};
Metadata.prototype.set = function (key, value) {
if (key[0] === '_') {
this._dict[key] = JSON.parse(JSON.stringify(value));
} else {
this._dict[key] = Metadata.normalizeValue(value);
}
if (this._dict[key] === undefined) {
delete this._dict[key];
}
return this;
};
Metadata.prototype.remove = function (key) {
delete this._dict[key];
return this;
};
Metadata.prototype.forEach = function (key, fun) {
var k, i, value, that = this;
if (typeof key === 'function') {
fun = key;
key = undefined;
}
function forEach(key, fun) {
value = that._dict[key];
if (!Array.isArray(that._dict[key])) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
if (typeof value[i] === 'object') {
fun.call(that, key, deepClone(value[i]), i);
} else {
fun.call(that, key, {'content': value[i]}, i);
}
}
}
if (key === undefined) {
for (k in this._dict) {
if (this._dict.hasOwnProperty(k)) {
forEach(k, fun);
}
}
} else {
forEach(key, fun);
}
return this;
};
Metadata.prototype.toFullDict = function () {
var dict = {};
this.forEach(function (key, value, index) {
dict[key] = dict[key] || [];
dict[key][index] = value;
});
return dict;
};
Metadata.asJsonableValue = function (value) {
switch (typeof value) {
case 'string':
case 'boolean':
return value;
case 'number':
if (isFinite(value)) {
return value;
}
return null;
case 'object':
if (value === null) {
return null;
}
if (value instanceof Date) {
// XXX this block is to enable phantomjs and browsers compatibility with
// Date.prototype.toJSON when it is a invalid date. In phantomjs, it
// returns `"Invalid Date"` but in browsers it returns `null`. Here, the
// result will always be `null`.
if (isNaN(value.getTime())) {
return null;
}
}
if (typeof value.toJSON === 'function') {
return Metadata.asJsonableValue(value.toJSON());
}
return value; // dict, array
// case 'undefined':
default:
return null;
}
};
Metadata.isDict = function (o) {
return typeof o === 'object' &&
Object.getPrototypeOf(o || []) === Object.prototype;
};
Metadata.isContent = function (c) {
return typeof c === 'string' ||
(typeof c === 'number' && isFinite(c)) ||
typeof c === 'boolean';
};
Metadata.contentValue = function (value) {
if (Array.isArray(value)) {
return Metadata.contentValue(value[0]);
}
if (Metadata.isDict(value)) {
return value.content;
}
return value;
};
Metadata.normalizeArray = function (value) {
var i;
value = value.slice();
i = 0;
while (i < value.length) {
value[i] = Metadata.asJsonableValue(value[i]);
if (Metadata.isDict(value[i])) {
value[i] = Metadata.normalizeObject(value[i]);
if (value[i] === undefined) {
value.splice(i, 1);
} else {
i += 1;
}
} else if (Metadata.isContent(value[i])) {
i += 1;
} else {
value.splice(i, 1);
}
}
if (value.length === 0) {
return;
}
if (value.length === 1) {
return value[0];
}
return value;
};
Metadata.normalizeObject = function (value) {
var i, count = 0, ok = false, new_value = {};
for (i in value) {
if (value.hasOwnProperty(i)) {
value[i] = Metadata.asJsonableValue(value[i]);
if (Metadata.isContent(value[i])) {
new_value[i] = value[i];
if (new_value[i] === undefined) {
delete new_value[i];
}
count += 1;
if (i === 'content') {
ok = true;
}
}
}
}
if (ok === false) {
return;
}
if (count === 1) {
return new_value.content;
}
return new_value;
};
Metadata.normalizeValue = function (value) {
value = Metadata.asJsonableValue(value);
if (Metadata.isContent(value)) {
return value;
}
if (Array.isArray(value)) {
return Metadata.normalizeArray(value);
}
if (Metadata.isDict(value)) {
return Metadata.normalizeObject(value);
}
};
Metadata.checkArray = function (value) {
var i;
for (i = 0; i < value.length; i += 1) {
if (Metadata.isDict(value[i])) {
if (!Metadata.checkObject(value[i])) {
return false;
}
} else if (!Metadata.isContent(value[i])) {
return false;
}
}
return true;
};
Metadata.checkObject = function (value) {
var i, ok = false;
for (i in value) {
if (value.hasOwnProperty(i)) {
if (Metadata.isContent(value[i])) {
if (i === 'content') {
ok = true;
}
} else {
return false;
}
}
}
if (ok === false) {
return false;
}
return true;
};
Metadata.checkValue = function (value) {
if (Metadata.isContent(value)) {
return true;
}
if (Array.isArray(value)) {
return Metadata.checkArray(value);
}
if (Metadata.isDict(value)) {
return Metadata.checkObject(value);
}
return false;
};
exports.Metadata = Metadata;
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global */
/**
* An array that contain object (or array) references.
*
* @class ReferenceArray
* @constructor
* @param {array} [array] The array where to work on
*/
function ReferenceArray(array) {
if (Array.isArray(array)) {
this._array = array;
} else {
this._array = [];
}
}
/**
* Returns the array version of the job queue
*
* @method asArray
* @return {Array} The job queue as array
*/
ReferenceArray.prototype.asArray = function () {
return this._array;
};
/**
* Returns the index of the object
*
* @method indexOf
* @param {Object} object The object to search
*/
ReferenceArray.prototype.indexOf = function (object) {
var i;
for (i = 0; i < this._array.length; i += 1) {
if (this._array[i] === object) {
return i;
}
}
return -1;
};
/**
* Put an object to the list. If an object already exists, do nothing.
*
* @method put
* @param {Object} object The object to add
*/
ReferenceArray.prototype.put = function (object) {
var i;
for (i = 0; i < this._array.length; i += 1) {
if (this._array[i] === object) {
return false;
}
}
this._array[i] = object;
return true;
};
/**
* Removes an object from the list
*
* @method remove
* @param {Object} object The object to remove
*/
ReferenceArray.prototype.remove = function (object) {
var i;
for (i = 0; i < this._array.length; i += 1) {
if (this._array[i] === object) {
this._array.splice(i, 1);
return true;
}
}
return false;
};
/**
* Clears the list.
*
* @method clear
*/
ReferenceArray.prototype.clear = function () {
this._array.length = 0;
return this;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global exports, defaults */
function Storage() { // (storage_spec, util)
return undefined; // this is a constructor
}
// end Storage
function createStorage(storage_spec, util) {
if (typeof storage_spec.type !== 'string') {
throw new TypeError("Invalid storage description");
}
if (!defaults.storage_types[storage_spec.type]) {
throw new TypeError("Unknown storage '" + storage_spec.type + "'");
}
return new defaults.storage_types[storage_spec.type](storage_spec, util);
}
function addStorage(type, Constructor) {
// var proto = {};
if (typeof type !== 'string') {
throw new TypeError("jIO.addStorage(): Argument 1 is not of type 'string'");
}
if (typeof Constructor !== 'function') {
throw new TypeError("jIO.addStorage(): " +
"Argument 2 is not of type 'function'");
}
if (defaults.storage_types[type]) {
throw new TypeError("jIO.addStorage(): Storage type already exists");
}
// dictUpdate(proto, Constructor.prototype);
// inherits(Constructor, Storage);
// dictUpdate(Constructor.prototype, proto);
defaults.storage_types[type] = Constructor;
}
exports.addStorage = addStorage;
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global */
/**
* A class that acts like localStorage on a simple object.
*
* Like localStorage, the object will contain only strings.
*
* @class Workspace
* @constructor
*/
function Workspace(object) {
this._object = object;
}
// // Too dangerous, never use it
// /**
// * Empty the entire space.
// *
// * @method clear
// */
// Workspace.prototype.clear = function () {
// var k;
// for (k in this._object) {
// if (this._object.hasOwnProperty(k)) {
// delete this._object;
// }
// }
// return undefined;
// };
/**
* Get an item from the space. If the value does not exists, it returns
* null. Else, it returns the string value.
*
* @method getItem
* @param {String} key The location where to get the item
* @return {String} The item
*/
Workspace.prototype.getItem = function (key) {
return this._object[key] === undefined ? null : this._object[key];
};
/**
* Set an item into the space. The value to store is converted to string before.
*
* @method setItem
* @param {String} key The location where to set the item
* @param {Any} value The value to store
*/
Workspace.prototype.setItem = function (key, value) {
if (value === undefined) {
this._object[key] = 'undefined';
} else if (value === null) {
this._object[key] = 'null';
} else {
this._object[key] = value.toString();
}
return undefined;
};
/**
* Removes an item from the space.
*
* @method removeItem
* @param {String} key The location where to remove the item
*/
Workspace.prototype.removeItem = function (key) {
delete this._object[key];
return undefined;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global exports, defaults */
// adds
// - jIO.addJobRuleCondition(name, function)
function addJobRuleCondition(name, method) {
if (typeof name !== 'string') {
throw new TypeError("jIO.addJobRuleAction(): " +
"Argument 1 is not of type 'string'");
}
if (typeof method !== 'function') {
throw new TypeError("jIO.addJobRuleAction(): " +
"Argument 2 is not of type 'function'");
}
if (defaults.job_rule_conditions[name]) {
throw new TypeError("jIO.addJobRuleAction(): Action already exists");
}
defaults.job_rule_conditions[name] = method;
}
exports.addJobRuleCondition = addJobRuleCondition;
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, regexp: true */
/*global constants, dictUpdate, deepClone */
function restCommandRejecter(param, args) {
// reject(status, reason, message, {"custom": "value"});
// reject(status, reason, {..});
// reject(status, {..});
var a = args[0], b = args[1], c = args[2], d = args[3], weak, strong;
weak = {"result": "error"};
strong = {};
weak.status = constants.http_status.unknown;
weak.statusText = constants.http_status_text.unknown;
weak.message = 'Command failed';
weak.reason = 'fail';
weak.method = param.method;
if (param.kwargs._id) {
weak.id = param.kwargs._id;
}
if (/Attachment$/.test(param.method)) {
weak.attachment = param.kwargs._attachment;
}
if (typeof a !== 'object' || Array.isArray(a)) {
strong.status = constants.http_status[a];
strong.statusText = constants.http_status_text[a];
if (strong.status === undefined ||
strong.statusText === undefined) {
return restCommandRejecter(param, [
// can create infernal loop if 'internal_storage_error' is not defined
// in the constants
'internal_storage_error',
'invalid response',
'Unknown status "' + a + '"'
]);
}
a = b;
b = c;
c = d;
}
if (typeof a !== 'object' || Array.isArray(a)) {
strong.reason = a;
a = b;
b = c;
}
if (typeof a !== 'object' || Array.isArray(a)) {
strong.message = a;
a = b;
}
if (typeof a === 'object' && !Array.isArray(a)) {
dictUpdate(weak, a);
}
dictUpdate(weak, strong);
strong = undefined;
if (weak.error === undefined) {
weak.error = weak.statusText.toLowerCase().replace(/ /g, '_').
replace(/[^_a-z]/g, '');
}
if (typeof weak.message !== 'string') {
weak.message = "";
}
if (typeof weak.reason !== 'string') {
weak.reason = "unknown";
}
return param.solver.reject(deepClone(weak));
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global constants, methodType, dictUpdate, Blob, deepClone,
restCommandRejecter */
function restCommandResolver(param, args) {
// resolve('ok', {"custom": "value"});
// resolve(200, {...});
// resolve({...});
var a = args[0], b = args[1], weak = {"result": "success"}, strong = {};
if (param.method === 'post') {
weak.status = constants.http_status.created;
weak.statusText = constants.http_status_text.created;
} else if (methodType(param.method) === "writer" ||
param.method === "check") {
weak.status = constants.http_status.no_content;
weak.statusText = constants.http_status_text.no_content;
} else {
weak.status = constants.http_status.ok;
weak.statusText = constants.http_status_text.ok;
}
if (param.kwargs._id) {
weak.id = param.kwargs._id;
}
if (/Attachment$/.test(param.method)) {
weak.attachment = param.kwargs._attachment;
}
weak.method = param.method;
if (typeof a === 'string' || (typeof a === 'number' && isFinite(a))) {
strong.status = constants.http_status[a];
strong.statusText = constants.http_status_text[a];
if (strong.status === undefined ||
strong.statusText === undefined) {
return restCommandRejecter(param, [
'internal_storage_error',
'invalid response',
'Unknown status "' + a + '"'
]);
}
a = b;
}
if (typeof a === 'object' && !Array.isArray(a)) {
dictUpdate(weak, a);
}
dictUpdate(weak, strong);
strong = undefined; // free memory
if (param.method === 'post' && (typeof weak.id !== 'string' || !weak.id)) {
return restCommandRejecter(param, [
'internal_storage_error',
'invalid response',
'New document id have to be specified'
]);
}
if (param.method === 'getAttachment') {
if (typeof weak.data === 'string') {
weak.data = new Blob([weak.data], {
"type": weak.content_type || weak.mimetype || ""
});
delete weak.content_type;
delete weak.mimetype;
}
if (!(weak.data instanceof Blob)) {
return restCommandRejecter(param, [
'internal_storage_error',
'invalid response',
'getAttachment method needs a Blob as returned "data".'
]);
}
} else if (methodType(param.method) === 'reader' &&
param.method !== 'check' &&
(typeof weak.data !== 'object' ||
Object.getPrototypeOf(weak.data) !== Object.prototype)) {
return restCommandRejecter(param, [
'internal_storage_error',
'invalid response',
param.method + ' method needs a dict as returned "data".'
]);
}
return param.solver.resolve(deepClone(weak));
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global arrayInsert, indexOf, deepClone, defaults, restCommandRejecter */
// creates
// - some defaults job rule actions
function enableJobChecker(jio, shared, options) {
// dependencies
// - shared.jobs Object Array
// - param.promise Object
// creates
// - shared.job_rules Array
// uses 'job' event
var i;
shared.job_rule_action_names = [undefined, "ok", "wait", "update", "deny"];
shared.job_rule_actions = {
wait: function (original_job, new_job) {
original_job.promise.always(function () {
shared.emit('job', new_job);
});
new_job.state = 'waiting';
new_job.modified = new Date();
},
update: function (original_job, new_job) {
if (!new_job.solver) {
// promise associated to the job
new_job.state = 'done';
shared.emit('jobDone', new_job);
} else {
if (!original_job.solver) {
original_job.solver = new_job.solver;
} else {
original_job.promise.then(
new_job.command.resolve,
new_job.command.reject
);
}
}
new_job.state = 'running';
new_job.modified = new Date();
},
deny: function (original_job, new_job) {
new_job.state = 'fail';
new_job.modified = new Date();
restCommandRejecter(new_job, [
'precondition_failed',
'command denied',
'Command rejected by the job checker.'
]);
}
};
function addJobRule(job_rule) {
var i, old_position, before_position, after_position;
// job_rule = {
// code_name: string
// conditions: [string, ...]
// action: 'wait',
// after: code_name
// before: code_name
// }
if (typeof job_rule !== 'object' || job_rule === null) {
// wrong job rule
return;
}
if (typeof job_rule.code_name !== 'string') {
// wrong code name
return;
}
if (!Array.isArray(job_rule.conditions)) {
// wrong conditions
return;
}
if (job_rule.single !== undefined && typeof job_rule.single !== 'boolean') {
// wrong single property
return;
}
if (indexOf(job_rule.action, shared.job_rule_action_names) === -1) {
// wrong action
return;
}
if (job_rule.action !== 'deny' && job_rule.single === true) {
// only 'deny' action doesn't require original_job parameter
return;
}
if (typeof job_rule.after !== 'string') {
job_rule.after = '';
}
if (typeof job_rule.before !== 'string') {
job_rule.before = '';
}
for (i = 0; i < shared.job_rules.length; i += 1) {
if (shared.job_rules[i].code_name === job_rule.after) {
after_position = i + 1;
}
if (shared.job_rules[i].code_name === job_rule.before) {
before_position = i;
}
if (shared.job_rules[i].code_name === job_rule.code_name) {
old_position = i;
}
}
job_rule = {
"code_name": job_rule.code_name,
"conditions": job_rule.conditions,
"single": job_rule.single || false,
"action": job_rule.action || "ok"
};
if (before_position === undefined) {
before_position = shared.job_rules.length;
}
if (after_position > before_position) {
before_position = undefined;
}
if (job_rule.action !== "ok" && before_position !== undefined) {
arrayInsert(shared.job_rules, before_position, job_rule);
}
if (old_position !== undefined) {
if (old_position >= before_position) {
old_position += 1;
}
shared.job_rules.splice(old_position, 1);
}
}
function jobsRespectConditions(original_job, new_job, conditions) {
var j;
// browsing conditions
for (j = 0; j < conditions.length; j += 1) {
if (defaults.job_rule_conditions[conditions[j]]) {
if (
!defaults.job_rule_conditions[conditions[j]](original_job, new_job)
) {
return false;
}
}
}
return true;
}
function checkJob(job) {
var i, j;
if (job.state === 'ready') {
// browsing rules
for (i = 0; i < shared.job_rules.length; i += 1) {
if (shared.job_rules[i].single) {
// no browse
if (
jobsRespectConditions(
job,
undefined,
shared.job_rules[i].conditions
)
) {
shared.job_rule_actions[shared.job_rules[i].action](
undefined,
job
);
return;
}
} else {
// browsing jobs
for (j = 0; j < shared.jobs.length; j += 1) {
if (shared.jobs[j] !== job) {
if (
jobsRespectConditions(
shared.jobs[j],
job,
shared.job_rules[i].conditions
)
) {
shared.job_rule_actions[shared.job_rules[i].action](
shared.jobs[j],
job
);
return;
}
}
}
}
}
}
}
if (options.job_management !== false) {
shared.job_rules = [{
"code_name": "readers update",
"conditions": [
"sameStorageDescription",
"areReaders",
"sameMethod",
"sameParameters",
"sameOptions"
],
"action": "update"
}, {
"code_name": "writers update",
"conditions": [
"sameStorageDescription",
"areWriters",
"sameMethod",
"sameParameters"
],
"action": "update"
}, {
"code_name": "writers wait",
"conditions": [
"sameStorageDescription",
"areWriters",
"sameDocumentId"
],
"action": "wait"
}];
if (options.clear_job_rules === true) {
shared.job_rules.length = 0;
}
if (Array.isArray(options.job_rules)) {
for (i = 0; i < options.job_rules.length; i += 1) {
addJobRule(deepClone(options.job_rules[i]));
}
}
shared.on('job', checkJob);
}
jio.jobRules = function () {
return deepClone(shared.job_rules);
};
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global setTimeout, Job, createStorage, deepClone, min, restCommandResolver,
restCommandRejecter */
function enableJobExecuter(jio, shared) { // , options) {
// uses 'job', 'jobDone', 'jobFail' and 'jobNotify' events
// emits 'jobRun' and 'jobEnd' events
// listeners
shared.on('job', function (param) {
var storage;
if (param.state === 'ready') {
param.tried += 1;
param.started = new Date();
param.state = 'running';
param.modified = new Date();
shared.emit('jobRun', param);
try {
storage = createStorage(deepClone(param.storage_spec));
} catch (e) {
return param.command.reject(
'internal_storage_error',
'invalid description',
'Check if the storage description respects the ' +
'constraints provided by the storage designer. (' +
e.name + ": " + e.message + ')'
);
}
if (typeof storage[param.method] !== 'function') {
return param.command.reject(
'not_implemented',
'method missing',
'Storage "' + param.storage_spec.type + '", "' +
param.method + '" method is missing.'
);
}
setTimeout(function () {
storage[param.method](
deepClone(param.command),
deepClone(param.kwargs),
deepClone(param.options)
);
});
}
});
shared.on('jobDone', function (param, args) {
if (param.state === 'running') {
param.state = 'done';
param.modified = new Date();
shared.emit('jobEnd', param);
if (param.solver) {
restCommandResolver(param, args);
}
}
});
shared.on('jobFail', function (param, args) {
if (param.state === 'running') {
param.state = 'fail';
param.modified = new Date();
shared.emit('jobEnd', param);
if (param.solver) {
restCommandRejecter(param, args);
}
}
});
shared.on('jobNotify', function (param, args) {
if (param.state === 'running' && param.solver) {
param.solver.notify(args[0]);
}
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global arrayExtend */
function enableJobMaker(jio, shared, options) {
// dependencies
// - param.method
// - param.storage_spec
// - param.kwargs
// - param.options
// uses (Job)
// - param.created date
// - param.modified date
// - param.tried number >= 0
// - param.state string 'ready'
// - param.method string
// - param.storage_spec object
// - param.kwargs object
// - param.options object
// - param.command object
// uses method events
// add emits 'job' events
// the job can emit 'jobDone', 'jobFail' and 'jobNotify'
shared.job_keys = arrayExtend(shared.job_keys || [], [
"created",
"modified",
"tried",
"state",
"method",
"storage_spec",
"kwargs",
"options"
]);
function addCommandToJob(param) {
param.command = {};
param.command.resolve = function () {
shared.emit('jobDone', param, arguments);
};
param.command.success = param.command.resolve;
param.command.reject = function () {
shared.emit('jobFail', param, arguments);
};
param.command.error = param.command.reject;
param.command.notify = function () {
shared.emit('jobNotify', param, arguments);
};
param.command.storage = function () {
return shared.createRestApi.apply(null, arguments);
};
}
// listeners
shared.rest_method_names.forEach(function (method) {
shared.on(method, function (param) {
if (param.solver) {
// params are good
shared.emit('job', param);
}
});
});
shared.on('job', function (param) {
// new or recovered job
param.state = 'ready';
if (typeof param.tried !== 'number' || !isFinite(param.tried)) {
param.tried = 0;
}
if (!param.created) {
param.created = new Date();
}
if (!param.command) {
addCommandToJob(param);
}
param.modified = new Date();
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global arrayExtend, localStorage, Workspace, uniqueJSONStringify, JobQueue,
constants, indexOf */
function enableJobQueue(jio, shared, options) {
// dependencies
// - shared.storage_spec Object
// uses
// - options.workspace Workspace
// - shared.job_keys String Array
// creates
// - shared.storage_spec_str String
// - shared.workspace Workspace
// - shared.job_queue JobQueue
// uses 'job', 'jobRun', 'jobStop', 'jobEnd' events
// emits 'jobEnd' events
if (options.job_management !== false) {
shared.job_keys = arrayExtend(shared.job_keys || [], ["id"]);
if (typeof options.workspace !== 'object') {
shared.workspace = localStorage;
} else {
shared.workspace = new Workspace(options.workspace);
}
if (!shared.storage_spec_str) {
shared.storage_spec_str = uniqueJSONStringify(shared.storage_spec);
}
shared.job_queue = new JobQueue(
shared.workspace,
'jio/jobs/' + shared.storage_spec_str,
shared.job_keys
);
shared.on('job', function (param) {
if (indexOf(param.state, ['fail', 'done']) === -1) {
if (!param.stored) {
shared.job_queue.load();
shared.job_queue.post(param);
shared.job_queue.save();
param.stored = true;
}
}
});
['jobRun', 'jobStop'].forEach(function (event) {
shared.on(event, function (param) {
if (param.stored) {
shared.job_queue.load();
if (param.state === 'done' || param.state === 'fail') {
if (shared.job_queue.remove(param.id)) {
shared.job_queue.save();
delete param.storad;
}
} else {
shared.job_queue.put(param);
shared.job_queue.save();
}
}
});
});
shared.on('jobEnd', function (param) {
if (param.stored) {
shared.job_queue.load();
if (shared.job_queue.remove(param.id)) {
shared.job_queue.save();
}
}
});
}
shared.on('job', function (param) {
if (!param.command.end) {
param.command.end = function () {
shared.emit('jobEnd', param);
};
}
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global setTimeout, methodType */
function enableJobRecovery(jio, shared, options) {
// dependencies
// - JobQueue enabled and before this
// uses
// - shared.job_queue JobQueue
function numberOrDefault(number, default_value) {
return (typeof number === 'number' &&
isFinite(number) ? number : default_value);
}
function recoverJob(param) {
shared.job_queue.remove(param.id);
delete param.id;
if (methodType(param.method) === 'writer' ||
param.state === 'ready' ||
param.state === 'running' ||
param.state === 'waiting') {
shared.job_queue.save();
shared.emit('job', param);
}
}
function jobWaiter(id, modified) {
return function () {
var job;
shared.job_queue.load();
job = shared.job_queue.get(id);
if (job.modified === modified) {
// job not modified, no one takes care of it
recoverJob(job);
}
};
}
var i, job_array, delay, deadline, recovery_delay;
recovery_delay = numberOrDefault(options.recovery_delay, 10000);
if (recovery_delay < 0) {
recovery_delay = 10000;
}
if (options.job_management !== false && options.job_recovery !== false) {
shared.job_queue.load();
job_array = shared.job_queue.asArray();
for (i = 0; i < job_array.length; i += 1) {
delay = numberOrDefault(job_array[i].timeout + recovery_delay,
recovery_delay);
deadline = new Date(job_array[i].modified).getTime() + delay;
if (!isFinite(delay)) {
// 'modified' date is broken
recoverJob(job_array[i]);
} else if (deadline <= Date.now()) {
// deadline reached
recoverJob(job_array[i]);
} else {
// deadline not reached yet
// wait until deadline is reached then check job again
setTimeout(jobWaiter(job_array[i].id, job_array[i].modified),
deadline - Date.now());
}
}
}
}
/*jslint indent: 2, maxlen: 80, sloppy: true, unparam: true */
/*global ReferenceArray */
function enableJobReference(jio, shared, options) {
// creates
// - shared.jobs Object Array
// uses 'job', 'jobEnd' events
shared.jobs = [];
var job_references = new ReferenceArray(shared.jobs);
shared.on('job', function (param) {
job_references.put(param);
});
shared.on('jobEnd', function (param) {
job_references.remove(param);
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global arrayExtend, setTimeout, methodType, min, constants */
function enableJobRetry(jio, shared, options) {
// dependencies
// - param.method
// - param.storage_spec
// - param.kwargs
// - param.options
// - param.command
// uses
// - options.default_writers_max_retry number >= 0 or null
// - options.default_readers_max_retry number >= 0 or null
// - options.default_max_retry number >= 0 or null
// - options.writers_max_retry number >= 0 or null
// - options.readers_max_retry number >= 0 or null
// - options.max_retry number >= 0 or null
// - param.modified date
// - param.tried number >= 0
// - param.max_retry >= 0 or undefined
// - param.state string 'ready' 'waiting'
// - param.method string
// - param.storage_spec object
// - param.kwargs object
// - param.options object
// - param.command object
// uses 'job' and 'jobRetry' events
// emits 'job', 'jobFail' and 'jobStateChange' events
// job can emit 'jobRetry'
shared.job_keys = arrayExtend(shared.job_keys || [], ["max_retry"]);
var writers_max_retry, readers_max_retry, max_retry;
function defaultMaxRetry(param) {
if (methodType(param.method) === 'writers') {
if (max_retry === undefined) {
return writers_max_retry;
}
return max_retry;
}
if (max_retry === undefined) {
return readers_max_retry;
}
return max_retry;
}
function positiveNumberOrDefault(number, default_value) {
return (typeof number === 'number' &&
number >= 0 ?
number : default_value);
}
function positiveNumberNullOrDefault(number, default_value) {
return ((typeof number === 'number' &&
number >= 0) || number === null ?
number : default_value);
}
max_retry = positiveNumberNullOrDefault(
options.max_retry || options.default_max_retry,
undefined
);
writers_max_retry = positiveNumberNullOrDefault(
options.writers_max_retry || options.default_writers_max_retry,
null
);
readers_max_retry = positiveNumberNullOrDefault(
options.readers_max_retry || options.default_readers_max_retry,
2
);
// listeners
shared.on('job', function (param) {
if (typeof param.max_retry !== 'number' || param.max_retry < 0) {
param.max_retry = positiveNumberOrDefault(
param.options.max_retry,
defaultMaxRetry(param)
);
}
param.command.reject = function (status) {
if (constants.http_action[status || 0] === "retry") {
shared.emit('jobRetry', param, arguments);
} else {
shared.emit('jobFail', param, arguments);
}
};
param.command.retry = function () {
shared.emit('jobRetry', param, arguments);
};
});
shared.on('jobRetry', function (param, args) {
if (param.state === 'running') {
if (param.max_retry === undefined ||
param.max_retry === null ||
param.max_retry >= param.tried) {
param.state = 'waiting';
param.modified = new Date();
shared.emit('jobStop', param);
setTimeout(function () {
param.state = 'ready';
param.modified = new Date();
shared.emit('job', param);
}, min(10000, param.tried * 2000));
} else {
shared.emit('jobFail', param, args);
}
}
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global arrayExtend, setTimeout, clearTimeout */
function enableJobTimeout(jio, shared, options) {
// dependencies
// - param.tried number > 0
// - param.state string 'running'
// uses
// - param.tried number > 0
// - param.timeout number >= 0
// - param.timeout_ident Timeout
// - param.state string 'running'
// uses 'job', 'jobDone', 'jobFail', 'jobRetry' and 'jobNotify' events
shared.job_keys = arrayExtend(shared.job_keys || [], ["timeout"]);
function positiveNumberOrDefault(number, default_value) {
return (typeof number === 'number' &&
number >= 0 ?
number : default_value);
}
// 10 seconds by default
var default_timeout = positiveNumberOrDefault(options.default_timeout, 10000);
function timeoutReject(param) {
return function () {
param.command.reject(
'request_timeout',
'timeout',
'Operation canceled after around ' + (
Date.now() - param.modified.getTime()
) + ' milliseconds of inactivity.'
);
};
}
// listeners
shared.on('job', function (param) {
if (typeof param.timeout !== 'number' || param.timeout < 0) {
param.timeout = positiveNumberOrDefault(
param.options.timeout,
default_timeout
);
}
param.modified = new Date();
});
["jobDone", "jobFail", "jobRetry"].forEach(function (event) {
shared.on(event, function (param) {
clearTimeout(param.timeout_ident);
delete param.timeout_ident;
});
});
["jobRun", "jobNotify", "jobEnd"].forEach(function (event) {
shared.on(event, function (param) {
clearTimeout(param.timeout_ident);
if (param.state === 'running' && param.timeout > 0) {
param.timeout_ident = setTimeout(timeoutReject(param), param.timeout);
param.modified = new Date();
} else {
delete param.timeout_ident;
}
});
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global arrayValuesToTypeDict, dictClear, RSVP, deepClone */
// adds methods to JIO
// - post
// - put
// - get
// - remove
// - allDocs
// - putAttachment
// - getAttachment
// - removeAttachment
// - check
// - repair
// event shared objet
// - storage_spec object
// - method string
// - kwargs object
// - options object
// - solver object
// - solver.resolve function
// - solver.reject function
// - solver.notify function
// - cancellers object
// - promise object
function enableRestAPI(jio, shared) { // (jio, shared, options)
shared.rest_method_names = [
"post",
"put",
"get",
"remove",
"allDocs",
"putAttachment",
"getAttachment",
"removeAttachment",
"check",
"repair"
];
function prepareParamAndEmit(method, storage_spec, args) {
var callback, type_dict, param = {};
type_dict = arrayValuesToTypeDict(Array.prototype.slice.call(args));
type_dict.object = type_dict.object || [];
if (method !== 'allDocs') {
param.kwargs = type_dict.object.shift();
if (param.kwargs === undefined) {
throw new TypeError("JIO()." + method +
"(): Argument 1 is not of type 'object'");
}
param.kwargs = deepClone(param.kwargs);
} else {
param.kwargs = {};
}
param.solver = {};
param.options = deepClone(type_dict.object.shift()) || {};
param.promise = new RSVP.Promise(function (resolve, reject, notify) {
param.solver.resolve = resolve;
param.solver.reject = reject;
param.solver.notify = notify;
}, function () {
var k;
for (k in param.cancellers) {
if (param.cancellers.hasOwnProperty(k)) {
param.cancellers[k]();
}
}
});
type_dict['function'] = type_dict['function'] || [];
if (type_dict['function'].length === 1) {
callback = type_dict['function'][0];
param.promise.then(function (answer) {
callback(undefined, answer);
}, function (answer) {
callback(answer, undefined);
});
} else if (type_dict['function'].length > 1) {
param.promise.then(type_dict['function'][0],
type_dict['function'][1],
type_dict['function'][2]);
}
type_dict = dictClear(type_dict);
param.storage_spec = storage_spec;
param.method = method;
shared.emit(method, param);
return param.promise;
}
shared.createRestApi = function (storage_spec, that) {
if (that === undefined) {
that = {};
}
shared.rest_method_names.forEach(function (method) {
that[method] = function () {
return prepareParamAndEmit(method, storage_spec, arguments);
};
});
return that;
};
shared.createRestApi(shared.storage_spec, jio);
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global Blob, restCommandRejecter, Metadata */
function enableRestParamChecker(jio, shared) {
// dependencies
// - param.solver
// - param.kwargs
// checks the kwargs and convert value if necessary
// which is a dict of method to use to announce that
// the command is finished
// tools
function checkId(param) {
if (typeof param.kwargs._id !== 'string' || param.kwargs._id === '') {
restCommandRejecter(param, [
'bad_request',
'wrong document id',
'Document id must be a non empty string.'
]);
delete param.solver;
return false;
}
return true;
}
function checkAttachmentId(param) {
if (typeof param.kwargs._attachment !== 'string' ||
param.kwargs._attachment === '') {
restCommandRejecter(param, [
'bad_request',
'wrong attachment id',
'Attachment id must be a non empty string.'
]);
delete param.solver;
return false;
}
return true;
}
// listeners
shared.on('post', function (param) {
if (param.kwargs._id !== undefined) {
if (!checkId(param)) {
return;
}
}
new Metadata(param.kwargs).format();
});
["put", "get", "remove"].forEach(function (method) {
shared.on(method, function (param) {
if (!checkId(param)) {
return;
}
new Metadata(param.kwargs).format();
});
});
shared.on('putAttachment', function (param) {
if (!checkId(param) || !checkAttachmentId(param)) {
return;
}
if (!(param.kwargs._blob instanceof Blob) &&
typeof param.kwargs._data === 'string') {
param.kwargs._blob = new Blob([param.kwargs._data], {
"type": param.kwargs._content_type || param.kwargs._mimetype || ""
});
delete param.kwargs._data;
delete param.kwargs._mimetype;
delete param.kwargs._content_type;
} else if (param.kwargs._blob instanceof Blob) {
delete param.kwargs._data;
delete param.kwargs._mimetype;
delete param.kwargs._content_type;
} else if (param.kwargs._data instanceof Blob) {
param.kwargs._blob = param.kwargs._data;
delete param.kwargs._data;
delete param.kwargs._mimetype;
delete param.kwargs._content_type;
} else {
restCommandRejecter(param, [
'bad_request',
'wrong attachment',
'Attachment information must be like {"_id": document id, ' +
'"_attachment": attachment name, "_data": string, ["_mimetype": ' +
'content type]} or {"_id": document id, "_attachment": ' +
'attachment name, "_blob": Blob}'
]);
delete param.solver;
}
});
["getAttachment", "removeAttachment"].forEach(function (method) {
shared.on(method, function (param) {
if (!checkId(param)) {
checkAttachmentId(param);
}
});
});
["check", "repair"].forEach(function (method) {
shared.on(method, function (param) {
if (param.kwargs._id !== undefined) {
if (!checkId(param)) {
return;
}
}
});
});
}
}));
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