Para você: EntityConverter para qualquer entidade e tipo de Id

51 respostas
Flavio_Almeida

Depois de ter encontrado a solução SimpleEntityConverter em http://www.rponte.com.br/tag/entity-converter/, resolvi melhorá-la, deixando-a ainda mais genérica. O próprio autor diz: "(…) É possível estende-las e até melhora-las, você é livre para isso, e dependendo da tua necessidade provavelmente será o melhor caminho, só não deixe de contribuir com o código para a comunidade (…).

1 - Não há necessidade de implementar a interface BaseEntity
2 - O id da classe pode ser String, Integer, etc. e não apenas Long como em SimpleEntityConverter, pois a classe identifica o retorno pela anotação @Id. Essa foi uma das motivações que me levaram a melhorar SimpleEntityConverter, pois algumas entidades de nossos sistemas não possuem id do tipo Long, mas String.

package net.metha.jsf.converter;

import java.lang.reflect.Field;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.persistence.Id;

/**
 * Converter para entidades JPA. 
 * Baseia-se na anotação @Id para identificar o atributo que representa a identidade da entidade.  
 * @author Flávio Henrique 
 * @version 1.0 
 * @since 05/10/2010
 */
public class EntityConverter implements Converter {

	public Object getAsObject(FacesContext ctx, UIComponent component,
			String value) {
		if (value != null) {
			return component.getAttributes().get(value);
		}
		return null;
	}

	public String getAsString(FacesContext ctx, UIComponent component,
			Object obj) {
		if (obj != null && !"".equals(obj)) {
			String id;
			try {
				id = this.getId(getClazz(ctx, component), obj);
				if (id == null){
					id = "";
				}
				id = id.trim();
				component.getAttributes().put(id,
						getClazz(ctx, component).cast(obj));
				return id;
			} catch (SecurityException e) {
				e.printStackTrace(); // seu log aqui
			} catch (IllegalArgumentException e) {
				e.printStackTrace(); // seu log aqui
			} catch (NoSuchFieldException e) {
				e.printStackTrace(); // seu log aqui
			} catch (IllegalAccessException e) {
				e.printStackTrace(); // seu log aqui
			}
		}
		return null;
	}

	private Class<?> getClazz(FacesContext facesContext, UIComponent component) {
		return component.getValueExpression("value").getType(
				facesContext.getELContext());
	}

	public String getId(Class<?> clazz, Object obj) throws SecurityException,
			NoSuchFieldException, IllegalArgumentException,
			IllegalAccessException {
		for (Field field : clazz.getDeclaredFields()) {
			if ((field.getAnnotation(Id.class)) != null) {
				Field privateField = clazz.getDeclaredField(field.getName());
				privateField.setAccessible(true);
				if (privateField.get(clazz.cast(obj)) != null) {
					return (String)field.getType()
							.cast(privateField.get(clazz.cast(obj))).toString();
				} else {
					return null;
				}
			}
		}
		return null;
	}
}

Registre o converter em faces-config.xml:

entityConverter
net.metha.jsf.converter.EntityConverter

Use e abuse:

<h:selectOneMenu id=“comboRestaurantes"
value=”#{consumoMB.restauranteOperacao}" required="true"
label=“Restaurante” converter=“entityConverter”>

10/02/2011 -> RETIFICANDO: O CÓDIGO ACIMA NÃO É THREADSAFE, USAR DESTA FORMA:

<h:selectOneMenu id="comboRestaurantes"
value="#{consumoMB.restauranteOperacao}" required="true"
label="Restaurante">
<f:converter converterId="entityConverter"/>
</selectOneMenu>

Esta solução está sendo usada em ambiente de produção.

Atenciosamente,

Flávio Henrique de Souza Almeida

51 Respostas

Ataxexe

Bacana. Pro pessoal que usa o JBoss Seam isso não é necessário. Basta usar a tag s:convertEntity.

Mais uma coisinha: com @EmbeddedId sua solução não funcionará, bem como subclasses cujo id esteja na superclasse.

Flavio_Almeida

Obrigado pelas sugestões.

Estamos cientes do s:converEntity do Seam, mas como a versão 2.0 não suporta CDI (out-of-the-box), estamos aguardando a versão 3.0.

Sobre o @EmbeddedId: acredito que eu não tenha dificuldade em adicionar esta funcionalidade.

Sobre herança: farei a alteração.

Flavio_Almeida

