<commit_before>/**
  *   Modelizer - by <NAME>
  *   https://github.com/dreampulse/modelizer
  */

////// Micro libs ////

function assert(condition, message) {
  if (!condition) {
    console.log('Assertion failed', message);
    console.trace();
    throw new Error(message || "Assertion failed");
  }
}

function check(condition, message) {
  if (!condition) {
    throw new Error(message || "Check failed");
  }
}

// http://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object-from-json
function isEmptyObject(obj) {
  var name;
  for (name in obj) {
    return false;
  }
  return true;
}


////// Platform dependent code ///

// http://stackoverflow.com/questions/4224606/how-to-check-whether-a-script-is-running-under-node-js
if (typeof window === 'undefined') {
  // assume running in node environment
  var Q = require('q');
} else {
  // assume running in browser environment
  assert(Q !== undefined, 'Missing Q in the enviroment');
}

/////////////////////////
// All models which ever are being created
// not used so far
var AllModels = [];

//////////////////////////
// The Model implementation

var Model = function(modelName, schema) {
  this.self = this;

  this.modelName = modelName;
  this.schema = schema;

  // im scope der Model-Methode speichere ich alles was ich dem Modell übergeben kann
  // also Attribute, operationen usw..
  // die Funktionen hierfür sehen eigentlich immer gleich auch
  // deswegen sollte das auch in ein meta-modell wandern

  this.attrs = {};


  // a reference to another model
  this.attrRefs = {};

  // a reference to another model
  this.attrObjs = {};

  // an inline object
  this.methods = {};

  this.methodImpls = {};

  this.virtualAttrs = {};

  this.virtualAttrImpls = {};

  this.attrArrays = {};

  this.attrRefArrays = {};

  this.operations = {};

  this.operationImpls = {};

  this.factorys = {};

  this.factoryImpls = {};

  // setup filters for using the model
  this.readFilters = [];


  // setup write filters for using the model
  this.writeFilters = [];



  /////////////////
  // Constructing

  // 'execute' schema definition
  if (this.schema) { // a schema was provided
    this.processSchema(this.schema);
  }


  // set connection for all models
  if (Model.globalConnection) {
    this.connection(Model.globalConnection);
  }

  // set express for all models
  if (Model.globalExpress) {
    this.express(Model.globalExpress);
    this.serve();
  }


  // init sample Server
  if (Model.sampleServer) {
    console.log("init sample server for", this.modelName);

    // say that our model should use express and the database connector
    this.connection(Model.sampleServer.connector);
    this.express(Model.sampleServer.app);
    this.serve();
  }

  // store instance of this model
  AllModels.push(this);

  return this;
};



// "Subclasses"

// The Storage-System using Mongo-DB
Model.MongoConnector = function(databaseConnection) {
  var db = databaseConnection;

  return function(theModel) {
    var collection = db.collection(theModel.modelName);

    // extensions for operation & factory call
    collection.callOperation = function(opName, params, HTMLrequest) {
      var deferred = Q.defer();

      var res;
      if (theModel.operations.hasOwnProperty(opName)) {  // call is an operation
        res = theModel.operationImpls[opName](params, HTMLrequest);
      } else if (theModel.factorys.hasOwnProperty(opName)) {  // call is a factory
        res = theModel.factoryImpls[opName](params, HTMLrequest);
      } else {
        assert(false, "Call '"+opName+"' was not defined");
      }

      if (res != undefined && res.name == 'promise') return res;  // if the function already returns a promise
      deferred.resolve(res);
      return deferred.promise;
    };

    // findOne overwrite - workaround for Bug #10
    collection.findOne = function(search, callback) {
      collection.find(search, function(err, docs) {
        if (err) {
          callback(err, docs);
          return;
        }
        if (docs.lengh < 1) {
          callback(err, null);
          return;
        }
        callback(err, docs[0]);
      });
    };

    return collection;  // the collection for this model
  }
};


