Multi field validation, obtaining value of another component results in NullPointerException - validation

I get javax.faces.FacesException: java.lang.NullPointerException when I type something in the zip code and hit submit with country set to default null value. If I select the country and then type something everything works. I tried SubmittedValue, but it is working the opposite way - with null is working and after this is giving null exception.
#FacesValidator("zipV")
public class ZipValidator implements Validator {
LocaleBean Bean = new LocaleBean();
String language;
private static final String ZIP_PATTERN_BG = "\\d{4}";
private static final String ZIP_PATTERN_US = "\\d{5}";
private static final String ZIP_PATTERN_DEFAULT = "[A-Za-z0-9]*";
private String zip_pattern;
private Pattern pattern;
private Matcher matcher;
private String country;
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
language = Bean.getLanguage();
UIInput Input = (UIInput) component.getAttributes().get("country");
country = Input.getValue().toString();
String zip = (String) value;
if (country == null || country.isEmpty()) {
return;
}
switch (country) {
case "BGR":
zip_pattern = ZIP_PATTERN_BG;
break;
case "USA":
zip_pattern = ZIP_PATTERN_US;
break;
default:
zip_pattern = ZIP_PATTERN_DEFAULT;
break;
}
pattern = Pattern.compile(zip_pattern);
matcher = pattern.matcher(value.toString());
if (!matcher.matches()) {
switch (language) {
case "en": {
FacesMessage msg = new FacesMessage("Invalid zip.");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(msg);
}
case "bg": {
FacesMessage msg = new FacesMessage("Невалиден пощенски код.");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(msg);
}
}
}
}
}
Here's the view:
<h:selectOneMenu id="country" value="#{account.country}" required="true" requiredMessage="#{msg['register.required']}" binding="#{country}">
<f:selectItem itemValue="#{null}" itemLabel="#{msg['register.countryQ']}"/>
<f:selectItems value="#{account.countries}"/>
<f:ajax listener="#{account.loadStates()}" render="state"/>
</h:selectOneMenu>
<h:inputText id="zipcode" required="true" requiredMessage="#{msg['register.required']}" value="#{account.zipcode}">
<f:validator validatorId="zipV"/>
<f:attribute name="country" value="#{country}"/>
</h:inputText>

Here,
country = Input.getValue().toString();
you should not be using toString() at all. You should be casting it:
country = (String) Input.getValue();
Otherwise it will throw NullPointerException if the getValue() returned null. As its javadoc clearly says, a NullPointerException will be thrown among others when you attempt to call an instance method on null (like as you did with toString()).
Please note that this problem is technically unrelated to JSF. It's just basic Java. The java.lang package of the exception is a very good hint in this. If you got an exception of javax.faces (or javax.el) package, then we can talk about a true JSF (or EL) problem.
See also:
jsf validate two fields in one time
JSF doesn't support cross-field validation, is there a workaround?
Unrelated to the concrete problem, I'd really respect Java naming conventions. Variable names start with lowercase. Use input instead of Input. Also, your manual control of localization is strange. What if you ever need to support 10 languages? Do you expand the switches over all place? Make use of JSF builtin localization facilities with <resource-bundle> and ResourceBundle#getBundle().

Related

ClassCastException for <h:selectManyCheckbox> with validation on ajax update when INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL is active

