spring-projects / spring-projects/spring-data-rest

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

Open
#1,949 1 comment 1 reaction 1 assignee View on GitHub

@odrotbohm is already working on this.

Since Dec 31, 2020.

type: bug
Dominant language
Java
Stars
958
Forks
568
PR merge metrics
No merged PRs in 30d

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.