Programador ajudante e aprendiz da comunidade open source.

Upload e Download com VRaptor 3

É 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

  1. Guilherme 9 Jun 2014 15:41

    Muito bom o tutorial.
    Apenas uma sugestão. Sou iniciante em VRaptor e estava recebendo null no uploadfile do controller. Levei um bom tempo até descobrir que meu pom.xml precisava estar com a dependência commons-fileupload. Tvz seja interessante citar isso no tutorial.

    []'s

  2. Ricardo 3 Out 2012 15:44

    Ótimo post.. agora só uma sugestão.. poderia explorar mais os verbos http no controle ext.. Put para editar, Post incluir, Delete deletar..
    Abraço..

    1. Washington Botelho autor 3 Out 2012 16:16

      Oi Ricardo,

      Poderia sim, porém foi feito com get, para utilizarmos um link em vez de um formulário com um campo hidden/_method, facilitando assim, a didática. Já que os verbos foram introduzidos no primeiro post sobre o VRaptor.

  3. Glesio Santos da Silva 28 Mai 2012 14:06

    Botelhos, estou acompanhando a revista da Java Magazine o tutorial do Vraptor 3, fiquei parado no tutorial da Upload de imagens, a minha aplicação roda que uma beleza mais não consigo criar o caminho para Upload e Download da aplicação principalmente na parte IMAGE_PATH teria com me ajudar?

    O problema todo e como criar esta pasta e meu servidor encontrar este caminho

    1. Washington Botelho autor 29 Mai 2012 10:01

      Oi Glesio,

      Você terá que descobrir o caminho completo de alguma pasta na qual você tem permissão de escrita.
      Algo como /home/glesio/www/public e então criar as pastas lá e apontar esse caminho no seu código.

  4. Flavio Duarte 20 Ago 2010 08:43

    Oi Washington, você já encontrou uma solução para gerar as thumbnails?

    eu tentei esse código seu só que uma imagem de qualidade boa quando diz a thumb ela ficou com uma qualidade bem ruim

    1. Washington Botelho autor 26 Ago 2010 14:15

      Ainda não encontrei Flávio, mas assim que eu conseguir um bom código, lhe aviso. :)

  5. Rodrigo 30 Jul 2010 10:24

    E ae cara, muito bom post!

    Só uma duvida.... como vc faz pra redimensionar uma imagem, pq se o cara mandar uma foto de 3 mb, fica pesado pra ser avatar....

    abs

    1. Washington Botelho autor 30 Jul 2010 10:48

      Rodrigo,

      Eu ainda não achei uma forma de redimensionar que não deforme a imagem a ponto de usá-la perfeitamente, assim como manter imagens com fundo transparente.
      Hoje uso este método no Movie Collection, mas ainda preciso pesquisar uma melhor forma.

      public static void redimensionar(String imagemInPath, String imagemOutPath, String extensao, int largura, int altura) {
        try {
        BufferedImage imagemIn = ImageIO.read(new File(imagemInPath));
        BufferedImage imagemOut = new BufferedImage(largura, altura, BufferedImage.TYPE_INT_RGB);
      
        Graphics2D graph = imagemOut.createGraphics();
        graph.drawImage(imagemIn, 0, 0, largura, altura, null);
        ImageIO.write(imagemOut, extensao, new File(imagemOutPath));
        } catch (IOException e) {
        System.out.println("[Util.redimensionar] Erro: " + e.getMessage());
        e.printStackTrace();
        }
      }
      
  6. Rodrigo Bisterço 30 Jul 2010 09:02

    Washington, resolvido.

    Estou usando o Windows 7 e a aplicação tentava acessar o caminho C:/Users/rodrigob/img/1.jpg, acontece que eu não tinha o diretório img. Criei o diretório e funcionou.

    Valeu.

  7. Rodrigo Bisterço 29 Jul 2010 18:56

    Washington,

    Mais uma vez parabéns pelo post !!!
    Estou com algum problema ... toda imagem que tento fazer o upload ele me apresenta a mensagem "Arquivo não encontrado.". O que pode estar errado?

    Um grande abraço

    1. Washington Botelho autor 29 Jul 2010 20:08

      Fala Rodrigo,

      Provavelmente o que não esta sendo encontrado é o caminho de saída da imagem.
      Divida o código em passos menores e crie as variávels fileOut e streamOut para que você possa debugar e verificar em qual parte esta o erro:

        File fileOut = new File(PATH_FOTO, user.getFoto());
        FileOutputStream streamOut = new FileOutputStream(streamOut);
        IOUtils.copy(imagem.getFile(), fileOut);
      

      Se a sua variável PATH_FOTO estiver incorreta, o caminho de saída não será encontrado.
      Ex.: (/User/wbotelhos/inexistente)(/)(1.jpg)

      Copie o caminho montado do diretório, cole no seu browser e veja se ele é encontrado.

      E obrigado pelos parabéns. (:

Em resposta:
(cancelar)
Formate seu código utilizando Markdown.