vraptor3: Cannot forward after response has been committed [RESOLVIDO]

6 respostas
hyr4x

Olá Pessoal, estou tendo mais um problema.

Possuo um resource VendaController que está no escopo da sessão. Este recurso é responsável pela lógica da venda de produtos que é dada por algumas etapas. Em um determinado momento, o usuário deve selecionar um cliente e em seguida será encaminhado para tela de seleção de produtos que serão vendidos. Esta tela é composta por dois forms. Um form para adicionar os itens na venda e o outro form leva o usuário para seleção da forma de pagamento.

Meu problema está na adição dos itens na venda. A ideia é: toda vez que o usuário clicar no botão “Adicionar item”, um novo item é criado, passado para o servidor, que por sua vez, o insere numa coleção de itens e em seguida, volta para tela de seleção de itens para que o usuário possa adicionar novos itens conforme ilustra o código abaixo, no método adicionaItem.

@Resource
@SessionScoped
public class VendaController {

	private final DAOFactory daoFactory;
	private final Result result;

	/* propriedades da venda */
	private Venda venda;
	private Set<Item> itens;
	private Cliente cliente;
	private Pagamento pagamento;

	public VendaController(Result result, DAOFactory daoFactory) {
		this.result = result;
		this.daoFactory = daoFactory;
	}

	@Post
	public void salva() {
		this.venda.setData(new Date());
		this.venda.setPagamento(this.pagamento);
		this.venda.setItens(this.itens);
		this.venda.setFuncionario(this.daoFactory.getFuncionarioDao().get(1001L));

		this.daoFactory.getVendaDao().saveOrUpdate(this.venda);

		this.result.use(Results.logic()).forwardTo(VendaController.class).formulario();
	}

	@Get
	public void formulario() {
		this.venda = new Venda();
		this.itens = new HashSet<Item>();
		this.pagamento = new Pagamento();
		this.cliente = null;

		this.result.include("clientes", this.daoFactory.getClienteDao().lista());
	}

	@Get
	public void selecaoProduto(Cliente cliente) {
		if (cliente != null) {
			this.cliente = this.daoFactory.getClienteDao().get(cliente.getId());
		}

		if (this.cliente != null) {
			this.result.include("nomeCliente", this.cliente.getNome());
			this.venda.setCliente(this.cliente);
		}

		this.result.include("itens", this.itens);
		this.result.include("produtos", this.daoFactory.getProdutoDao().lista());
	}

	@Post
	public void adicionaItem(Item item) {
		if (item != null) {
			item.setVenda(this.venda);
			item.setProduto(this.daoFactory.getProdutoDao().get(item.getProduto().getId()));
			this.itens.add(item);
		}

		this.result.use(Results.logic()).forwardTo(VendaController.class).selecaoProduto(null);
	}

	@Post
	public void selecaoPagamento(Pagamento pagamento) {
		this.result.include("formaPagamento", TipoPagamento.values());
		this.result.include("venda", this.venda);
	}

}

O código funciona aparentemente bem. Ou seja, toda vez que o usuário adiciona um novo item na coleção, esta última é atualizada e exibida na tela de seleção de produtos. Entretanto, lança a seguinte exceção:

java.lang.IllegalStateException: Cannot forward after response has been committed
	at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:312)
	at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:302)
	at br.com.caelum.vraptor.view.DefaultPageResult.forward(DefaultPageResult.java:75)
	at br.com.caelum.vraptor.extra.ForwardToDefaultViewInterceptor.intercept(ForwardToDefaultViewInterceptor.java:64)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:57)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
	at br.com.caelum.vraptor.interceptor.download.DownloadInterceptor.intercept(DownloadInterceptor.java:67)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:57)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
	at br.com.caelum.vraptor.interceptor.OutjectResult.intercept(OutjectResult.java:67)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:57)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
	at br.com.caelum.vraptor.interceptor.ExecuteMethodInterceptor.intercept(ExecuteMethodInterceptor.java:65)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:57)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
	at br.com.caelum.vraptor.interceptor.ParametersInstantiatorInterceptor.intercept(ParametersInstantiatorInterceptor.java:95)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:57)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
	at br.com.caelum.vraptor.interceptor.InstantiateInterceptor.intercept(InstantiateInterceptor.java:54)
	at br.com.caelum.vraptor.core.InstantiatedInterceptorHandler.execute(InstantiatedInterceptorHandler.java:51)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
	at br.com.caelum.vraptor.interceptor.multipart.MultipartInterceptor.intercept(MultipartInterceptor.java:58)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:57)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
	at br.com.sudipel.interceptor.JPAInterceptor.intercept(JPAInterceptor.java:23)
	at br.com.caelum.vraptor.core.InstantiatedInterceptorHandler.execute(InstantiatedInterceptorHandler.java:51)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
	at br.com.caelum.vraptor.interceptor.InterceptorListPriorToExecutionExtractor.intercept(InterceptorListPriorToExecutionExtractor.java:58)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:57)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
	at br.com.caelum.vraptor.core.URLParameterExtractorInterceptor.intercept(URLParameterExtractorInterceptor.java:45)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:57)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
	at br.com.caelum.vraptor.interceptor.ResourceLookupInterceptor.intercept(ResourceLookupInterceptor.java:70)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:57)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:71)
	at br.com.caelum.vraptor.core.DefaultRequestExecution.execute(DefaultRequestExecution.java:71)
	at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:99)
	at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:37)
	at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:97)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
	at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
	at java.lang.Thread.run(Unknown Source)

