15

I think this problem raises because I didn't get something with EJBs. I have a service class for my entity, which is @Stateless. When I use it by injecting with @EJB in my session scoped presentation model, all is ok. But now I wanted to use this EJB service in a DataModel I have overwritten for use in my presentation model:

public class LazyUserDataModel extends LazyDataModel<User> {

    @EJB
    private UserService service;

    @Override
    public List<User> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map filters) {
        List<User> users;
        users= service.findAllUsers();
        this.setRowCount(users.size());
        return users; 
    }
}

On execution I get a NullPointerException at position "users= service.findAllUsers();" The same works, when I overwrite this DataModel in my presentation model:

@Named
@SessionScoped
public class UserPM {
    @EJB
    private UserService service;

    private LazyDataModel<User> lazyUsers;

    public UserPM() {

            // Don't works
            //lazyUsers = new LazyUserDataModel();

            lazyUsers = new LazyDataModel() {
            @Override
                public List<User> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map filters) {
                    List<User> users;
                    users = service.findAllUsers();
                    this.setRowCount(users .size());
                    return users ; 
                }
        };
    }
}

Isn't it possible to Inject an EJB in a normal Java Class? What must I do that I don't have to define the DataModel in the presentation model?

Thanks

timmornYE
  • 708
  • 2
  • 8
  • 22
  • Try to put your BeanInterface into the EJB annotation. I has the same problem some times ago and that fixed it. `@EJB(beanInterface=YourInterface.class)` – ZeusNet Aug 02 '13 at 12:43

2 Answers2

18

EJBs are only injected in managed beans. A bean is managed when it's managed by some injection container, such as via JSF's own @ManagedBean, CDI's @Named, etc. You can even inject an EJB in another EJB. You cannot inject an EJB in an unmanaged class (you can however manually grab it from JNDI, but that's plain ugly).

You've basically the following options:

  1. In @PostConstruct of your managed bean, construct the datamodel whereby you pass the result as argument (note that this is also how standard data models like ListDataModel work).

    @PostConstruct
    public void init() {
        lazyUsers = new LazyUserDataModel(service.findAllUsers());
    }
    

  2. Make LazyUserDataModel abstract whereby you ask the user to provide the result.

    public abstract class LazyUserDataModel extends LazyDataModel<User> {
    
        @Override
        public List<User> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map filters) {
            List<User> users;
            users = findAllUsers();
            this.setRowCount(users.size());
            return users ; 
        }
    
        public abstract List<User> findAllUsers();
    
    }
    

    so that the anonymous class hurts less

    lazyUsers = new LazyUserDataModel() {
        @Override
        public List<User> findAllUsers() {
            return service.findAllUsers();
        }
    };
    

  3. Make LazyUserDataModel a managed bean as well and inject it instead.

    @Named @RequestScoped
    public class LazyUserDataModel extends LazyDataModel<User> {
        // ...
    }
    

    with

    @Inject
    private LazyUserDataModel lazyUsers;
    

  4. Create a fullfledged anonymous instance like as you figured.


Unrelated to the concrete problem, there's no point of having a LazyDataModel whereby you provide all records. Its intent is that it offers you the possibility to request only a subset or records using SQL powers (LIMIT, OFFSET and friends) based on the current paginated state so that you don't need to have hundreds if not thousands records in Java's memory but only ten or so. In other words, if you are never using first and/or pageSize argument of the load() method, you're most likely approaching it completely wrong.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks for this detailed answer @BalusC. Before I try this, to the two off topics: I know what that LazyDataModel makes no sense this way. I only wanted to simplify the code as much as possible. But what I don't really understand is your first statement: In what constructor should service be null? In "public UserPM()"? If I haven't overlooked something then this is exactly how it works for me. – timmornYE Aug 02 '13 at 15:09
  • Injected dependencies are not available in constrcutor, but at earliest only in `@PostConstruct`, for the simple reason because instance variables can't be set before the instance is constructed. – BalusC Aug 02 '13 at 15:11
  • That is strange. As you say, I can remember this. But it worked exactly this way. Perhaps something is different because of the overriding? – timmornYE Aug 02 '13 at 15:18
  • 3
    Sorry, I was wrong. You're referencing it in an anonymous method which is of course not invoked at the moment of construction. But at least, this is not good coding style. Put postconstruct logic in `@PostConstruct` method. Keep constructor empty. Preferably, your managed/enterprise beans should not have any explicit constructor. – BalusC Aug 02 '13 at 15:21
  • One correction to your code BalusC: The LazyUserDataModel only worked as desired, when the CDI is SessionScoped. You know why? – timmornYE Aug 02 '13 at 15:22
  • (I was about to comment on the non-problem of referencing "service" from an inner class, so good update :-).) – Brett Kail Aug 02 '13 at 15:23
  • @Wotim: Please describe "working as desired". Do you mean, command buttons/links are not invoked when bean is put in request scope? If so, refer point 4 of http://stackoverflow.com/questions/2118656/hcommandlink-hcommandbutton-is-not-being-invoked/2120183#2120183 – BalusC Aug 02 '13 at 15:37
  • @BalusC changing the page, entering a filter all gives a "/ by zero" in "org.primefaces.model.LazyDataModel.setRowIndex(LazyDataModel.java:62)" if I set to RequestScoped. With SessionScoped all runs without error. – timmornYE Aug 03 '13 at 07:36
  • This is a known PF issue. At some point, rowcount became `0`. Putting bean in view scope should fix it. Googling using keywords "ArithmeticException org.primefaces.model.LazyDataModel.setRowIndex" should give clues and workarounds. – BalusC Aug 03 '13 at 12:02
5

You can only inject beans and EJBs inside beans and EJBs that are created by the container. Not inside objects that you instantiate yourself using new. The container is unaware of the objects that you instantiate, and thus is unable to inject anything in these objects.

So, simply let the container instantiate and inject the LazyUserDataModel object:

@Named
@SessionScoped
public class UserPM {
    @Inject
    private LazyUserDataModel lazyUsers;

    ...
}
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255