blob: b01492e48a017eaacd125af6ece6779f5cab56a5 [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.validation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.extensions.validator.beanval.ExtValBeanValidationContext;
import org.apache.myfaces.extensions.validator.beanval.validation.message.FacesMessageHolder;
import org.apache.myfaces.extensions.validator.beanval.annotation.ModelValidation;
import org.apache.myfaces.extensions.validator.beanval.storage.ModelValidationEntry;
import org.apache.myfaces.extensions.validator.beanval.util.BeanValidationUtils;
import org.apache.myfaces.extensions.validator.internal.ToDo;
import org.apache.myfaces.extensions.validator.internal.Priority;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.validation.ConstraintViolation;
import javax.validation.MessageInterpolator;
import javax.validation.Path;
import javax.validation.metadata.ConstraintDescriptor;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* @author Gerhard Petracek
* @since x.x.3
*/
public class ModelValidationPhaseListener implements PhaseListener
{
private static final long serialVersionUID = -3482233893186708878L;
protected final Log logger = LogFactory.getLog(getClass());
public void afterPhase(PhaseEvent phaseEvent)
{
if (logger.isTraceEnabled())
{
logger.trace("jsr303 start model validation");
}
List<Object> processedValidationTargets = new ArrayList<Object>();
Map<String, ModelValidationResult> results = new HashMap<String, ModelValidationResult>();
for (ModelValidationEntry modelValidationEntry : getModelValidationEntriesToValidate())
{
processModelValidation(modelValidationEntry, processedValidationTargets, results);
}
processViolations(FacesContext.getCurrentInstance(), results);
if (logger.isTraceEnabled())
{
logger.trace("jsr303 validation finished");
}
}
private List<ModelValidationEntry> getModelValidationEntriesToValidate()
{
return ExtValBeanValidationContext.getCurrentInstance().getModelValidationEntriesToValidate();
}
private void processModelValidation(ModelValidationEntry modelValidationEntry,
List<Object> processedValidationTargets,
Map<String, ModelValidationResult> results)
{
for (Object validationTarget : modelValidationEntry.getValidationTargets())
{
if (processedValidationTargets.contains(validationTarget) &&
!modelValidationEntry.getMetaData().displayInline())
{
continue;
}
if (!processedValidationTargets.contains(validationTarget))
{
processedValidationTargets.add(validationTarget);
}
validateTarget(modelValidationEntry, validationTarget, modelValidationEntry.getGroups(), results);
}
}
@ToDo(value = Priority.HIGH, description = "refactor and remove FacesContext#renderResponse")
private void validateTarget(ModelValidationEntry modelValidationEntry,
Object validationTarget,
Class[] groups,
Map<String, ModelValidationResult> results)
{
Set<ConstraintViolation<Object>> violations = ExtValBeanValidationContext.getCurrentInstance()
.getValidatorFactory().usingContext()
.messageInterpolator(ExtValBeanValidationContext.getCurrentInstance().getMessageInterpolator())
.getValidator()
.validate(validationTarget, groups);
if (violations != null && !violations.isEmpty())
{
FacesContext facesContext = FacesContext.getCurrentInstance();
facesContext.renderResponse();
//jsf 2.0 is able to display multiple messages per component - so process all violations
//jsf < 2.0 will just use the first one (it's only a little overhead)
Iterator violationsIterator = violations.iterator();
ConstraintViolation constraintViolation;
ModelValidationResult result;
while (violationsIterator.hasNext())
{
if (!results.containsKey(modelValidationEntry.getComponent().getClientId(facesContext)))
{
result = new ModelValidationResult();
results.put(modelValidationEntry.getComponent().getClientId(facesContext), result);
}
result = results.get(modelValidationEntry.getComponent().getClientId(facesContext));
constraintViolation = (ConstraintViolation) violationsIterator.next();
if (modelValidationEntry.getMetaData().displayInline())
{
result.addFacesMessageHolder(createFacesMessageHolderForConstraintViolation(
constraintViolation, modelValidationEntry, validationTarget, true));
}
else
{
result.addFacesMessageHolder(createFacesMessageHolderForConstraintViolation(
constraintViolation, modelValidationEntry, validationTarget, false));
}
}
}
}
private FacesMessageHolder createFacesMessageHolderForConstraintViolation(final ConstraintViolation violation,
ModelValidationEntry modelValidationEntry,
final Object validationTarget,
boolean displayAtComponent)
{
final String newViolationMessage = tryToChangeViolationMessage(
modelValidationEntry, validationTarget, violation);
ConstraintViolation newConstraintViolation = new ConstraintViolation()
{
private ConstraintViolation wrapped = violation;
public String getMessage()
{
return newViolationMessage;
}
public String getMessageTemplate()
{
return wrapped.getMessageTemplate();
}
public Object getRootBean()
{
return wrapped.getRootBean();
}
public Class getRootBeanClass()
{
return wrapped.getRootBeanClass();
}
public Object getLeafBean()
{
return wrapped.getLeafBean();
}
public Path getPropertyPath()
{
return wrapped.getPropertyPath();
}
public Object getInvalidValue()
{
return wrapped.getInvalidValue();
}
public ConstraintDescriptor getConstraintDescriptor()
{
return wrapped.getConstraintDescriptor();
}
};
UIComponent uiComponent = null;
String clientId = null;
if (displayAtComponent)
{
uiComponent = modelValidationEntry.getComponent();
clientId = uiComponent.getClientId(FacesContext.getCurrentInstance());
}
FacesMessageHolder result = new FacesMessageHolder(BeanValidationUtils
.createFacesMessageForConstraintViolation(uiComponent, validationTarget, newConstraintViolation));
result.setClientId(clientId);
return result;
}
private String tryToChangeViolationMessage(ModelValidationEntry modelValidationEntry,
Object validationTarget,
ConstraintViolation violation)
{
if (!isDefaultMessage(modelValidationEntry))
{
return interpolateValidationErrorMessage(
modelValidationEntry.getMetaData().message(), validationTarget, violation);
}
return violation.getMessage();
}
private boolean isDefaultMessage(ModelValidationEntry modelValidationEntry)
{
return ModelValidation.DEFAULT_MESSAGE.equals(modelValidationEntry.getMetaData().message());
}
private String interpolateValidationErrorMessage(String extValInlineMessage,
final Object validationTarget, final ConstraintViolation violation)
{
return ExtValBeanValidationContext.getCurrentInstance().getMessageInterpolator()
.interpolate(
extValInlineMessage,
new MessageInterpolator.Context()
{
public ConstraintDescriptor<?> getConstraintDescriptor()
{
return violation.getConstraintDescriptor();
}
public Object getValidatedValue()
{
return validationTarget;
}
}
);
}
private void processViolations(FacesContext facesContext, Map<String, ModelValidationResult> results)
{
for (ModelValidationResult result : results.values())
{
BeanValidationUtils.processViolationMessages(facesContext, result.getFacesMessageHolderList(), false);
}
}
public void beforePhase(PhaseEvent phaseEvent)
{
//do nothing
}
public PhaseId getPhaseId()
{
return PhaseId.UPDATE_MODEL_VALUES;
}
}