Alguém pode me dizer o que está errado?

Mais uma vez agradeço a ajuda de vocês.

6 Respostas

Paulo_Silveira

oi hyr4x

esta usando a ultima versao do beta do vraptor?

Adriano_Almeida

oi hyr4x, td bom?

O problema é que o response ja foi utilizado na primeira requisicao, mas como o seu @Resource está como @SessionScoped, o response só pode ser passado esse @Resource uma unica vez, aí na segunda vez que vc chama esse controller eh utilizado o mesmo response, que por sua vez já está num estado inválido (pq foi utilizado antes).

Uma sugestão é ao invés de vc colocar o Resource na sessão, coloque apenas os dados (objetos) que vc precisa na sessão e dessa forma vc não terá problemas.

Vou criar uma issue lá no git do vraptor pra dar um warning quando usa @SessionScoped ou @ApplicationScoped em alguem que utilize o Response ou o Request.

Qualquer dúvida poste aí,

[]'s

hyr4x

Sim! Estou utilizando a versão beta5.

Entendi o que você me disse, mas não sei como fazer. Você sugere que eu anote meu bean Venda com @SessionScoped? No caso seria:

@SessionScoped
@Entity
public class Venda extends Persistivel {

	private static final long serialVersionUID = -1337026127756372244L;

	@OneToMany(cascade = CascadeType.ALL, mappedBy = "venda")
	private Set<Item> itens = new HashSet<Item>();

	@ManyToOne(cascade = CascadeType.ALL)
	@JoinColumn(name = "cliente_id")
	private Cliente cliente;

	@NotNull
	@ManyToOne(cascade = CascadeType.ALL)
	@JoinColumn(name = "funcionario_id")
	private Funcionario funcionario;

	@NotNull
	@OneToOne(cascade = CascadeType.ALL)
	@JoinColumn(name = "pagamento_id")
	private Pagamento pagamento;

	@Temporal(TemporalType.TIMESTAMP)
	private Date data;
}

Se puder me prestar mais esclarecimentos eu agradeço!

Abração galera!

Adriano_Almeida

Na verdade a idéia é o seguinte:

Da pra vc criar uma classe que seja algo como um Wrapper para os dados que vc precisa na sessao para esse caso, entao, vc criaria uma classe que teria dentro dela a Venda, o Cliente o Pagamento e os Itens, e receberia essa classe no construtor do seu Resource (que agora passaria a ser RequestScoped). Essa sua classe nova (o wrapper para todas aquelas informacoes), seria um @Component e @SessionScoped (ja que so essas informacoes tem que durar na sessao do usuario).

Dentro das suas logicas, a unica coisa que vc precisaria fazer eh alterar o objeto que esta na sessao da forma que vc quiser.

Ok?

Duvidas mande ae.

[]'s

Lucas_Cavalcanti

não faz muito sentido o seu @Resource ser Session ou Application Scoped, principalmente pq ele depende
do Result, que muda a cada requisição…

Uma dica: após um post (como o método adiciona) é bom usar um redirect ao inves do forward, pq daí, se o usuário atualizar
a página (apertar F5) você não vai adicionar de novo o mesmo objeto…

hyr4x

Opa galera! Obrigado pelas dicas!

Funcionou perfeitamente. A solução utilizada foi o wrapper sugerido pelo pafuncio.

Abração!

Criado 15 de setembro de 2009
Ultima resposta 15 de set. de 2009
Respostas 6
Participantes 4