Commit 9f24a5dd authored by Pedro Eduardo Trujillo's avatar Pedro Eduardo Trujillo
Browse files

Replantea módulo de entrada/salida de datos

Migra el antiguo módulo MasterStore y el widget QueryStore al nuevo
módulo RestManager con su implementación.
En general se mantiene su funcionamiento, aunque hay algún bug pendiente
de corregir.
Optimiza su funcionamiento, simplificando la cadena de deferreds y la
creación de nuevas instancias tras cada petición.
Se arregla la recepción y emisión de errores, que llevaban rotas
bastante tiempo.
Hace posible la recepción del status de respuesta en los módulos que han
pedido datos.
parent 9bc64aa1
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ define([
	, 'redmic/base/Credentials'
	, 'redmic/modules/base/Selector'
	, 'redmic/modules/components/Sidebar/MainSidebarImpl'
	, 'redmic/modules/store/MasterStore'
	, 'redmic/modules/store/RestManagerImpl'
	, 'redmic/modules/store/QueryStore'
	, 'redmic/modules/notification/Notification'
	, 'redmic/modules/socket/_IngestData'
@@ -29,7 +29,7 @@ define([
	, Credentials
	, Selector
	, MainSidebarImpl
	, MasterStore
	, RestManagerImpl
	, QueryStore
	, Notification
	, _IngestData
@@ -137,7 +137,7 @@ define([
				parentChannel: this.ownChannel
			});

			new MasterStore({
			new RestManagerImpl({
				parentChannel: this.ownChannel
			});

+0 −280
Original line number Diff line number Diff line
define([
	"dojo/_base/declare"
	, "dojo/_base/lang"
	, "dojo/store/Observable"
	, "redmic/store/QueryStore"
	, "redmic/modules/base/_Module"
], function(
	declare
	, lang
	, Observable
	, Store
	, _Module
){
	return declare(_Module, {
		//	summary:
		//		Todo lo necesario para trabajar con master Store.
		//	description:
		//		Proporciona métodos manejar datos de la api.

		//	config: Object
		//		Opciones por defecto.

		constructor: function(args) {

			this.config = {
				// own events
				events: {
					GET: "get",
					REQUEST: "request",
					REQUEST_QUERY: "requestQuery",
					TARGET_LOADING: "targetLoading",
					TARGET_LOADED: "targetLoaded"
				},
				// own actions
				actions: {
					REQUEST: "request",
					AVAILABLE: "available",
					GET: "get",
					ITEM_AVAILABLE: "itemAvailable",
					INJECT_DATA: "injectData",
					INJECT_ITEM: "injectItem",
					AVAILABLE_QUERY: "availableQuery",
					TARGET_LOADING: "targetLoading",
					TARGET_LOADED: "targetLoaded"
				},
				// mediator params
				ownChannel: "data",
				defaultErrorDescription: "Error",
				defaultErrorCode: "0"
			};

			lang.mixin(this, this.config, args);
		},

		_defineSubscriptions: function () {

			var options = {
				predicate: lang.hitch(this, this._chkRequestHasTarget)
			};

			this.subscriptionsConfig.push({
				channel : this.getChannel("REQUEST"),
				callback: "_subRequest",
				options: options
			},{
				channel : this.getChannel("GET"),
				callback: "_subGet",
				options: options
			},{
				channel : this.getChannel("INJECT_DATA"),
				callback: "_subInjectData",
				options: options
			},{
				channel : this.getChannel("INJECT_ITEM"),
				callback: "_subInjectItem",
				options: options
			});
		},

		_definePublications: function() {

			this.publicationsConfig.push({
				event: 'GET',
				channel: this.getChannel("ITEM_AVAILABLE")
			},{
				event: 'REQUEST',
				channel: this.getChannel("AVAILABLE")
			},{
				event: 'TARGET_LOADING',
				channel: this.getChannel("TARGET_LOADING")
			},{
				event: 'TARGET_LOADED',
				channel: this.getChannel("TARGET_LOADED")
			},{
				event: 'REQUEST_QUERY',
				channel: this.getChannel("AVAILABLE_QUERY")
			});
		},

		_chkRequestHasTarget: function(request) {

			if (!request || !request.target) {
				return false;
			}

			return true;
		},

		_subRequest: function(req) {

			this._emitTargetLoadingState("TARGET_LOADING", req.target, req.requesterId, "request");

			var target = this._getSafeTarget(req.target),
				obj = {
					target: target
				};

			if (req.type) {
				obj.type = req.type;
			}

			if (req.action) {
				obj.action = req.action;
			}

			this.collection = new Observable(new Store(obj));
			// TODO - IMPLEMENTAR -> this.collection.destroy();

			var method = req.method ? req.method : "GET",
				query = req.query ? req.query : {},
				queryString = req.queryString ? req.queryString : "",
				options = req.options ? req.options: {},
				queryResult = (method === "GET") ?
					this.collection.query(query, options) :
					this.collection.post(query, queryString, options);

			queryResult.then(
				lang.hitch(this, function(req, result) {

					this._emitRequestResults(req, result, result.total);
				}, req),
				lang.hitch(this, this._emitError, target));

			this._emitEvt('REQUEST_QUERY', {
				target: req.target,
				query: query
			});
		},

		_emitTargetLoadingState: function(event, target, requesterId, type) {

			this._emitEvt(event, {
				target: target,
				requesterId: requesterId,
				type: type
			});
		},

		_emitRequestResults: function(request, result, total) {

			this._emitEvt('REQUEST', {
				success: true,
				body: {
					data: result,
					target: request.target,
					requesterId: request.requesterId,
					total: total
				}
			});
		},

		_emitError: function(target, error) {

			if (!error) {
				error = {};
			}

			if (!error.code) {
				error.code = this.defaultErrorCode;
			}

			if(!error.description) {
				error.description = this.defaultErrorDescription/* + ' ' + (error.code || '')*/;
			}

			this._emitTargetLoadingState("TARGET_LOADED", /*request.*/target/*, request.requesterId, "request"*/);

			this._emitEvt('COMMUNICATION', {
				type: "alert",
				level: "error",
				description: error.description
			});

			this._emitEvt('REQUEST', {
				success: false,
				error: {
					target: target,
					error: error
				}
			});
		},

		_getSafeTarget: function(target) {

			if (target.indexOf('?') === -1) {
				return target + '/';
			}

			return target;
		},

		_subGet: function(req) {

			this._emitTargetLoadingState("TARGET_LOADING", req.target, req.requesterId, "get");

			var target = this._getSafeTarget(req.target),
				obj = {
					target: target
				};

			if (req.type) {
				obj.type = req.type;
			}

			this.collection = new Observable(new Store(obj));
			// TODO - IMPLEMENTAR -> this.collection.destroy();

			var result = this.collection.get(req.id, req.options);

			result.then(
				lang.hitch(this, this._emitGetResults, req),
				lang.hitch(this, this._emitError, target));
		},

		_emitGetResults: function(request, result) {

			var objRequest = {
				success: true,
				body: {
					data: result,
					target: request.target,
					requesterId: request.requesterId
				}
			};

			if (request.noSetTotal) {
				objRequest.body.noSetTotal = true;
			}

			this._emitEvt('GET', objRequest);
		},

		_subInjectData: function(request) {

			var result = request.data,
				total = result ? (request.total || result.length) : 0;

			if (!result) {
				result = [];
			}

			if (request.total) {
				delete request.total;
			}

			this._emitRequestResults(request, result, total);
		},

		_subInjectItem: function(request) {

			var result = request.data;

			if (!result) {
				result = [];
			}

			this._emitGetResults(request, result);
		}
	});
});
+247 −0
Original line number Diff line number Diff line
define([
	'dojo/_base/declare'
	, 'dojo/_base/lang'
	, 'redmic/modules/base/_Module'
], function(
	declare
	, lang
	, _Module
){
	return declare(_Module, {
		//	summary:
		//		Módulo encargado de la entrada/salida con respecto a servicios externos.
		//	description:
		//		Permite manejar las peticiones de datos y su respuesta.

		constructor: function(args) {

			this.config = {
				events: {
					GET: 'get',
					REQUEST: 'request',
					REQUEST_QUERY: 'requestQuery',
					TARGET_LOADING: 'targetLoading',
					TARGET_LOADED: 'targetLoaded'
				},
				actions: {
					REQUEST: 'request',
					AVAILABLE: 'available',
					GET: 'get',
					ITEM_AVAILABLE: 'itemAvailable',
					INJECT_DATA: 'injectData',
					INJECT_ITEM: 'injectItem',
					AVAILABLE_QUERY: 'availableQuery',
					TARGET_LOADING: 'targetLoading',
					TARGET_LOADED: 'targetLoaded'
				},
				ownChannel: 'data',
				defaultErrorDescription: 'Error',
				defaultErrorCode: '0'
			};

			lang.mixin(this, this.config, args);
		},

		_defineSubscriptions: function () {

			var options = {
				predicate: lang.hitch(this, this._chkRequestHasTarget)
			};

			this.subscriptionsConfig.push({
				channel : this.getChannel('REQUEST'),
				callback: '_subRequest',
				options: options
			},{
				channel : this.getChannel('GET'),
				callback: '_subGet',
				options: options
			},{
				channel : this.getChannel('INJECT_DATA'),
				callback: '_subInjectData',
				options: options
			},{
				channel : this.getChannel('INJECT_ITEM'),
				callback: '_subInjectItem',
				options: options
			});
		},

		_definePublications: function() {

			this.publicationsConfig.push({
				event: 'GET',
				channel: this.getChannel('ITEM_AVAILABLE')
			},{
				event: 'REQUEST',
				channel: this.getChannel('AVAILABLE')
			},{
				event: 'TARGET_LOADING',
				channel: this.getChannel('TARGET_LOADING')
			},{
				event: 'TARGET_LOADED',
				channel: this.getChannel('TARGET_LOADED')
			},{
				event: 'REQUEST_QUERY',
				channel: this.getChannel('AVAILABLE_QUERY')
			});
		},

		_chkRequestHasTarget: function(request) {

			if (!request || !request.target) {
				return false;
			}

			return true;
		},

		_subRequest: function(req) {

			this._emitTargetLoadingState('TARGET_LOADING', req.target, req.requesterId, 'request');

			var target = this._getBuiltTarget(req),
				method = req.method || 'GET',
				query = req.query || {},
				queryString = req.queryString || '',
				options = req.options || {},
				queryResult = (method === 'GET') ?
					this.query(target, query, options) :
					this.post(target, query, queryString, options);

			queryResult.then(
				lang.hitch(this, this._emitRequestResults, req),
				lang.hitch(this, this._emitError, req));

			this._emitEvt('REQUEST_QUERY', {
				target: req.target,
				query: query
			});
		},

		_emitTargetLoadingState: function(event, target, requesterId, type) {

			this._emitEvt(event, {
				target: target,
				requesterId: requesterId,
				type: type
			});
		},

		_emitRequestResults: function(req, res, total) {

			var handledResponse = this._handleResponse(res);

			var responseObj = {
				target: req.target,
				requesterId: req.requesterId,
				total: total
			};

			lang.mixin(responseObj, handledResponse);

			// TODO suprimir envoltura de respuesta
			this._emitEvt('REQUEST', {
				success: true,
				body: responseObj
			});
		},

		_emitError: function(req, err) {

			var target = req.target,
				requesterId = req.requesterId,
				handledError = this._handleError(err),
				data = handledError.data,
				status = handledError.status;

			// TODO creo que estos valores por defecto deberían ponerse donde se escuchan, no aquí
			if (!data.code) {
				data.code = this.defaultErrorCode;
			}
			if (!data.description) {
				data.description = this.defaultErrorDescription/* + ' ' + (data.code || '')*/;
			}

			this._emitTargetLoadingState('TARGET_LOADED', target/*, requesterId, 'request'*/);

			this._emitEvt('COMMUNICATION', {
				type: 'alert',
				level: 'error',
				description: data.description
			});

			this._emitEvt('REQUEST', {
				success: false,
				error: {
					target: target,
					requesterId: requesterId,
					error: data
				}
			});
		},

		_subGet: function(req) {

			this._emitTargetLoadingState('TARGET_LOADING', req.target, req.requesterId, 'get');

			var target = this._getSafeTarget(req.target),
				result = this.get(target, req.id, req.options);

			result.then(
				lang.hitch(this, this._emitGetResults, req),
				lang.hitch(this, this._emitError, req));
		},

		_emitGetResults: function(req, res) {

			var handledResponse = this._handleResponse(res);

			var responseObj = {
				target: req.target,
				requesterId: req.requesterId
			};

			lang.mixin(responseObj, handledResponse);

			// TODO suprimir envoltura de respuesta
			var getResponse = {
				success: true,
				body: responseObj
			};

			// TODO creo que mejor no hacer esto aquí de forma genérica, sino tratar la respuesta donde se desee
			if (req.noSetTotal) {
				getResponse.body.noSetTotal = true;
			}

			this._emitEvt('GET', getResponse);
		},

		_subInjectData: function(req) {

			var res = {
				data: req.data || []
			};

			var total = req.data ? (req.total || req.data.length) : 0;

			if (req.total) {
				delete req.total;
			}

			this._emitRequestResults(req, res, total);
		},

		_subInjectItem: function(req) {

			var res = req.data;

			if (!res) {
				res = [];
			}

			this._emitGetResults(req, res);
		}
	});
});
+226 −0
Original line number Diff line number Diff line
define([
	"dojo/io-query"
	, "dojo/request"
	, "dojo/_base/lang"
	, "dojo/_base/declare"
	, "dojo/Deferred"
	, "dojo/store/util/QueryResults"
	, "redmic/store/util/SimpleQueryEngine"
	'dojo/_base/declare'
	, 'dojo/_base/lang'
	, 'dojo/io-query'
	, 'dojo/request'
	, 'dojo/Deferred'
	, './RestManager'
], function(
	ioQuery
	, xhr
	declare
	, lang
	, declare
	, ioQuery
	, request
	, Deferred
	, QueryResults
	, SimpleQueryEngine
	, RestManager
){
	return declare(null, {
	return declare(RestManager, {
		//	summary:
		//		Componente que nos permite traer datos de nuestro servicio
		//		Implementación del módulo RestManager, que nos permite intercambiar datos con los diferentes servicios.
		//	description:
		//		Proporciona los métodos get y query

		//	config: Object
		//		Opciones por defecto.
		//		Proporciona los métodos de consulta (get, query y post).

		constructor: function(args) {
			// summary:
			//		Constructor del store.
			// tags:
			//		extension
			// args:
			//		Atributo de inicialización.

			this.config = {
				timeout: 45000,
@@ -38,11 +27,9 @@ define([
				defaultType: 'ES',
				action: '_search',
				headers: {},
				target: '',
				idProperty: 'id',
				ascendingPrefix: '%2B',
				descendingPrefix: '-',
				queryEngine: SimpleQueryEngine,
				limitDefault: 100,
				handleAs: 'json',
				accepts: 'application/javascript, application/json'
@@ -51,9 +38,7 @@ define([
			lang.mixin(this, this.config, args);
		},

		// TODO GENERAL: quitar Json.parser si api con elastic devuelve json no string line 84, 102 y 200

		get: function(id, options) {
		get: function(target, id, options) {
			//	summary:
			//		Devuelve el item correspondiente al id en el servicio especificado por target
			//	id: Integer
@@ -63,57 +48,31 @@ define([
			//	returns: Object
			//		El item traido del servicio

			var responseDfd = new Deferred(),
				target = this.target + id,
				successCallback = lang.hitch(this, this._handleGetResponse, responseDfd),
				errorCallback = lang.hitch(this, this._handleGetError, responseDfd);
			var builtTarget = target + id;

			var requestDfd = xhr(target, {
			var requestDfd = request(builtTarget, {
				method: 'GET',
				handleAs: this.handleAs,
				headers: this._getHeaders('get', options || {}),
				timeout: this.timeout
			});

			requestDfd.response.then(successCallback, errorCallback);

			return responseDfd;
		},

		_handleGetResponse: function(dfd, res) {

			var data = res.data,
				status = res.status;

			// TODO sustituir propiedad 'success' por el uso del status de la respuesta
			if (data.success || data.success === undefined) {
				dfd.resolve(data.body || data);
			} else {
				dfd.reject(data);
			}
			return requestDfd.response;
		},

		_handleGetError: function(dfd, err) {

			dfd.reject(this._parseError(err));
		},

		query: function(query, options) {
		query: function(target, query, options) {
			//	summary:
			//		Devuelve los items del servicio especificado por target, los cuales coinciden con la query establecida
			//		Devuelve, desde el servicio especificado por target, los items que satisfacen la query establecida
			//	query: Object
			//		Query que especifica los datos requeridos
			// options: __QueryOptions?
			//	options: Object?
			//
			// returns: dojo/store/api/Store.QueryResults
			//	returns: Object

			var responseDfd = new Deferred(),
				queryString = this._getQuery(query, options) || '',
				target = this.target + (this.type === this.defaultType ? this.action : '') + queryString,
				successCallback = lang.hitch(this, this._handleQueryResponse, responseDfd),
				errorCallback = lang.hitch(this, this._handleQueryError, responseDfd);
			var queryString = this._getQuery(target, query, options) || '',
				builtTarget = target + queryString;

			var requestDfd = xhr(target, {
			var requestDfd = request(builtTarget, {
				method: 'GET',
				handleAs: this.handleAs,
				headers: this._getHeaders('query', options),
@@ -121,53 +80,19 @@ define([
				query : queryString ? {} : query
			});

			requestDfd.response.then(successCallback, errorCallback);

			return QueryResults(responseDfd);
		},

		_handleQueryResponse: function(dfd, res) {

			var data = res.data,
				status = res.status;

			// TODO sustituir propiedad 'success' por el uso del status de la respuesta
			if (data.success) {
				var response = data.body;

				// TODO parece funcionar bien sin este bloque, borrar si sigue todo correcto
				/*if (this.type !== this.defaultType) {
					response.total = data.total || data.body.length;
				} else {
					response.total = data.body.total || data.body.length;
				}*/

				dfd.resolve(response);
			} else {
				dfd.reject(lang.delegate(data, {total: 0}));
			}
		},

		_handleQueryError: function(dfd, err) {

			dfd.reject(lang.delegate(err, { total: 0 }));
			return requestDfd.response;
		},

		post: function(queryObject, queryString, options) {
		post: function(target, queryObject, queryString, options) {
			//	summary:
			//		Consultas vía post
			//	queryObject: Object
			//		Query object.
			// options: __PutDirectives?
			//	options: Object?
			//
			// returns: dojo/_base/Deferred

			var responseDfd = new Deferred(),
				target = this.target + (this.type === this.defaultType ? this.action : ''),
				successCallback = lang.hitch(this, this._handlePostResponse, responseDfd),
				errorCallback = lang.hitch(this, this._handlePostError, responseDfd);
			//	returns: Object

			var requestDfd = xhr(target, {
			var requestDfd = request(target, {
				method: 'POST',
				data: JSON.stringify(queryObject),
				handleAs: this.handleAs,
@@ -176,50 +101,62 @@ define([
				query: queryString
			});

			requestDfd.response.then(successCallback, errorCallback);

			return responseDfd;
			return requestDfd.response;
		},

		_handlePostResponse: function(dfd, res) {
		_handleResponse: function(res) {

			var data = res.data,
				status = res.status;

			// TODO sustituir propiedad 'success' por el uso del status de la respuesta
			if (data.success) {
				var response = data.body;

				// TODO parece funcionar bien sin este bloque, borrar si sigue todo correcto
				/*if (!this.type || this.type === this.defaultType) {
					response.total = response.length;
				} else {
					response.total = response.total || 0;
				}*/

				dfd.resolve(response);
			} else {
				dfd.reject(data);
			if (data && data.body) {
				data = data.body;
			}

			return {
				data: data,
				status: status
			};
		},

		_handlePostError: function(dfd, err) {
		_handleError: function(err) {

			console.error(err.message);

			var errObj = err.response,
				data = errObj.data,
				status = errObj.status;

			if (data && data.error) {
				data = data.error;
			}

			dfd.reject(this._parseError(err));
			return {
				data: data,
				status: status
			};
		},

		_parseError: function(err) {
		_getBuiltTarget: function(req) {

			var data = err;
			var target = this._getSafeTarget(req.target),
				type = req.type || this.type,
				action = req.action || this.action;

			if (data && data.response) {
				data = data.response;
				if (data && data.data) {
					data = data.data;
			if (type === this.defaultType) {
				target += action;
			}

			return target;
		},

		_getSafeTarget: function(target) {

			if (target.indexOf('?') === -1) {
				return target + '/';
			}

			return data.error || data;
			return target;
		},

		_getHeaders: function(type, options) {
@@ -229,42 +166,43 @@ define([

				if (options.start >= 0 || options.count >= 0) {
					//set X-Range for Opera since it blocks "Range" header
					headers.Range = headers["X-Range"] = "items=" + (options.start || '0') + '-' +
						(("count" in options && options.count != Infinity) ?
							(options.count + (options.start || 0) - 1) : this.limitDefault);  // cambiar this.limitDefault por ''
					headers.Range = headers['X-Range'] = 'items=' + (options.start || '0') + '-' +
						(('count' in options && options.count != Infinity) ?
							(options.count + (options.start || 0) - 1) :
							this.limitDefault);  // cambiar this.limitDefault por ''
				}
				return headers;

			} else if (type === "get") {
			} else if (type === 'get') {
				return lang.mixin({Accept: this.accepts}, this.headers, options.headers || options);

			} else if (type === "post") {
			} else if (type === 'post') {
				return lang.mixin({
						"Content-Type": "application/json",
						Accept: this.accepts,
						"If-Match": options.overwrite === true ? "*" : null,
						"If-None-Match": options.overwrite === false ? "*" : null
					'Content-Type': 'application/json',
					'Accept': this.accepts,
					'If-Match': options.overwrite === true ? '*' : null,
					'If-None-Match': options.overwrite === false ? '*' : null
				}, this.headers, options.headers);
			}
		},

		_getQuery: function(query, options) {
		_getQuery: function(target, query, options) {

			var hasQuestionMark = this.target.indexOf("?") > -1;
			var hasQuestionMark = target.indexOf('?') > -1;

			if (query && typeof query == "object") {
			if (query && typeof query == 'object') {
				query = ioQuery.objectToQuery(query);
				query = query ? (hasQuestionMark ? "&" : "?") + query: "";
				query = query ? (hasQuestionMark ? '&' : '?') + query: '';
			}

			if (options && options.sort) {
				if (!query) {
					query = "";
					query = '';
				}

				var sortParam = this.sortParam;

				query += (query || hasQuestionMark ? "&" : "?") + (sortParam ? sortParam + '=' : "sort=(");
				query += (query || hasQuestionMark ? '&' : '?') + (sortParam ? sortParam + '=' : 'sort=(');

				for (var i = 0; i < options.sort.length; i++) {
					var sort = options.sort[i];
@@ -273,12 +211,12 @@ define([
						sort.attribute = sort.property;
					}

					query += (i > 0 ? "," : "") + (sort.descending ? this.descendingPrefix :
					query += (i > 0 ? ',' : '') + (sort.descending ? this.descendingPrefix :
						this.ascendingPrefix) + encodeURIComponent(sort.attribute || sort.property);
				}

				if (!sortParam) {
					query += ")";
					query += ')';
				}
			}