Upload de Arquivos com Servlet 3

Neste tutorial será abordado como realizar o upload de arquivos em uma aplicação Java Web utilizando Servlet 3, o que retira a necessidade de qualquer configuração no arquivo web.xml. Os arquivos serão armazenados em um diretório no disco rígido e algumas informações destes arquivos serão armazenadas no MySQL com uso do Hibernate. Além do upload, será demonstrado também como recuperar os arquivos armazenados e disponibilizados para download em uma página JSP.

Após fazer o upload de um arquivo teremos uma tabela que listará os arquivos armazenados, conforme a figura a seguir. A tabela exibe as informações do arquivo que foram salvas no banco de dados e na coluna File exibe uma miniatura dos arquivos de imagem. Agora, vamos ao projeto.

Tabela com a lista de uploads

1. Construindo o Projeto

A primeira classe do projeto a ser abordada será a classe de entidade FileEntity. Esta classe terá alguns atributos para armazenar informações durante o processo de upload. Note que na Listagem 1 a classe FileEntity está mapeada com anotações para uso do Hibernate. Quando o upload do arquivo for realizado, um diretório base será criado em c:\uploads. Entretanto, dentro deste diretório, vamos criar novos diretórios, referentes ao ano e ao mês que este arquivo estará sendo salvo. Por este motivo, precisamos salvar no banco de dados, o nome do arquivo, o ano e também o mês que este arquivo foi armazenado. E por último, vamos salvar também o tipo de arquivo que está sendo armazendo (jpeg, png, pdf, doc, etc).

Listagem 1. Classe FileEntity
package com.mballem.tutorial.entity;

import javax.persistence.*;
import java.io.Serializable;

/**
 * Created by Marcio Ballem on 15/04/2014.
 */
@Entity
@Table(name = "FILE_UPLOAD")
public class FileEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    @Column(name = "FILE_NAME", unique = true)
    private String name;
    @Column(name = "FILE_YEAR")
    private String year;
    @Column(name = "FILE_MONTH")
    private String month;
    @Column(name = "FILE_CONTENT_TYPE")
    private String type;

    //métodos getters/setters foram omitidos

}

Para salvar as informações dos arquivos no banco de dados, vamos usar o HIbernate. Na classe FileDao é possível observar os métodos save(), find() e findAll(), conforme Listagem 2. A interface IFileDao e a classe HibernateUtil serão omitidas, mas estão disponíveis para download junto ao projeto completo ao final do tutorial.

Listagem 2. Classe FileDao
package com.mballem.tutorial.dao;

import com.mballem.tutorial.entity.FileEntity;
import com.mballem.tutorial.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;

import java.util.List;

/**
 * Created by Marcio Ballem on 16/04/2014.
 */
public class FileDao implements IFileDao<FileEntity> {

    public void save(FileEntity entity) {
        Session session = HibernateUtil.getSession();
        session.beginTransaction();
        session.save(entity);
        session.getTransaction().commit();
    }

    @SuppressWarnings("unchecked")
    public List<FileEntity> findAll() {
        Session session = HibernateUtil.getSession();
        session.beginTransaction();

        List<FileEntity> entities = 
                session.createCriteria(FileEntity.class).list();

        session.getTransaction().commit();
        return entities;
    }

    @Override
    public FileEntity find(String year, String month, String name) {
        Session session = HibernateUtil.getSession();
        session.beginTransaction();

        FileEntity entity = (FileEntity) session.createCriteria(FileEntity.class)
                .add(Restrictions.eq("year", year))
                .add(Restrictions.eq("month", month))
                .add(Restrictions.eq("name", name))
                .uniqueResult();

        session.getTransaction().commit();
        return entity;
    }
}

Vamos agora analisar o servlet responsável por realizar o upload dos arquivos. A classe FileUploadServletListagem 3 – anotada com a anotação @WebServlet, a qual recebe as requisições pela URL /upload. A anotação @MultipartConfig tem o objetivo de lidar com os pedidos do tipo form-data que contêm dados de upload de arquivos. A anotação tem as seguintes opções como:

  • fileSizeThreshold: tamanho do arquivo que é superior a este limite será diretamente gravados no disco, em vez de guardar na memória;
  • maxFileSize: tamanho máximo de um único arquivo de upload;
  • maxRequestSize: tamanho máximo para uma solicitação. Todos os tamanhos são medidos em bytes.
Listagem 3. Classe FileUploadServlet
package com.mballem.tutorial.servlet;

