Upload GAE (Blobstore) entidade chegando null

Estou utilizando o vraptor 3.3.1 em meu primeiro contato com o GAE.

Preciso de uma interface para fazer upload de arquivos e segundo a documentação do GAE tenho que utilizar um serviço de blob deles o BlobstoreService. Segui conforme manda a documentação o único problema é que minha entidade chega como null no controlador apesar de o resto funcionar como deveria (o upload é feito corretamente) . Vou publicar minhas classes e formulario para ver se alguem me ajuda !!

Controlador.java

package br.com.jslsolucoes.erbuss.admin.controllers;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import br.com.caelum.vraptor.Path;
import br.com.caelum.vraptor.Post;
import br.com.caelum.vraptor.Resource;
import br.com.caelum.vraptor.Result;
import br.com.caelum.vraptor.view.Results;
import br.com.jslsolucoes.erbuss.admin.annotations.Paginated;
import br.com.jslsolucoes.erbuss.admin.annotations.PutAccessInView;
import br.com.jslsolucoes.erbuss.admin.annotations.Rules;
import br.com.jslsolucoes.erbuss.admin.annotations.VerifyAccess;
import br.com.jslsolucoes.erbuss.admin.daos.ExplicacaoDao;
import br.com.jslsolucoes.erbuss.admin.models.Explicacao;
import br.com.jslsolucoes.erbuss.admin.util.view.VraptorViewUtil;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.images.ImagesService;
import com.google.appengine.api.images.ImagesServiceFactory;

@Resource
@Path("explicacao")
public class ExplicacaoController {

	private ExplicacaoDao daoExplicacao;
	private Result result;
	private HttpServletResponse response;
	private HttpServletRequest request;
	private BlobstoreService blobstoreService;

	public ExplicacaoController(Result result, ExplicacaoDao daoExplicacao,
			HttpServletResponse response, HttpServletRequest request) {
		this.result = result;
		this.daoExplicacao = daoExplicacao;
		this.response = response;
		this.request = request;
		this.blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
	}


        //Funciona belezinha
	@Path("foto/{explicacao.id}")
	public void foto(Explicacao explicacao) throws IOException, SQLException {
		Explicacao loadExplicacao = daoExplicacao.load(explicacao.getId());
		BlobKey blobKey = new BlobKey(loadExplicacao.getUrl());
		blobstoreService.serve(blobKey, response);
		result.use(Results.nothing());
	}


	@VerifyAccess
	public void form() {
                //Aqui ele pede pra criar uma url que vai na action do formulario que sai algo do tipo "/_ah/upload/_um_hash_louco"
		this.result.include("uploadUrl",
				blobstoreService.createUploadUrl("/explicacao/saveOrUpdate"));
	}

        public void message() {

	}

	@Post
	public void saveOrUpdate(Explicacao explicacao) throws IOException, SQLException {		
		
                //Nesse ponto chega null
		System.out.println(explicacao);
		Map<String, BlobKey> blobs = blobstoreService.getUploadedBlobs(request);
		BlobKey foto = blobs.get("foto");
		if (foto != null) {
                        ImagesService imagesService = ImagesServiceFactory
					.getImagesService();
			imagesService.applyTransform(
					ImagesServiceFactory.makeResize(50, 50),
					ImagesServiceFactory.makeImageFromBlob(foto));
                        //Aqui gera um null pointer exception é claro
			explicacao.setUrl(foto.getKeyString());
		}

		result.include("operation", daoExplicacao.saveOrUpdate(explicacao2));
		
		result.redirectTo(this).message();

	}
}

E meu formulario

<form enctype="multipart/form-data" method="post" action="/_ah/upload/agxlcmJ1c3MtYWRtaW5yHAsSFV9fQmxvYlVwbG9hZFNlc3Npb25fXxipAQw" id="par_formulario">
	<input type="hidden" name="explicacao.id"/>
       Foto : <input type="file" name="foto"/>
       Título :<input type="text" name="explicacao.titulo"/>
        Descricao :<textarea name="explicacao.descricao"/>
</form>

Alguma dica ?

vc está usando o gmultipart? os dados do upload de verdade chegam completos?

