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
920 stars 563 forks source link

Query methods expecting complex parameter types not handled correctly [DATAREST-27] #409

Closed spring-projects-issues closed 12 years ago

spring-projects-issues commented 12 years ago

Oliver Drotbohm opened DATAREST-27 and commented

Assume you have a repository like this:

interface OrderRepository extends CrudRepository<Order, Long> {

  List<Order> findByCustomer(Customer customer);
}

If the method now gets exposed, how shall one provide the Customer object for the GET request? Currently a plain call to

http://localhost:8080/order/search/findByCustomer

causes a

java.lang.NullPointerException
    at java.io.StringReader.<init>(StringReader.java:33)
    at org.codehaus.jackson.JsonFactory.createJsonParser(JsonFactory.java:636)
    at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1863)
    at org.springframework.data.rest.webmvc.RepositoryRestController.query(RepositoryRestController.java:418)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:213)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:126)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:96)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:617)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:923)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:735)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:643)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:450)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:131)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:524)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1067)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:377)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:192)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1001)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:250)
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:149)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:111)
    at org.eclipse.jetty.server.Server.handle(Server.java:360)
    at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:454)
    at org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:890)
    at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:944)
    at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:630)
    at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:230)
    at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:77)
    at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:622)
    at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:46)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:603)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:538)
    at java.lang.Thread.run(Thread.java:680)

Affects: 1.0.0.RC1

spring-projects-issues commented 12 years ago

wims.tijd commented

would it not be just to send the json over as in :

invocation.open("GET", url, true); invocation.send(json);

spring-projects-issues commented 12 years ago

Jon Brisbin commented

The latest snapshot includes code to deal with this situation by allowing you to specify a query parameter of the ID of the complex parameter. In your example, adding a query string of "?customer=1" would look up the Customer entity with ID "1" and use that as the parameter. If the type is not managed by a Repository, then replace the ID value with serialized JSON.

Additionally, it was necessary to add code to enforce putting @Param on query method parameters since abstract classes (including interfaces) do not keep debug information in the bytecode so it isn't possible to use the method parameter name on the interface as the query parameter key. To properly expose the search method you use as an example, you'd need to add @Param("customer") to your method parameter

spring-projects-issues commented 12 years ago

Jon Brisbin commented

Added code to allow you to specify an ID in a query parameter for complex objects managed by a repository. The exporter will look up a managed entity by that ID and use that as the parameter. If the complex object is not managed by a repository, then there should either be a ConversionService converter for that type to turn a String (the URL query string parameter) into that object or you can specify a complex object as a fragment of JSON as the value of the query string parameter. Be sure the JSON is properly URL-encoded, though