How to create CommonsMultipartFile object given only a file - spring

I have a junit test method that takes a CommonsMultipartFile object as a parameter.
I'm trying to create a FileItem object so I can pass it to the constructor,
CommonsMultipartFile(org.apache.commons.fileupload.FileItem fileItem)
To do that, I'm trying to create the FileItem object using the DiskFileItem constructor,
DiskFileItem(java.lang.String fieldName, java.lang.String contentType, boolean isFormField, java.lang.String fileName, int sizeThreshold, java.io.File repository)
but I'm not sure how to pass any of those parameters.
I have all of this working in a Spring 3 MVC controller, but to do my junit tests, I need to pass a method two objects. One is the UploadItem object which looks like the following,
import org.springframework.web.multipart.commons.CommonsMultipartFile;
public class UploadItem {
private String fileName;
private String filePath;
private CommonsMultipartFile fileData;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public CommonsMultipartFile getFileData() {
return fileData;
}
public void setFileData(CommonsMultipartFile fileData) {
this.fileData = fileData;
}
}
The setFileData() method requires the CommonsMultipartFile object which I'm trying to create just given a file in my src/test/resources directory.
Would anyone know how I can take a file, create a FileItem object and pass that to the CommonsMultipartFile object constructor?
Thanks. If anything is unclear, please let me know - I'm not that familiar with Spring MVC file uploads.

Use the more common interface org.springframework.web.multipart.MultipartFile. instead of
org.springframework.web.multipart.commons.CommonsMultipartFile in your Command (UploadItem). (CommonsMultipartFile is a 1:1 implementation of the Interface).
Now you can create an instance of CommonsMultipartFile with the mock class org.springframework.mock.web.MockMultipartFile. (which is element of spring-test.jar).
Then the creation of an MultipartFile in the tests is only one statement, without any cast:
MockMultipartFile mockMultipartFile = new MockMultipartFile(
"test.txt", //filename
"Hallo World".getBytes()); //content

How is this of any help? you don't set the file on the request, it should be used like this:
MultipartFile multipartFile = getMockCommonsMultipartFile(BULK_CSV);
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
request.addFile(multipartFile);
CommonsMultipartFile commonsMultipartFile = (CommonsMultipartFile) request.getFile(BULK_CSV);
I am using a method with the argument CommonsMultipartFile, otherwise I could have used MockMultipartFile directly.
private MultipartFile getMockCommonsMultipartFile(String name, String path) throws IOException {
InputStream is = getClass().getResourceAsStream(path);
MultipartFile multipartFile = new MockMultipartFile(name, name, "", is);
return multipartFile;
}

Related

Spring Boot marshall Xml from RestTemplate without RootElement

I am using a RestTemplate like this:
return this.getForEntity(baseUrl, BasicResponse.class, parameters);
This is the BasicResponse class:
public class BasicResponse {
private String status;
private String statusMsg;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getStatusMsg() {
return statusMsg;
}
public void setStatusMsg(String statusMsg) {
this.statusMsg = statusMsg;
}
}
No exceptions are thrown but the fields in the returned ResponseEntity body are 'null'. I think it's because the element does not have a valid XML structure (as in no root element). I do not have control over the parsed XML. How can I map my object?
Since the XML is not valid,
I believe that you will not be able to use RestTemplate.getForEntity
to get a BasicResponse object.
Try this:
private static final String VALUE_END_TAG = "</blammy>";
private static final String VALUE_START_TAG = "<blammy>";
private XmlMapper xmlMapper; // initialize this correctly, somewhere off page.
method stuff
{
final String actualResponse;
final StringBuilder correctedResponse = new StringBuilder();
final BasicResponse returnValue;
actualResponse = restTemplate.getForEntity(baseUrl, BasicResponse.class, parameters);
correctedResponse.append(VALUE_START_TAG);
correctedResponse.append(actualResponse);
correctedResponse.append(VALUE_END_TAG);
returnValue = xmlMapper.readValue(correctedResponse.toString(), BasicResponse.class);
return returnValue;
}
Use some reasonable value as the element name in the start and end tags,
perhaps "" and "".
Consider using some Jackson annotations,
for example #JacksonXmlRootElement(localName = "blammy")
(this local name matches my example).

Spring-boot MultipartFile issue with ByteArrayResource