se voce se refere ao “gmultipart.jar” na lib sim estou usando sim pois ele vem no blank project gae do vraptor.

Quantos aos dados de upload de verdade chegar completos ??? não entendi muito bem a pergunta…

Se for referindo ao trecho

Map<String, BlobKey> blobs = blobstoreService.getUploadedBlobs(request);  
BlobKey foto = blobs.get("foto");   

esse objeto BlobKey foto nao chega null não …chega o objeto certinho tanto é que
consigo pegar a chave gerada com foto.getKeyString(); alem do que o arquivo é salvo na pasta appengine-generated corretamente!!

Alguma ideia ?

Um sysout de request traz os seguintes dados… não sei se ajuda …

[VRaptorRequest POST /_ah/upload/agxlcmJ1c3MtYWRtaW5yHAsSFV9fQmxvYlVwbG9hZFNlc3Npb25fXxiuAQw HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-BR; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: pt-br,pt;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Referer: http://localhost:8888/explicacao/form
Cookie: JSESSIONID=1pf71fecu2i8o
Content-Type: multipart/form-data; boundary=---------------------------15009643022794
Content-Length: 75787

]

tem que ver na documentação do BlobStore se pode mandar mais dados junto com o upload, e como que se pegam esses dados…

o VRaptor pega os parâmetros direto do request, talvez isso mude no BlobStore.

Sim, é possível enviar parâmetros de request junto ao arquivo no upload, via Blobstore.

Sugestão:

Ao invés de usar a APid e Image para dar um resize na imagem durante o request, usa a API de ImageServing

Creio que o Blobstore não seja compatível com o upload do VRaptor. Isso porque o VRaptor internamente usa as libs gmultipart e commons-fileupload para upload no GAE/J.

Quando você tem um form para upload e você lê os dados, eles ficam indisponíveis para um segundo uso. Digamos que a grosso modo essas informações sejam descartáveis. Sendo assim quando o request é feito, internamente o VRaptor faz o parse do request e monta os objetos de upload via UploadedFile. Quando o Blobstore for ler os dados, os mesmos estão indisponíveis porque o VRaptor já leu. Isso não é um problema causado pelo VRaptor, pois a spec diz que é assim.

O que você pode fazer é receber o objeto UploadedFile direto no seu método e não usar o Blobstore. Ou então remover as libs commons-fileupload para desabilitar o upload interno do VRaptor, e usar apenas o Blobstore assim como você faz.

Outra sugestão é sobrescrever o CommonsUploadMultipartInterceptor sobrescrevendo o método accept para retornar sempre FALSE.

[quote=garcia-jj]Creio que o Blobstore não seja compatível com o upload do VRaptor. Isso porque o VRaptor internamente usa as libs gmultipart e commons-fileupload para upload no GAE/J.

Quando você tem um form para upload e você lê os dados, eles ficam indisponíveis para um segundo uso. Digamos que a grosso modo essas informações sejam descartáveis. Sendo assim quando o request é feito, internamente o VRaptor faz o parse do request e monta os objetos de upload via UploadedFile. Quando o Blobstore for ler os dados, os mesmos estão indisponíveis porque o VRaptor já leu. Isso não é um problema causado pelo VRaptor, pois a spec diz que é assim.

O que você pode fazer é receber o objeto UploadedFile direto no seu método e não usar o Blobstore. Ou então remover as libs commons-fileupload para desabilitar o upload interno do VRaptor, e usar apenas o Blobstore assim como você faz.

Outra sugestão é sobrescrever o CommonsUploadMultipartInterceptor sobrescrevendo o método accept para retornar sempre FALSE.[/quote]

Bom garcia dos dois modos não rolou . Retirando a commons upload ele reclama de classnotfound !!

Sobrescrevendo o CommonsUploadMultipartInterceptor continua a chegar null tb!!

Tinha resolvido de outra maneira. Em vez de utilizar serviço de diretório blobstoreservice estou gravando direto em uma coluna de blob do gae e parece funiconar so que me deparei com um problema com arquivos maior que 1mb ou seja voltei a estaca zero , já que a regra de negocio os arquivos podem ser musicas , videos etc . E para isso o gae pelo jeito obriga a utilizar o blobstoreservice!!

