Capítulo 15

Apêndice - Download e Upload

Nesse capítulo, vamos adicionar imagens aos produtos, para uma melhor visualização na listagem. Para isso, precisamos fazer o upload dessas imagens para o servidor, e depois fazer o download para mostrá-las na listagem.

15.1 - Exercícios

  1. Modifique a página de edição de Produtos (/WEB-INF/jsp/produtos/edita.jsp), para incluir um formulário de Upload de imagem. Esse formulário vai submeter para uma lógica nova que vamos criar a seguir.

    <!--...-->
    <form action=
      "<c:url value="/produtos/${produto.id }/imagem"/>" 
      method="POST" enctype="multipart/form-data">
      <fieldset>
        <legend>Upload de Imagem</legend>
        <input type="file" name="imagem" />
        
        <button type="submit">Enviar</button>
      </fieldset>
    </form>
    
  2. Crie o Controller abaixo, que vai tratar dos uploads e downloads de imagens.

    package br.com.caelum.goodbuy.controller;
    
    import br.com.caelum.vraptor.interceptor.multipart.UploadedFile;
    
    @Resource
    public class ImagensController {
    
      @Post("/produtos/{produto.id}/imagem")
      public void upload(Produto produto, UploadedFile imagem) {
    
      }
    }
    
  3. Só estamos interessados em fazer uploads de imagens. Então vamos validar se o upload foi uma imagem mesmo. O ContentType de imagens começa com image:

    @Post("/produtos/{produto.id}/imagem")
    public void upload(Produto produto, 
        final UploadedFile imagem) {
      validator.checking(new Validations() {{
        if (that(imagem, is(notNullValue()), "imagem",
            "imagem.nula")) {
          that(imagem.getContentType(), 
              startsWith("image"), "imagem", "nao.eh.imagem");
        }
      }});
      validator.onErrorRedirectTo(ProdutosController.class)
          .edita(produto.getId());
    }
    
  4. Coloque as mensagens internacionalizadas no messages.properties:

    imagem.nula = Digite uma imagem
    nao.eh.imagem = Selecione uma imagem
    
  5. Crie o componente do VRaptor que será responsável por guardar as imagens no servidor. Esse componente vai criar uma pasta fixa onde todas as imagens serão guardadas. Para isso, precisamos conseguir o caminho de uma pasta do servidor, e a classe que consegue nos dar essa informação é o ServletContext.

    package br.com.caelum.goodbuy.imagens;
    
    import java.io.File;
    import javax.servlet.ServletContext;
    import br.com.caelum.vraptor.ioc.Component;
    
    @Component
    public class Imagens {
    
      private File pastaImagens;
    
      public Imagens(ServletContext context) {
        String caminhoImagens = context.getRealPath("/WEB-INF/imagens");
        pastaImagens = new File(caminhoImagens);
        pastaImagens.mkdir();
      }
    }
    
  6. Crie um método que salve a imagem do upload na pasta padrão. Colocaremos uma extensão fixa para facilitar. O IOUtils vem do projeto commons-io e copia um InputStream para algum OutputStream

    package br.com.caelum.goodbuy.imagens;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    import org.apache.commons.io.IOUtils;
    
    @Component
    public class Imagens {
    
      //...
      public void salva(UploadedFile imagem, Produto produto) {
        File destino = new File(pastaImagens, produto.getId() + ".imagem");
    
        try {
          IOUtils.copy(imagem.getFile(), new FileOutputStream(destino));
        } catch (IOException e) {
          throw new RuntimeException("Erro ao copiar imagem", e);
        }
      }
    }
    
  7. Use a classe Imagens para salvar a imagem que veio no upload. E a classe Result para voltar para o formulario de edição.

    package br.com.caelum.goodbuy.controller;
    
    @Resource
    public class ImagensController {
    
      private final Validator validator;
      private final Imagens imagens;
      private final Result result;
    
      public ImagensController(Validator validator, 
          Imagens imagens, Result result) {
        this.validator = validator;
        this.imagens = imagens;
        this.result = result;
      }
    
      @Post("/produtos/{produto.id}/imagem")
      public void upload(Produto produto, 
          final UploadedFile imagem) {
        validator.checking(new Validations() {{
          if (that(imagem, is(notNullValue()), "imagem",
              "imagem.nula")) {
            that(imagem.getContentType(), 
                startsWith("image"), "imagem", "nao.eh.imagem");
          }
        }});
        validator
            .onErrorRedirectTo(ProdutosController.class)
            .edita(produto.getId());
        
        imagens.salva(imagem, produto);
        result.redirectTo(ProdutosController.class)
            .edita(produto.getId());
      }
    }
    
  8. Crie a lógica para fazer o download da imagem no ImagensController:

    @Get("/produtos/{produto.id}/imagem")
    public File download(Produto produto) {
      return imagens.mostra(produto);
    }
    
  9. Crie o método mostra na classe Imagens:

    public File mostra(Produto produto) {
      return new File(pastaImagens, produto.getId() + ".imagem");
    }
    
  10. Mude o formulário de edição para incluir a imagem do produto. Repare que estamos usando nosso controller para mostrar a imagem. Coloque essa imagem na listagem de produtos também.

    <img src="<c:url value="/produtos/${produto.id}/imagem"/>"
        width="100" height="100"/>