/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you 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 | |
* | |
* http://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.apache.myfaces.extensions.validator.core.interceptor; | |
import org.apache.myfaces.extensions.validator.core.el.ELHelper; | |
import org.apache.myfaces.extensions.validator.internal.UsageCategory; | |
import org.apache.myfaces.extensions.validator.internal.UsageInformation; | |
import org.apache.myfaces.extensions.validator.core.ExtValContext; | |
import org.apache.myfaces.extensions.validator.core.ValidationModuleKey; | |
import org.apache.myfaces.extensions.validator.core.ExtValCoreConfiguration; | |
import org.apache.myfaces.extensions.validator.core.metadata.extractor.MetaDataExtractor; | |
import org.apache.myfaces.extensions.validator.core.property.PropertyInformation; | |
import org.apache.myfaces.extensions.validator.core.storage.RendererInterceptorPropertyStorage; | |
import org.apache.myfaces.extensions.validator.core.renderkit.exception.SkipBeforeInterceptorsException; | |
import org.apache.myfaces.extensions.validator.core.renderkit.exception.SkipRendererDelegationException; | |
import org.apache.myfaces.extensions.validator.core.renderkit.exception.SkipAfterInterceptorsException; | |
import org.apache.myfaces.extensions.validator.core.renderkit.RendererProxy; | |
import org.apache.myfaces.extensions.validator.core.recorder.ProcessedInformationRecorder; | |
import org.apache.myfaces.extensions.validator.util.ExtValUtils; | |
import javax.faces.context.FacesContext; | |
import javax.faces.component.UIComponent; | |
import javax.faces.component.EditableValueHolder; | |
import javax.faces.convert.ConverterException; | |
import javax.faces.render.Renderer; | |
import javax.faces.validator.ValidatorException; | |
import javax.el.PropertyNotFoundException; | |
import java.io.IOException; | |
import java.util.Map; | |
import java.util.HashMap; | |
import java.util.logging.Level; | |
/** | |
* A basic implementation of {@link RendererInterceptor} for validating fields. | |
* It adds some extension point for subclasses and performs tasks like : <br/> | |
* - storing field values ({@link #recordProcessedInformation}) <br/> | |
* - resetting required information property UIComponent <br/> | |
* - calling before and after Validation interceptors <br/> | |
* - etc ... | |
* | |
* @author Gerhard Petracek | |
* @since x.x.3 | |
*/ | |
@UsageInformation(UsageCategory.REUSE) | |
public abstract class AbstractValidationInterceptor extends AbstractRendererInterceptor | |
{ | |
private ELHelper elHelper; | |
/** | |
* In case of required initialization | |
* it's needed for some use-cases to reset the (required-)state of the components. | |
* Such a reset is just needed if required initialization is activated. | |
* | |
* @return true if required initialization is supported by the current implementation, false otherwise | |
*/ | |
protected boolean isRequiredInitializationSupported() | |
{ | |
return false; | |
} | |
/** | |
* Sets required property of UIComponent to false after decoding. | |
* It's needed for special use-cases if required initialization is supported. | |
* The final required validation will be done by the corresponding constraint validator. | |
* | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void afterDecode(FacesContext facesContext, UIComponent uiComponent, Renderer wrapped) | |
throws SkipAfterInterceptorsException | |
{ | |
/* | |
* component initialization sets a component to required if there are constraints which indicate it | |
* the required flag in a component leads to problems with h:messages (additional message) as well as | |
* incompatibilities with skip validation and severities | |
*/ | |
if(uiComponent instanceof EditableValueHolder && ExtValUtils.isRequiredResetActivated() && | |
isRequiredInitializationSupported() && ExtValUtils.isRequiredInitializationActive()) | |
{ | |
((EditableValueHolder)uiComponent).setRequired(false); | |
} | |
} | |
/** | |
* Before the component gets rendered the interceptor initializes the component based on the meta-data | |
* which is provided by the referenced property (if component initialization is activated). | |
* | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void beforeEncodeBegin(FacesContext facesContext, UIComponent uiComponent, Renderer wrapped) | |
throws IOException, SkipBeforeInterceptorsException, SkipRendererDelegationException | |
{ | |
if(processComponent(uiComponent) && !isComponentInitializationDeactivated()) | |
{ | |
initComponent(facesContext, uiComponent); | |
} | |
} | |
/** | |
* Initialize the component based on the meta-data which is provided by the referenced property. | |
* | |
* @param facesContext The JSF Context | |
* @param uiComponent The component which is processed | |
*/ | |
protected abstract void initComponent(FacesContext facesContext, UIComponent uiComponent); | |
/** | |
* The method performs the validation of the field and calls the registered interceptors regarding the validation. | |
* | |
* The main steps are :<br/> | |
* - Get the converted value from the renderer (possibly cached by the RendererProxy)<br/> | |
* - Record the value (e.g. for cross validation)<br/> | |
* - Adjust the converted value for interpret empty values as null.<br/> | |
* - Execute the beforeValidation method of the registered PropertyValidationInterceptor's. <br/> | |
* - Perform the validation when the PropertyValidationInterceptor have indicate it that it should be performed. | |
* <br/> | |
* - When a validation error occurred, ask the ViolationSeverityInterpreter if this validation should result in an | |
* exception <br/> | |
* - Execute the afterValidation method of the registered PropertyValidationInterceptor's. (when validation actually | |
* tooks place) | |
* | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void beforeGetConvertedValue(FacesContext facesContext, UIComponent uiComponent, Object o, Renderer wrapped) | |
throws ConverterException, SkipBeforeInterceptorsException, SkipRendererDelegationException | |
{ | |
Object convertedObject; | |
try | |
{ | |
if(wrapped instanceof RendererProxy) | |
{ | |
convertedObject = ((RendererProxy)wrapped).getCachedConvertedValue(facesContext, uiComponent, o); | |
} | |
else | |
{ | |
convertedObject = wrapped.getConvertedValue(facesContext, uiComponent, o); | |
} | |
} | |
catch (PropertyNotFoundException r) | |
{ | |
this.logger.log(Level.SEVERE, "it seems you are using an invalid binding. " + wrapped.getClass().getName() | |
+ ": conversion failed. normally this is >not< a myfaces extval issue!", r); | |
throw r; | |
} | |
setRendererInterceptorProperties(uiComponent); | |
if(recordProcessedInformation()) | |
{ | |
//record user input e.g. for cross-component validation | |
for(ProcessedInformationRecorder recorder : ExtValContext.getContext().getProcessedInformationRecorders()) | |
{ | |
recorder.recordUserInput(uiComponent, convertedObject); | |
logger.finest(recorder.getClass().getName() + " called"); | |
} | |
} | |
boolean validateValue = false; | |
try | |
{ | |
if(processComponent(uiComponent)) | |
{ | |
convertedObject = transformValueForValidation(convertedObject); | |
validateValue = validateValue(convertedObject); | |
if(validateValue && processBeforeValidation(facesContext, uiComponent, convertedObject)) | |
{ | |
processValidation(facesContext, uiComponent, convertedObject); | |
} | |
} | |
} | |
catch (ValidatorException e) | |
{ | |
try | |
{ | |
//ViolationSeverityInterpreter might decide that it isn't an exception | |
ExtValUtils.tryToThrowValidatorExceptionForComponent(uiComponent, e.getFacesMessage(), e); | |
} | |
catch (ValidatorException finalException) | |
{ | |
throw new ConverterException(e.getFacesMessage(), e); | |
} | |
} | |
finally | |
{ | |
if(validateValue) | |
{ | |
processAfterValidation(facesContext, uiComponent, convertedObject); | |
} | |
resetRendererInterceptorProperties(uiComponent); | |
} | |
} | |
/** | |
* Execute the beforeValidation method of the registered PropertyValidationInterceptor's. | |
* | |
* @param facesContext The JSF Context | |
* @param uiComponent The UIComponent which is processed. | |
* @param value The value to validate | |
* @return true when validation can proceed, false otherwise. | |
*/ | |
protected boolean processBeforeValidation(FacesContext facesContext, UIComponent uiComponent, Object value) | |
{ | |
return ExtValUtils.executeGlobalBeforeValidationInterceptors(facesContext, uiComponent, value, | |
PropertyInformation.class.getName(), getPropertyInformation(facesContext, uiComponent), getModuleKey()); | |
} | |
/** | |
* Execute the afterValidation method of the registered PropertyValidationInterceptor's. | |
* | |
* @param facesContext The JSF Context | |
* @param uiComponent The UIComponent which is processed. | |
* @param value The value which has just been validated. | |
*/ | |
protected void processAfterValidation(FacesContext facesContext, UIComponent uiComponent, Object value) | |
{ | |
ExtValUtils.executeGlobalAfterValidationInterceptors(facesContext, uiComponent, value, | |
PropertyInformation.class.getName(), getPropertyInformation(facesContext, uiComponent), getModuleKey()); | |
} | |
/** | |
* Extrancts the {@link PropertyInformation} for the given component. | |
* | |
* @param facesContext The JSF Context | |
* @param uiComponent The UIComponent which is processed. | |
* @return the information of the referenced property (e.g. base object, property name, meta-data, ...) | |
*/ | |
protected PropertyInformation getPropertyInformation(FacesContext facesContext, UIComponent uiComponent) | |
{ | |
Map<String, Object> properties = getPropertiesForComponentMetaDataExtractor(uiComponent); | |
MetaDataExtractor metaDataExtractor = getComponentMetaDataExtractor(properties); | |
return metaDataExtractor.extract(facesContext, uiComponent); | |
} | |
/** | |
* Implementations must return the MetaDataExtractor that will perform the extraction of the meta data from the | |
* component. The component itself is present in the properties map (it might influence the type of the returned | |
* {@link MetaDataExtractor}. | |
* | |
* @param properties Properties that can be used to determine the extractor which is returned. | |
* @return The MetaDataExtractor used for performing the xtraction of the meta data from the component. | |
*/ | |
protected abstract MetaDataExtractor getComponentMetaDataExtractor(Map<String, Object> properties); | |
/** | |
* Converts an empty String to null when the parameter interpretEmptyStringValuesAsNull is set. | |
* | |
* @param convertedObject Converted objected | |
* @return Adjusted value that should be used from now on as converted value. | |
*/ | |
protected Object transformValueForValidation(Object convertedObject) | |
{ | |
if ("".equals(convertedObject) && interpretEmptyStringValuesAsNull()) | |
{ | |
return null; | |
} | |
return convertedObject; | |
} | |
/** | |
* Evaluates if the value should be validated in case it is empty (null or no characters). | |
* ExtVal also uses a config parameter introduced by JSF 2 which allows to skip the validation of empty fields. | |
* | |
* @param convertedObject The converted value. | |
* @return true if the given value should be validated, false otherwise | |
*/ | |
protected boolean validateValue(Object convertedObject) | |
{ | |
if(isValueToValidateEmpty(convertedObject) && !validateEmptyFields()) | |
{ | |
this.logger.fine("empty field validation is deactivated in the web.xml - see: " + | |
"javax.faces.VALIDATE_EMPTY_FIELDS"); | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Defines if a value is empty. The definition of empty is that it is null or has no characters in the String value. | |
* | |
* @param convertedObject The converted value. | |
* @return true if the given value is the representation of an empty value | |
*/ | |
protected boolean isValueToValidateEmpty(Object convertedObject) | |
{ | |
return convertedObject == null || "".equals(convertedObject); | |
} | |
/** | |
* Uses a config parameter (javax.faces.VALIDATE_EMPTY_FIELDS) which was introduced by JSF 2 for deactivating | |
* the validation of empty fields. | |
* | |
* @return true if the validation of empty fields is enabled, false otherwise | |
*/ | |
protected boolean validateEmptyFields() | |
{ | |
return ExtValUtils.validateEmptyFields(); | |
} | |
/** | |
* Uses a config parameter (javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL) | |
* which was introduced by JSF 2 for converting empty strings to null. | |
* | |
* @return true if an empty string should be replaced with null (for the validation process), false otherwise | |
*/ | |
protected boolean interpretEmptyStringValuesAsNull() | |
{ | |
return ExtValUtils.interpretEmptyStringValuesAsNull(); | |
} | |
/** | |
* A concrete implementation has to perform the actual validation of the value. | |
* | |
* @param facesContext The JSF Context | |
* @param uiComponent The UIComponent which is processed. | |
* @param convertedObject The (adjusted) converted value. | |
*/ | |
protected abstract void processValidation( | |
FacesContext facesContext, UIComponent uiComponent, Object convertedObject); | |
/** | |
* Based on basic rules the method checks if the current component should be processed. | |
* | |
* @param uiComponent The UIComponent which is processed. | |
* @return true if the given component should be processed by the current interceptor, false otherwise | |
*/ | |
protected boolean processComponent(UIComponent uiComponent) | |
{ | |
return uiComponent instanceof EditableValueHolder && isValueBindingOfComponentValid(uiComponent); | |
} | |
/** | |
* Returns the ELHelper to be used in the process of the validation. It is cached for performance reasons. | |
* @return The ELHelper. | |
*/ | |
protected ELHelper getELHelper() | |
{ | |
if(this.elHelper == null) | |
{ | |
this.elHelper = ExtValUtils.getELHelper(); | |
} | |
return this.elHelper; | |
} | |
private boolean isValueBindingOfComponentValid(UIComponent uiComponent) | |
{ | |
try | |
{ | |
return getELHelper().getPropertyDetailsOfValueBinding(uiComponent) != null; | |
} | |
catch (Exception e) | |
{ | |
return false; | |
} | |
} | |
private boolean isComponentInitializationDeactivated() | |
{ | |
return ExtValCoreConfiguration.get().deactivateComponentInitialization(); | |
} | |
/** | |
* Signals if the converted value should be stored e.g. for the cross-validation process. | |
* @return true if the converted value should be recorded, false otherwise | |
*/ | |
protected boolean recordProcessedInformation() | |
{ | |
//override if needed | |
return false; | |
} | |
/** | |
* Identification of the validation module. | |
* A key is just needed if other implementations should be restricted to 1-n special validation modules. | |
* | |
* @return Identification of the validation module. | |
*/ | |
protected Class getModuleKey() | |
{ | |
//override if needed | |
return null; | |
} | |
/** | |
* Create the properties which can be used by a {@link MetaDataExtractor}. | |
* By default it adds a key which identifies the current validation-module | |
* and the current component (to avoid changes of the api for quite special use-cases). | |
* | |
* @param uiComponent The UIComponent which is processed. | |
* @return properties used by the selection of the MetaDataExtractor | |
*/ | |
protected Map<String, Object> getPropertiesForComponentMetaDataExtractor(UIComponent uiComponent) | |
{ | |
return createProperties(uiComponent); | |
} | |
/** | |
* Create the properties which can be used by a {@link MetaDataExtractor}. | |
* Returns the properties which will be made available to interceptors. By default the moduleKey and the UIComponent | |
* itself is added. | |
* | |
* @param uiComponent The UIComponent which is processed. | |
* @return properties for the interceptors. | |
*/ | |
protected Map<String, Object> getInterceptorProperties(UIComponent uiComponent) | |
{ | |
return createProperties(uiComponent); | |
} | |
private Map<String, Object> createProperties(UIComponent uiComponent) | |
{ | |
Map<String, Object> result = new HashMap<String, Object>(); | |
if(getModuleKey() != null) | |
{ | |
result.put(ValidationModuleKey.class.getName(), getModuleKey()); | |
} | |
result.put(UIComponent.class.getName(), uiComponent); | |
return result; | |
} | |
/** | |
* Stores additional properties for the current process (to avoid api changes). | |
* | |
* @param uiComponent The UIComponent which is processed. | |
*/ | |
private void setRendererInterceptorProperties(UIComponent uiComponent) | |
{ | |
RendererInterceptorPropertyStorage interceptorPropertyStorage = getRendererInterceptorPropertyStorage(); | |
Map<String, Object> properties = getInterceptorProperties(uiComponent); | |
for(Map.Entry<String, Object> entry : properties.entrySet()) | |
{ | |
interceptorPropertyStorage.setProperty(entry.getKey(), entry.getValue()); | |
} | |
} | |
private void resetRendererInterceptorProperties(UIComponent uiComponent) | |
{ | |
RendererInterceptorPropertyStorage interceptorPropertyStorage = getRendererInterceptorPropertyStorage(); | |
for(String key : getInterceptorProperties(uiComponent).keySet()) | |
{ | |
interceptorPropertyStorage.removeProperty(key); | |
} | |
} | |
private RendererInterceptorPropertyStorage getRendererInterceptorPropertyStorage() | |
{ | |
return ExtValUtils.getStorage(RendererInterceptorPropertyStorage.class, | |
RendererInterceptorPropertyStorage.class.getName()); | |
} | |
} |