Suporte à herança adicionado.

public String getId(Class<?> clazz, Object obj) throws SecurityException,
			NoSuchFieldException, IllegalArgumentException,
			IllegalAccessException {
		Class<?> classAnotada = clazz;
		if(clazz.getSuperclass() != Object.class){
			classAnotada = clazz.getSuperclass();		
		}
		for (Field field : classAnotada.getDeclaredFields()) {
			if ((field.getAnnotation(Id.class)) != null) {
				Field privateField = classAnotada.getDeclaredField(field.getName());
				privateField.setAccessible(true);
				if (privateField.get(clazz.cast(obj)) != null) {
					return (String) field.getType()
							.cast(privateField.get(clazz.cast(obj))).toString();
				} else {
					return null;
				}
			}
		}
		return null;
	}
G

} catch (SecurityException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }

Isso é um dos piores erros que um programador pode fazer quando trabalha com exceptions. :shock:

http://today.java.net/article/2006/04/04/exception-handling-antipatterns

Flavio_Almeida

Podemos gravar em um log essas exceções ou fazer com que getId() lance uma exceção personalizada (EntityConverterException).

rponte

Olá Flavio,

Muito bacana a solução e fico feliz que você tenha disponibilizado a mesma no GUJ. Acredito que não exista lugar melhor para isso! :slight_smile:

Como já é sábido por você, o JBoss Seam se utiliza dessa mesma solução, através do s:entityConvert, e provavelmente o código dele tenha coisas bem interessantes para se observar se tratando de problemas comuns, como o problema de herança já corrigido por você.

Eu havia pensado nessa solução também, mas ela acabou ficando fora do post. Contudo, o mais interessante de tudo isso são estes tipos de iniciativas em compartilhar conhecimento e código com a comunidade.

Enfim, como o garcia-jj comentou, melhore o tratamento de exceções, pois ignora-las é uma má prática. E claro, se possível disponibilize o código no GitHub :slight_smile:

Um abraço e parabéns novamente.

G

Usar o github é excelente, pois muitas pessoas podem fazer um fork e contribuir.

Flavio_Almeida

Adicionei suporte à anotação @EmbeddedId e corrigi um problema na hierarquia de classes.

package net.metha.jsf.converter;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.persistence.EmbeddedId;
import javax.persistence.Id;

/**
 * Converter para entidades JPA. Baseia-se nas anotações @Id e @EmbeddedId para identificar o
 * atributo que representa a identidade da entidade. Capaz de detectar as anotações nas classes superiores.
 * 
 * @author Flávio Henrique
 * @version 1.0.3
 * @since 05/10/2010
 */
public class EntityConverter implements Converter {

	public Object getAsObject(FacesContext ctx, UIComponent component,
			String value) {
		if (value != null) {
			return component.getAttributes().get(value);
		}
		return null;
	}

	public String getAsString(FacesContext ctx, UIComponent component,
			Object obj) {
		if (obj != null && !"".equals(obj)) {
			String id;
			try {
				id = this.getId(getClazz(ctx, component), obj);
				if (id == null) {
					id = "";
				}
				id = id.trim();
				component.getAttributes().put(id,
						getClazz(ctx, component).cast(obj));
				return id;
			} catch (SecurityException e) {
				e.printStackTrace(); // seu log aqui
			} catch (IllegalArgumentException e) {
				e.printStackTrace(); // seu log aqui
			} catch (NoSuchFieldException e) {
				e.printStackTrace(); // seu log aqui
			} catch (IllegalAccessException e) {
				e.printStackTrace(); // seu log aqui
			}
		}
		return null;
	}

	/**
	 * Obtém, via expression language, a classe do objeto.
	 *
	 * @param FacesContext facesContext
	 * 
	 * @param UICompoment compoment
	 *     
	 * @return  Class<?>
	 */
	private Class<?> getClazz(FacesContext facesContext, UIComponent component) {
		return component.getValueExpression("value").getType(
				facesContext.getELContext());
	}


