How can I send List<> items as POST data the right way in Java?

Roshan Upreti :

I'm working on a java service where data from Table A goes to Table B. The service sends POST requests to a REST server and for each row copied, a location header gets created as a response, confirming that a new resource is created and returning a POJO as JSON when GET request is issued to the newly created resource. This is the resource method that handles POST requests.

@POST
@Produces({MediaType.APPLICATION_JSON})
public Response migrateToMinio(@Context UriInfo uriInfo) throws Exception {
    tiedostoService = new TiedostoService();
    attachmentService = new AttachmentService();
    List<Tiedosto> tiedostoList = tiedostoService.getAllFiles();
    List<String> responseList = new ArrayList<>();
    Response r;
    Integer newRow;
    String responseData = null;
    int i=1;
    for (Tiedosto tiedosto : tiedostoList) {
        Attachment attachment = new Attachment();
        attachment.setCustomerId(tiedosto.getCustomerId());
        attachment.setSize(tiedosto.getFileSize());
        newRow = attachmentService.createNew(attachment);
        UriBuilder builder = uriInfo.getAbsolutePathBuilder();
        if (newRow == 1) {
            builder.path(Integer.toString(i));
            r = Response.created(builder.build()).build();
            responseData = r.getLocation().toString();
            i++;
        }
        responseList.add(responseData);
    }
    String jsonString = new Gson().toJson(responseList);
    return Response.status(Response.Status.OK).entity(jsonString).build();
}

tiedostoService and attachmentService are service classes for two tables. tiedostoList has all the rows from tiedosto table and has been iterated inside for loop so that a new row in attachments table is created for every item in tiedostoList. When I send POST request to /rest/attachments, it takes a few seconds to process and returns status 200 with a list of created resource like this: Postman output

Now, my question is, is there a way to implement it so that the newly created resource location is returned immediately (maybe as 201 created) after it's creation, without having to wait for the final status 200?

Justin Albano :

Such a solution would presume that you the service saving the attachment (AttachmentService) would be able to compute the location prior to saving it. For example, the service should be able to know that if the last attachment was stored with an ID of 10, the next attachment would be stored with an ID of 11 (or however the subsequent ID would be calculated) and therefore would have a location of http://localhost:8080/rest/attachments/11.

Presuming that logic is possible in your service, you could create a receipt object that contains the location of the created resource and a future that represents the saved resource. This receipt object can be returned by the service instead of the attachment itself. The implementation of such a receipt object could resemble the following:

public class CreationReceipt<T> {

    private final String location;
    private final Future<T> attachment;

    public CreationReceipt(String location, Future<T> attachment) {
        this.location = location;
        this.attachment = attachment;
    }

    public String getLocation() {
        return location;
    }

    public Future<T> getAttachment() {
        return attachment;
    }
}

In AttachmentService, there would exist a method that stores a new Attachment and returns a CreationReceipt<Attachment>:

public class AttachmentService {

    public CreationReceipt<Attachment> createNew(Attachment attachment) {
        String location = computeLocationFor(attachment);
        Future<Attachment> savedAttachment = saveAsynchronously(attachment);
        return new CreationReceipt<>(location, savedAttachment);
    }

    private Future<Attachment> saveAsynchronously(Attachment attachment) { ... }

    private String computeLocationFor(Attachment attachment) { ... }
}

The logic for pre-computing the location of an Attachment will depend on the specifics of your application (and I will leave it stubbed for you to add the logic), but the logic for saving an Attachment asynchronously can follow a common pattern. Using an ExecutorService, synchronous logic for saving an Attachment (which your application already uses) can be made asynchronous. To do this, the synchronous logic is submitted to an ExecutorService (using the submit method) and a Future is returned by the ExecutorService which wraps the saved Attachment and will complete when the Attachment is successfully saved.

An example (albeit incomplete) implementation would resemble:

public class AttachmentService {

    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    public CreationReceipt<Attachment> createNew(Attachment attachment) {
        String location = computeLocationFor(attachment);
        Future<Attachment> savedAttachment = saveAsynchronously(attachment);
        return new CreationReceipt<>(location, savedAttachment);
    }

    private Future<Attachment> saveAsynchronously(Attachment attachment) {
        return executor.submit(() -> save(attachment));
    }

    private Attachment save(Attachment attachment) { ... }

    private String computeLocationFor(Attachment attachment) { ... }
}

Note that this implementation only allows one Attachment to be saved at a time (by nature of using Executors.newSingleThreadExecutor()), but that logic can be changed by defining executor to use a different ExecutorService implementation. To complete this implementation, simply add an implementation for the save method that synchronously saves a new Attachment object (this synchronous logic should be found in your existing createNew method from AttachmentService).

From this point, the logic for saving an Attachment would be:

List<String> savedLocations = new ArrayList<>();

for (Tiedosto tiedosto : tiedostoList) {
    Attachment attachment = new Attachment();
    attachment.setCustomerId(tiedosto.getCustomerId());
    attachment.setSize(tiedosto.getFileSize());

    CreationReceipt<Attachment> receipt = attachmentService.createNew(attachmentToSave);
    savedLocations.add(receipt.getLocation());
}

// Do something with savedLocations

This is an imcomplete implementation, but it should demonstrate the basic structure that would be used. Once the loop is complete, you could include saveLocations as the body of the response and set the response status code to 201 Created.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=108648&siteId=1