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