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

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

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()));
}

Related

How do I write a Spring Controller method that returns an image?

I would like to write a Spring controller method which returns an image from storage. Below is my current version, but it has two problems:
The #GetMapping annotation requires the 'produces' parameter which is a string array of media types. The program does not work if that parameter is not present; it just displays the image data as text. The problem is that if I want to support an additional media type then I have to recompile the program. Is there a way to set up the 'produces' media type from inside the viewImg method?
The code below will display any image type except svg, which will display only the message "The image cannot be displayed because it contains errors". The web browser (Firefox) identifies it as media type "webp". However, if I remove all media types from the 'produces' string array except the "image/svg+xml" entry, the image is displayed.
Please advise how to write a controller method that is more general (so that it works with any media type) and does not have issues with svg media type.
Here is my test code:
#GetMapping(value = "/pic/{id}",
produces = {
"image/bmp",
"image/gif",
"image/jpeg",
"image/png",
"image/svg+xml",
"image/tiff",
"image/webp"
}
)
public #ResponseBody
byte[] viewImg(#PathVariable Long id) {
byte[] data = new byte[0];
String inputFile = "/path/to/image.svg";
try {
InputStream inputStream = new FileInputStream(inputFile);
long fileSize = new File(inputFile).length();
data = new byte[(int) fileSize];
inputStream.read(data);
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
I recommend FileSystemResource for handling file contents. You can avoid .contentType(..) started line if you don't want to send Content-Type value.
#GetMapping("/pic/{id}")
public ResponseEntity<Resource> viewImg(#PathVariable Long id) throws IOException {
String inputFile = "/path/to/image.svg";
Path path = new File(inputFile).toPath();
FileSystemResource resource = new FileSystemResource(path);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(Files.probeContentType(path)))
.body(resource);
}

Google OAuth 2.0 Access Token Request Error

I have a small Google App Engine project on GitHub what I "finished" several months ago.
It worked until now (Last use was 2-3 months ago), but it seems the Google changed something and I don't find what the real problem is and how I can solve it.
The problem is in this file (line 59):
File
Code:
public class TokenManager {
static private final String TOKEN_URL = "https://www.googleapis.com/oauth2/v3/token";
static private final String ENC = "UTF-8";
private String _refreshToken;
private String _clientId;
private String _clientSecret;
private final Gson _gson = new Gson();
private TokenData _tokenData = new TokenData();
...
private void refreshAccessToken() throws Exception {
String payload = "client_secret=$client_secret&grant_type=refresh_token&refresh_token=$refresh_token&client_id=$client_id";
payload = payload.replace("$client_secret", URLEncoder.encode(_clientSecret, ENC));
payload = payload.replace("$refresh_token", URLEncoder.encode(_refreshToken, ENC));
payload = payload.replace("$client_id", URLEncoder.encode(_clientId, ENC));
HTTPRequest request = new HTTPRequest(new URL(TOKEN_URL), HTTPMethod.POST);
request.setPayload(payload.getBytes(ENC));
String stringData = new String(URLFetchServiceFactory.getURLFetchService().fetch(request).getContent(), ENC);
_tokenData = _gson.fromJson(stringData, TokenData.class);
}
}
The problem is around the access token request. (I use offline refresh token.)
The response is this:
{
"error": "invalid_grant",
"error_description": "Bad Request",
"error_uri": ""
}
I have tried to modify the TOKEN_URL to "https://www.googleapis.com/oauth2/v4/token"
I have tried to generate new refresh token.
I have tried create new OAuth Client ID.
All attempts had same problem.
Have anyone any idea what is wrong?
Thanks for help!
I found the problem.
Probably my previous refresh token was expired. I tried to generating
a new but I misunderstood the process and I considered "code"
parameter as refresh token.
https://developers.google.com/identity/protocols/OAuth2WebServer#offline

How to download image using rest template?

I have the following code:
restTemplate.getForObject("http://img.championat.com/news/big/l/c/ujejn-runi_1439911080563855663.jpg", File.class);
I especially took image which doesn't require authorization and available absolutely for all.
when following code executes I see the following stacktrace:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class java.io.File] and content type [image/jpeg]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:108)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:559)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:512)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:243)
at com.terminal.controller.CreateCompanyController.handleFileUpload(CreateCompanyController.java:615)
what do I wrong?
Image is a byte array, so you need to use byte[].class object as a second argument for RestTemplate.getForObject:
String url = "http://img.championat.com/news/big/l/c/ujejn-runi_1439911080563855663.jpg";
byte[] imageBytes = restTemplate.getForObject(url, byte[].class);
Files.write(Paths.get("image.jpg"), imageBytes);
To make it work, you will need to configure a ByteArrayHttpMessageConverter in your application config:
#Bean
public RestTemplate restTemplate(List<HttpMessageConverter<?>> messageConverters) {
return new RestTemplate(messageConverters);
}
#Bean
public ByteArrayHttpMessageConverter byteArrayHttpMessageConverter() {
return new ByteArrayHttpMessageConverter();
}
I've tested this in a Spring Boot project and the image is saved to a file as expected.
If you simply need to get an image from a URL, Java comes with the javax.imageio.ImageIO class, which contains this method signature:
public static BufferedImage read(URL var0) throws IOException;
example use:
try {
BufferedImage image = ImageIO.read(new URL("http://www.foo.com/icon.png"));
int height = image.getHeight();
int width = image.getWidth();
} catch (IOException e) {}
The RestTemplate is expecting a class (e.g. some in-memory representation) to convert the response from the server into. For example, it could convert a response like:
{id: 1, name: "someone"}
into a class like:
class NamedThing {
private int id;
private String name;
// getters/setters go here
}
By calling:
NamedThing thing = restTemplate.getForObject("/some/url", NamedThing.class);
But, what you seem to really want to do is to take the response from the server and stream it directly to a file. Various methods exist to get the response body of your HTTP request as something like an InputStream that you can read incrementally, and then write out to an OutputStream (e.g. your file).
This answer shows how to use IOUtils.copy() from commons-io to do some the dirty work. But you need to get an InputStream of your file... A simple way is to use an HttpURLConnection. There's a tutorial with more information.

