Sent request parameters to UploadAction in gwt-upload - java

I get gwt-upload working in a GAE application. As suggested, I implemented a Custom UploadAction to handle the storage of the file in the DataStore. The code goes like this:
public String executeAction(HttpServletRequest request,
List<FileItem> sessionFiles) throws UploadActionException {
logger.info("Starting: DatastoreUploadAction.executeAction");
String executeAction = super.executeAction(request, sessionFiles);
for (FileItem uploadedFile : sessionFiles) {
Long entityId = new Long(2001); // This is where i wanna use a request parameter
InputStream imgStream;
try {
imgStream = uploadedFile.getInputStream();
Blob attachment = new Blob(IOUtils.toByteArray(imgStream));
String contentType = uploadedFile.getContentType();
appointmentDao.setAppointmentAttachment(entityId, attachment,
contentType);
} catch (IOException e) {
logger.error("Unable to store file", e);
throw new UploadActionException(e);
}
}
return executeAction;
}
As you see, the DAO class requires the "EntityID" to store the uploaded file in the DataStore. Now i'm working with a hard-coded value and it goes fine, but i'd like to have the entityID sent by the client as a request parameter. The widget that does the upload is a MultiUploader:
private MultiUploader defaultUploader;
Is it posible to the MultiUploader -or any other Widget- to set a request parameter so i can use it in my UploadAction?

Yes, you can set it on your client-side code. There is method: MultiUploader #setServletPath(java.lang.String), for example:
final MultiUploader u = new MultiUploader();
...
...
...
u.setServletPath(u.getServletPath() + "?entityId="+myObject.getEntityId());
on server side:
String entityId= request.getParameter("entityId");
Read this for more information: Sending additional parameters to the servlet

Related

How to return both JSON response and Byte Array of file in Springboot RestController Response while downloading a file

