Testing a Rest controller post request - java

So i have a method tha takes and user Id and a multipartFile and uploads it to aws and im trying to test it but i get and error java.lang.IllegalArgumentException: Not enough variable values available to expand
this is the method to upload the file
public void uploadUserProfileImage(#PathVariable("userProfileId")UUID userProfileId ,
#RequestParam("file")MultipartFile file){
userProfileService.uploadUserProfileImage(userProfileId,file);
}
and this is the test i came up with, im trying to learn how to test my methods.
#Test
void uploadUserProfileImage() throws Exception {
BufferedImage img;
img = ImageIO.read(new File("src/main/java/images/bane.png"));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(img, "file", byteArrayOutputStream);
byte[] byteArryaImg = byteArrayOutputStream.toByteArray();
//given
MockMultipartFile file = new MockMultipartFile("file",
"file",
MediaType.IMAGE_PNG_VALUE,
byteArryaImg);
UserProfileModel user = new UserProfileModel(UUID.randomUUID(), "luad", "ada");
String userProfileId = user.getUserProfileId().toString();
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
mockMvc.perform(multipart("/{userProfileId}/image/upload")
.file(file)
.accept(MediaType.IMAGE_PNG))
.andExpect(status()
.isOk());
}

I suppose that you may use such as following;
MockMultipartFile mockMultipartFile = new MockMultipartFile("image-name", "mock-file.png", "image/jpeg", "image".getBytes());
MockHttpServletRequestBuilder mockRequest = fileUpload("/{userProfileId}/image/upload").file(mockMultipartFile)
you need to import fileUpluad method from;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload;
then, just perform such as;
mockMvc.perform(mockRequest).andExpect(status().isOk());

Related

Return a zip (or any file) from the server on the client browser (REST)

