JSF 2.2 ViewDeclarationLanguage createComponent passes attributes as String? - jsf-2.2

I am trying to write a JSF custom component that dynamically chooses creates and renders an existing composite component. So far everthing is working fine except for passing attributes to the composite.
This is my custom component class (error handling etc. stripped for better reading):
#FacesComponent(createTag = true)
public class ChooseEZComp extends UIComponentBase {
#Override
public void encodeBegin(FacesContext context) throws IOException {
Object value = getAttributes().get("value");
String ezCompName = value.getClass().getSimpleName().toLowerCase();
// ezCompName is something like "freelink" or "treenode"
Map<String, Object> params = new HashMap<>();
params.put("node", value);
// log.debug(params.get("node").getClass().getName()) -> yields correct class name
ViewDeclarationLanguage viewDeclarationLanguage = context
.getApplication()
.getViewHandler()
.getViewDeclarationLanguage(context, context.getViewRoot().getViewId());
UIComponent component = viewDeclarationLanguage
.createComponent(context,
"http://xmlns.jcp.org/jsf/composite/ezcomp",
ezCompName,
params);
component.encodeAll(context);
}
}
A composite component (I have several of them), that gets choosen and rendered by this class:
<cc:interface>
<cc:attribute name="node" required="true"/>
</cc:interface>
<cc:implementation>
<h:outputText value="The class of node is: #{cc.attrs.node.class.name}"/>
</cc:implementation>
This is how I use the tag in my JSF page:
<test:chooseEZComp value="#{treeView.selectedNode.data}"/>
So "value" is always guaranteed not of type java.lang.String (it's some JPA #Entity).
But nevertheless the result output in the JSF page is always:
The class of node is: java.lang.String
Where am I wrong? Isn't it possible to pass something other than String as parameter to an composite?
I am runnind wildfly-8.2.0-final with Java EE 7 (and Primefaces 5 but which is not used here)
Any hints welcome!
Edit: of course I also tried to force the type of the attribute in the cc:interface
<cc:interface>
<cc:attribute name="node" required="true" type="some.package.type"/>
</cc:interface>
But this consequently resulted in a IllegalArgument Exception:
IllegalArgumentException: Cannot convert ... of type class java.lang.String to class

Turns out I misunderstood the API ... Map<String, Object> in the signature made me think I can pass an object. But the Javadoc is more precise about this:
attributes - any name=value pairs that would otherwise have been given on the markup that would cause the creation of this component [..]
So you do not pass the value into createComponent but rather the expression that was used to calculate the value for the specified attribute or property name:
Map<String, Object> attributes = new HashMap<>();
ValueExpression valueExpression = getValueExpression("value");
attributes.put("node", valueExpression.getExpressionString());
Funny side read:
to find a solution I debugged through the jsf-imp-2.2.8-jbossorg and stumbled upon the code to create the component. Basically what it does is:
create a JSF xhtml file in the temp folder and use OutputStreamWriter#append to write a JSF page with exactly one tag in it (the one you want to create)
loop through all attributes and write them as attribues into the tag
Save the file and feed it to the DefaultFaceletFactory#createFacelet
create a naming container and make it parent of the generated facelet (apply)
use findComponent on the naming container to get hold of the generated tag and return it
At least after finding this it's clear why you need to pass in the value expression rather than the value itself.

Related

Inputtext validator attribute set by an existing field in a bakedbean

I have some inputtext in a form which is managed by a bean #Named and i would like to centralize the information concerning this fields such as which validator is assigned to which field.
If i directly write the name of the #FaceValidator, it works.
<h:inputText validator="validatorLogin"/>
If i tried to put the name of the validator with a bean property such as String validatorLogin = validatorLogin.
It will throw an error in the .xhtml like "Expression must be a method expression but is a value expression".
If i try to still run the code it will throw the following exception "validator=#{bean.validatorLogin}: Method not found".
<h:inputText validator="#{bean.validatorLogin}"/>
I expect the validator name to be set in the bean and the bean to feed the validator id in the inputtext field. So all informations about the form are centralized in one bean.
As well is it dumb to do so or is it something that will make the code more organized ?
As the validator attribute documentation states, there is no way to provide a validatorId via bean property to this attribute:
validator: MethodExpression representing a validator method that will be called
during Process Validations to perform correctness checks on the value
of this component. The expression must evaluate to a public method
that takes FacesContext, UIComponent, and Object parameters, with a
return type of void.
You would normally either hard code a validatorId as you did in your first example, or a method expression (in your second example) that resolves to a method like this:
public void validatorLogin(FacesContext context, UIComponent component, Object value)
throws ValidatorException {
// ...
}
Of course it's up to this bean implementation how the input is validated then. If you want to combine both approaches, you can delegate validation to one (or multiple) validators known by ID in your validatorLogin method:
public void validatorLogin(FacesContext context, UIComponent component, Object value)
throws ValidatorException {
final Collection<String> validatorIds = determineValidatorIds(context, component);
for (String validatorId : validatorIds) {
Validator<Object> validator = context.getApplication().createValidator(validatorId);
validator.validate(context, component, value);
}
}
private Collection<String> determineValidatorIds(FacesContext context, UIComponent component) {
// return hard coded validatorIDs or determine them on arbitrary logic.
}
If you urgently need to provide a validatorId via bean property, you can do so by using the f:validator tag within your input component:
<h:inputText id="txt" value="#{myBean.textValue}">
<f:validator validatorId="#{myBean.arbitraryValidatorId}" />
</h:inputText>

How do I clear a required field value - reverts back to last valid value

I have an input field on my facelet with a custom converter (to whatever other object, just another string in my example) as well as a custom validator ("required" value, amongst other checks). Here is a use case describing expected, and the problematic actual, steps:
Enter a valid value and blur field (tab out or click on other element).
The converter changes the field value to a normalized string representation of the object. as expected
Go back and enter an invalid value, blur again.
Validation message shows error. as expected
Field value stays at the invalid value. as expected
Go back and delete value, blur again.
Validation message shows error. as expected
The field value changes back to the last valid (normalized) value. NOT AS EXPECTED, want it to stay empty
It is a bit counter-intuitive if the validation message shows that a value should be supplied, while the old valid value shows. Any idea what I can do to have the field stay clear even if the validation fails?
I perhaps need to emphasise the following about the converter:
null/empty strings are converted to the null object. (But the validator requires a valid non-null value.)
An ajax call on blur is employed to always run the converter when done typing in the value - the intention is that the supplied value is converted to the object of the backing bean member, then converted back to a ("normalized") string representation of the object. ("Normalization" examples: conforming to the enum constant name's case, or being in a certain date format.)
I'm using JSF 2.2 (Mojarra 2.2.7) on GlassFish 4.1.
Sample code (pruned back considerably to just show a test case, but contains the important parts - I think):
Backing bean:
#ManagedBean
#ViewScoped
public class TestBean implements Serializable {
:
private String test; // plus g/setters
:
}
JSF view page:
<h:form>
:
<h:inputText id="test" binding="#{test}" value="#{testBean.test}">
<cp:uppercaseConverter />
<f:validator binding="${testValidator}" />
<f:ajax event="blur" render="#this msg_test" /><!-- #this forces converter on blur -->
</h:inputText>
<h:messages id="msg_test" for="test" />
:
</h:form>
Custom converter: (Remember, somewhat artificial for testing purposes)
/**
* The "object" as used in the backing bean is simply the uppercase version of
* the input value in the field.
*/
#FacesConverter("UppercaseConverter")
public class UppercaseConverter implements Converter, Serializable {
private static final long serialVersionUID = 1L;
#Override
public Object getAsObject(
FacesContext ctx, UIComponent component, String newValue
) throws ConverterException {
if (newValue == null || newValue.trim().equals(""))
return null;
return newValue.toUpperCase();
}
#Override
public String getAsString(
FacesContext ctx, UIComponent component, Object value
) throws ConverterException {
if (value == null) return "";
return value.toString();
}
}
Custom validator: (Remember, somewhat artificial for testing purposes)
/**
* Values are valid only if they start with an "A" (not case sensitive).
* Thus, null or empty values are also invalid implicitly.
*/
#ManagedBean
#FacesValidator
public class TestValidator implements Validator {
#Override
public void validate(
FacesContext context, UIComponent component, Object value
) throws ValidatorException {
if (value == null || !value.toString().trim().toLowerCase().startsWith("a"))
throw new ValidatorException(new FacesMessage("A value starting with A or a is required"));
}
}
The problem is obviously caused by the <f:ajax> call that also renders #this, together with the message element.
Rendering #this causes the converter to run and show the converted value in the field (however this can't be done for an invalid or empty value, obviously).
I think the simplest workaround for now is to remove the #this from the render attribute. The result will have only a slight aesthetic drawback which is not the end of the world - much less severe (confusing the user) in my opinion than having an "this is invalid" message showing on a valid and previously entered and overwritten value.

jsf converter loses injected property

I had this working before, but then I changed some things, and I can't get it to work again. I am trying to use my service tier to hit the database and get a correct object from my converter class, depending on what the user clicks. I inject the service property into my converter with spring. During debugging, I can see that the property gets sets properly. But then when I go to call getService, it is null.
#FacesConverter("PlaceConverter")
#SessionScoped
public class PlaceConverter implements Converter {
private SearchQueryService searchQueryService;
/**
* #return the searchQueryService
*/
public SearchQueryService getSearchQueryService() {
return searchQueryService;
}
/**
* #param searchQueryService the searchQueryService to set
*/
public void setSearchQueryService(SearchQueryService searchQueryService) {
this.searchQueryService = searchQueryService;
}
#Override
public Object getAsObject(FacesContext arg0, UIComponent arg1, String submittedValue) {
try {
Criteria criteria = new Criteria();
criteria.setId(Integer.parseInt(submittedValue));
return getSearchQueryService().findPlaces(criteria).get(0);
} catch (NumberFormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
public String getAsString(FacesContext arg0, UIComponent arg1, Object value) {
((Place) value).setCategory(" (" + ((Place) value).getCategory() + ")");
return String.valueOf(((Place) value).getPlaceId());
}
}
<bean id="placeConverterBean" class="com.ghghg.converter.PlaceConverter">
<property name="searchQueryService" ref="searchQueryServiceBean" />
</bean>
Dependency injection in a converter works only if the converter is declared as a managed bean by the dependency injection framework in question. E.g. JSF's own #ManagedBean, or CDI's #Named, or Spring's #Component. You should remove the #FacesConverter altogether and reference the converter instance in EL scope instead of referencing it by the converter ID.
Thus, so
<h:inputXxx converter="#{placeConverter}" />
or
<f:converter binding="#{placeConverter}" />
instead of
<h:inputXxx converter="PlaceConverter" />
or
<f:converter converterId="PlaceConverter" />
Your concrete problem suggests that you were referencing it by converter ID (thus, via #FacesConverter). This way you end up getting a converter instance without any injected dependencies.
See also:
How to inject Spring bean into JSF converter
As to the role of the converter itself, this is mandatory because HTML code is represented as one large string and HTTP request parameter values can only be represented as strings. Complex Java objects would otherwise be printed via Object#toString() like so com.example.Place#hashcode, making it unusable in the server side.
I found a better way, and probably more proper way to do get what I wanted. I was not completely sure how the converter works and how the value of the selected item gets passed back to the managed bean. I just declared a new Place object in my method, set the required values. Then I saw that it got passed to my managed bean
I got it to work like this in java EE with jsf 2.0. By making the converter a member of the backing bean. I instantiate this member using CDI but it should work the same with spring.
First the backing bean:
#ViewScoped
#ManagedBean
public class SomeView implements Serializable {
private static final long serialVersionUID = 1L;
#Inject
private SomeConverter converter;
public Converter getConverter() {
return converter;
}
}
And then this is the jsf xhtml:
<p:selectOneMenu id="someId" value="#{someView.value}" converter="#{someView.converter}">
<f:selectItems value="#{someView.values}" var="object" itemLabel="#{object.name}" />
</p:selectOneMenu>
Converter comes to play before updating your model bean. When user fill some input and this value is transferred to server first are updated your server side components and next conversion has happened. Converted values as saved in your bean (with method getAsObject) and before rendering the view values from beans are again converted to String because from user side everything is a string (then method getAsString is invoked).
In summary - Converter methods are the best place to change user input into your application logic, bean fields and in other way to convert your logic, bean fields into user friendly strings.
Due to your question and problem. You mean that SearchQueryService isn't available inside getAsObject method. Try to add an addnotation #Resource with proper name attribute and then it should be injected by your container.

Can I make a JSF Validator with options specified in the JSF markup?

For example: I have a JSF Validator to validate e-mail thusly:
#FacesValidator(value="validateEmail")
public class Email implements Validator
{
private static final String EMAIL_REGEXP =
"^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*#[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
#Override
public void validate(FacesContext context, UIComponent c, Object val) throws ValidatorException
{
String email = (String) val;
Pattern mask = null;
mask = Pattern.compile(EMAIL_REGEXP);
Matcher matcher = mask.matcher(email);
if (!matcher.matches()) {
FacesMessage message = new FacesMessage();
message.setDetail("Must be of the form xxx#yyy.zzz");
message.setSummary("E-mail Addresss not valid");
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}
}
}
This validator will throw an exception if the user doesn't enter an e-mail. However sometimes I want to make the e-mail an optional field. Is there a way to do this that doesn't require me to write a different validator?
Ideally I would like it to check for a parameter somewhere in the JSF markup that uses it.
To answer your concrete question, you can make use of <f:attribute> which is enclosed in the same component.
<h:inputText>
<f:validator validatorId="validateEmail" />
<f:attribute name="foo" value="bar" />
</h:inputText>
Inside the validate() method it's available by UIComponent#getAttributes() of the 2nd UIComponent argument.
String foo = component.getAttributes().get("foo"); // bar
The better solution in your particular case, however, is just to let the validator return when the value is null or empty.
if (email == null || email.isEmpty()) {
return;
}
In JSF 1.x this would not be necessary because validators wouldn't be called when the input is null or empty. But in JSF 2.x this has changed in order to support JSR303 bean validation. So if you want to skip validation on empty fields, you'd need to check this in every single JSF Validator.
Note that when you have this in your web.xml
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
then you can safely omit the email.isEmpty() part from the check because it would always be null instead of an empty string.
Unrelated to the concrete problem, the pattern which you've there is outdated. These days unicode characters are allowed in email addresses.
private static final String EMAIL_REGEXP = "([^.#]+)(\\.[^.#]+)*#([^.#]+\\.)+([^.#]+)";
(note that ^ and $ are unnecessary because Pattern implicitly uses this already)
Further I also suggest to do the Pattern#compile() directly on a static final variable. Compiling a pattern is relatively expensive and a Pattern instance is threadsafe anyway.
private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEXP);

Why does <h:inputText required="true"> allow blank spaces?

When I set required="true" in a <h:inputText>, it still allows blank spaces. I have been trying to modify the jsf-api.jar but I could not understand how to generate new a JAR, so I tried to modify the isEmpty() method from UIInput class and compile it, open the jsf-api.jar and replace it with the new one, but it did not work.
What I need is to do trim() when the user writes in a <h:inputText> to do not allow blank spaces. How can I achieve this?
If you want to download the jsf-api.jar resource, you can do it, just read how to at: http://javaserverfaces.java.net/checkout.html.
That's normal and natural behaviour and not JSF specific. A blank space may be perfectly valid input. The required="true" only kicks in on empty inputs, not in filled inputs. In JSF you can however just create a Converter for String class to automatically trim the whitespace.
#FacesConverter(forClass=String.class)
public class StringTrimmer implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return value != null ? value.trim() : null;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return (String) value;
}
}
Put this class somewhere in your project. It'll be registered automatically thanks to #FacesConverter and invoked automatically for every String entry thanks to forClass=String.class.
No need to hack the JSF API/impl. This makes no sense.
If you want to turn off the behavior that BalusC notes as one of the answers as standard JSF behavior, you can modify the web.xml and include the following.
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
<context-param>
This will trigger the JSF framework to consider the values null which may be preferable, or an alternative to the answer from BalusC.

Resources