Tuesday, July 15, 2014

Dropzone JS + Spring MVC + Hibernate Integration (Complete Example)


This tutorial will demonstrate how to integrate DropzoneJS with Spring MVC. I'll cover in detail starting from creating a simple Spring MVC project, uploading files using DropzonJS and saving them in the server side, storing the files information in the database, downloading the files. The technologies used:

  • Spring MVC 4.0.3 as a web framework
  • Maven 3.1.1 as a build tool and for dependency management
  • Hibernate 4.2.7.Final as an ORM tool
  • Tomcat 7 as a web server

Let's get started and have some fun using DropzoneJS.

Objective:

The primary goal is to depict how to upload files using DropzoneJS. I'll also touch some of the customizing features. We'll then store the uploaded files information into database. We'll also see how to download those files.

Snapshots:

If you're curious to know what it will look like at the end, the following pictures are for you. For the image files, DropzoneJS displays a nice preview too.

1. Home Screen

2. Dragging the file over the region to attach


3. Listing out the attached files


4. Uploading completed


5. View All page with download option for each file



Project Setup

Let's begin setting up the project for eclipse. I would prefer setting up from scratch so that you understand everything happening behind the scenes. I created the the following hierarchical directory structure, springmvc-dropzonejs being the root directory.


Now, add the following pom.xml right under the root springmvc-dropzonejs directory.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
       <modelVersion>4.0.0</modelVersion>
       <groupId>np.com.mshrestha</groupId>
       <artifactId>springmvc-dropzonejs</artifactId>
       <name>photo-gallery</name>
       <packaging>war</packaging>
       <version>1.0.0-BUILD-SNAPSHOT</version>
       <properties>
              <spring-version>4.0.3.RELEASE</spring-version>
              <hibernate-version>4.2.7.Final</hibernate-version>
              <mysql-version>5.1.27</mysql-version>
              <apache-connection-pooling-version>1.4</apache-connection-pooling-version>             
       </properties>
       <dependencies>
              <!-- Spring -->
              <dependency>
                     <groupId>org.springframework</groupId>
                     <artifactId>spring-context</artifactId>
                     <version>${spring-version}</version>
              </dependency>
              <dependency>
                     <groupId>org.springframework</groupId>
                     <artifactId>spring-webmvc</artifactId>
                     <version>${spring-version}</version>
              </dependency>
              <dependency>
                     <groupId>org.springframework</groupId>
                     <artifactId>spring-orm</artifactId>
                     <version>${spring-version}</version>
              </dependency>

              <!-- Servlet -->
              <dependency>
                     <groupId>javax.servlet</groupId>
                     <artifactId>servlet-api</artifactId>
                     <version>2.5</version>
                     <scope>provided</scope>
              </dependency>
              <dependency>
                     <groupId>javax.servlet.jsp</groupId>
                     <artifactId>jsp-api</artifactId>
                     <version>2.1</version>
                     <scope>provided</scope>
              </dependency>
              <dependency>
                     <groupId>javax.servlet</groupId>
                     <artifactId>jstl</artifactId>
                     <version>1.2</version>
              </dependency>

              <!-- Apache Commons FileUpload -->
              <dependency>
                     <groupId>commons-fileupload</groupId>
                     <artifactId>commons-fileupload</artifactId>
                     <version>1.3.1</version>
              </dependency>

              <!-- Common IO -->
              <dependency>
                     <groupId>org.apache.commons</groupId>
                     <artifactId>commons-io</artifactId>
                     <version>1.3.2</version>
              </dependency>

              <!-- Jackson JSON Processor -->
              <dependency>
                     <groupId>org.codehaus.jackson</groupId>
                     <artifactId>jackson-core-asl</artifactId>
                     <version>1.9.13</version>
              </dependency>
              <dependency>
                     <groupId>org.codehaus.jackson</groupId>
                     <artifactId>jackson-mapper-asl</artifactId>
                     <version>1.9.13</version>
              </dependency>
              
              <!-- Hibernate -->
              <dependency>
                     <groupId>org.hibernate</groupId>
                     <artifactId>hibernate-core</artifactId>
                     <version>${hibernate-version}</version>
              </dependency>
              <dependency>
                     <groupId>org.hibernate</groupId>
                     <artifactId>hibernate-entitymanager</artifactId>
                     <version>${hibernate-version}</version>
              </dependency>

              <!-- Apache's Database Connection Pooling -->
              <dependency>
                     <groupId>commons-dbcp</groupId>
                     <artifactId>commons-dbcp</artifactId>
                     <version>${apache-connection-pooling-version}</version>
              </dependency>
              <dependency>
                     <groupId>commons-pool</groupId>
                     <artifactId>commons-pool</artifactId>
                     <version>${apache-connection-pooling-version}</version>
              </dependency>

              <!-- MySQL -->
              <dependency>
                     <groupId>mysql</groupId>
                     <artifactId>mysql-connector-java</artifactId>
                     <version>${mysql-version}</version>
              </dependency>
       </dependencies>
       <build>
              <plugins>
                     <!-- Tomcat 7 plugin -->
                     <plugin>
                           <groupId>org.apache.tomcat.maven</groupId>
                           <artifactId>tomcat7-maven-plugin</artifactId>
                           <version>2.0</version>
                     </plugin>
                     <plugin>
                           <artifactId>maven-eclipse-plugin</artifactId>
                           <version>2.9</version>
                           <configuration>
                                  <additionalProjectnatures>
                       <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                                  </additionalProjectnatures>
                                  <additionalBuildcommands>
                                         <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                                  </additionalBuildcommands>
                                  <downloadSources>true</downloadSources>
                                  <downloadJavadocs>true</downloadJavadocs>
                           </configuration>
                     </plugin>
              </plugins>
       </build>
