JSF Exibindo Objeto e Mensagens após Redirect

Olá, tudo bem?

Vamos falar hoje sobre como enviar uma mensagem utilizando JSF após um redirect. O redirect acaba por eliminar os dados da requisição original impossibilitando a exibição de alguma informação.

Essa situação é muito comum quando se utiliza Ajax para navegar entre páginas, e uma alternativa para forçar a troca de páginas, é utilizar o redirect.

Outros posts de JSF/Web você poderá encontrar aqui: Validação de Login de Usuário com JSF e JAASJSF: Converter and Bean AutoCompleteJSF – Hello World, AutoCompleteTratando Exceções em uma Aplicação WebAutenticação de Usuários (Filter/Servlet)Criando um WebServer.

Ao final do post você poderá fazer o download do código do post de hoje.

Vou abordar dois modos para envio de mensagem e/ou objeto após um redirect. Utilizando o Flash (API do JSF 2.0) ou então criando uma classe para controlar o os ciclos (JSF 1.2 para cima).

Abaixo, as imagens mostrando a navegação e a classe do nosso projeto.

/*******************************************************************************
 * Copyright (c) 2010 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package demo;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

/**
 * Created by JBoss Tools
 */
@ManagedBean(name="user")
@RequestScoped
public class User {
	private String name;

	public User() {
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String sayHello() {
		return "greeting";
	}
}
<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
	version="2.0">

	<navigation-rule>
		<from-view-id>/pages/inputname.xhtml</from-view-id>
		<navigation-case>
			<from-outcome>greeting</from-outcome>
			<to-view-id>/pages/greeting.xhtml</to-view-id>
		</navigation-case>
	</navigation-rule>

    <application>
        <resource-bundle>
            <base-name>resources</base-name>
            <var>msgs</var>
        </resource-bundle>
    </application>
</faces-config>

Agora, faremos uma pequena alteração para que a ação de ir para página final seja através de um redirect. O código abaixo demonstra como ficará nosso faces-config.xml e o resultado dessa alteração (note a linha 14).

<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
	version="2.0">

	<navigation-rule>
		<from-view-id>/pages/inputname.xhtml</from-view-id>
		<navigation-case>
			<from-outcome>greeting</from-outcome>
			<to-view-id>/pages/greeting.xhtml</to-view-id>
			<redirect/>
		</navigation-case>
	</navigation-rule>

    <application>
        <resource-bundle>
            <base-name>resources</base-name>
            <var>msgs</var>
        </resource-bundle>
    </application>
</faces-config>


Onde o Peter foi parar? É isso que acontece quando utilizamos um redirect e o objeto não está na sessão. Para consertar esse “problema” vamos utilizar o não tão famoso Flash Scope.

Veja como o código da classe User ficará (repare nas linhas 38,39):

/*******************************************************************************
 * Copyright (c) 2010 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package demo;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

import com.sun.faces.context.flash.ELFlash;

/**
 * Created by JBoss Tools
 */
@ManagedBean(name="user")
@RequestScoped
public class User {
	private String name;

	public User() {
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String sayHello() {
		// Put the object inside the flash scope
		ELFlash.getFlash().put("user", this);
		return "greeting";
	}
}

E para acessar o objeto usuário (user), basta alterar a página:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">

	<ui:composition template="/templates/common.xhtml">
			<ui:define name="pageTitle">Greeting to User</ui:define>
			<ui:define name="pageHeader">Greeting Page</ui:define>
			<ui:define name="body">
				#{msgs.greeting} #{flash.user.name}!
			</ui:define>
	</ui:composition>
</html>

E o Peter voltará a aparecer.

O escopo Flash nada mais faz do que guarda o objeto como se fosse sessão. Atenção para o fato de que após a sua utilização o objeto “user” será removido do escopo. Como manter o objeto no Flash Scope mesmo após um redirect? Fácil, basta você trocar o comando

      #{flash.user.name}

por

      #{flash.keep.user.name}

Desse modo o objeto usuário irá continuar dentro do Flash Scope.

Para JSF 1.2: Infelizmente para se manter um objeto em uma requisição após um redirect no JSF 1.2 (até o dia de hoje), será necessário fazer o controle do objeto na sessão. =/ MAS, caso você queira enviar apenas um alerta para a próxima tela existe outra solução.

Vou criar uma nova página para exibir uma mensagem em forma de alerta “<h:messages/>“. Veja como nosso código ficará:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ez="http://java.sun.com/jsf/composite/demo">

