I’m trying to upload multipart form data from a web client to a Spring MVC Controller. I search a “Spring” implementation for following curl command:
curl -v -X POST -F "logo=@walmart.jpg" -F "id=12345" -F "name=Walmart" http://localhost:8080/api/cardprovider/logo
Please note: I’m not doing a form upload over a html form and this is not what I want to achieve.
Now my question is:
- Would you use Apache Commons HttpClient for this as explained in this
blog? - Or is there a “Spring” kind of Magic library that will
help me do this?
I have spent days searching for a solution using Spring libraries but all examples in the Spring manual refer to html form uploads or are very basic and simple with only basic text.
Spring is fairly new to me but there are so many great libraries inside that I would be surprised if there wouldn’t be something the guys from Spring would have thought of to make it more easy to upload multipart data (looking as if it would be sent from a form).
The above curl command is working successfully with following server side code:
Controller:
@Controller
@RequestMapping("/api/cardprovider/logo")
public class CardproviderLogoResourceController {
@Resource(name = "cardproviderLogoService")
private CardproviderLogoService cardproviderLogoService;
/**
* Used to upload a binary logo of a Cardprovider through a Multipart request. The id is provided as form element.
* <p/>
* Example to upload a file using curl:
* curl -v -X POST -F "image=@star.jpg" -F "id=12345" -F "name=Walmart" http://localhost:8080/api/cardprovider/logo
* <p/>
*
* @param logo the Multipart request
* @param id the Cardprovider id
* @param name the Cardprovider name
*/
@RequestMapping(value = "", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void storeCardproviderLogo(@RequestParam(value = "logo", required = false) MultipartFile logo,
@RequestParam(value = "name") String name,
@RequestParam(value = "id") String id) throws IOException {
cardproviderLogoService.storeLogo(logo, id, name);
}
}
And here is my Service Class that stores the Multipart request in to a GridFS database:
Service:
@Service
public class CardproviderLogoService {
@Autowired
GridFsOperations gridOperation;
/**
* Create the logo in MongoDB GridFS with the data returned from the Multipart
* <p/>
*
* @param logo the MultipartFile content
* @param id the Cardprovider id
* @param name the Cardprovider name
* @return true if the image can be saved in the database, else false
*/
public Boolean storeLogo(MultipartFile logo, String id, String name) {
Boolean save_state = false;
BasicDBObject metadata = new BasicDBObject();
metadata.put("cardproviderId", id);
metadata.put("cardproviderName", name);
metadata.put("contentType", logo.getContentType());
metadata.put("fileName", createLogoFilename(name, logo.getOriginalFilename()));
metadata.put("originalFilename", logo.getOriginalFilename());
metadata.put("dirShortcut", "cardproviderLogo");
metadata.put("filePath", "/resources/images/cardproviders/logos/");
try {
gridOperation.store(logo.getInputStream(),
metadata.getString("fileName").toLowerCase().replace(" ", "-"),
metadata);
save_state = true;
} catch (Exception ex) {
Logger.getLogger(CardproviderLogoService.class.getName())
.log(Level.SEVERE, "Storage of Logo failed!", ex);
}
return save_state;
}
/**
* Creates the new filename before storing the file in MongoDB GridFS. The filename is created by taking the
* name of the Cardprovider, lowercase the name and replace the whitespaces with dashes. At last the file
* extension is added that was provided by the original filename.
* <p/>
*
* @param name the Cardprovider name
* @param originalFilename the original filename
* @return the new filename as String
*/
private String createLogoFilename(String name, String originalFilename) {
String cpName = name.toLowerCase().replace(" ", "-");
String extension = "";
int i = originalFilename.lastIndexOf('.');
if (i > 0 && i < originalFilename.length() - 1) {
extension = originalFilename.substring(i + 1).toLowerCase();
}
return cpName + "." + extension;
}
}
Thanks for your help and kind regards,
Chris
Solution
I could solve the problem the following way. I have my HtmlController that takes the form Input and builds up a connection to the RestController to transfer the files.
Here is the method of the HtmlController:
@RequestMapping(value = "/add", method = RequestMethod.POST)
public String addCardprovider(
@ModelAttribute("cardproviderAttribute") Cardprovider cardprovider,
Model model) {
// Prepare acceptable media type
List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
acceptableMediaTypes.add(MediaType.APPLICATION_XML);
// Prepare header
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptableMediaTypes);
// Pass the new person and header
HttpEntity<Cardprovider> entity = new HttpEntity<Cardprovider>(
cardprovider, headers);
// Send the request as POST
try {
// Save Cardprovider details
ResponseEntity<Cardprovider> response = restTemplate.exchange(
"http://localhost:8080/api/cardprovider", HttpMethod.POST,
entity, Cardprovider.class);
// Save Cardprovider logo
String tempDir = System.getProperty("java.io.tmpdir");
File file = new File(tempDir + "/" + cardprovider.getLogo().getOriginalFilename());
cardprovider.getLogo().transferTo(file);
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add("id", response.getBody().getId());
parts.add("name", response.getBody().getName());
parts.add("logo", new FileSystemResource(file));
restTemplate.postForLocation("http://localhost:8080/api/cardprovider/logo", parts);
} catch (Exception e) {
e.printStackTrace();
}
// This will redirect to /web/cardprovider/getall
return "redirect:/web/cardprovider/getall";
}
Nothing in Spring I know of. Using Apache Commons HttpClient for such tasks is common practice in my circles.
Update
Of course Spring provides support for that – sorry, I overlooked the most obvious: RestTemplate!
XML:
Java: