blob: 06dc85964bd64a7aec39e5b556021608ea1d4de8 [file] [log] [blame]
/*
* 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.beanval;
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.recorder.ProcessedInformationRecorder;
import org.apache.myfaces.extensions.validator.core.ExtValContext;
import org.apache.myfaces.extensions.validator.core.WebXmlParameter;
import org.apache.myfaces.extensions.validator.core.validation.strategy.ValidationStrategy;
import org.apache.myfaces.extensions.validator.core.property.PropertyInformation;
import org.apache.myfaces.extensions.validator.core.property.PropertyInformationKeys;
import org.apache.myfaces.extensions.validator.core.property.PropertyDetails;
import org.apache.myfaces.extensions.validator.core.metadata.extractor.MetaDataExtractor;
import org.apache.myfaces.extensions.validator.core.metadata.MetaDataEntry;
import org.apache.myfaces.extensions.validator.core.metadata.transformer.MetaDataTransformer;
import org.apache.myfaces.extensions.validator.core.interceptor.AbstractRendererInterceptor;
import org.apache.myfaces.extensions.validator.util.ExtValUtils;
import org.apache.myfaces.extensions.validator.beanval.property.BeanValidationPropertyInformationKeys;
import org.apache.myfaces.extensions.validator.beanval.validation.strategy.BeanValidationStrategyAdapter;
import org.apache.myfaces.extensions.validator.internal.ToDo;
import org.apache.myfaces.extensions.validator.internal.Priority;
import org.apache.myfaces.extensions.validator.internal.UsageInformation;
import org.apache.myfaces.extensions.validator.internal.UsageCategory;
import javax.faces.context.FacesContext;
import javax.faces.component.UIComponent;
import javax.faces.component.EditableValueHolder;
import javax.faces.render.Renderer;
import javax.faces.convert.ConverterException;
import javax.faces.validator.ValidatorException;
import javax.faces.application.FacesMessage;
import javax.validation.Validation;
import javax.validation.BeanDescriptor;
import javax.validation.ElementDescriptor;
import javax.validation.ConstraintDescriptor;
import javax.validation.ValidatorFactory;
import javax.validation.ConstraintViolation;
import java.util.Set;
import java.util.Map;
import java.io.IOException;
/**
* @author Gerhard Petracek
* @since x.x.3
*/
@ToDo(value = Priority.HIGH, description = "sync jsf 2.0 specific changes with bv-branch")
@UsageInformation(UsageCategory.INTERNAL)
public class BeanValidationInterceptor extends AbstractRendererInterceptor
{
private ValidatorFactory validationFactory = Validation.buildDefaultValidatorFactory();
@Override
public void beforeEncodeBegin(FacesContext facesContext, UIComponent uiComponent, Renderer wrapped)
throws IOException, SkipBeforeInterceptorsException, SkipRendererDelegationException
{
if(processComponent(uiComponent))
{
initComponent(facesContext, uiComponent);
}
}
@ToDo(value = Priority.HIGH, description = "check groups")
protected void initComponent(FacesContext facesContext, UIComponent uiComponent)
{
if (logger.isTraceEnabled())
{
logger.trace("start to init component " + uiComponent.getClass().getName());
}
MetaDataExtractor metaDataExtractor = ExtValUtils.getComponentMetaDataExtractor();
PropertyDetails propertyDetails = metaDataExtractor.extract(facesContext, uiComponent)
.getInformation(PropertyInformationKeys.PROPERTY_DETAILS, PropertyDetails.class);
if(propertyDetails.getBaseObject() == null)
{
if(this.logger.isWarnEnabled())
{
this.logger.warn("no base object at " + propertyDetails.getKey() +
" component-id: " + uiComponent.getClientId(facesContext));
}
return;
}
/*TODO
//TODO extract groups - see ValidationGroupProvider
Class[] foundGroups = ExtValBeanValidationContext.getCurrentInstance().getGroups(
facesContext.getViewRoot().getViewId(),
uiComponent.getClientId(facesContext));
foundGroups = mergeFoundGroupsWithValidatorGroups(
foundGroups, ((EditableValueHolder)uiComponent).getValidators());
*/
BeanDescriptor beanDescriptor = this.validationFactory.getValidator().getConstraintsForClass(
propertyDetails.getBaseObject().getClass()/*, foundGroups*/);
ElementDescriptor elementDescriptor = beanDescriptor.getConstraintsForProperty(propertyDetails.getProperty());
if(elementDescriptor == null)
{
return;
}
ValidationStrategy validationStrategy;
MetaDataTransformer metaDataTransformer;
MetaDataEntry entry;
Map<String, Object> metaData;
for (ConstraintDescriptor<?> constraintDescriptor : elementDescriptor.getConstraintDescriptors())
{
//TODO check groups
validationStrategy = new BeanValidationStrategyAdapter(constraintDescriptor);
/*
* per default
* org.apache.myfaces.extensions.validator.beanval.metadata.transformer.BeanValidationMetaDataTransformer
* is bound to BeanValidationStrategyAdapter
* don't use it directly - it's possible to deactivate
* org.apache.myfaces.extensions.validator.beanval.metadata.transformer.mapper
* .DefaultBeanValidationStrategyToMetaDataTransformerNameMapper
*/
metaDataTransformer = ExtValUtils.getMetaDataTransformerForValidationStrategy(validationStrategy);
if (metaDataTransformer != null)
{
if (this.logger.isDebugEnabled())
{
this.logger.debug(metaDataTransformer.getClass().getName() + " instantiated");
}
entry = new MetaDataEntry();
entry.setKey(constraintDescriptor.getAnnotation().annotationType().getName());
entry.setValue(constraintDescriptor);
//TODO (?) add type of property for meta-data transformation (e.g. size: string vs. number)
metaData = metaDataTransformer.convertMetaData(entry);
}
else
{
metaData = null;
}
if (metaData == null)
{
continue;
}
//get component initializer for the current component and configure it
//also in case of skipped validation to reset e.g. the required attribute
if (!metaData.isEmpty())
{
ExtValUtils.configureComponentWithMetaData(facesContext, uiComponent, metaData);
}
}
if (logger.isTraceEnabled())
{
logger.trace("init component of " + uiComponent.getClass().getName() + " finished");
}
}
@Override
public void beforeGetConvertedValue(FacesContext facesContext, UIComponent uiComponent, Object o, Renderer wrapped)
throws ConverterException, SkipBeforeInterceptorsException, SkipRendererDelegationException
{
Object convertedObject = wrapped.getConvertedValue(facesContext, uiComponent, o);
//recorde user input e.g. for cross-component validation
for (ProcessedInformationRecorder recorder : ExtValContext.getContext().getProcessedInformationRecorders())
{
recorder.recordUserInput(uiComponent, convertedObject);
if (logger.isTraceEnabled())
{
logger.trace(recorder.getClass().getName() + " called");
}
}
try
{
if(processComponent(uiComponent))
{
processValidation(facesContext, uiComponent, convertedObject);
}
}
catch (ValidatorException e)
{
throw new ConverterException(e.getFacesMessage(), e);
}
}
protected boolean processComponent(UIComponent uiComponent)
{
return uiComponent instanceof EditableValueHolder;
}
@ToDo(value = Priority.HIGH, description = "use ExtValUtils#createFacesMessage")
protected void processValidation(FacesContext facesContext, UIComponent uiComponent, Object convertedObject)
{
if (!"true".equalsIgnoreCase(WebXmlParameter.DEACTIVATE_EMPTY_STRING_INTERPRETATION)
&& "".equals(convertedObject))
{
convertedObject = null;
}
MetaDataExtractor metaDataExtractor = ExtValUtils.getComponentMetaDataExtractor();
PropertyInformation propertyInformation = metaDataExtractor.extract(facesContext, uiComponent);
//e.g.: extract groups for validation
if(!ExtValUtils.executeBeforeValidationInterceptors(facesContext, uiComponent, convertedObject,
PropertyInformation.class.getName() ,propertyInformation))
{
return;
}
try
{
if (logger.isTraceEnabled())
{
logger.trace("jsr303 start validation");
}
processFieldValidation(facesContext, uiComponent, convertedObject, propertyInformation);
if (logger.isTraceEnabled())
{
logger.trace("jsr303 validation finished");
}
}
finally
{
ExtValUtils.executeAfterValidationInterceptors(facesContext, uiComponent, convertedObject,
PropertyInformation.class.getName(), propertyInformation);
}
}
protected void processFieldValidation(FacesContext facesContext,
UIComponent uiComponent,
Object convertedObject,
PropertyInformation propertyInformation)
{
Class baseBeanClass = (propertyInformation.getInformation(
PropertyInformationKeys.PROPERTY_DETAILS, PropertyDetails.class)).getBaseObject().getClass();
String propertyName = (propertyInformation.getInformation(
PropertyInformationKeys.PROPERTY_DETAILS, PropertyDetails.class)).getProperty();
ExtValBeanValidationContext beanValidationContext = ExtValBeanValidationContext.getCurrentInstance();
Class[] groups = beanValidationContext.getGroups(
facesContext.getViewRoot().getViewId(), uiComponent.getClientId(facesContext));
if(groups == null)
{
return;
}
Set<ConstraintViolation> violations = this.validationFactory.usingContext()
.messageInterpolator(ExtValBeanValidationContext.getCurrentInstance().getMessageInterpolator())
.getValidator()
.validateValue(baseBeanClass, propertyName, convertedObject, groups);
if (violations != null && violations.size() > 0)
{
//TODO jsf 2.0 supports multiple messages -> use all messages
ConstraintViolation violation = (ConstraintViolation) violations.toArray()[0];
String violationMessage = violation.getMessage();
String labeledMessage = "{0}: " + violationMessage;
ValidatorException validatorException = new ValidatorException(
new FacesMessage(FacesMessage.SEVERITY_ERROR, labeledMessage, labeledMessage));
ExtValUtils.executeAfterThrowingInterceptors(
uiComponent,
null,
convertedObject,
validatorException,
null);
//TODO check & remove
propertyInformation.setInformation(
BeanValidationPropertyInformationKeys.CONSTRAINT_VIOLATIONS, violations);
if (labeledMessage.equals(validatorException.getFacesMessage().getSummary()) ||
labeledMessage.equals(validatorException.getFacesMessage().getDetail()))
{
throw new ValidatorException(
new FacesMessage(FacesMessage.SEVERITY_ERROR, violationMessage, violationMessage));
}
else
{
throw validatorException;
}
}
}
}