org.omnifaces.cdi.validator.ValidatorManager Maven / Gradle / Ivy
Show all versions of omnifaces Show documentation
/*
* Copyright OmniFaces
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.omnifaces.cdi.validator;
import static org.omnifaces.util.BeansLocal.getReference;
import static org.omnifaces.util.BeansLocal.resolveExact;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.AmbiguousResolutionException;
import jakarta.enterprise.inject.Specializes;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.faces.application.Application;
import jakarta.faces.application.NavigationHandler;
import jakarta.faces.application.ResourceHandler;
import jakarta.faces.convert.Converter;
import jakarta.faces.event.ActionListener;
import jakarta.faces.event.PhaseListener;
import jakarta.faces.event.SystemEventListener;
import jakarta.faces.validator.FacesValidator;
import jakarta.faces.validator.Validator;
import jakarta.inject.Inject;
import org.omnifaces.application.OmniApplication;
import org.omnifaces.application.OmniApplicationFactory;
/**
*
* The @FacesValidator
is by default not eligible for dependency injection by @Inject
nor @EJB
.
* There is a workaround
* for EJB, but this is nasty and doesn't work out for CDI. Another way
* would be to make it a JSF or CDI managed bean, however this doesn't register the validator instance into the JSF application context,
* and hence you won't be able to make use of {@link Application#createValidator(String)} on it.
*
* Initially, this should be solved in JSF 2.2 which comes with new support for dependency injection in among others all
* jakarta.faces.*.*Factory
, {@link NavigationHandler}, {@link ResourceHandler},
* {@link ActionListener}, {@link PhaseListener} and {@link SystemEventListener} instances.
* The {@link Converter} and {@link Validator} were initially also among them, but they broke a TCK test and were at the
* last moment removed from dependency injection support.
*
* The support is expected to come back in JSF 2.3, but we just can't wait any longer.
* MyFaces CODI has support for it,
* but it requires an additional @Advanced
annotation.
* OmniFaces solves this by implicitly making all {@link FacesValidator} instances eligible for dependency injection
* without any further modification.
*
* The {@link ValidatorManager} provides access to all {@link FacesValidator} annotated {@link Validator} instances which are made eligible for CDI.
*
*
bean-discovery-mode
*
* In Java EE 7's CDI 1.1, when having a CDI 1.1 compatible beans.xml
, by default only classes with an
* explicit CDI managed bean scope annotation will be registered for dependency injection support. In order to cover
* {@link FacesValidator} annotated classes as well, you need to explicitly set bean-discovery-mode="all"
* attribute in beans.xml
. This was not necessary in Mojarra versions older than 2.2.9 due to an
* oversight. If you want to keep the default of
* bean-discovery-mode="annotated"
, then you need to add {@link Dependent} annotation to the validator class.
*
*
AmbiguousResolutionException
*
* In case you have a {@link FacesValidator} annotated class extending another {@link FacesValidator} annotated class
* which in turn extends a standard validator, then you may with bean-discovery-mode="all"
face an
* {@link AmbiguousResolutionException}. This can be solved by placing {@link Specializes} annotation on the subclass.
*
*
JSF 2.3 compatibility
*
* OmniFaces 3.0 continued to work fine with regard to managed validators which are initially developed for JSF 2.2.
* However, JSF 2.3 introduced two new features for validators: parameterized validators and managed validators.
* When the validator is parameterized as in implements Validator<T>
, then you need to use
* at least OmniFaces 3.1 wherein the incompatibility was fixed. When the validator is managed with the new JSF 2.3
* managed=true
attribute set on the {@link FacesValidator} annotation, then the validator won't be
* managed by OmniFaces and will continue to work fine for JSF. But the <o:validator> tag won't be able to
* set attributes on it.
*
* @author Radu Creanga {@literal }
* @author Bauke Scholtz
* @see OmniApplication
* @see OmniApplicationFactory
* @since 1.6
*/
@ApplicationScoped
@SuppressWarnings("rawtypes")
public class ValidatorManager {
// Dependencies ---------------------------------------------------------------------------------------------------
@Inject
private BeanManager manager;
private Map> validatorsById = new HashMap<>();
// Actions --------------------------------------------------------------------------------------------------------
/**
* Returns the validator instance associated with the given validator ID,
* or null
if there is none.
* @param application The involved JSF application.
* @param validatorId The validator ID of the desired validator instance.
* @return the validator instance associated with the given validator ID,
* or null
if there is none.
*/
public Validator createValidator(Application application, String validatorId) {
Validator validator = application.createValidator(validatorId);
Bean bean = validatorsById.get(validatorId);
if (bean == null && !validatorsById.containsKey(validatorId)) {
if (isUnmanaged(validator)) {
bean = resolve(validator.getClass(), validatorId);
}
validatorsById.put(validatorId, bean);
}
return (bean != null) ? getReference(manager, bean) : validator;
}
// Helpers --------------------------------------------------------------------------------------------------------
private boolean isUnmanaged(Validator validator) {
if (validator == null) {
return false;
}
FacesValidator annotation = validator.getClass().getAnnotation(FacesValidator.class);
if (annotation == null) {
return false;
}
return !annotation.managed();
}
@SuppressWarnings("unchecked")
private Bean resolve(Class validatorClass, String validatorId) {
// First try by class.
Bean bean = (Bean) resolveExact(manager, validatorClass);
if (bean == null) {
FacesValidator annotation = validatorClass.getAnnotation(FacesValidator.class);
if (annotation != null) {
// Then by own annotation, if any.
bean = (Bean) resolveExact(manager, validatorClass, annotation);
}
if (bean == null) {
// Else by fabricated annotation literal.
bean = (Bean) resolveExact(manager, validatorClass, new FacesValidator() {
@Override
public Class annotationType() {
return FacesValidator.class;
}
@Override
public String value() {
return validatorId;
}
@Override
public boolean managed() {
return false;
}
@Override
public boolean isDefault() {
return false;
}
});
}
}
return bean;
}
}