Uploading multiple files from page controllers

I’m trying to upload multiple files from my module. Initially my controller signature looked like below

public String post(HttpServletRequest request, PageModel model, UiUtils uiUtils) {

}

Retrieving the files from the HttpServletRequest using request.getParts was not working. The methods HttpServletRequest.getPart and HttpServletRequest.getParts are not been resolved and the interface javax.servlet.http.Part is not in my class path. So I checked the api docs for servlets and it says the HttpServletRequest.getParts method was introduced since Servlet 3.0. My module uses openmrs 2.0.6 so i checked the 2.0.x branch on GitHub to see which version of the API is been used. As you can see below, it shows version 3.0.1 is been used

But I’m not sure why those methods and the javax.servlet.Part interface are not been resolved.

So in search of a possible solution on openmrs talk and wiki, I came across Flexible Method Signatures for UI Framework Controller and Action Methods

And under this section @RequestParam annotation, it says

You may put the @org.springframework.web.bind.annotation.RequestParam annotation (from Spring MVC) on any parameter and the UI Framework will take the specified parameter from the HTTP request, and use Spring’s ConversionService to convert it to the specified argument type.

And gives this example

public void controller(@RequestParam("patientId") Patient patient) { ... }

So I modified my controller signature to

public String post(HttpServletRequest request, PageModel model, UiUtils uiUtils, @RequestParam("files") MultipartFile[] files) {

}

For some reason, the files array is always containing a single element even when I upload multiple files. I tried changing the array to a List but I still get the same result.

I’ve run my code through a debugger and it shows that just one file is contained in the array. I’m not sure what is happening

My view looks something like this

<form class="simple-form-ui" id="patientRegistrationForm" method="post" enctype="multipart/form-data">
    // form elements
       
    <input type="file" name="files" multiple="true"/>

    <input type="submit" value="Enter form"/>
</form>

In Attachments we also implement a scenario where multiple files can be uploaded. However we have limited by design the upload widget on the UI side to take only one file at a time. For your reference here is the method signature of the upload controller.

As you can see here the various MultipartFile objects are gotten from the encompassing MultipartHttpServletRequest instance.

P.S. Btw is Attachments not of any use for your use case?


With another module depending on Core 2.x and in another context I have also come across a dependency issue once with javax.servlet. I was surprised that the one from Core was not being picked up. I “solved” it by forcing the dependency right away in my OMOD subproject’s POM. To make things work it had to be the first dependency in line, so even before openmrs-api. The omod/pom.xml looks like that:

<dependencies>
 <dependency>
  <groupId>${project.parent.groupId}</groupId>
  <artifactId>${project.parent.artifactId}-api</artifactId>
  <version>${project.parent.version}</version>
 </dependency>

 <dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.0.1</version>
  <scope>provided</scope>
 </dependency>

 <dependency>
  <groupId>org.openmrs.api</groupId>
  <artifactId>openmrs-api</artifactId>
  <version>${openMRSVersion}</version>
  <scope>provided</scope>
  <type>jar</type>
 </dependency>
...
1 Like

3 posts were merged into an existing topic: Visit Documents Module does not display images saved as complex obs from another module

I tried this option

When my request is made, MultipartHttpServletRequest request object is null.

So I changed the parameter back from MultipartHttpServletRequest to HttpServletRequest and in my code I’m doing a check to see if it’s an instance of MultipartHttpServletRequest and cast it to MultipartHttpServletRequest so I can be able to use the methods MultipartHttpServletRequest.getFileNames.

if (request instanceof MultipartHttpServletRequest) {
    MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
    savePatientFilesAslComplexObs(multipartRequest, encounter);
}

But I’m experiencing a strange behavior. From the debugger the iterator seems to have 4 elements(I uploaded 4 files) but when looping through using Iterator.hasNext and getting the next object with Iterator.next, the string returned seems to be “file” which is the parameter of the input element I used to upload the files instead of the file names. And this loop runs only once. Do you have an idea of what might be happening?

As you can see below, for the first run, uploadedFilename = “file”. And after the first run, the condition(fileNameIterator.hasNext) becomes false and the loop is exited. Looking at the created obs for this patient shows only one file created.

You mean when you try a similar pattern in your own code?

If yes then it depends also how the call is made on the client-side. We rely on DropzoneJS so it’s all being taken care of. Peaking at their code I can see that you would need the MIME type to be "multipart/form-data" (see here for instance, or here an entire fallback form that it ships with).


Did you have any chance to investigate (debug) the possible bug further?

I’m doing normal http form submit and the MIME type is indeed set to multipart/form-data

I’m doing that now. Will let you know what I find.

You could sniff the HTTP request made by DropzoneJS when uploading a file using Attachments’ file upload screen, and compare it to your HTTP request to spot any possible difference(s). When you have cleared that, and if things still don’t work, you would have to compare the controllers.

I was able to fix this. After trying the example you guys used in the visit module and my problems still persisted(only one file was been returned by request.getFilenames()), From the debugger, I evaluated a lot of expressions on the request object and realized that, the call to

request.getMultiFileMap() 

returns a MultiFileMap with one element whose key is “file” (the parameter used in the form submit). So I evaluated

request.getMultiFileMap().get("file")

And that returned a List of MultipartFile objects with each containing the name of my individual files. So my solution was,

List<MultipartFile> multipartFiles = request.getMultiFileMap().get("file");
for (MultipartFile multipartFile: multipartFiles) {
    InputStream in = multipartFile.getInputStream();
    ComplexData complexData = new ComplexData(multipartFile.getOriginalFilename(), in);
    Concept concept = RegistrationUtils.getComplexConceptForPatientFiles();
    Obs obs = new Obs(encounter.getPatient(), concept, encounter.getEncounterDatetime(), encounter.getLocation());
    obs.setComplexData(complexData);
    obs.setEncounter(encounter);
    Context.getObsService().saveObs(obs, "");
}

And now all my files are been saved successfully as complex obs.

2 Likes

So I have a similar scenario to what is described here…I want to save a complexObs sent from a form and I have been testing out different scenarios given here but still blocked.

  1. When I tried using MultipartHttpServletRequest I get a null object aswell
  1. So I went ahead to try with this however it appears this condition doesnt evaluate to true thus whatever is inside the if is skipped which might mean that what am sending doesnt contain the multipart enconding or files
  2. I added encType to both the form and Ajax call here and here but without much success either. Not sure of the possible configuration that am missing. @mksd @dkayiwa @ruhanga

FWIW I took a detour but still noteworthy that when sending multipart formdata you may need to do the following

  1. Instatiate a new form data

var formData = new FormData(jq(“#formId”)[0]);

rather than the usual serialization

var formData = jq(‘#formId’).serialize();

  1. The following options should be declared in the ajax call

processData: false,

contentType: ‘multipart/form-data’,

MultiValueMap<String, MultipartFile> files = ((MultipartHttpServletRequest) req).getMultiFileMap();

			for(Map.Entry<String, List<MultipartFile>> fileEntry  : files.entrySet()) {
				for (MultipartFile file : fileEntry.getValue()) {
					System.out.println("Key:  " +  fileEntry.getKey() + " - " + file.getOriginalFilename());
				}
			}