// Using the REST-Interface
Model.AngularConnector = function(connectionUrl) {
  assert(angular !== undefined, "AngularJS has to be loaded!");

  var $http = angular.injector(['ng']).get('$http');

  var findUnpackInterceptor = function(model, callback) {
    return function(err, docs) {
      if (err == undefined) {
        for (var i=0; i<docs.length; i++) {
          model._transform(model, docs[i], 'unpack');
        }
      }
      callback(err, docs);
    }
  }

  var findOneUnpackInterceptor = function(model, callback) {
    return function(err, doc) {
      if (err == undefined) {
        model._transform(model, doc, 'unpack');
      }
      callback(err, doc);
    }
  }


  // ajax Call to the server backend
  var ajaxCall = function(method, url, data, callback) {

    $http({method:method, url:url, data:data, withCredentials: true, cache: false})
      .success(function(data, status, headers, config) {
        if (status == 200) {
          if (!status.hasOwnProperty("error")) {
            callback(undefined, data);
          } else {
            callback(status.error, null);
          }
        } else {
          callback(new Error("HTTP Response != 200"), null);
        }
      })
      .error(function(data, status, headers, config) {
        if (data.hasOwnProperty("error")) {
          callback(new Error(data.error), null);
        } else {
          callback(new Error("Error in $http-request"), null);
        }
      });
  };


  return function(theModel) {
    return {
      find : function(search, callback) {
        if (isEmptyObject(search)) {
          ajaxCall('GET', connectionUrl + theModel.modelName + '/all', undefined, findUnpackInterceptor(theModel, callback));
        } else {
          ajaxCall('POST', connectionUrl + theModel.modelName + '/find', search, findUnpackInterceptor(theModel, callback));
        }
      },
      findOne : function(search, callback) {
        assert(search.hasOwnProperty("_id"), "Only searching for id implemented so far");
        ajaxCall('GET', connectionUrl + theModel.modelName + '/' + search._id, undefined, findOneUnpackInterceptor(theModel, callback));
      },
      save : function(doc, callback) {
        theModel._transform(theModel, doc, 'pack');
        ajaxCall('PUT', connectionUrl + theModel.modelName + '/', doc, callback);
      },
      remove : function(id, ignored, callback) {
        ajaxCall('DELETE', connectionUrl + theModel.modelName + '/' + id._id, undefined, callback);
      },
      callOperation : function(opName, params, HTMLrequest) {
        var deferred = Q.defer();
        ajaxCall('PUT', connectionUrl + theModel.modelName + '/' + opName, params, function(err, result) {

          if (theModel.operations.hasOwnProperty(opName)) {  // call is an operation

            if (err) deferred.reject(err);
            else deferred.resolve(result);

          } else if (theModel.factorys.hasOwnProperty(opName)) {  // call is an factory

            if (err) {
              deferred.reject(err);
              return deferred.promise;
            }

            // restore object from document
            if (Array.isArray(result)) {  // result is a collection
              // für jedes document in der DB ein object anlegen
              for (var i=0; i<result.length; i++) {
                result[i] = theModel.loadFromDoc(result[i]);
              }
            } else {  // result is one object
              result = theModel.loadFromDoc(result);  // restore one object
            }

            deferred.resolve(result);

          } else {
            assert(false, "operation or factory is not defined");
          }
        });

        return deferred.promise;
      }
    }
  }
};

///////////////////////////
// Methods

Model.prototype.attr = function(attrName) {
  var filters = [];
  for (var i in arguments) {
    if (i != '0')  // except for attrName
      filters.push(arguments[i]);
  }
  this.attrs[attrName] = {name : attrName, filters : filters};

  return this;
};


Model.prototype.attrRef = function(attrName, reference) {
  this.attrRefs[attrName] = reference;

  return this;
};


Model.prototype.attrObj = function(attrName, obj) {
  this.attrObjs[attrName] = obj;

  return this;
};


Model.prototype.method = function(methodName) {
  this.methods[methodName] = methodName;

  return this;
};


Model.prototype.methodImpl = function(methodName, fnImpl) {
  this.methodImpls[methodName] = fnImpl;

  return this;
};


Model.prototype.virtualAttr = function(attrName, attrType) {
  this.virtualAttrs[attrName] = {name : attrName, type : attrType};

  return this;
};


Model.prototype.virtualAttrImpl = function(virtualAttrName, fnImpl) {
  this.virtualAttrImpls[virtualAttrName] = fnImpl;

  return this;
};


Model.prototype.attrArray = function(attrName, obj) {
  this.attrArrays[attrName] = obj;

  return this;
};


