0

I have a Spring application that uses a JPA repository with WebMVC, and I'm trying to extend it for image support. I can upload images and store them server-side, but when it comes to actually retrieving the image with a client, I cannot actually successfully send a response.

First, this is the client API that I expose:

@Streaming
@GET(PATIENT_EXTRA_PATH + "/{id}" + GET_IMAGE_RELPATH)
public Response getImageData(@Path(ID) long id, @Query(IMAGE_FILE) String imageFile);

Here is my first attempt at an implementation:

@RequestMapping(value = PainManagementSvcApi.PATIENT_EXTRA_PATH + "/{id}" +
        PainManagementSvcApi.GET_IMAGE_RELPATH, method = RequestMethod.GET,
        produces = MediaType.IMAGE_JPEG_VALUE)
public HttpServletResponse getImageData(@PathVariable(PainManagementSvcApi.ID) long id,
        @RequestParam(PainManagementSvcApi.IMAGE_FILE) String imageFile,
        Principal principal, HttpServletResponse response) {
    // Do some stuff to ensure image availability and access

    // All of this works
    response.setContentType("image/jpeg");
    imageFileManager.copyImageData(imageFile, response.getOutputStream());
    response.setStatus(HttpServletResponse.SC_OK);

    // Return the response
    return response;
}

However, that approach produces the following exception when testing:

javax.servlet.ServletException: Could not resolve view with name 'extra/1/image' in servlet with name 'dispatcherServlet'

After looking around for a bit, I thought that I perhaps needed the @ResponseBody annotation as well as follows:

@RequestMapping(value = PainManagementSvcApi.PATIENT_EXTRA_PATH + "/{id}" +
        PainManagementSvcApi.GET_IMAGE_RELPATH, method = RequestMethod.GET,
        produces = MediaType.IMAGE_JPEG_VALUE)
public HttpServletResponse getImageData(@PathVariable(PainManagementSvcApi.ID) long id,
        @RequestParam(PainManagementSvcApi.IMAGE_FILE) String imageFile,
        Principal principal, HttpServletResponse response) {
    // Same code as before
}

However, adding @ResponseBody conflicts with a working video example that I have (which uses Spring, but does not use a JPA repository or WebMVC), and produces the following exception:

org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation

In addition to using a Response return value, I've also tried returing a FileSystemResource instead, but that produces JSON errors like the following:

Expected BEGIN_OBJECT but was STRING at line 1 column 1

Since I'm just trying to return an image, I would wouldn't think that JSON is needed, but I can't figure out how to remove the JSON header information, since the produces and setContentType above apparently don't have any impact. Additionally, since the images can potentially be large, I'd think that the @Streaming annotation is warranted, and that can only be used with a Response.

If it helps, here is the code that I've been using to test my application:

Response response = user.getImageData(extra.getId(), fileName);
assertEquals(HttpStatus.SC_OK, response.getStatus());
InputStream imageStream = response.getBody().in();
byte[] retrievedFile = IOUtils.toByteArray(imageStream);
byte[] originalFile = IOUtils.toByteArray(new FileInputStream(images[index++]));
assertTrue(Arrays.equals(originalFile, retrievedFile));

I've been at this, now, for a few days, and I've not found anything that would suggest how to overcome my problems above. I would think that using a JPA repository with WebMVC to gate access to a static file store comes up often, but I've yet to find anything useful. Any help would be appreciated.

Thanks

user3570982
  • 539
  • 1
  • 6
  • 14

3 Answers3

0

Try adding ByteArrayHttpMessageConverter to converters in Spring MVC setup and change the return type of the method to byte[]. Also do not forget to add @ResponseBody annotation.