</project>

Now, open the command prompt, go to the root directory and then type mvn eclipse:eclipse. This will generate the Eclipse configuration files so that eclipse recognizes the root directory as a project directory. For instance, you'll see the files .classpath, .project created under the root directory.

The above pom.xml contains dependencies for Spring, Servlet, Apache File I/O and Jackson JSON processor. You can also see the Tomcat 7 plugin.

Now, we're ready to import the project in eclipse. You can import the project either as 'General -> Existing Projects into Workspace' or as 'Maven -> Existing Maven Projects'.

Let's create the following java source packages under the 'src/java/main' directory.
  • np.com.mshrestha.dropzonetest.model
  • np.com.mshrestha.dropzonetest.dao
  • np.com.mshrestha.dropzonetest.dao.impl
  • np.com.mshrestha.dropzonetest.service
  • np.com.mshrestha.dropzonetest.service.impl
  • np.com.mshrestha.dropzonetest.controller
Please note that just to upload the files to server, we don't need any model classes, daos, services. Only controller is sufficient for that. Having said that, in this example we're going to store the uploaded file information in the database so that we can retrieve back the information.

Deployment Descriptor: web.xml

Now, let's create another folder 'WEB-INF' under the webapp directory and add the deployment descriptor web.xml under WEB-INF. The web.xml defines root spring container and the heart of the spring MVC - 'DispatcherServlet'.

web.xml   ( path: /webapp/WEB-INF/web.xml )
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app.xsd">

       <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
       <context-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>/WEB-INF/root-context.xml</param-value>
       </context-param>

       <!-- Creates the Spring Container shared by all Servlets and Filters -->
       <listener>
              <listener-class>
                      org.springframework.web.context.ContextLoaderListener
              </listener-class>
       </listener>

       <!-- Processes application requests -->
       <servlet>
              <servlet-name>dispatcherServlet</servlet-name>
              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
              <init-param>
                     <param-name>contextConfigLocation</param-name>
                     <param-value>
                           /WEB-INF/servlet-context.xml
                     </param-value>
              </init-param>
              <load-on-startup>1</load-on-startup>
       </servlet>

       <servlet-mapping>
              <servlet-name>dispatcherServlet</servlet-name>
              <url-pattern>/</url-pattern>
       </servlet-mapping>

</web-app>

The above web.xml simply defines the dispatcher servlet and the two configuration xmls. I intentionally created those two config xmls to let you know that we can initialize parameters in two ways. This will be needed if you have multiple servlets and filters that shares common parameters. You can define only one servlet config xml and put all of contents from both xmls into the one. The tag <context-param> defines parameters that are shared by all servlets and filters whereas <init-param> defines parameters that are accessible by only that <servlet> inside which it is placed. We also defined the DispatcherServlet which is the default request handler for all the requests as defined by the pattern "/" in <url-pattern>.

Let's create the two context configuration files alongside web.xml under the WEB-INF directory.

