/**
* MOST Web Framework
* A JavaScript Web Framework
* http://themost.io
*
* Copyright (c) 2014, Kyriakos Barbounakis k.barbounakis@gmail.com, Anthi Oikonomou anthioikonomou@gmail.com
*
* Released under the BSD3-Clause license
* Date: 2014-06-09
*/
/**
* @ignore
*/
var util = require('util'),
mvc = require('./http-mvc'),
xml = require('most-xml'),
string = require('string'),
common = require('./common');
/**
* @classdesc HttpDataController class describes a common MOST Web Framework data controller.
* This controller is inherited by default from all data models. It offers a set of basic actions for CRUD operations against data objects
* and allows filtering, paging, sorting and grouping data objects with options similar to [OData]{@link http://www.odata.org/}.
<h2>Basic Features</h2>
<h3>Data Filtering ($filter query option)</h3>
<p>Logical Operators</p>
<p>The following table contains the logical operators supported in the query language:</p>
<table class="table-flat">
<thead><tr><th>Operator</th><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>eq</td><td>Equal</td><td>/Order/index.json?$filter=customer eq 353</td></tr>
<tr><td>ne</td><td>Not Equal</td><td>/Order/index.json?$filter=orderStatus/alternateName ne 'OrderDelivered'</td></tr>
<tr><td>gt</td><td>Greater than</td><td>/Order/index.json?$filter=orderedItem/price gt 1000</td></tr>
<tr><td>ge</td><td>Greater than or equal</td><td>/Order/index.json?$filter=orderedItem/price ge 500</td></tr>
<tr><td>lt</td><td>Lower than</td><td>/Order/index.json?$filter=orderedItem/price lt 500</td></tr>
<tr><td>le</td><td>Lower than or equal</td><td>/Order/index.json?$filter=orderedItem/price le 1000</td></tr>
<tr><td>and</td><td>Logical and</td><td>/Order/index.json?$filter=orderedItem/price gt 1000 and orderStatus/alternateName eq 'OrderPickup'</td></tr>
<tr><td>or</td><td>Logical or</td><td>/Order/index.json?$filter=orderStatus/alternateName eq 'OrderPickup' or orderStatus/alternateName eq 'OrderProcessing'</td></tr>
</tbody>
</table>
<p>Arithmetic Operators</p>
<p>The following table contains the arithmetic operators supported in the query language:</p>
<table class="table-flat">
<thead><tr><th>Operator</th><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>add</td><td>Addition</td><td>/Order/index.json?$filter=(orderedItem/price add 10) gt 1560</td></tr>
<tr><td>sub</td><td>Subtraction</td><td>/Order/index.json?$filter=(orderedItem/price sub 10) gt 1540</td></tr>
<tr><td>mul</td><td>Multiplication</td><td>/Order/index.json?$filter=(orderedItem/price mul 1.20) gt 1000</td></tr>
<tr><td>div</td><td>Division</td><td>/Order/index.json?$filter=(orderedItem/price div 2) le 500</td></tr>
<tr><td>mod</td><td>Modulo</td><td>/Order/index.json?$filter=(orderedItem/price mod 2) eq 0</td></tr>
</tbody>
</table>
<p>Functions</p>
<p>A set of functions are also defined for use in $filter query option:</p>
<table class="table-flat">
<thead><tr><th>Function</th><th>Example</th></tr></thead>
<tbody>
<tr><td colspan="2"><b>String Functions</b></td></tr>
<tr><td>startswith(field,string)</td><td>/Product/index.json?$filter=startswith(name,'Apple') eq true</td></tr>
<tr><td>endswith(field,string)</td><td>/Product/index.json?$filter=endswith(name,'Workstation') eq true</td></tr>
<tr><td>contains(field,string)</td><td>/Product/index.json?$filter=contains(name,'MacBook') eq true</td></tr>
<tr><td>length(field)</td><td>/Product/index.json?$filter=length(name) gt 40</td></tr>
<tr><td>indexof(field,string)</td><td>/Product/index.json?$filter=indexof(name,'Air') gt 1</td></tr>
<tr><td>substring(field,number)</td><td>/Product/index.json?$filter=substring(category,1) eq 'aptops'</td></tr>
<tr><td>substring(field,number,number)</td><td>/Product/index.json?$filter=substring(category,1,2) eq 'ap'</td></tr>
<tr><td>tolower(field)</td><td>/Product/index.json?$filter=tolower(category) eq 'laptops'</td></tr>
<tr><td>toupper(field)</td><td>/Product/index.json?$filter=toupper(category) eq 'LAPTOPS'</td></tr>
<tr><td>trim(field)</td><td>/Product/index.json?$filter=trim(category) eq 'Laptops'</td></tr>
<tr><td colspan="2"><b>Date Functions</b></td></tr>
<tr><td>day(field)</td><td>/Order/index.json?$filter=day(orderDate) eq 4</td></tr>
<tr><td>month(field)</td><td>/Order/index.json?$filter=month(orderDate) eq 6</td></tr>
<tr><td>year(field)</td><td>/Order/index.json?$filter=year(orderDate) ge 2014</td></tr>
<tr><td>hour(field)</td><td>/Order/index.json?$filter=hour(orderDate) ge 12 and hour(orderDate) lt 14</td></tr>
<tr><td>minute(field)</td><td>/Order/index.json?$filter=minute(orderDate) gt 15 and minute(orderDate) le 30</td></tr>
<tr><td>second(field)</td><td>/Order/index.json?$filter=second(orderDate) ge 0 and second(orderDate) le 45</td></tr>
<tr><td>date(field)</td><td>/Order/index.json?$filter=date(orderDate) eq '2015-03-20'</td></tr>
<tr><td colspan="2"><b>Math Functions</b></td></tr>
<tr><td>round(field)</td><td>/Product/index.json?$filter=round(price) le 389</td></tr>
<tr><td>floor(field)</td><td>/Product/index.json?$filter=floor(price) eq 389</td></tr>
<tr><td>ceiling(field)</td><td>/Product/index.json?$filter=ceiling(price) eq 390</td></tr>
</tbody>
</table>
<h3>Attribute Selection ($select query option)</h3>
<p>The following table contains attribute selection expressions supported in the query language:</p>
<table class="table-flat">
<thead><tr><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>Select attribute</td><td>/Order/index.json?$select=id,customer,orderStatus</td></tr>
<tr><td>Select attribute with alias</td><td>/Order/index.json?$select=id,customer/description as customerName,orderStatus/name as orderStatusName</td></tr>
<tr><td>Select attribute with aggregation</td><td>/Order/index.json?$select=count(id) as totalCount&$filter=orderStatus/alternateName eq 'OrderProcessing'</td></tr>
<tr><td> </td><td>/Product/index.json?$select=max(price) as maxPrice&$filter=category eq 'Laptops'</td></tr>
<tr><td> </td><td>/Product/index.json?$select=min(price) as minPrice&$filter=category eq 'Laptops'</td></tr>
</tbody>
</table>
<h3>Data Sorting ($orderby or $order query options)</h3>
<table class="table-flat">
<thead><tr><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>Ascending order</td><td>/Product/index.json?$orderby=name</td></tr>
<tr><td>Descending order</td><td>/Product/index.json?$orderby=category desc,name desc</td></tr>
</tbody>
</table>
<h3>Data Paging ($top, $skip and $inlinecount query options)</h3>
<p>The $top query option allows developers to apply paging in the result-set by giving the max number of records for each page. The default value is 25.
The $skip query option provides a way to skip a number of records. The default value is 0.
The $inlinecount query option includes in the result-set the total number of records of the query expression provided:
<pre class="prettyprint"><code>
{
"total": 94,
"records": [ ... ]
}
</code></pre>
<p>The default value is false.</p>
</p>
<table class="table-flat">
<thead><tr><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>Limit records</td><td>/Product/index.json?$top=5</td></tr>
<tr><td>Skip records</td><td>/Product/index.json?$top=5&$skip=5</td></tr>
<tr><td>Paged records</td><td>/Product/index.json?$top=5&$skip=5&$inlinecount=true</td></tr>
</tbody>
</table>
<h3>Data Grouping ($groupby or $group query options)</h3>
<p>The $groupby query option allows developers to group the result-set by one or more attributes</p>
<table class="table-flat">
<thead><tr><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>group</td><td>/Product/index.json?$select=count(id) as totalCount,category&$groupby=category</td></tr>
<tr><td>group and sort</td><td>/Product/index.json?$select=count(id) as totalCount,category&$groupby=category&$orderby=count(id) desc</td></tr>
</tbody>
</table>
<h3>Data Expanding ($expand)</h3>
<p>The $expand query option forces response to include associated objects which are not marked as expandable by default.</p>
<table class="table-flat">
<thead><tr><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>expand</td><td>/Order/index.json?$filter=orderStatus/alternateName eq 'OrderProcessing'&$expand=customer</td></tr>
</tbody>
</table>
<p>The $expand option is optional for a <a href="https://docs.themost.io/most-data/DataField.html">DataField</a> marked as expandable.</p>
* @class
* @constructor
* @augments HttpController
* @property {DataModel} model - Gets or sets the current data model.
* @memberOf module:most-web.controllers
*/
function HttpDataController()
{
var model_;
var self = this;
Object.defineProperty(this, 'model', {
get: function() {
if (model_)
return model_;
model_ = self.context.model(self.name);
return model_;
},
set: function(value) {
model_ = value;
}, configurable:false, enumerable:false
});
}
util.inherits(HttpDataController, mvc.HttpController);
/**
* Handles data object creation (e.g. /user/1/new.html, /user/1/new.json etc)
* @param {Function} callback
*/
HttpDataController.prototype.new = function (callback) {
try {
var self = this,
context = self.context;
context.handle(['GET'],function() {
callback(null, self.result());
}).handle(['POST', 'PUT'],function() {
var target = self.model.convert(context.params[self.model.name] || context.params.data, true);
self.model.save(target, function(err)
{
if (err) {
callback(common.httpError(err));
}
else {
if (context.params.attr('returnUrl'))
callback(null, context.params.attr('returnUrl'));
callback(null, self.result(target));
}
});
}).unhandle(function() {
callback(new common.HttpMethodNotAllowed());
});
}
catch (e) {
callback(common.httpError(e));
}
};
/**
* Handles data object edit (e.g. /user/1/edit.html, /user/1/edit.json etc)
* @param {Function} callback
*/
HttpDataController.prototype.edit = function (callback) {
try {
var self = this,
context = self.context;
context.handle(['POST', 'PUT'], function() {
//get context param
var target = self.model.convert(context.params[self.model.name] || context.params.data, true);
if (target) {
self.model.save(target, function(err)
{
if (err) {
console.log(err);
console.log(err.stack);
callback(common.httpError(err));
}
else {
if (context.params.attr('returnUrl'))
callback(null, context.params.attr('returnUrl'));
callback(null, self.result(target));
}
});
}
else {
callback(new common.HttpBadRequest());
}
}).handle('DELETE', function() {
//get context param
var target = context.params[self.model.name] || context.params.data;
if (target) {
//todo::check if object exists
self.model.remove(target, function(err)
{
if (err) {
callback(common.httpError(err));
}
else {
if (context.params.attr('returnUrl'))
callback(null, context.params.attr('returnUrl'));
callback(null, self.result(null));
}
});
}
else {
callback(new common.HttpBadRequest());
}
}).handle('GET', function() {
if (context.request.route) {
if (context.request.route.static) {
callback(null, self.result());
return;
}
}
//get context param (id)
var filter = null, id = context.params.attr('id');
if (id) {
//create the equivalent open data filter
filter = util.format('%s eq %s',self.model.primaryKey,id);
}
else {
//get the requested open data filter
filter = context.params.attr('$filter');
}
if (filter) {
self.model.filter(filter, function(err, q) {
if (err) {
callback(common.httpError(err));
return;
}
q.take(1, function (err, result) {
try {
if (err) {
callback(err);
}
else {
if (result.length>0)
callback(null, self.result(result));
else
callback(null, self.result(null));
}
}
catch (e) {
callback(common.httpError(e));
}
});
});
}
else {
callback(new common.HttpBadRequest());
}
}).unhandle(function() {
callback(new common.HttpMethodNotAllowed());
});
}
catch (e) {
callback(common.httpError(e));
}
};
HttpDataController.prototype.schema = function (callback) {
var self = this, context = self.context;
context.handle('GET', function() {
if (self.model) {
//prepare client model
var clone = JSON.parse(JSON.stringify(self.model));
var m = util._extend({}, clone);
//delete private properties
var keys = Object.keys(m);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key.indexOf("_")==0)
delete m[key];
}
//delete other server properties
delete m.view;
delete m.source;
delete m.fields;
delete m.privileges;
delete m.constraints;
delete m.eventListeners;
//set fields equal attributes
m.attributes = JSON.parse(JSON.stringify(self.model.attributes));
m.attributes.forEach(function(x) {
var mapping = self.model.inferMapping(x.name);
if (mapping)
x.mapping = JSON.parse(JSON.stringify(mapping));;
//delete private properties
delete x.value;
delete x.calculation;
});
//prepape views and view fields
if (m.views) {
m.views.forEach(function(view) {
if (view.fields) {
view.fields.forEach(function(field) {
if (/\./.test(field.name)==false) {
//extend view field
var name = field.name;
var mField = m.attributes.filter(function(y) {
return (y.name==name);
})[0];
if (mField) {
for (var key in mField) {
if (mField.hasOwnProperty(key) && !field.hasOwnProperty(key)) {
field[key] = mField[key];
}
}
}
}
});
}
});
}
callback(null, self.result(m));
}
else {
callback(new common.HttpNotFoundException());
}
}).unhandle(function() {
callback(new common.HttpMethodNotAllowed());
});
}
/**
* Handles data object display (e.g. /user/1/show.html, /user/1/show.json etc)
* @param {Function} callback
*/
HttpDataController.prototype.show = function (callback) {
try {
var self = this, context = self.context;
context.handle('GET', function() {
if (context.request.route) {
if (context.request.route.static) {
callback(null, self.result());
return;
}
}
var filter = null, id = context.params.attr('id');
if (id) {
//create the equivalent open data filter
filter = util.format('%s eq %s',self.model.primaryKey,id);
}
else {
//get the requested open data filter
filter = context.params.attr('$filter');
}
self.model.filter(filter, function(err, q) {
if (err) {
callback(common.httpError(err));
return;
}
q.take(1, function (err, result) {
try {
if (err) {
callback(common.httpError(e));
}
else {
if (result.length>0)
callback(null, self.result(result));
else
callback(new common.HttpNotFoundException('Item Not Found'));
}
}
catch (e) {
callback(common.httpError(e));
}
});
});
}).unhandle(function() {
callback(new common.HttpMethodNotAllowed());
});
}
catch (e) {
callback(e);
}
}
/**
* Handles data object deletion (e.g. /user/1/remove.html, /user/1/remove.json etc)
* @param {Function} callback
*/
HttpDataController.prototype.remove = function (callback) {
try {
var self = this, context = self.context;
context.handle(['POST','DELETE'], function() {
var target = context.params[self.model.name] || context.params.data;
if (target) {
self.model.remove(target, function(err)
{
if (err) {
callback(common.httpError(err));
}
else {
if (context.params.attr('returnUrl'))
callback(null, context.params.attr('returnUrl'));
callback(null, self.result(target));
}
});
}
else {
callback(new common.HttpBadRequest());
}
}).unhandle(function() {
callback(new common.HttpMethodNotAllowed());
});
}
catch (e) {
callback(common.httpError(e))
}
}
/**
* @param {Function(Error,DataQueryable)} callback
* @private
*/
HttpDataController.prototype.filter = function (callback) {
var self = this, params = self.context.params;
if (typeof self.model !== 'object' || self.model == null) {
callback(new Error('Model is of the wrong type or undefined.'));
return;
}
var filter = params['$filter'],
select = params['$select'],
skip = params['$skip'] || 0,
orderBy = params['$orderby'] || params.attr('$order'),
groupBy = params.attr('$group') || params.attr('$groupby'),
expand = params.attr('$expand');
self.model.filter(filter,
/**
* @param {Error} err
* @param {DataQueryable} q
*/
function (err, q) {
try {
if (err) {
callback(err);
}
else {
//set $groupby
if (groupBy) {
var arr = groupBy.split(',');
var fields = [];
for (var i = 0; i < arr.length; i++) {
var item = string(arr[i]).trim().toString();
var field = self.model.field(item);
if (field) {
fields.push(field.name);
}
else if (/(\w+)\((.*?)\)/i.test(item)) {
fields.push(q.fieldOf(item));
}
else if (/\//.test(item)) {
fields.push(item);
}
}
if (fields.length>0) {
q.groupBy(fields);
}
}
//set $select
if (select) {
var arr = select.split(',');
var fields = [];
for (var i = 0; i < arr.length; i++) {
var item = string(arr[i]).trim().toString();
var field = self.model.field(item);
if (field) {
fields.push(field.name);
}
else if (/(\w+)\((.*?)\)/i.test(item) || /^(\w+)\s+as\s+(.*?)$/i.test(item)) {
fields.push(q.fieldOf(item));
}
else if (/\//.test(item)) {
//pass nested field as string
fields.push(item);
}
}
if (fields.length>0) {
q.select(fields);
}
else {
//search for data view
if (arr.length==1) {
var view = self.model.dataviews(arr[0]);
if (view) {
q.select(view.name);
}
}
}
}
//set $skip
q.skip(skip);
//set $orderby
if (orderBy) {
var arr = orderBy.split(',');
for (var i = 0; i < arr.length; i++) {
var item = string(arr[i]).trim().toString(), name = null, direction = 'asc';
if (/ asc$/i.test(item)) {
name=item.substr(0,item.length-4);
}
else if (/ desc$/i.test(item)) {
direction = 'desc';
name=item.substr(0,item.length-5);
}
else if (!/\s/.test(item)) {
name = item;
}
if (name) {
var field = self.model.field(name);
//validate model field
if (field) {
if (direction=='desc')
q.orderByDescending(name);
else
q.orderBy(name);
}
//validate aggregate functions or associated field expression e.g. user/username
else if (/(\w+)\((.*?)\)/i.test(name) || /\//.test(name)) {
if (direction=='desc')
q.orderByDescending(name);
else
q.orderBy(name);
}
else if (/\//.test(name)) {
if (direction=='desc')
q.orderByDescending(name);
else
q.orderBy(name);
}
}
}
}
if (expand) {
if (expand.length>0) {
expand.split(',').map(function(x) { return x.replace(/\s/g,''); }).forEach(function(x) {
if (x.length)
q.expand(x.replace(/\s/g,''));
});
}
}
//return
callback(null, q);
}
}
catch (e) {
callback(e);
}
});
};
/**
*
* @param {Function} callback
*/
HttpDataController.prototype.index = function(callback)
{
try {
var self = this, context = self.context,
top = parseInt(self.context.params.$top),
take = top > 0 ? top : (top == -1 ? top : 25);
var count = /^true$/ig.test(context.params.attr('$inlinecount')) || false,
expand = context.params.attr('$expand'),
first = /^true$/ig.test(context.params.attr('$first')) || false,
asArray = /^true$/ig.test(context.params.attr('$array')) || false;
common.debug(context.request.url);
context.handle('GET', function() {
if (context.request.route) {
if (context.request.route.static) {
callback(null, self.result([]));
return;
}
}
self.filter(function(err, q) {
try {
if (err) {
callback(common.httpError(err));
}
else {
if (expand) {
if (expand.length>0) {
var arr = expand.split(',');
arr.forEach(function(x) {
q.expand(x.replace(/\s/g,''));
});
}
}
//check $first context param
if (first) {
q.first(function(err, result) {
if (err) {
callback(common.httpError(err));
}
else {
callback(null, self.result(result));
}
});
return;
}
var q1 = null;
if (count) {
q1 = q.clone();
}
//pass as array option
q.asArray(asArray);
if (take<0) {
q.all(function(err, result)
{
if (err) {
callback(common.httpError(err));
return;
}
if (count) {
result = { records: (result || []) };
result.total = result.records.length;
callback(null, self.result(result));
}
else {
callback(null, self.result(result || []));
}
});
}
else {
q.take(take, function(err, result)
{
if (err) {
callback(common.httpError(err));
return;
}
if (count) {
q1.count(function(err, total) {
if (err) {
callback(common.httpError(err));
}
else {
result = { total: total, records: (result || []) };
callback(null, self.result(result));
}
});
}
else {
callback(null, self.result(result || []));
}
});
}
}
}
catch (e) {
callback(e);
}
});
}).handle(['POST', 'PUT'], function() {
var target;
try {
target = self.model.convert(context.params[self.model.name] || context.params.data, true);
}
catch(err) {
common.log(err);
var er = new common.HttpException(422, "An error occured while converting data objects.", err.message);
er.code = 'EDATA';
return callback(er);
}
if (target) {
self.model.save(target, function(err)
{
if (err) {
common.log(err);
callback(common.httpError(err));
}
else {
callback(null, self.result(target));
}
});
}
else {
return callback(new common.HttpBadRequest());
}
}).handle('DELETE', function() {
//get data
var target;
try {
target = self.model.convert(context.params[self.model.name] || context.params.data, true);
}
catch(err) {
common.log(err);
var er = new common.HttpException(422, "An error occured while converting data objects.", err.message);
er.code = 'EDATA';
return callback(er);
}
if (target) {
self.model.remove(target, function(err)
{
if (err) {
callback(common.httpError(err));
}
else {
callback(null, self.result(target));
}
});
}
else {
return callback(new common.HttpBadRequest());
}
}).unhandle(function() {
return callback(new common.HttpMethodNotAllowed());
});
}
catch (e) {
callback(common.httpError(e));
}
};
/**
* Returns an instance of HttpResult class which contains a collection of items based on the specified association.
* This association should be a one-to-many association or many-many association.
* A routing for this action may be:
<pre class="prettyprint"><code>
{ "url":"/:controller/:parent/:model/index.json", "mime":"application/json", "action":"association" }
</code></pre>
<p>
or
</p>
<pre class="prettyprint"><code>
{ "url":"/:controller/:parent/:model/index.html", "mime":"text/html", "action":"association" }
</code></pre>
<pre class="prettyprint"><code>
//get orders in JSON format
/GET /Party/353/Order/index.json
</code></pre>
<p>
This action supports common query options like $filter, $order, $top, $skip etc.
The result will be a result-set with associated items:
</p>
<pre class="prettyprint"><code>
//JSON Results:
{
"total": 8,
"skip": 0,
"records": [
{
"id": 37,
"customer": 353,
"orderDate": "2015-05-05 01:19:34.000+03:00",
"orderedItem": {
"id": 407,
"additionalType": "Product",
"category": "PC Components",
"price": 1625.49,
"model": "HR5845",
"releaseDate": "2015-09-20 03:35:33.000+03:00",
"name": "Nvidia GeForce GTX 650 Ti Boost",
"dateCreated": "2015-11-23 14:53:04.884+02:00",
"dateModified": "2015-11-23 14:53:04.887+02:00"
},
"orderNumber": "OFV804",
"orderStatus": {
"id": 1,
"name": "Delivered",
"alternateName": "OrderDelivered",
"description": "Representing the successful delivery of an order."
},
"paymentDue": "2015-05-25 01:19:34.000+03:00",
"paymentMethod": {
"id": 6,
"name": "Direct Debit",
"alternateName": "DirectDebit",
"description": "Payment by direct debit"
},
"additionalType": "Order",
"dateCreated": "2015-11-23 21:00:18.264+02:00",
"dateModified": "2015-11-23 21:00:18.266+02:00"
}
...]
...
}
</code></pre>
* @param {Function} callback - A callback function where the first argument will contain the Error object if an error occured, or null otherwise.
*/
HttpDataController.prototype.association = function(callback) {
try {
var self = this, parent = self.context.params.parent, model = self.context.params.model;
if (common.isNullOrUndefined(parent) || common.isNullOrUndefined(model)) {
callback(new common.HttpBadRequest());
return;
}
self.model.where(self.model.primaryKey).equal(parent).select([self.model.primaryKey]).first(function(err, result) {
if (err) {
common.log(err);
callback(new common.HttpServerError());
return;
}
if (common.isNullOrUndefined(result)) {
callback(new common.HttpNotFoundException());
return;
}
//get parent object (DataObject)
var obj = self.model.convert(result);
var associatedModel = self.context.model(model);
if (common.isNullOrUndefined(associatedModel)) {
callback(new common.HttpNotFoundException());
return;
}
/**
* Search for object junction
*/
var field = self.model.attributes.filter(function(x) { return x.type === associatedModel.name; })[0], mapping;
if (field) {
/**
* Get association mapping fo this field
* @type {DataAssociationMapping}
*/
mapping = self.model.inferMapping(field.name);
if (mapping) {
if ((mapping.parentModel===self.model.name) && (mapping.associationType==='junction')) {
/**
* @type {DataQueryable}
*/
var junction = obj.property(field.name);
junction.model.filter(self.context.params, function(err, q) {
if (err) {
callback(err);
}
else {
//merge properties
if (q.query.$select) { junction.query.$select = q.query.$select; }
if (q.query.$group) { junction.query.$group = q.query.$group; }
if (q.query.$order) { junction.query.$order = q.query.$order; }
if (q.query.$prepared) { junction.query.$where = q.query.$prepared; }
if (q.query.$skip) { junction.query.$skip = q.query.$skip; }
if (q.query.$take) { junction.query.$take = q.query.$take; }
junction.list(function(err, result) {
callback(err, self.result(result));
});
}
});
return;
}
}
}
field = associatedModel.attributes.filter(function(x) { return x.type === self.model.name; })[0];
if (common.isNullOrUndefined(field)) {
callback(new common.HttpNotFoundException());
return;
}
//get field mapping
mapping = associatedModel.inferMapping(field.name);
associatedModel.filter(self.context.params, function(err, q) {
if (err) {
callback(err);
}
else {
q.where(mapping.childField).equal(parent).list(function(err, result) {
callback(err, self.result(result));
});
}
});
});
}
catch(e) {
common.log(e);
callback(e, new common.HttpServerError());
}
};
if (typeof module !== 'undefined') module.exports = HttpDataController;