I am trying to store an image in postgresql db from my spring application but I am stuck with multiple problems and confusion.
First let me give you the overview of my spring application code:
var documentData = new FormData();
function update(){
var fname=document.getElementById("fname").value;
var lname=document.getElementById("lname").value;
var password=document.getElementById("password").value.trim();
var email=document.getElementById("email").value;
documentData.append('fname',fname);
documentData.append('lname',lname);
documentData.append('password',password);
documentData.append('email',email);
documentData.append('profilePic',$('#profilePic').attr('src'));
alert($('#profilePic').attr('src'));
$
.ajax({
type : 'PUT',
url : baseUrl + "/restApi/UpdateUser",
data : JSON
.stringify({
documentData
}),
success: function(){
location.reload(true);
},
error : function(e) {
},
dataType : "json",
contentType : "application/json"
});
}
}
$(function () {
$(":file").change(function () {
if (this.files && this.files[0]) {
var reader = new FileReader();
reader.onload = imageIsLoaded;
reader.readAsDataURL(this.files[0]);
}
});
});
function imageIsLoaded(e) {
$('#profilePic').attr('src', e.target.result);
$('#viewPic').attr('src',e.target.result);
};
I have this controller
#RequestMapping(value = "/restApi/UpdateUser", method = RequestMethod.PUT, headers = "Accept=application/json")
public ServiceResponse modifyUser(#RequestBody Object user)
{
return setDataPut("http://localhost:7020/UpdateUser",user,getUserObject().getUsername(),getUserObject().getPassword());
}
In my setDataPut method I am sending response with GSON
WebResource webResource = client
.resource(path);
ClientResponse response = webResource.type("application/json").accept("application/json")
.put(ClientResponse.class, gson.toJson(object));
In model class I took byte[] type variable and in db I made column with type bytea
Now In above gson service the call is made to rest services hosted.
#CrossOrigin
#RequestMapping(value = "/ModifyUser", method = RequestMethod.PUT, headers = "Accept=application/json")
public ServiceResponse modifyUser(#RequestBody User user) {
/*Code which deals with storing User data*/
}
So I have taken all data through model User class.
Now earlier it was working perfectly until I wanted to store image also.
Nothing is getting saved no error.
Confusion: If I am sending image with some data then should I change content type or add enctype as "multipart/form-data". But If I use multipart then what should be changed in headers. Like #produces #consumes. Major doubt is whether I need to convert the image in binary code before sending?
Problem: I am having trouble in storing image in postgresql through ajax request. Please look through my code what is the problem.
You are asking quite a lot in one question here. Essentially, you are asking how to upload files from the browser/client to the Spring-based server, how to handle that upload in the Spring-based server in order to save it into a Postgresql database and associate it with my User entity so that I can fetch it again later.
So, let's have a go at answering all of that for you.
Let's start on the client-side. This code will upload the chosen file to an existing resource:-
index.html
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
<script>
function upload() {
var data = new FormData();
data.append('file', jQuery('#file')[0].files[0]);
jQuery.ajax({
url: '/userImage/userId',
data: data,
cache: false,
contentType: false,
processData: false,
method: 'POST',
type: 'POST', // For jQuery < 1.9
success: function(data){
alert(data);
}
});
}
</script>
</head>
<body>
<div>
<h1>New File</h1>
<input type="file" id="file" name="file"/>
<button onclick="upload()">Upload</button>
</div>
</body>
</html>
Now, turning our attention to the Spring-bsed server side. To abstract away the implementation of exactly how to store the uploaded file in the database (and how to update it, and how to fetch it, and how to delete it and so on) I would use Spring Content otherwise you have a lot of code to write that Spring Content already implements for you.
So, add the following dependencies:
pom.xml
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-jpa</artifactId>
<version>0.1.0</version> // 0.0.11 for Spring Boot 1 dependencies
</dependency>
Configure the database schema creation in one of your config classes:
Config.java
#Configuration
#EnableJpaStores // enable JPA-based storage
public class PostgresqlTestConfig {
...dataSource and entityManager, etc beans...
#Value("/org/springframework/content/jpa/schema-drop-postgresql.sql")
private Resource dropReopsitoryTables;
#Value("/org/springframework/content/jpa/schema-postgresql.sql")
private Resource dataReopsitorySchema;
#Bean
DataSourceInitializer datasourceInitializer() {
ResourceDatabasePopulator databasePopulator =
new ResourceDatabasePopulator();
databasePopulator.addScript(dropReopsitoryTables);
databasePopulator.addScript(dataReopsitorySchema);
databasePopulator.setIgnoreFailedDrops(true);
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource());
initializer.setDatabasePopulator(databasePopulator);
return initializer;
}
}
Associate content with your User entity:
User.java
#Entity
public class User {
...existing fields...
#ContentId private String contentId;
private String mimeType;
}
Create a UserStore:
UserImageStore.java
public interface UserImageStore extends AssociativeStore<User, String> {
}
Update your controller to handle the upload of files, store them in the database and associating that stored image on your entity:
UserController.java
#Autowired
private UserImageStore store;
...
#RequestMapping(value="/userImage/{userId}", method = RequestMethod.POST)
public ResponseEntity<?> setContent(#PathVariable("userId") Long id, #RequestParam("file") MultipartFile file)
throws IOException {
User user = // fetch your existing user here
user.setMimeType(file.getContentType());
String originalFilename = file.getOriginalFilename();
InputStream is = file.getInputStream();
OutputStream os = ((WritableResource)store.getResource(originalFilename)).getOutputStream();
IOUtils.copyLarge(is, os);
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
// associate content (this will update the #ContentId field)
store.associate(user, originalFilename);
// save updated content-related info
save(user);
return new ResponseEntity<Object>(HttpStatus.OK);
}
return null;
#RequestMapping(value="/userImage/{userId}", method = RequestMethod.GET)
public ResponseEntity<?> getContent(#PathVariable("userId") Long id) {
User user = // fetch your existing user here
Resource r = store.getResource(user.getContentId());
HttpHeaders headers = new HttpHeaders();
headers.setContentLength(r.getContentLength());
headers.set("Content-Type", user.getMimeType());
return new ResponseEntity<Object>(r, headers, HttpStatus.OK);
}
return null;
}
That's about it. So what's going to happen here is that when your app starts it sees the dependency on spring-content-jpa and then it sees your UserImageStore. Assumes that you want to store images (BLOBs) in jpa and injects a JPA implementation of the UserImageStore interface meaning that you don't need to write it yourself. Spring Content hides the implementation but exposes a relatively simply interface (based on Spring Resource actually) that is #Autowired into your controller making that implementation simple.
Anyways, let me know if you are using Spring Data or Spring Boot and I can update this answer so that it is more relevant for you.
HTH
Related
I am calling a service in an orders controller which receives a multipart file and processes it and saving it into a database. I am trying to create a Spring Rest Doc for it but it is not even hitting the endpoint. I am creating a list of orders which is what the service expects. It receives the order as a stream as shown and converts into a stream of orders before saving it into a database. I have shown the main part of the controller and my code for generating the rest docs. When I run the code I get the following exception, it never even hits the endpoint when I set a breakpoint. I also used fileupload() but that did not work either.
Exception is:
Content type = application/json
Body = {"path":"/orders/order_reception","exceptionName":
"MissingServletRequestPartException","message":"Required request part 'uploadFile' is not
present",
"rootExceptionName":"MissingServletRequestPartException",
"rootMessage":"MissingServletRequestPartException: Required request part 'uploadFile' is not present"}
#RestController
#RequestMapping(value = "/orders")
#Validated
class OrderController{
#PostMapping(path = "/order_reception")
public ResponseEntity receiveData(#RequestPart MultipartFile uploadFile,
HttpServletRequest request,
HttpServletResponse response) {
if (!uploadFile.isEmpty()) {
try {
Reader reader = new InputStreamReader(request.getInputStream()));
... save file
return new ResponseEntity<>(HttpStatus.HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
#Test
public void sendData() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Order order = repository.getOrder("1233333");
List<Order> orderList = new ArrayList<>():
resourceList.add(order);
MockMultipartFile orderFile = new MockMultipartFile("order-data", "order.json", "application/json",
mapper.writeValueAsString(orderList).getBytes(Charset.defaultCharset()));
mockMvc.perform(multipart("/orders/order_reception")
.file(orderFile))
.andExpect(status().isCreated())
.andDo(document("send-order",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint())));
}
Thank you Marten Deinum, your suggestion that the file name was wrong fixed it.
I simply changed name in the MockMultipartFile( "uploadsFile", ...)
I'm trying to send a Product and product images from Angular 7 frontend to a SpringMVC backend.
To add support for Multipart files I've added this bean inside my AppConfig.
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(1000000);
return multipartResolver;
}
Since I want to receive the Product object separately inside the controller I'm using #RequestPart to fetch both separately like this:
#RequestMapping(value = "save", method = RequestMethod.POST)
public ResponseEntity addProduct(#Valid #RequestPart Product product, #RequestPart MultipartFile[] images, BindingResult bindingResult, HttpServletRequest
}
On the frontend I'm adding the image to FormData like this:
let formData = new FormData();
formData.append('product', new Blob([JSON.stringify(this.product)],{ type: "application/json" }));
// I iterate and append all the images like this
formData.append('image[]', this.images, this.images.name);
this.http.post(this.appService.getApiUrl() + "api/product/save/", product);
The problem is that whenever I submit the form, I get this exception as a response: HTTP Status 415 – Unsupported Media Type.
I tried debugging this issue by setting breakpoints inside CommonsMultipartResolver class and after tracing the request through the code I've found that when the getSupportedMediaTypes() is called it returns only two media types:
application/json
application/*+json
Inside the following method in AbstractHttpMessageConverter:
protected boolean canRead(#Nullable MediaType mediaType) {
if (mediaType == null) {
return true;
} else {
Iterator var2 = this.getSupportedMediaTypes().iterator();
MediaType supportedMediaType;
do {
if (!var2.hasNext()) {
return false;
}
supportedMediaType = (MediaType)var2.next();
} while(!supportedMediaType.includes(mediaType));
return true;
}
}
Finding this I tried adding MediaType.MULTIPART_FORM_DATA like this inside AppConfig:
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
List<MediaType> types = new ArrayList<>();
types.add(MediaType.APPLICATION_JSON);
types.add(MediaType.APPLICATION_JSON_UTF8);
types.add(MediaType.MULTIPART_FORM_DATA);
((MappingJackson2HttpMessageConverter) converter).setSupportedMediaTypes(types);
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(hibernate5Module);
}
}
}
But it still wouldn't work. When the app starts up, I do see the constructor of AbstractJackson2HttpMessageConverter being called with my MediaTypes but they get overwritten by more calls to the same constructor after it.
Is there any way I can get the MediaType to persist? I might be looking in the wrong direction so any insight will be helpful.
The Jackson library is required on the classpath. Spring does not declare this by default. Make sure that at least com.fasterxml.jackson.core:jackson-databind is available in the classpath of the Spring MVC application. Example for Apache Maven:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
Example for the jackson.version value: 2.9.0
1) You need to give input data supported at server end. Since you are sending File, means server is consuming the Multipart Data.
For multipart we need to set consumes = "multipart/form-data"
#RequestMapping(value = "save", method = RequestMethod.POST, consumes = "multipart/form-data")
public ResponseEntity addProduct(#Valid #RequestPart Product product, #RequestPart MultipartFile[] images, BindingResult bindingResult, HttpServletRequest
}
2) Since form is sending multipart data we need to set content-type at front end too in http header in post call.
content-type: multipart/form-data"
i am using freemaker with spring boot.
every thing is working fine expect one.
when i am sending request and returning to user
it is showing that resource not found.
but when i am restarting my server
and refresh my browser it is working fine.
here is my rest controller method
#GetMapping("/activation")
public ModelAndView activateUser(#RequestParam(value="token") String token) {
LOGGER.info("inside #class UserController #method activation entry..");
Map<String, Object> model = new HashMap<String, Object>();
String msg = userService.activateUser(token);
if(msg != null) {
model.put("error", true);
model.put("message", msg);
}else {
model.put("error", false);
model.put("message", UtilMessages.USER_ACTIVATION_SUCCESS);
}
return new ModelAndView("user-activation-response",model);
}
also i am using #RestController but it is also behaving same with #Controller.
and my application property configuration as.
spring.freemarker.template-loader-path: classpath:/templates
spring.freemarker.suffix: .ftl
also when i am sending mail it is working fine with java mail.
I am stuck in getting the request at my REST Controller.
Below is my Controller method :
#RestController
#CrossOrigin(origins = "http://localhost:4200", allowedHeaders = "*")
public class UserController {
#Autowired
public UserService userService;
#PostMapping(value = "/fileUploadApi", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public #ResponseBody StatusUpload fileUpload(#RequestPart("uploadfile") MultipartFile file, #RequestPart("user") User userDto) throws Exception{
StatusUpload status= new StatusUpload();
status = userService.callUserServiceForFileRead(file, userDto);
return status;
}
In ApplicationConfig.java, I added the following :
#Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(500000000);
return multipartResolver;
}
Below is the code I have written for file upload at Angular5 end:
HTML:
<!-- Other form elements -->
<!-- Used PrimeNG Custom Upload Handler to get the File Object https://www.primefaces.org/primeng/#/fileupload -->
<label>Upload template </label>
<p-fileUpload (uploadHandler)="getFile($event)" auto="true" customUpload="true" accept=".xlsx,application/msexcel" previewWidth="0" [showUploadButton]="false" [showCancelButton]="false">
</p-fileUpload>
<!-- Form Submit Button -->
<button type="button" (click)="submit()">SUBMIT</button>
COMPONENT :
user: User = new User;
// Set other form element data in user object.
uploadFile: File;
getFile(event) {
this.uploadfile= event.files[0];
}
submit() {
this.userService.saveUserData(this.user,this.uploadFile);
}
SERVICE :
options = {
headers: new HttpHeaders({ 'Content-Type': '' })
};
constructor(private http: HttpClient) { }
saveUserData(user:User, uploadFile:File){
var formData: FormData = new FormData();
formData.append('user',JSON.stringify(user));
formData.append('uploadfile',uploadFile);
return this.http.post<StatusUpload>(baseUrl + "/fileUploadApi", formData, this.options);
}
When I use the above Service, it gives me no response/no Exception don't know why. The request even didn't reach my RESTController.
I read a different approach in some post & used it as below :
saveUserData(user: User, uploadFile: File) {
var formData: FormData = new FormData();
Observable.fromPromise(new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
formData.append('user', new Blob([JSON.stringify(user)],
{
type: "application/json"
}));
formData.append('uploadfile', uploadFile);
xhr.open("POST", baseUrl + "/fileUploadApi", true);
xhr.send(formData);
}));
}
With above, I get org.springframework.web.multipart.support.MissingServletRequestPartException.
Can anyone please help to get this code working. I need to use the http.post() in my Angular service, not the XMLHttpRequest.send() one.
You need to subscribe to the observable.
An HttpClient method does not begin its HTTP request until you call subscribe() on the observable returned by that method. This is true for all HttpClient methods.
Docs
I am trying to send a csv file to my java spring boot backend. The code to send my file is below:
var url = 'http://localhost:3001/UploadFile';
var file = this.state.file;
var formData = new FormData();
formData.append("file", file);
axios.post(url, formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
And the code to accept my file from Spring Boot:
#CrossOrigin
#RequestMapping("/UploadFile")
#ResponseBody
public void uploadFile(#RequestParam("file") MultipartFile file) {
}
However, it doesn't seem to work. I keep getting an error saying that the 'Current request is not a multipart request'. Any ideas?
It's not sufficient to specify content-type in frontend you need to do it in controller as well.
You should tell to spring controller what it should consume and also it would be nice to set RequestMethod as POST like this:
#CrossOrigin
#RequestMapping("/UploadFile")
#ResponseBody
public void uploadFile(#RequestParam("file") MultipartFile file, method = RequestMethod.POST, consumes = "multipart/form-data") {
}