I have a <h:selectManyCheckbox> that has a required-validation on. If I submit the form, I get a validation error when nothing is selected. So far, this ist expected. However, if I do an ajax update on the checkbox then, I get a ClassCastException. But only if empty values are treated as null.
So, I have the following setup. In the web.xml I set
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
Then I have an xhtml-page like this:
<h:form id="main">
<h:selectManyCheckbox id="value" value="#{testcb.selected}" required="true" requiredMessage="Select at least one entry">
<f:selectItems value="#{testcb.available}"/>
</h:selectManyCheckbox>
<div><h:message for="value" style="color:red;"/></div>
<h:outputLabel for="checkit" value="Enter some text: "/>
<h:inputText id="checkit" value="#{testcb.text}">
<f:ajax event="change" execute="#this" render=":main:value"/>
</h:inputText>
<div><h:commandButton type="submit" value="Submit" action="#{testcb.action}"/></div>
</h:form>
And this backing bean:
#Named("testcb")
#SessionScoped
public class TestCBBean implements Serializable {
private final Set<TestValue> available = EnumSet.allOf(TestValue.class);
private final Set<TestValue> selected = EnumSet.noneOf(TestValue.class);
private String text;
public void action() {}
public Set<TestValue> getAvailable() { return available; }
public void setAvailable(Set<TestValue> available) {
this.available.clear();
this.available.addAll(available);
}
public Set<TestValue> getSelected() { return selected; }
public void setSelected(Set<TestValue> selected) {
this.selected.clear();
this.selected.addAll(selected);
}
public String getText() { return text; }
public void setText(String text) { this.text = text; }
}
And this enum:
public enum TestValue { ONE, TWO, THREE }
I am running this in Wildfly 26.0.1-Final (JavaEE 8). But this also happens in older versions (like Wildfly 15). What I am doing:
enter some text and leave the box: an ajax update runs setting the value successfully in the model
I press submit: the validation error for the empty checkboxes pops up
I modify the text in the input and leave the box: the ajax update results in the following Exception:
java.lang.ClassCastException: class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.renderkit.html_basic.MenuRenderer.getSubmittedSelectedValues(MenuRenderer.java:508)
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.renderkit.html_basic.SelectManyCheckboxListRenderer.encodeEnd(SelectManyCheckboxListRenderer.java:89)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:600)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponent.encodeAll(UIComponent.java:1655)
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.context.PartialViewContextImpl$PhaseAwareVisitCallback.visit(PartialViewContextImpl.java:628)
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.component.visit.PartialVisitContext.invokeVisitCallback(PartialVisitContext.java:159)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponent.visitTree(UIComponent.java:1457)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIForm.visitTree(UIForm.java:355)
On the ajax update the checkboxes are not submitted. But they seem to contain an empty string as submitted value from the validation step before.
When setting the context parameter to false this works. But I want to keep it on true. Any ideas how I could work around this problem?
Reproduced. This is indeed a bug in Mojarra.
It boils down to that the following method in UIInput superclass ...
#Override
public Object getSubmittedValue() {
if (submittedValue == null && !isValid() && considerEmptyStringNull(FacesContext.getCurrentInstance())) {
return "";
} else {
return submittedValue;
}
}
... is not overridden in UISelectMany superclass in such way that it returns new String[0] instead of "". This was an oversight during implementing Faces issue 671.
I have fixed it in Mojarra issue 5081.
In the meanwhile, until you can upgrade to the Mojarra version containing the fix, you can temporarily work around it by copy pasting the entire source code file of UISelectMany into your project while maintaining the package and adding the following method to it:
#Override
public Object getSubmittedValue() {
Object submittedValue = super.getSubmittedValue();
return "".equals(submittedValue) ? new String[0] : submittedValue;
}

custom validator call on button through ajax