	<ui:composition template="/templates/common.xhtml">

		<ui:define name="pageTitle">Input User Name</ui:define>

		<ui:define name="pageHeader">JSF 2 Hello Application</ui:define>

		<ui:define name="body">
			<h:message showSummary="true" showDetail="false" style="color: red; font-weight: bold;" for="inputname" />
			<ez:input id="inputname" label="${msgs.prompt}" value="#{user.name}" action="#{user.sayHello}" submitlabel="Say Hello"/>
			<ez:input id="inputname2" label="${msgs.prompt}" value="#{user.name}" action="#{user.sayHelloWithAlert}" submitlabel="Say Hello With Alert"/>
		</ui:define>
	</ui:composition>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">

	<ui:composition template="/templates/common.xhtml">
			<ui:define name="pageTitle">Greeting to User With alert</ui:define>
			<ui:define name="pageHeader">Greeting Page With alert</ui:define>
			<ui:define name="body">
				<h:messages />
			</ui:define>
	</ui:composition>
</html>
/*******************************************************************************
 * Copyright (c) 2010 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package demo;

import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;

import com.sun.faces.context.flash.ELFlash;

/**
 * Created by JBoss Tools
 */
@ManagedBean(name="user")
@RequestScoped
public class User {
	private String name;

	public User() {
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String sayHello() {
		// Put the object inside the flash scope
		ELFlash.getFlash().put("user", this);
		return "greeting";
	}

	public String sayHelloWithAlert() {
		String message = "Hello : " + name;
		FacesContext context = FacesContext.getCurrentInstance();

		context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, message, message));

		return "greetingWithAlert";
	}
}
<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
	version="2.0">

	<navigation-rule>
		<from-view-id>/pages/inputname.xhtml</from-view-id>
		<navigation-case>
			<from-outcome>greeting</from-outcome>
			<to-view-id>/pages/greeting.xhtml</to-view-id>
			<redirect/>
		</navigation-case>
		<navigation-case>
			<from-outcome>greetingWithAlert</from-outcome>
			<to-view-id>/pages/greetingWithAlert.xhtml</to-view-id>
		</navigation-case>
	</navigation-rule>

    <application>
        <resource-bundle>
            <base-name>resources</base-name>
            <var>msgs</var>
        </resource-bundle>
    </application>
</faces-config>


E caso utilizemos o redirect a mensagem nossa mensagem não será exibida. Veja o que acontece:

<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
	version="2.0">

	<navigation-rule>
		<from-view-id>/pages/inputname.xhtml</from-view-id>
		<navigation-case>
			<from-outcome>greeting</from-outcome>
			<to-view-id>/pages/greeting.xhtml</to-view-id>
			<redirect/>
		</navigation-case>
		<navigation-case>
			<from-outcome>greetingWithAlert</from-outcome>
			<to-view-id>/pages/greetingWithAlert.xhtml</to-view-id>
			<redirect/>
		</navigation-case>
	</navigation-rule>

    <application>
        <resource-bundle>
            <base-name>resources</base-name>
            <var>msgs</var>
        </resource-bundle>
    </application>
</faces-config>


A solução abaixo eu encontrei no site: “ocpsoft.com/java/persist-and-pass-facesmessages-over-page-redirects/”.

O que a classe faz é adicionar sua mensagem ao contexto de sessão, exibe e depois remove a mensagem da sessão. Uma solução bem útil quando se quer prolongar as mensagens.

package demo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

/**
 * Enables messages to be rendered on different pages from which they were set.
 *
 * After each phase where messages may be added, this moves the messages
 * from the page-scoped FacesContext to the session-scoped session map.
 *
 * Before messages are rendered, this moves the messages from the
 * session-scoped session map back to the page-scoped FacesContext.
 *
 * Only global messages, not associated with a particular component, are
 * moved. Component messages cannot be rendered on pages other than the one on
 * which they were added.
 *
 * To enable multi-page messages support, add a <code>lifecycle</code> block to your
 * faces-config.xml file. That block should contain a single
 * <code>phase-listener</code> block containing the fully-qualified classname
 * of this file.
 *
 * @author Jesse Wilson jesse[AT]odel.on.ca
 * @secondaryAuthor Lincoln Baxter III lincoln[AT]ocpsoft.com
 */
public class MultiPageMessagesSupport implements PhaseListener
{

