Skip to content

Commit 7438358

Browse files
committed
Merge pull request #15 from rkusuma/master
Add mapping to avoid infinite loop
2 parents bda7392 + 5ee6fd1 commit 7438358

5 files changed

+128
-15
lines changed

dist/angular-spring-data-rest.js

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function ()
6767
},
6868

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

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

251257
// if there are links to fetch, then process and fetch them
252258
if (fetchLinkNames != undefined) {
259+
var self = data[config.linksKey][config.linksSelfLinkName][config.linksHrefKey];
260+
261+
// add the self link value as key and add an empty map to store all other links which are fetched for this entity
262+
if (!linkMap[self]) {
263+
linkMap[self] = [];
264+
}
253265

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

260272
// check if:
261-
// 1. the all link names key is given then fetch the link
262-
// 2. the given key is equal
263-
// 3. the given key is inside the array
264-
if (fetchLinkNames == config.fetchAllKey ||
273+
// 1. the link was not fetched already
274+
// 2. the all link names key is given then fetch the link
275+
// 3. the given key is equal
276+
// 4. the given key is inside the array
277+
if (linkMap[self].indexOf(linkName) < 0 &&
278+
(fetchLinkNames == config.fetchAllKey ||
265279
(typeof fetchLinkNames === "string" && linkName == fetchLinkNames) ||
266-
(fetchLinkNames instanceof Array && fetchLinkNames.indexOf(linkName) >= 0)) {
280+
(fetchLinkNames instanceof Array && fetchLinkNames.indexOf(linkName) >= 0))) {
267281
promisesArray.push(fetchFunction(getProcessedUrl(data, linkName), linkName,
268282
processedData, fetchLinkNames, recursive));
269-
283+
linkMap[self].push(linkName);
270284
}
271285
}
272286
});
@@ -361,8 +375,14 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function ()
361375
}
362376
};
363377

378+
// empty the map and
364379
// return an object with the processData function
365-
return {process: processData};
380+
return {
381+
process: function(promiseOrData, fetchLinkNames, recursive) {
382+
linkMap = {};
383+
return processData(promiseOrData, fetchLinkNames, recursive);
384+
}
385+
};
366386
}]
367387
};
368388

dist/angular-spring-data-rest.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/angular-spring-data-rest-provider.js

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function ()
5555
},
5656

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

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

239245
// if there are links to fetch, then process and fetch them
240246
if (fetchLinkNames != undefined) {
247+
var self = data[config.linksKey][config.linksSelfLinkName][config.linksHrefKey];
248+
249+
// add the self link value as key and add an empty map to store all other links which are fetched for this entity
250+
if (!linkMap[self]) {
251+
linkMap[self] = [];
252+
}
241253

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

248260
// check if:
249-
// 1. the all link names key is given then fetch the link
250-
// 2. the given key is equal
251-
// 3. the given key is inside the array
252-
if (fetchLinkNames == config.fetchAllKey ||
261+
// 1. the link was not fetched already
262+
// 2. the all link names key is given then fetch the link
263+
// 3. the given key is equal
264+
// 4. the given key is inside the array
265+
if (linkMap[self].indexOf(linkName) < 0 &&
266+
(fetchLinkNames == config.fetchAllKey ||
253267
(typeof fetchLinkNames === "string" && linkName == fetchLinkNames) ||
254-
(fetchLinkNames instanceof Array && fetchLinkNames.indexOf(linkName) >= 0)) {
268+
(fetchLinkNames instanceof Array && fetchLinkNames.indexOf(linkName) >= 0))) {
255269
promisesArray.push(fetchFunction(getProcessedUrl(data, linkName), linkName,
256270
processedData, fetchLinkNames, recursive));
257-
271+
linkMap[self].push(linkName);
258272
}
259273
}
260274
});
@@ -349,8 +363,14 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function ()
349363
}
350364
};
351365

366+
// empty the map and
352367
// return an object with the processData function
353-
return {process: processData};
368+
return {
369+
process: function(promiseOrData, fetchLinkNames, recursive) {
370+
linkMap = {};
371+
return processData(promiseOrData, fetchLinkNames, recursive);
372+
}
373+
};
354374
}]
355375
};
356376

test/angular-spring-data-rest-provider.spec.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,51 @@ describe("the spring data rest adapter", function () {
107107
this.rootScope.$apply();
108108
});
109109

110+
it("must only fetch link once to avoid infinite loop", function () {
111+
var allLinks = this.config.fetchAllKey;
112+
var accidentHref = 'http://localhost:8080/api/reports/00001/accident';
113+
var reportHref = 'http://localhost:8080/api/accidents/00001/report';
114+
115+
this.httpBackend.whenGET(accidentHref).respond(200, mockDataAccident());
116+
this.httpBackend.expectGET(accidentHref);
117+
118+
this.httpBackend.whenGET(reportHref).respond(200, mockDataReport());
119+
this.httpBackend.expectGET(reportHref);
120+
121+
this.rawResponse = mockDataReport();
122+
SpringDataRestAdapter.process(this.rawResponse, allLinks, true).then(function (processedData) {
123+
// expect that accident will not fetched twice
124+
expect(processedData.accident).toBeDefined();
125+
expect(processedData.accident.report).toBeDefined();
126+
expect(processedData.accident.report.accident).not.toBeDefined();
127+
});
128+
129+
this.httpBackend.flush();
130+
this.rootScope.$apply();
131+
});
132+
133+
it("must only reinitialized the map when process called twice or more", function () {
134+
var allLinks = this.config.fetchAllKey;
135+
var accidentHref = 'http://localhost:8080/api/reports/00001/accident';
136+
var reportHref = 'http://localhost:8080/api/accidents/00001/report';
137+
138+
this.httpBackend.whenGET(accidentHref).respond(200, mockDataAccident());
139+
this.httpBackend.expectGET(accidentHref);
140+
141+
this.httpBackend.whenGET(reportHref).respond(200, mockDataReport());
142+
this.httpBackend.expectGET(reportHref);
143+
144+
this.rawResponse = mockDataReport();
145+
SpringDataRestAdapter.process(this.rawResponse, allLinks, true).then(function (processedData) {
146+
SpringDataRestAdapter.process(mockDataReport(), allLinks, true).then(function (processedData2) {
147+
// expect linkMap to be reinitialized after process method called twice
148+
expect(JSON.stringify(processedData)).toEqual(JSON.stringify(processedData2));
149+
});
150+
});
151+
152+
this.httpBackend.flush();
153+
this.rootScope.$apply();
154+
});
155+
110156
});
111157

test/angular-spring-data-rest.helper.spec.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,30 @@ var mockWithRawEmbeddedValueTypes = function () {
366366
);
367367
};
368368

369+
var mockDataReport = function() {
370+
return angular.copy({
371+
"reportNumber": "00001",
372+
"_links": {
373+
"self": {
374+
"href": "http://localhost:8080/api/reports/00001"
375+
},
376+
"accident": {
377+
"href": "http://localhost:8080/api/reports/00001/accident"
378+
}
379+
}
380+
});
381+
};
382+
383+
var mockDataAccident = function() {
384+
return angular.copy({
385+
"accidentDate": "2015-07-05",
386+
"_links": {
387+
"self": {
388+
"href": "http://localhost:8080/api/accidents/00001"
389+
},
390+
"report": {
391+
"href": "http://localhost:8080/api/accidents/00001/report"
392+
}
393+
}
394+
});
395+
};

0 commit comments

Comments
 (0)