hi i wrote a custom a validator which gets the system name and compare it against the id in database, now i wanna apply a check if this value is exactly the same, user must be allowed to click the button and move on else some error message should be displayed. and i am really confused how to call the validator() on through ajax.
my view page code is
<h:commandButton action="sample?faces-redirect=true" value="submit">
<f:ajax execute="#{csample.UserValidator}" render="#form" >
<h:inputText name="idtext" value="#{csampleBean.id}" />
</f:ajax>
</h:commandButton>
and my custom validator
public void UserValidator(FacesContext context, UIComponent toValidate, Object value)
throws UnknownHostException, ValidatorException, SQLException, NamingException
{
java.net.InetAddress localMachine = java.net.InetAddress.getLocalHost();
String machine= localMachine.getHostName();
String query = "select * from USER_ where USER_ID = '"+machine+"'";
Context initContext = new InitialContext();
Context envContext = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/myoracle");
Connection conn = ds.getConnection();
Statement stat = conn.createStatement();
//get customer data from database
ResultSet result = stat.executeQuery(query);
if (query==machine)
// what to do here
conn.close();
need some guidance
You need to create a class implementing the Validator interface. On validation fail, just throw a ValidatorException with a FacesMessage. JSF will then take care that the FacesMessage ends up in the right <h:message> associated with the input component.
You can register the custom validator to JSF by annotating it with #FacesValidator with therein the validator ID. You can reference it in <h:inputXxx validator> or <f:validator validatorId>.
Here's a kickoff example:
#FacesValidator("userValidator")
public class UserValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
// ...
if (!valid) {
String message = "Sorry, validation has failed because [...]. Please try again.";
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, message, null));
}
}
}
Which is been used as follows (note: <h:inputText> does not have name attribute! instead use id; also note that your initial code snippet has some nesting which isn't making any sense):
<h:inputText id="idtext" value="#{csampleBean.id}" validator="userValidator">
<f:ajax render="idtextMessage" />
</h:inputText>
<h:message id="idtextMessage" for="idtext" />
<h:commandButton action="sample?faces-redirect=true" value="submit" />
See also:
How to perform validation in JSF, how to create a custom validator in JSF
Unrelated to the concrete problem, your JDBC code is leaking DB resources. Please fix that as well.

how to use value change listener

I don't know how to deal with f:valueChangeListener , I want to select country and the capital appear so this is my code but it doen't work what is miss or what is the wrong?
Country:
<h:selectOneMenu value="#{event.country}" onchange="submit()">
<f:valueChangeListener type="org.jsf.model.ValueListener"/>
<f:selectItems value="#{event.countries}"/>
</h:selectOneMenu>
Capital: #{event.capital}
My Managed bean
public class EventsBean{
private String capital;
private String country;
String countryCapital;
private String [] countries = {"Select","Egypt","United States","Kuwait"};
public String[] getCountries() {
return countries;
}
// getters and setters
}
The class that implements ValueChangeListener
package org.jsf.model;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ValueChangeEvent;
import javax.faces.event.ValueChangeListener;
public class ValueChangeClass implements ValueChangeListener {
String capital;
#Override
public void processValueChange(ValueChangeEvent event)throws AbortProcessingException {
if ("Egypt".equals(event.getNewValue()) capital = "Cairo";
else if ("Kuwait".equals(event.getNewValue())) capital = "Kuwait";
else if ("United States".equals(event.getNewValue())) capital = "Washington";
else capital = "";
new EventsBean().setCapital(capital);
}
}
It doesn't work !
Is this new EventsBean().setCapital(capital);right ?
Is this new EventsBean().setCapital(capital);right ?
No, it is not right. You're manually creating a brand new instance instead of using the one which is managed by JSF. Your instance would totally disappear once the method finishes and returns. You should instead be setting the capital in the instance which is managed by JSF. There are several ways to achieve this. If you really intend to use the ValueChangeListener this way (which is rather strange for this particular purpose by the way), then you need to fix it as follows:
FacesContext context = FacesContext.getCurrentInstance();
EventsBean eventsBean = context.getApplication().evaluateExpressionGet(context, "#{event}", EventsBean.class);
eventsBean.setCapital(capital);
Easier would be to do the job just in the EventsBean itself.
<h:selectOneMenu value="#{event.country}" valueChangeListener="#{event.changeCountry}" onchange="submit()">
<f:selectItems value="#{event.countries}"/>
</h:selectOneMenu>
Capital: #{event.capital}
private String country;
private String capital;
private Map<String, String> capitals;
#PostConstruct
public void init() {
capitals = new HashMap<>();
capitals.put("Egypt", "Cairo");
capitals.put("Kuwait", "Kuwait");
capitals.put("United States", "Washington D.C.");
}
public void changeCountry(ValueChangeEvent event) {
capital = capitals.get(event.getNewValue());
}
Or, since you're already using JSF 2.0, much better is to use <f:ajax>. It does the right job at the right moment. (Ab)using the valueChangeListener the way as in your original code is actually a leftover of the JSF 1.x era.
<h:selectOneMenu value="#{event.country}">
<f:selectItems value="#{event.countries}"/>
<f:ajax listener="#{event.changeCountry}" render="capital" />
</h:selectOneMenu>
Capital: <h:outputText id="capital" value="#{event.capital}" />
// ...
public void changeCountry() {
capital = capitals.get(country);
}
See also:
When to use valueChangeListener or f:ajax listener?

Disable validator via ajax

I have a simple request scoped entity / pojo which has a Enum and a String as properties.
public Enum Type
{
None,
Email,
Fax;
}
#ManagedBean(name = "testEntity")
#RequestScoped
public class TestEntity
{
private Type type; //Default = None
private String address;
//getter and setter
}
This Enum has a field 'Email' which identifies a e-mail address with a related address.
In JSF I now want to enable/disable a validator of a address InputText field regarding the currently selected type in a SelectOneMenu.
<h:form id="formId">
<p:selectOneMenu id="type" value="#{testEntity.type}>
<p:ajax event="change" update=":formId:address"/>
<f:selectItem itemLabel="E-mail" itemValue="Email"/>
<f:selectItem itemLabel="Fax" itemValue="Fax"/>
</p:selectOneMenu>
<p:inputText id="address" value="#{testEntity.address}">
<f:validator validatorId="emailValidator" disabled="#{testEntity.type != 'Email'}"/>
</p:inputText>
<!-- button to call bean method with testEntity as param -->
</h:form>
It is not working the validator is never active but the ajax call is working since I can see the change value in other fields.
That's unfortunately not possible. The <f:xxx> tags are taghandlers (not UI components) which run during view build time, not during view render time. So if it's disabled during building of the view, it'll always be disabled until the view is recreated (e.g. by new request or a non-null navigation).
You'd need to have a "global" validator which delegates further to the desired validator based on the type attribute.
E.g.
<p:inputText ... validator="#{testEntity.validateAddress}" />
with
public void validateAddress(FacesContext context, UIComponent component, Object value) throws ValidatorException {
if (type == Email) {
context.getApplication().createValidator("emailValidator").validate(context, component, value);
}
}
Update OmniFaces has recently added a new <o:validator> tag which should solve exactly this problem as follows:
<o:validator validatorId="emailValidator" disabled="#{testEntity.type != 'Email'}"/>
See the showcase example here.
Maybe someone is interested in how I solved it thanks to BalusC help.
Pass type component clientId to custom converter.
<f:attribute name="typeComponentId" value=":formId:type"/>
Validator:
public class TestEntity implements Validator
{
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException
{
final String typeComponentId = (String)component.getAttributes().get("typeComponentId");
final UIInput compType = (UIInput)context.getViewRoot().findComponent(typeComponentId);
if(compType != null)
{
final Type type = (Type)compType.getValue();
if(type == Type.Email)
new EmailValidator().validate(context, component, value);
}
}
}
Edit:
Not working inside a ui:repeat component such as p:datatable.

jsf validate two fields in one time [duplicate]

This question already has answers here:
JSF doesn't support cross-field validation, is there a workaround?
(2 answers)
Closed 5 years ago.
can I validate two interdependent fields in with one validator?
<h:form>
<h:inputText value="#{logRegBean.person.name}" >
<f:validator validatorId="loginCorrectValidator" />
</h:inputText>
<h:inputSecret value="#{logRegBean.person.password}" />
<h:commandButton action="#{logRegBean.login}" />
</h:form>
I want to search for the user in the DB and if there is the user, I'll test if the passwords(in db and inputted) match. But how can I access even the password field in one validator? I tried to evaluate the value int the other field via createValueExpression(), but it looks like I can't access the value in that time since I always get empty strings.
Best what you can do is to grab the other UIInput component by UIViewRoot#findComponent() inside the validate() method and then determine the submitted value by either UIInput#getSubmittedValue() (when it occurs after the currently validated component in the component tree) or UIInput#getValue() (when it occurs before the current component and thus is already validated).
E.g.
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
UIInput otherInput = (UIInput) context.getViewRoot().findComponent("clientId");
String otherValue = (String) otherInput.getSubmittedValue();
// ...
}
See also:
JSF doesn't support cross-field validation, is there a workaround?
The validation mechanism in JSF was designed to validate a single component.
However, in practice, you often need to ensure that related components have reasonable values before letting the values propagate into the model.
For example, it is not a good idea to ask users to enter a date into a single textfield.
Instead, you would use three different textfields, for the day, month, and year.
If the user enters an illegal date, such as February 30, you would like to show a validation error and prevent the illegal data from entering the model.
The trick is to attach the validator to the last of the components. By the time its validator is called, the preceding components passed validation and had their local values set. The last component has passed conversion, and the converted value is passed as the Object parameter of the validation method.
Of course, you need to have access to the other components. You can easily achieve that access by using a backing bean that contains all components of the current form. Simply attach the validation method to the backing bean:
public class BackingBean {
private int day;
private int month;
private int year;
private UIInput dayInput;
private UIInput monthInput;
private UIInput yearInput;
// PROPERTY: day
public int getDay() { return day; }
public void setDay(int newValue) { day = newValue; }
// PROPERTY: month
public int getMonth() { return month; }
public void setMonth(int newValue) { month = newValue; }
// PROPERTY: year
public int getYear() { return year; }
public void setYear(int newValue) { year = newValue; }
// PROPERTY: dayInput
public UIInput getDayInput() { return dayInput; }
public void setDayInput(UIInput newValue) { dayInput = newValue; }
// PROPERTY: monthInput
public UIInput getMonthInput() { return monthInput; }
public void setMonthInput(UIInput newValue) { monthInput = newValue; }
// PROPERTY: yearInput
public UIInput getYearInput() { return yearInput; }
public void setYearInput(UIInput newValue) { yearInput = newValue; }
public void validateDate(FacesContext context, UIComponent component, Object value) {
int d = ((Integer) dayInput.getLocalValue()).intValue();
int m = ((Integer) monthInput.getLocalValue()).intValue();
int y = ((Integer) value).intValue();
if (!isValidDate(d, m, y)) {
throw new ValidatorException(new FacesMessage("Invalid Date"));
}
}
private static boolean isValidDate(int d, int m, int y) {
//DO YOUR VALIDATION HERE
}
}
Here is your JSP
<html>
<%# taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%# taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<f:view>
<head></head>
<body>
<h:form>
<h:panelGrid columns="3">
<h:inputText value="#{bb.day}" binding="#{bb.dayInput}" size="2" required="true"/>
<h:inputText value="#{bb.month}" binding="#{bb.monthInput}" size="2" required="true"/>
<h:inputText value="#{bb.year}" binding="#{bb.yearInput}" size="4" required="true" validator="#{bb.validateDate}"/>
<h:message for="year" styleClass="errorMessage"/>
</h:panelGrid>
<h:commandButton value="Submit" action="submit"/>
</h:form>
</body>
</f:view>
</html>
Reference:
Core JavaServer™ Faces
By DAVID GEARY, CAY HORSTMANN
Publisher : Addison Wesley
Pub Date : June 15, 2004
ISBN : 0-13-146305-5
I think SeamFaces' s:validateForm feature may be just what you need. (Seam Faces is a very useful library that brings some nifty CDI-based features to JSF.)

Resources