Nested content properties together with FS store do not convert to `@ContentId` type correctly.
vierbergenlars opened this issue · 1 comments
Describe the bug
We have a JPA entity with an @Embeddable
object that contains content properties.
Entities and stores
@Entity
@NoArgsConstructor
@Getter
@Setter
public class Contract {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
private String number;
@Embedded
@AttributeOverride(name="id", column = @Column(name = "content__id"))
@AttributeOverride(name="length", column = @Column(name = "content__length"))
@AttributeOverride(name="mimetype", column = @Column(name = "content__mimetype"))
@AttributeOverride(name="filename", column = @Column(name = "content__filename"))
private EmbeddedContent content = new EmbeddedContent();
}
@Embeddable
@NoArgsConstructor
@Getter
@Setter
public class EmbeddedContent {
@ContentId
private String id;
@ContentLength
private long length;
@MimeType
private String mimetype;
@OriginalFileName
private String filename;
}
@StoreRestResource
interface ContractContentStore extends ContentStore<Contract, String> {
}
The DefaultFilesystemStoreImpl#getResource(S, PropertyPath)
generates a new UUID in case none exists yet. I then calls #convertToExternalContentIdType()
to conver to the type of the @ContentId
-annotated field.
However, BeanUtils.getFieldWithAnnotationType(property,ContentId.class)
returns null
, because there is no @ContentId
-annotated field on the entity itself (only on the nested object). Then TypeDescriptor.valueOf(null)
returns Object.class
, and the PlacementService
converts the generated UUID to Object
(so it does nothing).
Then setting the content ID on the embedded object fails, because the UUID can not be assigned to a String (type type of content.id
).
The PropertyAccessor
machinery then tries to convert UUID to String with its internal machinery and finally throws an exception:
Failed to convert property value of type 'java.util.UUID' to required type 'java.lang.String' for property 'content.id'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.util.UUID' to required type 'java.lang.String' for property 'id': no matching editors or conversion strategy found
org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.util.UUID' to required type 'java.lang.String' for property 'content.id'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.util.UUID' to required type 'java.lang.String' for property 'id': no matching editors or conversion strategy found
at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:595)
at org.springframework.beans.AbstractNestablePropertyAccessor.convertForProperty(AbstractNestablePropertyAccessor.java:609)
at org.springframework.beans.AbstractNestablePropertyAccessor.processLocalProperty(AbstractNestablePropertyAccessor.java:458)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:278)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:246)
at internal.org.springframework.content.fs.repository.DefaultFilesystemStoreImpl.setContentId(DefaultFilesystemStoreImpl.java:422)
at internal.org.springframework.content.fs.repository.DefaultFilesystemStoreImpl.getResource(DefaultFilesystemStoreImpl.java:95)
at internal.org.springframework.content.commons.repository.factory.StoreImpl.getResource(StoreImpl.java:309)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at internal.org.springframework.content.commons.repository.factory.StoreMethodInterceptor.invoke(StoreMethodInterceptor.java:68)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at eu.xenit.contentcloud.userapps.xenit.insurance.store.$Proxy155.getResource(Unknown Source)
at internal.org.springframework.content.rest.controllers.resolvers.AssociativeStoreResourceResolver.resolve(AssociativeStoreResourceResolver.java:21)
at internal.org.springframework.content.rest.controllers.ResourceHandlerMethodArgumentResolver.resolveArgument(ResourceHandlerMethodArgumentResolver.java:129)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPut(FrameworkServlet.java:920)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:668)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at eu.xenit.contentcloud.thunx.spring.data.rest.AbacRequestFilter.doFilter(AbacRequestFilter.java:41)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.util.UUID' to required type 'java.lang.String' for property 'id': no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:262)
Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.util.UUID' to required type 'java.lang.String' for property 'id': no matching editors or conversion strategy found
at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:590)
... 71 more
To Reproduce
Steps to reproduce the behavior:
- Use the model from above (any JPA
@Entity
with an@Embeddable
object will dol) - Create a new entity
curl -XPOST http://localhost:8080/contracts -H "Content-Type: application/json" -d '{"number":"123"}'
, use the returned URL. - Perform a PUT to the content property:
curl -XPUT http://localhost:8080/contracts/45af1f9b-759d-4fba-9c6d-ab9e6131f774/content -H "Content-Type: text/plain" -d"blablabla"
- A HTTP 500 error is returned. No stacktace is returned, but if you put a breakpoint in
StoreImpl
, you can see the caught exception there.
Expected behavior
I would expect the content just to be set without a problem.
When I change EmbeddedContent
to have a UUID
id field (and change the type parameter in ContractContentStore
as well), setting the content works without any problems.
Additional context
I have seen that the DefaultS3StoreImpl
retrieves the type information by using MappingContext
, which at first look does implement lookup for nested properties correctly.
Thanks @vierbergenlars, confirmed. I think you are right re DefaultS3StoreImpl too. Will fix soonest.