Skip to content

Commit

Permalink
Merge pull request #15 from rkusuma/master
Browse files Browse the repository at this point in the history
Add mapping to avoid infinite loop
  • Loading branch information
guylabs committed Sep 8, 2015
2 parents bda7392 + 5ee6fd1 commit 7438358
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 15 deletions.
34 changes: 27 additions & 7 deletions dist/angular-spring-data-rest.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function ()
},

$get: ["$injector", function ($injector) {
/**
* Link map which contains the 'self' link as key and the list of already fetched link names as value.
* This is used to check that every link on each entity is only fetched once to avoid an infinite recursive loop.
* @type {{Object}}
*/
var linkMap = {};

/**
* Returns the Angular $resource method which is configured with the given parameters.
Expand Down Expand Up @@ -250,6 +256,12 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function ()

// if there are links to fetch, then process and fetch them
if (fetchLinkNames != undefined) {
var self = data[config.linksKey][config.linksSelfLinkName][config.linksHrefKey];

// add the self link value as key and add an empty map to store all other links which are fetched for this entity
if (!linkMap[self]) {
linkMap[self] = [];
}

// process all links
angular.forEach(data[config.linksKey], function (linkValue, linkName) {
Expand All @@ -258,15 +270,17 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function ()
if (linkName != config.linksSelfLinkName) {

// check if:
// 1. the all link names key is given then fetch the link
// 2. the given key is equal
// 3. the given key is inside the array
if (fetchLinkNames == config.fetchAllKey ||
// 1. the link was not fetched already
// 2. the all link names key is given then fetch the link
// 3. the given key is equal
// 4. the given key is inside the array
if (linkMap[self].indexOf(linkName) < 0 &&
(fetchLinkNames == config.fetchAllKey ||
(typeof fetchLinkNames === "string" && linkName == fetchLinkNames) ||
(fetchLinkNames instanceof Array && fetchLinkNames.indexOf(linkName) >= 0)) {
(fetchLinkNames instanceof Array && fetchLinkNames.indexOf(linkName) >= 0))) {
promisesArray.push(fetchFunction(getProcessedUrl(data, linkName), linkName,
processedData, fetchLinkNames, recursive));

linkMap[self].push(linkName);
}
}
});
Expand Down Expand Up @@ -361,8 +375,14 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function ()
}
};

// empty the map and
// return an object with the processData function
return {process: processData};
return {
process: function(promiseOrData, fetchLinkNames, recursive) {
linkMap = {};
return processData(promiseOrData, fetchLinkNames, recursive);
}
};
}]
};

Expand Down
2 changes: 1 addition & 1 deletion dist/angular-spring-data-rest.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 27 additions & 7 deletions src/angular-spring-data-rest-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function ()
},

$get: ["$injector", function ($injector) {
/**
* Link map which contains the 'self' link as key and the list of already fetched link names as value.
* This is used to check that every link on each entity is only fetched once to avoid an infinite recursive loop.
* @type {{Object}}
*/
var linkMap = {};

/**
* Returns the Angular $resource method which is configured with the given parameters.
Expand Down Expand Up @@ -238,6 +244,12 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function ()

// if there are links to fetch, then process and fetch them
if (fetchLinkNames != undefined) {
var self = data[config.linksKey][config.linksSelfLinkName][config.linksHrefKey];

// add the self link value as key and add an empty map to store all other links which are fetched for this entity
if (!linkMap[self]) {
linkMap[self] = [];
}

// process all links
angular.forEach(data[config.linksKey], function (linkValue, linkName) {
Expand All @@ -246,15 +258,17 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function ()
if (linkName != config.linksSelfLinkName) {

// check if:
// 1. the all link names key is given then fetch the link
// 2. the given key is equal
// 3. the given key is inside the array
if (fetchLinkNames == config.fetchAllKey ||
// 1. the link was not fetched already
// 2. the all link names key is given then fetch the link
// 3. the given key is equal
// 4. the given key is inside the array
if (linkMap[self].indexOf(linkName) < 0 &&
(fetchLinkNames == config.fetchAllKey ||
(typeof fetchLinkNames === "string" && linkName == fetchLinkNames) ||
(fetchLinkNames instanceof Array && fetchLinkNames.indexOf(linkName) >= 0)) {
(fetchLinkNames instanceof Array && fetchLinkNames.indexOf(linkName) >= 0))) {
promisesArray.push(fetchFunction(getProcessedUrl(data, linkName), linkName,
processedData, fetchLinkNames, recursive));

linkMap[self].push(linkName);
}
}
});
Expand Down Expand Up @@ -349,8 +363,14 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function ()
}
};

// empty the map and
// return an object with the processData function
return {process: processData};
return {
process: function(promiseOrData, fetchLinkNames, recursive) {
linkMap = {};
return processData(promiseOrData, fetchLinkNames, recursive);
}
};
}]
};

Expand Down
46 changes: 46 additions & 0 deletions test/angular-spring-data-rest-provider.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,51 @@ describe("the spring data rest adapter", function () {
this.rootScope.$apply();
});

it("must only fetch link once to avoid infinite loop", function () {
var allLinks = this.config.fetchAllKey;
var accidentHref = 'http://localhost:8080/api/reports/00001/accident';
var reportHref = 'http://localhost:8080/api/accidents/00001/report';

this.httpBackend.whenGET(accidentHref).respond(200, mockDataAccident());
this.httpBackend.expectGET(accidentHref);

this.httpBackend.whenGET(reportHref).respond(200, mockDataReport());
this.httpBackend.expectGET(reportHref);

this.rawResponse = mockDataReport();
SpringDataRestAdapter.process(this.rawResponse, allLinks, true).then(function (processedData) {
// expect that accident will not fetched twice
expect(processedData.accident).toBeDefined();
expect(processedData.accident.report).toBeDefined();
expect(processedData.accident.report.accident).not.toBeDefined();
});

this.httpBackend.flush();
this.rootScope.$apply();
});

it("must only reinitialized the map when process called twice or more", function () {
var allLinks = this.config.fetchAllKey;
var accidentHref = 'http://localhost:8080/api/reports/00001/accident';
var reportHref = 'http://localhost:8080/api/accidents/00001/report';

this.httpBackend.whenGET(accidentHref).respond(200, mockDataAccident());
this.httpBackend.expectGET(accidentHref);

this.httpBackend.whenGET(reportHref).respond(200, mockDataReport());
this.httpBackend.expectGET(reportHref);

this.rawResponse = mockDataReport();
SpringDataRestAdapter.process(this.rawResponse, allLinks, true).then(function (processedData) {
SpringDataRestAdapter.process(mockDataReport(), allLinks, true).then(function (processedData2) {
// expect linkMap to be reinitialized after process method called twice
expect(JSON.stringify(processedData)).toEqual(JSON.stringify(processedData2));
});
});

this.httpBackend.flush();
this.rootScope.$apply();
});

});

27 changes: 27 additions & 0 deletions test/angular-spring-data-rest.helper.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,30 @@ var mockWithRawEmbeddedValueTypes = function () {
);
};

var mockDataReport = function() {
return angular.copy({
"reportNumber": "00001",
"_links": {
"self": {
"href": "http://localhost:8080/api/reports/00001"
},
"accident": {
"href": "http://localhost:8080/api/reports/00001/accident"
}
}
});
};

var mockDataAccident = function() {
return angular.copy({
"accidentDate": "2015-07-05",
"_links": {
"self": {
"href": "http://localhost:8080/api/accidents/00001"
},
"report": {
"href": "http://localhost:8080/api/accidents/00001/report"
}
}
});
};

0 comments on commit 7438358

Please sign in to comment.