	/**
	 * Retorna a representação em String do retorno do método anotado com @Id ou @EmbeddedId do objeto.
	 *
	 * @param Class<?> clazz
	 *            
	 * @return  String
	 */
	public String getId(Class<?> clazz, Object obj) throws SecurityException,
			NoSuchFieldException, IllegalArgumentException,
			IllegalAccessException {

		List<Class<?>> hierarquiaDeClasses = this.getHierarquiaDeClasses(clazz);
		
		for (Class<?> classeDaHierarquia : hierarquiaDeClasses) {
			for (Field field : classeDaHierarquia.getDeclaredFields()) {
				if ((field.getAnnotation(Id.class)) != null
						|| field.getAnnotation(EmbeddedId.class) != null) {
					Field privateField = classeDaHierarquia
							.getDeclaredField(field.getName());
					privateField.setAccessible(true);
					if (privateField.get(clazz.cast(obj)) != null) {

						return (String) field.getType()
								.cast(privateField.get(clazz.cast(obj)))
								.toString();
					}
				}
			}
		}
		return null;
	}

	/**
	 * Retorna uma lista com a hierarquia de classes, sem considerar a classe Object.class
	 *
	 * @param Class<?> clazz
	 *            
	 * @return  List<Class<?>> clazz
	 */
	public List<Class<?>> getHierarquiaDeClasses(Class<?> clazz) {

		List<Class<?>> hierarquiaDeClasses = new ArrayList<Class<?>>();
		Class<?> classeNaHierarquia = clazz;
		while(classeNaHierarquia != Object.class) {
			hierarquiaDeClasses.add(classeNaHierarquia);
			classeNaHierarquia = classeNaHierarquia.getSuperclass();
			
		}
		return hierarquiaDeClasses;
	} 
}
Flavio_Almeida

Estamos usando em produção EntityConverter e até agora não tivemos problemas.
A diferença básica entre EntityConverter e o s:ConvertEntity do Seam é a seguinte:

1 - s:ConvertyEntity obtém o id da entidade e realiza a procura no banco de dados utilizando JPA. Eles resolvem o problema da unidade de persistência, permitindo que o usuário defina este atributo na própria tag quando necessário. Acho essa solução interessante, ficando ainda melhor quando temos um cache de segundo nível como ehcache.

2- EntityConverter, assim como SimpleEntityConverter, altera o estado do componente no lado do servidor. A vantagem é de não precisamos acessar o EntityManager dentro do converter, mas há um custo de processamento, que em nossos sistemas, até o momento, é imperceptível.

Eu poderia alterar o código e usar a mesma estratégia do s:ConvertEntity (o código de infraestrutura já permite isso), mas se isso fosse necessário, eu utilizaria o s:ConvertEntity e não o EntityConverter.

O que eu gostaria deixar bem claro aqui é que EntityConverter foi criado pensando nas aplicações que desenvolvo, desta forma, será necessário que cada um avalie qual tipo de conversor de entidade utilizar.

Abraço

Ataxexe

Muito bom, meus parabéns! Tanto pelas melhorias como pela iniciativa de compartilhar suas ideias conosco.

Flavio_Almeida

Ataxexe sugeriu a utilização de uma biblioteca de reflexão para deixar o código mais legível. A biblioteca escolhida foi a Trugger 2.7.0 que pode ser baixada em:

O método getId() foi reescrito com base no código enviado pelo colega. Reparem que não é mais necessário o método getHierarquiaDeClasses() nem o parâmetro da classe.

Para o código ficar ainda mais rápido, será necessário limar o método getClazz (evoca EL), mas eu ainda preciso dele no método getAsString() devido a necessidade de sabermos qual a classe do objeto que chega até o converter para podermos gravá-lo dentro do map. Como ainda estou conhecendo a biblioteca trugger, acredito que ela tenha a chave para esta resposta.

Ataxexe, você vê alguma solução?

Segue o código alterado. Não esqueçam de adicionar trugger-2.7.0.jar
OBS: eu fiz um teste premilinar e funcionou. Amanhã, farei outro teste colocarei em produção.

package net.metha.jsf.converter;

import static net.sf.trugger.element.Elements.element;
import static net.sf.trugger.reflection.ReflectionPredicates.annotatedWith;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.persistence.EmbeddedId;
import javax.persistence.Id;

/**
 * Converter para entidades JPA. Baseia-se nas anotações @Id e @EmbeddedId para
 * identificar o atributo que representa a identidade da entidade. Também
 * as anotações nas super classes.
 * 
 * @author Flávio Henrique
 * @version 1.0.4
 * @since 05/10/2010
 */
public class EntityConverter implements Converter {

	public Object getAsObject(FacesContext ctx, UIComponent component,
			String value) {
		if (value != null) {
			return component.getAttributes().get(value);
		}
		return null;
	}

