spring-projects / spring-hateoas

Spring HATEOAS - Library to support implementing representations for hyper-text driven REST web services.
https://spring.io/projects/spring-hateoas
Apache License 2.0
1.04k stars 478 forks source link

Infinite Recursion when calling pre-upgrade object #1222

Closed kogden closed 3 years ago

kogden commented 4 years ago

I've upgraded to Hateoas 1.0.3.RELEASE from version 0.25.1.RELEASE. Our app has users that may save Reports and come back to it later. I've upgraded our functionality from:

    @PostMapping("user")
    public ResponseEntity<Resource<MUser>> getCurrentUser(@AuthenticationPrincipal User authUser) {
        MUseruser = userService.findByEmail(authUser.getUsername());

        LinkBuilder links = entityLinks.linkFor(MUser.class);

        Resource<MUser> userRes = new Resource<>(user);
        userRes.add(entityLinks.linkToSingleResource(MUser.class, user.getUserID()));
        userRes.add(links.slash(user.getUserID()).slash("report").withRel("report"));

        return new ResponseEntity<>(userRes, HttpStatus.OK);
    }

to:

    @PostMapping("user")
    public ResponseEntity<EntityModel<MUser>> getCurrentUser(@AuthenticationPrincipal User authUser) {
        MUser user = userService.findByEmail(authUser.getUsername());

        LinkBuilder links = entityLinks.linkFor(MUser.class);

        EntityModel<MUser> userRes = new EntityModel<>(user);
        userRes.add(entityLinks.linkToItemResource(MUser.class, user.getUserID()));
        userRes.add(links.slash(user.getUserID()).slash("report").withRel("report"));

        return new ResponseEntity<>(userRes, HttpStatus.OK);
    }

I can still create new users and log in just fine since the upgrade. However, trying to log in as an old user pre-upgrade causes an Infinite Loop. I'm thinking that it this new version handles projections differently?