I am developing a rest controller to download a .docx file into the client system. My code is working fine as the file is getting downloaded. Now I want to enhance the response. My requirement is to also send a JSON payload in the response along with the .docx file content, something like
{"message":"Report downloaded Successfully"}
incase of successful download or with a different message incase of failure.
Below is my restcontroller code:
#RestController
public class DownloadController {
#PostMapping(value="/download",
consumes = {"multipart/form-data"},
produces = {"application/octet-stream"})
public ResponseEntity<?> downloadFile(#RequestParam("file") MultipartFile uploadFile){
//business logic to create the attachment file
try {
File file = new File("path_to_.DOCX file_I_have_created");
byte[] contents = Files.readAllBytes(Paths.get(file.getAbsolutePath()));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDisposition(ContentDisposition.attachment().filename("survey.docx").build());
return new ResponseEntity<>(contents, headers, HttpStatus.OK);
} catch (Exception e){
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
How do I modify my response code to send both the JSON message and the byte[] contents so that the file gets downloaded and I can see the JSON message in the response preview tab in the chrome or response body in postman?
UPDATE: I tried to define a response class like below
public class Downloadresponse {
private byte[] content;
private String message;
//getter,setters
}
With this change in place, I am getting below exception:
Resolved [ HttpMessageNotWritableException: No converter for [class ...Downloadresponse] with preset content-type "application/octet-stream”]
You can't. HTTP doesn't allow you to defined multiple content-types on 1 request/response. That being said, you could send the byte array base64 encoded as part of a json response but would need to handle it in the front-end (if you have any) as it would not trigger the file download process of the browser.
You can define custom class which hold your current content and message . So you can return that class in the response
ResponseClass
{
byte[] contents;
String message;
}
You can send a json object that contain the message and the file as encoded64 string.
And in the client side you decode it and download it.
public record MyRecord(String message, String encodedStringBase64, String filename) {}
...
try {
File file = new File("path_to_.DOCX file_I_have_created");
byte[] contents = Files.readAllBytes(Paths.get(file.getAbsolutePath()));
String encodedString = Base64.getEncoder().encodeToString(contents);
MyRecord record = new MyRecord("Report downloaded Successfully", encodedString, file.getName());
return log.traceExit(ResponseEntity.ok().headers(headers)
.contentType(MediaType.valueOf("application/json")).body(record));
} ...

How to specify filename when using MailGun API

I am currently in the process of integrating MailGun into one of my applications. For my use cases I need to be able to send out attachments. So far, I have been able to send out attachments just fine but my problem is that I am unable to specify the attachment's name. Their documentation found here specifies that the attachment part should be added when including attachment, but does not state how to specify the file's name.
For reference I am using Spring's RestTemplate as my client and I am reading the file as a base64 encoded string which is then trasnformed into a ByteArrayResource. For reference my code is this:
#Override
public EmailDocument sendEmail(EmailDocument email) {
var properties = propProvider.findFor(email.getCompany());
var parts = new LinkedMultiValueMap<String, Object>();
parts.add("from", email.getFrom());
parts.add("to", toCommaString(email.getTo()));
if (!email.getCc().isEmpty()) {
parts.add("cc", toCommaString(email.getCc()));
}
if (!email.getBcc().isEmpty()) {
parts.add("bcc", toCommaString(email.getBcc()));
}
parts.add("subject", email.getSubject());
if (email.getIsHtml()) {
parts.add("html", email.getBody());
} else {
parts.add("text", email.getBody());
}
email.getAttachments().forEach(attachment -> {
var decoded = Base64.getDecoder().decode(attachment.getBytes(StandardCharsets.UTF_8));
parts.add("attachment", new ByteArrayResource(decoded));
});
var header = headerProvider.createHeader("api", properties.getApiKey(), inferMediaType(email));
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(parts, header);
try {
var response = restTemplate.exchange(createDomain(properties.getDomain()), HttpMethod.POST, request, MailGunApiResponse.class);
log.info("Got the following MailGun response {}", response);
if (!response.getStatusCode().is2xxSuccessful()) {
email.setFailureReason(Optional.ofNullable(response.getBody()).map(MailGunApiResponse::getMessage).orElse(null));
email.setRetries(email.getRetries() + 1);
email.setFailed(isFailed(email));
} else {
email.setSent(true);
}
} catch (Exception e) {
log.error("An error has occurred while attempting to send out email {}", email, e);
email.setFailureReason(e.getMessage());
email.setRetries(email.getRetries() + 1);
email.setFailed(isFailed(email));
}
return email;
}
Does anyone know how to specify a filename for the attachment?

As a body HttpServletRequest always returns CoyoteInputStream and not actual body

I'm trying to write a method that checks user credentials and if these are correct parses sent JSON. It is working fine but I cannot access JSON. In my code there is a command InputStream inputStream = request.getInputStream(); that should read JSON but every time it returns org.apache.catalina.connector.CoyoteInputStream#3f1e9348. Please have a look at my code:
#POST
#Path("auth")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.TEXT_HTML)
public String controller(#Context HttpServletRequest request) {
String authorization = request.getHeader("Authorization");
if (authorization == null) {
authorization = request.getHeader("authorization");
}
String basicHeader = "basic";
if (authorization != null && authorization.toLowerCase().startsWith(basicHeader)) {
String base64Credentials = authorization.substring(basicHeader.length()).trim();
String credentials = new String(Base64.getDecoder().decode(base64Credentials),
Charset.forName("UTF-8"));
String[] values = credentials.split(":", 2);
}
try {
InputStream inputStream = request.getInputStream();
System.out.println(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
When I try to use request.getReader() I get the infamous IllegalStateException: getInputStream() has already been called for this request exception. Please see the relevant piece of code:
if ("POST".equalsIgnoreCase(request.getMethod()))
{
try {
String req = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
} catch (IOException e) {
e.printStackTrace();
}
}
I use curl to send POST:
curl -u myusername:mypasswor -H "Content-Type: application/json"
-X POST -d '{"username":"xyz","password":"xyz"}' localhost
You can obtain your body content by declaring a parameter on your method:
public String controller(String body, #Context HttpServletRequest request)
But you can also get the JAX-RS implementation to deserialize that JSON to your intended type:
public String controller(MyExpectedType body, #Context HttpServletRequest request)
This should work because you've declared your expected content type, assuming you have a provider that's applicable (such as jackson-jaxrs...).
Regarding the input stream error: this is probably because the container JAXRS implementation has already parsed the request.
But if you were to process it in a normal scenario, such as in a servlet, you'd still have to correct the way you read it:
The documentation for getInputStream state:
Retrieves the body of the request as binary data using a ServletInputStream. Either this method or getReader() may be called to read the body, not both.
This means that to get the content sent by your client in the body, you need to read the stream:
String body = request.getReader().lines()
.collect(Collectors.joining("\n"));
You can also use the Stream-based API:
byte[] bytes = new byte[request.getContentLength()];
request.getInputStream().read(bytes);
String body = new String(bytes); //you may need to specify the character set
The actual input stream class is implementation-based (container-supplied), so you shouldn't need to be concerned with CoyoteInputStream

File upload along with other object in Jersey restful web service

I want to create an employee information in the system by uploading an image along with employee data. I am able to do it with different rest calls using jersey. But I want to achieve in one rest call.
I provide below the structure. Please help me how to do in this regard.
#POST
#Path("/upload2")
#Consumes({MediaType.MULTIPART_FORM_DATA,MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response uploadFileWithData(
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
Employee emp) {
//..... business login
}
Whenever I am trying to do, I get error in Chrome postman. The simple structure of my Employee json is given below.
{
"Name": "John",
"Age": 23,
"Email": "john#gmail.com",
"Adrs": {
"DoorNo": "12-A",
"Street": "Street-11",
"City": "Bangalore",
"Country": "Karnataka"
}
}
However I can do it by making two different call, but I want to achieve in one rest call so that I can receive the file as well as the actual data of the employee.
Request you to help in this regard.
You can't have two Content-Types (well technically that's what we're doing below, but they are separated with each part of the multipart, but the main type is multipart). That's basically what you are expecting with your method. You are expecting mutlipart and json together as the main media type. The Employee data needs to be part of the multipart. So you can add a #FormDataParam("emp") for the Employee.
#FormDataParam("emp") Employee emp) { ...
Here's the class I used for testing
#Path("/multipart")
public class MultipartResource {
#POST
#Path("/upload2")
#Consumes({MediaType.MULTIPART_FORM_DATA})
public Response uploadFileWithData(
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition cdh,
#FormDataParam("emp") Employee emp) throws Exception{
Image img = ImageIO.read(fileInputStream);
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(img)));
System.out.println(cdh.getName());
System.out.println(emp);
return Response.ok("Cool Tools!").build();
}
}
First I just tested with the client API to make sure it works
#Test
public void testGetIt() throws Exception {
final Client client = ClientBuilder.newBuilder()
.register(MultiPartFeature.class)
.build();
WebTarget t = client.target(Main.BASE_URI).path("multipart").path("upload2");
FileDataBodyPart filePart = new FileDataBodyPart("file",
new File("stackoverflow.png"));
// UPDATE: just tested again, and the below code is not needed.
// It's redundant. Using the FileDataBodyPart already sets the
// Content-Disposition information
filePart.setContentDisposition(
FormDataContentDisposition.name("file")
.fileName("stackoverflow.png").build());
String empPartJson
= "{"
+ " \"id\": 1234,"
+ " \"name\": \"Peeskillet\""
+ "}";
MultiPart multipartEntity = new FormDataMultiPart()
.field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)
.bodyPart(filePart);
Response response = t.request().post(
Entity.entity(multipartEntity, multipartEntity.getMediaType()));
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
response.close();
}
I just created a simple Employee class with an id and name field for testing. This works perfectly fine. It shows the image, prints the content disposition, and prints the Employee object.
I'm not too familiar with Postman, so I saved that testing for last :-)
It appears to work fine also, as you can see the response "Cool Tools". But if we look at the printed Employee data, we'll see that it's null. Which is weird because with the client API it worked fine.
If we look at the Preview window, we'll see the problem
There's no Content-Type header for the emp body part. You can see in the client API I explicitly set it
MultiPart multipartEntity = new FormDataMultiPart()
.field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)
.bodyPart(filePart);
So I guess this is really only part of a full answer. Like I said, I am not familiar with Postman So I don't know how to set Content-Types for individual body parts. The image/png for the image was automatically set for me for the image part (I guess it was just determined by the file extension). If you can figure this out, then the problem should be solved. Please, if you find out how to do this, post it as an answer.
See UPDATE below for solution
And just for completeness...
See here for more about MultiPart with Jersey.
Basic configurations:
Dependency:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey2.version}</version>
</dependency>
Client config:
final Client client = ClientBuilder.newBuilder()
.register(MultiPartFeature.class)
.build();
Server config:
// Create JAX-RS application.
final Application application = new ResourceConfig()
.packages("org.glassfish.jersey.examples.multipart")
.register(MultiPartFeature.class);
If you're having problems with the server configuration, one of the following posts might help
What exactly is the ResourceConfig class in Jersey 2?
152 MULTIPART_FORM_DATA: No injection source found for a parameter of type public javax.ws.rs.core.Response
UPDATE
So as you can see from the Postman client, some clients are unable to set individual parts' Content-Type, this includes the browser, in regards to it's default capabilities when using FormData (js).
We can't expect the client to find away around this, so what we can do, is when receiving the data, explicitly set the Content-Type before deserializing. For example
#POST
#Path("upload2")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFileAndJSON(#FormDataParam("emp") FormDataBodyPart jsonPart,
#FormDataParam("file") FormDataBodyPart bodyPart) {
jsonPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
Employee emp = jsonPart.getValueAs(Employee.class);
}
It's a little extra work to get the POJO, but it is a better solution than forcing the client to try and find it's own solution.
Another option is to use a String parameter and use whatever JSON library you use to deserialze the String to the POJO (like Jackson ObjectMapper). With the previous option, we just let Jersey handle the deserialization, and it will use the same JSON library it uses for all the other JSON endpoints (which might be preferred).
Asides
There is a conversation in these comments that you may be interested in if you are using a different Connector than the default HttpUrlConnection.
You can access the Image File and data from a form using MULTIPART FORM DATA By using the below code.
#POST
#Path("/UpdateProfile")
#Consumes(value={MediaType.APPLICATION_JSON,MediaType.MULTIPART_FORM_DATA})
#Produces(value={MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public Response updateProfile(
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
#FormDataParam("ProfileInfo") String ProfileInfo,
#FormDataParam("registrationId") String registrationId) {
String filePath= "/filepath/"+contentDispositionHeader.getFileName();
OutputStream outputStream = null;
try {
int read = 0;
byte[] bytes = new byte[1024];
outputStream = new FileOutputStream(new File(filePath));
while ((read = fileInputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
outputStream.flush();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch(Exception ex) {}
}
}
}
When I tried #PaulSamsotha's solution with Jersey client 2.21.1, there was 400 error. It worked when I added following in my client code:
MediaType contentType = MediaType.MULTIPART_FORM_DATA_TYPE;
contentType = Boundary.addBoundary(contentType);
Response response = t.request()
.post(Entity.entity(multipartEntity, contentType));
instead of hardcoded MediaType.MULTIPART_FORM_DATA in POST request call.
The reason this is needed is because when you use a different Connector (like Apache) for the Jersey Client, it is unable to alter outbound headers, which is required to add a boundary to the Content-Type. This limitation is explained in the Jersey Client docs. So if you want to use a different Connector, then you need to manually create the boundary.
Your ApplicationConfig should register the MultiPartFeature.class from the glassfish.jersey.media.. so as to enable file upload
#javax.ws.rs.ApplicationPath(ResourcePath.API_ROOT)
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
//register the necessary headers files needed from client
register(CORSConfigurationFilter.class);
//The jackson feature and provider is used for object serialization
//between client and server objects in to a json
register(JacksonFeature.class);
register(JacksonProvider.class);
//Glassfish multipart file uploader feature
register(MultiPartFeature.class);
//inject and registered all resources class using the package
//not to be tempered with
packages("com.flexisaf.safhrms.client.resources");
register(RESTRequestFilter.class);
}
I used file upload example from,
http://www.mkyong.com/webservices/jax-rs/file-upload-example-in-jersey/
in my resource class i have below method
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response attachupload(#FormDataParam("file") byte[] is,
#FormDataParam("file") FormDataContentDisposition fileDetail,
#FormDataParam("fileName") String flename){
attachService.saveAttachment(flename,is);
}
in my attachService.java i have below method
public void saveAttachment(String flename, byte[] is) {
// TODO Auto-generated method stub
attachmentDao.saveAttachment(flename,is);
}
in Dao i have
attach.setData(is);
attach.setFileName(flename);
in my HBM mapping is like
<property name="data" type="binary" >
<column name="data" />
</property>
This working for all type of files like .PDF,.TXT, .PNG etc.,
The request type is multipart/form-data and what you are sending is essentially form fields that go out as bytes with content boundaries separating different form fields.To send an object representation as form field (string), you can send a serialized form from the client that you can then deserialize on the server.
After all no programming environment object is actually ever traveling on the wire. The programming environment on both side are just doing automatic serialization and deserialization that you can also do. That is the cleanest and programming environment quirks free way to do it.
As an example, here is a javascript client posting to a Jersey example service,
submitFile(){
let data = new FormData();
let account = {
"name": "test account",
"location": "Bangalore"
}
data.append('file', this.file);
data.append("accountKey", "44c85e59-afed-4fb2-884d-b3d85b051c44");
data.append("device", "test001");
data.append("account", JSON.stringify(account));
let url = "http://localhost:9090/sensordb/test/file/multipart/upload";
let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
axios.post(url, data, config).then(function(data){
console.log('SUCCESS!!');
console.log(data.data);
}).catch(function(){
console.log('FAILURE!!');
});
},
Here the client is sending a file, 2 form fields (strings) and an account object that has been stringified for transport. here is how the form fields look on the wire,
On the server, you can just deserialize the form fields the way you see fit. To finish this trivial example,
#POST
#Path("/file/multipart/upload")
#Consumes({MediaType.MULTIPART_FORM_DATA})
public Response uploadMultiPart(#Context ContainerRequestContext requestContext,
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition cdh,
#FormDataParam("accountKey") String accountKey,
#FormDataParam("account") String json) {
System.out.println(cdh.getFileName());
System.out.println(cdh.getName());
System.out.println(accountKey);
try {
Account account = Account.deserialize(json);
System.out.println(account.getLocation());
System.out.println(account.getName());
} catch (Exception e) {
e.printStackTrace();
}
return Response.ok().build();
}