    private static final long serialVersionUID = 1250469273857785274L;
    private static final String sessionToken = "MULTI_PAGE_MESSAGES_SUPPORT";

    public PhaseId getPhaseId()
    {
        return PhaseId.ANY_PHASE;
    }

    /*
     * Check to see if we are "naturally" in the RENDER_RESPONSE phase. If we
     * have arrived here and the response is already complete, then the page is
     * not going to show up: don't display messages yet.
     */
    // TODO: Blog this (MultiPageMessagesSupport)
    public void beforePhase(final PhaseEvent event)
    {
        FacesContext facesContext = event.getFacesContext();
        this.saveMessages(facesContext);

        if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()))
        {
            if (!facesContext.getResponseComplete())
            {
                this.restoreMessages(facesContext);
            }
        }
    }

    /*
     * Save messages into the session after every phase.
     */
    public void afterPhase(final PhaseEvent event)
    {
        if (!PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()))
        {
            FacesContext facesContext = event.getFacesContext();
            this.saveMessages(facesContext);
        }
    }

    @SuppressWarnings("unchecked")
    private int saveMessages(final FacesContext facesContext)
    {
        List<FacesMessage> messages = new ArrayList<FacesMessage>();
        for (Iterator<FacesMessage> iter = facesContext.getMessages(null); iter.hasNext();)
        {
            messages.add(iter.next());
            iter.remove();
        }

        if (messages.size() == 0)
        {
            return 0;
        }

        Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap();
        List<FacesMessage> existingMessages = (List<FacesMessage>) sessionMap.get(sessionToken);
        if (existingMessages != null)
        {
            existingMessages.addAll(messages);
        }
        else
        {
            sessionMap.put(sessionToken, messages);
        }
        return messages.size();
    }

    @SuppressWarnings("unchecked")
    private int restoreMessages(final FacesContext facesContext)
    {
        Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap();
        List<FacesMessage> messages = (List<FacesMessage>) sessionMap.remove(sessionToken);

        if (messages == null)
        {
            return 0;
        }

        int restoredCount = messages.size();
        for (Object element : messages)
        {
            facesContext.addMessage(null, (FacesMessage) element);
        }
        return restoredCount;
    }
}

E por último precisamos alterar o “faces-config.xml”:

<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
	version="2.0">

	<navigation-rule>
		<from-view-id>/pages/inputname.xhtml</from-view-id>
		<navigation-case>
			<from-outcome>greeting</from-outcome>
			<to-view-id>/pages/greeting.xhtml</to-view-id>
			<redirect/>
		</navigation-case>
		<navigation-case>
			<from-outcome>greetingWithAlert</from-outcome>
			<to-view-id>/pages/greetingWithAlert.xhtml</to-view-id>
			<redirect/>
		</navigation-case>
	</navigation-rule>
	<lifecycle>
		<phase-listener>demo.MultiPageMessagesSupport</phase-listener>
	</lifecycle>	

    <application>
        <resource-bundle>
            <base-name>resources</base-name>
            <var>msgs</var>
        </resource-bundle>
    </application>
</faces-config>

E ao executar novamente nosso sistema, veja quem voltou… Peter! o/

Para realizar o download do código fonte do post, clique aqui. Para executar o código você precisará ter o JBoss 6 configurado em seu Eclipse, para isso, terá que instalar o JBoss Tools. Nesse post, a instalação e configuração são abordadas: User Login Validation with JAAS and JSF.

Espero que o post de hoje possa te ajudar.

Qualquer dúvida ou colocação basta postar.

Esse post foi baseado:

  • Livro: “Core JavaServer Faces” Capítulo: “Redirection and Flash”
  • Código gerado pela ferramenta JBoss Tools
  • Código da classe de mensagem persistida “ocpsoft.com/”

Até a próxima! o_

