I have an application written in Java EE with EJB and JSF. I wanted to create a JSF converter which has an EJB injected:
#ManagedBean(name="addressConverter")
#RequestScoped
public class AddressConverter implements Converter {
#EJB(name = "AddressDao")
private AddressDao addressDao;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return addressDao.find(Long.valueOf(value));
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return String.valueOf(((Address) value).getId());
}
}
When in application any exception appears, the injected AddressDao is missing and I get the meeste that "...env.../AddressDao" is missing (sorry, I can't get the exact error right now).
How do I handle that?
Related
Server: Payara 5.183.
When the converter is used, a NullPointerException is raised because the injected EJB is null (System.out.println prints "null").
It works (injection not null) if I use a workaround used before JSF 2.3: replacement of #FacesConverter by #Name.
Converter:
#FacesConverter(value = "compteConverter", managed = true)
public class CompteConverter implements Converter<CompteBancaire> {
#EJB
private GestionnaireCompte gestionnaireCompte;
#Override
public CompteBancaire getAsObject(FacesContext context, UIComponent component, String id) {
if (id == null || id.isEmpty()) {
return null;
}
try {
System.out.println("*****EJB gestionnaireCompte=" + gestionnaireCompte);
return gestionnaireCompte.getCompte(Long.parseLong(id));
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage("Id de compte invalide"), e);
}
}
#Override
public String getAsString(FacesContext arg0, UIComponent arg1, CompteBancaire compte) { ... }
Usage of this converter:
<ui:define name="metadata">
<f:metadata>
<f:viewParam name="id" value="#{operations.compte}"
converter="compteConverter"/>
Is it a bug of Mojarra/Payara (managed = true is not working) or can you help me to find my error?
Managed converters don't work by default. To make them work I added a CDI bean annotated by #FacesConfig (for JSF 2.3 to be used) and #ApplicationScoped (it will be a CDI bean with this annotation).
Here is my code
Pojo
public class Deal implements Serializable {
private int id;
private String name;
private String description;
private Customer customer;
//getter setter omitted
}
public class Customer implements Serializable {
private int id;
private String name;
private String email;
private String phone;
//getter setter and equal hashcode omitted
}
Managed Bean
#ManagedBean(name="dealBean")
#ViewScoped
public class DealBean implements Serializable {
private List<Customer> customerList;
private List<Deal> dealList;
private Deal deal;
#PostConstruct
public void init() {
deal = new Deal();
dealList = new ArrayList<Deal>();
customerList = new ArrayList<Customer>();
customerList.add(new Customer(1, "MPRL", "mprl#mail.com", "1234455"));
customerList.add(new Customer(2, "Total", "total#mail.com", "3434323"));
customerList.add(new Customer(3, "Petronas", "petronas#mail.com", "8989876"));
}
//getter setter omitted
}
Customer Converter
#FacesConverter("customerConverter")
public class CustomerConverter implements Converter {
#Override
public Object getAsObject(FacesContext arg0, UIComponent arg1, String customerID) {
DealBean dealBean = (DealBean) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("dealBean");
if (dealBean != null) {
List<Customer> customerList = dealBean.getCustomerList();
for (Customer customer : customerList) {
if (customerID.equals(String.valueOf(customer.getId()))) {
return customer;
}
}
}
return null;
}
#Override
public String getAsString(FacesContext arg0, UIComponent arg1, Object obj) {
if (obj != null) {
return String.valueOf(((Customer)obj).getId());
}
return null;
}
}
XHTML
Customer : <h:selectOneMenu id="customer" value="#{dealBean.deal.customer}">
<f:converter converterId="customerConverter" />
<f:selectItems value="#{dealBean.customerList}" var="cus"
itemLabel="#{cus.name}" itemValue="#{cus}" />
</h:selectOneMenu>
When the managed bean is in request or session scope, the Customer pojo is set correctly to Deal pojo. The problem is when the managed bean is in View scope, the Customer pojo is set to Deal pojo as NULL.
I am using JSF 2.2.0
Thanks much for the help in advance.
It's not the converter, is the view scoped the one broken:
Since you're using JSF tags, you cannot use #ViewScoped annotation, because it was removed from specification and recovered only for CDI usage. You could use omnifaces view scoped or the components of apache myFaces (I personally recommend omnifaces).
You can confirm this creating a
System.out.print("Creating");
in the constructor and checking how is called each Ajax request, so the bean is not recovered and since is marked as view and is a partial request, the values are not setted again (unless you send all the form, which is not a nice solution), other workaround could be making the bean request and recover all the data each request, making it Session (but will be alive for the session), or the #ConvesationScoped, in which you'll have to destroy and start the conversation manually.
Again, my first recommendation could be change to a Java ee server compliant and use the CDI annotations since JSF are being depreciated and not updated anymore
Is it possible to know what UIComponent is calling the getter of some property in managed bean?
#ManagedBean
#SessionScoped
public class SomeBean {
private String color;
public String getColor() {
// Here I would like to know which UIComponent called this method.
return color;
}
}
You can use UIComponent#getCurrentComponent() for this.
public String getColor() {
UIComponent component = UIComponent.getCurrentComponent(FacesContext.getCurrentInstance());
// ...
return color;
}
I'm showing a list of suggested items in an autocomplete input element. For that I need to implement a converter to convert the entity<entityName, entityId> to entityName & vice versa. However while implementing that I realized that I had to read the DB more than 1 time to find the corresponding entityId for the chosen entityName(while getAsObject()), I am wondering why isn't this stored somewhere client side so that the entityId could be passed when the entityname is selected.
Is there any way I could avoid this extra read?
This is indeed "by design" and perhaps a little oversight in the JSF spec. You can in theory perfectly avoid it by extracting the items from the UIComponent argument and comparing against them instead. It's however a bit of work. My colleague Arjan Tijms has written a blog about this: Automatic to-Object conversion in JSF selectOneMenu & Co.
Here's an extract of relevance; the below is the base converter which you'd need to extend instead:
public abstract class SelectItemsBaseConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return SelectItemsUtils.findValueByStringConversion(context, component, value, this);
}
}
Here's the SelectItemsUtils class which is partly copied from Mojarra's source:
public final class SelectItemsUtils {
private SelectItemsUtils() {}
public static Object findValueByStringConversion(FacesContext context, UIComponent component, String value, Converter converter) {
return findValueByStringConversion(context, component, new SelectItemsIterator(context, component), value, converter);
}
private static Object findValueByStringConversion(FacesContext context, UIComponent component, Iterator<SelectItem> items, String value, Converter converter) {
while (items.hasNext()) {
SelectItem item = items.next();
if (item instanceof SelectItemGroup) {
SelectItem subitems[] = ((SelectItemGroup) item).getSelectItems();
if (!isEmpty(subitems)) {
Object object = findValueByStringConversion(context, component, new ArrayIterator(subitems), value, converter);
if (object != null) {
return object;
}
}
} else if (!item.isNoSelectionOption() && value.equals(converter.getAsString(context, component, item.getValue()))) {
return item.getValue();
}
}
return null;
}
public static boolean isEmpty(Object[] array) {
return array == null || array.length == 0;
}
/**
* This class is based on Mojarra version
*/
static class ArrayIterator implements Iterator<SelectItem> {
public ArrayIterator(SelectItem items[]) {
this.items = items;
}
private SelectItem items[];
private int index = 0;
public boolean hasNext() {
return (index < items.length);
}
public SelectItem next() {
try {
return (items[index++]);
}
catch (IndexOutOfBoundsException e) {
throw new NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}
Here's how you should use it for your own converter, you only have to implement getAsString() (the getAsObject() is already handled):
#FacesConverter("someEntitySelectItemsConverter")
public class SomeEntitySelectItemsConverter extends SelectItemsBaseConverter {
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return ((SomeEntity) value).getId().toString();
}
}
Update the above concept has ended up in JSF utility library OmniFaces in flavor of the following converters:
SelectItemsConverter - for <f:selectItem(s)> based on Object#toString().
SelectItemsIndexConverter - for <f:selectItem(s)> based on item's index.
ListConverter - for e.g. <p:autoComplete> based on Object#toString()
ListIndexConverter - for e.g. <p:autoComplete> based on item's index.
The only way I have found to do this so that your converter does not need to access the DB, is to make the Converter a managed bean so that it can access some other bean which stores the list of suggested values of the AutoComplete component.
Something like this:
#ManagedBean
#RequestScoped
public class EntityConverter implements Converter
{
#ManagedProperty(value = "#{autoCompleteBean}")
private AutoCompleteBean autoCompleteBean;
public void setAutoCompleteBean(AutoCompleteBean autoCompleteBean)
{
this.autoCompleteBean = autoCompleteBean;
}
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value)
{
final List<Entity> entities = autoCompleteBean.getCachedSuggestions();
for (final Enity entity : entities)
{
if (entity.getIdAsString().equals(value))
{
return entity;
}
}
throw new IllegalStateException("Entity was not found!");
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value)
{ ... }
In your jsf page, make sure you reference the converter as a bean. ie:
<p:autoComplete value="#{autoCompleteBean.selectedEntity}"
completeMethod="#{autoCompleteBean.getSuggestions}" var="theEntity"
itemValue="#{theEntity}" itemLabel=#{theEntity.someValue}
converter="#{entityConverter}">
I'm trying to inject a ManagedBean in my FacesConverted the following way:
#ManagedBean
#RequestScoped
#FacesConverter(forClass = Group.class)
public class GroupConverter implements Converter {
#ManagedProperty("#{groupService}")
private GroupService groupService;
#Override
public Group getAsObject(FacesContext context, UIComponent arg1,
String groupName) {
return groupService.findGroupByName(groupName);
}
#Override
public String getAsString(FacesContext arg0, UIComponent arg1, Object group) {
return ((Group) group).getName();
}
public GroupService getGroupService() {
return groupService;
}
public void setGroupService(GroupService groupService) {
this.groupService = groupService;
}
}
The problem is that groupService isn't being injected and I get a NullPointerEx. Shouldn't it be autowired automatically since it's also a ManagedBean? It all works when I change "getAsObject" to "return new Group();" obviously.
Any ideas?
It is likely that you are not resolving the managed bean name.
#ManagedBean(name = "myConverter")
#RequestScoped
#FacesConverter(value = "myConverter")
public class MyConverter implements Converter {
For example, consider these two components:
<h:inputText converter="myConverter" value="#{foo.prop}" />
<h:inputText converter="#{myConverter}" value="#{bar.prop}" />
When the converter is set on the first component, it will be created by Application.createConverter. A converter is not a managed bean. The same rules apply if you match a converter by type.
In the second component, a value expression is used to return a class that implements Converter. This uses the usual managed bean mechanisms. In this case, the #FacesConverter annotation is irrelevant.