/*______________
| ______ | U I Z E J A V A S C R I P T F R A M E W O R K
| / / | ---------------------------------------------------
| / O / | MODULE : Uize.Service Class
| / / / |
| / / / /| | ONLINE : http://uize.com
| /____/ /__/_| | COPYRIGHT : (c)2012-2014 UIZE
| /___ | LICENSE : Available under MIT License or GNU General Public License
|_______________| http://uize.com/license.html
*/
/* Module Meta Data
type: Class
importance: 7
codeCompleteness: 0
docCompleteness: 5
*/
/*?
Introduction
The =Uize.Service= module defines a base class from which classes that define services can inherit.
*DEVELOPERS:* `Chris van Rensburg`
*/
Uize.module ({
name:'Uize.Service',
superclass:'Uize.Class',
builder:function (_superclass) {
'use strict';
var
/*** Variables for Scruncher Optimization ***/
_undefined,
_false = false,
_true = true,
/*** constants ***/
_SERVICE_TAKING_TOO_LONG = 5000
;
/*** Private Instance Methods ***/
function _warn (m,_message) {
m.fire ({name:'warning',warning:_message});
}
function _methodCaller (m,_methodName) {
return function () {return m [_methodName].apply (m,arguments)};
}
var _class = _superclass.subclass ({
staticProperties:{
_serviceMethods:{}
},
staticMethods:{
serviceMethods:function (_serviceMethods) {
var
m = this,
_thisServiceMethods = m._serviceMethods,
_serviceMethodPublicWrappers = {}
;
function _declareServiceMethod (_methodName,_methodProfile) {
var _isInitMethod = _methodName == 'init';
function _methodError (_message) {return '<< ' + _methodName + ' >> ' + _message}
if (m.prototype [_methodName])
throw new Error (
_methodError ('You may not override a non-service public method with a service method')
)
;
_thisServiceMethods [_methodName] = _methodProfile || (_methodProfile = {});
/* NOTES:
normalize method profile
eg.
{
async: true (default) | false,
params: {...}
}
*/
_methodProfile.async = _methodProfile.async !== _false;
_serviceMethodPublicWrappers [_methodName] = function (_params,_callback) {
var
m = this,
_adapter = m.get ('adapter'),
_methodIsAsync = _methodProfile.async
;
if (!_methodIsAsync) {
if (!_adapter)
throw new Error (
_methodError (
'To call a synchronous service method, a service adapter must be set and the service must be initialized'
)
)
;
if (!_isInitMethod && !m.get ('initialized'))
throw new Error (
_methodError (
'In order to call a synchronous service method, the service must already be initialized'
)
)
;
}
if (_params == _undefined) {
_params = {};
} else if (typeof _params != 'object') {
throw new Error (_methodError ('First argument (params) must be an object, null, or undefined'));
}
var
_adapterMethodWasAsync = _false,
_timeCalled,
_timeReturned,
_takingTooLongTimeout,
_result,
_error,
_handleReturnFromAdapterMethod = function () {
_takingTooLongTimeout && clearTimeout (_takingTooLongTimeout);
function _callCallback () {
var _onSuccess, _onError;
if (_callback) {
var _typeofCallback = typeof _callback;
if (_typeofCallback == 'function') {
_onSuccess = _callback;
} else if (_typeofCallback == 'object') {
_onSuccess = _callback.onSuccess;
_onError = _callback.onError;
}
}
if (_error) {
if (_onError) {
_onError (_error);
} else {
typeof _error == 'string'
? (_error = new Error (_methodError (_error)))
: (_error.message = _methodError (_error.message))
;
throw _error;
}
} else {
_isInitMethod && m.set ('initialized',_true);
_onSuccess && _onSuccess (_result);
}
}
if (_timeReturned !== _undefined) {
throw new Error (_methodError ('Service adapter method should only return once'));
} else {
_timeReturned = Uize.now ();
var _adapterMethodDuration = _timeReturned - _timeCalled;
_adapterMethodDuration > _SERVICE_TAKING_TOO_LONG &&
_warn (
m,
_methodError(
'Service adapter method took too long to return (' + _adapterMethodDuration + 'ms)'
)
)
;
if (_methodProfile.async) {
_adapterMethodWasAsync ? _callCallback () : setTimeout (_callCallback,0);
} else {
if (_adapterMethodWasAsync) {
throw new Error (
_methodError (
'Service method is declared as synchronous, but implementation in adapter is asynchronous'
)
);
} else {
_callCallback ();
}
}
}
},
_callAdapterMethod = function () {
if (
_adapter [_methodName] (
_params,
function (_response) {
_result = _response;
_handleReturnFromAdapterMethod ();
},
function (_errorResponse) {
_error = _errorResponse || {};
_handleReturnFromAdapterMethod ();
}
) !== _undefined
)
throw new Error (
_methodError (
'Service adapter method should always provide its result through a callback, not a return statement'
)
)
;
_adapterMethodWasAsync = _true;
}
;
// now ready to start the call
_timeCalled = Uize.now ();
if (_methodIsAsync)
_takingTooLongTimeout = setTimeout (
function () {
var _initialized = m.get ('initialized');
_warn (
m,
_methodError (
_adapter && _initialized
? 'Service adapter method taking too long to return'
: (
'Taking too long to be ready to call service adapter method (' +
'adapter ' + (_adapter ? '' : 'not ') + 'set, ' +
(_initialized ? '' : 'not ') + 'initialized' +
')'
)
)
);
},
_SERVICE_TAKING_TOO_LONG
)
;
if (_isInitMethod) {
_params.serviceInterface = {
fire:_methodCaller (m,'fire'),
wire:_methodCaller (m,'wire'),
set:_methodCaller (m,'set'),
get:_methodCaller (m,'get')
};
_params.service = m;
m.once ('adapter',_callAdapterMethod);
} else {
if (!_adapter) {
_warn (m,_methodError ('Adapter is not yet set when service method is called'));
} else if (!m.get ('initialized')) {
_warn (
m,
_methodError (
'Service adapter is set but not yet initialized when service method is called'
)
);
}
m.once (
'adapter',
function () {
_adapter = m.get ('adapter');
m.once ('initialized',_callAdapterMethod);
}
);
}
return _result;
};
}
if (arguments.length != 1 || typeof _serviceMethods != 'object')
_serviceMethods = [].slice.call (arguments)
;
Uize.forEach (
_serviceMethods,
Uize.isArray (_serviceMethods)
? function (_methodName) {_declareServiceMethod (_methodName)}
: function (_methodProfile,_methodName) {_declareServiceMethod (_methodName,_methodProfile)}
);
Uize.copyInto (m.prototype,_serviceMethodPublicWrappers);
/*?
Static Methods
Uize.Service.serviceMethods
.
EXAMPLE
..........................................
var FileSystem = Uize.Service.subclass ();
FileSystem.serviceMethods ({
readFile:{
async:false
},
writeFile:{
async:false
},
getFiles:{
async:false
},
getFolder:{
async:false
},
// etc.
// etc.
});
..........................................
*/
}
},
stateProperties:{
_adapter:{
name:'adapter',
conformer:function (_adapter) {
if (typeof _adapter == 'string') {
var _adapterClass = Uize.getModuleByName (_adapter);
if (_adapterClass) {
_adapter = new _adapterClass;
} else {
throw new Error (
'The adapter module ' + _adapter + ' must be required and loaded first if you wish to set an adapter by module name'
);
}
}
if (_adapter != _undefined) {
// validate the service adapter
var _missingServiceMethods = [];
Uize.forEach (
this.constructor._serviceMethods,
function (_serviceMethodProfile,_serviceMethod) {
typeof _adapter [_serviceMethod] != 'function' &&
_missingServiceMethods.push (_serviceMethod)
;
}
);
if (_missingServiceMethods.length) {
_adapter = _undefined;
throw new Error (
'Service module adapter is missing implementations for service methods: ' + _missingServiceMethods.sort ().join (', ')
);
}
}
return _adapter;
},
onChange:function () {this.set ({initialized:_false})}
/*?
State Properties
adapter
.
*/
},
_initialized:{
name:'initialized',
value:_false
/*?
State Properties
initialized
.
*/
}
}
});
return _class;
}
});