iesen
  • 65
  • 1
  • 11
  • Unfortunately, that results in the same issue on the client that I encountered with the FileSystemResource approach; namely: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1. However, it does clean up exceptions server-side. To register the Bean, do I need anything in my server other than: `@Bean public ByteArrayHttpMessageConverter byteArrayHttpMessageConverter () {...}` – user3570982 Nov 11 '14 at 23:04
  • You should also add the same converters on the client side – iesen Nov 12 '14 at 06:38
  • Thank you for the suggestion. I'm still new to Spring, so I'm not exactly certain how to add additional conversion outside of the default on the client side. For the sake of clarity, I've posted a separate question at [link](http://stackoverflow.com/questions/26881125/convert-responseentity-on-client-retrofit-retrofiterror-com-google-gson-jsons). – user3570982 Nov 12 '14 at 07:10
  • It seems that you are using Retrofit library for client. You can try adding this to your interface **@Headers({"Content-Type: image/jpeg"})**. After that you can get the inputstream with response.getBody().in(). Then you can convert this InputStream to byte array with some helper method. You can follow this link with Retrofit file support. (http://blog.pawelhajduk.net/retrofit-file-support/). – iesen Nov 12 '14 at 07:23
  • I suggest that your server side controller should return **byte[]**. If you add **Content-Type: image/jpeg** to the client than you should also set this header in the server side as Paul Vargas metioned above. If you do not set this on server side it should have some default content-type like **application/octet-stream** or smth like that.(You can see the response headers with Firebug like tool on the browser). If client side matches the header on the server side sent, it should be able to process the response. – iesen Nov 12 '14 at 07:29
  • Sorry, I should have responded earlier. I did try it, but it results in a 406 error. I've since moved on to other aspects of this project, and will return to image transfers if time allows. If, however, you have an interest in digging into this issue yourself, I can package up a scaled down version of what I have and send it your way. – user3570982 Nov 15 '14 at 23:55
  • if you send it, i can look up when i have time and update the answer – iesen Nov 16 '14 at 09:41
  • I've packaged up a pared down version of the server (basically just the image transfer and minimal authentication). It can be obtained [here](http://www.hasslefixes.com/ss_version.html) – user3570982 Nov 16 '14 at 20:04
  • I know that you've probably not yet had time to take a look, but I just wanted to make sure that you were able to obtain the server contents. – user3570982 Nov 20 '14 at 00:48
  • You wouldn't happen to have had a chance to examine the issue yet, right? – user3570982 Nov 26 '14 at 16:26
0

You can try the following:

@RequestMapping(value = PATIENT_EXTRA_PATH + "/{id}" + GET_IMAGE_RELPATH, 
                method = RequestMethod.GET)
public ResponseEntity<byte[]> getImageData(@PathVariable(PainManagementSvcApi.ID) long id,
        @RequestParam(PainManagementSvcApi.IMAGE_FILE) String imageFile,
        Principal principal, HttpServletResponse response) {

    // Do some stuff to ensure image availability and access

    response.setContentType("image/jpeg");

    // image to byte array
    byte[] contents = imageFileManager.copyImageData(imageFile);

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.parseMediaType("image/jpeg"));
    return new ResponseEntity<byte[]>(contents, headers, HttpStatus.OK);
}

As you can see, you need an array of bytes of the image. However, you can also use a stream. See Return a stream with Spring MVC's ResponseEntity.

Community
  • 1
  • 1
Paul Vargas
  • 38,878
  • 15
  • 91
  • 139
  • Unfortunately, that results in the same issue on the client that I encountered with the `FileSystemResource` approach; namely: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1. However, it does clean up exceptions server-side. – user3570982 Nov 11 '14 at 22:37
  • I just realized that the issue is likely no longer on the server side, but on the client after your suggestion (thanks for the link to the InputStreamResource discussion, by the way). I take it, then, that the client needs to do something with `RestAdapter.Builder.setConverter`, is that correct? – user3570982 Nov 12 '14 at 06:31
  • Which client library you are using in your test? – Paul Vargas Nov 12 '14 at 07:15
  • I'm just using an Apache HttpClient implementing the API. Is that what you're asking? If it would help, and you don't mind taking a look, I could simply package the server and test project for you to review. – user3570982 Nov 12 '14 at 17:09
  • I think that [this](http://www.java2s.com/Code/Java/Network-Protocol/GettinganImagefromaURL.htm) is more simply. :) – Paul Vargas Nov 12 '14 at 17:19
  • That's great, except, as mentioned in my original question, the image is not served unless the user has access to it. I could supply a URL to the image path, but, unless they have a valid OAuth2 token, they still won't be able to view it. Could the approach you linked be used with authentication? – user3570982 Nov 12 '14 at 17:37
  • I'm not familiar with OAuth2. You may want to see http://stackoverflow.com/questions/2793150/using-java-net-urlconnection-to-fire-and-handle-http-requests or http://www.ibm.com/developerworks/library/wa-oauthsupport/ – Paul Vargas Nov 12 '14 at 18:48
0

Alright, this is just about the dumbest way I can think of to solve my problem, and I don't think it's a real answer, but it's the only thing I can get to work. For some reason, my setup seems to absolutely require a persisted entity in order for JSON errors not to appear. That being the case, I built a wrapper around a List<Byte> (it seems that even a byte[] wasn't good enough -- it needed to be something that could be annotated with @ElementCollection). When returning an image, I copy the image contents into the wrapper I just mentioned; on the client end, I extract the bytes stored. I then erase the persisted image data.

I know that this is excessively intensive in terms of the overhead required, and probably won't work in a realistic setting, but, as I said, it's all I've got. It's stupid beyond all belief, but I've given up on trying to find a correct solution.

By the way, if anyone would like to take a look at the original attempts at a correct solution (sans the Image wrapper mentioned above), that is available here

user3570982
  • 539
  • 1
  • 6
  • 14