Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mapping to avoid infinite loop #15

Merged
merged 4 commits into from
Sep 8, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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"
}
}
});
};