Wednesday, 11 September 2013

Spring MVC loses my session state when allowing user to upload 2 files in different steps on the form

Spring MVC loses my session state when allowing user to upload 2 files in
different steps on the form

I have a form that allows a user to enter a couple of fields and upload
two separate files. As each file is uploaded, a request is sent to the
server to retrieve the file and build the display. Once the user is
satisfied that the display looks good, they can commit the new data to the
database.
The problem I am having I that Spring-MVC seems to lose the session object
on the last call to the server. I need it to retain the session until I
specifically tell it to drop the objects using the SessionStatus.
Here is the code for my controller class:
package com.mbn.modeldisplay.controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.multipart.MultipartFile;
import com.mbn.modeldisplay.pojo.CandidateModel;
import com.mbn.modeldisplay.pojo.Model;
import com.mbn.modeldisplay.service.IModelService;
import com.mbn.modeldisplay.service.ServiceException;
import com.mbn.modeldisplay.util.TempFileManager;
@SessionAttributes(value = { "candidateModel" })
@Controller
@RequestMapping("/model/*")
public class ModelHandler {
protected static Logger log =
LoggerFactory.getLogger(ModelHandler.class);
protected IModelService modelService = null;
private static final String DEFAULT_TEMPLATE_TEXTURE =
"/assets/defaultimage.png";
private static final String CANDIDATE_MODEL_NAME = "candidateModel";
@ModelAttribute(CANDIDATE_MODEL_NAME)
public CandidateModel createCandidate() {
CandidateModel candidate = new CandidateModel();
log.debug("createCandidate returning: " + candidate);
log.debug("\n\n");
return candidate;
}
@RequestMapping(method = RequestMethod.GET)
public String startEditModel(
@RequestParam(value = "modelId", required = false) String
modelId,
@ModelAttribute CandidateModel candidate) {
try {
Model m = modelService.read(modelId);
if (null == m) {
m = new Model();
m.modelId = modelId;
}
candidate.setOriginal(m);
log.debug("startEditModel returning: " + candidate);
} catch (ServiceException se) {
log.error("Unable to access datastore", se);
}
log.debug("\n\n");
return "ModelEdit";
}
@RequestMapping(value = "register/binary", method = RequestMethod.POST)
public String addModel(@RequestParam MultipartFile model,
@ModelAttribute(CANDIDATE_MODEL_NAME) CandidateModel
tempCandidate) {
log.debug("addModel received: " + tempCandidate);
try {
File tempFile = tempCandidate.getCandidateModelFile();
if (null != tempFile) {
log.debug("Remove previous candidate: "
+ tempFile.getAbsolutePath());
tempFile.delete();
}
tempFile = TempFileManager.getTempFile(
"norrim01",
"ModelDisplay",
model.getOriginalFilename().substring(
model.getOriginalFilename().lastIndexOf('.')));
log.debug("Writing to temp file: " + tempFile.getAbsolutePath());
InputStream is = model.getInputStream();
FileOutputStream fw = new FileOutputStream(tempFile);
byte[] buffer = new byte[1024];
int read;
while (-1 != (read = is.read(buffer))) {
fw.write(buffer, 0, read);
}
fw.flush();
fw.close();
tempCandidate.setCandidateModelFile(tempFile);
} catch (Exception e) {
log.error("Unable to process model file candidate.", e);
return "ErrorPage";
}
log.debug("\n\n");
return "ModelEdit";
}
@RequestMapping(value = "register/binary", method = RequestMethod.GET)
public void getCandidateModel(HttpServletResponse response,
@ModelAttribute(CANDIDATE_MODEL_NAME) CandidateModel
tempCandidate)
throws Exception {
log.debug("getCandidateModel received: " + tempCandidate);
File tempFile = tempCandidate.getCandidateModelFile();
if (null == tempFile || !tempFile.exists()) {
throw new FileNotFoundException("Candidate Model File");
}
log.debug("register/binary returning " + tempFile.getAbsolutePath());
response.setContentType("application/octet-stream");
response.setHeader(
"Content-Disposition",
"attachment; filename=candidatemodel"
+ tempFile.getName().substring(
tempFile.getName().lastIndexOf('.')));
InputStream is = new FileInputStream(tempFile);
OutputStream fw = response.getOutputStream();
byte[] buffer = new byte[1024];
int read;
while (-1 != (read = is.read(buffer))) {
fw.write(buffer, 0, read);
}
log.debug("\n\n");
fw.flush();
}
@RequestMapping(value = "register/template", method = RequestMethod.POST)
public String addTemplate(@RequestParam MultipartFile template,
@ModelAttribute(CANDIDATE_MODEL_NAME) CandidateModel
tempCandidate)
throws Exception {
log.debug("addTemplate received: " + tempCandidate);
try {
File tempFile = tempCandidate.getCandidateTemplateFile();
if (null != tempFile) {
log.debug("Remove previous candidate: "
+ tempFile.getAbsolutePath());
tempFile.delete();
}
tempFile = TempFileManager.getTempFile(
"norrim01",
"ModelDisplay",
template.getOriginalFilename().substring(
template.getOriginalFilename().lastIndexOf('.')));
log.debug("Writing to temp file: " + tempFile.getAbsolutePath());
InputStream is = template.getInputStream();
FileOutputStream fw = new FileOutputStream(tempFile);
byte[] buffer = new byte[1024];
int read;
while (-1 != (read = is.read(buffer))) {
fw.write(buffer, 0, read);
}
fw.flush();
fw.close();
tempCandidate.setCandidateTemplateFile(tempFile);
} catch (Exception e) {
log.error("Unable to process model file candidate.", e);
return "ErrorPage";
}
log.debug("\n\n");
return "ModelEdit";
}
@RequestMapping(value = "register/template", method = RequestMethod.GET)
public void getCandidateTemplate(
@ModelAttribute(CANDIDATE_MODEL_NAME) CandidateModel
tempCandidate,
HttpSession session, HttpServletResponse response) throws
Exception {
log.debug("getCandidateTemplate received: " + tempCandidate);
File tempFile = tempCandidate.getCandidateTemplateFile();
log.debug("register/template found in session: " + tempFile);
if (null == tempFile || !tempFile.exists()) {
String location = session.getServletContext().getRealPath(
DEFAULT_TEMPLATE_TEXTURE);
if (null != location)
tempFile = new File(location);
else
throw new FileNotFoundException(
"Unable to locate the default template texture");
}
log.debug("register/template returning " +
tempFile.getAbsolutePath());
response.setContentType("application/octet-stream");
response.setHeader(
"Content-Disposition",
"attachment; filename=candidatetemplate"
+ tempFile.getName().substring(
tempFile.getName().lastIndexOf('.')));
InputStream is = new FileInputStream(tempFile);
OutputStream fw = response.getOutputStream();
byte[] buffer = new byte[1024];
int read;
while (-1 != (read = is.read(buffer))) {
fw.write(buffer, 0, read);
}
fw.flush();
log.debug("\n\n");
}
/**
* @return the modelService
*/
public IModelService getModelService() {
return modelService;
}
/**
* @param modelService
* the modelService to set
*/
@Autowired
public void setModelService(IModelService modelService) {
this.modelService = modelService;
}
}
The model file is uploaded and is correctly retrieved using a GET request
to the same URL, and the graphic file is also correctly uploaded to the
/register/template URL and added to the candidateModel session object, but
when the register/template GET is issued, Spring-MVC creates a new, empty
CandidateModel object and I cannot retrieve the uploaded graphic, as seen
from the debug output, here:
2013-09-11 11:58:10,078 DEBUG [ModelHandler] addModel received:
CandidateModel [original=null, candidateModelFile=null,
candidateTemplateFile=null, object
Id=com.mbn.modeldisplay.pojo.CandidateModel@1c6cc9c]
2013-09-11 11:58:10,216 DEBUG [ModelHandler] Writing to temp file:
C:\Users\Michael
Norris\Development\Tomcat\apache-tomcat-7.0.29\temp\norrim011291263503728127085.obj
2013-09-11 11:58:10,221 DEBUG [ModelHandler]
2013-09-11 11:58:10,398 DEBUG [ModelHandler] getCandidateModel received:
CandidateModel [original=null, candidateModelFile=C:\Users\Michael
Norris\Development\Tomcat\apache-tomcat-7.0.29\temp\norrim011291263503728127085.obj,
candidateTemplateFile=null, object
Id=com.mbn.modeldisplay.pojo.CandidateModel@1c6cc9c]
2013-09-11 11:58:10,398 DEBUG [ModelHandler] register/binary returning
C:\Users\Michael
Norris\Development\Tomcat\apache-tomcat-7.0.29\temp\norrim011291263503728127085.obj
2013-09-11 11:58:10,400 DEBUG [ModelHandler]
2013-09-11 11:58:10,460 DEBUG [ModelHandler] createCandidate returning:
CandidateModel [original=null, candidateModelFile=null,
candidateTemplateFile=null, object
Id=com.mbn.modeldisplay.pojo.CandidateModel@3c7038b9]
2013-09-11 11:58:10,460 DEBUG [ModelHandler]
2013-09-11 11:58:10,463 DEBUG [ModelHandler] getCandidateTemplate
received: CandidateModel [original=null, candidateModelFile=null,
candidateTemplateFile=null, object
Id=com.mbn.modeldisplay.pojo.CandidateModel@3c7038b9]
2013-09-11 11:58:10,463 DEBUG [ModelHandler] register/template found in
session: null
2013-09-11 11:58:10,464 DEBUG [ModelHandler] register/template returning
C:\Users\Michael
Norris\Development\Tomcat\apache-tomcat-7.0.29\webapps\ModelDisplay\assets\defaultimage.png
2013-09-11 11:58:10,465 DEBUG [ModelHandler]
2013-09-11 11:58:15,426 DEBUG [ModelHandler] addTemplate received:
CandidateModel [original=null, candidateModelFile=C:\Users\Michael
Norris\Development\Tomcat\apache-tomcat-7.0.29\temp\norrim011291263503728127085.obj,
candidateTemplateFile=null, object
Id=com.mbn.modeldisplay.pojo.CandidateModel@1c6cc9c]
2013-09-11 11:58:15,604 DEBUG [ModelHandler] Writing to temp file:
C:\Users\Michael
Norris\Development\Tomcat\apache-tomcat-7.0.29\temp\norrim01647051706271088553.PNG
2013-09-11 11:58:15,986 DEBUG [ModelHandler]
2013-09-11 11:58:16,014 DEBUG [ModelHandler] getCandidateModel received:
CandidateModel [original=null, candidateModelFile=C:\Users\Michael
Norris\Development\Tomcat\apache-tomcat-7.0.29\temp\norrim011291263503728127085.obj,
candidateTemplateFile=C:\Users\Michael
Norris\Development\Tomcat\apache-tomcat-7.0.29\temp\norrim01647051706271088553.PNG,
object Id=com.mbn.modeldisplay.pojo.CandidateModel@1c6cc9c]
2013-09-11 11:58:16,015 DEBUG [ModelHandler] register/binary returning
C:\Users\Michael
Norris\Development\Tomcat\apache-tomcat-7.0.29\temp\norrim011291263503728127085.obj
2013-09-11 11:58:16,016 DEBUG [ModelHandler]
2013-09-11 11:58:16,117 DEBUG [ModelHandler] createCandidate returning:
CandidateModel [original=null, candidateModelFile=null,
candidateTemplateFile=null, object
Id=com.mbn.modeldisplay.pojo.CandidateModel@1ae73783]
2013-09-11 11:58:16,118 DEBUG [ModelHandler]
2013-09-11 11:58:16,119 DEBUG [ModelHandler] getCandidateTemplate
received: CandidateModel [original=null, candidateModelFile=null,
candidateTemplateFile=null, object
Id=com.mbn.modeldisplay.pojo.CandidateModel@1ae73783]
2013-09-11 11:58:16,119 DEBUG [ModelHandler] register/template found in
session: null
2013-09-11 11:58:16,119 DEBUG [ModelHandler] register/template returning
C:\Users\Michael
Norris\Development\Tomcat\apache-tomcat-7.0.29\webapps\ModelDisplay\assets\defaultimage.png
2013-09-11 11:58:16,121 DEBUG [ModelHandler]
My view is using JQuery and Ajax to send the file as soon as it is
selected. On a successful result, showCandidateModel queries the server to
retrieve the files using routines from THREE.js. Here is an example of the
upload that happens immediately upon choosing a file:
if (formdata) {
$.ajax({
url: "/ModelDisplay/rest/model/register/binary",
type: "POST",
data: formdata,
processData: false,
contentType: false,
success: function (res) {
showCandidateModel();
},
error: function (jqXHR, textStatus, errorThrown) {
document.getElementById("response").innerHTML =
"<h2>" + textStatus + "</h2><BR><BR><p>" +
errorThrown + "<p>";
}
});
}
I have also tried adding HttpSession object to the handler methods and
interacting with it directly using addAttribute and getAttribute, but it
exhibits the same behavior, leading me to believe the Spring-MVC does not
actually use a real session object from Tomcat, but some other
implementation that it manages.
For completeness, here is my Spring configuration:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- the application context definition for the springapp
DispatcherServlet -->
<bean id="mongoDBDatabaseName" class="java.lang.String">
<constructor-arg value="ModelDisplay"/>
</bean>
<bean id="mongoDBDatabaseHost" class="java.lang.String">
<constructor-arg value="localhost"/>
</bean>
<bean id="mongoDBDatabasePort" class="java.lang.Integer">
<constructor-arg value="27017"/>
</bean>
<bean id="mongoDB" class="com.mbn.modeldisplay.dao.ModelDisplayMongoDB">
<constructor-arg ref="mongoDBDatabaseName"/>
<constructor-arg ref="mongoDBDatabaseHost"/>
<constructor-arg ref="mongoDBDatabasePort"/>
</bean>
<context:component-scan base-package="com.mbn.modeldisplay"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix" value="/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- one of the properties available; the maximum file size in bytes
-->
<property name="maxUploadSize" value="100000000"/>
</bean>
</beans>
I have searched for examples and I have found many that show an upload of
a single file, and multiple file upload examples, but none that provide
for the uploading of multiple files in more than one step. Thanks in
advance for your time.

No comments:

Post a Comment