function registerClass(className, claz){
if(beans[className]){
logger.warn("Overriding existing bean: ", className);
}
if(claz._angoosemeta && (claz._angoosemeta.baseClass == 'Service' || claz._angoosemeta.baseClass == 'Model') ){
beans[className] = claz;
logger.info("===== Registered ", claz._angoosemeta.baseClass, className);
}
else{
throw "Invalid class: must be a Model or Service class: " + claz;
}
}
function configureRoutes(app, options){
if(!app){
logger.warn("app not provided, RMI functionality is disabled");
return;
}
app.post(options.urlPrefix+"/rmi/:model/:method", rmiAccept);
app.get(options.urlPrefix+"/angoose-client.js", function(req, res){
logger.debug("Handling AngooseClinet.js load request");
var filename = options.clientFile ;
var content = fs.readFileSync(filename , 'ascii');
res.set('Content-Type', 'application/javascript');
res.send(200, content );
});
}
function decode(obj){
if(!obj || typeof obj != 'object') return obj;
if(Array.isArray(obj)){
for(var i=0;i<obj.length;i++){
obj[i] = decode(obj[i]);
};
}
else{
Object.keys(obj).forEach(function(key){
var val = decode(obj[key]);
if(key.indexOf('_mongo_$')==0){
delete obj[key];
key = key.substring(7)
}
obj[key] = val;
})
}
return obj;
}
function getValueType(obj){
if(!obj || typeof(obj) == 'string') return 'string';
if( obj.schema && obj.schema.paths && obj.schema.methods ) return 'model';
if( obj.length ){
for(var i =0; obj && obj.length>i; i++){
var retval = getValueType(obj[i]);
if(retval != 'model') return 'object'
}
return 'models';
}
return 'object';
}
function formatError (ex, seqnumber){
logger.debug("====== END RMI with Error #", seqnumber, ex);
return {success:false, exception:ex, seqnumber: seqnumber };
}
function injectDependencies(func, args, ctx, callback ){
var declaredArguments = schemaUtil.parseDeclaredArguments(func);
declaredArguments = declaredArguments.replace(/\s+/g, "").split(",");
logger.trace("injectDependencies() declaredArguments: ", declaredArguments, "provided args:", args);
var useCallback = false;
for(var i=0;i<declaredArguments.length;i++){
if(typeof( args[i]) == 'undefined')
args[i] = undefined;
else
continue;
switch(declaredArguments[i]){
case '$callback':
logger.debug("Injecting callback handler");
args[i] = callback;
useCallback = true;
break;
case '$context':
logger.debug("Injecting context");
args[i] = ctx;
break;
default:
break;
}
}
return useCallback;
}
function rmiAccept(req,res){
var invocation = decode(req.body);
invocation.method = req.params.method;
var ctx = new angoose.Context({request:req, response:res, session:req.session});
var excutionStorage = storageFactory.createNamespace("angoose");
excutionStorage.run(function(){
logger.debug("Setting execution context");
var sent = false;
excutionStorage.set("context", ctx);
try {
processInvocation(invocation, ctx, function(ex, data){
if(ex)
data = data || formatError({message:ex.toString(), value:ex, code:500}, invocation.seqnumber ) ;
res.send(200, data);
sent = true;
});
}
catch(err){
res.send(200, formatError( {message: err.toString(), value:err, code:500 }, invocation.seqnumber ));
sent = true;
}
setTimeout(function(){
if(sent) return;
var msg ="Timeout occurred when processing call "+invocation.clazz +"." + invocation.method;
logger.error(msg, invocation.seqnumber);
res.send(200, formatError( {message: msg, value: msg, code:500 }, invocation.seqnumber ));
}, options.request_timeout || 5000);
});
}
function processInvocation(invocation, ctx, sendResponse){
var modelName = invocation.clazz;
var method = invocation.method;
var seqnum = invocation.seqnumber;
logger.debug("====== BEGIN RMI #", seqnum, modelName, invocation );
var callbackDeferred= Q.defer();
var useCallback = false;
function callbackHandler(err, result){
logger.trace("In callback handler #"+seqnum, err, result);
if(err) callbackDeferred.reject(err);
else callbackDeferred.resolve(result);
}
var args = _.map(invocation.args || [], function(item){
if(item === "$callback"){
useCallback = true;
return callbackHandler;
}
return item;
});
var modelClass = getClass(modelName);
var modelError = null;
prepareTarget(invocation, function(target){
if(typeof(target) == 'string')
return sendResponse( formatError( {message: err.toString(), value:err, code:500 }, seqnum ) );
invokeMethod(target);
})
function invokeMethod(target){
try{
useCallback = useCallback || injectDependencies(target[method], args, ctx, callbackHandler);
logger.trace("invoking method ", method.toUpperCase() , " with arguments", args, " on target ", target)
var ret = target[method].apply(target, args);
if(ret && ret.exec){
logger.debug("Return value is mongoose query, call exec()" );
var deferred = Q.defer();
ret.exec(function(err, data){
if(err){
logger.error("error exuecuting Model method", err);
return deferred.reject(err);
}
logger.debug("Model method res", data)
deferred.resolve( data);
});
ret = deferred.promise;
}
else if(useCallback){
ret = callbackDeferred.promise;
}
else {
ret = Q(ret);
}
function checkForModelError(result, target ){
var err = ( target && target.errors) || (result && result.errors);
if(err)
{
var ex = { value: err, code:500, message:""};
Object.keys(err).forEach(function (errItem) {
ex.message += ex.message?";":"";
ex.message += err[errItem].message;
})
ex.message = ex.message || err.toString();
return ex;
}
}
ret.done(function(result){
var ex = checkForModelError(result, (invocation.isStaitc?null:target) );
if(ex) return sendResponse ( ex );
var valueType = getValueType(result);
var retdata = { success:true,
exception:null,
retval:result,
datatype: valueType,
seqnumber:seqnum
};
if( !invocation.static && (! _.isEqual(target.toJSON(), invocation.target)))
{
retdata.instance = target.toJSON();
}
sendResponse(false, retdata);
logger.debug("====== END RMI Success Result #", seqnum, " DONE: ", retdata);
}, function(err){
sendResponse( err );
});
}
catch(err){
logger.error("Unhandled server error", err)
sendResponse(true, formatError( {message: err.toString(), value:err, code:501 }, seqnum ));
}
};
};
function prepareTarget(invocation, targetCallback){
logger.debug("Preparing target from JSON");
var modelClass = getClass( invocation.clazz );
if(invocation.static)
return targetCallback(modelClass);
if( typeof(invocation.instance) != 'object')
return targetCallback( "Missing invocation instance property" );
if(modelClass instanceof angoose.Service || !invocation.instance._id)
return targetCallback(new modelClass(invocation.instance));
var _id = invocation.instance._id;
logger.debug("Loading pristine instance as base",invocation.clazz, _id)
modelClass.findById(_id, function(err, pristineInstance){
if(err || !pristineInstance){
logger.error("Failed to load model by id", _id);
return targetCallback(err);
}
var pristineObject = pristineInstance.toObject();
var schema = modelClass.schema;
Object.keys(schema.paths).forEach(function(path){