Question

I'm currently in the process of converting the Virtual Trainer example app from Ed Burns' textbook on JSF 2.0 from JSF Managed Beans to CDI. Most of the problems I've encountered so far have been related to scoping and forgetting to inject correctly but now I'm struggling to overcome the most recent hurdle that relates to extracting a CDI Bean (actually an entity class) from the RequestMap. From what I've been able to ascertain so far, it would appear that a request-scoped Managed Bean can be extracted very simply by using the boilerplate .get(String managedbeanname) method provided by the Map implementation. However, with CDI the bean is wrapped by Weld in a CreationalContextImp instance and I can't extract the object I'm really after even though it I've confirmed it is present in the RequestMap. I can get as far as briefly accessing a proxy object from RequestMap but this reverts to null after calling .get("user") and I doubt it would do much good anyway as the fields in the proxy are all nulled.

I've found a post from BalusC that discusses using a filter class to access CDI beans held in SessionScope (How do I get a SessionScoped CDI bean from inside a Filter?) It seems a little bit involved - is there a simpler solution? I'm also very aware that I may be mucking up scoping/mixing of Managed Bean vs CDI strategies so feel free to set me straight... I am also a little uncertain about using an Entity Bean directly in this fashion rather than using a Facade. Is this causing me/likely to cause me a problem later?

Environment: JEE7, Glassfish 4, Netbeans 7.4, Maven EE Web Archetype Original code relating to use of Managed Beans has been commented out.

Abstract backing bean class:

@RequestScoped
public abstract class AbstractBacking implements Serializable {

    //@ManagedProperty(value="#{facesContext}")
    private FacesContext facesContext;

    //@ManagedProperty(value="#{requestScope}")
    private Map<String, Object> requestMap;

    //@ManagedProperty(value="#{sessionScope}")
    private Map<String, Object> sessionMap;

    @PostConstruct
    public void init() {
        this.facesContext = FacesContext.getCurrentInstance();
        this.sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
        this.requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
    }

Registration page backing bean:

@Named
@RequestScoped
public class RegisterBacking extends AbstractBacking implements Serializable {

    private Object password1;

    @Inject
    private User newUser;

    public String registerUser() {
        String result = null;
        User newUser = (User) getRequestMap().get("user");
        // set the password into the user, because we know the validator was
        // successful if we reached here.
        newUser.setPassword((String) getRequestMap().get("password1"));
        try {
            UserRegistry.getCurrentInstance().addUser(newUser);
            // Put the current user in the session
            setCurrentUser(newUser);
            // redirect to the main page
            result = "/user/allEvents?faces-redirect=true";
        } catch (EntityAccessorException ex) {
            getFacesContext().addMessage(null,
                    new FacesMessage("Error when adding user"
                            + ((null != newUser) ? " " + newUser.toString() : "") + "."));

        }

        return result;

    }

User entity bean:

@Entity
@Named
@Table(name = "Users")
@RequestScoped
@NamedQueries({
    @NamedQuery(name = "user.getAll", query = "select u from User as u"), // @NamedQuery(name = "user.getTrainers", query = "select u from User as u where u.trainer = TRUE"),
// @NamedQuery(name = "user.getUsersForTrainerId", query = "select u from User as u where u.personalTrainerId = :theId")
})

public class User extends AbstractEntity implements Serializable {

    protected String firstName;
    protected String lastName;
    @Temporal(TemporalType.DATE)
    protected Date dob;
    protected String sex;
    protected String email;
    private String serviceLevel = "medium";
    @Column(name = "userid", nullable = false)
    private String userid;
    private String password;
    private boolean trainer;
    private List<Long> subscribedEventIds;
    private Long personalTrainerId;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<TrainingSession> sessions;

    private boolean sessionsInitialized = false;

    public User() {
        this.init();
    }

    public User(String firstName, String lastName,
            String sex, Date dob, String email, String serviceLevel,
            String userid, String password, boolean isTrainer) {
        this.init();
        this.setFirstName(firstName);
        this.setLastName(lastName);
        this.setSex(sex);
        this.setDob(dob);
        this.setEmail(email);
        this.setServiceLevel(serviceLevel);
        this.setUserid(userid);
        this.setPassword(password);
        this.setTrainer(isTrainer);
    }
.....
Getters/setters/etc
.....

Registration page:

<!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">
<body>
<ui:composition template="template.xhtml">
    <ui:define name="content">
<h:form prependId="false">
    <h:panelGrid columns="3">

        <h:outputLabel for="fname" value="First Name:" />
        <h:inputText label="First Name"
                     id="fname" value="#{user.firstName}"
                     required="true"/>
        <h:message for="fname" />

        <h:outputLabel for="lname" value="Last Name:" />
        <h:inputText label="Last Name"
                     id="lname" value="#{user.lastName}"
                     required="true"/>
        <h:message for="lname" />

        <h:outputLabel for="sex" value="Sex:" />
        <h:selectOneRadio label="Sex"
                          id="sex" value="#{user.sex}" required="true">
          <f:selectItem itemLabel="Male" itemValue="male" />
          <f:selectItem itemLabel="Female" itemValue="female" />
        </h:selectOneRadio>
        <h:message for="sex" />

        <h:outputLabel for="dob" value="Date of Birth:" />
        <h:panelGroup>
            <h:inputText label="Date of Birth"
                     id="dob" value="#{user.dob}" required="true">
                <f:convertDateTime pattern="MM-dd-yy" />
            </h:inputText> (mm-dd-yy)
        </h:panelGroup>
        <h:message for="dob" />

        <h:outputLabel for="email" value="Email Address:" />
        <h:inputText label="Email Address"
                     id="email" value="#{user.email}" required="true" />
        <h:message for="email" />

