Skip to content
This repository was archived by the owner on Aug 13, 2020. It is now read-only.

Multipart File Upload Endpoint Generation

allanmckenzie edited this page Mar 16, 2017 · 19 revisions

As of version 1.5, the Framework now allows Multipart endpoints for file upload to be generated using RAML in a similar way to the other endpoint's generation.

The endpoint is specified in RAML and the framework will use that RAML to generate the multipart endpoint. When a request reaches that endpoint, the generated code will save the file into the database using the File-Service. The generated code will create a JsonEnvelope containing the newly stored file's id with the property name of the file part specified in RAML, and any rest path parameters.

Then a handler with an annotation matching the command name is invoked, passing it the new JsonEnvelope.

The Raml

/records/albums/{albumId}/artwork:
post:
  description: |
    Upload Album artwork
    ...
    (mapping):
      requestType: multipart/form-data
      name: records.command.upload-album-artwork
    ...
  body:
    multipart/form-data:
      formParameters:
        # This name is the name of the file part and, once the file is stored and 
        # we have an id for the file, will be used as the name of the field 
        # containing the file id in your JsonEnvelope
        albumArtworkId:  
          description: The id of the newly stored artwork
          type: file # this is mandatory and must always be set to 'file'
  responses:
    202:
      description: Request accepted
    400:
      description: Bad Request
    500:
      description: Internal Server Error

This pretty much matches the RAML for other endpoint generation. It starts with the URL path (with any path parameters) and contains the mapping to your command name: in this case records.command.upload-album-artwork.

The requestType must always be multipart/form-data

Next is the body. This too must be multipart/form-data. Then the form parameters which must contain one form parameter who's name is the name used for the file part/field name in the JsonEnvelope. In this case the name is set to albumArtworkId

The type of the form parameter must be set to file. This is required by RAML to let it know we are dealing with a multipart endpoint here

filePartName/fieldName

The endpoint generation needs to link the file part name in the multipart request with a name for the fileId field in the resulting JsonEnvelope. It is very important therefore, to ensure that the form parameter name in the RAML and the filePart name in your rest client match. Failure to ensure they match will result in a BadRequestException and a HTTP 400 as a response.

Handler Class

In a similar way to the other endpoints, a handler class needs to be created:

@ServiceComponent(COMMAND_API)
public class AlbumArtworkUploadCommandHandler {

    @Inject
    Sender sender;

    @Handles("records.command.upload-album-artwork")
    public void uploadFile(final JsonEnvelope command)  {
        
        // the JsonEnvelope will contain both path parameters and the id of the new file
        final JsonObject payload = command.payloadAsJsonObject();

        // path parameter from the url
        final String albumId = payload.getString("albumId");

        // the id of the newly stored file. The field name is specified in the RAML
        // and is the name of the file part
        final String albumArtworkId = payload.getString("albumArtworkId");

       // do any required logic...

       sender.send(jsonEnvelope);
    }
}

The handler will be discovered at startup using the command name in the method's annotation. The JsonEnvelope containing path parameters and the file id can then be passed to the lower tiers in the usual way.

Maven

    <dependency>
        <groupId>uk.gov.justice.services</groupId>
        <artifactId>rest-adapter-file-service</artifactId>
    </dependency>
    <dependency>
        <groupId>uk.gov.justice.services</groupId>
        <artifactId>file-service-persistence-postgres</artifactId>
    </dependency>

Testing/client

To aid integration tests for your endpoint there is a test client class in test-utils:

uk.gov.justice.services.test.utils.core.rest.MultipartRestClient

To Use:

      final String url = ...
      final String filename = ...
   
      // this must match the form parameter name in the RAML, and will be the 
      // field name of the new file id in the Handler's JsonEnvelope. In our case
      // the name should be 'albumArtworkId'
      final String filePartName = ... 

      final File file = ...

      try(final InputStream inputStream = new FileInputStream(file)) {

           final MultipartRestClient multipartRestClient = new MultipartRestClient();

           final FilePart filePart = new FilePart(
               filePartName,
               filename,
               inputStream);

           final HttpResponse response = multipartRestClient.post(filePart, url);
      }