25 novembro 2010

Appengine - Armazenando arquivos no Datastore

Já faz algum tempo em que eu tenho utilizado o Google App Engine para desenvolver meus projetos pessoais em Java, que atualmente o maior é o e-Karros. Apesar dos vários benefícios da plataforma, existem alguns pontos que trazem dificuldades ao desenvolvimento. A dificuldade mais recente que encontrei foi o armazenamento de arquivos no Datastore do App Engine. No meu caso específico foi o armazenamento de imagens. Após um bom tempo de pesquisa na net encontrei a solução abaixo.

Para podermos armazenar arquivos precisamos utilizar o tipo de dado Blob, através da API Blobstore. Bom, em primeiro lugar temos que criar nossa classe que irá armazenar o arquivo, que neste caso pode ser uma imagem:
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Blob;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Imagem {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @Persistent
    private Blob imagem;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setImagem(Blob imagem) {
        this.imagem = imagem;
    }

    public Blob getImagem() {
        return imagem;
    }
       
    public void adiciona() throws Exception {
        new ImagemDAO().adiciona(this);
    }

    public ImagemCarro buscaImagem(Long id) {
        return new ImagemDAO().buscaImagem(id);
    }
}

Vamos supor que nossa classe ImagemDAO já esteja implementada.

Após isso temos nossa classe Servlet que irá receber o arquivo que foi feito upload, tratá-lo e chamar o método de gravação. Para se "caputrar" o arquivo e converter para o formato da classe Blob (array de bytes), precisando da biblioteca Commons IOUtils, da Apache.

import org.apache.commons.io.IOUtils;

public class ArmazenaImagem extends HttpServlet {
 
    public void doPost(HttpServletRequest req, HttpServletResponse resp) {
        try {
            InputStream imgStream = req.getInputStream();
            Blob imageBlob = new Blob(IOUtils.toByteArray(imgStream));

            Imagem img = new Imagem();
            img.setImagem(imageBlob);

            img.adiciona();

            resp.setStatus(HttpServletResponse.SC_OK);
            resp.setContentType("text/plain");
            resp.getWriter().println( "{success: true}" );

        } catch (Exception e {
            e.printStackTrace();
            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            resp.setContentType("text/plain");
            resp.getWriter().println( "{success: false}" );
        }
    }
}
Como podemos ver, é bem simples gravar um arquivo no Datastore.

Aqui uma pausa para explicar a linha resp.getWriter().println( "{success: true}" ); e seu equivalente no bloco catch. É um requerimento da plugin JavaScript Ajax Upload, o melhor que encontrei para esse trabalho ;-). A utilização de um plugin para upload foi a única forma que eu encontrei para enviar o arquivo ao servidor utilizando AJAX, porém ainda estou a procura de uma outra forma.

A recuperação da imagem é igualmente simples. Abaixo segue o servlet:
public class BuscaIMagemServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp) {
        try {
            resp.setContentType("image/jpeg");
            resp.setHeader("Content-Disposition","inline");
   
            Long id = Long.parseLong( req.getParameter( "id" ) );
            Imagem imagem = new Imagem().buscaImagem(ido);
   
            Blob blobImagem = imagem.getImagem();
            byte[] btImg = blobImagem.getBytes();
   
            resp.getOutputStream().write(btImg);
   
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
É somente isso. Ao recuperarmos a imagem do Datastore precisamos transformá-la em um array de bytes.

Nesse caso usei o campo id para recuperar uma imagem específica. Para exibir a imagem na página precisamos inserir a tag img em nossa página:
Sem Imagem
Onde '/buscaImagem?id=1' é o nosso servlet que recupera a imagem, e id=1 é o parâmetro para buscarmos uma imagem específica (o número 1 q coloquei é só um exemplo).

Espero que este breve tutorial sirva para que outras pessoas não fiquem batendo tanta cabeça quanto eu ;-).

5 comentários:

Gustavo disse...

Parabéns, muito bom.
Meu problema atual era somente a recuperação do arquivo na Nuvem.
Estava convertendo errado o tipo Blob.
Muito obrigado, ótimo post ;)

Tiago dos Santos disse...

Gustavo, obrigado pelo comentário. É bom saber que o blog está sendo útil.

Gustavo disse...

Opa Tiago,
você pode me ajudar?
Estou com um problema...
Estou tentando listar vários arquivos que realizei upload no app engine...
Só que quando chamo o list no jsp... e tento lista-los
aparece o seguinte erro:

No meta data for modelo.Arquivo. Perhaps you need to run the enhancer on this class?

Tiago dos Santos disse...

Gustavo, essa mensagem eu nunca vi, mas se poder, me envie mais detalhes por e-mail (tiago_stos(at)yahoo . com . br).

Gustavo disse...

Opa Tiago,
Consegui resolver, este erro estava aparecendo porque não utilizei um modelo na classe arquivo. @PersistenceCapable.
Mais obrigado pela atenção, abraço.