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.
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).
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.
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 FileUploadServlet
– Listagem 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.
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.
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()
.
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()
.
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.
@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.
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
.
<%@ 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
.
<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