javarestjerseyjersey-clientcloudconvert

How do I send nested json POST request in java using jersey?


I am using a document converter api called cloudconvert. They don't have an official java library, but a third party java option. I needed a little customization so I cloned the github project and added it to my project. I am sending cloudconvert a .epub file and getting a .pdf file in return. If I use the default settings it works without issue and properly converts my .epub to a .pdf. Here is the code that makes it happen.

Here is what triggers the conversion:

    // Create service object
    CloudConvertService service = new CloudConvertService("api-key");

    // Create conversion process
    ConvertProcess process = service.startProcess(convertFrom, convertTo);

    // Perform conversion
    //convertFromFile is a File object with a .epub extension
    process.startConversion(convertFromFile);

    // Wait for result
    ProcessStatus status;
    waitLoop:
    while (true) {
        status = process.getStatus();
        switch (status.step) {
            case FINISHED:
                break waitLoop;
            case ERROR:
                throw new RuntimeException(status.message);
        }
        // Be gentle
        Thread.sleep(200);
    }
    //Download result
    service.download(status.output.url, convertToFile);

    //lean up
    process.delete();

startConversion() calls:

public void startConversion(File file) throws ParseException, FileNotFoundException, IOException {
    if (!file.exists()) {
        throw new FileNotFoundException("File not found: " + file);
    }       
    startConversion(new FileDataBodyPart("file", file));        
}

Which calls this to actually send the POST request using jersey:

private void startConversion(BodyPart bodyPart) {
    if (args == null) {
        throw new IllegalStateException("No conversion arguments set.");
    }
    MultiPart multipart = new FormDataMultiPart()
              .field("input", "upload")
              .field("outputformat", args.outputformat)
              .bodyPart(bodyPart);
    //root is a class level WebTarget object
    root.request(MediaType.APPLICATION_JSON).post(Entity.entity(multipart, multipart.getMediaType()));
}

Up to this point everything is working. My problem is that the when the conversion happens the .pdf that returns has very small margins. cloudconvert provides a way to change those margins. You can send in an optional json param converteroptions and set the margins manually. I have tested this out using postman and it works without issue, I was able to get a properly formatted margin document. So know this is possible. Here is the POSTMAN info I used:

@POST : https://host123d1qo.cloudconvert.com/process/WDK9Yq0z1xso6ETgvpVQ
Headers: 'Content-Type' : 'application/json'
Body:
    {
    "input": "base64",
    "file": "0AwAAIhMAAAAA",  //base64 file string that is much longer than this
    "outputformat": "pdf",
    "converteroptions": {
        "margin_bottom": 75,
        "margin_top": 75,
        "margin_right": 50,
        "margin_left": 50
    }
}

Here are my attempts at getting the POST request formatted properly, I'm just not very experienced with jersey and the couple of answers I did find on stackoverflow didn't work for me.

Attempt 1, I tried adding the json string as a Multipart.field. It didn't give me any errors and still returned a converted .pdf file, but the margins didn't get changed so I must not be sending it back right.

private void startConversion(BodyPart bodyPart) {
    String jsonString = "{\"margin_bottom\":75,\"margin_top\":75,\"margin_right\":50,\"margin_left\":50}";
    MultiPart multipart = new FormDataMultiPart()
                  .field("input", "upload")
                  .field("outputformat", args.outputformat)
                  .field("converteroptions", jsonString)
                  .bodyPart(bodyPart);
    root.request(MediaType.APPLICATION_JSON).post(Entity.entity(multipart, multipart.getMediaType()));
}

Attempt 2, when I had it working in POSTMAN it was using the 'input' type as 'base64' so I tried changing it to that but it this time it doesn't return anything at all, no request errors, just a timeout error at the 5 minute mark.