19 thoughts on “JSF Exibindo Objeto e Mensagens após Redirect

  1. Opa desde já otimo post.

    Mais infelizmente não funcionou comigo. Segue meu codigo se der para dar uma ajuda ficarei grato.

    Meu Bean:

    public String gravar(){

    DAO dao = new DAO(Sistema.class);
    usuarios = usuarioModel.getSource();
    usuariosSelecionados = usuarioModel.getTarget();

    if(sistema.getIdSistema() == null){
    sistema.setUsuarios(usuariosSelecionados);
    dao.adiciona(sistema);
    FacesContext.getCurrentInstance().addMessage(“:cadastro_sistema:msgSistema”, new FacesMessage(FacesMessage.SEVERITY_INFO,”Sistema ” + sistema.getNomeSistema() +” cadastrado com sucesso.”, “”));;
    this.sistema = new Sistema();

    }else{
    sistema.setUsuarios(usuariosSelecionados);
    dao.atualiza(sistema);
    FacesContext.getCurrentInstance().addMessage(“:cadastro_sistema:msgSistema”, new FacesMessage(FacesMessage.SEVERITY_INFO,”Sistema ” + sistema.getNomeSistema() +” alterado com sucesso.”, “”));;
    this.sistema = new Sistema();
    }

    sistemas = new DAO(Sistema.class).listaTodos();
    this.sistema = new Sistema();
    return “cadastrosistema?faces-redirect=true”;
    }

    E eu adicionei no faces o codigo.

    br.com.sisapropriacao.util.MultiPageMessagesSupport

    Obrigado.

  2. uaihebert, parabens p/ post!!

    ..mas, tenho 1 App c/ RichFaces/A4JSF e tenho 1 problema parecido: tentei mudar o escopo de Session p/ Request e utilizar 1 PhaseListener, como o seu (só q de outro blog), mas ou o AJAX pára de funcionar, ou quando ocorre o dispatch p/ a pág destino, está não renderiza.
    Existe alguma solução para isto??

    Tnx in advance..

    • Derlon, boa tarde.

      Infelizmente não tenho como te ajudar, pois teria que realizar uma análise no seu código (ainda mais pelo fato de você estar misturando com código de outros blogs).

      Você poderia postar seu código no http://www.guj.com.br, o pessoal lá realiza uma análise de código gratuita e de boa qualidade.

      Uma conselho que dou é: cuidado ao alterar o escopo de um MB. Isso não é uma alteração simples pois pode afetar diversos componentes que dependem daquele ManagedBean.

      Obrigado pela visita.

  3. Ótimo post! Eu utilizava com sessão, sempre achei que fosse um bug, valeu pela ótima explicação. O bom é que sempre está pensando nas duas versões, tanto a nova 2.x quanto a antiga 1.2.

    Continue com o ótimo trabalho

  4. Caraa…

    Eu posso usar o flashScope mesmo se não fizer um redirect?
    Bom… eu postei minha duvida no guj…

    Mas vou ser um pouco menos especifico…
    imagine um datatable com uma coluna e um botão de editar…
    Atualmente esta assim:

    onde o getFormPath retorna /view/grupo/editar.xhtml …

    como seria a maneira mais legal para editar a entidade usando o mesmo managedBean, sendo que, ele vai parar de existir quando eu trocar de pagina?
    Com flashScope funcionaria? mas nao seria o ideal, imagino!

    • Marco, boa tarde.

      O flashscope pode ser utilizado mesmo sem redirect, por exemplo, ao sair de um MB view para o outro. [=

      Não vejo por que não o utilizar.

      Até mais. [=

  5. hebert, essa classe PhaseListener ela é apenas para jsf 1.2…? ela torna é um forward da vida ?

    estava a procura de algo redirect que enviasse as msgs automaticamentes sem ter q colocar no flash scope.. :) sabe de algo rsrs

    • Otávio, bom dia.

      Não, ela existe no jsf 2.

      PhaseListener não é simples para explicar aqui, basicamente funciona como um interceptor dos ciclos de vida do JSF.

      O que eu sei eu coloquei aqui, infelizmente não achei nada além disso quando pesquisei sobre esse assunto a tempos atrás.

      Obrigado pela visita.

    • Bruno, boa tarde.

      Teria que analisar a lógica aplicada. Como eu disse, foi um código encontrado na internet mas que é muito utilizado.

      Obrigado pela visita.

  6. Ola.
    Eu fiz um teste na minha aplicação e funcionou tudo de boa com o FlashScope.
    Porém quando eu coloquei o JAAS na minha aplicação o a funcionalidade parou…agora tudo que eu coloco do FlashScope não é apresentado depois do redirect.
    Para utilizar o JAAS em conjunto com o FlashScope é necessário algum tipo de configuração extra?

    • Bruno, boa tarde.

      Nunca vi esse problema acontece relacionado ao JAAS não.

      Infelizmente não tenho como te ajudar agora pois teria que analisar seus códigos.

      Se você postar sua dúvida no GUJ ou no http://br.stackoverflow.com/ o pessoal vai te ajudar.

      Obrigado pela visita.

Leave a Comment