É normal nos cadastrarmos em um sistema e lá ter uma opção de enviar sua própria foto. Outra opção mais do que usada é o simples download de algum arquivo. Iremos ver como o VRaptor nos auxiliam nessas tarefas e nos poupam um bocado de código e tempo.
Objetivo:
Criar uma funcionalidade na qual o usuário pode enviar uma foto para o sistema como seu avatar e logo em seguida fazer a apresentação desta imagem na tela.
Criando o usuário (Usuario.java):
public class Usuario {
private Integer id;
private String foto;
// get and set...
}
Precisamos do ID do usuário para ser usado como identificador na foto e um campo String para mantermos o nome da foto.
Criando a sessão do usuário (UserSession.java):
@Component
@SessionScoped
public class UserSession {
private Usuario user;
// getters and setters...
}
Para trabalharmos com o avatar do usuário logado, precisamos ter este usuário em sessão, logo criamos tal componente que é SessionScoped
.
Criando a classe de negócios (UsuarioBusiness.java):
Salvando a imagem do usuário:
public void salvarFoto(UploadedFile imagem) throws IOException {
Usuario user = userSession.getUser();
if (imagem != null) {
String fileName = imagem.getFileName();
int start = fileName.lastIndexOf(".");
String extensao = (start > 0) ? fileName.substring(start) : ".jpg";
user.setFoto(user.getId() + extensao);
try {
IOUtils.copy(imagem.getFile(), new FileOutputStream(new File(PATH_FOTO, user.getFoto())));
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new FileNotFoundException("Arquivo não encontrado!");
} catch (IOException e) {
e.printStackTrace();
throw new IOException("Não foi possível enviar o arquivo!");
}
}
this.atualizarFoto(user);
userSession.setUser(user);
}
Linha 5-10: É recuperado o nome do arquivo e em seguida sua extensão que é concatenado com o ID do usuário. Depois é colocado de volta este novo nome na entidade para que seja persistida.
Usamos o ID do usuário como o nome do avatar porque o mesmo não se repete e facilita para localização.
Linha 13:
É utilizado o método copy
da biblioteca commons-io da Apache para pegar a stream de entrada da imagem e jogar para uma stream de saída em um local físico.
Armazene as imagens em uma pasta fora da sua aplicação, pois assim você evita ter que fazer backup a cada redeploy, já que isto apaga todo conteúdo da sua aplicação.
Linha 23-24: É feita a atualização dessa imagem no banco e reposto o usuário na sessão.
Removendo a imagem do usuário:
public void removerFoto() {
Usuario user = userSession.getUser();
if (user.getFoto() != null && !user.getFoto().isEmpty() && !user.getFoto().equals("default.jpg")) {
File file = new File(PATH_FOTO, user.getFoto());
if (file.exists()) {
file.delete();
}
}
user.setFoto("default.jpg");
this.trocarFoto(user);
userSession.setUser(user);
}
Linha 4: É verificado se o usuário esta usando uma imagem e se esta não é a imagem padrão (default.jpg).
É importante verificar se o usuário não esta usando a imagem padrão, pois se esta for removida todos os usuários com imagem padrão serão prejudicados, já que todos apontam para a única e mesma imagem.
Linha 5-9: É recuperado a imagem e verificado se a imagem existe, se sim, a mesma é removida.
Linha 12: Como não deixamos o usuário sem foto, é setado a imagem padrão para o mesmo.
Linha 14-15: É feito a atualização do nome da nova imagem no banco e reposto o usuário na sessão.
Método usado para o download:
public File downloadFoto() {
File file = new File(PATH_FOTO, userSession.getUser().getFoto());
return (file.exists()) ? file : new File(PATH_FOTO + "/default.jpg");
}
Procuramos a imagem do usuário, se ela existir é retornada, senão retornamos a imagem padrão.
Criando o controlador (UsuarioController.java):
Recebendo a imagem do usuário:
@Post
@Path("/usuario/foto")
public void atualizarFoto(UploadedFile imagem) {
try {
business.atualizarFoto(imagem);
} catch (IOException e) { // Sua exception...
result.include("error", e.getMessage()).forwardTo(this).upload();
}
result.redirectTo(this).upload();
}
Nosso método espera como argumento um UploadedFile
que, de fato, é a imagem enviada pelo usuário através do formulário. E então a passamos para a nossa classe de negócios salvá-la em disco.
Ahm? E cadê o servlet com meus objetos ServletFileUpload
, DiskFileItemFactory
e etc para fazer esse upload?
Esquece isso, o VRaptor já tem tudo “mastigado” na classe UploadedFile
que intercepta o envio do formulário com a imagem e já faz isso tudo por você. Basta manipulá-la como uma stream. (:
Estou ouvindo você falar: “Maldito servlet de upload que eu fazia na unha.”
Removendo a imagem do usuário:
@Get
@Path("/usuario/foto/remover")
public void atualizarFoto() {
business.removerFoto();
result.redirectTo(this).upload();
}
O método para remover a imagem simplesmente chama o nosso método já construido na nossa classe de negócios.
Fazendo o download da imagem:
@Get
@Path("/usuario/foto")
public File downloadFoto() {
return business.downloadFoto();
}
No download temos um outra facilidade, já que precisamos apenas de retorna um File
que o VRaptor já repassa para a view que iremos ver como irá ficar a seguir.
Criando a página de envio da foto (upload.jsp):
Para apresentar nossa foto na view, iremos fazer algo que até o dia em que o Makoto me falou eu não sabia.
Apresentando a imagem do download:
<a href="${pageContext.request.contextPath}/usuario/foto">
<img src="${pageContext.request.contextPath}/usuario/foto" border="0"/>
</a>
Adicionamos diretamente como atributo a URL do nosso controller que nos retorna um File
, se lembra? Então você me pergunta: “tá, eu tenho um File
e não o caminho da imagem”.
O caminho direto não importa, sua aplicação não achará nenhum caminho fora dela mesmo, então fazemos um download e recuperamos essa imagem como uma stream, e esta pode ser setada diretamente no src
do componente da imagem. :o
Se você não quiser fazer download da imagem terá de deixá-la dentro da sua aplicação.
Upload da imagem:
<form action="${pageContext.request.contextPath}/usuario/foto" enctype="multipart/form-data" method="post">
Imagem: <input type="file" name="imagem"/> <input type="submit"/>
</form>
Nosso formulário submete a foto para a URL do método que a atualiza em nosso controller via POST
. Precisamos de habilitar o envio de “anexo” do nosso formulário com o atributo enctype
como multipart/form-data
. Não se esqueça!
Remoção da imagem:
<a href="${pageContext.request.contextPath}/usuario/foto/remover">Remover</a><br/>
Para remover a imagem basta chamar o link de remoção e o usuário passará a ter a foto padrão.
Agora que você já sabe fazer upload e download de arquivos, irei listar algumas melhorias que eu costumo fazer em minhas aplicações que não cabe mostrar aqui neste artigo, mas que poderia ser uma segunda parte no futuro:
- Fazer o upload e download via ajax usando o jQuery Form;
- Validar a extensão ainda na view com o jQuery Validation;
- Validar a extensão também server side;
- Aumentar ou diminuir o limite do upload;
- Retorna uma imagem de erro caso ocorra uma exception;
- Integrar com o Gravatar.
Link do projeto: http://github.com/wbotelhos/upload-e-download-com-vraptor-3