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
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 ;)
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 byAlpsHttpMessageConverter
(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:
I guess the main issue here is that the
JsonpAdvice
is processed before theAlpsHttpMessageConverter
which is aResponseBodyAdvice
too. After theJsonpAdvice
is processed theRootResourceInformation
is encapsulated in aMappingJacksonValue
so that theAlpsHttpMessageConverter
cannot convert the containingRootResourceInformation
. Finally, theAbstractMessageConverterMessageProcessor
tries to write the result - aRootResourceInformation
- resulting in an infinite recursion.You can watch this behaviour when settings a breakpoint on line 146 of the
RequestResponseBodyAdviceChain
which first processes theJsonpAdvice
and afterwards processes theAlpsHttpMessageConverter
.Partial response from webserver:
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