I'm trying to implement a rest api consuming excel file. I'm using spring-boot and code is available here.
Code works fine when using FileSystemResource for payload. But i'm not able to make the code work with ByteArrayResource in replacement of FileSystemResource:
RestApi.java:
#RestController
public class RestApi {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
#PostMapping("/api/upload")
public ResponseEntity<?> uploadFile(#RequestParam("file") MultipartFile uploadfile) {
LOGGER.debug("Single file upload!");
try {
LOGGER.info("\n\n ****** File name: {}, type {}! ************", uploadfile.getOriginalFilename(), uploadfile.getContentType());
this.processExcelFile(uploadfile.getInputStream());
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>("Successfully uploaded - " + uploadfile.getOriginalFilename(), new HttpHeaders(), HttpStatus.OK);
}
private List<String> processExcelFile(InputStream stream) throws Exception {
List<String> result = new ArrayList<String>();
//Create Workbook instance holding reference to .xlsx file
try(XSSFWorkbook workbook = new XSSFWorkbook(stream);) {
//Get first/desired sheet from the workbook
XSSFSheet sheet = workbook.getSheetAt(0);
//Iterate through each rows one by one
Iterator<Row> rowIterator = sheet.iterator();
while (rowIterator.hasNext()) {
Row row = rowIterator.next();
String cellValue = row.getCell(0).getRichStringCellValue().toString();
result.add(cellValue);
LOGGER.info("\n\n ****** Cell value: {} ************", cellValue);
}
return result;
}
}
}
RestApiTest:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class RestApiTest {
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private ResourceLoader loader;
#Test
public void testUploadFile() throws Exception {
Resource resource = this.loader.getResource("classpath:test.xlsx");
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
// parts.add("file", new FileSystemResource(resource.getFile()));
parts.add("file", new ByteArrayResource(IOUtils.toByteArray(resource.getInputStream())));
String response = this.restTemplate.postForObject("/api/upload", parts, String.class);
Assertions.assertThat(response).containsIgnoringCase("success");
}
}
I'm getting following error when running test:
java.lang.AssertionError:
Expecting:
<"{"timestamp":1487852597527,"status":400,"error":"Bad Request","exception":"org.springframework.web.multipart.support.MissingServletRequestPartException","message":"Required request part 'file' is not present","path":"/api/upload"}">
to contain:
<"success">
(ignoring case)
Any idea?
when using loader.getResource(...) you must use resource itself as answered above. So you don't need ByteArrayResource. I got this problem, but I'm not using resource from classpath. So if someone really need to use ByteArrayResource, here is my workaround
public class FileNameAwareByteArrayResource extends ByteArrayResource {
private String fileName;
public FileNameAwareByteArrayResource(String fileName, byte[] byteArray, String description) {
super(byteArray, description);
this.fileName = fileName;
}
#Override
public String getFilename() {
return fileName;
}
}
and then use it
parts.add("file", new FileNameAwareByteArrayResource("filename", byteArray));

test method with MultipartFile as a parameter

I've got this method
#RequestMapping(value = "/upload", method = RequestMethod.POST, produces = "text/plain")
#ResponseBody
public String uploadFile(#RequestParam("file") MultipartFile file) {
LOGGER.debug("Attempt to upload file with template.");
try {
return FileProcessUtils.processFileUploading(file);
} catch (UtilityException e) {
LOGGER.error("Failed to process file.", e.getWrappedException());
return null;
}
}
I want to test it using junit and powermock.
#Test
public void testUploadFile() {
MultipartFile file = buildMultipartFile();
mockStatic(FileProcessUtils.class);
FileProcessUtils.processFileUploading(file);
expectLastCall().andReturn(FILE_CONTENT);
replay(FileProcessUtils.class);
String fileContent = templateSupportRest.uploadFile(file);
verify(FileProcessUtils.class);
assertEquals(FILE_CONTENT, fileContent);
}
But the problem is that I need somehow to pass as MultipartFile object with specific FILE_CONTENT.
To do that I need to build instance of MultipartFile.
private MultipartFile buildMultipartFile() {
DiskFileItem diskFileItem = new DiskFileItem("file", "text/plain", true, "file", 100, null);
MultipartFile file = new CommonsMultipartFile(diskFileItem);
return file;
}
However I've got problems with DiskFileItem object. When I create it like this I get NullPointerException in getSize() method of DiskFileItem object.
The constructor of DiskFileItem itself has this parameters
String fieldName,
String contentType,
boolean isFormField,
String fileName,
int sizeThresold,
File repository
So the question is - how to instantiate DiskFileItem or is there another way to handle the situation?

Spring rest Json issue

I found below answered question
Different names of JSON property during serialization and deserialization
Unfortunately this does not work when we use Spring Restful webservice. I am not sure what is cauisng the issue but it gives some Field abiguity exception.
What I want to do is Serialize and deserialize a field name with different names.
For e.g.
class Test {
private String name;
#JsonProperty("myName")
public String getName() {
return name;
}
#JsonProperty("yourName")
public void setName(String name) {
this.name = name;
}
}
This does not work in Spring rest
You can not set #JsonProperty for both (getter & setter). You can set for the field or setter method.
But you want different name for request and response, Create two classes like this.
class StudentResponse{
#JsonProperty(name="student_name)
private String name;
//getter & setter
}
class StudentRequest{
#JsonProperty(name="name)
private String name;
//getter & setter
}
Damith is right, you seem to not be able to mark both methods within the same class, however there is a way to solve this:
First off, you will have to Create a custom deserializer (or serializer, depends on your preference).
My example object:
#JsonDeserialize(using = ObjectDeserializer.class)
public class MyObject {
private String name;
public void setName(String name) {
this.name = name;
}
#JsonProperty("SomeOtherName")
public String getName() {
return name;
}
}
Note, i mark the getter as the property with the first name. And I give the class a custom deserializer. Which looks like that:
public class ObjectDeserializer extends JsonDeserializer<MyObject> {
#Override
public MyObject deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
MyObject object = new MyObject();
JsonNode node = jp.getCodec().readTree(jp);
JsonNode jsonNode = node.get("MyCustomSerializeName");
object.setName(jsonNode.getTextValue());
return object;
}
}
This class will create my custom object and get the name of the setter field description (rather than relying on the property name).
Put together, i get:
public class DeserializeTest {
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
MyObject o = new MyObject();
o.setName("Hello");
String writeValueAsString = mapper.writeValueAsString(o);
System.out.println(writeValueAsString);
String jsonObj = "{\"MyCustomSerializeName\":\"Other Test\"}";
MyObject readValue = mapper.readValue(jsonObj, MyObject.class);
System.out.println(readValue.getName());
}
}
And this outputs:
{"SomeOtherName":"Hello"}
Other Test
I hope that helps you.

