/**
Hop Core module
This is the primary impelementation behind Hop
@module Hop
**/
var crypto = require('crypto');
var util = require('hopjs-common');
/*
- display docs
- enable unit tests
- log functions
- protect docs /w password
- site name
- readme.md
- doc.css
-
*/
var Hop=function(){
this.objects={};
this.interfaces={};
}
Hop.use=function(what){
what(Hop);
}
Hop.log=console.log;
Hop.error=console.error;
Hop.warn=console.warn;
/**
Given a string representation of a stack for a trace made during a call, return a more focused stack trace
This function will attempt to cull a larger stack trace into something more relevant for what went on during
a call.
@param {String} errorStack a stack trace captured from an error
@returns stack a focused stack trace
*/
Hop.callStack=function(errorStack){
var lines = errorStack.split("\n");
var stack=[];
var capture=false;
for(var i=lines.length-1;i>0;i--){
var l = lines[i];
var f = (l.replace(/^\s+at\s+([^ ]+).+/,"$1"));
if(f=="Hop_call_preTask"){
capture=true;
}
if(f=="Hop_call_postTask"){
capture=false;
}
if(capture==true && f.indexOf("Hop_call")!=0){
stack.push(l);
}
}
return stack.join("\n");
}
Hop.logCall=function(method,type,err,result){
var error = new Error();
var stack = Hop.callStack(error.stack);
Hop[type](method,err+"\n"+stack);
}
Hop.sendFile=function(file,options){
return new Hop.File(file,options);
}
Hop.sendAttachment=function(file,options){
options=options||{};
options.attachment=true;
return new Hop.File(file,options);
}
Hop.render=function(template,input){
return new Hop.Template(template,input);
}
Hop.Template=function(templateName,input){
this.template=templateName;
this.input=input;
}
Hop.Template.prototype.send=function(response){
response.render(this.template,this.input);
}
Hop.File=function(file,options){
this.file=file;
this.options=options||{};
}
Hop.File.prototype.send=function(response){
var filename = this.file || this.options.filename;
if(this.options.attachment==true)
response.download(this.file,filename);
else
response.sendfile(this.file);
}
Hop.Objects={};
Hop.Interfaces={};
/**
Calculate a checksum for the Hop
This is used to detect changes in the version of the Hop
@for Hop
@method checksum
@static
**/
Hop.checksum = function(){
if(Hop._checksum)
return Hop._checksum;
var md5 = crypto.createHash('md5');
md5.update(Hop.toJSON(true));
Hop._checksum = md5.digest('hex');
return Hop._checksum;
}
/**
Utility class for providing mock response objects
@class Hop.StubResponse
**/
Hop.StubResponse=function(){
this.header={};
}
Hop.StubResponse.prototype.set=function(name,value){
this.header[name]=value;
}
Hop.StubResponse.prototype.get=function(name){
return this.header[name];
}
/**
Utility class for providing mock request objects
@class Hop.StubRequest
**/
Hop.StubRequest=function(){
this.header={};
this.response = new Hop.StubResponse();
this.session={};
}
Hop.StubRequest.prototype.getResponse=function(){
return this.response;
}
Hop.StubRequest.prototype.set=function(name,value){
this.header[name]=value;
}
Hop.StubRequest.prototype.get=function(name){
return this.header[name];
}
Hop.getTemplates=function(obj,when,type){
if(!obj._templates)
return null;
if(!obj._templates[type])
return null;
if(!obj._templates[type][when])
return null;
return obj._templates[type][when];
}
Hop.renderTemplates=function(obj,when,type,input){
try {
if(!obj._templates)
return "";
if(!obj._templates[type])
return "";
if(!obj._templates[type][when])
return "";
var _input={};
for(var i in input){
_input[i]=input[i];
}
_input.Hop=Hop;
var out = "";
obj._templates[type][when].map(function(template){
out+=(Hop.renderTemplate(template)(_input));
});
return out;
} catch(e){
console.error(e);
console.error(e.stack);
}
}
Hop.addTemplate=function(obj,when,type,template){
if(!obj._templates)
obj._templates={};
if(!obj._templates[type])
obj._templates[type]={};
if(!obj._templates[type][when])
obj._templates[type][when]=[];
if(obj._templates[type][when].indexOf(template)==-1){
obj._templates[type][when].push(template);
}
}
Hop.renderBeforeTemplates=function(type,input){
return Hop.renderTemplates(Hop,"before",type,input);
}
Hop.renderAfterTemplates=function(type,input){
return Hop.renderTemplates(Hop,"after",type,input);
}
Hop.getBeforeTemplates=function(type){
return Hop.getTemplates(Hop,"before",type);
}
Hop.getAfterTemplates=function(type){
return Hop.getTemplates(Hop,"after",type);
}
Hop.addBeforeTemplate=function(type,template){
Hop.addTemplate(Hop,"before",type,template);
}
Hop.addAfterTemplate=function(type,template){
Hop.addTemplate(Hop,"after",type,template);
}
/**
Define a new class
* Use #classname to have the class name substituted into the URL
@param {string} name the name of the class
@param {object} [instance] an instance of the object
@param {function} onDefine the lambda used to define the interface
@example
Hop.defineClass("Email",new Email(),function(api){
//define the class
});
@for Hop
@method defineClass
@static
**/
Hop.defineClass=function(name,instance,onDefine){
delete Hop._checksum;
var api = new Hop.Object(name,instance);
api.setLocalInterface();
onDefine(api);
}
/**
Define a new interface
* Use #classname to have the class name substituted into the URL
@param {string} name the name of the interface
@param {function} onDefine the lambda used to define the interface
@example
Hop.defineInterface("Notification",function(api){
api.post("send","#classname/send").usage("Sends a message").demand("msg").demand("subject").demand("to");
}
@for Hop
@method defineInterface
@static
**/
Hop.defineInterface=function(name,onDefine){
delete Hop._checksum;
var intf = new Hop.Interface(name,onDefine);
}
Hop.toJSON=function(noChecksum){
var obj = {};
obj.Objects=Hop.Objects;
obj.basePath = Hop.basePath;
if(noChecksum!=true)
obj.checksum = Hop.checksum();
for(var i in this._toJSONHandler){
var handler = this._toJSONHandler[i];
handler(obj);
}
return JSON.stringify(obj,function(key,value){
if(/^_/.test(key)){
return undefined;
}
return value;
}," ");
}
Hop.fromJSON=function(jsonString){
if(typeof jsonString=="object")
var obj = jsonString;
else if(typeof jsonString=="string")
var obj = JSON.parse(jsonString);
if(obj.Objects){
for(var objectName in obj.Objects){
var _object = obj.Objects[objectName];
var object = new Hop.Object(objectName);
for(var j in _object){
object[j]=_object[j];
}
for(var methodName in obj.Objects[i].methods){
var _method = obj.Objects[i].methods[methodName];
var method = new Hop.Method(_method.method,object,_method.name,_method.path);
object[_method.name]=method;
for(var j in _method){
method[j]=_method[j];
}
}
}
}
}
/**
Add a call back which will be called when a version of the API must be built from JSON
@param {function} onJSON Callback to be called when a json version of Hop is requested
@param {object} The stub object which is being populated for conversion to JSON
@for Hop
@method addToJSONHandler
@static
**/
Hop.addToJSONHandler=function(onJSON){
if(!this._fromJSONHandler){
this._fromJSONHandler=[];
}
this._fromJSONHandler.push(onJSON);
}
/**
Add a call back which will be called when a JSON version of the API is requested
@param {function} onJSON Callback to be called when a json version of Hop is requested
@param {object} The stub object which is being populated for conversion to JSON
@for Hop
@method addToJSONHandler
@static
**/
Hop.addToJSONHandler=function(onJSON){
if(!this._toJSONHandler){
this._toJSONHandler=[];
}
this._toJSONHandler.push(onJSON);
}
Hop.Interface = function(name,onDefine){
this.onDefine=onDefine;
this.name=name;
Hop.Interfaces[name]=this;
}
/**
Defines an object
This object is created by .defineClass
@class Hop.Object
**/
Hop.Object = function(name,instance){
this.name=name;
this._instance=instance;
this.methods={};
Hop.Objects[name]=this;
}
/**
Wrap an object so that it may be called localy with all of the functionality that Hop normally provides
This function will create a wrapped version of a defined object with all of the functionality that Hop would normally provide.
@example
Hop.defineClass("UserService",function(api){
api.get("load","/user/:userID").demand("userID").cacheId("/user/:userID");
});
var userService = Hop.Object.wrap("UserService");
//This call will now be subject to all of the normal constraints and functionality provided by Hop
userService.load({ userID:5 },function(err,result){
},request);
@param {String} objectName Then name of the object to wrap
@returns {Object}
An object with implementations of all the functions defined by the object.
The returned functions will have the calling signature (input,onComplete,request)
@method Hop.Object.wrap
**/
Hop.Object.wrap=function(objectName){
var result = {};
var obj = Hop.Object.findObject(objectName);
if(obj){
for(var i in obj.methods){
var method = obj.methods[i];
(function(method){
result[method.name]=function(input,onComplete,request){
Hop.log(method.getMethod(),input,onComplete,request);
Hop.call(method.getMethod(),input,onComplete,request);
};
})(method);
}
} else throw "Invalid object name specified";
return result;
}
Hop.Object.prototype.callOnError=function(method,request,input,error,stack){
if(!stack){
var exp = new Error();
var stack = Hop.callStack(exp.stack);
} else {
stack = Hop.callStack(stack);
}
if(this.onError){
var res = this.onError(method,request,input,error,stack);
if(res!==null)
return res;
}
Hop.error(method.getMethod(),":returned an error result of '"+error+"'\n"+stack);
return error;
}
/**
Provide a function that will be called when an error occured for any call in the class
This function can be used to filter error messages. A typical use case would be to have a small
set of allowed error messages, and return a generic error message in all other cases. Or to attach
a unique number to each error and provide details in the console log. This function should return a string
for the desired error string or null if no custom error is returned. This function is not asynchronous.
@param {Function} onError function to be called when an error has occured
@param {Object} onError.method The method call associated with the error
@param {Object} onError.request The request for the call
@param {Object} onError.input The input for the call
@param {String} onError.error The error string
@param {String} onError.stack A stack trace of where the error occured
@example
Hop.defineClass("UserService",function(api){
api.post("update","/user/:userID");
api.errorHandler(function(method,request,input,error,stack){
//If we know what the error is simply return it
if(allowedErrors.contains(error)){
return null;
} else {
//If we don't recognize the error produce a unique ID for it and spit it out in the logs
var id = Date.now();
var err = "Error ("+id+")";
Log.error(method.getName(),request,input,id,error,stack);
return err;
}
});
});
@for Hop.Object
@method errorHandler
*/
Hop.Object.prototype.errorHandler=function(onError){
this.onError=onError;
}
Hop.Object.prototype.setLocalInterface=function(){
this._localInterface=true;
}
Hop.Object.prototype.isLocalInterface=function(){
return this._localInteraface===true;
}
Hop.Object.prototype.renderBeforeTemplates=function(type,input){
return Hop.renderTemplates(this,"before",type,input);
}
Hop.Object.prototype.renderAfterTemplates=function(type,input){
return Hop.renderTemplates(this,"after",type,input);
}
Hop.Object.prototype.getBeforeTemplates=function(type){
return Hop.getTemplates(this,"before",type);
}
Hop.Object.prototype.getAfterTemplates=function(type){
return Hop.getTemplates(this,"after",type);
}
Hop.Object.prototype.addBeforeTemplate=function(type,template){
Hop.addTemplate(this,type,"before",type,template);
}
Hop.Object.prototype.addAfterTemplate=function(type,template){
Hop.addTemplate(this,type,"after",type,template);
}
/**
Have this object extend from an interface
@example
Hop.defineInterface("Notification",function(api){
api.post("send","#classname/send").usage("Sends a message").demand("msg").demand("subject").demand("to");
}
Hop.defineClass("Email",function(api){
//This will essentially evaluate the interface defined above against thsi class adding the send function
api.extend("Notification");
});
@for Hop.Object
@method extend
**/
Hop.Object.prototype.extend=function(intf){
if(!this.interfaces){
this.interfaces={};
}
this.interfaces[intf]=true;
if(Hop.Interfaces[intf]){
Hop.Interfaces[intf].onDefine(this);
} else throw ("Invalid interface specified:"+intf);
}
/**
Define a HTTP get call on this method
@params {String} method The name of the method on the associated object
@params {String} path The HTTP path that this call can be found on. Variables can be specified as part of the path utilizing ':'
@example
Hop.defineClass("UserService",function(api){
api.get("load","/user/:userID");
//..
});
@for Hop.Object
@method get
@chainable
**/
Hop.Object.prototype.get=function(name,path){
this.methods[name]=new Hop.Method("get",this,name,path);
return this.methods[name];
}
/**
Define a HTTP post call on this method
@params {String} method The name of the method on the associated object
@params {String} path The HTTP path that this call can be found on. Variables can be specified as part of the path utilizing ':'
@example
Hop.defineClass("UserService",function(api){
api.post("update","/user/:userID");
//..
});
@for Hop.Object
@method post
@chainable
**/
Hop.Object.prototype.post=function(name,path){
this.methods[name]=new Hop.Method("post",this,name,path);
return this.methods[name];
}
/**
Define a HTTP del call on this method
@params {String} method The name of the method on the associated object
@params {String} path The HTTP path that this call can be found on. Variables can be specified as part of the path utilizing ':'
@example
Hop.defineClass("UserService",function(api){
api.del("delete","/user/:userID");
//..
});
@for Hop.Object
@method del
@chainable
**/
Hop.Object.prototype.del=function(name,path){
this.methods[name]=new Hop.Method("delete",this,name,path);
return this.methods[name];
}
/**
Define a HTTP put call on this method
@params {String} method The name of the method on the associated object
@params {String} path The HTTP path that this call can be found on. Variables can be specified as part of the path utilizing ':'
@example
Hop.defineClass("UserService",function(api){
api.put("create","/user/");
//..
});
@for Hop.Object
@method put
@chainable
**/
Hop.Object.prototype.put=function(name,path){
this.methods[name]=new Hop.Method("put",this,name,path);
return this.methods[name];
}
/**
Define the usage for this class
@example
Hop.defineClass("UserService",function(api){
api.usage("Manages Users");
//..
});
@for Hop.Object
@method usage
@chainable
**/
Hop.Object.prototype.usage=function(usage){
this.desc=usage;
return this;
}
/**
Find an object by name
@return {string} The name of the method
@example
Hop.defineClass("UserService",function(api){
api.get("load","/user/:userID")
});
var method = Hop.Method.findMethod("UserService.load");
Hop.log(method.getMethod()); //returns UserService.load
@for Hop.Object
@method findObject
@static
**/
Hop.Object.findObject=function(objName){
if(objName.indexOf(".")!=-1){
var parts = objName.split(".");
objName = parts.splice(0,parts.length-1).join(".");
}
return Hop.Objects[objName];
}
/**
Class used to define methods
@class Hop.Method
@constructor
**/
Hop.Method = function(method,object,name,_path){
this._className = object.name;
this.method=method;
this.name=name;
this.path=_path;
this.params={};
this._preCall=[];
this._postCall=[];
this.defaults={};
this.options={};
this.fullPath = Hop.Method.getPath(Hop.basePath,this._className,this);
//Let's figure out what parameters we must have!
var self=this;
var pathParts = _path.split("/");
pathParts.map(function(part){
if(/^:.*/.test(part)){
self.demand(part.replace(/^:/,""));
}
});
}
Hop.Method.prototype.toJSON=function(noChecksum){
var obj = {};
this.fullPath = Hop.Method.getPath(Hop.basePath,this._className,this);
return this;
}
/**
Find a method by name
@return {string} The name of the method
@example
Hop.defineClass("UserService",function(api){
api.get("load","/user/:userID")
});
var method = Hop.Method.findMethod("UserService.load");
Hop.log(method.getMethod()); //returns UserService.load
@method findMethod
@static
**/
Hop.Method.findMethod=function(objName){
var obj = Hop.Object.findObject(objName);
if(obj){
var parts = objName.split(".");
var method = parts.splice(parts.length-1);
return obj.methods[method];
} else return null;
}
/**
Get the name of the method
This will get the name of the method
@return {string} The name of the method
@example
Hop.defineClass("UserService",function(api){
api.get("load","/user/:userID")
});
var method = Hop.Method.findMethod("UserService.load");
Hop.log(method.getMethod()); //returns UserService.load
@method getMethod
**/
Hop.Method.prototype.getMethod=function(){
return Hop.Method.getMethod(this._className,this);
}
Hop.Method.getMethod=function(className,method){
return className+"."+method.name;
}
/**
Get the full url for the method
This will get the full path for the url for the method.
@return {string} The URL for the method
@method getPath
**/
Hop.Method.prototype.getPath=function(){
return Hop.Method.getPath(Hop.basePath,this._className,this);
}
Hop.Method.getPath=function(basePath,className,method){
var _path = method.path.replace("#classname",className.toLowerCase());
_path = _path.replace("#ClassName",className);
return util.webpath.join(basePath,_path);
}
/**
Specify the default values for this call
These values will be copied into the input if no existing value is found.
@param {object} defaults
@method defaultValues
@chainable
**/
Hop.Method.prototype.defaultValues=function(defaults){
this.defaults=defaults;
return this;
}
/**
Specify a number of optionals for for a call
@example
api.post("create","/user/profile/").optionals("email","name","password");
@param {string} name of parameter (+)
@for Hop.Method
@method optionals
@chainable
**/
Hop.Method.prototype.optionals=function(){
for(var i=0;i<arguments.length;i++)
this.optional(arguments[i]);
return this;
}
/**
Specify a number of demands for for a call
@example
api.post("create","/user/profile/").demands("email","name","password");
@param {string} name name of parameter (+)
@for Hop.Method
@method demands
@chainable
**/
Hop.Method.prototype.demands=function(){
for(var i=0;i<arguments.length;i++)
this.demand(arguments[i]);
return this;
}
/**
Demand a parameter for a call
@example
api.post("create","/user/profile/").demand("email","Email address");
@param {string} name of parameter
@param {string} desc description of parameter
@for Hop.Method
@method demand
@chainable
**/
Hop.Method.prototype.demand=function(name,desc,validate){
this.params[name]={ desc: desc, validate: validate, demand:true };
return this;
}
/**
Describe this method
@param {string} usage text describing the function for documentation purposes
@chainable
@method usage
**/
Hop.Method.prototype.usage=function(desc){
this.desc=desc;
return this;
}
/**
Note that this method can only be called when there is a user in the session
*This will only allow this method to be called if req.session.user exists*
@example
Hop.defineClass("UserService",UserService,function(api){
//Only allow this method to be called if a user is in the session
api.post("sendMsg","/user/:userId/message").authed();
});
@chainable
@method authed
**/
Hop.Method.prototype.authed=function(desc){
this.authed=true;
this.addPreCall(function Hop_Method_preCall_authed(req,input,onComplete,next){
if(!req.session.user){
return onComplete("Permission denied");
} else return next();
},"demand");
return this;
}
/**
Note that this method can only be called using https
@chainable
@method secure
**/
Hop.Method.prototype.secure=function(desc){
this.secure=true;
this.addPreCall(function Hop_Method_preCall_secure(req,input,onComplete,next){
if(!req.secure){
return onComplete("Permission denied");
} else return next();
},"demand");
return this;
}
/**
Include the express CSRF token in the response headers for this method
The CSRF token is utilized to prevent cross site request forgery attacks, and is a middleware component
for express.
See here for information: http://www.senchalabs.org/connect/middleware-csrf.html
By default HopJS will attempt to utilize the CSRF functionality in express if it is enabled, but
some clients require a means to access the CSRF token, hence this function will will send the csrf token
in the headers as 'x-crsf-token'
The primary usage for this function is with secure login functions
@example
Hop.defineClass("UserService",UserService,function(api){
api.post("login","/login").demands("username","password").sendCSRFToken();
});
@chainable
@method sendCSRFToken
**/
Hop.Method.prototype.sendCSRFToken=function(){
this.sendsCSRFToken=true;
this.addPostCall(function Hop_Method_postCall_sendCSRFToken(req,input,err,result,next){
if(!err && req.session._csrf){
req._response.set('X-CSRF-Token',req.session._csrf);
}
next();
},"last");
return this;
}
/**
Optional parameter for a call
@example
api.post("create","/user/profile/").optional("phoneNumber","Phone Number");
@param {string} name of parameter
@param {string} desc description of parameter
@for Hop.Method
@method optional
@chainable
**/
Hop.Method.prototype.optional=function(name,desc,validate){
this.params[name]={ desc: desc, validate: validate, optional:true };
return this;
}
/**
Indicate this function performs longPolling
@example
api.get("status","/server/:serverID/status").longPoll();
@for Hop.Method
@method longPoll
@chainable
**/
Hop.Method.prototype.longPoll=function(){
this.options.noCache=true;
this.options.longPoll=true;
return this;
}
/**
Indicate this function should avoid caching
@example
api.post("create","/user/profile/").noCache();
@for Hop.Method
@method noCache
@chainable
**/
Hop.Method.prototype.noCache=function(){
this.options.noCache=true;
return this;
}
Hop.Method.prototype.renderBeforeTemplates=function(type,input){
return Hop.renderTemplates(this,"before",type,input);
}
Hop.Method.prototype.renderAfterTemplates=function(type,input){
return Hop.renderTemplates(this,"after",type,input);
}
Hop.Method.prototype.getBeforeTemplates=function(type){
return Hop.getTemplates(this,"before",type);
}
Hop.Method.prototype.getAfterTemplates=function(type){
return Hop.getTemplates(this,"after",type);
}
Hop.Method.prototype.addBeforeTemplate=function(type,template){
Hop.addTemplate(this,"before",type,template);
}
Hop.Method.prototype.addAfterTemplate=function(type,template){
Hop.addTemplate(this,"after",type,template);
}
/**
Demand a file be provided for this method.
@example
api.post("create","/user/profile/").demandFile("avatar","Users avatar image");
@for Hop.Method
@method demandFile
@chainable
**/
Hop.Method.prototype.demandFile=function(name,desc,validate){
this.params[name]={ desc: desc, validate: validate, demandFile:true, demand:true };
return this;
}
/**
Specify that a file may optionally be provided as an input to this call.
@example
api.post("create","/user/profile/").optionalFile("avatar","Users avatar image");
@for Hop.Method
@method optionalFile
@chainable
**/
Hop.Method.prototype.optionalFile=function(name,desc,validate){
this.params[name]={ desc: desc, validate: validate, optionalFile:true };
return this;
}
/**
Add a function that will be called after this call is completed
@param {function} call function to be called when this call is completed, which is passed the following parameters:
@param {object} call.request the ExpressJS / HTTP request object
@param {object} call.input the input parameters to the call
@param call.err the resulting err from the call
@param {object} call.result - the result of the call
@param {function} call.next - to be called when the callback is completed, causing the next call back to be called
@param {string} phase the phase in which this function will be called (see below)
*Phases*
1. (Pre call phases)
2. **CALL**
3. first - called before any other phases
4. data - data processing and conversion
5. event - event emission
6. cache - cache phase
7. last - called last
@example
api.get("load","/user/:userID").addPostCall(function(req,input,err,result,next){
//Let's caclulate the users age:
if(result && result.birthdate){
result.age = User.calculateAge(result.birthdate);
}
next();
},"data");
@for Hop.Method
@method addPostCall
@chainable
**/
Hop.Method.postCallPhases = [ "first", "data", "event", "cache", "last" ];
Hop.Method.prototype.addPostCall=function(call,phase){
if(Hop.Method.postCallPhases.indexOf(phase)==-1)
throw "Invalid post call phase specified '"+phase+"' valid phases are "+Hop.Method.postCallPhases.join(", ");
this._postCall.push({ call: call, phase: phase });
this._postCall.sort(function(a,b){
var aPhase = Hop.Method.postCallPhases.indexOf(a.phase);
var bPhase = Hop.Method.postCallPhases.indexOf(b.phase);
if(aPhase < bPhase){
return -1;
} else if(bPhase > aPhase){
return 1;
} else return 0;
});
return this;
}
/**
Add a function that will be called before this call is executed
@param {function} call function to be called prior to when this call is executed , which is passed the following parameters:
@param {object} call.request the ExpressJS / HTTP request object
@param {object} call.input the input parameters to the call
@param {function} call.onComplete to be called if the function wants to short circuit and return a result
@param {function} call.next to be called when the callback is completed, causing the next call back to be called
@param {string} phase the phase in which this function will be called (see below)
*Phases*
1. first - called first
2. demand - verifies that the required parameters are in place
3. conversion - will convert the input types to the expected types
4. validation - input validation
5. auth - authentication phase
6. event - event emission
7. cache - cache phase
8. last - the last set of calls to be called prior to the function call
9. **CALL**
10. (post calls)
@example
api.get("load","/user/:userID").addPreCall(function(req,input,err,onComplete,next){
//If we have a user allow this call to complete
if(req && req.session && req.session.user){
next();
//If not return an error
} else {
return onComplete("Permission denied");
}
},"auth");
@for Hop.Method
@method addPreCall
@chainable
**/
Hop.Method.preCallPhases=[ "first","demand", "conversion", "validation", "auth", "event","cache","last"];
Hop.Method.prototype.addPreCall=function(call,phase){
if(Hop.Method.preCallPhases.indexOf(phase)==-1)
throw "Invalid pre call phase specified '"+phase+"' valid phases are "+Hop.Method.preCallPhases.join(", ");
this._preCall.push({ call: call, phase: phase });
this._preCall.sort(function(a,b){
var aPhase = Hop.Method.preCallPhases.indexOf(a.phase);
var bPhase = Hop.Method.preCallPhases.indexOf(b.phase);
if(aPhase < bPhase){
return -1;
} else if(bPhase > aPhase){
return 1;
} else return 0;
});
return this;
}
/**
Indicate that this method call has been depricated
@for Hop.Method
@method depricated
@chainable
**/
Hop.Method.prototype.depricated=function(){
this.depricated=true;
}
/**
Calls the specified method
This function is provided so that all functionality around a specific call may be utilized.
@param {string} name Name of the function to call
@param {object} input Input for the call
@param {function} callback for completion
@param callback.err The error returned from the call
@param callback.result The result returned from the call
@param {object} [request] ExpressJS/HTTP request object
@example
var input = { username: "cfox", email:"cfox@gmail.com"}
Hop.call("UserService.create",input,function(err,res){
Hop.log(err,res);
});
@for Hop
@method call
@static
**/
Hop.call=function(name,input,onComplete,request){
var output = {};
var obj = Hop.Object.findObject(name);
if(!obj)
throw ("Invalid object specified: "+name);
var method = Hop.Method.findMethod(name);
if(!method)
throw ("Invalid method specified: "+name);
for(var paramName in method.defaults){
output[paramName]=method.defaults[paramName];
}
for(var paramName in method.params){
var param = method.params[paramName];
if(typeof input[paramName]!="undefined"){
output[paramName]=input[paramName];
}
}
for(var paramName in method.params){
if(param.demand && typeof output[paramName]=="undefined")
throw ("Missing parameter:" +paramName);
}
if(obj._instance){
if(!obj._instance[method.name])
throw ("Method not found on object instance: "+method.name);
var preTasks = method._preCall.length;
var runPreTaskFunctions=function Hop_call_preTask(){
if(preTasks>0){
method._preCall[method._preCall.length-preTasks].call(request,output,onComplete,function Hop_call_preCall(){
preTasks--;
runPreTaskFunctions();
});
} else {
obj._instance[method.name](output,function Hop_call_instance(err,result){
var postTasks = method._postCall.length;
var runPostTaskFunctions=function Hop_call_postTask(){
if(postTasks>0){
method._postCall[method._postCall.length-postTasks].call(request,output,err,result,function Hop_call_postCall(){
postTasks--;
runPostTaskFunctions();
});
} else {
return onComplete(err,result);
}
}
runPostTaskFunctions();
},request);
}
}
runPreTaskFunctions();
} else {
//FIXME
throw "I don't know what to do here";
}
}
var Stream = require('stream');
Hop.DataEncoder=util.DataEncoder;
Hop.addslashes=function(string) {
return string.replace(/\\/g, '\\\\').
replace(/\u0008/g, '\\b').
replace(/\t/g, '\\t').
replace(/\n/g, '\\n').
replace(/\f/g, '\\f').
replace(/\r/g, '\\r').
replace(/'/g, '\\\'').
replace(/"/g, '\\"');
}
module.exports=Hop;