Infinite recursion (StackOverflowError) (through reference chain: 
report.Report["holder"]->holder.Holder["report"]->report.Report["holder"]->holder.Holder["report"]->report.Report["holder"]

(Some of the above path is withheld for client confidentiality.)

Is this because of a naming mismatch? As far as I can see, linkToItemResource does the same exact thing as linkToSingleResource. Is it the new EntityLinks object? EntityModel instead of Resource?

Here is some of the stacktrace. I cut it off because it gets caught in a loop of serialize functions.

{
  "method": "defineClass1",
  "class": "java.lang.ClassLoader",
  "exact": false,
  "version": "1.8.0_242",
  "file": "ClassLoader.java",
  "location": "?",
  "line": -2
},
{
  "method": "defineClass",
  "class": "java.lang.ClassLoader",
  "exact": false,
  "version": "1.8.0_242",
  "file": "ClassLoader.java",
  "location": "?",
  "line": 757
},
{
  "method": "defineClass",
  "class": "java.security.SecureClassLoader",
  "exact": false,
  "version": "1.8.0_242",
  "file": "SecureClassLoader.java",
  "location": "?",
  "line": 142
},
{
  "method": "defineClass",
  "class": "java.net.URLClassLoader",
  "exact": false,
  "version": "1.8.0_242",
  "file": "URLClassLoader.java",
  "location": "?",
  "line": 468
},
{
  "method": "access$100",
  "class": "java.net.URLClassLoader",
  "exact": false,
  "version": "1.8.0_242",
  "file": "URLClassLoader.java",
  "location": "?",
  "line": 74
},
{
  "method": "run",
  "class": "java.net.URLClassLoader$1",
  "exact": false,
  "version": "1.8.0_242",
  "file": "URLClassLoader.java",
  "location": "?",
  "line": 369
},
{
  "method": "run",
  "class": "java.net.URLClassLoader$1",
  "exact": false,
  "version": "1.8.0_242",
  "file": "URLClassLoader.java",
  "location": "?",
  "line": 363
},
{
  "method": "doPrivileged",
  "class": "java.security.AccessController",
  "exact": false,
  "version": "1.8.0_242",
  "file": "AccessController.java",
  "location": "?",
  "line": -2
},
{
  "method": "findClass",
  "class": "java.net.URLClassLoader",
  "exact": false,
  "version": "1.8.0_242",
  "file": "URLClassLoader.java",
  "location": "?",
  "line": 362
},
{
  "method": "loadClass",
  "class": "java.lang.ClassLoader",
  "exact": false,
  "version": "1.8.0_242",
  "file": "ClassLoader.java",
  "location": "?",
  "line": 419
},
{
  "method": "loadClass",
  "class": "org.springframework.boot.loader.LaunchedURLClassLoader",
  "exact": false,
  "version": "4.8.0",
  "file": "LaunchedURLClassLoader.java",
  "location": "sws-mor.jar",
  "line": 92
},
{
  "method": "loadClass",
  "class": "java.lang.ClassLoader",
  "exact": false,
  "version": "1.8.0_242",
  "file": "ClassLoader.java",
  "location": "?",
  "line": 352
},
{
  "method": "serializeFields",
  "class": "com.fasterxml.jackson.databind.ser.std.BeanSerializerBase",
  "exact": false,
  "version": "2.10.2",
  "file": "BeanSerializerBase.java",
  "location": "jackson-databind-2.10.2.jar!/",
  "line": 740
},
{
  "method": "serialize",
  "class": "com.fasterxml.jackson.databind.ser.BeanSerializer",
  "exact": false,
  "version": "2.10.2",
  "file": "BeanSerializer.java",
  "location": "jackson-databind-2.10.2.jar!/",
  "line": 166
},
{
  "method": "serializeAsField",
  "class": "com.fasterxml.jackson.databind.ser.BeanPropertyWriter",
  "exact": false,
  "version": "2.10.2",
  "file": "BeanPropertyWriter.java",
  "location": "jackson-databind-2.10.2.jar!/",
  "line": 727
},
{
  "method": "serializeFields",
  "class": "com.fasterxml.jackson.databind.ser.std.BeanSerializerBase",
  "exact": false,
  "version": "2.10.2",
  "file": "BeanSerializerBase.java",
  "location": "jackson-databind-2.10.2.jar!/",
  "line": 722
},
{
  "method": "serialize",
  "class": "com.fasterxml.jackson.databind.ser.BeanSerializer",
  "exact": false,
  "version": "2.10.2",
  "file": "BeanSerializer.java",
  "location": "jackson-databind-2.10.2.jar!/",
  "line": 166
},
{
  "method": "serializeAsField",
  "class": "com.fasterxml.jackson.databind.ser.BeanPropertyWriter",
  "exact": false,
  "version": "2.10.2",
  "file": "BeanPropertyWriter.java",
  "location": "jackson-databind-2.10.2.jar!/",
  "line": 727
},
{
  "method": "serializeFields",
  "class": "com.fasterxml.jackson.databind.ser.std.BeanSerializerBase",
  "exact": false,
  "version": "2.10.2",
  "file": "BeanSerializerBase.java",
  "location": "jackson-databind-2.10.2.jar!/",
  "line": 722
},
{
  "method": "serialize",
  "class": "com.fasterxml.jackson.databind.ser.BeanSerializer",
  "exact": false,
  "version": "2.10.2",
  "file": "BeanSerializer.java",
  "location": "jackson-databind-2.10.2.jar!/",
  "line": 166
},
{
  "method": "serializeAsField",
  "class": "com.fasterxml.jackson.databind.ser.BeanPropertyWriter",
  "exact": false,
  "version": "2.10.2",
  "file": "BeanPropertyWriter.java",
  "location": "jackson-databind-2.10.2.jar!/",
  "line": 727
},
{
  "method": "serializeFields",
  "class": "com.fasterxml.jackson.databind.ser.std.BeanSerializerBase",
  "exact": false,
  "version": "2.10.2",
  "file": "BeanSerializerBase.java",
  "location": "jackson-databind-2.10.2.jar!/",
  "line": 722
},
{
  "method": "serialize",
  "class": "com.fasterxml.jackson.databind.ser.BeanSerializer",
  "exact": false,
  "version": "2.10.2",
  "file": "BeanSerializer.java",
  "location": "jackson-databind-2.10.2.jar!/",
  "line": 166
},
{
  "method": "serializeAsField",
  "class": "com.fasterxml.jackson.databind.ser.BeanPropertyWriter",
  "exact": false,
  "version": "2.10.2",
  "file": "BeanPropertyWriter.java",
  "location": "jackson-databind-2.10.2.jar!/",
  "line": 727
},
gregturn commented 4 years ago

I can still create new users and log in just fine since the upgrade. However, trying to log in as an old user pre-upgrade causes an Infinite Loop. I'm thinking that it this new version handles projections differently?

This is the part I don't quite grasp. Are you saying that an existing user that was created under the old system cannot log in now, but a newly created user can?

And when you mention projections, does that mean you are also using Spring Data REST?