spring-projects / spring-data-rest

Simplifies building hypermedia-driven REST web services on top of Spring Data repositories
https://spring.io/projects/spring-data-rest
Apache License 2.0
913 stars 559 forks source link

Infinite recursion on ALPS combined with JsonpAdvice [DATAREST-733] #1949

Open spring-projects-issues opened 8 years ago

spring-projects-issues commented 8 years ago

Jan Zeppenfeld opened DATAREST-733 and commented

I run into an infinite recursion when defining a JsonpAdvice and calling paths like "/profile/{some repository}" which are processed by AlpsHttpMessageConverter (cp. stacktrace and partial response from webserver at the end). Be aware: When doing this, my Eclipse freezes and I have to kill Eclipse and the created java process manually. I extracted the stacktrace from the created log file. I set up my project with Spring Boot 1.3.1RELEASE which uses Spring Data REST 2.4.2.

Here is a stackoverflow Link referring to a similar but not identical issue.

Here is my main configuration which leads to this infinite recursion and a workaround fixing it for my case although i'm not sure if this workaround is a clean approach:

@Configuration
@EnableSpringDataWebSupport
public class WebConfiguration extends RepositoryRestMvcConfiguration {

    // JsonpAdvice causing the issue
    @ControllerAdvice
    public static class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
        public JsonpAdvice() {
            super("callback");
        }
    }

    // Workaround fixing the infinite recursion
    @Override
    @Bean
    public AlpsJsonHttpMessageConverter alpsJsonHttpMessageConverter() {
        return new AlpsJsonHttpMessageConverter(super.alpsConverter()) {
            @Override
            public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
                // The JsonpAdvice has already been called here if it was defined above, so that "body" is of type "MappingJacksonValue".
                // The AlpsJsonHttpMessageConverter cannot convert this value but the contained RootResourceInformation so that we need to extract it.
                if (body instanceof MappingJacksonValue) {
                    Object tmpBody = ((MappingJacksonValue) body).getValue();
                    tmpBody = super.beforeBodyWrite(tmpBody, returnType, selectedContentType, selectedConverterType, request, response);
                    ((MappingJacksonValue) body).setValue(tmpBody);

                    return body;
                }

                return super.beforeBodyWrite(body, returnType, selectedContentType, selectedConverterType, request, response);
            }

            // Not sure why but the AlpsJsonHttpMessageConverter returned here, gets proxied by Spring so that its class does not equals AlpsJsonHttpMessageConverter.class.
            // Therefore we need to compare "converterType" to this class too.
            @Override
            public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
                return converterType.equals(this.getClass()) || super.supports(returnType, converterType);
            }
        };
    }

    // ...
}

I guess the main issue here is that the JsonpAdvice is processed before the AlpsHttpMessageConverter which is a ResponseBodyAdvice too. After the JsonpAdvice is processed the RootResourceInformation is encapsulated in a MappingJacksonValue so that the AlpsHttpMessageConverter cannot convert the containing RootResourceInformation. Finally, the AbstractMessageConverterMessageProcessor tries to write the result - a RootResourceInformation - resulting in an infinite recursion.

You can watch this behaviour when settings a breakpoint on line 146 of the RequestResponseBodyAdviceChain which first processes the JsonpAdvice and afterwards processes the AlpsHttpMessageConverter.

2015-12-23 17:49:40.540  WARN 8744 --- [http-nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: Infinite recursion (StackOverflowError) (through reference chain: org.springframework.data.jpa.mapping.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping.JpaPersistentPropertyImpl["owner"]-> … ->org.springframework.data.jpa.mapping.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping.JpaPersistentPropertyImpl["owner"]->org.springframework.data.jpa.mapping.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping.JpaPersistentPropertyImpl["owner"])
2015-12-23 17:49:40.549  WARN 8744 --- [http-nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Handling of [org.springframework.http.converter.HttpMessageNotWritableException] resulted in Exception

java.lang.IllegalStateException: Cannot call sendError() after the response has been committed
    at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:478) ~[tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.sendServerError(DefaultHandlerExceptionResolver.java:488) ~[spring-webmvc-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.handleHttpMessageNotWritable(DefaultHandlerExceptionResolver.java:402) ~[spring-webmvc-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.doResolveException(DefaultHandlerExceptionResolver.java:146) ~[spring-webmvc-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:137) [spring-webmvc-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.servlet.handler.HandlerExceptionResolverComposite.resolveException(HandlerExceptionResolverComposite.java:74) [spring-webmvc-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1182) [spring-webmvc-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1020) [spring-webmvc-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:971) [spring-webmvc-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) [spring-webmvc-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:969) [spring-webmvc-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:860) [spring-webmvc-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:845) [spring-webmvc-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) [tomcat-embed-websocket-8.0.30.jar:8.0.30]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:87) [spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) [spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121) [spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:521) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1096) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:674) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_40]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_40]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.0.30.jar:8.0.30]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_40]

