[RESOLVIDO] Primefaces Chips (componente web)

Eu tenho um CRUD e quero mudar o inputTexArea:

<p:inputTextarea id=“tags” value="#{myController.selected.tags}" />

Para o novo componente chips do Primefaces:

<p:chips id=“tags” value="#{myController.selected.tags}" />

Trecho da minha entity class:

@Lob
@Size(max = 2147483647)
@Column(name = “tags”)
private String tags;
//Getters e Setter omitidos

O método get funciona, pois as tags são exibidas dentro do inputText como esperado:

public List getTags() {
return Arrays.asList(tags.split(","));
}

Porém, o método set não está funcionando, quando eu clico em Salvar, é lançada a Exception:

public void setTags(List tags) {
this.tags = String.join(",", tags);
}
java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.lang.CharSequence
at org.hibernate.validator.internal.constraintvalidators.SizeValidatorForCharSequence.isValid(SizeValidatorForCharSequence.java:33)
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:281)

Descobri que o p:chips usa uma List para armazenar os valores, daí usei get e set abaixo:

public List getTags() {
return Arrays.asList(tags.split(","));
}
public void setTags(List tags) {
this.tags = String.join(",", tags);
}

Mas daí é lançada outra Exception:

java.util.ArrayList cannot be cast to java.lang.CharSequence

Alguém pode me ajudar a resolver isso ?

O Chips usa uma lista de string e não uma lista

private List<String> lista;

Basta ter a lista preenchida para mostrar os valores no Chips

Oi Mike!

Você quis dizer para mapear a coluna como uma List? Assim:

@Column(name = “DESCRIPTION”)
//private String description;
private List description;

Pois eu fiz isso e lançou a Exception:

Exception [EclipseLink-3002] (Eclipse Persistence Services - 2.6.1.v20150605-31e8258): org.eclipse.persistence.exceptions.ConversionException Exception Description: The object [Accounting Application], of class [class java.lang.String], from mapping [org.eclipse.persistence.mappings.DirectToFieldMapping[description-->APP.PRODUCT.DESCRIPTION]] with descriptor [RelationalDescriptor(entity.Product --> [DatabaseTable(APP.PRODUCT)])], could not be converted to [class [B].

Acredito não ser possível mapear uma coluna string como uma List…
Me ajude! rsrs

O que eu quis dizer foi:
O componente Chips usa uma lista de Strings para preenche-lo

private List<String> lista = new ArrayList<>();

Se você deixar essa lista com alguns valores, automaticamente quando a tela renderizar, o componente Chips vai estar preenchido com esses valores

Ex:

@Named
@ViewScoped
public class TestBean implements Serializable {
	private String test;

	private List<String> palavras = new ArrayList<>();
	
	@PostConstruct
	public void  init()
	{
		palavras.add("Primeira");
		palavras.add("Segunda");
	}
	public void mostrar()
	{
		for(String p: palavras)
		{
			System.out.println(p);
		}
	}
	
	public List<String> getPalavras() {
		return palavras;
	}

	public void setPalavras(List<String> palavras) {
		this.palavras = palavras;
	}
}
<h:body>
		<h:form>
			<p:chips value="#{testBean.palavras}"></p:chips>
		</h:form>
	</h:body>

Você esta usando um List, o certo seria

List<String>

Então Mike, note que estou salvando/recuperando os dados do Banco de Dados (JPA 2.1).
Para exibir o chips na tela, o método get já estava/está funcionando:

public List getDescription() {
return Arrays.asList(description.split(",")); //quando salvo no Banco de Dados usando chips, cada tag é delimitada por uma vírgula, por isso é necessário usar o split; se não fizer esse split como está aí, será exibido um único chips juntamente com todas as respectivas vírgulas.
}

O problema está acontecendo ao salvar no Banco de Dados, que, fazendo da forma como você colocou no seu exemplo (que é a correta de acordo com a especificação Bean Validation), o Hibernate está lançando a Exception:

java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.lang.CharSequence
at org.hibernate.validator.internal.constraintvalidators.SizeValidatorForCharSequence.isValid(SizeValidatorForCharSequence.java:33)
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:281)

