I try to learn JSF and encountered on a problem connected with ManagedProperty. However I have tried to use it, it always failed - null exception pointer. What am I doing wrongly?
I have read some "similar posts" on stackoverflow, but they did not helped to me. (I use GlassFish 4.0, JSF 2.2, JDK 1.7, Netbeans 7.3.1 (Java EE pack) and Java EE 6.0)
<?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://xmlns.jcp.org/jsf/html">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
Hello from Facelets
<br/>
User: #{books.user.name}<br/>
1: #{param.pageId}<br/>
2: #{books.pageId}<br/>
<h:form>
<h:inputText value="#{user.name}" /><br/>
<h:inputText value="#{books.v1}" /><br/>
<h:inputText value="#{books.v2}" /><br/>
<h:inputText value="#{books.result}" /><br/>
<h:commandButton value="dodaj" action="#{books.add}" />
</h:form>
</h:body>
</html>
Book
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package tpsa.books.managed;
import javax.inject.Named;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedProperty;
/**
*
* #author tomasz
*/
#Named(value = "books")
#RequestScoped
public class Books {
private int v1;
private int v2;
private int result;
#ManagedProperty(value = "#{user}")
private User user;
#ManagedProperty(value="#{param.pageId}")
private int pageId;
/**
* Creates a new instance of Books
*/
public Books() {
}
public void add() {
result = v1 + v2;
}
public int getV1() {
return v1;
}
public void setV1(int v1) {
this.v1 = v1;
}
public int getV2() {
return v2;
}
public void setV2(int v2) {
this.v2 = v2;
}
public int getResult() {
return result;
}
public void setResult(int result) {
this.result = result;
}
public User getUser() {
if (user == null) {
System.err.println("WTF");
}
return user;
}
public void setUser(User user) {
this.user = user;
}
public int getPageId() {
return pageId;
}
public void setPageId(int pageId) {
this.pageId = pageId;
}
}
User
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package tpsa.books.managed;
import javax.inject.Named;
import javax.enterprise.context.SessionScoped;
import java.io.Serializable;
/**
*
* #author tomasz
*/
#Named(value = "user")
#SessionScoped
public class User implements Serializable {
private String name;
/**
* Creates a new instance of User
*/
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#ManagedProperty is managed bean annotaion, that can't be used with CDI. In above code, you used CDI bean i.e. #Named that is default in JSF 2.2. In this case you can't use ManagedProperty. Please read following line copied from Java EE docs of ManagedBean.
If this annotation is present on a class that does not have the
ManagedBean annotation, the implementation must take no action on this
annotation.
For details see the link:
http://docs.oracle.com/javaee/6/api/javax/faces/bean/ManagedProperty.html
So, use #Inject instead of #ManagedProperty for CDI bean.
#Inject
private User user;
Note that a getter/setter is unnecessary here.
Related
Given the component: https://www.primefaces.org/showcase/ui/input/oneMenu.xhtml
Full source for my test is at: https://github.com/dannymk/PrimefacesTest
Can't get the component to work with an Object using a converter. Not sure how to solve this one.
package org.primefaces.test;
import java.io.Serializable;
public class Player implements Serializable {
private Integer id;
private String name;
Player(Integer id, String name){
this.id = id;
this.name = name;
}
/**
* #return the id
*/
public Integer getId() {
return id;
}
/**
* #param id the id to set
*/
public void setId(Integer id) {
this.id = id;
}
/**
* #return the name
*/
public String getName() {
return name;
}
/**
* #param name the name to set
*/
public void setName(String name) {
this.name = name;
}
}
package org.primefaces.test;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
*
* #author Daniel Maldonado
*/
public class PlayerService implements Serializable{
private List<Player> available = new ArrayList<>();
public PlayerService() {
available.add(new Player(1, "One"));
available.add(new Player(2, "Two"));
available.add(new Player(3, "Three"));
available.add(new Player(4, "Four"));
available.add(new Player(5, "Five"));
}
/**
* #return the available
*/
public List<Player> getAvailable() {
return available;
}
/**
* #param available the available to set
*/
public void setAvailable(List<Player> available) {
this.available = available;
}
}
package org.primefaces.test;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
import javax.inject.Inject;
import javax.inject.Named;
/**
*
* #author Daniel Maldonado
*/
#Named
#FacesConverter(value = "playerConverter", managed = true)
public class PlayerConverter implements Converter<Player> {
#Inject
PlayerService service;
#Override
public Player getAsObject(FacesContext context, UIComponent component, String value) {
if (value != null && value.trim().length() > 0) {
Player found = service.getAvailable().stream()
.filter(player -> player.getName().equals(value))
.findAny()
.orElse(null);
return found;
}
return null;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Player o) {
if (o != null){
return o.getName();
}
return null;
}
}
package org.primefaces.test;
import java.io.Serializable;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.view.ViewScoped;
import javax.inject.Inject;
import javax.inject.Named;
import lombok.Data;
#Data
#Named
#ViewScoped
public class TestView implements Serializable {
private String hello;
private List<Player> available;
private Player selected;
#Inject
PlayerService service;
#PostConstruct
public void init() {
setHello("Welcome to PrimeFaces!!!");
this.available = service.getAvailable();
}
/**
* #return the available
*/
public List<Player> getAvailable() {
return available;
}
/**
* #param available the available to set
*/
public void setAvailable(List<Player> available) {
this.available = available;
}
/**
* #return the selected
*/
public Player getSelected() {
return selected;
}
/**
* #param selected the selected to set
*/
public void setSelected(Player selected) {
this.selected = selected;
}
/**
* #return the hello
*/
public String getHello() {
return hello;
}
/**
* #param hello the hello to set
*/
public void setHello(String hello) {
this.hello = hello;
}
}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>PrimeFaces Test - SelectOneMenu</title>
<h:outputScript name="test.js" />
</h:head>
<h:body>
<h1>#{testView.hello}</h1>
<h:form id="frmTest">
<p:panelGrid id="playerPanel" columns="2">
<p:outputLabel value="Selected player: #{testView.selected.name}" />
<p:selectOneMenu id="playerContainer" value="#{testView.selected}" var="p" converter="playerConverter" >
<f:selectItem itemLabel="Select One" itemValue="" />
<f:selectItems value="#{testView.available}" var="actual" itemLabel="#{actual.name}" itemValue="#{actual}" />
<p:column>
#{p.name}
</p:column>
<p:ajax event="valueChange" update="playerPanel" />
</p:selectOneMenu>
</p:panelGrid>
</h:form>
</h:body>
</html>
I get errors like:
java.lang.ClassCastException: java.lang.String cannot be cast to org.primefaces.test.Player
at org.primefaces.test.PlayerConverter.getAsString(PlayerConverter.java:38)
It does not seem to matter what I change and believe me I have tried plenty of implementations of the converter and in the view using "#{palyerConverter}" instead of "playerConverter" and still can't get it to work.
Your problem is at this line:
<f:selectItem itemLabel="Select One" itemValue="" />
Currently you are passing an empty string as the itemValue, which is not expected by your converter. Either use itemValue="#{null}" or totally remove the itemValue from the no select option.
See also:
Best way to add a "nothing selected" option to a selectOneMenu in JSF
Finally got it to work with just a few changes thanks to #Jasper de Vries:
<?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:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>PrimeFaces Test - SelectOneMenu</title>
<h:outputScript name="test.js" />
</h:head>
<h:body>
<h1>#{testView.hello}</h1>
<h:form id="frmTest">
<p:panelGrid id="playerPanel" columns="2">
<p:outputLabel value="Selected player: #{testView.selected.name}" />
<p:selectOneMenu id="playerContainer" value="#{testView.selected}" var="p" converter="playerConverter">
<f:selectItems value="#{testView.available}" var="actual" itemLabel="#{actual.name}" itemValue="#{actual}" />
<p:column>
#{p.name}
</p:column>
<p:ajax event="valueChange" update="playerPanel" />
</p:selectOneMenu>
</p:panelGrid>
</h:form>
</h:body>
</html>
and specially don't forget to implement the EQUALS method in the Object that is "listed":
package org.primefaces.test;
import java.io.Serializable;
import java.util.Objects;
public class Player implements Serializable {
private Integer id;
private String name;
Player(Integer id, String name){
this.id = id;
this.name = name;
}
#Override
public boolean equals(Object obj) {
if(obj == null)
return false;
if(!(obj instanceof Player))
return false;
Player compare = (Player) obj;
return Objects.equals(compare.getId(), this.getId());
}
#Override
public int hashCode() {
int hash = 1;
return hash * 31 + this.getName().hashCode();
}
/**
* #return the id
*/
public Integer getId() {
return id;
}
/**
* #param id the id to set
*/
public void setId(Integer id) {
this.id = id;
}
/**
* #return the name
*/
public String getName() {
return name;
}
/**
* #param name the name to set
*/
public void setName(String name) {
this.name = name;
}
}
My specs:
Dynamic Web Module 3.1
GlassFish Web Extensions 4.0
Java 1.8
JavaScript 1.0
JavaServer Faces 2.2
Server: glassfish-4.1.1
OS: Win 10
IDE: Version: Neon.2 Release (4.6.2)
Please, note that I have researched this topic and found several related posts.
e.g.
#ManagedProperty + #PostConstruct + init() = Nullpointer
#ManagedProperty injected AFTER #PostConstruct
But neither of the proposed solutions worked for me or applied to my situation.
I don't mix CDI and/or JSF and/or Spring. It's JSF 2.2 annotations only.
I inject #ManagedProperty("#{country}") Country country; to my ChangeCountrySystemEventListener but the value of the #ManagedProperty country is null.
I don't really see where the issue is. The Country constructor does get invoked.
Any tips where the issues is?
Here's my full code:
index.xhtml
<!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://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>
<title>Title</title>
</h:head>
<h:body>
<h3><h:outputText value="#{country.name}" /> </h3>
<h:form>
<h:commandButton
id="changeCountryNameBtn"
value="Change"
action="result"
actionListener="#{appBean.changeCountryName}"
/>
</h:form>
</h:body>
</html>
AppBean.java
package com.test.beans;
import javax.faces.application.Application;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
#ManagedBean
#SessionScoped
public class AppBean {
public void changeCountryName(ActionEvent ev) {
FacesContext context = FacesContext.getCurrentInstance();
Application app = context.getApplication();
app.publishEvent(context, ChangeCountrySystemEvent.class, ev.getSource());
System.out.println(">>>> AppBean.publishEvent(ChangeCountrySystemEvent) fired... " + ev.getSource());
}
}
ChangeCountrySystemEvent.java
package com.test.beans;
import javax.faces.event.SystemEvent;
public class ChangeCountrySystemEvent extends SystemEvent {
private static final long serialVersionUID = -1587717461942271611L;
public ChangeCountrySystemEvent(Object source) {
super(source);
System.out.println(">>>> ChangeCountrySystemEvent.class :: constructor invoked!");
}
}
ChangeCountrySystemEventListener.java
package com.test.beans;
import javax.faces.bean.ManagedProperty;
import javax.faces.context.FacesContext;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
public class ChangeCountrySystemEventListener implements SystemEventListener {
#ManagedProperty("#{country}")
Country country;
// getters and setters
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
public ChangeCountrySystemEventListener(FacesContext fc) {
super();
System.out.println(">>>> ChangeCountrySystemEventListener.class :: Listener constructor invoked!!!");
}
#Override
public void processEvent(SystemEvent se) {
if (country != null) {
country.setName("Sweden");
System.out.println(">>>> ChangeCountrySystemEventListener.class :: SYSTEM EVENT PROCESSED... <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ");
} else if (country == null) {
System.out.println(">>>> ChangeCountrySystemEventListener.class :: processEvent() > country managed property is EMPTY !!!!");
}
}
#Override
public boolean isListenerForSource(Object source) {
return true; // needs to be set to true, otherwise "processEvent" won't be called...
}
}
Country.java
package com.test.beans;
import javax.annotation.PostConstruct;
import javax.faces.application.Application;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
#ManagedBean(name = "country", eager = true)
#SessionScoped
public class Country {
private String name = "Norway";
public Country() {
System.out.println(">>>> Country constructor called...");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#PostConstruct
public void init() {
FacesContext fc = FacesContext.getCurrentInstance();
Application app = fc.getApplication();
app.subscribeToEvent(ChangeCountrySystemEvent.class, new ChangeCountrySystemEventListener(fc));
System.out.println(">>>> Country.class :: app.subscribeToEvent() called... ");
}
}
Console output:
2017-04-02T21:49:51.392-0300|Info: JSF_TestProject was successfully deployed in 579 milliseconds.
2017-04-02T21:49:52.251-0300|Info: >>>> Country constructor called...
2017-04-02T21:49:52.277-0300|Info: >>>> ChangeCountrySystemEventListener.class :: Listener constructor invoked!!!
2017-04-02T21:49:52.277-0300|Info: >>>> Country.class :: app.subscribeToEvent() called...
2017-04-02T21:50:16.572-0300|Info: >>>> ChangeCountrySystemEvent.class :: constructor invoked!
2017-04-02T21:50:16.572-0300|Info: >>>> ChangeCountrySystemEventListener.class :: processEvent() > country managed property is EMPTY !!!!
2017-04-02T21:50:16.572-0300|Info: >>>> AppBean.publishEvent(ChangeCountrySystemEvent) fired... javax.faces.component.html.HtmlCommandButton#424c250c
The ManagedProperty can be used on a field of a class annotated with ManagedBean to inject a value into this property.
If this annotation is present on a class that does not have the ManagedBean annotation, the implementation must take no action on this annotation.
Please see http://docs.oracle.com/javaee/6/api/javax/faces/bean/ManagedProperty.html
Since the class ChangeCountrySystemEventListener is not annotated with ManagedBean, no action is taken on the ManagedProperty field country and its null.
I keep getting this message when trying to run the code below. It is supposed to show the test from the document to be uploaded. This is a Java Server Faces program. Why am I getting this message:Unable to find matching navigation case with from-view-id '/upload.xhtml' for action 'vidClass2.upload' with outcome 'vidClass2.upload'
<?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://xmlns.jcp.org/jsf/html">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body >
<h:form id="form" enctype="multipart/form-data" prependId="false">
<p><h:inputFile id="file" value="#{vidClass2.vidData}">
</h:inputFile>
</p>
<br/>
<h:commandButton id="button" value ="upload" action ="vidClass2.upload">
</h:commandButton>
<p id="textOutput">Text: #{vidClass2.vidName}</p>
</h:form>
</h:body>
</html>
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
import javax.servlet.http.Part;
/**
*
* #author David Jennings
*/
#ManagedBean(name = "vidClass2")
#RequestScoped
public class VidClass {
private String userName;
private String vidName;
private Part vidData;
/**
* Creates a new instance of VidClass
*/
public VidClass() {
}
/**
* #return the userName
*/
public String getUserName() {
return userName;
}
/**
* #param userName the userName to set
*/
public void setUserName(String userName) {
this.userName = userName;
}
/**
* #return the vidName
*/
public String getVidName() {
return vidName;
}
/**
* #param vidName the vidName to set
*/
public void setVidName(String vidName) {
this.vidName = vidName;
}
/**
* #return the vidData
*/
public Part getVidData() {
return vidData;
}
/**
* #param vidData the vidData to set
*/
public void setVidData(Part vidData) {
this.vidData = vidData;
}
public void upload() {
if (null != vidData) {
try {
InputStream is = vidData.getInputStream();
vidName = new Scanner(is).useDelimiter("\\A").next();
} catch (IOException ex) {
}
}
}
}
enter image description here
in case you are using JavaBeans components on yours page you should use expression #{bean.method} in action.
<h:commandButton id="button" value ="upload" action ="#{vidClass2.upload}"/>
I am reading Java EE 7 tutorial. In chapter 13.12, there is an example application, ajaxguessnumber. I run the example in Glassfish 4 and everything works fine. I then put System.out.println in bean constructor and I realized that the constructor is being called twice during initial page loading. Why is that so, even for #SessionScoped bean?
Here is the xhtml file
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>
<h:outputStylesheet library="css" name="default.css"/>
<title>Ajax Guess Number Facelets Application</title>
</h:head>
<h:body>
<h:form id="AjaxGuess">
<h:graphicImage value="#{resource['images:wave.med.gif']}"
alt="Duke waving his hand"/>
<h2>
Hi, my name is Duke. I am thinking of a number from
#{dukesNumberBean.minimum} to #{dukesNumberBean.maximum}.
Can you guess it?
</h2>
<p>
<h:inputText
id="userNo"
title="Type a number from 0 to 10:"
value="#{userNumberBean.userNumber}">
<f:validateLongRange
minimum="#{dukesNumberBean.minimum}"
maximum="#{dukesNumberBean.maximum}"/>
</h:inputText>
<h:commandButton id="submit" value="Submit" >
<f:ajax execute="userNo" render="outputGroup" />
</h:commandButton>
</p>
<p>
<h:panelGroup layout="block" id="outputGroup">
<h:outputText id="result" style="color:blue"
value="#{userNumberBean.response}" rendered="#{!facesContext.validationFailed}"/>
<h:message id="errors1"
showSummary="true"
showDetail="false"
style="color: #d20005;
font-family: 'New Century Schoolbook', serif;
font-style: oblique;
text-decoration: overline"
for="userNo"/>
</h:panelGroup>
</p>
</h:form>
</h:body>
</html>
Here is the bean DukesNumberBean
package javaeetutorial.ajaxguessnumber;
import java.io.Serializable;
import java.util.Random;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
#Named
#SessionScoped
public class DukesNumberBean implements Serializable {
private Integer randomInt = null;
private long maximum = 10;
private long minimum = 0;
public DukesNumberBean() {
System.out.println("Inside DukesNumberBean constructor");
Random randomGR = new Random();
long range = maximum+minimum+1;
randomInt = (int) (minimum + randomGR.nextDouble()*range);
System.out.println("Duke's number: " + randomInt);
}
public long getMaximum() {
return (this.maximum);
}
public void setMaximum(long maximum) {
this.maximum = maximum;
}
public long getMinimum() {
return (this.minimum);
}
public void setMinimum(long minimum) {
this.minimum = minimum;
}
/**
* #return the randomInt
*/
public Integer getRandomInt() {
return randomInt;
}
/**
* #param randomInt the randomInt to set
*/
public void setRandomInt(Integer randomInt) {
this.randomInt = randomInt;
}
}
And here's the bean UserNumberBean
package javaeetutorial.ajaxguessnumber;
import java.io.Serializable;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
#Named
#RequestScoped
public class UserNumberBean implements Serializable {
#Inject
DukesNumberBean dukesNumberBean;
private Integer userNumber = null;
String response = null;
public UserNumberBean()
{
System.out.println("Inside constructor");
}
public void setUserNumber(Integer user_number) {
userNumber = user_number;
}
public Integer getUserNumber() {
return userNumber;
}
public String getResponse() {
if ((userNumber != null) && (userNumber.compareTo(dukesNumberBean.getRandomInt()) == 0)) {
return "Yay! You got it!";
}
if (userNumber == null) {
return null;
} else {
return "Sorry, " + userNumber + " is incorrect.";
}
}
}
Any help would be much appreciated. Thank you.
This happens because the scoped objects and injected by CDI using proxies. CDI has first to create a proxy of your object that is a subclass of your real object - this is the first time your constructor gets called because when you instantiate a subclass the parent constructor is always called. And then CDI instantiates your real object in order to inject it - this is the second time your constructor gets called.
A better approach is to put your initialization login in a #PostConstruct method like this.
#PostConstruct
public void init() {
Random randomGR = new Random();
long range = maximum+minimum+1;
randomInt = (int) (minimum + randomGR.nextDouble()*range);
System.out.println("Duke's number: " + randomInt);
}
It will be called only once - when the real object gets instantiated. More info about the proxies you can find here.
EDIT:
I just found another explanation of the problem - here.
I am getting the null Converter error for what I thought was a very simple scenario:
<!-- My View -->
<ui:composition template="/template/template_v1.xhtml"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html">
<!-- Simplified for clarity -->
<h:form>
<div class="block-panel customer-data">
<h:outputLabel for="txtUsername">Username:</h:outputLabel>
<h:inputText id="txtUsername" name="Username"
value="#{userBean.user.id}"
styleClass="text" />
<rich:message id="errorUsername" for="txtUsername"/>
</div>
<!-- Other fields omitted for clarity -->
</h:form>
/* The User Bean - simplified */
#ManagedBean
#ViewScoped
public class UserBean implements Serializable {
private User user;
public User getUser() {
// Contains logic for reading a user from the database or creating a new
// user object
return user;
}
public void setUser(User user) {
this.user = user;
}
}
/* The user Entity - Simplified */
#Entity
#Table(name = "user")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "user_type", discriminatorType = DiscriminatorType.STRING)
public class User implements IEntity<String>, Serializable {
private static final long serialVersionUID = 1L;
private String id;
#Id
#Column(name = "username", length = 50)
#NotNull(message = "{userIdMandatory}")
#Size(max = 50)
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
}
/* The IEntity interface */
public interface IEntity<ID extends Serializable> {
ID getId();
void setId(final ID pId);
}
So essentially I'm trying to bind a string property of my user entity to a inputText field. As far as I'm concerned there should be no need for a conversion so I shouldn't be getting the error I'm seeing.
Interestingly, if I add the following getter and setter to my entity:
public String getTmpId() {
return this.id;
}
public void setTmpId(String id) {
this.id = id;
}
And then make the necessary changes to my view to bind to tmpId rather than id, everything works as expected.
This seems like a bug to me either to do with the fact that I am binding to a getter/setter defined in an interface, defined in a generic interface or because the getter is marked with the Id attribute. I would appreciate someone else's ideas however.
As an aside, I have inherited this design and don't particularly like it so I may just end up refactoring it to introduce a new username property rather than trying to use the Id.
To the best of my knowledge I believe this is caused by an obscure bug in BeanELResolver which is used to get the type of the property being bound to - rather than returning String it is returning Serializable, for which there is no converter and hence the error I am seeing.
It's not particularly elegant, but I have worked around this by adding a userId property to my userBean and then binding to that in my view instead of the Id property on the user entity. I then use that value to set the Id manually on the entity when I save it:
<!-- My New View -->
<ui:composition template="/template/template_v1.xhtml"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html">
<!-- Simplified for clarity -->
<h:form>
<div class="block-panel customer-data">
<h:outputLabel for="txtUsername">Username:</h:outputLabel>
<h:inputText id="txtUsername" name="Username"
value="#{userBean.userId}"
styleClass="text" />
<rich:message id="errorUsername" for="txtUsername"/>
</div>
<!-- Other fields omitted for clarity -->
/* The New User Bean - simplified */
#ManagedBean
#ViewScoped
public class UserBean implements Serializable {
private string userId;
private User user;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public User getUser() {
// Contains logic for reading a user from the database or creating a new
// user object
return user;
}
public void setUser(User user) {
this.user = user;
}
public void saveUser() {
if (user.getId() == null) {
user.setId(userId);
}
// Actual saving omitted for brevity
}
}