Merge branch 'master' of https://github.com/nasa/openmctweb into search
Conflicts: platform/commonUI/general/src/controllers/TreeNodeController.js platform/persistence/elastic/src/ElasticSearchProvider.js
This commit is contained in:
@@ -21,6 +21,11 @@
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
/**
|
||||
* This bundle decorates the persistence service to maintain a local cache
|
||||
* of persisted documents.
|
||||
* @namespace platform/persistence/cache
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
@@ -31,72 +36,19 @@ define(
|
||||
* that have been loaded, and keeps them in sync after writes. This allows
|
||||
* retrievals to occur more quickly after the first load.
|
||||
*
|
||||
* @memberof platform/persistence/cache
|
||||
* @constructor
|
||||
* @param {string[]} CACHE_SPACES persistence space names which
|
||||
* @param {string[]} cacheSpaces persistence space names which
|
||||
* should be cached
|
||||
* @param {PersistenceService} persistenceService the service which
|
||||
* implements object persistence, whose inputs/outputs
|
||||
* should be cached.
|
||||
* @implements {PersistenceService}
|
||||
*/
|
||||
function CachingPersistenceDecorator(CACHE_SPACES, persistenceService) {
|
||||
var spaces = CACHE_SPACES || [], // List of spaces to cache
|
||||
function CachingPersistenceDecorator(cacheSpaces, persistenceService) {
|
||||
var spaces = cacheSpaces || [], // List of spaces to cache
|
||||
cache = {}; // Where objects will be stored
|
||||
|
||||
// Update the cached instance of an object to a new value
|
||||
function replaceValue(valueHolder, newValue) {
|
||||
var v = valueHolder.value;
|
||||
|
||||
// If it's a JS object, we want to replace contents, so that
|
||||
// everybody gets the same instance.
|
||||
if (typeof v === 'object' && v !== null) {
|
||||
// Only update contents if these are different instances
|
||||
if (v !== newValue) {
|
||||
// Clear prior contents
|
||||
Object.keys(v).forEach(function (k) {
|
||||
delete v[k];
|
||||
});
|
||||
// Shallow-copy contents
|
||||
Object.keys(newValue).forEach(function (k) {
|
||||
v[k] = newValue[k];
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Otherwise, just store the new value
|
||||
valueHolder.value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Place value in the cache for space, if there is one.
|
||||
function addToCache(space, key, value) {
|
||||
if (cache[space]) {
|
||||
if (cache[space][key]) {
|
||||
replaceValue(cache[space][key], value);
|
||||
} else {
|
||||
cache[space][key] = { value: value };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a function for putting value into a cache;
|
||||
// useful for then-chaining.
|
||||
function putCache(space, key) {
|
||||
return function (value) {
|
||||
addToCache(space, key, value);
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
// Wrap as a thenable; used instead of $q.when because that
|
||||
// will resolve on a future tick, which can cause latency
|
||||
// issues (which this decorator is intended to address.)
|
||||
function fastPromise(value) {
|
||||
return {
|
||||
then: function (callback) {
|
||||
return fastPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Arrayify list of spaces to cache, if necessary.
|
||||
spaces = Array.isArray(spaces) ? spaces : [ spaces ];
|
||||
|
||||
@@ -105,97 +57,107 @@ define(
|
||||
cache[space] = {};
|
||||
});
|
||||
|
||||
// Provide PersistenceService interface; mostly delegate to the
|
||||
// decorated service, intervene and cache where appropriate.
|
||||
this.spaces = spaces;
|
||||
this.cache = cache;
|
||||
this.persistenceService = persistenceService;
|
||||
}
|
||||
|
||||
// Wrap as a thenable; used instead of $q.when because that
|
||||
// will resolve on a future tick, which can cause latency
|
||||
// issues (which this decorator is intended to address.)
|
||||
function fastPromise(value) {
|
||||
return {
|
||||
/**
|
||||
* List all persistence spaces that are supported by the
|
||||
* decorated service.
|
||||
* @memberof CachingPersistenceDecorator#
|
||||
* @returns {Promise.<string[]>} spaces supported
|
||||
*/
|
||||
listSpaces: function () {
|
||||
return persistenceService.listSpaces();
|
||||
},
|
||||
/**
|
||||
* List all objects in a specific space.
|
||||
* @memberof CachingPersistenceDecorator#
|
||||
* @param {string} space the space in which to list objects
|
||||
* @returns {Promise.<string[]>} keys for objects in this space
|
||||
*/
|
||||
listObjects: function (space) {
|
||||
return persistenceService.listObjects(space);
|
||||
},
|
||||
/**
|
||||
* Create an object in a specific space. This will
|
||||
* be cached to expedite subsequent retrieval.
|
||||
* @memberof CachingPersistenceDecorator#
|
||||
* @param {string} space the space in which to create the object
|
||||
* @param {string} key the key associate with the object for
|
||||
* subsequent lookup
|
||||
* @param {object} value a JSONifiable object to store
|
||||
* @returns {Promise.<boolean>} an indicator of the success or
|
||||
* failure of this request
|
||||
*/
|
||||
createObject: function (space, key, value) {
|
||||
addToCache(space, key, value);
|
||||
return persistenceService.createObject(space, key, value);
|
||||
},
|
||||
/**
|
||||
* Read an object from a specific space. This will read from a
|
||||
* cache if the object is available.
|
||||
* @memberof CachingPersistenceDecorator#
|
||||
* @param {string} space the space in which to create the object
|
||||
* @param {string} key the key which identifies the object
|
||||
* @returns {Promise.<object>} a promise for the object; may
|
||||
* resolve to undefined (if the object does not exist
|
||||
* in this space)
|
||||
*/
|
||||
readObject: function (space, key) {
|
||||
return (cache[space] && cache[space][key]) ?
|
||||
fastPromise(cache[space][key].value) :
|
||||
persistenceService.readObject(space, key)
|
||||
.then(putCache(space, key));
|
||||
},
|
||||
/**
|
||||
* Update an object in a specific space. This will
|
||||
* be cached to expedite subsequent retrieval.
|
||||
* @memberof CachingPersistenceDecorator#
|
||||
* @param {string} space the space in which to create the object
|
||||
* @param {string} key the key associate with the object for
|
||||
* subsequent lookup
|
||||
* @param {object} value a JSONifiable object to store
|
||||
* @returns {Promise.<boolean>} an indicator of the success or
|
||||
* failure of this request
|
||||
*/
|
||||
updateObject: function (space, key, value) {
|
||||
return persistenceService.updateObject(space, key, value)
|
||||
.then(function (result) {
|
||||
addToCache(space, key, value);
|
||||
return result;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Delete an object in a specific space. This will
|
||||
* additionally be cleared from the cache.
|
||||
* @memberof CachingPersistenceDecorator#
|
||||
* @param {string} space the space in which to create the object
|
||||
* @param {string} key the key associate with the object for
|
||||
* subsequent lookup
|
||||
* @param {object} value a JSONifiable object to delete
|
||||
* @returns {Promise.<boolean>} an indicator of the success or
|
||||
* failure of this request
|
||||
*/
|
||||
deleteObject: function (space, key, value) {
|
||||
if (cache[space]) {
|
||||
delete cache[space][key];
|
||||
}
|
||||
return persistenceService.deleteObject(space, key, value);
|
||||
then: function (callback) {
|
||||
return fastPromise(callback(value));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Update the cached instance of an object to a new value
|
||||
function replaceValue(valueHolder, newValue) {
|
||||
var v = valueHolder.value;
|
||||
|
||||
// If it's a JS object, we want to replace contents, so that
|
||||
// everybody gets the same instance.
|
||||
if (typeof v === 'object' && v !== null) {
|
||||
// Only update contents if these are different instances
|
||||
if (v !== newValue) {
|
||||
// Clear prior contents
|
||||
Object.keys(v).forEach(function (k) {
|
||||
delete v[k];
|
||||
});
|
||||
// Shallow-copy contents
|
||||
Object.keys(newValue).forEach(function (k) {
|
||||
v[k] = newValue[k];
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Otherwise, just store the new value
|
||||
valueHolder.value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Place value in the cache for space, if there is one.
|
||||
CachingPersistenceDecorator.prototype.addToCache = function (space, key, value) {
|
||||
var cache = this.cache;
|
||||
if (cache[space]) {
|
||||
if (cache[space][key]) {
|
||||
replaceValue(cache[space][key], value);
|
||||
} else {
|
||||
cache[space][key] = { value: value };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a function for putting value into a cache;
|
||||
// useful for then-chaining.
|
||||
CachingPersistenceDecorator.prototype.putCache = function (space, key) {
|
||||
var self = this;
|
||||
return function (value) {
|
||||
self.addToCache(space, key, value);
|
||||
return value;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
CachingPersistenceDecorator.prototype.listSpaces = function () {
|
||||
return this.persistenceService.listSpaces();
|
||||
};
|
||||
|
||||
CachingPersistenceDecorator.prototype.listObjects = function (space) {
|
||||
return this.persistenceService.listObjects(space);
|
||||
};
|
||||
|
||||
CachingPersistenceDecorator.prototype.createObject = function (space, key, value) {
|
||||
this.addToCache(space, key, value);
|
||||
return this.persistenceService.createObject(space, key, value);
|
||||
};
|
||||
|
||||
CachingPersistenceDecorator.prototype.readObject = function (space, key) {
|
||||
var cache = this.cache;
|
||||
return (cache[space] && cache[space][key]) ?
|
||||
fastPromise(cache[space][key].value) :
|
||||
this.persistenceService.readObject(space, key)
|
||||
.then(this.putCache(space, key));
|
||||
};
|
||||
|
||||
CachingPersistenceDecorator.prototype.updateObject = function (space, key, value) {
|
||||
var self = this;
|
||||
return this.persistenceService.updateObject(space, key, value)
|
||||
.then(function (result) {
|
||||
self.addToCache(space, key, value);
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
CachingPersistenceDecorator.prototype.deleteObject = function (space, key, value) {
|
||||
if (this.cache[space]) {
|
||||
delete this.cache[space][key];
|
||||
}
|
||||
return this.persistenceService.deleteObject(space, key, value);
|
||||
};
|
||||
|
||||
return CachingPersistenceDecorator;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -33,6 +33,7 @@ define(
|
||||
* metadata field which contains a subset of information found
|
||||
* in the model itself (to support search optimization with
|
||||
* CouchDB views.)
|
||||
* @memberof platform/persistence/couch
|
||||
* @constructor
|
||||
* @param {string} id the id under which to store this mode
|
||||
* @param {object} model the model to store
|
||||
@@ -59,4 +60,4 @@ define(
|
||||
|
||||
return CouchDocument;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -55,70 +55,64 @@ define(
|
||||
* Indicator for the current CouchDB connection. Polls CouchDB
|
||||
* at a regular interval (defined by bundle constants) to ensure
|
||||
* that the database is available.
|
||||
* @constructor
|
||||
* @memberof platform/persistence/couch
|
||||
* @implements {Indicator}
|
||||
* @param $http Angular's $http service
|
||||
* @param $interval Angular's $interval service
|
||||
* @param {string} path the URL to poll to check for couch availability
|
||||
* @param {number} interval the interval, in milliseconds, to poll at
|
||||
*/
|
||||
function CouchIndicator($http, $interval, PATH, INTERVAL) {
|
||||
function CouchIndicator($http, $interval, path, interval) {
|
||||
var self = this;
|
||||
|
||||
// Track the current connection state
|
||||
var state = PENDING;
|
||||
this.state = PENDING;
|
||||
|
||||
this.$http = $http;
|
||||
this.$interval = $interval;
|
||||
this.path = path;
|
||||
this.interval = interval;
|
||||
|
||||
|
||||
// Callback if the HTTP request to Couch fails
|
||||
function handleError(err) {
|
||||
state = DISCONNECTED;
|
||||
self.state = DISCONNECTED;
|
||||
}
|
||||
|
||||
// Callback if the HTTP request succeeds. CouchDB may
|
||||
// report an error, so check for that.
|
||||
function handleResponse(response) {
|
||||
var data = response.data;
|
||||
state = data.error ? SEMICONNECTED : CONNECTED;
|
||||
self.state = data.error ? SEMICONNECTED : CONNECTED;
|
||||
}
|
||||
|
||||
// Try to connect to CouchDB, and update the indicator.
|
||||
function updateIndicator() {
|
||||
$http.get(PATH).then(handleResponse, handleError);
|
||||
$http.get(path).then(handleResponse, handleError);
|
||||
}
|
||||
|
||||
// Update the indicator initially, and start polling.
|
||||
updateIndicator();
|
||||
$interval(updateIndicator, INTERVAL);
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get the glyph (single character used as an icon)
|
||||
* to display in this indicator. This will return "D",
|
||||
* which should appear as a database icon.
|
||||
* @returns {string} the character of the database icon
|
||||
*/
|
||||
getGlyph: function () {
|
||||
return "D";
|
||||
},
|
||||
/**
|
||||
* Get the name of the CSS class to apply to the glyph.
|
||||
* This is used to color the glyph to match its
|
||||
* state (one of ok, caution or err)
|
||||
* @returns {string} the CSS class to apply to this glyph
|
||||
*/
|
||||
getGlyphClass: function () {
|
||||
return state.glyphClass;
|
||||
},
|
||||
/**
|
||||
* Get the text that should appear in the indicator.
|
||||
* @returns {string} brief summary of connection status
|
||||
*/
|
||||
getText: function () {
|
||||
return state.text;
|
||||
},
|
||||
/**
|
||||
* Get a longer-form description of the current connection
|
||||
* space, suitable for display in a tooltip
|
||||
* @returns {string} longer summary of connection status
|
||||
*/
|
||||
getDescription: function () {
|
||||
return state.description;
|
||||
}
|
||||
};
|
||||
|
||||
$interval(updateIndicator, interval);
|
||||
}
|
||||
|
||||
CouchIndicator.prototype.getGlyph = function () {
|
||||
return "D";
|
||||
};
|
||||
|
||||
CouchIndicator.prototype.getGlyphClass = function () {
|
||||
return this.state.glyphClass;
|
||||
};
|
||||
|
||||
CouchIndicator.prototype.getText = function () {
|
||||
return this.state.text;
|
||||
};
|
||||
|
||||
CouchIndicator.prototype.getDescription = function () {
|
||||
return this.state.description;
|
||||
};
|
||||
|
||||
return CouchIndicator;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
/**
|
||||
* This bundle implements a persistence service which uses CouchDB to
|
||||
* store documents.
|
||||
* @namespace platform/persistence/cache
|
||||
*/
|
||||
define(
|
||||
["./CouchDocument"],
|
||||
function (CouchDocument) {
|
||||
@@ -35,149 +40,110 @@ define(
|
||||
* The CouchPersistenceProvider reads and writes JSON documents
|
||||
* (more specifically, domain object models) to/from a CouchDB
|
||||
* instance.
|
||||
* @memberof platform/persistence/couch
|
||||
* @constructor
|
||||
* @implements {PersistenceService}
|
||||
* @param $http Angular's $http service
|
||||
* @param $interval Angular's $interval service
|
||||
* @param {string} space the name of the persistence space being served
|
||||
* @param {string} path the path to the CouchDB instance
|
||||
*/
|
||||
function CouchPersistenceProvider($http, $q, SPACE, PATH) {
|
||||
var spaces = [ SPACE ],
|
||||
revs = {};
|
||||
|
||||
// Convert a subpath to a full path, suitable to pass
|
||||
// to $http.
|
||||
function url(subpath) {
|
||||
return PATH + '/' + subpath;
|
||||
}
|
||||
|
||||
// Issue a request using $http; get back the plain JS object
|
||||
// from the expected JSON response
|
||||
function request(subpath, method, value) {
|
||||
return $http({
|
||||
method: method,
|
||||
url: url(subpath),
|
||||
data: value
|
||||
}).then(function (response) {
|
||||
return response.data;
|
||||
}, function () {
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
// Shorthand methods for GET/PUT methods
|
||||
function get(subpath) {
|
||||
return request(subpath, "GET");
|
||||
}
|
||||
function put(subpath, value) {
|
||||
return request(subpath, "PUT", value);
|
||||
}
|
||||
|
||||
// Pull out a list of document IDs from CouchDB's
|
||||
// _all_docs response
|
||||
function getIdsFromAllDocs(allDocs) {
|
||||
return allDocs.rows.map(function (r) { return r.id; });
|
||||
}
|
||||
|
||||
// Get a domain object model out of CouchDB's response
|
||||
function getModel(response) {
|
||||
if (response && response.model) {
|
||||
revs[response[ID]] = response[REV];
|
||||
return response.model;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the response to a create/update/delete request;
|
||||
// track the rev if it's valid, otherwise return false to
|
||||
// indicate that the request failed.
|
||||
function checkResponse(response) {
|
||||
if (response && response.ok) {
|
||||
revs[response.id] = response.rev;
|
||||
return response.ok;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* List all persistence spaces which this provider
|
||||
* recognizes.
|
||||
*
|
||||
* @returns {Promise.<string[]>} a promise for a list of
|
||||
* spaces supported by this provider
|
||||
*/
|
||||
listSpaces: function () {
|
||||
return $q.when(spaces);
|
||||
},
|
||||
/**
|
||||
* List all objects (by their identifiers) that are stored
|
||||
* in the given persistence space, per this provider.
|
||||
* @param {string} space the space to check
|
||||
* @returns {Promise.<string[]>} a promise for the list of
|
||||
* identifiers
|
||||
*/
|
||||
listObjects: function (space) {
|
||||
return get("_all_docs").then(getIdsFromAllDocs);
|
||||
},
|
||||
/**
|
||||
* Create a new object in the specified persistence space.
|
||||
* @param {string} space the space in which to store the object
|
||||
* @param {string} key the identifier for the persisted object
|
||||
* @param {object} value a JSONifiable object that should be
|
||||
* stored and associated with the provided identifier
|
||||
* @returns {Promise.<boolean>} a promise for an indication
|
||||
* of the success (true) or failure (false) of this
|
||||
* operation
|
||||
*/
|
||||
createObject: function (space, key, value) {
|
||||
return put(key, new CouchDocument(key, value))
|
||||
.then(checkResponse);
|
||||
},
|
||||
|
||||
/**
|
||||
* Read an existing object back from persistence.
|
||||
* @param {string} space the space in which to look for
|
||||
* the object
|
||||
* @param {string} key the identifier for the persisted object
|
||||
* @returns {Promise.<object>} a promise for the stored
|
||||
* object; this will resolve to undefined if no such
|
||||
* object is found.
|
||||
*/
|
||||
readObject: function (space, key) {
|
||||
return get(key).then(getModel);
|
||||
},
|
||||
/**
|
||||
* Update an existing object in the specified persistence space.
|
||||
* @param {string} space the space in which to store the object
|
||||
* @param {string} key the identifier for the persisted object
|
||||
* @param {object} value a JSONifiable object that should be
|
||||
* stored and associated with the provided identifier
|
||||
* @returns {Promise.<boolean>} a promise for an indication
|
||||
* of the success (true) or failure (false) of this
|
||||
* operation
|
||||
*/
|
||||
updateObject: function (space, key, value) {
|
||||
return put(key, new CouchDocument(key, value, revs[key]))
|
||||
.then(checkResponse);
|
||||
},
|
||||
/**
|
||||
* Delete an object in the specified persistence space.
|
||||
* @param {string} space the space from which to delete this
|
||||
* object
|
||||
* @param {string} key the identifier of the persisted object
|
||||
* @param {object} value a JSONifiable object that should be
|
||||
* deleted
|
||||
* @returns {Promise.<boolean>} a promise for an indication
|
||||
* of the success (true) or failure (false) of this
|
||||
* operation
|
||||
*/
|
||||
deleteObject: function (space, key, value) {
|
||||
return put(key, new CouchDocument(key, value, revs[key], true))
|
||||
.then(checkResponse);
|
||||
}
|
||||
};
|
||||
|
||||
function CouchPersistenceProvider($http, $q, space, path) {
|
||||
this.spaces = [ space ];
|
||||
this.revs = {};
|
||||
this.$q = $q;
|
||||
this.$http = $http;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
function bind(fn, thisArg) {
|
||||
return function () {
|
||||
return fn.apply(thisArg, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
// Pull out a list of document IDs from CouchDB's
|
||||
// _all_docs response
|
||||
function getIdsFromAllDocs(allDocs) {
|
||||
return allDocs.rows.map(function (r) { return r.id; });
|
||||
}
|
||||
|
||||
// Check the response to a create/update/delete request;
|
||||
// track the rev if it's valid, otherwise return false to
|
||||
// indicate that the request failed.
|
||||
function checkResponse(response) {
|
||||
if (response && response.ok) {
|
||||
this.revs[response.id] = response.rev;
|
||||
return response.ok;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get a domain object model out of CouchDB's response
|
||||
function getModel(response) {
|
||||
if (response && response.model) {
|
||||
this.revs[response[ID]] = response[REV];
|
||||
return response.model;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Issue a request using $http; get back the plain JS object
|
||||
// from the expected JSON response
|
||||
CouchPersistenceProvider.prototype.request = function (subpath, method, value) {
|
||||
return this.$http({
|
||||
method: method,
|
||||
url: this.path + '/' + subpath,
|
||||
data: value
|
||||
}).then(function (response) {
|
||||
return response.data;
|
||||
}, function () {
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
// Shorthand methods for GET/PUT methods
|
||||
CouchPersistenceProvider.prototype.get = function (subpath) {
|
||||
return this.request(subpath, "GET");
|
||||
};
|
||||
|
||||
CouchPersistenceProvider.prototype.put = function (subpath, value) {
|
||||
return this.request(subpath, "PUT", value);
|
||||
};
|
||||
|
||||
|
||||
CouchPersistenceProvider.prototype.listSpaces = function () {
|
||||
return this.$q.when(this.spaces);
|
||||
};
|
||||
|
||||
CouchPersistenceProvider.prototype.listObjects = function (space) {
|
||||
return this.get("_all_docs").then(bind(getIdsFromAllDocs, this));
|
||||
};
|
||||
|
||||
CouchPersistenceProvider.prototype.createObject = function (space, key, value) {
|
||||
return this.put(key, new CouchDocument(key, value))
|
||||
.then(bind(checkResponse, this));
|
||||
};
|
||||
|
||||
|
||||
CouchPersistenceProvider.prototype.readObject = function (space, key) {
|
||||
return this.get(key).then(bind(getModel, this));
|
||||
};
|
||||
|
||||
CouchPersistenceProvider.prototype.updateObject = function (space, key, value) {
|
||||
var rev = this.revs[key];
|
||||
return this.put(key, new CouchDocument(key, value, rev))
|
||||
.then(bind(checkResponse, this));
|
||||
};
|
||||
|
||||
CouchPersistenceProvider.prototype.deleteObject = function (space, key, value) {
|
||||
var rev = this.revs[key];
|
||||
return this.put(key, new CouchDocument(key, value, rev, true))
|
||||
.then(bind(checkResponse, this));
|
||||
};
|
||||
|
||||
return CouchPersistenceProvider;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Couch Persistence",
|
||||
"description": "Adapter to read and write objects using a CouchDB instance.",
|
||||
"name": "ElasticSearch Persistence",
|
||||
"description": "Adapter to read and write objects using an ElasticSearch instance.",
|
||||
"extensions": {
|
||||
"components": [
|
||||
{
|
||||
|
||||
@@ -46,71 +46,56 @@ define(
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicator for the current CouchDB connection. Polls CouchDB
|
||||
* at a regular interval (defined by bundle constants) to ensure
|
||||
* that the database is available.
|
||||
* Indicator for the current ElasticSearch connection. Polls
|
||||
* ElasticSearch at a regular interval (defined by bundle constants)
|
||||
* to ensure that the database is available.
|
||||
* @constructor
|
||||
* @memberof platform/persistence/elastic
|
||||
* @implements {Indicator}
|
||||
* @param $http Angular's $http service
|
||||
* @param $interval Angular's $interval service
|
||||
* @param {string} path the URL to poll for elasticsearch availability
|
||||
* @param {number} interval the interval, in milliseconds, to poll at
|
||||
*/
|
||||
function ElasticIndicator($http, $interval, PATH, INTERVAL) {
|
||||
function ElasticIndicator($http, $interval, path, interval) {
|
||||
// Track the current connection state
|
||||
var state = PENDING;
|
||||
var self = this;
|
||||
|
||||
// Callback if the HTTP request to Couch fails
|
||||
function handleError(err) {
|
||||
state = DISCONNECTED;
|
||||
this.state = PENDING;
|
||||
|
||||
// Callback if the HTTP request to ElasticSearch fails
|
||||
function handleError() {
|
||||
self.state = DISCONNECTED;
|
||||
}
|
||||
|
||||
// Callback if the HTTP request succeeds.
|
||||
function handleResponse(response) {
|
||||
state = CONNECTED;
|
||||
function handleResponse() {
|
||||
self.state = CONNECTED;
|
||||
}
|
||||
|
||||
// Try to connect to CouchDB, and update the indicator.
|
||||
// Try to connect to ElasticSearch, and update the indicator.
|
||||
function updateIndicator() {
|
||||
$http.get(PATH).then(handleResponse, handleError);
|
||||
$http.get(path).then(handleResponse, handleError);
|
||||
}
|
||||
|
||||
// Update the indicator initially, and start polling.
|
||||
updateIndicator();
|
||||
$interval(updateIndicator, INTERVAL, false);
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get the glyph (single character used as an icon)
|
||||
* to display in this indicator. This will return "D",
|
||||
* which should appear as a database icon.
|
||||
* @returns {string} the character of the database icon
|
||||
*/
|
||||
getGlyph: function () {
|
||||
return "D";
|
||||
},
|
||||
/**
|
||||
* Get the name of the CSS class to apply to the glyph.
|
||||
* This is used to color the glyph to match its
|
||||
* state (one of ok, caution or err)
|
||||
* @returns {string} the CSS class to apply to this glyph
|
||||
*/
|
||||
getGlyphClass: function () {
|
||||
return state.glyphClass;
|
||||
},
|
||||
/**
|
||||
* Get the text that should appear in the indicator.
|
||||
* @returns {string} brief summary of connection status
|
||||
*/
|
||||
getText: function () {
|
||||
return state.text;
|
||||
},
|
||||
/**
|
||||
* Get a longer-form description of the current connection
|
||||
* space, suitable for display in a tooltip
|
||||
* @returns {string} longer summary of connection status
|
||||
*/
|
||||
getDescription: function () {
|
||||
return state.description;
|
||||
}
|
||||
};
|
||||
|
||||
$interval(updateIndicator, interval, false);
|
||||
}
|
||||
|
||||
ElasticIndicator.prototype.getGlyph = function () {
|
||||
return "D";
|
||||
};
|
||||
ElasticIndicator.prototype.getGlyphClass = function () {
|
||||
return this.state.glyphClass;
|
||||
};
|
||||
ElasticIndicator.prototype.getText = function () {
|
||||
return this.state.text;
|
||||
};
|
||||
ElasticIndicator.prototype.getDescription = function () {
|
||||
return this.state.description;
|
||||
};
|
||||
|
||||
return ElasticIndicator;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
/**
|
||||
* This bundle implements a persistence service which uses ElasticSearch to
|
||||
* store documents.
|
||||
* @namespace platform/persistence/elastic
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
@@ -37,164 +42,126 @@ define(
|
||||
* The ElasticPersistenceProvider reads and writes JSON documents
|
||||
* (more specifically, domain object models) to/from an ElasticSearch
|
||||
* instance.
|
||||
* @memberof platform/persistence/elastic
|
||||
* @constructor
|
||||
* @implements {PersistenceService}
|
||||
* @param $http Angular's $http service
|
||||
* @param $interval Angular's $interval service
|
||||
* @param {string} space the name of the persistence space being served
|
||||
* @param {string} root the root of the path to ElasticSearch
|
||||
* @param {stirng} path the path to domain objects within ElasticSearch
|
||||
*/
|
||||
function ElasticPersistenceProvider($http, $q, SPACE, ROOT, PATH) {
|
||||
var spaces = [ SPACE ],
|
||||
revs = {};
|
||||
function ElasticPersistenceProvider($http, $q, space, root, path) {
|
||||
this.spaces = [ space ];
|
||||
this.revs = {};
|
||||
this.$http = $http;
|
||||
this.$q = $q;
|
||||
this.root = root;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
// Convert a subpath to a full path, suitable to pass
|
||||
// to $http.
|
||||
function url(subpath) {
|
||||
return ROOT + '/' + PATH + '/' + subpath;
|
||||
}
|
||||
function bind(fn, thisArg) {
|
||||
return function () {
|
||||
return fn.apply(thisArg, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
// Issue a request using $http; get back the plain JS object
|
||||
// from the expected JSON response
|
||||
function request(subpath, method, value, params) {
|
||||
return $http({
|
||||
method: method,
|
||||
url: url(subpath),
|
||||
params: params,
|
||||
data: value
|
||||
}).then(function (response) {
|
||||
return response.data;
|
||||
}, function (response) {
|
||||
return (response || {}).data;
|
||||
// Issue a request using $http; get back the plain JS object
|
||||
// from the expected JSON response
|
||||
ElasticPersistenceProvider.prototype.request = function (subpath, method, value, params) {
|
||||
return this.$http({
|
||||
method: method,
|
||||
url: this.root + '/' + this.path + '/' + subpath,
|
||||
params: params,
|
||||
data: value
|
||||
}).then(function (response) {
|
||||
return response.data;
|
||||
}, function (response) {
|
||||
return (response || {}).data;
|
||||
});
|
||||
};
|
||||
|
||||
// Shorthand methods for GET/PUT methods
|
||||
ElasticPersistenceProvider.prototype.get = function (subpath) {
|
||||
return this.request(subpath, "GET");
|
||||
};
|
||||
ElasticPersistenceProvider.prototype.put = function (subpath, value, params) {
|
||||
return this.request(subpath, "PUT", value, params);
|
||||
};
|
||||
ElasticPersistenceProvider.prototype.del = function (subpath) {
|
||||
return this.request(subpath, "DELETE");
|
||||
};
|
||||
|
||||
|
||||
// Handle an update error
|
||||
ElasticPersistenceProvider.prototype.handleError = function (response, key) {
|
||||
var error = new Error("Persistence error."),
|
||||
$q = this.$q;
|
||||
if ((response || {}).status === CONFLICT) {
|
||||
error.key = "revision";
|
||||
// Load the updated model, then reject the promise
|
||||
return this.get(key).then(function (response) {
|
||||
error.model = response[SRC];
|
||||
return $q.reject(error);
|
||||
});
|
||||
}
|
||||
// Reject the promise
|
||||
return this.$q.reject(error);
|
||||
};
|
||||
|
||||
// Shorthand methods for GET/PUT methods
|
||||
function get(subpath) {
|
||||
return request(subpath, "GET");
|
||||
// Get a domain object model out of ElasticSearch's response
|
||||
function getModel(response) {
|
||||
if (response && response[SRC]) {
|
||||
this.revs[response[ID]] = response[REV];
|
||||
return response[SRC];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
function put(subpath, value, params) {
|
||||
return request(subpath, "PUT", value, params);
|
||||
}
|
||||
function del(subpath) {
|
||||
return request(subpath, "DELETE");
|
||||
}
|
||||
|
||||
// Get a domain object model out of CouchDB's response
|
||||
function getModel(response) {
|
||||
if (response && response[SRC]) {
|
||||
revs[response[ID]] = response[REV];
|
||||
return response[SRC];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle an update error
|
||||
function handleError(response, key) {
|
||||
var error = new Error("Persistence error.");
|
||||
if ((response || {}).status === CONFLICT) {
|
||||
error.key = "revision";
|
||||
// Load the updated model, then reject the promise
|
||||
return get(key).then(function (response) {
|
||||
error.model = response[SRC];
|
||||
return $q.reject(error);
|
||||
});
|
||||
}
|
||||
// Reject the promise
|
||||
return $q.reject(error);
|
||||
}
|
||||
|
||||
// Check the response to a create/update/delete request;
|
||||
// track the rev if it's valid, otherwise return false to
|
||||
// indicate that the request failed.
|
||||
function checkResponse(response, key) {
|
||||
var error;
|
||||
if (response && !response.error) {
|
||||
revs[key] = response[REV];
|
||||
return response;
|
||||
} else {
|
||||
return handleError(response, key);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* List all persistence spaces which this provider
|
||||
* recognizes.
|
||||
*
|
||||
* @returns {Promise.<string[]>} a promise for a list of
|
||||
* spaces supported by this provider
|
||||
*/
|
||||
listSpaces: function () {
|
||||
return $q.when(spaces);
|
||||
},
|
||||
/**
|
||||
* List all objects (by their identifiers) that are stored
|
||||
* in the given persistence space, per this provider.
|
||||
* @param {string} space the space to check
|
||||
* @returns {Promise.<string[]>} a promise for the list of
|
||||
* identifiers
|
||||
*/
|
||||
listObjects: function (space) {
|
||||
return $q.when([]);
|
||||
},
|
||||
/**
|
||||
* Create a new object in the specified persistence space.
|
||||
* @param {string} space the space in which to store the object
|
||||
* @param {string} key the identifier for the persisted object
|
||||
* @param {object} value a JSONifiable object that should be
|
||||
* stored and associated with the provided identifier
|
||||
* @returns {Promise.<boolean>} a promise for an indication
|
||||
* of the success (true) or failure (false) of this
|
||||
* operation
|
||||
*/
|
||||
createObject: function (space, key, value) {
|
||||
return put(key, value).then(checkResponse);
|
||||
},
|
||||
|
||||
/**
|
||||
* Read an existing object back from persistence.
|
||||
* @param {string} space the space in which to look for
|
||||
* the object
|
||||
* @param {string} key the identifier for the persisted object
|
||||
* @returns {Promise.<object>} a promise for the stored
|
||||
* object; this will resolve to undefined if no such
|
||||
* object is found.
|
||||
*/
|
||||
readObject: function (space, key) {
|
||||
return get(key).then(getModel);
|
||||
},
|
||||
/**
|
||||
* Update an existing object in the specified persistence space.
|
||||
* @param {string} space the space in which to store the object
|
||||
* @param {string} key the identifier for the persisted object
|
||||
* @param {object} value a JSONifiable object that should be
|
||||
* stored and associated with the provided identifier
|
||||
* @returns {Promise.<boolean>} a promise for an indication
|
||||
* of the success (true) or failure (false) of this
|
||||
* operation
|
||||
*/
|
||||
updateObject: function (space, key, value) {
|
||||
function checkUpdate(response) {
|
||||
return checkResponse(response, key);
|
||||
}
|
||||
return put(key, value, { version: revs[key] })
|
||||
.then(checkUpdate);
|
||||
},
|
||||
/**
|
||||
* Delete an object in the specified persistence space.
|
||||
* @param {string} space the space from which to delete this
|
||||
* object
|
||||
* @param {string} key the identifier of the persisted object
|
||||
* @param {object} value a JSONifiable object that should be
|
||||
* deleted
|
||||
* @returns {Promise.<boolean>} a promise for an indication
|
||||
* of the success (true) or failure (false) of this
|
||||
* operation
|
||||
*/
|
||||
deleteObject: function (space, key, value) {
|
||||
return del(key).then(checkResponse);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Check the response to a create/update/delete request;
|
||||
// track the rev if it's valid, otherwise return false to
|
||||
// indicate that the request failed.
|
||||
ElasticPersistenceProvider.prototype.checkResponse = function (response, key) {
|
||||
if (response && !response.error) {
|
||||
this.revs[key] = response[REV];
|
||||
return response;
|
||||
} else {
|
||||
return this.handleError(response, key);
|
||||
}
|
||||
};
|
||||
|
||||
// Public API
|
||||
ElasticPersistenceProvider.prototype.listSpaces = function () {
|
||||
return this.$q.when(this.spaces);
|
||||
};
|
||||
|
||||
ElasticPersistenceProvider.prototype.listObjects = function () {
|
||||
// Not yet implemented
|
||||
return this.$q.when([]);
|
||||
};
|
||||
|
||||
|
||||
ElasticPersistenceProvider.prototype.createObject = function (space, key, value) {
|
||||
return this.put(key, value).then(bind(this.checkResponse, this));
|
||||
};
|
||||
|
||||
ElasticPersistenceProvider.prototype.readObject = function (space, key) {
|
||||
return this.get(key).then(bind(getModel, this));
|
||||
};
|
||||
|
||||
ElasticPersistenceProvider.prototype.updateObject = function (space, key, value) {
|
||||
function checkUpdate(response) {
|
||||
return this.checkResponse(response, key);
|
||||
}
|
||||
return this.put(key, value, { version: this.revs[key] })
|
||||
.then(bind(checkUpdate, this));
|
||||
};
|
||||
|
||||
ElasticPersistenceProvider.prototype.deleteObject = function (space, key, value) {
|
||||
return this.del(key).then(bind(this.checkResponse, this));
|
||||
};
|
||||
|
||||
return ElasticPersistenceProvider;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -43,17 +43,51 @@ define(
|
||||
* @param $http Angular's $http service, for working with urls.
|
||||
* @param {ObjectService} objectService the service from which
|
||||
* domain objects can be gotten.
|
||||
* @param ROOT the constant ELASTIC_ROOT which allows us to
|
||||
* @param ROOT the constant `ELASTIC_ROOT` which allows us to
|
||||
* interact with ElasticSearch.
|
||||
*/
|
||||
function ElasticSearchProvider($http, objectService, ROOT) {
|
||||
this.$http = $http;
|
||||
this.objectService = objectService;
|
||||
this.root = ROOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through the filetree for domain objects using a search
|
||||
* term. This is done through querying elasticsearch. Returns a
|
||||
* promise for a result object that has the format
|
||||
* {hits: searchResult[], total: number, timedOut: boolean}
|
||||
* where a searchResult has the format
|
||||
* {id: string, object: domainObject, score: number}
|
||||
*
|
||||
* Notes:
|
||||
* * The order of the results is from highest to lowest score,
|
||||
* as elsaticsearch determines them to be.
|
||||
* * Uses the fuzziness operator to get more results.
|
||||
* * More about this search's behavior at
|
||||
* https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html
|
||||
*
|
||||
* @param searchTerm The text input that is the query.
|
||||
* @param timestamp The time at which this function was called.
|
||||
* This timestamp is used as a unique identifier for this
|
||||
* query and the corresponding results.
|
||||
* @param maxResults (optional) The maximum number of results
|
||||
* that this function should return.
|
||||
* @param timeout (optional) The time after which the search should
|
||||
* stop calculations and return partial results. Elasticsearch
|
||||
* does not guarentee that this timeout will be strictly followed.
|
||||
*/
|
||||
ElasticSearchProvider.prototype.query = function query(searchTerm, timestamp, maxResults, timeout) {
|
||||
var $http = this.$http,
|
||||
objectService = this.objectService,
|
||||
root = this.root,
|
||||
esQuery;
|
||||
|
||||
// Add the fuzziness operator to the search term
|
||||
function addFuzziness(searchTerm, editDistance) {
|
||||
if (!editDistance) {
|
||||
editDistance = '';
|
||||
}
|
||||
|
||||
|
||||
return searchTerm.split(' ').map(function (s) {
|
||||
// Don't add fuzziness for quoted strings
|
||||
if (s.indexOf('"') !== -1) {
|
||||
@@ -63,11 +97,11 @@ define(
|
||||
}
|
||||
}).join(' ');
|
||||
}
|
||||
|
||||
|
||||
// Currently specific to elasticsearch
|
||||
function processSearchTerm(searchTerm) {
|
||||
var spaceIndex;
|
||||
|
||||
|
||||
// Cut out any extra spaces
|
||||
while (searchTerm.substr(0, 1) === ' ') {
|
||||
searchTerm = searchTerm.substring(1, searchTerm.length);
|
||||
@@ -78,18 +112,18 @@ define(
|
||||
spaceIndex = searchTerm.indexOf(' ');
|
||||
while (spaceIndex !== -1) {
|
||||
searchTerm = searchTerm.substring(0, spaceIndex) +
|
||||
searchTerm.substring(spaceIndex + 1, searchTerm.length);
|
||||
searchTerm.substring(spaceIndex + 1, searchTerm.length);
|
||||
spaceIndex = searchTerm.indexOf(' ');
|
||||
}
|
||||
|
||||
|
||||
// Add fuzziness for completeness
|
||||
searchTerm = addFuzziness(searchTerm);
|
||||
|
||||
|
||||
return searchTerm;
|
||||
}
|
||||
|
||||
// Processes results from the format that elasticsearch returns to
|
||||
// a list of searchResult objects, then returns a result object
|
||||
|
||||
// Processes results from the format that elasticsearch returns to
|
||||
// a list of searchResult objects, then returns a result object
|
||||
// (See documentation for query for object descriptions)
|
||||
function processResults(rawResults, timestamp) {
|
||||
var results = rawResults.data.hits.hits,
|
||||
@@ -98,25 +132,25 @@ define(
|
||||
scores = {},
|
||||
searchResults = [],
|
||||
i;
|
||||
|
||||
|
||||
// Get the result objects' IDs
|
||||
for (i = 0; i < resultsLength; i += 1) {
|
||||
ids.push(results[i][ID]);
|
||||
}
|
||||
|
||||
|
||||
// Get the result objects' scores
|
||||
for (i = 0; i < resultsLength; i += 1) {
|
||||
scores[ids[i]] = results[i][SCORE];
|
||||
}
|
||||
|
||||
|
||||
// Get the domain objects from their IDs
|
||||
return objectService.getObjects(ids).then(function (objects) {
|
||||
var j,
|
||||
id;
|
||||
|
||||
|
||||
for (j = 0; j < resultsLength; j += 1) {
|
||||
id = ids[j];
|
||||
|
||||
|
||||
// Include items we can get models for
|
||||
if (objects[id].getModel) {
|
||||
// Format the results as searchResult objects
|
||||
@@ -127,7 +161,7 @@ define(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
hits: searchResults,
|
||||
total: rawResults.data.hits.total,
|
||||
@@ -135,76 +169,43 @@ define(
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// For documentation, see query below.
|
||||
function query(searchTerm, timestamp, maxResults, timeout) {
|
||||
var esQuery;
|
||||
|
||||
// Check to see if the user provided a maximum
|
||||
// number of results to display
|
||||
if (!maxResults) {
|
||||
// Else, we provide a default value.
|
||||
maxResults = DEFAULT_MAX_RESULTS;
|
||||
}
|
||||
|
||||
// If the user input is empty, we want to have no search results.
|
||||
if (searchTerm !== '' && searchTerm !== undefined) {
|
||||
// Process the search term
|
||||
searchTerm = processSearchTerm(searchTerm);
|
||||
|
||||
// Create the query to elasticsearch
|
||||
esQuery = ROOT + "/_search/?q=" + searchTerm +
|
||||
"&size=" + maxResults;
|
||||
if (timeout) {
|
||||
esQuery += "&timeout=" + timeout;
|
||||
}
|
||||
|
||||
// Get the data...
|
||||
return $http({
|
||||
method: "GET",
|
||||
url: esQuery
|
||||
}).then(function (rawResults) {
|
||||
// ...then process the data
|
||||
return processResults(rawResults, timestamp);
|
||||
}, function (err) {
|
||||
// In case of error, return nothing. (To prevent
|
||||
// infinite loading time.)
|
||||
return {hits: [], total: 0};
|
||||
});
|
||||
} else {
|
||||
return {hits: [], total: 0};
|
||||
}
|
||||
// Check to see if the user provided a maximum
|
||||
// number of results to display
|
||||
if (!maxResults) {
|
||||
// Else, we provide a default value.
|
||||
maxResults = DEFAULT_MAX_RESULTS;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Searches through the filetree for domain objects using a search
|
||||
* term. This is done through querying elasticsearch. Returns a
|
||||
* promise for a result object that has the format
|
||||
* {hits: searchResult[], total: number, timedOut: boolean}
|
||||
* where a searchResult has the format
|
||||
* {id: string, object: domainObject, score: number}
|
||||
*
|
||||
* Notes:
|
||||
* * The order of the results is from highest to lowest score,
|
||||
* as elsaticsearch determines them to be.
|
||||
* * Uses the fuzziness operator to get more results.
|
||||
* * More about this search's behavior at
|
||||
* https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html
|
||||
*
|
||||
* @param searchTerm The text input that is the query.
|
||||
* @param timestamp The time at which this function was called.
|
||||
* This timestamp is used as a unique identifier for this
|
||||
* query and the corresponding results.
|
||||
* @param maxResults (optional) The maximum number of results
|
||||
* that this function should return.
|
||||
* @param timeout (optional) The time after which the search should
|
||||
* stop calculations and return partial results. Elasticsearch
|
||||
* does not guarentee that this timeout will be strictly followed.
|
||||
*/
|
||||
query: query
|
||||
};
|
||||
}
|
||||
|
||||
// If the user input is empty, we want to have no search results.
|
||||
if (searchTerm !== '' && searchTerm !== undefined) {
|
||||
// Process the search term
|
||||
searchTerm = processSearchTerm(searchTerm);
|
||||
|
||||
// Create the query to elasticsearch
|
||||
esQuery = root + "/_search/?q=" + searchTerm +
|
||||
"&size=" + maxResults;
|
||||
if (timeout) {
|
||||
esQuery += "&timeout=" + timeout;
|
||||
}
|
||||
|
||||
// Get the data...
|
||||
return this.$http({
|
||||
method: "GET",
|
||||
url: esQuery
|
||||
}).then(function (rawResults) {
|
||||
// ...then process the data
|
||||
return processResults(rawResults, timestamp);
|
||||
}, function (err) {
|
||||
// In case of error, return nothing. (To prevent
|
||||
// infinite loading time.)
|
||||
return {hits: [], total: 0};
|
||||
});
|
||||
} else {
|
||||
return {hits: [], total: 0};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return ElasticSearchProvider;
|
||||
|
||||
@@ -26,4 +26,4 @@ define({
|
||||
OVERWRITE_KEY: "overwrite",
|
||||
TIMESTAMP_FORMAT: "YYYY-MM-DD HH:mm:ss\\Z",
|
||||
UNKNOWN_USER: "unknown user"
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,25 +29,29 @@ define(
|
||||
/**
|
||||
* Controller to support the template to be shown in the
|
||||
* dialog shown for persistence failures.
|
||||
* @constructor
|
||||
* @memberof platform/persistence/queue
|
||||
*/
|
||||
function PersistenceFailureController() {
|
||||
return {
|
||||
/**
|
||||
* Format a timestamp for display in the dialog.
|
||||
*/
|
||||
formatTimestamp: function (timestamp) {
|
||||
return moment.utc(timestamp)
|
||||
.format(Constants.TIMESTAMP_FORMAT);
|
||||
},
|
||||
/**
|
||||
* Format a user name for display in the dialog.
|
||||
*/
|
||||
formatUsername: function (username) {
|
||||
return username || Constants.UNKNOWN_USER;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a timestamp for display in the dialog.
|
||||
* @memberof platform/persistence/queue.PersistenceFailureController#
|
||||
*/
|
||||
PersistenceFailureController.prototype.formatTimestamp = function (timestamp) {
|
||||
return moment.utc(timestamp)
|
||||
.format(Constants.TIMESTAMP_FORMAT);
|
||||
};
|
||||
|
||||
/**
|
||||
* Format a user name for display in the dialog.
|
||||
* @memberof platform/persistence/queue.PersistenceFailureController#
|
||||
*/
|
||||
PersistenceFailureController.prototype.formatUsername = function (username) {
|
||||
return username || Constants.UNKNOWN_USER;
|
||||
};
|
||||
|
||||
return PersistenceFailureController;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -41,6 +41,8 @@ define(
|
||||
/**
|
||||
* Populates a `dialogModel` to pass to `dialogService.getUserChoise`
|
||||
* in order to choose between Overwrite and Cancel.
|
||||
* @constructor
|
||||
* @memberof platform/persistence/queue
|
||||
*/
|
||||
function PersistenceFailureDialog(failures) {
|
||||
var revisionErrors = [],
|
||||
@@ -72,4 +74,4 @@ define(
|
||||
|
||||
return PersistenceFailureDialog;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -26,7 +26,32 @@ define(
|
||||
function (PersistenceFailureDialog, PersistenceFailureConstants) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Handle failures to persist domain object models.
|
||||
* @param $q Angular's `$q`
|
||||
* @param {DialogService} dialogService the dialog service
|
||||
* @constructor
|
||||
* @memberof platform/persistence/queue
|
||||
*/
|
||||
function PersistenceFailureHandler($q, dialogService) {
|
||||
this.$q = $q;
|
||||
this.dialogService = dialogService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle persistence failures by providing the user with a
|
||||
* dialog summarizing these failures, and giving the option
|
||||
* to overwrite/cancel as appropriate.
|
||||
* @param {Array} failures persistence failures, as prepared
|
||||
* by PersistenceQueueHandler
|
||||
* @memberof platform/persistence/queue.PersistenceFailureHandler#
|
||||
*/
|
||||
PersistenceFailureHandler.prototype.handle = function handleFailures(failures) {
|
||||
// Prepare dialog for display
|
||||
var dialogModel = new PersistenceFailureDialog(failures),
|
||||
revisionErrors = dialogModel.model.revised,
|
||||
$q = this.$q;
|
||||
|
||||
// Refresh revision information for the domain object associated
|
||||
// with this persistence failure
|
||||
function refresh(failure) {
|
||||
@@ -93,39 +118,21 @@ define(
|
||||
return $q.all(failures.map(discard));
|
||||
}
|
||||
|
||||
// Handle failures in persistence
|
||||
function handleFailures(failures) {
|
||||
// Prepare dialog for display
|
||||
var dialogModel = new PersistenceFailureDialog(failures),
|
||||
revisionErrors = dialogModel.model.revised;
|
||||
|
||||
// Handle user input (did they choose to overwrite?)
|
||||
function handleChoice(key) {
|
||||
// If so, try again
|
||||
if (key === PersistenceFailureConstants.OVERWRITE_KEY) {
|
||||
return retry(revisionErrors);
|
||||
} else {
|
||||
return discardAll(revisionErrors);
|
||||
}
|
||||
// Handle user input (did they choose to overwrite?)
|
||||
function handleChoice(key) {
|
||||
// If so, try again
|
||||
if (key === PersistenceFailureConstants.OVERWRITE_KEY) {
|
||||
return retry(revisionErrors);
|
||||
} else {
|
||||
return discardAll(revisionErrors);
|
||||
}
|
||||
|
||||
// Prompt for user input, the overwrite if they said so.
|
||||
return dialogService.getUserChoice(dialogModel)
|
||||
.then(handleChoice, handleChoice);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Handle persistence failures by providing the user with a
|
||||
* dialog summarizing these failures, and giving the option
|
||||
* to overwrite/cancel as appropriate.
|
||||
* @param {Array} failures persistence failures, as prepared
|
||||
* by PersistenceQueueHandler
|
||||
*/
|
||||
handle: handleFailures
|
||||
};
|
||||
}
|
||||
// Prompt for user input, the overwrite if they said so.
|
||||
return this.dialogService.getUserChoice(dialogModel)
|
||||
.then(handleChoice, handleChoice);
|
||||
};
|
||||
|
||||
return PersistenceFailureHandler;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -50,6 +50,8 @@ define(
|
||||
* persistence when the queue is flushed
|
||||
* @param {number} [DELAY] optional; delay in milliseconds between
|
||||
* attempts to flush the queue
|
||||
* @constructor
|
||||
* @memberof platform/persistence/queue
|
||||
*/
|
||||
function PersistenceQueue(
|
||||
$q,
|
||||
@@ -74,4 +76,4 @@ define(
|
||||
|
||||
return PersistenceQueue;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -34,8 +34,29 @@ define(
|
||||
* @param $q Angular's $q, for promises
|
||||
* @param {PersistenceFailureHandler} handler to invoke in the event
|
||||
* that a persistence attempt fails.
|
||||
* @constructor
|
||||
* @memberof platform/persistence/queue
|
||||
*/
|
||||
function PersistenceQueueHandler($q, failureHandler) {
|
||||
this.$q = $q;
|
||||
this.failureHandler = failureHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the persist method on the provided persistence
|
||||
* capabilities.
|
||||
* @param {Object.<string,PersistenceCapability>} persistences
|
||||
* capabilities to invoke, in id->capability pairs.
|
||||
* @param {Object.<string,DomainObject>} domainObjects
|
||||
* associated domain objects, in id->object pairs.
|
||||
* @param {PersistenceQueue} queue the persistence queue,
|
||||
* to requeue as necessary
|
||||
* @memberof platform/persistence/queue.PersistenceQueueHandler#
|
||||
*/
|
||||
PersistenceQueueHandler.prototype.persist = function (persistences, domainObjects, queue) {
|
||||
var ids = Object.keys(persistences),
|
||||
$q = this.$q,
|
||||
failureHandler = this.failureHandler;
|
||||
|
||||
// Handle a group of persistence invocations
|
||||
function persistGroup(ids, persistences, domainObjects, queue) {
|
||||
@@ -79,33 +100,17 @@ define(
|
||||
// Handle any failures from the full operation
|
||||
function handleFailure(value) {
|
||||
return failures.length > 0 ?
|
||||
failureHandler.handle(failures) :
|
||||
value;
|
||||
failureHandler.handle(failures) :
|
||||
value;
|
||||
}
|
||||
|
||||
// Try to persist everything, then handle any failures
|
||||
return $q.all(ids.map(tryPersist)).then(handleFailure);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
/**
|
||||
* Invoke the persist method on the provided persistence
|
||||
* capabilities.
|
||||
* @param {Object.<string,PersistenceCapability>} persistences
|
||||
* capabilities to invoke, in id->capability pairs.
|
||||
* @param {Object.<string,DomainObject>} domainObjects
|
||||
* associated domain objects, in id->object pairs.
|
||||
* @param {PersistenceQueue} queue the persistence queue,
|
||||
* to requeue as necessary
|
||||
*/
|
||||
persist: function (persistences, domainObjects, queue) {
|
||||
var ids = Object.keys(persistences);
|
||||
return persistGroup(ids, persistences, domainObjects, queue);
|
||||
}
|
||||
};
|
||||
}
|
||||
return persistGroup(ids, persistences, domainObjects, queue);
|
||||
};
|
||||
|
||||
return PersistenceQueueHandler;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -41,19 +41,35 @@ define(
|
||||
* persistence when the queue is flushed
|
||||
* @param {number} [DELAY] optional; delay in milliseconds between
|
||||
* attempts to flush the queue
|
||||
* @constructor
|
||||
* @memberof platform/persistence/queue
|
||||
*/
|
||||
function PersistenceQueueImpl($q, $timeout, handler, DELAY) {
|
||||
var self,
|
||||
persistences = {},
|
||||
objects = {},
|
||||
lastObservedSize = 0,
|
||||
pendingTimeout,
|
||||
flushPromise,
|
||||
activeDefer = $q.defer();
|
||||
function PersistenceQueueImpl($q, $timeout, handler, delay) {
|
||||
|
||||
this.persistences = {};
|
||||
this.objects = {};
|
||||
this.lastObservedSize = 0;
|
||||
this.activeDefer = $q.defer();
|
||||
|
||||
// If no delay is provided, use a default
|
||||
this.delay = delay || 0;
|
||||
this.handler = handler;
|
||||
this.$timeout = $timeout;
|
||||
this.$q = $q;
|
||||
}
|
||||
|
||||
// Schedule a flushing of the queue (that is, plan to flush
|
||||
// all objects in the queue)
|
||||
PersistenceQueueImpl.prototype.scheduleFlush = function () {
|
||||
var self = this,
|
||||
$timeout = this.$timeout,
|
||||
$q = this.$q,
|
||||
handler = this.handler;
|
||||
|
||||
// Check if the queue's size has stopped increasing)
|
||||
function quiescent() {
|
||||
return Object.keys(persistences).length === lastObservedSize;
|
||||
return Object.keys(self.persistences).length
|
||||
=== self.lastObservedSize;
|
||||
}
|
||||
|
||||
// Persist all queued objects
|
||||
@@ -62,74 +78,72 @@ define(
|
||||
// this will be replaced with a promise for the next round
|
||||
// of persistence calls, so we want to make sure we clear
|
||||
// the correct one when this flush completes.
|
||||
var flushingDefer = activeDefer;
|
||||
var flushingDefer = self.activeDefer;
|
||||
|
||||
// Clear the active promise for a queue flush
|
||||
function clearFlushPromise(value) {
|
||||
flushPromise = undefined;
|
||||
self.flushPromise = undefined;
|
||||
flushingDefer.resolve(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Persist all queued objects
|
||||
flushPromise = handler.persist(persistences, objects, self)
|
||||
.then(clearFlushPromise, clearFlushPromise);
|
||||
self.flushPromise = handler.persist(
|
||||
self.persistences,
|
||||
self.objects,
|
||||
self
|
||||
).then(clearFlushPromise, clearFlushPromise);
|
||||
|
||||
// Reset queue, etc.
|
||||
persistences = {};
|
||||
objects = {};
|
||||
lastObservedSize = 0;
|
||||
pendingTimeout = undefined;
|
||||
activeDefer = $q.defer();
|
||||
self.persistences = {};
|
||||
self.objects = {};
|
||||
self.lastObservedSize = 0;
|
||||
self.pendingTimeout = undefined;
|
||||
self.activeDefer = $q.defer();
|
||||
}
|
||||
|
||||
// Schedule a flushing of the queue (that is, plan to flush
|
||||
// all objects in the queue)
|
||||
function scheduleFlush() {
|
||||
function maybeFlush() {
|
||||
// Timeout fired, so clear it
|
||||
pendingTimeout = undefined;
|
||||
// Only flush when we've stopped receiving updates
|
||||
(quiescent() ? flush : scheduleFlush)();
|
||||
// Update lastObservedSize to detect quiescence
|
||||
lastObservedSize = Object.keys(persistences).length;
|
||||
}
|
||||
|
||||
// If we are already flushing the queue...
|
||||
if (flushPromise) {
|
||||
// Wait until that's over before considering a flush
|
||||
flushPromise.then(maybeFlush);
|
||||
function maybeFlush() {
|
||||
// Timeout fired, so clear it
|
||||
self.pendingTimeout = undefined;
|
||||
// Only flush when we've stopped receiving updates
|
||||
if (quiescent()) {
|
||||
flush();
|
||||
} else {
|
||||
// Otherwise, schedule a flush on a timeout (to give
|
||||
// a window for other updates to get aggregated)
|
||||
pendingTimeout = pendingTimeout ||
|
||||
$timeout(maybeFlush, DELAY, false);
|
||||
self.scheduleFlush();
|
||||
}
|
||||
|
||||
return activeDefer.promise;
|
||||
// Update lastObservedSize to detect quiescence
|
||||
self.lastObservedSize = Object.keys(self.persistences).length;
|
||||
}
|
||||
|
||||
// If no delay is provided, use a default
|
||||
DELAY = DELAY || 0;
|
||||
// If we are already flushing the queue...
|
||||
if (self.flushPromise) {
|
||||
// Wait until that's over before considering a flush
|
||||
self.flushPromise.then(maybeFlush);
|
||||
} else {
|
||||
// Otherwise, schedule a flush on a timeout (to give
|
||||
// a window for other updates to get aggregated)
|
||||
self.pendingTimeout = self.pendingTimeout ||
|
||||
$timeout(maybeFlush, self.delay, false);
|
||||
}
|
||||
|
||||
self = {
|
||||
/**
|
||||
* Queue persistence of a domain object.
|
||||
* @param {DomainObject} domainObject the domain object
|
||||
* @param {PersistenceCapability} persistence the object's
|
||||
* undecorated persistence capability
|
||||
*/
|
||||
put: function (domainObject, persistence) {
|
||||
var id = domainObject.getId();
|
||||
persistences[id] = persistence;
|
||||
objects[id] = domainObject;
|
||||
return scheduleFlush();
|
||||
}
|
||||
};
|
||||
return self.activeDefer.promise;
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue persistence of a domain object.
|
||||
* @param {DomainObject} domainObject the domain object
|
||||
* @param {PersistenceCapability} persistence the object's
|
||||
* undecorated persistence capability
|
||||
* @returns {Promise} a promise which will resolve upon persistence
|
||||
*/
|
||||
PersistenceQueueImpl.prototype.put = function (domainObject, persistence) {
|
||||
var id = domainObject.getId();
|
||||
this.persistences[id] = persistence;
|
||||
this.objects[id] = domainObject;
|
||||
return this.scheduleFlush();
|
||||
};
|
||||
|
||||
return PersistenceQueueImpl;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -34,6 +34,8 @@ define(
|
||||
* capability
|
||||
* @param {DomainObject} domainObject the domain object which exposes
|
||||
* the capability
|
||||
* @constructor
|
||||
* @memberof platform/persistence/queue
|
||||
*/
|
||||
function QueuingPersistenceCapability(queue, persistence, domainObject) {
|
||||
var queuingPersistence = Object.create(persistence);
|
||||
@@ -48,4 +50,4 @@ define(
|
||||
|
||||
return QueuingPersistenceCapability;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
/*global define,Promise*/
|
||||
|
||||
/**
|
||||
* Module defining CoreCapabilityProvider. Created by vwoeltje on 11/7/14.
|
||||
* This bundle decorates the persistence service to handle persistence
|
||||
* in batches, and to provide notification of persistence errors in batches
|
||||
* as well.
|
||||
* @namespace platform/persistence/queue
|
||||
*/
|
||||
define(
|
||||
['./QueuingPersistenceCapability'],
|
||||
@@ -35,12 +38,23 @@ define(
|
||||
* will be handled in batches (allowing failure notification to
|
||||
* also be presented in batches.)
|
||||
*
|
||||
* @memberof platform/persistence/queue
|
||||
* @constructor
|
||||
* @implements {CapabilityService}
|
||||
* @param {platform/persistence/queue.PersistenceQueue} persistenceQueue
|
||||
* @param {CapabilityService} the decorated capability service
|
||||
*/
|
||||
function QueuingPersistenceCapabilityDecorator(
|
||||
persistenceQueue,
|
||||
capabilityService
|
||||
) {
|
||||
this.persistenceQueue = persistenceQueue;
|
||||
this.capabilityService = capabilityService;
|
||||
}
|
||||
|
||||
QueuingPersistenceCapabilityDecorator.prototype.getCapabilities = function (model) {
|
||||
var capabilityService = this.capabilityService,
|
||||
persistenceQueue = this.persistenceQueue;
|
||||
|
||||
function decoratePersistence(capabilities) {
|
||||
var originalPersistence = capabilities.persistence;
|
||||
@@ -49,8 +63,8 @@ define(
|
||||
// Get/instantiate the original
|
||||
var original =
|
||||
(typeof originalPersistence === 'function') ?
|
||||
originalPersistence(domainObject) :
|
||||
originalPersistence;
|
||||
originalPersistence(domainObject) :
|
||||
originalPersistence;
|
||||
|
||||
// Provide a decorated version
|
||||
return new QueuingPersistenceCapability(
|
||||
@@ -63,35 +77,11 @@ define(
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
function getCapabilities(model) {
|
||||
return decoratePersistence(
|
||||
capabilityService.getCapabilities(model)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get all capabilities associated with a given domain
|
||||
* object.
|
||||
*
|
||||
* This returns a promise for an object containing key-value
|
||||
* pairs, where keys are capability names and values are
|
||||
* either:
|
||||
*
|
||||
* * Capability instances
|
||||
* * Capability constructors (which take a domain object
|
||||
* as their argument.)
|
||||
*
|
||||
*
|
||||
* @param {*} model the object model
|
||||
* @returns {Object.<string,function|Capability>} all
|
||||
* capabilities known to be valid for this model, as
|
||||
* key-value pairs
|
||||
*/
|
||||
getCapabilities: getCapabilities
|
||||
};
|
||||
}
|
||||
return decoratePersistence(
|
||||
capabilityService.getCapabilities(model)
|
||||
);
|
||||
};
|
||||
|
||||
return QueuingPersistenceCapabilityDecorator;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user