        <h:outputLabel for="slevel" value="Service Level:" />
        <h:selectOneMenu label="Service Level" id="slevel"
                         value="#{user.serviceLevel}">
          <f:selectItem itemLabel="Medium" itemValue="medium" />
          <f:selectItem itemLabel="Basic" itemValue="basic" />
          <f:selectItem itemLabel="Premium" itemValue="premium" />
        </h:selectOneMenu>
        <h:message for="slevel" />

        <h:outputLabel for="userid" value="Userid:" />
        <h:inputText required="true" id="userid" value="#{user.userid}" />
        <h:message for="userid" />

        <h:outputLabel for="password" value="Password:" />
        <h:inputSecret required="true" id="password" 
                       validator="#{registerBacking.validatePassword1}"
                       value="#{requestScope.password1}" />
        <h:message for="password" />

        <h:outputLabel for="password2" value="Retype Password:" />
        <h:inputSecret required="true" id="password2" value="#{requestScope.password2}"
                       validator="#{registerBacking.validatePassword2}" />
        <h:message for="password2" />


    </h:panelGrid>

    <p><h:commandButton value="Register" 
                     action="#{registerBacking.registerUser}" /></p>
</h:form>

    </ui:define>
</ui:composition>
</body>
</html>
Was it helpful?

Solution

Indeed you seem to mix CDI Beans and JSF Beans and to be honest the example you are translating seems pretty strange I would just throw it out the window all together.

Because, JSF too has it's own dependency injection and you don't really extract stuff yourself from the scoped maps unless you have a very specific use case (say a ServletFilter).

To clarify CDI uses the http://en.wikipedia.org/wiki/Inversion_of_control principle and to "extract" stuff yourself is a anti pattern.

Further on CDI beans are created and managed by the CDI container (Weld or OWB in almost all cases) and you simply can't retrieve a CDI bean from externalContext because it's JSF's context and it's a completely separate thing.

Thus instances created by JSF are not injectable in CDI Beans and vice versa. Anyways as I said earlier extracting stuff yourself is bad practice. See #10 in this list: http://zeroturnaround.com/rebellabs/watch-out-for-these-10-common-pitfalls-of-experienced-java-developers-architects/

So whatever you want you simply use @Inject (possibly with a qualifier).

Workaround For special cases when Inject will not work:

  • CDI context is not available in the current thread.
  • Current instance is not CDI managed

The first scenario happends when you have a ThreadLocal for example a QuartzJob. EJB containers might offer ways to get contextual threads but if you are on a plain servlet container (tomcat) or the thread is spawned with no CDI context for what ever reason you must attach this information yourself. To do this use Deltaspike CDI Control.

http://deltaspike.apache.org/container-control.html

That example is not updated, Today you should use DependentProvider to get a hold of ContextControl.

Example on my blog here: http://www.kildeen.com/blog/post/2013-10-11/Batch%20Jobs%20in%20EE/

The second scenario is when the ServletContainer created the instance and you are on a plain container (for example Tomcat), JSF created the instance, whatever.

Then, injection will not work. As workaround use BeanProvider from Deltaspike. http://deltaspike.apache.org/core.html It can force injection in your current instance and what's even more useful, it can extract a bean for you. If you do this and you still suffer from the first scenario you will get an exception, probably ContextNotActiveException.

To get started properly with JSF and CDI I would recommend using TomEE. It's a apache project thus it's open source. The mailing list and irc channel is really active and it's really on the rise and even now it's really great.

Of course this is my opinion and others prefer other solutions such as WildFly, build it yourself with Tomcat / Jetty, Glassfish etc.

Now, all the usual rules of JSF applies (must use convention for getters / setters etc). To expose your bean to EL expressions (that JSF uses) you must mark it as @Named. The default name will be myBean if the class is named MyBean.

Now comes the scopes. Always use @RequestScoped for every bean you have in your application. When you run into trouble because that scope is to short you should consider how long it should be, if the data you want to keep is interesting for other beans and if you want it available across all browser tabs.

If it's user information then chances are it's interesting for many beans. Thus we create a new class called WebUser. The user probably expects his information to be kept for the full duration of his session (and you might need to track him with that object too). So we use @SessionScoped from the correct package, must be import javax.enterprise.context.SessionScoped. But all of the sudden some logic should not be shared across tabs so you need a more fine grained scope. CDI comes with @ViewScoped (CDI 1.1, JSF 2.2) and @ConversationScoped but chances are you want the scopes from Myfaces CODI sooner or later. Now, most of CODI is already in deltaspike but not the scopes. They have however been split off and how to import them is explained here: http://os890.blogspot.fr/2013/07/add-on-codi-scopes-for-deltaspike.html You will see them in Deltaspike instead sooner or later.

So now you can use a lot of different scopes and you choose this wisely. Stuff that should only be read once from the DB can be stored with @ApplicationScoped.

For example you might want to read system settings from the database and store them in SettingManager that is a CDI bean annotated with @ApplicationScoped.

WelcomeBean is @RequestScoped and @Model and is responsible for logins and account creation. To find out if new accounts may be created it might look like this:

@ViewScoped
@Named
public class WelcomeBean {

    @Inject
    private SettingManager settingManager;


    private boolean allowCreateAccount;


    public boolean isAllowCreateAccount() {
        return allowCreateAccount;
    }


   // login and create account here

    @PostConstruct
    private void init() {
        allowCreateAccount = settingManager.getBooleanSetting("registrationOpen");
    }
}

The Facelet create account button looks like this:

<h:commandButton action="#{welcomeBean.createAccount}" value="login" disabled="#{welcomeBean.allowCreateAccount}"/>

Now when the user performs the login you might want to signal this as an event. Read up on CDI events. Really the most advanced examples differs little from this simple one.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top