So I am using Java for my Server and Angular for the Client. I am currently working on a feature where you can select multiple files from a table and when you press on download, it generates a zip file and downloads it to your browser. As of right now, the server now creates the zip file and I can access it in the server files. All that is left to do is to make it download on the client's browser. (the zip file is deleted after the client downloads it)
After doing some research, I found out that you can use a fileOutputStream to do this. I also saw some tools like retrofit... I am using REST and this is what my code looks like. How would I achieve my goal as simply as possible?
Angular
httpGetDownloadZip(target: string[]): Observable<ServerAnswer> {
const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
const options = {
headers,
params,
};
return this.http
.get<ServerAnswer>(this.BASE_URL + '/files/downloadZip', options)
.pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
Java zipping method
public void getDownloadZip(String[] files, String folderName) throws IOException {
[...] // The method is huge but basically I generate a folder called "Download/" in the server
// Zipping the "Download/" folder
ZipUtil.pack(new File("Download"), new File("selected-files.zip"));
// what do I return ???
return;
}
Java context
server.createContext("/files/downloadZip", new HttpHandler() {
#Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
Controller.getDownloadZip(files, folderName);
// what do I return to download the file on the client's browser ????
return;
}
});
A possible approach to successfully download your zip file can be the described in the following paragraphs.
First, consider returning a reference to the zip file obtained as the compression result in your downloadZip method:
public File getDownloadZip(String[] files, String folderName) throws IOException {
[...] // The method is huge but basically I generate a folder called "Download/" in the server
// Zipping the "Download/" folder
File selectedFilesZipFile = new File("selected-files.zip")
ZipUtil.pack(new File("Download"), selectedFilesZipFile);
// return the zipped file obtained as result of the previous operation
return selectedFilesZipFile;
}
Now, modify your HttpHandler to perform the download:
server.createContext("/files/downloadZip", new HttpHandler() {
#Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
// Get a reference to the zipped file
File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);
// Set the appropiate Content-Type
exchange.getResponseHeaders().set("Content-Type", "application/zip");
// Optionally, if the file is downloaded in an anchor, set the appropiate content disposition
// exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=selected-files.zip");
// Download the file. I used java.nio.Files to copy the file contents, but please, feel free
// to use other option like java.io or the Commons-IO library, for instance
exchange.sendResponseHeaders(200, selectedFilesZipFile.length());
try (OutputStream responseBody = httpExchange.getResponseBody()) {
Files.copy(selectedFilesZipFile.toPath(), responseBody);
responseBody.flush();
}
}
});
Now the problem is how to deal with the download in Angular.
As suggested in the previous code, if the resource is public or you have a way to manage your security token, including it as a parameter in the URL, for instance, one possible solution is to not use Angular HttpClient but an anchor with an href that points to your ever backend handler method directly.
If you need to use Angular HttpClient, perhaps to include your auth tokens, then you can try the approach proposed in this great SO question.
First, in your handler, encode to Base64 the zipped file contents to simplify the task of byte handling (in a general use case, you can typically return from your server a JSON object with the file content and metadata describing that content, like content-type, etcetera):
server.createContext("/files/downloadZip", new HttpHandler() {
#Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
// Get a reference to the zipped file
File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);
// Set the appropiate Content-Type
exchange.getResponseHeaders().set("Content-Type", "application/zip");
// Download the file
byte[] fileContent = Files.readAllBytes(selectedFilesZipFile.toPath());
byte[] base64Data = Base64.getEncoder().encode(fileContent);
exchange.sendResponseHeaders(200, base64Data.length);
try (OutputStream responseBody = httpExchange.getResponseBody()) {
// Here I am using Commons-IO IOUtils: again, please, feel free to use other alternatives for writing
// the base64 data to the response outputstream
IOUtils.write(base64Data, responseBody);
responseBody.flush();
}
}
});
After that, use the following code in you client side Angular component to perform the download:
this.downloadService.httpGetDownloadZip(['target1','target2']).pipe(
tap((b64Data) => {
const blob = this.b64toBlob(b64Data, 'application/zip');
const blobUrl = URL.createObjectURL(blob);
window.open(blobUrl);
})
).subscribe()
As indicated in the aforementioned question, b64toBlob will look like this:
private b64toBlob(b64Data: string, contentType = '', sliceSize = 512) {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return blob;
}
Probably you will need to slightly modify the httpGetDownloadZip method in your service to take into account the returned base 64 data - basically, change ServerAnswer to string as the returned information type:
httpGetDownloadZip(target: string[]): Observable<string> {
const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
const options = {
headers,
params,
};
return this.http
.get<string>(this.BASE_URL + '/files/downloadZip', options)
.pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
You could try using responseType as arraybuffer.
Eg:
return this.http.get(URL_API_REST + 'download?filename=' + filename, {
responseType: 'arraybuffer'
});
In My Project including both front end (angular) and back end (java).
We used the below solution ( hope it work for you ):
Angular:
https://github.com/eligrey/FileSaver.js
let observable = this.downSvc.download(opts);
this.handleData(observable, (data) => {
let content = data;
const blob = new Blob([content], { type: 'application/pdf' });
saveAs(blob, file);
});
Java:
public void download(HttpServletRequest request,HttpServletResponse response){
....
response.setHeader("Content-Disposition",
"attachment;filename=\"" + fileName + "\"");
try (
OutputStream os = response.getOutputStream();
InputStream is = new FileInputStream(file);) {
byte[] buf = new byte[1024];
int len = 0;
while ((len = is.read(buf)) > -1) {
os.write(buf, 0, len);
}
os.flush();
}
You can still use HttpServletRequest on the server...
Then get its OutputStream and write to it.
#RequestMapping(method = RequestMethod.POST , params="action=downloadDocument")
public String downloadDocument(#RequestParam(value="documentId", required=true) String documentId,
HttpServletRequest request,
HttpServletResponse response )
{
try {
String docName = null;
String documentSavePath = getDocumentSavePath();
PDocument doc = mainService.getDocumentById(iDocumentId);
if(doc==null){
throw new RuntimeException("document with id: " + documentId + " not found!");
}
docName = doc.getName();
String path = documentSavePath + ContextUtils.fileSeperator() + docName;
response.setHeader("Content-Disposition", "inline;filename=\"" + docName + "\"");
OutputStream out = response.getOutputStream();
response.setContentType("application/word");
FileInputStream stream = new FileInputStream(path);
IOUtils.copy(stream, out);
out.flush();
out.close();
} catch(FileNotFoundException fnfe){
logger.error("Error downloading document! - document not found!!!! " + fnfe.getMessage() , fnfe);
} catch (IOException e) {
logger.error("Error downloading document!!! " + e.getMessage(),e);
}
return null;
}

zxing - generate custom qrcode template

Ok, I thinnk it's more like "where I can find It" than a real question.
I'm using the zxing to build a QR Code engine for my system, it's work really cool, but I need make some customizations.
At https://www.qrcode-monkey.com/ I found a QR Code builder with some templates that can be read for any qr code reader.
The Colors can be changed with MatrixToImageConfig class, but I didn't found a way to change the shape.
Anyone knows a library that do this?!
This class is for change the qr code color.
public class QRCodeServiceImpl {
public static final int ON_COLOR = 0xFFFF9F37;
public static final int OFF_COLOR = 0xFF6638B6;
public byte[] buildQRCode(String data) throws WriterException, IOException {
Hashtable<EncodeHintType, Object> hintMap = new Hashtable<>();
hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
hintMap.put(EncodeHintType.MARGIN, 1);
hintMap.put(EncodeHintType.CHARACTER_SET, "UTF-8");
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(data, BarcodeFormat.QR_CODE, 512, 512, hintMap);
ByteArrayOutputStream pngOutputStream = new ByteArrayOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix, "PNG", pngOutputStream, getMatrixConfig());
byte[] fileData = pngOutputStream.toByteArray();
return fileData;
}
private MatrixToImageConfig getMatrixConfig() {
MatrixToImageConfig config = new MatrixToImageConfig(ON_COLOR, OFF_COLOR);
return config;
}
}

spring mvc display base64 as image

I am using CKEditor WYSWIG as my text editor on my site.
When User paste an image on the editor it is sent in post as <img src="data:image/png;base64,iVBORw0KGgoAAAANsd..." />
I would like to get this base64 string, save it in database and then create endpoint like /image/{id} which will show this image so in post I would not have to put whole base64 string in image source but just url like shown above.
this is how I save the base64 as byte[]:
#RequestMapping(value = {"/main/createpost"}, method = RequestMethod.POST)
public String postPost(Model model, Principal principal,#RequestParam(name="editor-content") String postPayload) throws IOException {
postPayload = checkAndSavePhotos(postPayload);
model.addAttribute("editor",postPayload);
return "createpost";
}
checkAndSavePhotos is checking if editor contains any images and if so it stores it in database:
private String checkAndSavePhotos(String postPayload) throws IOException {
int i =1;
Pattern pattern = Pattern.compile(".*<img src=\".*;base64,(.*?)\".*/>");
Matcher matcher = pattern.matcher(postPayload);
while (matcher.find()) {
PostPhoto postPhoto = new PostPhoto();
byte[] bytes = Base64.getDecoder().decode(matcher.group(i).getBytes());
MultipartFile mf =null;
try {
BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(bytes));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(originalImage, "png", baos);
baos.flush();
mf = new MockMultipartFile("test", baos.toByteArray());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
postPhoto.setContent(mf.getBytes());
postPhoto = postPhotoService.save(postPhoto);
}
return null;
}
I have made it this way because on my other form with <input type='file' /> when I was using FileBucket it was enough for me to show fileBucket.getFile().getBytes(); in order to show image. I was trying to create MultipartFile from byte[] and made it the same way.
My Endpoint to show image:
#RequestMapping(value = "/main/postphoto/{imageId}")
#ResponseBody
public byte[] getImage(#PathVariable Long imageId) throws IOException {
PostPhoto image = postPhotoService.findById(imageId);
return image.getContent();
}
Now when I am looking at database content column looks like:
\x89504e470d0a1a0a0000000d49484452000000280000002808060000008cfeb86d0000033f4944415478daed9(...)
while file from filebucket
\377\330\377\341\000\030Exif\000\000II*\000\010\000\000\000\000\000\000\000\000\000\000\000\377\354\000\021Ducky\000\001\000\004\000\000\000A\000\000\377\341\003ohttp://ns.adobe.com/xap/1.0/\000<?xpacket begin="\357\273\277" id="W5M0MpCehiHzreSzNTczkc9d"?> (...)
Can anyone give me a hint how to made it works?
It looks like it was a stupid mistake.
My database column content was type of text so I was storing byte[] as a text, so it's not wierd that the file was not decoded correctly by browser.
Changing database column type to bytea solved problem.

How to download CSV file from GUI from java spring controller on the go?

I am new in Software Development, just out of college. Doing my first big project.
I am trying to download the CSV file when the user chooses the start date and end date of the project, so the file is supposed to return the project.csv with project name, date from .. to ..
The most important thing is that I am trying to download the file from GUI after clicking on "export" link. All I know is I have to make a spring controller. I have to be missing some part as it's not working.
My java class is writing the csv file to my C disk but it doesn't do the download part. Also CSV file should be written from the database to users computer, not to my disk.
Hope you understand me. Let me know if that's clear.
my code:
ExportController.java
#RestController
#RequestMapping("/config")
public class ExportController {
private String filePath = "C:\\test\\project.csv";
private static final int BUFFER_SIZE = 4096;
#Autowired
private ExportService exportService;
ServletContext context;
#RequestMapping(value = "export/all", method = RequestMethod.POST)
public String list(#RequestParam("startDate")String date, #RequestParam("endDate")String date2, HttpServletResponse response, HttpServletRequest request) throws ServletException, ParseException, IOException {
DateFormat format = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
Date date_obj = format.parse(date);
Date date2_obj = format.parse(date2);
// get absolute path of the application
ServletContext context = request.getServletContext();
String appPath = context.getRealPath("");
System.out.println("appPath = " + appPath);
// construct the complete absolute path of the file
String fullPath = filePath;
File downloadFile = new File(fullPath);
FileInputStream inputStream = new FileInputStream(downloadFile);
// get MIME type of the file
String mimeType = context.getMimeType(fullPath);
if (mimeType == null) {
// set to binary type if MIME mapping not found
mimeType = "application/octet-stream";
}
System.out.println("MIME type: " + mimeType);
CsvWriterProject.savetofile();
String csv = "Employee FN/LN: Eatrick Hughes Contract type: External, Activity: WAR, Effort date: 2016-02-17, Name: udfuhfd, Entity: BA, Start date: 2016-02-17, End_date: 2016-02-18";
response.setContentType("application/csv");
response.setHeader("Content-Disposition", "attachment; filename=project.csv");
response.setHeader("Pragma", "public");
response.setHeader("Expires", "0");
response.setHeader("Content-Length", String.valueOf(csv.length()));
response.setHeader("Content-type","application/csv");
// response.setContentType(mimeType);
// response.setContentLength((int) downloadFile.length());
// get output stream of the response
OutputStream outStream = response.getOutputStream();
PrintWriter pw = new PrintWriter(outStream);
pw.print(pw);
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead = -1;
// write bytes read from the input stream into the output stream
while ((bytesRead = inputStream.read(buffer)) != -1) {
outStream.write(buffer, 0, bytesRead);
}
inputStream.close();
outStream.flush();
outStream.close();
return csv;
}
}
Here is angularJS
$scope.export_all = function(item){
$http.post('config/export/all?startDate='+item.startDate+"&endDate="+item.endDate)
.success(function(response) {
$scope.export = response;
});
};
Let me know if you need more information.
You can use HttpServletResponse (javax.servlet.http.HttpServletResponse)
Here is the sample code:
package com.export.test;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping(value = "/exportTest")
public class ExportControllerTest {
#RequestMapping(value = "/export", method = RequestMethod.GET)
public void exportStream(HttpServletResponse response) {
try {
String responseTosend = "Testing export for rest cliet";
response.getOutputStream()
.write((responseTosend).getBytes("UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
Modify as per your requirement.
Check with docs for more info https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html

How to retrieve images from server to user using Struts 2

I have a Product entity, which has a imageUrl String field.
Products images after obtaining from user will be saved in directory:
System.getProperty("user.home") + "shop/data/product/"
And when user wants to see some Product I need to get this image from "user.home"+... to JSP page.
I've tried to read the image into the byte array, convert it to Base64 encoding, and then refer in JSP like this:
<img alt="image from user home" src="data:image/png, base64;${requestScope.image}">
But this solution is not working, and as far as I understand, it has a limitation on image size.
Could you suggest me a way how to do such thing?
Try this ( i think you have some typo )
<img alt="image from user home" src="data:image/png;base64,${requestScope.image}">
Also use this site: http://www.askapache.com/online-tools/base64-image-converter/ to make sure that your output Base64 code is correct.
There's an example of ImageAction that serves image from the file system. It's called
Struts 2 dynamic image example.
Instead of base64 encoding/decoding which increases the content length two times and slows down page loading you can use the action that returnes image bytes from the file. It could be a database, in this way it should fetch bytes from Blob.
In your <img> tag that is using src attribute can contain the URL to the action that returns response with a header Content-Type: image/jpeg and bytes written to the body.
This is the code of the ImageAction:
#Result(type = "stream", params = {"contentType", "${type}"})
public class ImageAction extends ActionSupport implements ServletRequestAware {
byte[] imageInByte = null;
String imageId;
private HttpServletRequest servletRequest;
private final static String type = "image/jpeg";
public getInputStream() { return new ByteArrayInputStream(getCustomImageInBytes()); }
public String getType() { return type; }
private String getFilename() {
return this.filename;
}
public String getImageId() {
return imageId;
}
public void setImageId(String imageId) {
this.imageId = imageId;
}
public ImageAction() {
System.out.println("ImageAction");
}
public byte[] getCustomImageInBytes() {
System.out.println("imageId" + imageId);
BufferedImage originalImage;
try {
originalImage = ImageIO.read(getImageFile(this.imageId));
// convert BufferedImage to byte array
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(originalImage, "jpeg", baos);
baos.flush();
imageInByte = baos.toByteArray();
baos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return imageInByte;
}
private File getImageFile(String imageId) {
String filePath = servletRequest.getSession().getServletContext().getRealPath("/");
File file = new File(filePath + "/Image/", imageId);
System.out.println(file.toString());
return file;
}
#Override
public void setServletRequest(HttpServletRequest request) {
this.servletRequest = request;
}
}
This action supposed to have configuration created by convention-plugin. So it could be used in HTML like this
<img src="<s:url action='Image?imageId=darksouls.jpg' />" alt=""/>
So Alireza Fattahi was right that I had mistakes in my code. The first one is typo in img tag (see answer by Alireza Fattahi), the second one is after reading image to bytes array
byte[] image = ...;
I used
Base64.getEncoder().encode(image);
instead of
Base64.getEncoder().encodeToString(image));
So eventually this method with returning Base64 encoded image works. If there is a better choices - please left comments and answers.

Categories