5

I have this local URL: http://localhost:8084/Name/faces/Blah.xhtml?flkatid=AAA

and this is a segment from a facelet, that checks the parameter "flkatid" and assigns it to the bean:

    <f:metadata>
        <f:viewParam name="flkatid"
                     id="flkatid"
                     value="#{floKatBean.flkatid}"
                     required="true"
                     requiredMessage="URL incomplete"
                     validator="#{floKatBean.validateFlKatId}"
                     validatorMessage="URL incomplete or invalid"
                     >
        </f:viewParam>
        <f:event type="preRenderView" listener="#{floKatBean.init}"/>
        <f:event type="preRenderView" listener="#{floBean.holFloListe}"/>
    </f:metadata>

floKatBean.flkatid is an integer, thus the URL is invalid. A page is displayed, that tells this:

flkatid: 'AAA' must be a number consisting of one or more digits. flkatid: 'AAA' must be a number between -2147483648 and 2147483647 Example: 9346

JSF checks the parameter itself, because it knows that the bean element is an integer. The custom validator is only called if the parameter is an integer.

Is it possible to get the server to return a HTTP error code, e.g. 403 (forbidden) without leaving the "standard JSF world" (or switch to RichFaces, MyFaces, SmileyFaces or whatever) ? I can't find anything in f:viewParam to "fail" the page. Maybe elsewhere?

Tilman Hausherr
  • 17,731
  • 7
  • 58
  • 97

2 Answers2

4

You could do a ExternalContext#responseSendError() inside the validator. But this requires that the value is been declared as a String instead of Integer and that required="true" is been removed, all to prevent JSF default validation mechanism from doing the null/empty check and the conversion from String to Integer.

<f:metadata>
    <f:viewParam name="flkatid" value="#{floKatBean.flkatid}" 
        validator="#{floKatBean.validateFlKatId}" 
        validatorMessage="URL incomplete or invalid" 
    />
    ...
</f:metadata>

with

public void validate(FacesContext context, UIComponent component, Object value) 
    throws ValidatorException, IOException
{
    if (value == null || !((String) value).matches("\\d+")) {
        context.getExternalContext().responseSendError(
            403, (String) component.getAttributes().get("validatorMessage"));
    }
}

You could do the conversion in the init() method instead.

this.flkatidInteger = Integer.valueOf(this.flatkid);
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • I know... I successfully tried something similar before asking this question. But I want to keep the value as integer, it is another extra level of protection against SQL injections. I prefer using standard validators first and my own only an an extra. One can never be paranoid enough :-) – Tilman Hausherr May 31 '11 at 14:37
  • @BalusC hi, BalusC! I'm curious on what would you think about the solution presented in my answer. I'm still very immature in JSF development, would love to know your opinion! :) – Elias Dorneles May 29 '12 at 18:56
  • I suppose "public void validate" should be "public void validateFlKatId " – user1156544 Jul 03 '17 at 14:42
0

I've found that the best way to do this (dealing with request parameters and sending useful HTTP responses) is to drop the f:viewParams entirely, and deal "manually" with the params in a preRenderView listener like this:

public void preRenderViewCheckParameters() throws IOException {
    if (!isPostback()) {
        flkatid = getFlkatidFromRequest();
        if (flkatid == null) {
            responseSendError(404, "URL incomplete or invalid!");
        }
    }
}
private Integer getFlkatidFromRequest(){
    try {
        return Integer.valueOf(getRequestParameter("flkatid"));
    } catch (NumberFormatException e) {
        return null;
    }
}

// util methods stolen from OmniFaces (check out this project, you'll love it):
// https://github.com/omnifaces/omnifaces/blob/master/src/org/omnifaces/util/Faces.java
public static String getRequestParameter(String name) {
    return FacesContext.getCurrentInstance().getExternalContext()
                       .getRequestParameterMap().get(name);
}
public static void responseSendError(int status, String message)
                           throws IOException {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    facesContext.getExternalContext().responseSendError(status, message);
    facesContext.responseComplete();
}

and then, in the view:

<f:metadata>
    <f:event listener="#{portariasMB.preRenderViewLoadPortaria}"
             type="preRenderView" />
</f:metadata>

It's a little bit more code maybe, but it's more straightforward, at least.

Also, doing this way you avoid weird problems with messages not being displayed, so... that's what I've been doing.

Community
  • 1
  • 1
Elias Dorneles
  • 22,556
  • 11
  • 85
  • 107
  • Can also. I'm only not sure how this solves the problem with facesmessages not being displayed as you go to the 404 error page anyway. – BalusC May 29 '12 at 19:05
  • Yeah, this example doesn't enqueue messages, but if you do an `addMessage` instead of a `responseSendError` it will work. In a @PostConstruct method for a bean that gets initialized after the messages component is rendered, it doesn't. – Elias Dorneles May 29 '12 at 19:28