servlet-context.xml    ( path: /webapp/WEB-INF/servlet-context.xml )
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:beans="http://www.springframework.org/schema/beans"       
xmlns:context="http://www.springframework.org/schema/context" 
xmlns:mvc="http://www.springframework.org/schema/mvc"
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
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"
       default-lazy-init="true">

       <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

       <!-- Enables the Spring MVC @Controller programming model -->
       <annotation-driven />
       <context:component-scan base-package="np.com.mshrestha.dropzonetest.controller" />

       <!-- Defines a resolver implementation bean. It gets applied to all requests handled by that DispatcherServlet -->
       <beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

       <!-- Handles HTTP GET requests for /web-resources/** by efficiently serving up static resources in the ${webappRoot}/resources/ directory -->
       <resources mapping="/web-resources/**" location="/resources/" />
       <beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
              <beans:property name="useTrailingSlashMatch" value="true" />
       </beans:bean>

       <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
       <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
              <beans:property name="prefix" value="/WEB-INF/views/" />
              <beans:property name="suffix" value=".jsp" />
       </beans:bean>
</beans:beans>

In the above, it sets the property useTrailingSlashMatch of the bean RequestMappingHandlerMapping true. This is to match to URLs irrespective of the presence of a trailing slash. When it's true, the controller method mapped to "/listFiles" also matches to "/listFiles/".

Also, it defines the CommonsMultipartResolver bean, an implementation class of MultipartResolver.
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
We needed to define it explicitly because there is no default resolver implementation used for Spring DispatcherServlets, as an application might choose to parse its multipart requests itself. To define an implementation, create a bean with the id "multipartResolver" in a DispatcherServlet's application context. Such a resolver gets applied to all requests handled by that DispatcherServlet.

root-context.xml    ( path: /webapp/WEB-INF/root-context.xml )
<?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:tx="http://www.springframework.org/schema/tx" 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
  http://www.springframework.org/schema/tx
  http://www.springframework.org/schema/tx/spring-tx.xsd">

       <!-- Root Context: defines shared resources visible to all other web components -->
       <context:component-scan base-package="np.com.mshrestha.dropzonetest" />

       <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
              <property name="locations">
                     <list>
                           <value>classpath:database.properties</value>
                     </list>
              </property>
       </bean>

       <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
             destroy-method="close" p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}"
              p:username="${jdbc.username}" p:password="${jdbc.password}" />

       <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
              <property name="dataSource" ref="dataSource" />
              <property name="packagesToScan">
                     <list>
                           <value>np.com.mshrestha.dropzonetest.model</value>
                     </list>
              </property>
              <property name="hibernateProperties">
                     <props>
                           <prop key="hibernate.hbm2ddl.auto">update</prop>
                           <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                           <prop key="hibernate.show_sql">false</prop>
                     </props>
              </property>
       </bean>

       <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
              <property name="sessionFactory" ref="sessionFactory" />
       </bean>

       <tx:annotation-driven transaction-manager="transactionManager" />
</beans>

The above root-context.xml looks pretty straight-forward. The database property file looks like as follows:

database.properties    ( path: /src/main/resources/database.properties )
jdbc.driverClassName=com.mysql.jdbc.Driver

jdbc.url=jdbc:mysql://localhost/dropzone_test?createDatabaseIfNotExist=true&amp;amp;useUnicode=true&amp;amp;characterEncoding=utf-8&amp;amp;autoReconnect=true

jdbc.username=root
jdbc.password=root

hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect


Persistence

Let's create a pojo class as a model. We're going to use this class and corresponding table to store the uploaded file information in the database. Let's define a class UploadedFile under the package np.com.mshrestha.dropzonetest.modelThe class corresponds to the table uploaded_file.

UploadedFile.java
package np.com.mshrestha.dropzonetest.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "uploaded_file")
public class UploadedFile {

  private Long id;
  private String name;
  private String location;
  private Long size;
  private String type;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  public Long getId() {
    return id;
  }

  @Column(nullable = false)
  public String getName() {
    return name;
  }

  @Column(nullable = false)
  public String getLocation() {
    return location;
  }

  @Column()
  public Long getSize() {
    return size;
  }

  @Column(nullable = false)
  public String getType() {
    return type;
  }

  // setter methods...
}

Note that we don't store the file itself in the database. Instead, we only store its name and location.

Dao Layer

It's time to work on DAO layer. Let's define an interface FileUploadDao and the implementation class FileUploadDaoImpl in their respective packages. The methods and implementations don't need any explanations.

FileUploadDao.java
package np.com.mshrestha.dropzonetest.dao;
import java.util.List;
import np.com.mshrestha.dropzonetest.model.UploadedFile;

public interface FileUploadDao {
         List<UploadedFile> listFiles();
         UploadedFile getFile(Long id);
         UploadedFile saveFile(UploadedFile uploadedFile);
}

FileUploadDaoImpl.java
package np.com.mshrestha.dropzonetest.dao.impl;

import java.util.List;

import np.com.mshrestha.dropzonetest.dao.FileUploadDao;
import np.com.mshrestha.dropzonetest.model.UploadedFile;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class FileUploadDaoImpl implements FileUploadDao {

       @Autowired
       private SessionFactory sessionFactory;

       public List<UploadedFile> listFiles() {
              return getSession().createCriteria(UploadedFile.class).list();
       }

       public UploadedFile getFile(Long id) {
              return (UploadedFile) getSession().get(UploadedFile.class, id);
       }

       public UploadedFile saveFile(UploadedFile uploadedFile) {
              return (UploadedFile) getSession().merge(uploadedFile);
       }

       private Session getSession() {
              Session sess = getSessionFactory().getCurrentSession();
              if (sess == null) {
                     sess = getSessionFactory().openSession();
              }
              return sess;
       }

       private SessionFactory getSessionFactory() {
              return sessionFactory;
       }
}

Service Layer

So, for the service layer, just like we did in Dao Layer, let's create an interface FileUploadService and the implementation class FileUploadServiceImpl in their respective packages.

FileUploadService.java
package np.com.mshrestha.dropzonetest.service;
import java.util.List;
import np.com.mshrestha.dropzonetest.model.UploadedFile;

public interface FileUploadService {
         List<UploadedFile> listFiles();
         UploadedFile getFile(Long id);
         UploadedFile saveFile(UploadedFile uploadedFile);
}

FileUploadServiceImpl.java
package np.com.mshrestha.dropzonetest.service.impl;

import java.util.List;

import np.com.mshrestha.dropzonetest.dao.FileUploadDao;
import np.com.mshrestha.dropzonetest.model.UploadedFile;
import np.com.mshrestha.dropzonetest.service.FileUploadService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class FileUploadServiceImpl implements FileUploadService {

       @Autowired
       private FileUploadDao dao;

       @Transactional(readOnly = true)
       public List<UploadedFile> listFiles() {

              return dao.listFiles();
       }

       @Transactional(readOnly = true)
       public UploadedFile getFile(Long id) {
              
              return dao.getFile(id);
       }

       @Transactional
       public UploadedFile saveFile(UploadedFile uploadedFile) {
              
              return dao.saveFile(uploadedFile);
       }
}

So far, we've dealt everything needed for Service layer and DAO layer. I'm assuming you have no problem understanding the usage of the various annotations. Now, let's take a look into the Web Layer.

Presentation ( Web ) Layer

Controller 

Now we're going to create a controller that handles all the requests from the DispatcherServlet. Let's create the controller FileUploadController under the package np.com.mshrestha.dropzonetest.controller.

FileUploadController.java
package np.com.mshrestha.dropzonetest.controller;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import np.com.mshrestha.dropzonetest.model.UploadedFile;
import np.com.mshrestha.dropzonetest.service.FileUploadService;

import org.apache.commons.io.FileUtils;

import org.springframework.util.FileCopyUtils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

@Controller
public class FileUploadController {

       @Autowired
       private FileUploadService uploadService;

       @RequestMapping("/")
       public String home() {
              
              // will be resolved to /views/fileUploader.jsp      
              return "fileUploader";
       }

       @RequestMapping(value = "/upload", method = RequestMethod.POST)
       public @ResponseBody
       List<UploadedFile> upload(MultipartHttpServletRequest request,
                     HttpServletResponse response) throws IOException {

              // Getting uploaded files from the request object
              Map<String, MultipartFile> fileMap = request.getFileMap();

              // Maintain a list to send back the files info. to the client side
              List<UploadedFile> uploadedFiles = new ArrayList<UploadedFile>();

              // Iterate through the map
              for (MultipartFile multipartFile : fileMap.values()) {

                     // Save the file to local disk
                     saveFileToLocalDisk(multipartFile);

                     UploadedFile fileInfo = getUploadedFileInfo(multipartFile);

                     // Save the file info to database
                     fileInfo = saveFileToDatabase(fileInfo);

                     // adding the file info to the list
                     uploadedFiles.add(fileInfo);
              }

              return uploadedFiles;
       }

       @RequestMapping(value = { "/list" })
       public String listBooks(Map<String, Object> map) {

              map.put("fileList", uploadService.listFiles());

              // will be resolved to /views/listFiles.jsp
              return "/listFiles";
       }

       @RequestMapping(value = "/get/{fileId}", method = RequestMethod.GET)
       public void getFile(HttpServletResponse response, @PathVariable Long fileId) {

              UploadedFile dataFile = uploadService.getFile(fileId);

              File file = new File(dataFile.getLocation(), dataFile.getName());

              try {
                     response.setContentType(dataFile.getType());
                     response.setHeader("Content-disposition", "attachment; filename=\""
                                  + dataFile.getName() + "\"");

                     FileCopyUtils.copy(FileUtils.readFileToByteArray(file),
                                  response.getOutputStream());

              } catch (IOException e) {
                     e.printStackTrace();
              }
       }

       private void saveFileToLocalDisk(MultipartFile multipartFile)
                     throws IOException, FileNotFoundException {

              String outputFileName = getOutputFilename(multipartFile);

              FileCopyUtils.copy(multipartFile.getBytes(), new FileOutputStream(
                           outputFileName));
       }

       private UploadedFile saveFileToDatabase(UploadedFile uploadedFile) {

              return uploadService.saveFile(uploadedFile);

       }

       private String getOutputFilename(MultipartFile multipartFile) {

              return getDestinationLocation() + multipartFile.getOriginalFilename();
       }

       private UploadedFile getUploadedFileInfo(MultipartFile multipartFile)
                     throws IOException {

              UploadedFile fileInfo = new UploadedFile();
              fileInfo.setName(multipartFile.getOriginalFilename());
              fileInfo.setSize(multipartFile.getSize());
              fileInfo.setType(multipartFile.getContentType());
              fileInfo.setLocation(getDestinationLocation());

              return fileInfo;
       }

       private String getDestinationLocation() {
              return "D:/uploaded-files/";
       }
}

upload(): This method is called when the files are uploaded to server. The method is pretty simple and understandable. Otherwise, the comments will help you to understand. As you can see, the method parameters are little different than the usual controller methods. The interface MultipartHttpServletRequest
extends HttpServletRequest and MultipartRequest. It provides additional methods for dealing with multipart content within a servlet request, allowing to access uploaded files. And it's implementation comes from the MultipartResolver bean called CommonsMultipartResolver that we defined in servlet-context.xml.

getFile(): The method returns the requested file in the downloadable format. Note that we need to add the header 'Content-disposition' to the response to make the file downloadable.

The rest of the methods should be effortless to understand.

JSPs

We're going to create two JSP pages, one, the homepage, for uploading the files while another is for viewing all the uploaded files in the servers which will have download option too.

fileUploader.jsp    ( path: /webapp/WEB-INF/views/fileUploader.jsp )
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" name="viewport"
       content="width=device-width, initial-scale=1">
<title>Spring MVC + Dropzone.js Example</title>

<link rel="stylesheet" type="text/css"
       href='<c:url value="/web-resources/libs/bootstrap-3.1.1/css/bootstrap.min.css"/>'>
<link rel="stylesheet" type="text/css"
       href='<c:url value="/web-resources/libs/bootstrap-dialog/css/bootstrap-dialog.min.css"/>'>
<link rel="stylesheet" type="text/css"
       href='<c:url value="/web-resources/css/style.css"/>'>

</head>
<body>
       <div class="container">
              <div class="panel panel-default">
                     <div class="panel-heading text-center">
                           <h3>Spring MVC + Dropzone.js Example</h3>
                     </div>
                     <div class="panel-body">
                           <div>
                                  <form id="dropzone-form" class="dropzone"
                                         enctype="multipart/form-data">

                                         <div class="dz-default dz-message file-dropzone text-center well col-sm-12">
                                                 <span class="glyphicon glyphicon-paperclip"></span> <span>
                                                       To attach files, drag and drop here</span><br>
                                                <span>OR</span><br>
                                                <span>Just Click</span>
                                         </div>

                                         <!-- this is were the previews should be shown. -->
                                         <div class="dropzone-previews"></div>
                                  </form>
                                  <hr>
                                  <button id="upload-button" class="btn btn-primary">
                                         <span class="glyphicon glyphicon-upload"></span> Upload
                                  </button>
                                  <a class="btn btn-primary pull-right" href="list"> <span
                                         class="glyphicon glyphicon-eye-open"></span> View All Uploads
                                  </a>
                           </div>
                     </div>
              </div>
       </div>

       <script type="text/javascript"
              src='<c:url value="/web-resources/libs/jquery/jquery-2.1.1.js"/>'></script>
       <script type="text/javascript"
              src='<c:url value="/web-resources/libs/bootstrap-3.1.1/js/bootstrap.js"/>'></script>
       <script type="text/javascript"
              src='<c:url value="/web-resources/libs/bootstrap-dialog/js/bootstrap-dialog.min.js"/>'></script>
       <script type="text/javascript"
              src='<c:url value="/web-resources/libs/dropzone.js"/>'></script>
       <script type="text/javascript"
              src='<c:url value="/web-resources/js/app.js"/>'></script>
</body>
</html>

It's quite easy to integrate DropzoneJS. It starts with the <form> tag with class="dropzone". That's pretty much of it. Dropzone will find all form elements with the class dropzone, automatically attach itself to it, and upload files dropped into it to the specified action attribute. The only additional thing to do is to initialize the Dropzone configuration. The configuration is stored under the Dropzone.options object. It looks something like this:
Dropzone.options.dropzoneForm = {

      url : "/form/action/url",
      autoProcessQueue : false,
      uploadMultiple : true,
      maxFilesize : 256, // MB
      previewsContainer : ".dropzone-previews",
      
      init : function() {
           // handling events
      }
}

I've configured that in a separate JS file which we'll see in few minutes later. That's all we have to do for setting up DropzoneJS. Isn't that cool?

For each uploading file, Dropzone generates some HTML elements with dropzone classes. This is to display the file specific information like file name, file size, image preview, upload progress, success indicator, error indicator, error messages. And the best part here is they are easily customizable. The generated HTML is as follows:
<div class="dz-preview dz-file-preview">
       <div class="dz-details">
              <div class="dz-filename">
                     <span data-dz-name></span>
              </div>
              <div class="dz-size" data-dz-size></div>
              <img data-dz-thumbnail />
       </div>
       <div class="dz-progress">
              <span class="dz-upload" data-dz-uploadprogress></span>
       </div>
       <div class="dz-success-mark">
              <span></span>
       </div>
       <div class="dz-error-mark">
              <span></span>
       </div>
       <div class="dz-error-message">
              <span data-dz-errormessage></span>
       </div>

</div>

We can access the HTML of the file preview in any of the events with file.previewElement.

Dropzone also creates a <div> element with class="dz-default dz-message" with default message "Drop files to upload (or click)". As depicted in the above JSP page, we can override the default message by putting the <div> element with class="dz-message" inside the dropzone form. This <div> element is the actual drop zone where we can drop the files or simply click to open the file chooser dialog.

Also, I've added an effect such that if a file is dragged over the message <div>, it outlines the <div> so that we can know the file is over the correct region to drop.

Also, we've created an empty <div> element with class="dropzone-previews". I've configured this div in the dropzone configuration as a container for all the dropzone generated preview elements.  This is done by adding previewsContainer : ".dropzone-previews" in the dropzone configuration.

Moreover, I've customized the default progress bar too. In the dropzone configuration, the progress bar has been customized to show the progress percentage. It is done inside dropzone's uploadprogress event.

The following is the JS file I created and placed it under the '/webapp/resources/js/' directory.

app.jsp    ( path: /webapp/resources/js/app.js )
$(document).ready(function() {

       $(".file-dropzone").on('dragover', handleDragEnter);
       $(".file-dropzone").on('dragleave', handleDragLeave);
       $(".file-dropzone").on('drop', handleDragLeave);

       function handleDragEnter(e) {

              this.classList.add('drag-over');
       }

       function handleDragLeave(e) {

              this.classList.remove('drag-over');
       }

       // "dropzoneForm" is the camel-case version of the form id "dropzone-form"
       Dropzone.options.dropzoneForm = {

              url : "upload"// not required if the <form> element has action attribute
              autoProcessQueue : false,
              uploadMultiple : true,
              maxFilesize : 256, // MB
              parallelUploads : 100,
              maxFiles : 100,
              addRemoveLinks : true,
              previewsContainer : ".dropzone-previews",

              // The setting up of the dropzone
              init : function() {

                     var myDropzone = this;

                     // first set autoProcessQueue = false
                     $('#upload-button').on("click", function(e) {

                           myDropzone.processQueue();
                     });

                     // customizing the default progress bar
                     this.on("uploadprogress", function(file, progress) {

                           progress = parseFloat(progress).toFixed(0);

                           var progressBar = file.previewElement.getElementsByClassName("dz-upload")[0];
                           progressBar.innerHTML = progress + "%";
                     });

                     // displaying the uploaded files information in a Bootstrap dialog
                     this.on("successmultiple", function(files, serverResponse) {
                           showInformationDialog(files, serverResponse);
                     });
              }
       }

       
       function showInformationDialog(files, objectArray) {

              var responseContent = "";

              for (var i = 0; i < objectArray.length; i++) {

                     var infoObject = objectArray[i];

                     for ( var infoKey in infoObject) {
                           if (infoObject.hasOwnProperty(infoKey)) {
                             responseContent = responseContent + " " + infoKey + " -> " + infoObject[infoKey] + "<br>";
                           }
                     }
                     responseContent = responseContent + "<hr>";
              }

              // from the library bootstrap-dialog.min.js
              BootstrapDialog.show({
                     title : '<b>Server Response</b>',
                     message : responseContent
              });
       }

});

That's all I have for the dropzone integration. The only remaining part for this tutorial is to see another page that lists out all the uploaded files in the server. The page will have a download button for each file listed. Let's have a look.

listFiles.jsp    ( path: /webapp/WEB-INF/views/listFiles.jsp )
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" name="viewport"
       content="width=device-width, initial-scale=1">
<title>Spring MVC + Dropzone.js Example</title>

<link rel="stylesheet" type="text/css"
       href='<c:url value="/web-resources/libs/bootstrap-3.1.1/css/bootstrap.min.css"/>'>
<link rel="stylesheet" type="text/css"
       href='<c:url value="/web-resources/libs/bootstrap-dialog/css/bootstrap-dialog.min.css"/>'>
<link rel="stylesheet" type="text/css"
       href='<c:url value="/web-resources/css/style.css"/>'>

</head>
<body>
       <div class="container">
              <div class="panel panel-default">
                    
                     <div class="panel-heading text-center">
                           <h3>Spring MVC + Dropzone.js Example</h3>
                     </div>
                    
                     <div class="panel-body">

                           <a class="btn btn-primary" href="${pageContext.request.contextPath}">
                                  <span class="glyphicon glyphicon-chevron-left"></span> Go Back
                           </a>
                           <br>
                           <h4>List of All Uploaded Files</h4>

                     </div>
                     <table class="table table-hover table-condensed">
                           <thead>
                                  <tr>
                                         <th width="5%">S.N</th>
                                         <th width="40%">File Name</th>
                                         <th width="20%">File Type</th>
                                         <th width="15%">File Size</th>
                                         <th width="10%">Actions</th>
                                  </tr>
                           </thead>
                           <tbody>
                                  <c:forEach items="${fileList}" var="dataFile" varStatus="loopCounter">
                                         <tr>
                                                <td><c:out value="${loopCounter.count}" /></td>
                                                <td><c:out value="${dataFile.name}" /></td>
                                                <td><c:out value="${dataFile.type}" /></td>
                                               
                                                <td><c:out value="${dataFile.size}"</td>
                                               
                                                <td>
                                                    <a class="btn btn-primary" href="${pageContext.request.contextPath}/get/${dataFile.id}">
                                                    <span class="glyphicon glyphicon-download"></span> Download
                                                    </a>
                                                </td>
                                         </tr>
                                  </c:forEach>
                           </tbody>
                     </table>
              </div>
       </div>

       <script type="text/javascript"
              src='<c:url value="/web-resources/libs/jquery/jquery-2.1.1.js"/>'>
       </script>
       <script type="text/javascript"
              src='<c:url value="/web-resources/libs/bootstrap-3.1.1/js/bootstrap.js"/>'>
       </script>
</body>

</html>


Download

The source code is available in GitHub. You can either download the zip or directly import the project in eclipse from the following location:


Running the application

Now, running the application is quite simple enough.
  1. Open the Command Prompt
  2. Go to the root project directory ( springmvc-dropzonejs )
  3. Run the mvn command to download all dependent JARs.
    • Type mvn and hit <Enter>.
  4. Run Tomcat server 
    • mvn tomcat7:run
  5. Go to the browser and enter the following URL: 
    • http://localhost:8080/springmvc-dropzonejs/
    • The port number might be different in your case. Please have a look at the tomcat log in console for that.
Questions???

Feel free to shoot me an email or comment below if you have any questions or confusions. 

42 comments :

  1. How to run this project in eclipse?

    ReplyDelete
    Replies
    1. Hello Shaunak,

      For running the project in eclipse, please follow the steps below ( I'm assuming you've already imported the project in eclipse):

      1. Right click on the project -> Run As -> Run Configurations...
      2. On the left side of the dialog, you'll see 'Maven Build'. Double click it to create a new maven build configuration.
      3. The configuration name is defaulted to 'New_configuration' at the top. You can rename it whatever you like.
      4. For the 'Base Directory:', click on the 'Browse Workspace...' button and select the project.
      5. Now, in the 'Goals...' section, put 'eclipse:clean eclipse:clean tomcat7:run'
      6. Then, Apply and Run. You can see the logs in eclipse console.
      7. Once the tomcat has started, go to a browser and enter the url.
      8. That's it.

      I hope this helps.

      Thanks.

      Delete
    2. I am getting an exception

      I can see the view but file is not fetting uploaded....

      here follows the exception:

      Delete
    3. https://docs.google.com/document/d/1dEVIlBHXTW-k5MDEd6KCTXaoGAdUrq1AV8AweT9CsHI/edit?usp=sharing

      this is the, link...pl...look at it

      Delete
    4. Here i think problem is with database access.

      I don't know hibernate, i m only familiar with spring and jsp and JS.

      Please do help me with database connectivity and storing part.


      Awaiting for you reply...

      Delete
    5. Yes, as you said, the problem is due to connection failure with the database. In the 'database.properties' file, I've set both username and password as 'root'. Could you check your database login credentials and update the file accordingly? Let me know if that works or not.

      Delete
    6. Thank you so much bro...

      Its perfectly working fine...


      Just one more favour...

      can u guide me how to deploy it on the server?
      means i have my own server and a website hosted on it.
      I want to put this app there, can you guide me fr it?

      Delete
    7. Is there any solution to limit the number of files dropped at a time?

      I want to allow only one file dropped at a time.

      Delete
    8. can you reply to my last message please...???

      I want to knw that how we can restrict no. of file while dropping?

      please...help me!

      Thank you!

      Delete
    9. You can check the no. of files in drag & drop event handlers, perform any custom logic as per required.

      For example, if only one file should be allowed to drop at a time, you can do something as follows:
      ========================================================
      $(".file-dropzone").on('drop', handleDragDrop);

      function handleDragDrop(e) {

      var files = e.originalEvent.dataTransfer.files;

      if ( files.length > 1) {

      alert( "Can't drag and drop more than one file at a time...");

      e.stopPropagation();
      e.preventDefault();
      }
      }

      Delete
    10. Manoj Shrestha'S Blog: Dropzone Js + Spring Mvc + Hibernate Integration (Complete Example) >>>>> Download Now

      >>>>> Download Full

      Manoj Shrestha'S Blog: Dropzone Js + Spring Mvc + Hibernate Integration (Complete Example) >>>>> Download LINK

      >>>>> Download Now

      Manoj Shrestha'S Blog: Dropzone Js + Spring Mvc + Hibernate Integration (Complete Example) >>>>> Download Full

      >>>>> Download LINK 9e

      Delete
  2. Hello Manoj,
    Best tuto!! thanks
    But, how do we can easly integrate DropeZone into an existing form with other fields like: name, email...
    Thanks in advance for your answer,
    Ismail

    ReplyDelete
  3. Hello Ismail,

    Since, MultipartHttpServletRequest extends HttpServletRequest, the form fields like name, email etc. also come along as request parameters. You might already know, we can check the request parameters like this:

    Enumeration params = request.getParameterNames();

    while (params.hasMoreElements()) {

    String param = params.nextElement();

    System.out.println("Param: " + param);
    System.out.println("Value: " + request.getParameter(param));
    }
    ---
    So, if the form field name is "email", you can access the input value using:

    String email = request.getParameter("email");

    ---

    I hope this helps.

    ReplyDelete
  4. Hello,
    Thanks again for your quick answer, getting form info is working fine using getParameter method...
    Otherwise, How do you use return value uploadedFiles of upload method on client side? is there a hiden generated JSTL ( or Java code ) code allowing the use of uploadedFiles object?
    Thanks.
    Ismail

    ReplyDelete
  5. I am asking about return value uploadedFiles management as i need it to perform a validation of the values of my form ( name, email...etc )before putting them into database.
    Tahnks.
    Ismail

    ReplyDelete
    Replies
    1. If you need to have client side validation, why do you need uploadedFiles from server??? You should validate the form on client side before the form submission. Am I missing anything here?

      Delete
    2. Yes sure, i can do it using JavaScript on client side, but i prefer do it on server side in Java to avoid all issues
      linked to Javascript deactivation by user, so we will have alawys a good working of the application in such case...

      How can i do it using JSPs you provided in this tutorial?
      Thanks for your help,
      Ismail

      Delete
  6. THANK YOU SO MUCH, THIS WAS AN AMAZING TUTORIAL.... RUN AT THE FIRST TRIAL... READY TO INTEGRATED TO MY WEB SITE... SIMPLY AMAZING..

    ReplyDelete
  7. Hello Manoj,
    Do you know how to submit dropezone form with 0 files to upload?
    My form is including dropzone + other fields; name, email ...so i would like to make optional uploading files...
    Thanks in advance for your help.
    Ismail

    ReplyDelete
    Replies
    1. DropzoneJS's processQueue() method does not submit the form if there is no upload file. One solution could be to perform usual form submission. Inside the upload button click handler, you can check the uploading files size. If there's at least one uploading file, use DropzonJS's processQueue() othewise use Form.submit(). For instance:

      ---------
      var noOfQueuedFiles = myDropzone.getQueuedFiles().length;

      if (noOfQueuedFiles > 0)
      myDropzone.processQueue();
      else
      $("#dropzone-form").submit();

      ------

      And one last thing, don't forget to add "action" and "method" attributes in the < form > element.

      Hope this helps.

      Delete
    2. Hello,
      I tryied with this, there is no request received by the controller "/upload".
      So i have used another way to fix this issue => send a empty list of files when there is no file added into dropzone form:
      app.js:
      $('input[type="submit"]').on("click", function (e) {
      if (myDropzone.getQueuedFiles().length > 0){
      e.preventDefault();
      e.stopPropagation();
      myDropzone.processQueue();
      }else{
      e.preventDefault();
      e.stopPropagation();
      myDropzone.uploadFiles([]); //send empty
      }
      });

      It is working fine!!!! but i have another issue now, the validation of my form using JQuerry validator is not working.

      in my JSP:
      ....
      After form description i added this: ( all needed JS librairies have been included of course)
      $.validate(); => Validation does not work.

      Please have a look at this issue.
      Thanks,
      Ismail

      Delete
    3. On the other hand, How can i send object from /upload controller to UploadFile.JSP,
      I need it to display errors on form in case email, name..validation failed!
      Thanks

      Delete
  8. Hi Thanks for visit us:

    ARKA Softwares & Outsourcing is an IT Company focusing on software & Web development and providing offshore outsourcing solutions to enterprises worldwide.website development company in usa

    ReplyDelete
  9. in the listFiles.jsp, all the jstl were ignored. Only after adding this line to the top if the file, it started working

    <%@ page isELIgnored="false"%>

    Just in case someone has the same issue!

    ReplyDelete
    Replies
    1. Hello Anand,

      Sounds like you're using old version of deployment descriptor, version 2.3 or older. The new version of descriptor, 2.4 or newer, evaluates EL expressions by default.

      Thanks.

      Delete
  10. Nice sample...learn using maven w/elipse luna/1.8, spring+hibernate; jquery & bootstrap too...

    *updated pom.xml w/tomcat plugin to 2.2 & it worked:

    org.apache.tomcat.maven
    tomcat7-maven-plugin
    2.2

    ReplyDelete
  11. man where is the script for the databse, thanks!

    ReplyDelete
    Replies
    1. We don't need any database scripts. Once you run the tomcat, it will create the database and the tables on it's own based on the annotations defined in the model classes.

      Delete
  12. can we upload the folder using dropzonejs

    ReplyDelete
  13. Hi all,
    I have completed this demo but i try to read file from server and display same add page, but i use code below but image big size and not well,
    Can anyone help me to solve this problem Thanks you very much

    $(document).ready(function(){

    Dropzone.options.dropzoneForm = {
    init: function() {
    thisDropzone = this;

    var mockFile = { name: 'rajkram_1.jpg', size: 123 };

    thisDropzone.options.addedfile.call(thisDropzone, mockFile);

    thisDropzone.options.thumbnail.call(thisDropzone, mockFile, '/TLG_v2/resources/golf_course/Rachakram/rajkram_1.jpg');

    }
    };
    });

    ReplyDelete
  14. I just came by to tell you how much i appreciate you work.

    Regards,
    Branko

    ReplyDelete
  15. Hello Sir,
    Thank you for this awesome work. It helped me a lot. Please keep doing this great work.
    I have one question : I don't want to upload my file to my local disk, I want to use an online storage database/server. How can I do that ?
    Thank you

    ReplyDelete
  16. Manoj Shrestha'S Blog: Dropzone Js + Spring Mvc + Hibernate Integration (Complete Example) >>>>> Download Now

    >>>>> Download Full

    Manoj Shrestha'S Blog: Dropzone Js + Spring Mvc + Hibernate Integration (Complete Example) >>>>> Download LINK

    >>>>> Download Now

    Manoj Shrestha'S Blog: Dropzone Js + Spring Mvc + Hibernate Integration (Complete Example) >>>>> Download Full

    >>>>> Download LINK

    ReplyDelete
  17. Being one of the leading Website Development companies in India, our expertise in custom website development covers most of your needs.

    ReplyDelete