Estava procurando no forum e achei uma duvda semelhante que diz o cara ter conseguido resolver

http://www.guj.com.br/java/221168-resolvido---gae-blobstore--vraptor-/2

mas ele falou que conseguiu pegar os dados por request mesmo , coisa que eu nao consigo fazer ja que nao chega parametro algum em meu request !!!

Parece que o blobstore trata o upload e tenta mandar pra frente seguindo o que passei no createUploadUrl so que sem os dados de request original !!!

A documentação relativa a blobstore service está aqui

http://code.google.com/intl/pt-BR/appengine/docs/java/blobstore/overview.html

Tem como eu fala pro vraptor ignorar uma url especifica exemplo “/explicacao/saveOrUpdate” no web.xml e fazer uma servlet como no exemplo de implementação acima … dai trato os dados de request diretamente lá…

seria um teste a se fazer…

Se tem algum exemplo de como pegar os dados de request no upload rafael , pois não vi metodos em blobstoreservice que faça isso!!

Garcia eu sobrescrevi a classe CommonsUploadMultipartInterceptor mas mesmo assim no debug ela continua a ser chamada , eu sobrescrevi certo ?

22:26:28,126 INFO [br.com.caelum.vraptor.interceptor.multipart.CommonsUploadMultipartInterceptor] - Request contains multipart data. Try to parse with commons-upload.

@Component
public class CustomCommonsUploadMultipartInterceptor extends
		CommonsUploadMultipartInterceptor {

	public CustomCommonsUploadMultipartInterceptor(HttpServletRequest request,
			MutableRequest parameters, MultipartConfig cfg,
			Validator validator, ServletFileUploadCreator fileUploadCreator) {
		super(request, parameters, cfg, validator, fileUploadCreator);
	}

	@Override
	public boolean accepts(ResourceMethod method) {
		return false;
	}

}

sobrescrevi de maneira certa ?

isso não vai funcionar, tente apenas remover o gmultipart.jar e o commons-fileupload.jar do WEB-INF/lib

Se eu retiro esses jar do path acontece um classnotfound exception

14/03/2011 22:39:52 com.google.apphosting.utils.jetty.JettyLogger warn
AVISO: failed vraptor: java.lang.NoClassDefFoundError: org/apache/commons/fileupload/FileUploadBase$SizeLimitExceededException
14/03/2011 22:39:52 com.google.apphosting.utils.jetty.JettyLogger warn
AVISO: failed com.google.apphosting.utils.jetty.DevAppEngineWebAppContext@e06940{/,D:\www\erbuss-gae-admin\WebContent}: java.lang.NoClassDefFoundError: org/apache/commons/fileupload/FileUploadBase$SizeLimitExceededException
14/03/2011 22:39:52 com.google.apphosting.utils.jetty.JettyLogger warn
AVISO: failed JettyContainerService$ApiProxyHandler@e576d4: java.lang.NoClassDefFoundError: org/apache/commons/fileupload/FileUploadBase$SizeLimitExceededException
14/03/2011 22:39:52 com.google.apphosting.utils.jetty.JettyLogger warn
AVISO: Error starting handlers
java.lang.NoClassDefFoundError: org/apache/commons/fileupload/FileUploadBase$SizeLimitExceededException
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:169)
	at br.com.caelum.vraptor.scan.DynamicWebAppBootstrap.configure(DynamicWebAppBootstrap.java:45)
	at br.com.caelum.vraptor.ioc.spring.SpringProvider.start(SpringProvider.java:84)
	at br.com.caelum.vraptor.VRaptor.init(VRaptor.java:110)
	at br.com.caelum.vraptor.VRaptor.init(VRaptor.java:103)
	at org.mortbay.jetty.servlet.FilterHolder.doStart(FilterHolder.java:97)
	at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
	at org.mortbay.jetty.servlet.ServletHandler.initialize(ServletHandler.java:662)
	at org.mortbay.jetty.servlet.Context.startContext(Context.java:140)
	at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1250)
	at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:517)
	at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:467)
	at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
	at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
	at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
	at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
	at org.mortbay.jetty.Server.doStart(Server.java:224)
	at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
	at com.google.appengine.tools.development.JettyContainerService.startContainer(JettyContainerService.java:186)
	at com.google.appengine.tools.development.AbstractContainerService.startup(AbstractContainerService.java:149)
	at com.google.appengine.tools.development.DevAppServerImpl.start(DevAppServerImpl.java:219)
	at com.google.appengine.tools.development.DevAppServerMain$StartAction.apply(DevAppServerMain.java:164)
	at com.google.appengine.tools.util.Parser$ParseResult.applyArgs(Parser.java:48)
	at com.google.appengine.tools.development.DevAppServerMain.<init>(DevAppServerMain.java:113)
	at com.google.appengine.tools.development.DevAppServerMain.main(DevAppServerMain.java:89)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException
	at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
	at com.google.appengine.tools.development.IsolatedAppClassLoader.loadClass(IsolatedAppClassLoader.java:176)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
	... 26 more