	public String getAsString(FacesContext ctx, UIComponent component,
			Object obj) {
		if (obj != null && !"".equals(obj)) {
			String id;
		
				id = this.getId(obj);
				if (id == null) {
					id = "";
				}
				id = id.trim();
				component.getAttributes().put(id,
						getClazz(ctx, component).cast(obj));
				return id;
		}
		return null;
	}

	/**
	 * Obtém, via expression language, a classe do objeto
	 * 
	 * @param FacesContext
	 *            facesContext
	 * 
	 * @param UICompoment
	 *            compoment
	 * 
	 * @return Class<?>
	 */
	private Class<?> getClazz(FacesContext facesContext, UIComponent component) {
		return component.getValueExpression("value").getType(
				facesContext.getELContext());
	}

	/**
	 * Retorna a representação em String do retorno do método anotado com @Id ou @EmbeddedId
	 * do objeto.
	 * 
	 * @param Object obj
	 * 
	 * @return String
	 */
	public String getId(Object obj) {
		Object idValue = element()
				.thatMatches(
						annotatedWith(Id.class).or(
								annotatedWith(EmbeddedId.class))).in(obj)
				.value();
		return String.valueOf(idValue);
	}
}
Flavio_Almeida

O desempenho está muito bom.

Coloquei mais um sistema nosso utilizando esta classe. Para os managedBeans em escopo de visão, o desempenho é melhor ainda.

Flavio_Almeida

Este converter não funciona com rich:combobox. Por quê? Porque esta tag não usa selectedItens internamente, ou seja, é derivada da suggestionbox. Parece que isso foi resolvido no richfaces 4.

Assim que eu realizar o teste, eu posto aqui.

Abraço

HarryPodre

Boa tarde,

Estou usando este converter mas não consigo obter o resultado esperado.
No método getAsObject() recebo um null. Debuguei o método getAsString() e os objetos são inseridos no map normalmente, mas quando preciso obter no método getAsObject() recebo um null.

Segue o código:

public String getAsString(FacesContext ctx, UIComponent component, Object obj)
	{
		if (obj != null && !"".equals(obj))
		{
			String id;
			
			id = this.getId(obj);
			if (id == null)
			{
				id = "";
			}
			id = id.trim();
			//Aqui insere normal no map
			component.getAttributes().put(id, getClazz(ctx, component).cast(obj));
			return id;
		}
		return null;
	}
public Object getAsObject(FacesContext ctx, UIComponent component, String value)
	{
		if (value != null)
		{
			// Aqui retorna null sempre
			return component.getAttributes().get(value);
		}
		return null;
	}

O registro do converter no faces-config:

<converter>		
       <converter-id>entityConverter</converter-id>
       <converter-class>br.com.telefonica.indra.gestaodeativos.converters.EntityConverter</converter-class>
</converter>

Página:

<h:selectOneMenu id="perfil" value="#{usuarioBean.perfil}" converter="entityConverter">

Alguma idéia?

Preciso colocar algo mais do meu código?

Atte.

Gustavo Belloni Metzner

Flavio_Almeida

HarryPodre, como você populou sua lista de selectedItens? Coloca aqui o código para que eu possa analisar.
Abraço

HarryPodre

Flavio Almeida:
HarryPodre, como você populou sua lista de selectedItens? Coloca aqui o código para que eu possa analisar.
Abraço

Flávio, este trecho do código ajuda?

List<SelectItem> itens = new ArrayList<SelectItem>();
		itens.add(new SelectItem(null, "Selecione"));
		
		for (Perfil perfil : perfis)
		{
			itens.add(new SelectItem(perfil, perfil.getDescricao()));
		}

Valeu!

Flavio_Almeida

Hoje eu estou bem atarefado, mas responderei com mais calma à noite.

Faça assim:

itens.add(new SelectItem(null, “”)); // string em branco no lugar de “Selecione”

Perfil tem a anotação @Id ou @EmbeddableId?

Estamos usando este converter em produção sem problemas, mas estou interessado em ajudar você a resolver seu problema.

Flavio_Almeida

Você está usando a tag <f:selectItems> ?

Veja um exemplo:

<h:selectOneMenu id="papelUsuario" value="#{usuarioController.usuario.papel}" disabled="#{usuarioController.formStatus.consultando}" label="Papel" required="true" converter="entityConverter">
   <f:selectItems value="#{usuarioController.listaComboPapeis}" />
</h:selectOneMenu>