Spring 3 Custom Editor field replacement

Having my ValueObject
UserVO {
long id;
String username;
}
I created custom editor for parsing this object from string id#username
public class UserVOEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
Preconditions.checkArgument(text != null,"Null argument supplied when parsing UserVO");
String[] txtArray = text.split("\\#");
Preconditions.checkArgument(txtArray.length == 2, "Error parsing UserVO. Expected: id#username");
long parsedId = Long.valueOf(txtArray[0]);
String username = txtArray[1];
UserVO uvo = new UserVO();
uvo.setUsername(username);
uvo.setId(parsedId);
this.setValue(uvo);
}
#Override
public String getAsText() {
UserVO uvo = (UserVO) getValue();
return uvo.getId()+'#'+uvo.getUsername();
}
in my controller i register
#InitBinder
public void initBinder(ServletRequestDataBinder binder) {
binder.registerCustomEditor(UserVO.class, new UserVOEditor());
}
having in my model object ModelVO
ModelVO {
Set<UserVO> users = new HashSet<UserVO>();
}
after custom editor is invoked all you can see after form submission is
ModelVO {
Set<String> users (linkedHashSet)
}
so when trying to iterate
for(UserVO uvo : myModel.getUser()){ .. }
Im having classCastException .. cannot cast 1234#username (String) to UserVO ..
HOW THIS MAGIC IS POSSIBLE ?
It is not magic, it is because of Generics will be only proved at compile time. So you can put every thing in a Set at runtime, no one will check if you put the correct type in the Set.
What you can try, to make spring a bit more clever, is to put the ModelVO in your command object.
<form:form action="whatEver" method="GET" modelAttribute="modelVO">
#RequestMapping(method = RequestMethod.GET)
public ModelAndView whatEver(#Valid ModelVO modelVO){
...
}

Resources