Model.prototype.attrRefArray = function(attrName, obj) {
  this.attrRefArrays[attrName] = obj;

  return this;
};


Model.prototype.operation = function(operationName) {
  this.operations[operationName] = operationName;
  this[operationName] = function(params) {
    assert(this.collection != undefined, "Use a connectior!");
    return this.callOp(operationName, params, null);
  };

  return this;
};


Model.prototype.operationImpl = function(operationName, fnImpl) {
  this.operationImpls[operationName] = fnImpl;

  return this;
};


Model.prototype.factory = function(factoryName) {
  this.factorys[factoryName] = factoryName;
  this[factoryName] = function(params) {
    return this.callOp(factoryName, params, null);
  };

  return this;
};


Model.prototype.factoryImpl = function(factoryName, fnImpl) {
  this.factoryImpls[factoryName] = fnImpl;

  return this;
};


// setup connection stuff
Model.prototype.connection = function(connector) {
  assert(connector !== undefined, "Don't set a undefined connection!");
  this.connector = connector;
  this.collection = connector(this);
};


// setup webserver stuff
Model.prototype.express = function(app) {
  this.app = app;
};


// der express teil :-)
Model.prototype.serve = function() {
  var self = this;

  // filter to allow cross origin requests
  this.app.all('*', function(req, res, next) {
    res.header('Access-Control-Allow-Credentials', true);
    //res.header("Access-Control-Allow-Origin", "*");
    res.header('Access-Control-Allow-Origin', req.headers.origin);
    res.header('Access-Control-Allow-Methods', 'OPTIONS,GET,POST,PUT,DELETE');
    res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
    if ('OPTIONS' == req.method) { return res.send(200); }
    next();
  });


  // REST - GET all
  this.app.get('/' + this.modelName + '/all', function(req, res){

    res.setHeader('Content-Type', 'application/json');
    self.filtered_all(req)
      .then(function(docs) {
        // pack data for transport
        for (var i=0; i<docs.length; i++) {
          self._transform(self, docs[i], "pack");
        }
        res.send(JSON.stringify(docs, null, 2));
      })
      .fail(function (err) {
        console.log("Error in HTTP GET - all()\n", err.message, "Stack:\n", err.stack);
        res.send(err.statusCode || 500, {error: err.message} );
      }).done();
  });

  /*
   * Security Issue
   * transform ObjectID!!
   */

  // REST - GET find
  this.app.post('/' + this.modelName + '/find', function(req, res){
    res.setHeader('Content-Type', 'application/json');
    self.filtered_find(req.body, req)
      .then(function(docs) {
        // pack data for transport
        for (var i=0; i<docs.length; i++) {
          self._transform(self, docs[i], "pack");
        }
        res.send(JSON.stringify(docs, null, 2));
      })
      .fail(function (err) {
        console.log("Error in HTTP GET - find()\n", err.message, "Stack:\n", err.stack);
        res.send(err.statusCode || 500, {error: err.message} );
      }).done();
  });


  // REST - GET id
  this.app.get('/' + this.modelName + '/:id', function(req, res) {
    res.setHeader('Content-Type', 'application/json');
    self.filtered_get(req.params.id, req)
      .then(function(doc) {
        // pack data for transport
        self._transform(self, doc, "pack");
        res.send(JSON.stringify(doc, null, 2));
      })
      .fail(function (err) {
        console.log("Error in HTTP GET - get()\n", err.message, "Stack:\n", err.stack);
        res.send(err.statusCode || 500, {error: err.message} );
      }).done();
  });

  // REST - save doc
  this.app.put('/' + this.modelName, function(req, res) {
    assert(req.body != undefined, "request body missing! Use bodyParser!");

    res.setHeader('Content-Type', 'application/json');

    // client sendet obj._id als string ->  in ObjectId umwandeln
    if (req.body._id != undefined) {
      var ObjectId = require('mongojs').ObjectId;
      try {
        req.body._id = ObjectId(req.body._id);
      } catch (e) {
        res.send(err.statusCode || 400, {error: "Invalid ObjectId Format"} );
        return;
      }
    }

    // unpack data for storage
    self._transform(self, req.body, "unpack");

    self.filtered_save(req.body, req)
      .then(function(doc) {
        res.send(JSON.stringify(doc, null, 2));
      })
      .fail(function (err) {
        console.log("Error in HTTP GET - save()\n", err.message, "Stack:\n", err.stack);
        res.send(err.statusCode || 500, {error: err.message} );
      }).done();
  });

  // REST - remove doc
  this.app.del('/' + this.modelName + '/:id', function(req, res) {
    res.setHeader('Content-Type', 'application/json');
    self.filtered_remove(req.params.id, req)
      .then(function() {
        res.send(200);
      })
      .fail(function (err) {
        console.log("Error in HTTP GET - remove()\n", err.message, "Stack:\n", err.stack);
        res.send(err.statusCode || 500, {error: err.message} );
      }).done();
  });


  // REST - PUT operation
  this.app.put('/' + this.modelName + '/:op', function(req, res) {
    res.setHeader('Content-Type', 'application/json');
    assert(req.body != undefined, "No body in request!")
    self.filtered_callOp(req.params.op, req.body, req)
      .then(function(result) {
        res.send(200, result);
      })
      .fail(function (err) {
        console.log("Error in HTTP GET - operation()\n", err.message, "Stack:\n", err.stack);
        res.send(err.statusCode || 500, {error: err.message} );
      }).done();

  });

};