14/03/2011 22:39:52 com.google.apphosting.utils.jetty.JettyLogger info
INFO: Started SelectChannelConnector@127.0.0.1:8888
14/03/2011 22:39:52 com.google.appengine.tools.development.DevAppServerImpl start
INFO: The server is running at http://localhost:8888/

Lucas, porque não funcionaria? Anotando com @Intercepts ao invés de @Component deveria, não?

Eu criar um projeto blank aqui para ver como funciona o blobstore.

não funciona pq o plugin do gae já sobrescreve o CommonsUploadMultipartInterceptor, não dá pra sobrescrever duas vezes =(

o que dá pra fazer é abrir o vraptor-plugin-gae.jar e remover o AppEngineMultipartInterceptor.class, e numa próxima versão ter uma forma de desabilitá-lo facilmente

Ahh, é verdade, Lucas. Bem lembrado.

O blobstore está funcionando, quem não funciona é injetar o objeto Explicacao. Isso porque quando a requisição é multipart, o vraptor injeta os dados via commons-fileupload. Porém não há nada no fileupload, já que o blobstore faz isso usando request.getParameter. Creio que tenha que fazer outra abordagem para usar o blobstore.

[quote=garcia-jj]Ahh, é verdade, Lucas. Bem lembrado.

O blobstore está funcionando, quem não funciona é injetar o objeto Explicacao. Isso porque quando a requisição é multipart, o vraptor injeta os dados via commons-fileupload. Porém não há nada no fileupload, já que o blobstore faz isso usando request.getParameter. Creio que tenha que fazer outra abordagem para usar o blobstore.[/quote]

Lucas , Garcia , alguma ideia de como poderia fazer isso funcionar ??

abra o vraptor-plugin-gae.jar (como se fosse um .zip) e remova a classe AppEngineMultipartInterceptor

O BlobStore não é um upload normal que possa ser tratado pelo VRaptor, é um serviço direto na API do GAE.

Você precisa usar a classe BlobstoreService diretamente e do jeito que tá na documentação. Isto é, precisar chamar o createUploadUrl para colocar no seu form e deixar o GAE receber o upload. Aí depois disso ele te redireciona para um página sua onde você consegue pegar o BlobKey gerado por ele e guardar em algum lugar se for o caso. Mas todo o processo de upload é pelo GAE, não o VRaptor.

E uso o Blobstore sem problemas num projeto aqui no VRaptor, sem hack nenhum, mas usando a API dele diretamente.

Abraços

Removi esse interceptor do jar e tb removi a commons-file-upload e gmultipart , o server sobe corretamente e no console fala que a commons upload nao esta no path
mas continua a chegar null na entidade!!

[quote=Sergio Lopes]O BlobStore não é um upload normal que possa ser tratado pelo VRaptor, é um serviço direto na API do GAE.

Você precisa usar a classe BlobstoreService diretamente e do jeito que tá na documentação. Isto é, precisar chamar o createUploadUrl para colocar no seu form e deixar o GAE receber o upload. Aí depois disso ele te redireciona para um página sua onde você consegue pegar o BlobKey gerado por ele e guardar em algum lugar se for o caso. Mas todo o processo de upload é pelo GAE, não o VRaptor.

E uso o Blobstore sem problemas num projeto aqui no VRaptor, sem hack nenhum, mas usando a API dele diretamente.

Abraços[/quote]

Poderia postar seu form e seu controlador para mim ??