Lucas Sorrentino vai te ajudar!

HarryPodre

Estou sim.

<h:selectOneMenu id="perfil" value="#{usuarioBean.perfil}" converter="entityConverter">
    <f:selectItems value="#{usuarioBean.perfis}"/>			            			            
</h:selectOneMenu>
HarryPodre

Flavio Almeida:
Hoje eu estou bem atarefado, mas responderei com mais calma à noite.

Faça assim:

itens.add(new SelectItem(null, “”)); // string em branco no lugar de “Selecione”

Perfil tem a anotação @Id ou @EmbeddableId?

Estamos usando este converter em produção sem problemas, mas estou interessado em ajudar você a resolver seu problema.

O Perfil tem a anotação @Id.

Colocar uma string em branco só vai remover o “Selecione” do combo, mas o erro vai continuar.
Vou continuar debugando aqui, acho que deixei escapar algo rs.

Valeu!

Lucas_Sorrentino

cara, esse erro acontece quando você seleciona qualquer item da combo?
A combo renderiza direitinho, com todos os itens?

HarryPodre

Lucas Sorrentino:
cara, esse erro acontece quando você seleciona qualquer item da combo?
A combo renderiza direitinho, com todos os itens?

Então Lucas, ele renderiza normal, com todos os itens e o erro acontece com qualquer item selecionado.
Vou fazer um teste aqui, acho que o problema é no meu combo, to fazendo algo errado… assim que testar ja posto algo.

Valeu!

Lucas_Sorrentino

Harry,
essa lista que você ta passando pra sua página é uma lista de select item? Esse seu usuarioBean.perfis?

HarryPodre

É sim Lucas, é uma lista de select item, gero ela assim:

List<SelectItem> itens = new ArrayList<SelectItem>();
   itens.add(new SelectItem(null, "Selecione"));
		
   for (Perfil perfil : perfis)
   {
       itens.add(new SelectItem(perfil, perfil.getDescricao()));
   }
   return itens;

Esse combo não é required, mas resolvi deixa-lo para ver o que acontece. Mesmo que você selecione um item ele não entende que foi selecionado, exibe uma mensagem que o campo perfil é obrigatório, como se o meu equals não tivesse sido implementado, mas foi.
Enfim, já achei o erro mas não sei como solucionar, pois aparentemente está correto.

Tenho uma outra aplicação com um combo, mas que possui um converter especifico que funciona perfeitamente.
Refiz o combo baseado neste que funciona, exceto o converter pois quero utilizar o generico e mesmo assim é como se não tivesse selecionado nada no combo.

Fui claro?

Só percebi isso pois tentei utilizar o combo como required.

HarryPodre

Esquece o que falei no ultimo post, to viajando aqui…rs
Ele não reconhece que o combo foi preenchido pq o converter só retorna null no método getAsObject() rs.

A ideia do converter é colocar o objeto num map e depois recuperar pela chave que é o id, certo ?
Quando vou recupar o map está vazio:

// só retorna null  
  component.getAttributes().get(value);

Sei lá, vou recomeçar esse combo do zero, refazer tudo pra ver se não to fazendo besteira rs.

Valeu pela ajuda!

HarryPodre

Lucas e Flavio, fiz um projetinho do zero aqui e o converter funcionou perfeitamente.
Como suspeitava o problema é no meu código, vou ver o que consigo e posto aqui.

Muito obrigado pela ajuda!

Flavio_Almeida

Que bom que funcionou, já estava achando que era algum problema não identificado antes no código.
Abraço

HarryPodre

Só uma dúvida, vcs usam jsf com o que? richfaces, facelets?

HarryPodre

Ah, descobri o motivo de não funcionar rs.

Achei isso no web.xml

<context-param>
   <param-name>facelets.BUILD_BEFORE_RESTORE</param-name>
   <param-value>true</param-value>
</context-param>

Estava como ‘true’ então passei pra ‘false’:

<context-param>
  <param-name>facelets.BUILD_BEFORE_RESTORE</param-name>
  <param-value>false</param-value>
</context-param>

Como meu acesso a internet é limitado não pude procurar pra que realmente serve essa configuração.

Agora ta funcionando legal.

Valeu pela ajuda!

Flavio_Almeida

HarryPodre, muito bem apontado. Realmente, usamos essa opção com o valor “false” em nossas aplicações.