2015-12-23 17:49:40.558 ERROR 8744 --- [http-nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: Infinite recursion (StackOverflowError) (through reference chain: org.springframework.data.jpa.mapping.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping.JpaPersistentPropertyImpl["owner"]->org.springframework.data.jpa.mapping.JpaPersistentEntityImpl["idProperty"]->…->org.springframework.data.jpa.mapping.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping.JpaPersistentPropertyImpl["owner"]->org.springframework.data.jpa.mapping.JpaPersistentEntityImpl["idProperty"]->org.springframework.data.jpa.mapping.JpaPersistentPropertyImpl["owner"])] with root cause

java.lang.StackOverflowError: null
    at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_40]
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760) ~[na:1.8.0_40]
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[na:1.8.0_40]
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) ~[na:1.8.0_40]
    at java.net.URLClassLoader.access$100(URLClassLoader.java:73) ~[na:1.8.0_40]
    at java.net.URLClassLoader$1.run(URLClassLoader.java:368) ~[na:1.8.0_40]
    at java.net.URLClassLoader$1.run(URLClassLoader.java:362) ~[na:1.8.0_40]
    at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_40]
    at java.net.URLClassLoader.findClass(URLClassLoader.java:361) ~[na:1.8.0_40]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_40]
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) ~[na:1.8.0_40]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_40]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:691) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:693) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:675) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:693) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:675) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:693) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:675) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:693) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:675) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:693) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:675) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:693) ~[jackson-databind-2.6.4.jar:2.6.4]

... and so on until I kill the process

Partial response from webserver:

{
  "resourceMetadata" : {
    "description" : {
      "message" : "rest.description.buildings",
      "default" : true,
      "type" : {
        "type" : "text",
        "subtype" : "plain",
        "qualityValue" : 1.0,
        "wildcardType" : false,
        "concrete" : true,
        "wildcardSubtype" : false
      },
      "codes" : [ "rest.description.buildings" ]
    },
    "primary" : false,
    "domainType" : "my.package.Building",
    "rel" : "buildings",
    "exported" : true,
    "searchResourceMappings" : {
      "rel" : "search",
      "exported" : false,
      "pagingResource" : false
    },
    "itemResourceRel" : "building",
    "itemResourceDescription" : {
      "message" : "rest.description.building",
      "default" : true,
      "type" : {
        "type" : "text",
        "subtype" : "plain",
        "qualityValue" : 1.0,
        "wildcardType" : false,
        "concrete" : true,
        "wildcardSubtype" : false
      },
      "codes" : [ "rest.description.building" ]
    },
    "pagingResource" : true
  },
  "persistentEntity" : {
    "idProperty" : {
      "name" : "id",
      "rawType" : "java.lang.Long",
      "field" : {
        "name" : "id",
        "type" : "java.lang.Long",
        "modifiers" : 2,
        "annotations" : [ { }, { } ],
        "declaredAnnotations" : [ { }, { } ],
        "declaringClass" : "my.package.Building",
        "synthetic" : false,
        "annotatedType" : {
          "type" : "java.lang.Long"
        },
        "genericType" : "java.lang.Long",
        "enumConstant" : false,
        "accessible" : true
      },
      "association" : false,
      "owner" : {
        "idProperty" : {
          "name" : "id",
          "rawType" : "java.lang.Long",
          "field" : {
            "name" : "id",
            "type" : "java.lang.Long",
            "modifiers" : 2,
            "annotations" : [ { }, { } ],
            "declaredAnnotations" : [ { }, { } ],
            "declaringClass" : "my.package.Building",
            "synthetic" : false,
            "annotatedType" : {
              "type" : "java.lang.Long"
            },
            "genericType" : "java.lang.Long",
            "enumConstant" : false,
            "accessible" : true
          },
          "association" : false,
          "owner" : {
                    ...recursion from here...

I hope I clarified the issue and posted it at the correct place. Let me know if you need further information. It's the first issue I have ever posted ;)


Affects: 2.4.2 (Gosling SR2)

1 votes, 4 watchers

spring-projects-issues commented 3 years ago

Mark Paluch commented

Truncated the stack trace a bit