4

Environment: JSF 2.2 with Mojarra 2.2.12 & CDI ViewScoped beans & javax.faces.STATE_SAVING_METHOD set to client.

In order to properly initialize my bean thanks to <f:viewParam ... />, I would like to (re-)execute <f:viewAction action="#{bean.onLoad}" /> when my ViewScoped bean is recreated (view was pushed out from the LRU, cf. com.sun.faces.numberOfLogicalViews) following a POST request.

<f:metadata>
    <f:viewParam maxlength="100" name="name" value="#{bean.file}" />
    <f:viewAction action="#{bean.onLoad}"  />
</f:metadata>

<o:form includeRequestParams="true">
     <!-- action can only work if onLoad has been called -->
     <p:commandButton action="#{bean.action}" />
</o:form>

Any ideas?

Notes:

  • I'm aware of postBack="true" but it's not suitable as bean.onLoad() would be called on every POST request.
  • I cannot call onLoad() in @PostConstruct method because values have not been set by viewParam yet (cf. When to use f:viewAction versus PostConstruct?).
Community
  • 1
  • 1
Mathieu Castets
  • 5,861
  • 3
  • 28
  • 37
  • Is your `#{bean}` a `@ViewScoped` one? What's the purpose of `#{viewFile}` and why aren't you integrating the `onLoad()` method in the bean itself? – Aritz Oct 12 '15 at 10:14
  • @XtremeBiker `#{viewFile}` was a typo (bad copy and paste), I have edited my question. `#{bean}` is `@ViewScoped`. What do you mean by integrating the `onLoad()` in the bean itself? – Mathieu Castets Oct 12 '15 at 10:16
  • Now it looks OK ;-). Well, the number of logical views puts a threshold for the number of views you support in the same web session. With the default value of 15 you're supporting 15 different opened tabs (that's done for limiting the amount of memory a single web session could take in the server). If you happen to need more than 15, you could either increase this value or use the client state instead of the server one. So your corner case should just throw an exception when happening, as far as I understand. Are you just playing with the config or is this your case happening in production? – Aritz Oct 12 '15 at 10:25
  • 1
    My case happens in production. My app invites users to open multiple tabs, therefore the threshold could be reached sometimes. `javax.faces.STATE_SAVING_METHOD` is already set to `client` (so I don't have any `ViewExpiredException`). – Mathieu Castets Oct 12 '15 at 10:52
  • Try `onPostback="#{session['new']}"`. Should theoretically work, but may fail in specific circumstances (e.g. autologin+redirect) – BalusC Oct 14 '15 at 10:06
  • @BalusC, `onPostback="#{session['new']}"` should only work when my `ViewScoped` is being recreated following a session expiration. But I'm rather interested in the case where `ViewScoped` is destroyed because a new tab has been opened (session is still "alive") – Mathieu Castets Oct 14 '15 at 12:59
  • 1
    I see. Nice case. A way without hacking in JSF would be to drop `` and use OmniFaces `@Param` instead so you can continue with `@PostConstruct.` – BalusC Oct 14 '15 at 13:16
  • Great workaround. It works. Maybe you could post it as an answer. – Mathieu Castets Oct 14 '15 at 14:33
  • On a second thought, how about `onPostback="#{empty bean.someModelValue}"` such as `onPostback="#{empty bean.file}"`? – BalusC Oct 15 '15 at 21:27
  • It works too as long as `#{bean.someModelValue}` is required, otherwise `onPostback` could always be true. Both solutions can be used, depending on the requirements. I will stick with `@Param`. Thank you for your input. – Mathieu Castets Oct 15 '15 at 22:18

2 Answers2

3

I'm aware of postBack="true" but it's not suitable as bean.onLoad() would be called on every POST request.

You can just use EL in onPostback attribute wherein you check if the model value and/or request parameter is present.

If the model value is required, then just check if it's present or not:

<f:metadata>
    <f:viewParam maxlength="100" name="name" value="#{bean.file}" required="true" />
    <f:viewAction action="#{bean.onLoad}" onPostback="#{empty bean.file}" />
</f:metadata>

If the model value is not required, then check the request parameter too:

<f:metadata>
    <f:viewParam maxlength="100" name="name" value="#{bean.file}" />
    <f:viewAction action="#{bean.onLoad}" onPostback="#{empty bean.file and not empty param.name}" />
</f:metadata>

I cannot call onLoad() in @PostConstruct method because values have not been set by viewParam yet.

Given the presence of <o:form> in your snippet, I see that you're using OmniFaces. The very same utility library offers a CDI @Param annotation for the very purpose of injecting, converting and validating HTTP request parameters before the @PostConstruct runs.

The entire <f:viewParam><f:viewAction> can therefore be replaced as below:

@Inject @Param(name="name", validators="javax.faces.Length", validatorAttributes=@Attribute(name="maximum", value="100"))
private String file;

@PostConstruct
public void onLoad() {
    if (!Faces.isValidationFailed()) {
        // ...
    }
}

Or if you've Bean Validation (aka JSR303) at hands:

@Inject @Param(name="name") @Size(max=100)
private String file;

@PostConstruct
public void onLoad() {
    if (!Faces.isValidationFailed()) {
        // ...
    }
}
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
0

Though I havent been struggeling with the numberOfLogicalView I often need to reinitialize beans with a long living scope. Eg a usecase is to (re-) load some detail data from database when the user clicks on some entity in a table-view. Then I simply use some initialized-flag, eg

@SomeLongLivingScope
public class MyBean {

    private boolean initialized = false;

    public void preRenderView() {
        if ( initialized ) return;
        try {
             do_init();
        } finally {
            initialized = true;
        }
    }

    public void someDataChangedObserver( @Observes someData ) {
        ...
        initialized = false;
    }
}

Using this pattern you can use your viewAction with postBack="true".

frifle
  • 810
  • 5
  • 24