Test AJAX file upload

I want to test the file upload in my application. The upload itself is handled as described in section Direct file upload on http://www.playframework.org/documentation/2.0/JavaFileUpload.
I am using the latest Play20 version and build it as described here.
My current code looks like this, but obviously the part is missing which adds a test file to the request:
Test.java
FakeRequest request = fakeRequest(POST, "/measurement/123/file");
// how to append test file to this request?
Result result = routeAndCall(request);
assertOK(result);
Controller.java
public static uploadFile() {
RequestBody body = request().body();
if (body != null) {
RawBuffer rawBuffer = body.asRaw();
if (rawBuffer != null) {
File uploadedFile = rawBuffer.asFile();
// ...
}
}
return ok();
}
My solution for Play 2 Java file upload testing has been to create a UploadFakeRequest extending FakeRequest. Inside the class I have then my own withRawBody method with something like that:
RawBuffer raw = new RawBuffer(1000, data.getBytes()); // No clue what is the correct value here...
AnyContentAsRaw content = new AnyContentAsRaw(raw);
fake = new play.api.test.FakeRequest(POST, fake.path(), new play.api.test.FakeHeaders(Scala.asScala(map)), content, "127.0.0.1");
The signature of the FakeRequest call, as per source code, is:
case class FakeRequest[A](method: String, uri: String, headers: FakeHeaders, body: A) extends Request[A]
You should be able to inspect the body of a POST request that uploads a file to your application and copy that body as a parameter in your testing code. The alternative is to build it as per RFC description on multipart POST.

Showing images outside my application using Tapestry5

I am developing my first project with Tapestry and I am about to finish, except for the images..
What do I want? I just need to display an image outside my application, example: /home/app/images/image.jpg
What did I try? I have been "googling" and reading Tapestry5 forums, I found this: http://wiki.apache.org/tapestry/Tapestry5HowToStreamAnExistingBinaryFile
I followed the steps, creating classes but I need to display the image embed on another page (so I can't use ImagePage), I tried this:
On page java class
public StreamResponse getImage() {
InputStream input = DetallesMultimedia.class
.getResourceAsStream("/home/santi/Escritorio/evolution-of-mario.jpg"); //On application, i will retrieve this from DB
return new JPEGInline(input,"hellow");
}
On page template
...
<img src="${image}" alt:image/>
...
or
...
${image}
...
Obviusly, this didn't work and I really don't know how can I do it. I read about loading the image on an event (returning the OutputStream on that event, as it's said in the HowTo linked above) but my english is so bad (I am sure you already noticed) and I don't understand well how can I do that.
Could you help me please?
Thanks you all.
I've never seen the examples as on the wiki page. Below some code on how to load an image on the classpath though using a StreamResponse.
#Inject
private ComponentResources resources;
#OnEvent(value = "GET_IMAGE_STREAM_EVENT")
private Object getProfilePic() throws Exception {
InputStream openStream = DetallesMultimedia.class.getResourceAsStream("/home/santi/Escritorio/evolution-of-mario.jpg");
byte[] imageBytes = IOUtils.toByteArray(openStream);
final ByteArrayInputStream output = new ByteArrayInputStream(imageBytes);
final StreamResponse response = new StreamResponse() {
public String getContentType() {
"image/jpegOrPngOrGif";
}
public InputStream getStream() throws IOException {
return output;
}
public void prepareResponse(Response response) {
// add response headers if you need to here
}
};
return response;
}
public String getPicUrl() throws Exception {
return resources.createFormEventLink("GET_IMAGE_STREAM_EVENT");
}
In your template:
<img src="${picUrl}"/>

Categories