import com.mballem.tutorial.service.FileService;
import org.apache.log4j.Logger;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;

/**
 * Created by Marcio Ballem on 15/04/2014.
 */
@WebServlet(urlPatterns = "/upload")
@MultipartConfig(
        fileSizeThreshold = 1024 * 1024, // 1MB
        maxFileSize = 1024 * 1024 * 4,   // 4MB
        maxRequestSize = 1024 * 1024 * 4 // 4MB
)
public class FileUploadServlet extends HttpServlet {

    private static Logger logger = Logger.getLogger(FileUploadServlet.class);

    private static final String BASE_DIR = "C:\uploads";

    @Override
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) throws ServletException, IOException {

        response.setContentType("text/html;charset=UTF-8");

        FileService service = new FileService();

        Path destination = service.createFolder(BASE_DIR);

        for (Part part : request.getParts()) {
            if (Files.exists(destination)) {
                service.saveFile(destination, part);
            }
        }

        request.setAttribute("message", "Upload has been done successfully!");
        request.setAttribute("fileEntities", service.findAll());
        
        getServletContext()
                .getRequestDispatcher("/file-list.jsp")
                .forward(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response) throws ServletException, IOException {

        request.setAttribute("fileEntities", new FileService().findAll());
        
        getServletContext()
                .getRequestDispatcher("/file-list.jsp")
                .forward(request, response);
    }
}

A variável estática BASE_DIR foi atribuída com o diretório base onde cada arquivo será salvo após o upload. O método doPost() receberá o arquivo enviado através do formulário HTML. Para ter acesso ao objeto que contém o arquivo vamos usar o método getParts() do objeto request, o qual retorna uma coleção de objetos javax.servlet.http.Part. Cada um destes objetos conterá um arquivo em especifico. Observe também que no método doPost() temos uma instancia da classe FileService, a qual da acesso ao método createFolder(), Listagem 4.

Listagem 4. Método createFolder.
public Path createFolder(String baseDir) {

      Path path = Paths.get(baseDir, this.year, this.month);

      try {
          if (Files.exists(path)) {
              Files.createDirectories(path);
          }
      } catch (IOException e) {
          logger.error(e.getMessage());
      }

      return path;
}

Este método será responsável por criar o diretório onde o arquivo será armazenado, caso ele ainda não exista. Para criar os diretório vamos usar classes e métodos do pacote javax.nio. O objeto path recebe o diretório de destino por meio do método get() da classe Paths. Como atributos adicionamos o diretório base (c:\uploads) mais o ano e o mês, no final teremos algo do tipo: c:\uploads\2014\4. Porém, path vai apenas conter o diretório, para cria-lo usamos o método createDirectories()Outro método da classe FileService usado em FileUploadServlet é o saveFile(), responsável por copiar o arquivo no diretório de destino, criado pelo método createFolder().

Listagem 5. Método saveFile.
public void saveFile(Path destination, Part part) {

        String fileName = getFileName(part);
        String contentType = part.getContentType();

        try {
            part.write(destination + File.separator + fileName);
        } catch (IOException e) {
            logger.error(e.getMessage());
        }

        FileEntity fileEntity = new FileEntity();
        fileEntity.setName(fileName);
        fileEntity.setMonth(this.month);
        fileEntity.setYear(this.year);
        fileEntity.setType(contentType);

        dao.save(fileEntity);
}

Este método recebe como parâmetros dois objetos, um do tipo Path que contém o destino final de onde o arquivo será armazenado e o Part que é o objeto que contem o arquivo. A variável local fileName é atribuída com o nome do arquivo contido no objeto part. E a variável contentType é atribuída com o tipo referente ao arquivo. Para escrever o arquivo no disco rígido usamos o método write() do objeto part, passando como parâmetro o caminho e o nome do arquivo. Após escrever o arquivo em disco, vamos salvar as informações deste arquivo em um objeto da classe FileEntity para então armazena-las no banco de dados.

Na Listagem 6 é possível observar os demais métodos e atributos da classe FileService. Os atributos year e month, são inicializados na instancia da classe com o valor referente ao ano e mês que este arquivo está sendo armazenado. Para isso, usamos os métodos getYear() e getMonth() para obter o ano e o mês através da classe java.util.Calendar. Já para obter o nome do arquivo foi usado o método getFileName().

Listagem 6. Classe FileService
package com.mballem.tutorial.service;