http://www.jsfcentral.com/listings/A23139;jsessionid=B10FB0FDD00C333B6F6D884FD05D25D5.tomcat28?link

clebiovieira

:smiley: Flavio Almeida !!!

Meus Parabéns pela solução, pelo visto tivemos pesquisando nos mesmos lugares. rsrs (http://www.rponte.com.br)
Eu estava muito desapontado em ter que fazer um converter para cada entidade do meu modelo.

A priori, está funcionando bem. vou continuar os testes, porém não estou usando a versão com reflex, para evitar o uso de mais uma biblioteca.
O que acha ?
No que for possível te darei feedback.

Forte abraço. Continue assim, a nosso comunidade é forte por isso. :wink:

Flavio_Almeida

Não vejo problema algum. Eu prefiro a versão da biblioteca, porque deixa o código mais claro. Se não estou enganado, há um sistema nosso rodando a versão sem a biblioteca de reflexão há um bom tempo sem ter problema.

O feedback é muito importante, acredite, ainda mais que utilizamos essa classe em ambiente de produção.

Qualquer dúvida é só postar aqui.

Abraço

clebiovieira

Flavio Almeida:
O desempenho está muito bom.

Coloquei mais um sistema nosso utilizando esta classe. Para os managedBeans em escopo de visão, o desempenho é melhor ainda.

Flavio preciso da tua ajuda,
estou com um problema relacionado a atualização do selectOneMenu mesmo depois que atualizo o campo do meu managedBean, estou usando seu converter.

Abri um tópico hoje: http://www.guj.com.br/java/237628-problema-com-hselectonemenu—projeto-com-jsf-2-e-primefaces

Pode me dar uma ajuda com isso ? Obrigado … abs…

W

Parabens Flavio Almeida esse conversor é muito bom mesmo,
mas eu to com um probleminha aqui talvez vc conheça uma solução.
Estou usando esse conversor dentro um componente composto, por causa disso ou sei la o que, no metodo

private Class<?> getClazz(FacesContext facesContext, UIComponent component) { return component.getValueExpression("value").getType(facesContext.getELContext()); }

ele está me retornando null, não sei se é porque esta trocando o facesContext ou coisa parecida.
Mas aí a duvida tem como eu consertar isso ou passar a classe que eu desejo para o conversor?
Obrigado.

Flavio_Almeida

wanderman, hoje estou muito atarefado, talvez não consigo acompanhar o post.
Coloque o código da sua página + faces-config para que eu possa dar uma olhada.

Abraço!

W

Opa, estou usando jsf 2 e não estou utilizando o faces-config vou colocar resumidamente os trechos do codigo então para melhor entendimento.

na minha pagina tenho a seguinte chamada ao componente que eu criei:
<util:autoComplete id="autocomplete"  valor="#{contasFace.conta}"/>
e o meu componente esta assim
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:cc="http://java.sun.com/jsf/composite"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:c="http://java.sun.com/jsp/jstl/core"
      xmlns:p="http://primefaces.prime.com.tr/ui"
      >

    <cc:interface>
	<cc:attribute name="valor" required="true"/>
	<cc:attribute name="id" required="true" />
    </cc:interface>
    <cc:implementation>
	<div id="#{cc.clientId}">	
			<p:autoComplete value="#{cc.attrs.valor}"  completeMethod="#{utilFace.obterContas}" var="objeto" 
                             itemLabel="#{objeto.descricaoConta}" itemValue="#{objeto}">
			    <f:converter converterId="entityConverter"/>
			</p:autoComplete>
	</div>
    </cc:implementation>

</html>
caso eu não use o meu componente composto e coloque o p:autocomplete direto na pagina ele consegue me trazer a classe correta ex:
<p:autoComplete value="#{contasFace.conta}"  completeMethod="#{utilFace.obterContas}" var="objeto" 
                             itemLabel="#{objeto.descricaoConta}" itemValue="#{objeto}">
			    <f:converter converterId="entityConverter"/>
			</p:autoComplete>

não sei se nessa passagem de valor par ao componente ele perde a identificação da classe ou alguma coisa parecida.

Flavio_Almeida

Para que o código funcione, sua classe tem que estender SelectOneMenu do JSF. Por exemplo, no RF 3.X, o rich:combobox não estende SelectOneMenu, desta forma, impossibilitando o uso deste converter.

Abraço

W

Então com o autocomplete do primefaces ele funciona certinho se eu estiver utilizando-o na propria pagina, o problema ocorre somente quando eu utilizo-o dentro de um componente composto, não cheguei a testar mas creio q se eu criar um componente composto utilizando o SelectOneMenu ele pode gerar o mesmo problema, mas vou tentar aqui depois te falo.
Valeu

K

CARA VOCÊ SALVOU MEU EMPPREGO E CONSEQUENTEMENTE MINHA VIDA…OBRIGADO!!!

Flavio_Almeida

Olá Kristiano, puxa vida, fico muito contente em saber que o código que compartilhei o ajudou. Se quiser melhorá-lo, vá em frente.

Abraço!

K

ow fiquei feliz demais que deu certo, so que apareceu outro problema…
ele lista os items, mas nao consigo selecionalos… alguem sabe a solução?

I

Flavio Almeida,
Cara parabéns pelo otimizada no EntityConverter, realmente ficou excelente, utilizei a versão do primeiro post, que ja resolveu o meu problema, agora irei verificar com mais calma, não cheguei a ler todo o tópico, mas tens o git-hub para que possamos contribuir? :slight_smile:

M

Parabens pelo trabalho!!! ficou bem bacana… testei aqui e funcionou perfeito.

gugaa_df

Parabéns! FIcou muito bom mesmo!

Eu estava pesquisando bastante sobre converters e acabei testando essa solução e ficou muito bacana!

No fim das contas eu acabei utilizando a solução do Omnifaces http://showcase-omnifaces.rhcloud.com/showcase/converters/SelectItemsIndexConverter.xhtml. Ele não faz acesso ao banco! O único problema é colocar mais uma biblioteca na aplicação, mas se pesquisar tem um ou outra coisa válida tb no OmniFaces. To usando OmniFaces e Primefaces sem problemas!

carlos.scuna

Estava tentando entender este código aí… por que é necessário o método private Class<?> getClazz(FacesContext facesContext, UIComponent component) ???

Por que não podemos obter a classe direto do "obj" que vem como parâmetro no getAsString(FacesContext ctx, UIComponent component, Object obj) com obj.getClass()

Mesma coisa no component.getAttributes().put(id, getClazz(ctx, component).cast(obj));

Por que não podemos simplesmente fazer

component.getAttributes().put(id, obj); ???

felipe_gdr

Muito legal essa classe, vai me ajudar muito!!!

Fiz apenas um modificação, como uso a tag abaixo dentro de alguns selectOneMenus opcionais:

inclui esse teste no início do getAsString() para evitar ClassCastException:

if(obj instanceof String) { return obj.toString(); }

Valeu

lreao

Olá amigos, sou extremamente novato em programação, mas estudando muuuito e altas madrugadas!

Estou implementando envio de emails com picklist da biblioteca primefaces no jsf. E estou usando um converter para obter objetos do tipo Aluno, porém não estou conseguindo popular uma lista com os nomes dos alunos que estão no Banco de dados.
SEgue códigos:

//imports

@FacesConverter(value = "aluno")
@ManagedBean(name = "CommonsMail")
@SessionScoped
public class CommonsMail implements Converter {

    @EJB
    private  com.lopes.beans.AlunoFacade ejbFacade;
    private List<Aluno> nomes = new ArrayList();

//E aí tenho o metodo getAsObjetc - Meu problema

@Override
    public Object getAsObject(FacesContext facesContext, UIComponent component, String submittedValue) {
        if (submittedValue.trim().equals("")) {
            return null;
        } else {
            try {
                int number = Integer.parseInt(submittedValue);
                System.err.println("ID:" + number);

//Aqui preciso popular a List nomes com todos os nomes da tabela Aluno do BD. teoricamente a linha abaixo funcionaria, mas li em vários tópicos que não dá para usar EJB nesse método
            nomes = ejbFacade.findNomesComEmail();
                
              } catch (NumberFormatException exception) {
                throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", "Not a valid player"));
            }
        }
        return null;
    }

O que dá para eu fazer?? O Entity Manager também descobri que não dá para usar. Tentei fazer injeção na classe, porém não obtive sucesso!

Muito grato!

felipe_gdr

Pessoal, estou resgatando esse tópico pois trombei com um empecilho ao usar a entityConverter do primeiro post:

  • ao usá-la dentro de componentes que permitem múltipla seleção (selectManyCheckBox, por exemplo), é gerado um ClassCastException, pois a classe do objeto resultante da expressão value do componente é do tipo List (ou Set, ou qualquer coleção que você esteja usando no seu value), e não do tipo da entidade.

Minha solução foi passar um atributo para o componente nesses casos, e alterar o converter para que, quando o atributo estiver presente, ele pegar a classe informada no atributo, ao invés de checar o tipo do value:

<f:attribute name="classeParaEntityConverter" value="model.web.MinhaEntidade"/>

No converter:

private Class<?> getClazz(FacesContext facesContext, UIComponent component) {
		String classeParaEntityConverter = (String) component.getAttributes().get("classeParaEntityConverter");
		
		if(classeParaEntityConverter == null ) {
			return component.getValueExpression("value").getType(facesContext.getELContext());	
		} else {
			try {
				return Class.forName(classeParaEntityConverter);
			} catch (ClassNotFoundException e) {
				throw new IllegalArgumentException("A classe '" + classeParaEntityConverter + "' passada como atributo do compoente não foi encontrada");
			}
		}
	}

Alguém topou com esse problema também e talvez possa postar uma solução mais inteligente?!

Abs!

n10

Alguem achou a solução para componentes que permitem múltiplas seleções???
ou alguem pode indicar postando um exemplo de converter (genérico ou não) para componentes de multiplas seleções???

kdashu

Que iniciativa boa, parabêns.

Porém aqui não consegui implementá-lo, alguém pode me ajudar?
Possuo uma classe Contato cujo um dos atributo é do tipo empresa. Ambos anotados com @Id. O salvamento ocorre sem problemas, mas com o converter não consigo mais editar um contato.

Erro: “Error Rendering View[/views/cadastros/contato/edit.xhtml]: javax.el.PropertyNotFoundException: /views/cadastros/contato/edit.xhtml @26,150 value=”#{contatoMB.contato.empresa.id}": Target Unreachable, ‘empresa’ returned null".

Como estou usando:

<h:selectOneMenu id="contatoCodEmpresa" value="#{contatoMB.contato.empresa.id}" required="true" requiredMessage="Codigo da empresa é obrigatório">
   <f:converter converterId="entityConverter"/>		
   <f:selectItems value="#{empresaMB.listarTodos}" var="empresa" itemLabel="#{empresa.nome}" itemValue="#{empresa.id}"/>			
</h:selectOneMenu>

EmpresaMB:

...
public List<Empresa> getListarTodos(){
	return empresaService.listarTodos();
}

ContatoMB:

@ManagedBean
@RequestScoped
public class ContatoMB {

	@EJB
	private ContatoService contatoService;
	
	@EJB
	private EmpresaService empresaService;
	
	private Contato contato;
	private Empresa empresa;
	private Long empresaId;
...

web.xml

<welcome-file-list>
    <!--<welcome-file>/pages/protected/user/listAllDogs.xhtml</welcome-file>-->
    <welcome-file>/views/cadastros/contato/list.xhtml</welcome-file>
  </welcome-file-list>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
    <url-pattern>*.jsf</url-pattern>
    <url-pattern>*.xhtml</url-pattern>
  </servlet-mapping>
  
      <context-param>  
      <param-name>facelets.BUILD_BEFORE_RESTORE</param-name>  
      <param-value>false</param-value>  
    </context-param>

Estou usando em um projeto no padrão JEE. Deixei o converter no modulo web e anotei no web.xml. As outras classes estão no modulo ejb.
Alguma idéia?

A

Flavio,

Esse sua EntityConverter é muito bacana e me ajudou bastante aqui. So que eu precisei fazer uma pequena alteração no seu codigo para trabalhar com @viewscoped. segue a alteração.

public Object getAsObject(FacesContext context, UIComponent component, String value) {
		if (value != null) {
			Object objeto = JsfUtil.retornarObjetoSessao(value);
			JsfUtil.removerObjetoSessao(value);
			return objeto;
		}else{
			return null;
		}
	}

	public String getAsString(FacesContext context, UIComponent component, Object objeto) {
		try {
			if (objeto != null && !objeto.equals("")) {
				String id = getId(getClasse(context, component), objeto);
				if (id == null) {
					id = "";
				}
				
				JsfUtil.adicionarObjetoSessao(id.trim(), getClasse(context, component).cast(objeto));
				return id;
			}else{
				return null;
			}
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

vlw Flavio

Criado 6 de outubro de 2010
Ultima resposta 20 de nov. de 2013
Respostas 51
Participantes 18