Getting a GET request param into an @ViewScoped bean

Kawu picture Kawu · Oct 11, 2011 · Viewed 9.2k times · Source

I have a (request-scoped) list from which the user may select a "PQ" (list of links). When clicked or otherwise entered into the browser the main page for each PQ shall be displayed. Each PQ's page is of the form

http://localhost:8080/projectname/main.jsf?id=2

Here's the PQ bean first:

@Named
@ViewScoped
public class PqHome implements Serializable
{
    @PersistenceContext(unitName="...")
    private EntityManager em;

    private Integer id;
    private PQ instance;

    @PostConstruct
    public void init()
    {
        System.out.println("ID is " + id); // ID from URL param

        instance = em.find(PQ.class, id);       
    }

    public Integer getId()
    {
        return id;
    }

    public void setId(Integer id)
    {
        this.id = id;
    }

    public PQ getInstance()
    {
        return instance;
    }
}

Here's the main.xhtml:

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                ...>
  <ui:define name="metadata">
    <f:metadata>
      <f:viewParam name="id" value="#{pqHome.id}">
        <f:convertNumber integerOnly="#{true}" />
      </f:viewParam>
      <!--f:event type="preRenderView" listener="#{pqHome.init}" /-->
    </f:metadata>
  </ui:define>
  <ui:define name="title">
    <h:outputText value="Main" />
  </ui:define>
  ...
</ui:composition>

Any time I select or otherwise refresh the page/URL I get a NullPointerException from the EntityManager:

org.jboss.weld.exceptions.WeldException: WELD-000049 Unable to invoke [method] @PostConstruct public de.mycomp.myproj.beans.PqHome.init() on de.mycomp.myproj.beans.PqHome@4f0ea68f
    at org.jboss.weld.bean.AbstractClassBean.defaultPostConstruct(AbstractClassBean.java:595)
...
Caused by: java.lang.IllegalArgumentException: id to load is required for loading
at org.hibernate.event.spi.LoadEvent.<init>(LoadEvent.java:87)
at org.hibernate.event.spi.LoadEvent.<init>(LoadEvent.java:59)
at org.hibernate.internal.SessionImpl.get(SessionImpl.java:961)
at org.hibernate.internal.SessionImpl.get(SessionImpl.java:957)
at org.hibernate.ejb.AbstractEntityManagerImpl.find(AbstractEntityManagerImpl.java:787)
at org.hibernate.ejb.AbstractEntityManagerImpl.find(AbstractEntityManagerImpl.java:762)
at org.jboss.as.jpa.container.AbstractEntityManager.find(AbstractEntityManager.java:221)
at de.mycomp.myproj.beans.PqHome.init(PqHome.java:47)
... 56 more

[Line 47 is em.find(...)]

The line

<f:event type="preRenderView" listener="#{pqHome.init}" />

doesn't make things any better. I'm pretty desparate now.

How do you get URL GET request params into an @ViewScoped bean?

Note: I bet it's not a trivial thing to do. Chances are I'm doing something wrong here conceptually, so any tips on how to improve are welcome. I felt that I needed to choose @ViewScoped because there will be more complex AJAX-based GUI on that page which I'd really like to keep accessible via URL GET params.

Thanks

Answer

BalusC picture BalusC · Oct 11, 2011

The @PostConstruct is invoked directly after bean's construction and all dependency injection (such as @PersistenceContext, @EJB, @ManagedProperty, @Inject, etc..etc..).

The <f:viewParam> sets its value during the update model values phase, which is far after (post)construction of the bean. So inside the @PostConstruct the <f:viewParam> value is simply not yet been set. It'll be still null at that point.

You're close with <f:event type="preRenderView">, but you have to remove the @PostConstruct annotation.

So:

<f:viewParam name="pq" value="#{pqHome.id}">
    <f:convertNumber integerOnly="#{true}" />
</f:viewParam>
<f:event type="preRenderView" listener="#{pqHome.init}" />

with

private Integer id;

public void init() {
    instance = em.find(PQ.class, id);       
}

Unrelated to the concrete problem, I'd suggest to use a Converter for this instead. See also Communication in JSF 2.0 - Converting and validating GET request parameters.

Also the combination @Named @ViewScoped won't work as intended. The JSF-specific @ViewScoped works in combination with JSF-specific @ManagedBean only. Your CDI-specific @Named will behave like @RequestScoped this way. Either use @ManagedBean instead of @Named or use CDI-specific @ConversationScoped instead of @ViewScoped.