import com.mballem.tutorial.dao.FileDao;
import com.mballem.tutorial.dao.IFileDao;
import com.mballem.tutorial.entity.FileEntity;
import org.apache.log4j.Logger;

import javax.servlet.http.Part;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Calendar;
import java.util.List;

/**
 * Created by Marcio Ballem on 15/04/2014.
 */
@SuppressWarnings("unchecked")
public class FileService {

    private static Logger logger = Logger.getLogger(FileService.class);

    private String year;
    private String month;
    private IFileDao dao;

    public FileService() {
        this.year = getYear();
        this.month = getMonth();
        this.dao = new FileDao();
    }

    public Path createFolder(String baseDir) {
        //omitido, já apresentado
    }

    public FileEntity saveFile(Path destination, Part part) {
        //omitido, já apresentado
    }

    public List<FileEntity> findAll() {
        return dao.findAll();
    }

    public FileEntity find(String year, String month, String name) {
        return dao.find(year, month, name);
    }

    private String getYear() {
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        return String.valueOf(year);
    }

    private String getMonth() {
        Calendar calendar = Calendar.getInstance();
        int month = calendar.get(Calendar.MONTH) + 1;
        return String.valueOf(month);
    }

    private String getFileName(final Part part) {
        final String partHeader = part.getHeader("content-disposition");
        logger.info("Part Header = {0} " + partHeader);
        for (String content : part.getHeader("content-disposition").split(";")) {
            if (content.trim().startsWith("filename")) {
                return content.substring(
                        content.indexOf('=') + 1).trim().replace(""", "");
            }
        }
        return null;
    }
}

Quando for necessário exibir os arquivos, ou disponibiliza-los para download, vamos usar o servlet FileLoadServlet. Esta classe possui dois métodos, o doGet() que recebe requisições do tipo GET e também o método privado showFiles(). O método doGet(), conforme Listagem 7, funciona da seguinte forma. Quando receber uma requisição com o parâmetro year com o valor null, redirecionará para a página file-list.jsp tendo como parâmetro uma lista de objetos do tipo FileEntity. O conteúdo da lista será adiciona a uma tabela na página JSP.

Listagem 7. doGet – FileLoadServlet
@Override
protected void doGet(HttpServletRequest request, 
                     HttpServletResponse response) throws ServletException, IOException {
    if (request.getParameter("year") != null) {
        showFiles(request, response);
    } else {
        request.setAttribute("fileEntities", new FileService().findAll());

        getServletContext()
                .getRequestDispatcher("/file-list.jsp")
                .forward(request, response);       
    }
}

Porém, quando o atributo year for diferente de null o método showFiles()Listagem 8 – será invocado. Este método receberá três parâmetros enviados na requisição, year, month e name. Com estes parâmetros será possível realizar uma consulta nos diretórios de armazenamento para localizar o arquivo referente as informações armazenadas no banco de dados.

Listagem 8. showFiles – FileLoadServlet
private void showFiles(HttpServletRequest request, HttpServletResponse response) {

        String year = request.getParameter("year");
        String month = request.getParameter("month");
        String name = request.getParameter("name");

        InputStream in = null;

        try {
            DirectoryStream<Path> paths =
                    Files.newDirectoryStream(
                            Paths.get(BASE_DIR, year, month)
                    );

            for (Path path : paths) {

                String filePathName = path.getFileName().toString();

                if (name.equals(filePathName)) {

                    response.setContentLength((int) Files.size(path));
                    response.setContentType(Files.probeContentType(path));

                    ServletOutputStream ouputStream = response.getOutputStream();
                    ouputStream.write(
                            Files.readAllBytes(path), 0, (int) Files.size(path)
                    );
                    ouputStream.flush();
                    ouputStream.close();
                }
            }
        } catch (FileNotFoundException e) {
            logger.error(e.getMessage());
        } catch (IOException e) {
            logger.error(e.getMessage());
        }
}

O primeiro passo é recuperar os valores da requisição e adiciona-los as variáveis locais year, month e name. Com estes valores é possível usar localizar o diretório de origem do arquivo usando as classes Files, Path. No objeto paths, do tipo java.nio.file.DirectoryStream, será armazenada uma lista contendo cada um dos arquivos naquele diretório de origem. Para recuperar o arquivo desejado, basta percorrer esta lista com um for() e comparar o nome do arquivo com o nome armazenado na variável local name. Quando o resultado for verdadeiro, adicionamos no objeto response informações que devem ser enviadas a JSP. Por fim, usamos o método getOutputStream() do objeto response para inicializar um objeto do tipo ServletOutputStream. O método write() deste objeto ServletOutputStream irá “escrever” o arquivo na página JSP.

O código HTML para o formulário de upload pode ser conferido na Listagem 9. Neste código temos dois fatores importantes, o atributo enctype deve ser adicionado com o valor multipart/form-data e a tag input que vai selecionar o arquivo deve ter a propriedade type adicionada com o valor file.

Listagem 9. Página de upload
<%@ 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>File Upload</title>
</head>
<body>
<h1>File Upload</h1>

<form method="post" action="upload" enctype="multipart/form-data">
    Select file to upload:<br/>
    <input type="file" name="file" size="60"/><br/>
    <input type="submit" value="Upload"/>
</form>

</body>
</html>

Na Listagem 10 é possível observar parte do código da página que lista os arquivos em uma tabela. Através do parâmetro do tipo de arquivo é possível definir o tipo de miniatura que será exibido na página. Assim, quando o tipo for uma imagem, a miniatura na primeira coluna da tabela será a imagem do próprio arquivo. Se não for uma imagem, a miniatura será uma imagem adicionada a aplicação, mas ao clicar sobre a miniatura, o arquivo ou será aberto em uma janela ou será feito o download para aqueles tipos de arquivos que o navegador não pode abrir.

Mas a parte mais importante neste código é a tag <img> para arquivos do tipo imagem. O atributo src desta tag é que fará uma requisição do tipo GET ao servlet FileLoadServlet, e assim, quando o arquivo for do tipo imagem, a imagem será construída na página. Já o atributo href da tag <a> gera um link que faz o download do arquivo ou abre este arquivo na janela do navegador, também usando a requisição GET de FileLoadService.

Listagem 10. Página que lista os arquivos
<h2>${requestScope.message}</h2>
<c:if test="${!empty fileEntities}">
<table>
    <tr bgcolor=#f5f5dc>
        <th align="center" width="80px">File</th>
        <th align="center" width="150px">File ContentType</th>
        <th align="center" width="150px">File Year</th>
        <th align="center" width="150px">File Month</th>
        <th align="center" width="150px">File Name</th>
    </tr>
    <c:forEach items="${fileEntities}" var="file" varStatus="id">
        <tr bgcolor="#${id.count % 2 != 0 ? 'f0f8ff' : 'ffffff' }">
            <td align="center" width="100px">
                <a href="<c:url 
                          value="load?year=${file.year}&month=${file.month}&name=${file.name}"/>">
                    <c:choose>
                        <c:when test="${file.type eq 'application/pdf'}">
                            <img width="30" height="30"
                                 src="<c:url value="/image/pdf.png"/>"
                                 title="Download - ${file.name}"
                                 border="1"/>
                        </c:when>
                        <c:when test="${file.type eq 'application/octet-stream'}">
                            <img width="30" height="30"
                                 src="<c:url value="/image/rar.jpg"/>"
                                 title="Download - ${file.name}"
                                 border="1"/>
                        </c:when>
                        <c:when test="${file.type eq 'application/msword'}">
                            <img width="30" height="30"
                                 src="<c:url value="/image/doc.jpg"/>"
                                 title="Download - ${file.name}"
                                 border="1"/>
                        </c:when>
                        <c:otherwise>
                            <img width="30" height="30"
                                 src="<c:url 
                                       value="/load?year=${file.year}&month=${file.month}&name=${file.name}"/>"
                                 title="Download - ${file.name}"
                                 border="1"/>
                        </c:otherwise>
                    </c:choose>
                </a>
            </td>
            <td align="center" width="150px"><c:out value="${file.type}"/></td>
            <td align="center" width="150px"><c:out value="${file.year}"/></td>
            <td align="center" width="150px"><c:out value="${file.month}"/></td>
            <td align="center" width="150px"><c:out value="${file.name}"/></td>
        </tr>
    </c:forEach>
</table>
</c:if>

Referências

The fileupload Example Application – http://docs.oracle.com/javaee/6/tutorial/doc/glraq.html

Ballem

Marcio Ballem é bacharel em Sistemas de Informação pelo Centro Universitário Franciscano em Santa Maria/RS. Tem experiência com desenvolvimento Delphi e Java em projetos para gestão pública e acadêmica. Possui certificação em Java, OCJP 6.