Já vi uns exemplos (ex. 1, Primefaces User Guide 6.2, página 104), mas nenhum salva no Banco de Dados através da JPA, que é o cenário que gera essa exception.

Já fiz essa pergunta em outro Forum e tive orientação de um desenvolvedor do próprio Primefaces team e ele me orientou a verificar com o pessoal do Hibernate Validator team, mas o Forum Hibernate foi fechado e está apenas como read-only.

Me ajuda cara, pois não sei mais o que fazer.

Eu pensei que você estava querendo pegar do banco e colocar o valor no Chips, mas você quer pegar do Chips e colocar no banco, certo?

Aqui os dados não vem com virgulas,
Se eu escrever no Chips: “Primeira” “Segunda” e "Terceira"
me retorna "Primeira” “Segunda” “Terceira”

Você esta usando

List 
ou
List<String> ?

Oi Mike!

Então, eu preciso pegar os valores que o usuário inserir no chips e salvá-los no Banco de Dados, já tentei de diversas formas:

@Size(max = 50)
@Column(name = “DESCRIPTION”)
private String description;

public String getDescription() {
return Arrays.asList(description.split(",")).toString();
}

public void setDescription(String description) {
this.description = String.join(",", description).toString();
}

//lança a Exception:
java.lang.String cannot be cast to java.util.List

//---------------------------------------------------------------------
@Size(max = 50)
@Column(name = “DESCRIPTION”)
private String description;

public String getDescription() {
return description;
}

public void setDescription(String description) {
    this.description = description;
}

//lança a Exception:
java.util.ArrayList cannot be cast to java.lang.CharSequence

//---------------------------------------------------------------------
@Size(max = 50)
@Column(name = “DESCRIPTION”)
private String description;

public String getDescription() {
return Arrays.asList(description.split(",")).toString();
}

public void setDescription(String description) {
this.description = String.join(",", description);
}

//lança a Exception:
java.lang.String cannot be cast to java.util.List

//---------------------------------------------------------------------

@Size(max = 50)
@Column(name = “DESCRIPTION”)
private String description;

public List getDescription() {
return Arrays.asList(description.split(","));
}

public void setDescription(List description) {
this.description = String.join(",", description);
}

//lança a Exception:
java.util.ArrayList cannot be cast to java.lang.CharSequence

//---------------------------------------------------------------------

@Size(max = 50)
@Column(name = “DESCRIPTION”)
private List description;

public List getDescription() {
return description;
}

public void setDescription(List description) {
this.description = description;
}