How to post multiple files (images) using Jersey Web service.?

Hi I am new to Restful web service.
My Goal is to create multiple user account's in a single request.
I am choosing Jersey API to create a web service.
This WS will create multiple user's account. Each user account was associated with an avatar (Profile picture). I am sending the user information with avatar (The avatar file was encoded into string format using Base64 encoder).
My question is, If the request has many number of users, and each user is associated with bigger size of avatar, Is Restful web service can handle these request?
Also the Request data size in the restful webservice is limited?
Please Suggest me to create a better web service in Jersey API.
Instead of passing avatars in message body you should look at Jersey Multipart support - it will allow you to stream large files to your restful service. Another plus - you'll not need Base64 encoding anymore.
I can achieve this by doing form submit instead of preparing the object in JSON format.
#Path("/upload")
public class MultipleFiles
{
private static final String FILE_UPLOAD_PATH = "/Users/arun_kumar/Pictures";
private static final String CANDIDATE_NAME = "candidateName";
private static final String SUCCESS_RESPONSE = "Successful";
private static final String FAILED_RESPONSE = "Failed";
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces("text/plain")
#Path("/multipleFiles")
public String registerWebService(#Context HttpServletRequest request)
{
String responseStatus = SUCCESS_RESPONSE;
String candidateName = null;
//checks whether there is a file upload request or not
if (ServletFileUpload.isMultipartContent(request))
{
final FileItemFactory factory = new DiskFileItemFactory();
final ServletFileUpload fileUpload = new ServletFileUpload(factory);
try
{
/*
* parseRequest returns a list of FileItem
* but in old (pre-java5) style
*/
final List items = fileUpload.parseRequest(request);
if (items != null)
{
final Iterator iter = items.iterator();
while (iter.hasNext())
{
final FileItem item = (FileItem) iter.next();
final String itemName = item.getName();
final String fieldName = item.getFieldName();
final String fieldValue = item.getString();
if (item.isFormField())
{
candidateName = fieldValue;
System.out.println(" Name: " + fieldName + ", Value: " + fieldValue);
}
else
{
final File savedFile = new File(FILE_UPLOAD_PATH + File.separator
+ itemName);
System.out.println(" Saving the file: " + savedFile.getName());
item.write(savedFile);
}
}
}
}
catch (FileUploadException fue)
{
responseStatus = FAILED_RESPONSE;
fue.printStackTrace();
}
catch (Exception e)
{
responseStatus = FAILED_RESPONSE;
e.printStackTrace();
}
}
System.out.println("Returned Response Status: " + responseStatus);
return responseStatus;
}
}
Reference: Jersey REST Web Service to Upload Multiple Files

Categories