Tuesday, March 8, 2011

Spring MVC - MultipartFile

Hey...

Today I am going to work with files and do it with spring mvc including uploading, saving and downloading files. Here we can practice spring-mvc and its features more and learn how to work with files in spring mvc.

Use case scenario
This is a file repository application. This application is an extension of simpleSpringSecurity application relating Spring Security post. Users can log in to the application. Then the file list page will be shown.

image 1
User can navigate to folders and download them. current directory is shown anywhere user is in.
image 2

User can create or edit or delete files and folders. both of them are available in the same way. If a folder contains file(s) or folder(s), deleting will not be available and error will be shown. After clicking add button, user see this page
image 3

If user check the tick, so the input entry is file otherwise is folder. folders will be shown as link in list page. but files will be downloadable.
Note: each user has his or her own repository and others are not able to visit it.
Design and Class diagram
Files and Folders domain are the same. So we seperate them with a flag. isFile() method will determite if entry is file or folder. we should save file data in database. so in File table we should create a field with LONGBLOB datatype. In domain we use java.sql.Blob datatype. The class diagram is available here:

image 4
Spring MVC
Most of techniques are the same in past posts. But there are 4 points here:
  1. Configuration: In spring-servlet.xml we should create a bean:
    ...
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <property name="maxUploadSize" value="100000000"></property>
    </bean>
    This class will prepare you org.springframework.web.multipart.MultipartFile in your controller class if you have an uploaded file in your request. As you see, you can limit the uploaded size. the scale is in bite. For example above sample allows less than about 100MB to be uploaded.
  2. Upload: This phase is so easy. you should just create an input file in your jsp:
    <form:form id="fileForm" enctype="multipart/form-data">
        <form:hidden path="file.id" id="fileId"/>
        <form:hidden path="parentFolder.id" id="fileId"/>
        <table>
            <tr>
                <td><label for="name">name</label></td>
                <td><form:input path="file.name" id="name" maxlength="45"/></td>
            </tr>
            <tr>
                <td><label for="file">file</label></td>
                <td><input type="file" id="file" name="file"/></td>
            </tr>
            <tr>
                <td><label for="isFile">is file</label></td>
                <td><form:checkbox id="isFile" path="file.file"/></td>
            </tr>
        </table>
        <input type="submit" value="save" onclick="setFormAction('<spring:url value="/file/save.html"/>')"/>
        <input type="submit" value="back" onclick="setFormAction('<spring:url value="/file/list.html"/>')"/>
    </form:form>
    then in your uploading method of the controller you can just receive the file in your parameter:
        @RequestMapping("/save")
        public String save(@RequestParam(value = "file", required = false) MultipartFile inputFile) throws IOException, SQLException {
            if (inputFile.getSize() > 0) {
                file.setFileData(getBlobData(inputFile));
            }

            if (parentFolder != null && parentFolder.getId() != null) {
                file.setParentFolder(fileService.findById(parentFolder.getId()));
                getModel().addAttribute("parentFolder.id", parentFolder.getId());
            }

            file.setOwner(getCurrentUser());
            fileService.saveOrUpdate(file);
            return "redirect:list.html";
        }
    Its data type is MultipartFile but should be converted to Blob to be saved.
  3. Save: Here you can convert MultipartFile to Blob:
        public Blob getBlobData(MultipartFile file) throws IOException, SQLException {
            byte[] bytes = file.getBytes();
            return new SerialBlob(bytes);
        }
    now you can set it in your entity bean and lets hibernate save it:
    if (inputFile.getSize() > 0) {
                file.setFileData(getBlobData(inputFile));
            }
  4. Download: Nothing changes in download. we should do everything that is done in classic servlet to download a file:
        @RequestMapping("/download")
        public void download() throws IOException, SQLException {
            file = fileService.findById(file.getId());
            if (file.getOwner().getId().equals(getCurrentUser().getId())) {
                getResponse().setContentType("APPLICATION/OCTET-STREAM");
                getResponse().addHeader("Content-Disposition", "attachment; filename=" + file.getName());
                OutputStream out = getResponse().getOutputStream();
                out.write(file.getFileData().getBytes(1, (int) file.getFileData().length()));
            }
        }
Now you are able to work with files. It's the time to download the source code and try it yourselves. The application database script is available in [app-root]/db/filerepository.sql. you can restore it in your mysql server. the connection datasource properties is in [app-root]/src/database.properites. and after you deploy the project in you application server (like tomcat) the start page will be: http://localhost:8080/home/view.html
the admin user specification is:
username: administrator
password: 123456
you can use it to log in for the first time.
You may have exception whenever you'd like to upload a large file. you should change your server and database server configuration to be able to do it. You should increase the value of maxUploadSize field  in multipartResolver bean. Then refer to your DBMS configuration and change it to make it able to handle large files. For example in mysql server, open "my.cnf" file (/etc/my.cnf in Linux and [window drive]\Program Files\MySQL\MySQL Server 5.0\my.cnf in MS Windows) then add or change this key:
[mysqld]
...

max_allowed_packet=100M
Then restart mysqld service.

Ok. Try yourselves.


all rights reserved by Mostafa Rastgar and Programmer Assistant weblog

5 comments:

Anonymous said...

Hello, there's an error when file.setFileData(getBlobData(inputFile)) is executed, the error says: "ClassCastException: javax.sql.rowset.serial.SerialBlob cannot be cast to [B" could you help me? thanks in advance

mostafa rastgar said...

hey
well... I think That's because of your jdk version. Please send me the complete stack trace.
But forget Blob. Try these changes:
File.java:
change the fileData field to this:
@Column(name = "file", updatable = false)
@Lob
byte[] fileData;

public byte[] getFileData() {
return fileData;
}
public void setFileData(byte[] fileData) {
this.fileData = fileData;
}

FileController.java:
@RequestMapping("/save")
public String save(...){
if (inputFile.getSize() > 0) {
file.setFileData(inputFile.getBytes());
}
...
}
@RequestMapping("/download")
public void download()...{
...
out.write(file.getFileData(), 0, file.getFileData().length);
...
}
Please try that and notify me.
Thanks

Anonymous said...

How can we convert blob to multipartfile ?? any suggestions

Unknown said...

Thanks for sharing this nice post.
dbms student helper

Photo Editing Service said...

We the BRS team which stands for Background Removal Services are providing you with highly graphic design and photo editing and obviously the most popular brand identity design at your cost. Do Visit to have the view of creative ideas of designs. Creative designs do help to grab the attraction of the customers. Make your own business flourish with powerful design concept today!