// simuliert das verhalten einer referenz auf ein anderes Object
// Zugriff dann mit object.ref().XXX
Model.prototype.reference = function(refModel, parentModel) {

  // das weiterreichn wollte macht irgendwie probleme (sollte eigentlich auch ohne funktionieren) TODO
  refModel.connection(parentModel.connector);  // mongodb connection an child modell durchreichen

  this.create = function() {
    var refObj = refModel.create();
    this.ref = function() { return refObj; }  // definition von der .ref()-Methode
    return this.ref();
  }

  // load object
  // todo: soll erst verfügbar sein, wenns auch was zum laden gibt
  this.load = function() {
    var loadScope = this;<commit_msg>Error logging updated<commit_after>/**
  *   Modelizer - by <NAME>
  *   https://github.com/dreampulse/modelizer
  */

////// Micro libs ////

function assert(condition, message) {
  if (!condition) {
    console.log('Assertion failed', message);
    console.trace();
    throw new Error(message || "Assertion failed");
  }
}

function check(condition, message) {
  if (!condition) {
    throw new Error(message || "Check failed");
  }
}

// http://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object-from-json
function isEmptyObject(obj) {
  var name;
  for (name in obj) {
    return false;
  }
  return true;
}


////// Platform dependent code ///

// http://stackoverflow.com/questions/4224606/how-to-check-whether-a-script-is-running-under-node-js
if (typeof window === 'undefined') {
  // assume running in node environment
  var Q = require('q');
} else {
  // assume running in browser environment
  assert(Q !== undefined, 'Missing Q in the enviroment');
}

/////////////////////////
// All models which ever are being created
// not used so far
var AllModels = [];

//////////////////////////
// The Model implementation

var Model = function(modelName, schema) {
  this.self = this;

  this.modelName = modelName;
  this.schema = schema;

  // im scope der Model-Methode speichere ich alles was ich dem Modell übergeben kann
  // also Attribute, operationen usw..
  // die Funktionen hierfür sehen eigentlich immer gleich auch
  // deswegen sollte das auch in ein meta-modell wandern

  this.attrs = {};


  // a reference to another model
  this.attrRefs = {};

  // a reference to another model
  this.attrObjs = {};

  // an inline object
  this.methods = {};

  this.methodImpls = {};

  this.virtualAttrs = {};

  this.virtualAttrImpls = {};

  this.attrArrays = {};

  this.attrRefArrays = {};

  this.operations = {};

  this.operationImpls = {};

  this.factorys = {};

  this.factoryImpls = {};

  // setup filters for using the model
  this.readFilters = [];


  // setup write filters for using the model
  this.writeFilters = [];



  /////////////////
  // Constructing

  // 'execute' schema definition
  if (this.schema) { // a schema was provided
    this.processSchema(this.schema);
  }


  // set connection for all models
  if (Model.globalConnection) {
    this.connection(Model.globalConnection);
  }

  // set express for all models
  if (Model.globalExpress) {
    this.express(Model.globalExpress);
    this.serve();
  }


  // init sample Server
  if (Model.sampleServer) {
    console.log("init sample server for", this.modelName);

    // say that our model should use express and the database connector
    this.connection(Model.sampleServer.connector);
    this.express(Model.sampleServer.app);
    this.serve();
  }

  // store instance of this model
  AllModels.push(this);

  return this;
};



// "Subclasses"

// The Storage-System using Mongo-DB
Model.MongoConnector = function(databaseConnection) {
  var db = databaseConnection;

  return function(theModel) {
    var collection = db.collection(theModel.modelName);

    // extensions for operation & factory call
    collection.callOperation = function(opName, params, HTMLrequest) {
      var deferred = Q.defer();

      var res;
      if (theModel.operations.hasOwnProperty(opName)) {  // call is an operation
        res = theModel.operationImpls[opName](params, HTMLrequest);
      } else if (theModel.factorys.hasOwnProperty(opName)) {  // call is a factory
        res = theModel.factoryImpls[opName](params, HTMLrequest);
      } else {
        assert(false, "Call '"+opName+"' was not defined");
      }

      if (res != undefined && res.name == 'promise') return res;  // if the function already returns a promise
      deferred.resolve(res);
      return deferred.promise;
    };

    // findOne overwrite - workaround for Bug #10
    collection.findOne = function(search, callback) {
      collection.find(search, function(err, docs) {
        if (err) {
          callback(err, docs);
          return;
        }
        if (docs.lengh < 1) {
          callback(err, null);
          return;
        }
        callback(err, docs[0]);
      });
    };

    return collection;  // the collection for this model
  }
};


// Using the REST-Interface
Model.AngularConnector = function(connectionUrl) {
  assert(angular !== undefined, "AngularJS has to be loaded!");

  var $http = angular.injector(['ng']).get('$http');

  var findUnpackInterceptor = function(model, callback) {
    return function(err, docs) {
      if (err == undefined) {
        for (var i=0; i<docs.length; i++) {
          model._transform(model, docs[i], 'unpack');
        }
      }
      callback(err, docs);
    }
  }

  var findOneUnpackInterceptor = function(model, callback) {
    return function(err, doc) {
      if (err == undefined) {
        model._transform(model, doc, 'unpack');
      }
      callback(err, doc);
    }
  }


  // ajax Call to the server backend
  var ajaxCall = function(method, url, data, callback) {

    $http({method:method, url:url, data:data, withCredentials: true, cache: false})
      .success(function(data, status, headers, config) {
        if (status == 200) {
          if (!status.hasOwnProperty("error")) {
            callback(undefined, data);
          } else {
            callback(status.error, null);
          }
        } else {
          callback(new Error("HTTP Response != 200"), null);
        }
      })
      .error(function(data, status, headers, config) {
        if (data.hasOwnProperty("error")) {
          callback(new Error(data.error), null);
        } else {
          callback(new Error("Error in $http-request"), null);
        }
      });
  };


  return function(theModel) {
    return {
      find : function(search, callback) {
        if (isEmptyObject(search)) {
          ajaxCall('GET', connectionUrl + theModel.modelName + '/all', undefined, findUnpackInterceptor(theModel, callback));
        } else {
          ajaxCall('POST', connectionUrl + theModel.modelName + '/find', search, findUnpackInterceptor(theModel, callback));
        }
      },
      findOne : function(search, callback) {
        assert(search.hasOwnProperty("_id"), "Only searching for id implemented so far");
        ajaxCall('GET', connectionUrl + theModel.modelName + '/' + search._id, undefined, findOneUnpackInterceptor(theModel, callback));
      },
      save : function(doc, callback) {
        theModel._transform(theModel, doc, 'pack');
        ajaxCall('PUT', connectionUrl + theModel.modelName + '/', doc, callback);
      },
      remove : function(id, ignored, callback) {
        ajaxCall('DELETE', connectionUrl + theModel.modelName + '/' + id._id, undefined, callback);
      },
      callOperation : function(opName, params, HTMLrequest) {
        var deferred = Q.defer();
        ajaxCall('PUT', connectionUrl + theModel.modelName + '/' + opName, params, function(err, result) {

          if (theModel.operations.hasOwnProperty(opName)) {  // call is an operation

            if (err) deferred.reject(err);
            else deferred.resolve(result);

          } else if (theModel.factorys.hasOwnProperty(opName)) {  // call is an factory

            if (err) {
              deferred.reject(err);
              return deferred.promise;
            }

            // restore object from document
            if (Array.isArray(result)) {  // result is a collection
              // für jedes document in der DB ein object anlegen
              for (var i=0; i<result.length; i++) {
                result[i] = theModel.loadFromDoc(result[i]);
              }
            } else {  // result is one object
              result = theModel.loadFromDoc(result);  // restore one object
            }

            deferred.resolve(result);

          } else {
            assert(false, "operation or factory is not defined");
          }
        });

        return deferred.promise;
      }
    }
  }
};

///////////////////////////
// Methods

Model.prototype.attr = function(attrName) {
  var filters = [];
  for (var i in arguments) {
    if (i != '0')  // except for attrName
      filters.push(arguments[i]);
  }
  this.attrs[attrName] = {name : attrName, filters : filters};

  return this;
};


Model.prototype.attrRef = function(attrName, reference) {
  this.attrRefs[attrName] = reference;

  return this;
};


Model.prototype.attrObj = function(attrName, obj) {
  this.attrObjs[attrName] = obj;

  return this;
};


Model.prototype.method = function(methodName) {
  this.methods[methodName] = methodName;

  return this;
};


Model.prototype.methodImpl = function(methodName, fnImpl) {
  this.methodImpls[methodName] = fnImpl;

  return this;
};


Model.prototype.virtualAttr = function(attrName, attrType) {
  this.virtualAttrs[attrName] = {name : attrName, type : attrType};

  return this;
};


Model.prototype.virtualAttrImpl = function(virtualAttrName, fnImpl) {
  this.virtualAttrImpls[virtualAttrName] = fnImpl;

  return this;
};


Model.prototype.attrArray = function(attrName, obj) {
  this.attrArrays[attrName] = obj;

  return this;
};


Model.prototype.attrRefArray = function(attrName, obj) {
  this.attrRefArrays[attrName] = obj;

  return this;
};


Model.prototype.operation = function(operationName) {
  this.operations[operationName] = operationName;
  this[operationName] = function(params) {
    assert(this.collection != undefined, "Use a connectior!");
    return this.callOp(operationName, params, null);
  };

  return this;
};


Model.prototype.operationImpl = function(operationName, fnImpl) {
  this.operationImpls[operationName] = fnImpl;

  return this;
};


Model.prototype.factory = function(factoryName) {
  this.factorys[factoryName] = factoryName;
  this[factoryName] = function(params) {
    return this.callOp(factoryName, params, null);
  };

  return this;
};


Model.prototype.factoryImpl = function(factoryName, fnImpl) {
  this.factoryImpls[factoryName] = fnImpl;

  return this;
};


// setup connection stuff
Model.prototype.connection = function(connector) {
  assert(connector !== undefined, "Don't set a undefined connection!");
  this.connector = connector;
  this.collection = connector(this);
};


// setup webserver stuff
Model.prototype.express = function(app) {
  this.app = app;
};


// der express teil :-)
Model.prototype.serve = function() {
  var self = this;

  // filter to allow cross origin requests
  this.app.all('*', function(req, res, next) {
    res.header('Access-Control-Allow-Credentials', true);
    //res.header("Access-Control-Allow-Origin", "*");
    res.header('Access-Control-Allow-Origin', req.headers.origin);
    res.header('Access-Control-Allow-Methods', 'OPTIONS,GET,POST,PUT,DELETE');
    res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
    if ('OPTIONS' == req.method) { return res.send(200); }
    next();
  });


  // REST - GET all
  this.app.get('/' + this.modelName + '/all', function(req, res){

    res.setHeader('Content-Type', 'application/json');
    self.filtered_all(req)
      .then(function(docs) {
        // pack data for transport
        for (var i=0; i<docs.length; i++) {
          self._transform(self, docs[i], "pack");
        }
        res.send(JSON.stringify(docs, null, 2));
      })
      .fail(function (err) {
        if (!err.statusCode) console.log("Error in HTTP GET - all()\n", err.message, "\nStack:\n", err.stack);
        res.send(err.statusCode || 500, {error: err.message} );
      }).done();
  });

  /*
   * Security Issue
   * transform ObjectID!!
   */

  // REST - GET find
  this.app.post('/' + this.modelName + '/find', function(req, res){
    res.setHeader('Content-Type', 'application/json');
    self.filtered_find(req.body, req)
      .then(function(docs) {
        // pack data for transport
        for (var i=0; i<docs.length; i++) {
          self._transform(self, docs[i], "pack");
        }
        res.send(JSON.stringify(docs, null, 2));
      })
      .fail(function (err) {
        if (!err.statusCode) console.log("Error in HTTP GET - find()\n", err.message, "\nStack:\n", err.stack);
        res.send(err.statusCode || 500, {error: err.message} );
      }).done();
  });


  // REST - GET id
  this.app.get('/' + this.modelName + '/:id', function(req, res) {
    res.setHeader('Content-Type', 'application/json');
    self.filtered_get(req.params.id, req)
      .then(function(doc) {
        // pack data for transport
        self._transform(self, doc, "pack");
        res.send(JSON.stringify(doc, null, 2));
      })
      .fail(function (err) {
        if (!err.statusCode) console.log("Error in HTTP GET - get()\n", err.message, "\nStack:\n", err.stack);
        res.send(err.statusCode || 500, {error: err.message} );
      }).done();
  });

  // REST - save doc
  this.app.put('/' + this.modelName, function(req, res) {
    assert(req.body != undefined, "request body missing! Use bodyParser!");

    res.setHeader('Content-Type', 'application/json');

    // client sendet obj._id als string ->  in ObjectId umwandeln
    if (req.body._id != undefined) {
      var ObjectId = require('mongojs').ObjectId;
      try {
        req.body._id = ObjectId(req.body._id);
      } catch (e) {
        res.send(err.statusCode || 400, {error: "Invalid ObjectId Format"} );
        return;
      }
    }

    // unpack data for storage
    self._transform(self, req.body, "unpack");

    self.filtered_save(req.body, req)
      .then(function(doc) {
        res.send(JSON.stringify(doc, null, 2));
      })
      .fail(function (err) {
        if (!err.statusCode) console.log("Error in HTTP GET - save()\n", err.message, "\nStack:\n", err.stack);
        res.send(err.statusCode || 500, {error: err.message} );
      }).done();
  });

  // REST - remove doc
  this.app.del('/' + this.modelName + '/:id', function(req, res) {
    res.setHeader('Content-Type', 'application/json');
    self.filtered_remove(req.params.id, req)
      .then(function() {
        res.send(200);
      })
      .fail(function (err) {
        if (!err.statusCode) console.log("Error in HTTP GET - remove()\n", err.message, "\nStack:\n", err.stack);
        res.send(err.statusCode || 500, {error: err.message} );
      }).done();
  });


  // REST - PUT operation
  this.app.put('/' + this.modelName + '/:op', function(req, res) {
    res.setHeader('Content-Type', 'application/json');
    assert(req.body != undefined, "No body in request!")
    self.filtered_callOp(req.params.op, req.body, req)
      .then(function(result) {
        res.send(200, result);
      })
      .fail(function (err) {
        if (!err.statusCode) console.log("Error in HTTP GET - operation()\n", err.message, "\nStack:\n", err.stack);
        res.send(err.statusCode || 500, {error: err.message} );
      }).done();

  });

};


// simuliert das verhalten einer referenz auf ein anderes Object
// Zugriff dann mit object.ref().XXX
Model.prototype.reference = function(refModel, parentModel) {

  // das weiterreichn wollte macht irgendwie probleme (sollte eigentlich auch ohne funktionieren) TODO
  refModel.connection(parentModel.connector);  // mongodb connection an child modell durchreichen

  this.create = function() {
    var refObj = refModel.create();
    this.ref = function() { return refObj; }  // definition von der .ref()-Methode
    return this.ref();
  }

  // load object
  // todo: soll erst verfügbar sein, wenns auch was zum laden gibt
  this.load = function() {
    var loadScope = this;
{'fraction_non_alphanumeric': 0.1520206918849014, 'fraction_numerical': 0.004009052699644358, 'mean_word_length': 2.6835774681433846, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 7, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6912010', 'n_tokens_mistral': 9639, 'n_tokens_neox': 9293, 'n_words': 2215}