//lança a Exception:
The object [Identity Server], of class [class java.lang.String], from mapping [org.eclipse.persistence.mappings.DirectToFieldMapping[description–>APP.PRODUCT.DESCRIPTION]] with descriptor [RelationalDescriptor(entity.Product --> [DatabaseTable(APP.PRODUCT)])], could not be converted to [class [B].

//---------------------------------------------------------------------

O que fazer ?

Eu ja te dei a resposta, você tem que usar uma Lista de String

	private List<String> descricao;

    public void mostrar()
	{
		for(String s: descricao)
		{
			System.out.println(s);
		}
	}

    public List<String> getDescricao() {
		return descricao;
	}

	public void setDescricao(List<String> descricao) {
		this.descricao = descricao;
	}
<h:form>
		<p:chips value="#{seuBean.descricao}"></p:chips>
        <p:commandButton value="Mostrar no console" actionListener="#{testeBean.mostrar()}"></p:commandButton>
</h:form>

Oi Mike!

Eu já usei a Lista de Strings e, dessa forma, para salvar no Banco de Dados não funciona, pois a Exception é lançada:

org.eclipse.persistence.exceptions.ConversionException Exception Description: The object [chipsExemplo1, chipsExemplo2, chipsExemplo3], of class [class java.lang.String], from mapping [org.eclipse.persistence.mappings.DirectToFieldMapping[description--> APP.PRODUCT.DESCRIPTION]] with descriptor [RelationalDescriptor(entity.Product --> [DatabaseTable(APP.PRODUCT)])], could not be converted to [class [B].

Como passar uma lista de strings via JPA para salvar no Banco de Dados? Ou há alguma outra forma para se fazer isso (desde que esteja usando JPA) ?

Talvez o problema ai seja o mapeamento, mas o que você pode fazer é iterar a lista e passar os valores para uma String

Sim, o problema deve ser no mapeamento.

Para iterar a lista e passar os valores para uma String, eu usei o join do Java 8, o form até abre, as tags são exibidas corretamente, eu consigo incluir mais tags, mas quando clico Salvar, aí é lançada a Exception:

java.util.ArrayList cannot be cast to java.lang.CharSequence

Como tratar uma Exception dessas ?

Posta o código da iteracao e de salvar

Fiz aqui pra você, você estava convertendo algo errado…

Salvei tanto como lista quanto String.
Salvando como lista, uma nova tabela é criada pelo banco.
Estou usando CDI e o banco Postgres, ai é só adaptar…

Bean

package bean;

import java.io.Serializable;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

import model.Palavra;

@Named
@RequestScoped
public class TesteBean implements Serializable{
	
	private Palavra palavra = new Palavra();
	
	@PersistenceContext
	private EntityManager em;

	@Transactional
	public String salvar()
	{
		palavra.setPalavra("");
		for(String s: palavra.getPalavras())
		{
			palavra.setPalavra(palavra.getPalavra() + ", " + s);
		}
		palavra.setPalavra(palavra.getPalavra().substring(2));
		System.out.println(palavra.getPalavra());
		em.persist(palavra);
		return null;
	}
	
	public Palavra getPalavra() {
		return palavra;
	}

	public void setPalavra(Palavra palavra) {
		this.palavra = palavra;
	}
	
	
}

Model

package model;
import java.util.List;

import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;

@Entity
@SequenceGenerator(allocationSize = 1, initialValue = 1, name="seq_palavra_id", sequenceName="seq_palavra_id")
public class Palavra {
	
	@Id
	@GeneratedValue(generator="seq_palavra_id", strategy=GenerationType.SEQUENCE)
	private Integer id;
	
	private String palavra;

	@ElementCollection(fetch = FetchType.LAZY)
	private List<String> palavras;
	
	public List<String> getPalavras() {
		return palavras;
	}

	public void setPalavras(List<String> palavras) {
		this.palavras = palavras;
	}

	public Integer getId() {
		return id;
	}

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

	public String getPalavra() {
		return palavra;
	}

	public void setPalavra(String palavra) {
		this.palavra = palavra;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Palavra other = (Palavra) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}
	
}

XHTML

<?xml version="1.0" encoding="UTF-8" ?>
<!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:h="http://java.sun.com/jsf/html"
		xmlns:f="http://java.sun.com/jsf/core"
		xmlns:ui="http://java.sun.com/jsf/facelets"
		xmlns:p="http://primefaces.org/ui">
		
	<h:head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<title>JSF</title>
	</h:head>
	
	<h:body>
		<h:form>
			<p:chips value="#{testeBean.palavra.palavras}"></p:chips>
			<p:commandButton value="Salvar" action="#{testeBean.salvar()}"></p:commandButton>
		</h:form>
	</h:body>
</html>
1 curtida

Oi Mike!

Muito obrigado, sua solução me ajudou demais!

Adaptei aqui e agora está tudo funcionando:

 //Mapeamento da coluna no entity bean:
@Lob
@Size(max = 2147483647)
@Column(name = "tagsColab")
private String tagsColab;

//métodos no entity bean:
public String getTagsColab() {
    return tagsColab;
}

public void setTagsColab(String tagsColab) {
    this.tagsColab = tagsColab;
}

public List<String> getTagsColabList() {
    if (tagsColab==null) {
        tagsColab = ""; // workaround que fiz para evitar NullPointerException ao tentar carregar o formulário quando ia inserir um novo registro
    }
    return Arrays.asList(tagsColab.split(","));
}

public void setTagsColabList(List<String> tagsColab) {
    this.tagsColab = (tagsColab); 
}

// página JSF (para criar ou alterar registro):
<p:chips id="tagsColab" value="#{colabController.selected.tagsColabList}" />

Top! qualquer coisa to ae haha