/**
* Copyright 2005-2016 hdiv.org
*
* 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
*
* 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.hdiv.validation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialViewContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hdiv.config.HDIVConfig;
import org.hdiv.util.HDIVErrorCodes;
import org.hdiv.util.Method;
import org.hdiv.util.UtilsJsf;
import org.hdiv.validators.ComponentValidator;
import org.hdiv.validators.EditableValidator;
import org.hdiv.validators.GenericComponentValidator;
import org.hdiv.validators.HtmlInputHiddenValidator;
import org.hdiv.validators.UICommandValidator;
import org.hdiv.validators.UISelectValidator;
public class DefaultComponentTreeValidator implements ComponentTreeValidator {
private final static String FACELETS_COMPONENT_FAMILY = "facelets";
private static final Log log = LogFactory.getLog(DefaultComponentTreeValidator.class);
protected final List<ComponentValidator> componentValidators = new ArrayList<ComponentValidator>();
protected HDIVConfig config;
public void createComponentValidators() {
componentValidators.add(new GenericComponentValidator());
componentValidators.add(new HtmlInputHiddenValidator());
componentValidators.add(new UICommandValidator());
EditableValidator editableValidator = new EditableValidator(config.getEditableDataValidationProvider());
componentValidators.add(editableValidator);
componentValidators.add(new UISelectValidator());
}
public List<FacesValidatorError> validateComponentTree(final FacesContext facesContext) {
if (isExcludedUrl(facesContext)) {
return Collections.emptyList();
}
ValidationContext context = createValidationContext(facesContext);
PartialViewContext partialContext = facesContext.getPartialViewContext();
if (partialContext != null && partialContext.isPartialRequest()) {
// Is an ajax call partially processing the component tree
validateAjaxRequest(context);
}
else {
// This is not an Ajax request
validateNonAjaxRequest(context);
}
List<FacesValidatorError> errors = context.getErrors();
checkParameters(context, errors);
return errors;
}
protected void validateAjaxRequest(final ValidationContext context) {
UIComponent componentToValidate = null;
UIComponent source = findSourceComponent(context);
UIForm submittedForm = findParentSubmittedForm(context, source);
if (submittedForm != null) {
componentToValidate = submittedForm;
}
else {
// The executed component is not inside a form!
// Use the source component as the root component to validate
componentToValidate = source;
}
if (componentToValidate != null) {
if (log.isDebugEnabled()) {
log.debug("Validating Ajax request.");
log.debug("Components to validate:");
}
// Validate component tree
validateComponentTree(context, componentToValidate);
}
else {
if (log.isErrorEnabled()) {
log.error("Can't find root component to start the validation.");
}
}
}
protected void validateNonAjaxRequest(final ValidationContext context) {
// Find submitted form
UIForm submittedForm = findSubmittedForm(context, context.getFacesContext().getViewRoot());
if (submittedForm != null) {
if (log.isDebugEnabled()) {
log.debug("Validating Non Ajax request.");
log.debug("Components to validate:");
}
// Validate component tree starting in form
validateComponentTree(context, submittedForm);
}
else {
if (log.isErrorEnabled()) {
log.error("Can't find submitted form.");
}
}
}
protected ValidationContext createValidationContext(final FacesContext facesContext) {
return new ValidationContext(facesContext);
}
protected UIComponent findSourceComponent(final ValidationContext validationContext) {
FacesContext context = validationContext.getFacesContext();
String source = context.getExternalContext().getRequestParameterMap().get("javax.faces.source");
UIComponent sourceComp = null;
if (source != null) {
sourceComp = context.getViewRoot().findComponent(source);
}
if (sourceComp == null) {
sourceComp = context.getViewRoot().findComponent(UtilsJsf.removeRowId(source));
}
return sourceComp;
}
protected UIComponent findComponent(final ValidationContext validationContext, final String compId) {
FacesContext context = validationContext.getFacesContext();
UIComponent comp = context.getViewRoot().findComponent(compId);
if (comp == null) {
comp = context.getViewRoot().findComponent(UtilsJsf.removeRowId(compId));
}
return comp;
}
protected void validateComponentTree(final ValidationContext context, final UIComponent component) {
if (log.isDebugEnabled()) {
String clientId = component.getClientId(context.getFacesContext());
log.debug(" - Component: " + clientId + " of Type: " + component.getClass().getCanonicalName());
}
boolean excluded = isExcludedComponent(component);
if (!excluded) {
validateComponent(context, component);
}
Iterator<UIComponent> it = component.getFacetsAndChildren();
while (it.hasNext()) {
UIComponent child = it.next();
validateComponentTree(context, child);
}
}
protected void validateComponent(final ValidationContext context, final UIComponent component) {
for (ComponentValidator validator : componentValidators) {
if (validator.supports(component)) {
validator.validate(context, component);
}
}
}
protected List<FacesValidatorError> checkParameters(final ValidationContext context, final List<FacesValidatorError> errors) {
if (!errors.isEmpty()) {
Iterator<FacesValidatorError> it = errors.iterator();
while (it.hasNext()) {
FacesValidatorError error = it.next();
boolean excluded = isExcludedParameter(context, error.getParameterName());
if (excluded) {
it.remove();
}
}
}
Map<String, String[]> params = context.getFacesContext().getExternalContext().getRequestParameterValuesMap();
Map<String, Set<Object>> validParameters = context.getValidParameters();
for (String param : params.keySet()) {
String[] values = params.get(param);
boolean paramIsPressent = validParameters.keySet().contains(param);
if (!paramIsPressent) {
FacesValidatorError error = processUnknownParameter(context, param, values);
if (error != null) {
errors.add(error);
}
}
else {
if (values != null) {
for (String value : values) {
boolean paramValuePressent = false;
if (validParameters.get(param).contains(value)) {
paramValuePressent = true;
}
if (!paramValuePressent) {
if (!isExcludedParameter(context, param)) {
if (log.isDebugEnabled()) {
log.debug("Invalid parameter value for parameter: " + param + ". Valid values are: "
+ validParameters.get(param));
}
FacesValidatorError error = new FacesValidatorError(HDIVErrorCodes.INVALID_PARAMETER_VALUE, null, param,
value);
errors.add(error);
}
}
}
}
}
}
return errors;
}
protected FacesValidatorError processUnknownParameter(final ValidationContext context, final String paramName,
final String[] paramValues) {
if (isExcludedParameter(context, paramName)) {
return null;
}
if (log.isDebugEnabled()) {
log.debug("Invalid parameter name: " + paramName);
}
FacesValidatorError error = new FacesValidatorError(HDIVErrorCodes.INVALID_PARAMETER_NAME, null, paramName, null);
return error;
}
protected boolean isExcludedComponent(final UIComponent component) {
if (FACELETS_COMPONENT_FAMILY.equals(component.getFamily())) {
return true;
}
return false;
}
protected boolean isExcludedUrl(final FacesContext context) {
String target = UtilsJsf.getTargetUrl(context);
return config.isStartPage(target, Method.POST);
}
protected boolean isExcludedParameter(final ValidationContext context, final String paramName) {
if (UtilsJsf.isFacesViewParamName(paramName)) {
return true;
}
if (config.isStartParameter(paramName)) {
if (log.isDebugEnabled()) {
log.debug("Parameter '" + paramName + "' is a start parameter and is excluded from validation.");
}
return true;
}
String target = UtilsJsf.getTargetUrl(context.getFacesContext());
if (config.isParameterWithoutValidation(target, paramName)) {
if (log.isDebugEnabled()) {
log.debug("Parameter '" + paramName + "' for url '" + target
+ "' is a parameter without validation and is excluded from validation.");
}
return true;
}
return false;
}
/**
* Searches the form inside the component. Input component must be UICommand type and must be inside a form.
*
* @param context Validation context
* @param comp Base component
* @return UIForm component
*/
protected UIForm findSubmittedForm(final ValidationContext context, final UIComponent comp) {
if (comp instanceof UIForm) {
UIForm form = (UIForm) comp;
String clientId = form.getClientId();
String paramValue = context.getRequestParameters().get(clientId);
if (paramValue != null && paramValue.equals(clientId)) {
return form;
}
}
for (UIComponent child : comp.getChildren()) {
UIForm form = findSubmittedForm(context, child);
if (form != null) {
return form;
}
}
return null;
}
/**
* Searches the form inside the component. Input component must be UICommand type and must be inside a form.
*
* @param context Validation context
* @param comp Base component
* @return UIForm component
*/
protected UIForm findParentSubmittedForm(final ValidationContext context, final UIComponent comp) {
if (comp == null || comp instanceof UIViewRoot) {
return null;
}
UIComponent parent = comp.getParent();
while (parent != null && !(parent instanceof UIViewRoot)) {
if (parent instanceof UIForm) {
UIForm form = (UIForm) parent;
String clientId = form.getClientId();
String paramValue = context.getRequestParameters().get(clientId);
if (paramValue != null && paramValue.equals(clientId)) {
return form;
}
}
parent = parent.getParent();
}
return null;
}
public void setConfig(final HDIVConfig config) {
this.config = config;
}
}