katharsis-project / katharsis-framework

Katharsis adds powerful layer for RESTful endpoints providing implementenation of JSON:API standard
http://katharsis.io
Apache License 2.0
135 stars 65 forks source link

@JsonProperty not being mapped properly in PropertyUtils Katharsis (v 3.0.1) #433

Open sankar1v opened 7 years ago

sankar1v commented 7 years ago

In my application we use @JsonProperty on field names in our POJOs to be JSON API compliant by following the recommended naming conventions. This worked perfectly fine earlier, but in 3.0.1.

We recently upgraded to version 3.0.1. @JsonProperty mapping of an "embedded" POJO breaks in PropertyUtils if the JSON property name differs from the POJO name.

So in our case we have something like below:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@JsonApiResource(type = "segments")
public class Segment implements Serializable {

    @Id
    @JsonApiId
    private String segmentId;

    @JsonProperty("name")
    private String name;

    private String language;
    private String country;

    @OneToMany(targetEntity = SubSegment.class, mappedBy = "segmentId", fetch = FetchType.EAGER)
    @Fetch(value = FetchMode.SUBSELECT)
    @JsonApiRelation(lookUp = LookupIncludeBehavior.AUTOMATICALLY_ALWAYS, serialize = SerializeType.ONLY_ID, opposite = "segment")
    @JsonProperty("sub-segments")
    private List<SubSegment> subSegments;

}
@Component
@Log4j
public class SegmentRepository extends ResourceRepositoryBase<Segment, String> {

    @Autowired
    SegmentService segmentService;

    public SegmentRepository() {
        super(Segment.class);
    }

    @Override
    public synchronized ResourceList<Segment> findAll(QuerySpec querySpec) {
        return segmentService.findAll(querySpec);
    }

    @Override
    public Segment findOne(String id, QuerySpec querySpec) {
        log.info("Received request to findOne by id:" + id);
        Segment greeting = segmentService.findOne(id);
        ResourceList<Segment> list = querySpec.apply(Arrays.asList(greeting));
        Segment result = list.get(0);
        return result;
    }

    @Override
    public Segment save(Segment segment) {
        return segmentService.save(segment);
    }
}
public interface SegmentCrudRepository extends CrudRepository<Segment, String> {
}

When I try to access SomeObject resource that contains this property outputPorts, below exception is thrown by Katharsis:

java.lang.IllegalStateException: field Segment.subSegments not found at io.katharsis.repository.RelationshipRepositoryBase.getOppositeName(RelationshipRepositoryBase.java:240) at io.katharsis.repository.RelationshipRepositoryBase.findTargets(RelationshipRepositoryBase.java:178) at io.katharsis.core.internal.repository.adapter.RelationshipRepositoryAdapter$7.invoke(RelationshipRepositoryAdapter.java:223) at io.katharsis.core.internal.repository.adapter.ResponseRepositoryAdapter$RepositoryBulkRequestFilterChainImpl.doFilter(ResponseRepositoryAdapter.java:222) at io.katharsis.core.internal.repository.adapter.RelationshipRepositoryAdapter.findBulkManyTargets(RelationshipRepositoryAdapter.java:228) at io.katharsis.core.internal.resource.IncludeLookupSetter.lookupRelationshipField(IncludeLookupSetter.java:238) at io.katharsis.core.internal.resource.IncludeLookupSetter.populate(IncludeLookupSetter.java:133) at io.katharsis.core.internal.resource.IncludeLookupSetter.setIncludedElements(IncludeLookupSetter.java:77) at io.katharsis.core.internal.resource.DocumentMapper.addRelationDataAndInclusions(DocumentMapper.java:70) at io.katharsis.core.internal.resource.DocumentMapper.toDocument(DocumentMapper.java:63) at io.katharsis.core.internal.resource.DocumentMapper.toDocument(DocumentMapper.java:50) at io.katharsis.core.internal.dispatcher.controller.CollectionGet.handle(CollectionGet.java:59) at io.katharsis.core.internal.dispatcher.RequestDispatcher$DefaultFilterChain.doFilter(RequestDispatcher.java:126) at io.katharsis.core.internal.dispatcher.RequestDispatcher.dispatchRequest(RequestDispatcher.java:80) at io.katharsis.spring.KatharsisFilterV2.dispatchRequest(KatharsisFilterV2.java:131) at io.katharsis.spring.KatharsisFilterV2.invoke(KatharsisFilterV2.java:105) at io.katharsis.spring.KatharsisFilterV2.doFilter(KatharsisFilterV2.java:90) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at io.katharsis.spring.ErrorHandlerFilter.doFilterInternal(ErrorHandlerFilter.java:41) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) 12:07:42.904 [http-nio-9090-exec-1] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception io.katharsis.invoker.internal.KatharsisInvokerException: java.lang.IllegalStateException: field SomeObject.outputPorts not found at io.katharsis.spring.KatharsisFilterV2.invoke(KatharsisFilterV2.java:107) at io.katharsis.spring.KatharsisFilterV2.doFilter(KatharsisFilterV2.java:90) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at io.katharsis.spring.ErrorHandlerFilter.doFilterInternal(ErrorHandlerFilter.java:41) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745)

So it seems like Katharsis is no longer deserializing that lookup to be at com.san.katharsis.example.SomeObject.outputPorts, but instead looking for a POJO with the @ JsonProperty annotated value.

Thanks all!

FYI, it seems similar issue exists in 2.8.2(https://github.com/katharsis-project/katharsis-framework/issues/267), which is fixed later. But I could see the issue 3.0.1.

chb0github commented 7 years ago

You're @Id annotation is not defined - I will go on the assumption that you meant @JsonApiId

Also, you're OutputPort is not defined. And, your repository class is incomplete.

If you have a working example of the problem I can have a look

sankar1v commented 7 years ago

Hi,

Please find the code here. I have renamed class names SomeObject to Segment & OutputPort to SubSegment. Code will work fine if I comment //@JsonProperty("sub-segments") in Segment.java. Otherwise it will fail when I try to access the url http://localhost:9090/api/segments.

java.lang.IllegalStateException: field Segment.subSegments not found
at 

san-katharsis-sample.zip

sankar1v commented 7 years ago

@chb0github , Did you manage to check this ?

chb0github commented 7 years ago

I didn't see the attached zip file so no, I didn't. Better would have been to create a pr from the existing sample app.

Either myself or @hibaymj will have a look.

On Mon, Jun 19, 2017, 9:52 PM sankar1v notifications@github.com wrote:

@chb0github https://github.com/chb0github , Did you manage to check this ?

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/katharsis-project/katharsis-framework/issues/433#issuecomment-309644237, or mute the thread https://github.com/notifications/unsubscribe-auth/ABaI0McooPbEx09rkGYedpNteDqlEzFSks5sF0_wgaJpZM4Nw1-E .

taylormathewson commented 7 years ago

I've encountered a similar issue after switching jackson to kebab-case instead of default camel case.

Issue is where RelationshipRepositoryBase.getOppositeName calls resourceInformation.findRelationshipFieldByName which calls getJsonField which looks up by the name, not the underlying name. I have switched to dasherized/kebab-case serialization which is causing this problem. The resource information has a name like some-field and not someField, whereas underlyingName shows someField.

IMO, RelationshipRepositoryBase.getOppositeName should lead to a lookup by the underlyingName otherwise the serialization config (dasherized vs camel case) creeps into dtos/entities.

EDIT: clarity