//I pass in a File object rather than the bodypart object.
private void startConversion(File file) {
    byte[] encoded1 = Base64.getEncoder().encode(FileUtils.readFileToByteArray(file));
    String encoded64 = new String(encoded1, StandardCharsets.US_ASCII);
    String jsonString = "{\"margin_bottom\":75,\"margin_top\":75,\"margin_right\":50,\"margin_left\":50}";

    MultiPart multipart = new FormDataMultiPart()
              .field("input", "base64")
              .field("outputformat", args.outputformat)
              .field("file", encoded64)
              .field("converteroptions", jsonString);
    root.request(MediaType.APPLICATION_JSON).post(Entity.entity(multipart, multipart.getMediaType()));
}

Attempt 3, after some googling on how to properly send jersey json post requests I changed the format. This time it returned a 400 bad request error.

private void startConversionPDF(File file) throws IOException {
    byte[] encoded1 = Base64.getEncoder().encode(FileUtils.readFileToByteArray(file));
    String encoded64 = new String(encoded1, StandardCharsets.US_ASCII);

    String jsonString = "{\"input\":\"base64\",\"file\":\"" + encoded64 + "\",\"outputformat\":\"pdf\",\"converteroptions\":{\"margin_bottom\":75,\"margin_top\":75,\"margin_right\":50,\"margin_left\":50}}";
    root.request(MediaType.APPLICATION_JSON).post(Entity.json(jsonString));
}

Attempt 4, Someone said you don't need to manually use a jsonString you should use serializable java beans. So I created the corresponding classes and made the request like shown below. Same 400 bad request error.

@XmlRootElement
public class PDFConvert implements Serializable {
    private String input;
    private String file;
    private String outputformat;
    private ConverterOptions converteroptions;
    //with the a default constructor and getters/setters for all
}   
@XmlRootElement
public class ConverterOptions implements Serializable {
    private int margin_bottom;
    private int margin_top;
    private int margin_left;
    private int margin_right;
    //with the a default constructor and getters/setters for all
}

private void startConversionPDF(File file) throws IOException {
    byte[] encoded1 = Base64.getEncoder().encode(FileUtils.readFileToByteArray(file));
    String encoded64 = new String(encoded1, StandardCharsets.US_ASCII);
    PDFConvert data = new PDFConvert();
    data.setInput("base64");
    data.setFile(encoded64);
    data.setOutputformat("pdf");
    ConverterOptions converteroptions = new ConverterOptions();
    converteroptions.setMargin_top(75);
    converteroptions.setMargin_bottom(75);
    converteroptions.setMargin_left(50);
    converteroptions.setMargin_right(50);
    data.setConverteroptions(converteroptions);

    root.request(MediaType.APPLICATION_JSON).post(Entity.json(data));
}

I know this is quite the wall of text, but I wanted to show all the different things I tried so that I wouldn't waste anyone's time. Thank you for any help or ideas you might have to make this work. I really want to make it work with jersey because I have several other conversions I do that work perfectly, they just don't need any converteroptions. Also I know its possible because it works when manually running the process through POSTMAN.

Cloudconvert api documentation for starting a conversion

Github repo with the recommended 3rd party java library I am using/modifying


Solution

  • I finally figured it out. Hours of trial and error. Here is the code that did it:

    private void startConversionPDF(File file) throws IOException {
        if (args == null) {
            throw new IllegalStateException("No conversion arguments set.");
        }
    
        PDFConvert data = new PDFConvert();
        data.setInput("upload");
        data.setOutputformat("pdf");
        ConverterOptions converteroptions = new ConverterOptions();
        converteroptions.setMargin_top(60);
        converteroptions.setMargin_bottom(60);
        converteroptions.setMargin_left(30);
        converteroptions.setMargin_right(30);
        data.setConverteroptions(converteroptions);
    
        MultiPart multipart = new FormDataMultiPart()
                  .bodyPart(new FormDataBodyPart("json", data, MediaType.APPLICATION_JSON_TYPE))
                  .bodyPart(new FileDataBodyPart("file", file));
        root.request(MediaType.APPLICATION_JSON).post(Entity.entity(multipart, multipart.getMediaType()));
    }