The project development
All the nuts and bolts involved in developing this portal will show how to implement basic Spring MVC software.
- Configuration of the
DispatcherServlet
- Configuration of the Spring container
- Creating controllers
- Validation
- Views and ViewResolvers
- Encryption and decryption of files
- Creating error views for generic exceptions
Configuring the DispatcherServlet
There are two ways to perform file-uploading transactions, and the easiest and most immediate way is to use the Servlet 3.1 multipart request process. In order to configure this option, we add the tag <multipart-config>
inside the <servlet>
tag declaration of DispatcherServlet
. The next step, which involves configuring the Spring MVC container, will discuss multipart request processes.
Aside from the <multipart-config>
tag, there are no additional settings that have been added to the web.xml
. The deployment descriptor (web.xml
) still contains the <servlet>
tag for the front controller's declaration. Even though EDMS uploads files of all different content types, it is not a requirement to include all of those mime-types in the servlet's resources.
The following is the complete content of the web.xml
file:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>ChapterTwo</display-name> <!-- Declare Spring DispatcherServlet --> <servlet> <servlet-name>edms</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <multipart-config> <max-file-size>104857600</max-file-size> <max-request-size>104857600</max-request-size> </multipart-config> </servlet> <servlet-mapping> <servlet-name>edms</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> </web-app>
Creating the Spring container
In order for the Spring Framework to recognize requests from file-uploading transactions, it must have the following API components installed into the framework:
- Servlet API Multipart Request: This is the standard API for file uploading since Servlet 3.0
- Apache Commons FileUpload APIs: This is a non-standard and external library
The Servlet 3.1 API classes for a multipart request are already part of the Servlet container, but can be configured further to indicate where to store the uploaded file, and to set the maximum size (in bytes) of the file(s) to be uploaded. The setup of this is done within the definition of the DispatcherServlet
with the use of the <multipart-config>
tag. The configuration of the EDMS involves some of the following properties:
max-file-size
: This property sets the maximum size limit of the uploaded files in bytes. The default value is -1 which means no limit in size.max-request-size
: This property sets the maximum size limit of all the multipart/form-data requests in bytes. The default value is -1 which means it is unlimited when it comes to the size.location
: This property sets the default directory location of all the uploaded documents.file-size-threshold
: This property sets the maximum allowable size (in bytes) of uploaded files that can be written to the disk. The default value, which is 0, states that the container should never write bytes to disk.
The EDMS configures only two properties, namely the <max-file-size>
and the <max-request-size>
. If the transaction exceeds the maximum limit set for each property above, an exception like we see in the following will be thrown:
org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request;
To enable multipart handling using Apache Commons FileUpload, the library must first be installed, and then a resolver must be injected into the container for the DispatcherServlet
to access it. In the case of EDMS, the following is the resolver configured to handle file uploading using Commons FileUpload:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons. CommonsMultipartResolver"> <property name="maxUploadSize" value="104857600" /> <!-- 100MB --> <!-- max size of file in memory (in bytes) --> <property name="maxInMemorySize" value="1048576" /> <!-- 1MB --> </bean>
Once the DispatcherServlet
receives the request from the client, the task to process the transaction is delegated by the servlet to the resolver declared. When the resolver receives the request, it then maps and parses the request into multipartfiles and parameters to create an instance of the MultipartHttpServletRequest
.
The CommonsMultipartResolver
consists of optional properties that can be further configured to optimize the uploading process and these options are:
uploadTempDir
: This property sets the temporary directory where uploaded files are saved. The default value is the servlets container's default temporary folder for the web applications.maxUploadSize
: This property sets the maximum allowable size of files to be uploaded in bytes. The default value is -1 which means unlimited size.maxInMemorySize
: This property sets the maximum allowable size needed before the uploaded files can be saved in the temporary folder. The default value is 10240 bytes.
The EDMS has configured only two properties, namely the maxUploadSize
and maxInMemorySize
. If there is no resolver declared and file uploading proceeds, the following exception will be encountered by the EDMS:
java.lang.IllegalStateException,java.io.IOException]: java.lang.IllegalArgumentException: Expected MultipartHttpServletRequest: is a MultipartResolver configured?
Creating exception handling for multipart requests
In the case that EDMS encounters exceptions thrown by the file uploading, it has the capability to handle the exception through the org.springframework.web.servlet.handler.SimpleMappingExceptionResolver
. This resolver registers one, or more, exception resolver beans that recognize exceptions encountered by any transaction, especially requests, and maps these beans to the error pages declared in its view mappings. Ways of handling exceptions in several Spring MVC applications will be discussed in the next chapters. In EDMS, all the java.lang
exception classes are mapped to the error view file_error
.
<bean class="org.springframework.web.servlet .handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key="java.lang.Exception">file_error</prop> </props> </property> </bean>
Creating the controllers
The EDMS has these main controllers implementing the options for file uploading and downloading transactions. The EDMS custom controllers are as follows:
UploadSingleFileController
: This form controller responds to the common FileUpload multipart request transaction, and processes the MultipartFile in order to save the single file into the file system store.UploadMultipleFileController
: This is a form controller that has the same response asUploadSingleFileController
; the only difference is that the request consists of more than one file to be dropped into the file system.UploadSingleFileFtpController
: This is a special form controller that asks for a file to be uploaded into the FTP server, username and password of the server.UploadSingleFileAjax
: This is a typical controller that has two services that handle uploading forms through JQuery plugins and another one that handles form transactions using JQuery form data plugin.UploadEncryptFileController
: This is a form controller that asks for the file and encrypts it using the Bouncy Castle Crypto API, before saving it to the file system.DownloadFileController
: This is a typical controller used to download a desired file on the list.DowloadFileFtpController
: This is a typical controller used to retrieve a file from an FTP server.DownloadEncryptFileController
: This is the controller that needs to be called to decrypt file that has been encrypted.
Uploading a single document
The UploadSingleFileController
responds to a request from a form that carries a document or file that needs to be uploaded to the server. The EDMS stores its uploaded documents or files within the web server home folder, owing to easy security configuration and management. The service, or handler, method mapped to the request, extracts the multipart object, and creates a copy of the file to the location using OutputStreams
class.
The following snippet shows the service method with the process of uploading the file to the location:
@RequestMapping(method = RequestMethod.POST) public String submitForm( Model model, @Validated @ModelAttribute("singleFileForm") SingeFileUploadForm singleFileForm, BindingResult bindingResult) { String returnVal = "view_file_form"; if (bindingResult.hasErrors()) { model.addAttribute("singleFileForm", singleFileForm); returnVal = "upload_single_form"; } else { MultipartFile multipartFile = singleFileForm.getFile(); try { // creates the file system folder or repository File dir = createUploadDirectory("tmpFiles"); // copies the file on server returnVal = uploadSingleFile(dir, multipartFile, model, singleFileForm); } catch (Exception e) { model.addAttribute("singleFileForm", singleFileForm); returnVal = "upload_single_form"; } } return returnVal; }
The method known as createUploadDirectory()
asks for the name of the upload directory, verifies it exists, and if not, creates it for the succeeding file uploading. The following is the code of the method:
private File createUploadDirectory(String dirName){ String rootPath = System.getProperty("catalina.home"); File dir = new File(rootPath + File.separator + dirName); if (!dir.exists()) dir.mkdirs(); return dir; }
After directory verification and creation, the method known as uploadSingleFile()
is now called to copy the original file to the upload directory using BufferedOutputStream
. The java.io.BufferedOutputStream
class manages the copying of the bytes content in a source file to a destination file through a single batch of byte transfer. This makes the process faster than any other per byte, or per character, writing method.
private String uploadSingleFile(File dir, MultipartFile multipartFile, Model model, SingeFileUploadForm singleFileForm) throws IOException{ File serverFile = new File(dir.getAbsolutePath() + File.separator + multipartFile.getOriginalFilename()); BufferedOutputStream stream = new BufferedOutputStream( new FileOutputStream(serverFile)); byte[] bytes = multipartFile.getBytes(); stream.write(bytes); stream.close(); model.addAttribute("singleFileForm", singleFileForm); return "view_file_form"; }
Uploading multiple documents
Spring allows the uploading of more than one document, which is done by mapping the array of multipart objects to the request. The custom controller of the EDMS, namely UploadMultipleFileController
, has a service that extracts the array of multipart objects, which consists of a list of uploaded files ready to be uploaded into the local repository. The implementation is just an extended version of how to upload the single document. The code for the service method, that processes the bulk of files for uploading, is as follows:
@RequestMapping(method = RequestMethod.POST) public String submitForm( Model model, @Validated @ModelAttribute("multipleFileUploadForm") MultipleFilesUploadForm multipleFileUploadForm, BindingResult bindingResult) { String returnVal = "view_files_form"; if (bindingResult.hasErrors()) { model.addAttribute("multipleFileUploadForm", multipleFileUploadForm); returnVal = "upload_multiple_form"; } else { List<MultipartFile> docFiles = multipleFileUploadForm.getFiles(); if (docFiles.size() > 0 || docFiles != null) { Iterator<MultipartFile> iterate = docFiles.iterator(); // accesses the repository otherwise creates it File dir = createUploadDirectory("tmpFiles"); // retrieve all the files objects while (iterate.hasNext()) { MultipartFile multipartFile = iterate.next(); returnVal = uploadIndividualFile(dir, multipartFile, model, multipleFileUploadForm); } model.addAttribute("multipleFileUploadForm", multipleFileUploadForm); } } return returnVal; }
First, the @Controller
has the createUploadDirectory()
that verifies and creates the directory if it does not exist, just like in the previous topic. Then it retrieves the full list of the MultipartFile from the request to be processed by uploadIndividualFile()
, which will upload every file to the upload directory. The method uses BufferedOutputStream
to manage fixed memory allocation for the bytes transfer, and also to make the performance fast.
private String uploadIndividualFile(File dir, MultipartFile multipartFile, Model model, MultipleFilesUploadForm multipleFileUploadForm){ try { byte[] bytes = multipartFile.getBytes(); // accesses the file from the source folder and copies it // to the repository File serverFile = new File(dir.getAbsolutePath() + File.separator + multipartFile.getOriginalFilename()); BufferedOutputStream stream = new BufferedOutputStream( new FileOutputStream(serverFile)); stream.write(bytes); stream.close(); return "view_files_form"; } catch (Exception e) { model.addAttribute("multipleFileUploadForm", multipleFileUploadForm); return "upload_multiple_form"; } }
Uploading single or multiple documents into the FTP server
The only EDMS controller that manages all multipart objects for FTP server uploading is the UploadSingleFileFtpController
, which only implements single file FTP uploads. This process needs the Apache Commons Net API that has the necessary classes to help EDMS with FTP operations like creating connections, retrieving a list of all the files from the FTP, uploading files to the FTP, downloading files from the FTP, and other files and directory manipulations possible using the FTP protocol.
The uploading starts with instantiating the org.apache.commons.net.ftp.FTPClient
class. We then use the connect()
method of the FTPClient, access the FTP server, and evaluate the connection status. If it's successful, use the login()
method of the same class, using the valid credentials of the FTP server ,otherwise the login operation will become invalid.
Once logged into the server, read and extract the stream object of the file that needs to be uploaded. Using the storeFile()
method of the FTPClient
class, pass the desired complete name of the file once uploaded, and the stream object of the original file as the second argument. Always call the logout()
and disconnect()
, respectively, after a successful or unsuccessful FTP file upload. A solution found in EDMS is shown as follows:
@RequestMapping(method=RequestMethod.POST) public String submitForm(Model model, @Validated @ModelAttribute("singleFtpForm") SingleFileUploadFtpForm singleFtpForm, BindingResult bindingResult) throws ServletException, IOException{ String returnVal = "view_file_ftp"; if (bindingResult.hasErrors()) { model.addAttribute("singleFtpForm", singleFtpForm); returnVal = "upload_single_ftp"; } else { model.addAttribute("singleFtpForm",singleFtpForm); FTPClient ftpClient = new FTPClient(); FileInputStream inputStream = null; try { if (isFtpAccessValid(ftpClient, "<FTP URL here>", singleFtpForm)) { // entry point to the FTP server ftpClient.enterLocalPassiveMode(); // file byte type ftpClient.setFileType(FTP.BINARY_FILE_TYPE); // creates the file system folder or repository File dir = createUploadDirectory("tmpFiles"); // creates the empty file on server File serverFile = new File(dir.getAbsolutePath() + File.separator + singleFtpForm.getFile().getOriginalFilename()); inputStream = new FileInputStream(serverFile); // uploads the file, returns false if errors found boolean uploaded = ftpClient.storeFile( singleFtpForm.getFile().getOriginalFilename(), inputStream); if(!uploaded) throw new ServletException(); // logout the user, returned true if logout success boolean logout = ftpClient.logout(); if(!logout) throw new ServletException(); // See the sources } else { // Error page } } catch (SocketException e) { // Error page } catch (IOException e) { // Error page } finally { try { ftpClient.disconnect(); } catch (IOException e) { // Error page } } } return returnVal; }
First, the @Controller
annotation checks if the user has access to the FTP server, and if the URL is a valid FTP endpoint through the isFtpAccessValid()
method.
public boolean isFtpAccessValid(FTPClient ftpClient, String ftpUrl, SingleFileUploadFtpForm singleFtpForm) throws SocketException, IOException{ // the server FTP address String ftpServer = "<FTP URL here>"; ftpClient.connect(ftpServer); // access the file system folder or repository // otherwise it creates it boolean login = ftpClient.login(singleFtpForm.getUsername(), singleFtpForm.getPassword()); return login; }
Then, it configures the FTP access properties and verifies the upload directory for the backup directory. From the request, the @Controller
first creates the copy of the original file at the server, and then forwards it through the ftpClient.storeFile()
method. If the FTP transfer is successful, the client will be logged out through ftpClient.logout()
. Exceptions will be thrown if there are problems encountered along the way.
Uploading single or multiple files asynchronously
Many of the intranet applications are heavily loaded with portlets, and sometimes portlets are composed of AJAX that can access the server asynchronously together with other HTTP transactions. AJAX driven portlets, or applications, can communicate with Spring MVC in whatever container, once files are to be uploaded into the document repository. The existence of Webscript Framework in Alfresco ECM gives EDMS the idea that somehow there will be ways that document(s) can be uploaded and downloaded using JavaScript.
The controller of EDMS that processes the client-side implementation for file uploading with the server-side counterpart is the UploadSingleFileAjax
. The service can only process one document for uploading.
The EDMS prototype implements two types of asynchronous client-side implementations of file uploading and these are:
- AJAX-driven file uploading using a JQuery Plugin: This solution utilizes the jQuery form plugin that allows AJAX implementation for form transaction. The API has two main methods,
ajaxForm()
andajaxSubmit()
, that gather information from the form element to determine and manage form submission. To further optimize the process of submission, these two methods contain more options that fine-tune the request transaction. EDMS has implemented a simple solution that uses this JavaScript library:
function uploadJqueryForm(){ $('#result').html(''); $("#form").ajaxForm({ success:function(data) { $('#result').html(data); }, dataType:"text" }).submit(); }
- AJAX-driven file uploading using JQuery Form Data Plugin: This solution utilizes the
FormData
object of the JQuery plugin, that contains a set of key/value pairs that needs to be transported usingXMLHttpRequest
. Its main use is to send form data independent from form transaction. The transmitted data is in the same format that the form'ssubmit()
method would use to send the data, if the form's encoding type were set to multipart/form-data. Since EDMS is a prototype, users can examine and evaluate which of the two client-side solutions can best provide optimized file uploading transactions. The following is the EDMS simple solution using this option:
function uploadFormData(){ $('#result').html(''); var oMyForm = new FormData(); oMyForm.append("file", file.files[0]); $.ajax({ url: 'http://localhost:8080/ch02//edms/call_ajax.html', data: oMyForm, dataType: 'text', processData: false, contentType: false, type: 'POST', success: function(data){ $('#result').html(data); } }); }
On the server side, the controller UploadSingleFileAjax
has a service that uses the interface MultipartHttpServletRequest
to get the multipart object from the request. This is the easiest way to extract the said object(s) in order to start the uploading. The rest of the implementation is just the same with the UploadSingleFileController
controller. The following is the service for this request:
@RequestMapping(value="/edms/call_ajax", method=RequestMethod.POST) public String getFile(Model model, MultipartHttpServletRequest request, HttpServletResponse response){ Iterator<String> itr = request.getFileNames(); MultipartFile multipart = request.getFile(itr.next()); try { byte[] bytes = multipart.getBytes(); // Creating the directory to store file String rootPath = System.getProperty("catalina.home"); File dir = new File(rootPath + File.separator + "tmpFiles"); if (!dir.exists()) dir.mkdirs(); // Create the file on server File serverFile = new File(dir.getAbsolutePath() + File.separator + multipart.getOriginalFilename()); BufferedOutputStream stream = new BufferedOutputStream( new FileOutputStream(serverFile)); stream.write(bytes); stream.close(); model.addAttribute("multipart", multipart); } catch (IOException e) { } return "view_ajax"; }
Uploading documents with encryption/decryption
Some ECM has a document management system that encrypts the file before uploading, in order to secure the files from hacking, or infiltration, once they are uploaded to the repository. There are lots of cryptography API solutions used for securing files, but EDMS uses the Bouncy Castle Crypto API library.
The Bouncy Castle Crypto package is a Java implementation of cryptographic algorithms; it was developed by the Legion of the Bouncy Castle. The package is organized so that it contains a lightweight API suitable for use in any environment (including the newly released J2ME), with an additional infrastructure to conform the algorithms to the JCE framework. The API includes packages that encrypt credentials, or files, using populate cryptography algorithms like RSA, DES,AES and other variants. For simplicity, the EDMS uses the DES type of encryption-decryption, which needs a key to proceed with the transaction. The code of the service method is shown as follows:
@RequestMapping(method = RequestMethod.POST) public String submitForm( Model model, @Validated ModelAttribute("singleFileEncryptForm") SingleFileEncryptForm singleFileEncryptForm, BindingResult bindingResult) { String returnVal = "view_file_encrypt"; if (bindingResult.hasErrors()) { model.addAttribute("singleFileEncryptForm", singleFileEncryptForm); returnVal = "upload_single_encrypt"; } else { MultipartFile multipartFile = singleFileEncryptForm.getFile(); try { // encrypts the file using DES Algorithm byte[] encContent = encryptedContent(multipartFile, "12345678"); // accesses the repository otherwise creates it File dir = createUploadDirectory("tmpFiles"); FileOutputStream imageOutFile = new FileOutputStream(dir.getAbsolutePath() + File.separator + multipartFile.getOriginalFilename() +".signed"); BufferedOutputStream stream = new BufferedOutputStream(imageOutFile); stream.write(encContent); stream.flush(); stream.close(); imageOutFile.close(); model.addAttribute("signedFileName", multipartFile.getOriginalFilename() +".signed"); } catch (Exception e) { model.addAttribute("singleFileEncryptForm", singleFileEncryptForm); returnVal = "upload_single_encrypt"; } } return returnVal; }
After calling createUploadDirectory()
, the @Controller
will execute encryptedContent()
to extract the bytes content of the uploaded file and encode it using a cipher text, or key, through a DES algorithm of Bouncy Castle APIs. This process is found in the method encryptDESFile()
.
Note
This application exposes the cipher text or keys as a request parameter or hardcoded data, but it is recommended to store this in a keystore or a file cabinet.
private byte[] encryptedContent(MultipartFile multipartFile, String cipher) throws Exception, IOException{ File convFile = new File( multipartFile.getOriginalFilename()); multipartFile.transferTo(convFile); FileInputStream imageInFile = new FileInputStream(convFile); byte imageData[] = new byte[(int) convFile.length()]; imageInFile.read(imageData); byte[] bytes = encryptDESFile(cipher,imageData); imageInFile.close(); return bytes; } private byte[] encryptDESFile(String keys, byte[] plainText) { BlockCipher engine = new DESEngine(); byte[] key = keys.getBytes(); byte[] ptBytes = plainText; BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine)); cipher.init(true, new KeyParameter(key)); byte[] rv = new byte[cipher.getOutputSize(ptBytes.length)]; int tam = cipher.processBytes(ptBytes, 0, ptBytes.length, rv, 0); try { cipher.doFinal(rv, tam); } catch (Exception ce) { } return rv; }
To decrypt the file for download, the decryptDESFile()
method must be called:
public byte[] decryptDESFile(String key, byte[] cipherText) { BlockCipher engine = new DESEngine(); byte[] bytes = key.getBytes(); BufferedBlockCipher cipher = new PaddedBufferedBlockCipher( CBCBlockCipher(engine)); cipher.init(false, new KeyParameter(bytes)); byte[] rv = new byte[cipher.getOutputSize(cipherText.length)]; int tam = cipher.processBytes(cipherText, 0, cipherText.length, rv, 0); try { cipher.doFinal(rv, tam); } catch (Exception ce) { } return rv; }
Downloading individual documents from the file system
The EDMS has a controller, called DownloadFileController
, that accepts the filename of the original document as a request parameter; accesses and evaluates the content types; creates an InputStream
object copy of the files in the repository; and updates the content-disposition header using the HttpServletResponse
.
The service implementation is shown as follows:
@RequestMapping(value = "/edms/download_single_file", method = RequestMethod.GET) public String downloadFile(@RequestParam("fileName") String fileName, HttpServletRequest request, HttpServletResponse response) { // Creating the directory to store file String filePath = System.getProperty("catalina.home") + File.separator + "tmpFiles" + File.separator + fileName; File downloadFile = new File(filePath); // get MIME type of the file ServletContext context = request.getServletContext(); String mimeType = context.getMimeType(filePath); if (mimeType == null) { // set to binary type if MIME mapping not found mimeType = "application/octet-stream"; } System.out.println("MIME type: " + mimeType); // set content attributes for the response response.setContentType(mimeType); response.setContentLength((int) downloadFile.length()); // set headers for the response String headerKey = "Content-Disposition"; String headerValue = String.format("attachment; filename="%s"", downloadFile.getName()); response.setHeader(headerKey, headerValue); FileInputStream fis; try { fis = new FileInputStream(downloadFile); FileCopyUtils.copy(fis, response.getOutputStream()); } catch (FileNotFoundException e) { } catch (IOException e) { } return ""; }
Downloading individual documents from the FTP server
The controller DownloadFileFtpController
has a service that retrieves an uploaded file from the FTP server, for the user to download it from the view pages. The first phase of the solution uses the Apache Commons Net API, which is similar to the FTP upload service mentioned above. The only difference is that this solution uses the FileOutputStream
object to write the object stream of the uploaded file to a new file, specified with the file name given. The API class FtpClient
has a retrieveFile
(String remote, OutputStream
local) method that needs the downloadable file name (as it appears on the server) as its first argument, and to pass the output stream object as the second argument. EDMS has a simple solution indicated in the following code:
@RequestMapping(value = "/edms/download_single_ftp", method = RequestMethod.GET) public String downloadFile(@RequestParam("fileName") String fileName, @RequestParam("username") String username, @RequestParam("password") String password, HttpServletRequest request, HttpServletResponse response) { FTPClient ftpClient = new FTPClient(); FileOutputStream fos = null; String ftpServer = "ftp.alibatabusiness.com"; try { // pass directory path on server to connect ftpClient.connect(ftpServer); // pass username and password, returned true if // authentication is successful boolean login = ftpClient.login(username, password); if (login) { fos = new FileOutputStream(fileName); boolean download = ftpClient.retrieveFile( "C:\\Users\\sjctrags\\Downloads" + fileName, fos); // logout the user, returned true if logout successfully boolean logout = ftpClient.logout(); if (logout) { } } else { } } catch (SocketException e) { } catch (IOException e) { } finally { try { fos.close(); ftpClient.disconnect(); } catch (IOException e) {} } return "view_file_ftp"; }
The controllers mentioned in the preceding code are the present implementation in the EDMS project for this chapter. Some functionalities like multiple file uploads to FTP server, and multiple file uploads using AJAX, will be left as an exercise for the reader. Speaking of AJAX, we can create client-side solutions for file uploading using Jackrabbit API with the Spring MVC container.
The form domains
The EDMS has two domain models that generally represent a form for a single file upload, and one for a multiple file upload. These data will be passed to and from a multipart request transaction, through a defined controller and the view. Common to all domains, an interface org.springframework.web.multipart.MultipartFile
must be present, because it is the representation of an uploaded file received in a multipart request. After form submission, the file contents are either stored in memory or temporarily on the disk. Once the service method has received the Multipartfile
object, it is copied to the session-level or persistent store. The temporary storage used by the Multipartfile
will be cleared at the end of request processing.
The form domain model, that will contain the single multipart object after form submission, must be implemented this way:
package org.packt.edms.portal.model.form; import org.springframework.web.multipart.MultipartFile; public class SingeFileUploadForm { private MultipartFile file; public MultipartFile getFile() { return file; } public void setFile(MultipartFile file) { this.file = file; } }
The other form domain that contains multiple files for upload must look like this:
package org.packt.edms.portal.model.form; import java.util.List; import org.springframework.web.multipart.MultipartFile; public class MultipleFilesUploadForm { private List<MultipartFile> files; public List<MultipartFile> getFiles() { return files; } public void setFiles(List<MultipartFile> files) { this.files = files; } }
These domain objects will still contain more information than EDMS, as the requirement covers more features and workflows.
The views
The view and view resolvers are similar to that of our PWP prototype in Chapter 1, Creating a Personal Web Portal (PWP). Nothing has changed except for the actual view pages. The pages are typical of an HTML view consisting of the head and body HTML tags. It contains the Spring Form Taglib and JSTL libraries to create a form <form:form>
tag. It is recommended to set the request method to POST
, although there are complicated implementations where PUT
is the method setting. The commandName
property of the <form:form>
tag contains the name of the backing bean, bound to the appropriate domain model discussed above.
One very essential setting for all the actual pages used in EDMS is the use of the enctype="multipart/form-data"
attribute in the <form:form>
tag, through which the browser learns how to encode the form as a multipart request. The next important attribute is the input tag, with the type property set to file, through which the uploaded file is placed. Since validation needs to be part of the EDMS, the <form:errors>
tag defines where the error message of the specified field will be displayed in the view. Finally, the input tag, with the type property set to upload, is used for the upload button.
The following code is the actual view page of uploading a single document:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> <form:form commandName="singleFileForm" method="POST" enctype="multipart/form-data"> File to upload: <input type="file" name="file" /><br /> <form:errors path="file"/> <input type="submit" value="Upload"> Press here to upload the file! </form:form> </body> </html>
Part of the view pages is the file_error
view, which is called once DispatcherServlet
encounters an error during the request dispatch process. The page shows the exception message that can be custom made or generic. The EDMS has only one generic actual view page, and it is shown as follows:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> <h2>File Uploading Eror</h2> <p> ${ exception } </body> </html>
The validators
The EDMS will not be complete without an engine that checks whether the file(s) is (are) valid for uploading. The criteria that document storage must consider during the validation process can be any of the following:
- The maximum and/or minimum file size the user must upload
- The total size of the files uploaded or the quota of the whole multipart request
- The maximum number of files the system can upload per request
- The content types of the files to be uploaded into the repository
- The filename convention that the system follows (not mandatory)
- Any attributes from the document that the process wants to filter
The setup and configuration of the @InitBinder
and @Autowired
components in the controllers are just the same as with the PWP in Chapter 1, Creating a Personal Web Portal (PWP). Just to give you an idea how validation is being implemented in the EDMS, an actual code with regard to validating multiple file uploads, is shown as follows:
package org.packt.edms.portal.validator; import java.util.List; import org.packt.edms.portal.model.form.MultipleFilesUploadForm; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.springframework.web.multipart.MultipartFile; public class MultipleFileValidator implements Validator{ @Override public boolean supports(Class<?> clazz) { // TODO Auto-generated method stub return MultipleFilesUploadForm.class.equals(clazz); } @Override public void validate(Object obj, Errors error) { MultipleFilesUploadForm form = (MultipleFilesUploadForm) obj; List<MultipartFile> files = form.getFiles(); boolean isValid = true; StringBuilder sb = new StringBuilder(""); for(MultipartFile file:files) { if(file.getSize() == 0) { isValid = false; sb.append(file.getOriginalFilename()+" "); } } if(!isValid) error.rejectValue("files","error.file.size",new String[]{sb.toString()},"File size limit exceeded"); } }
The EDMS project
This chapter provides different solutions to how to start with a document uploader and downloader module. Although a full ECM application involves integration of different features like collaboration modules, records management plugins, watermarking, annotation plugins, and workflow management, it is still the document repository that is being most utilized by the client, including the flexibility of the software to upload and download any content types. Since Spring Framework 4.x supports file uploading, it will be just as easy for the developers to innovate techniques on file management as to spend time on basic uploading measures.
The following is the EDMS home page, which has all the types of uploading documents:
Note
Web design theme
The theme is based on the pool of open source templates from http://www.solucija.com/.