JSF JSF 2.3 Converter injection/persistence context -> null

Gort

Mitglied
Hallo,
ähnlich wie in dem thread "Converter + Zugriff auf DAO und EntityManager" will ich per jsf in einem selectOneMenu eine Entity auswählen um dann damit zu arbeiten.
Wenn ich https://stackoverflow.com/questions...ncecontext-inject-autowired-etc-in-facesconve glauben schenken darf, sollte es nun mit jsf2.3 möglich sein 'einfach so' die ServiceBean bzw. auch einen PersistenceContext in den entsprechenden Converter zu Injizieren.
Irgendwie klappt das aber nicht wirklich bei mir :/
Ich habe viele viele workarounds fuer Versionen <=2.2 gefunden, aber irgendwie sträube ich mich diese zu verwenden...
Das Fehlerbild ist dabei folgendes: Generiere ich ein paar entities, werden diese im Select-menu dargestellt, aber sobald ich etwas damit machen will (beispielsweise eine entity loeschen) bekomme ich eine NullPointerException. Diese bezieht sich darauf, dass die Servicebean (bzw. der Entitymanager) im Converter null ist.

Der Converter sieht wie folgt aus:
Java:
@FacesConverter(forClass = UserEntity.class, managed = true)
public class UserConverter implements Converter {

    @PersistenceContext(unitName = "primary")
    private EntityManager em;
    @Inject
    UserService userService;

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
        if (modelValue == null) {
            return "";
        }

        if (modelValue instanceof UserEntity) {
            return String.valueOf(((UserEntity) modelValue).getId());
        } else {
            throw new ConverterException(new FacesMessage(modelValue + " is not a valid User"));
        }
    }

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
        System.out.println("submitted: " + Long.valueOf(submittedValue));
       
        if (em == null) {
            System.out.println("em is nich");
        }
                if (userService == null) {
            System.out.println("Service is nich");
        }
               
        if (submittedValue == null || submittedValue.isEmpty()) {
            System.out.println("submitted = nil");
            return null;
        }

        try {
            //return em.find(UserEntity.class, Long.valueOf(submittedValue));
            return userService.getById(Long.valueOf(submittedValue));
        } catch (NumberFormatException e) {
            throw new ConverterException(new FacesMessage(submittedValue + " is not a valid User ID"), e);
        }
    }

}

Es scheint kein allgemeines Problem zu sein, da die injection in die REST-facade anstandslos funktioniert.
JSF-Version ist laut Kontrollausgabe 2.3.2 (Glassfish5 ebend)

komplettes minimalbeispiel -> http://github.com/istzustand/error

Welchen Punkt habe ich übersehen um das ganze wie gewünscht ans Laufen zu bringen?

P.S.: bitte um Nachsicht, falls ich was komplett idiotisches gemacht habe, oder zu doof war um die richtigen Stichwörter zu googeln, bin noch ein totaler noob...
 

Steven Hachel

Bekanntes Mitglied
Puhh, wo fange ich an...
Als erstes haust du erstmal aus all deinen Klassen die Annoation @ManagedBean raus und entscheidest dich nur für @Named und ob die Gültigkeit der Bean im @RequestScope, @SessionScope, @ConversationScope oder javax.faces.view.ViewScoped (JavaEE7) liegt.
@ManagedBean ist schon lange Deprecated und wird aus der nächsten JavaEE Version raus fliegen.
Wenn du @Stateless nutzt, musst du den CDI Context @RequestScope, @SessionScope etc. nicht verwenden! WÄre auch ne Vermischung. Würde aber @Stateless raus nehmen und dafür den @RequestScope bzw. @ConversationScope nutzen.

In deinem Fall vermischt du CDI Context mit @Named und den JSF container mit @ManagedBean. Tue das auf jedenfall nie und nimmer. Wenn du eine Bean für die JSF Seite Sichtbar machen möchtest, nutze immer @Named!

Dann entscheide dich, wie lange deine Bean gültig sein soll. Das machst du mit @RequestScope, @SessionScope... aus dem javax.enterprise.context Package. Immer darauf achten, dass nicht wieder alte Kram (javax.faces.bean) da rein flattert.

Für deinen Rest Controller hast du @Stateless genutzt, was richtig ist.

So, nun zu deinem Converter. Ich würde an deiner Stelle nicht mit dem @FacesConverter arbeiten. Nutze in diesem Falle auch die @Named Annotaion und definiere, welchen Context du nimmst... @RequestContext, @SessionContext etc. @Bei @ApplicationContext musst du darauf achten die überladenen Methoden zu synchroniesieren, da der der Converter im ApplicationContext nicht Threadsafe ist.

anstatt...
Java:
@FacesConverter(forClass = UserEntity.class, managed = true)
public class UserConverter implements Converter {
dann so...
Java:
@RequestScope
@Named
public class UserConverter implements Converter {

Dein JSF... du musst in deinem selectOneMenu Tag noch den converter angeben.

anstatt...
Code:
<h:selectOneMenu value="#{userBean.selectedUser}">
                    <f:selectItems value="#{userBean.userList}" var="su" itemLabel="#{su.name}" itemValue="#{su}"/>
</h:selectOneMenu>
dann so in etwa...
Code:
<h:selectOneMenu value="#{userBean.selectedUser}" converter=#{userConverter}>
                    <f:selectItems value="#{userBean.userList}" var="su" itemLabel="#{su.name}" itemValue="#{su}"/>
</h:selectOneMenu>

Ich würde deinen EntitManager in eine andere Bean packen und diese dann injecten, damit du nicht überall den @PersistenceContext definieren und injecten musst. Definiere ihn an einer Stelle, meinet wegen in einem DataAccessObjectService, und stelle diese Bean wo immer du sie benötigst mit Inject zur Verfügung.

so, ich weiß jetzt nicht, ob ich alles richtig formuliert habe, aber versuche es mal. :)

Steven

Kolumne: EnterpriseTales
Wenn Scopes fremdgehen: Mehr Spaß mit CDI-Scopes
https://jaxenter.de/wenn-scopes-fremdgehen-mehr-spass-mit-cdi-scopes-18851
 
Zuletzt bearbeitet:

Gort

Mitglied
Danke fuer die ausführliche Antwort :)

das funktioniert so (nachdem die entity noch ihre equals()-methode verpasst bekommen hat)
Das ist ja letztendlich die Lösung #1 aus dem Stackoverflow-link.

Warum aber würdest du @Request bzw. @Conversation einer @Stateless-annotation vorziehen? Ich meine, wenn ich einen State brauche, ists eh alternativlos, aber sonst?

Und da ich schon nachfragen stelle, hijacke ich meinen thread gleich noch ein wenig mehr:
Das von dir vorgeschlagene DAO existiert ja letztendlich schon in form des UserService. Ich frage mich nun: Wenn die REST-API eh nur crud macht... Spricht etwas dagegen das DAO entsprechend mit @GET @POST @DELETE zu annotieren, oder sollte man die beiden grundsätzlich getrennt halten?

Bezüglich packaging: Eher so wie ich es in dem Beispielprojekte habe? also nach Funktion (entity/controller/converter/etc) oder eher nach Objekt? Also entity, converter, controller usw. vom UserObject in ein package und fuer weitere Objekte jeweils ein package? Ganz anders?
 

Ähnliche Java Themen

Neue Themen


Oben