Package org.omnifaces.cdi.param

Source Code of org.omnifaces.cdi.param.RequestParameterProducer

/*
* Copyright 2013 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
*
*     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.omnifaces.cdi.param;

import static java.beans.Introspector.getBeanInfo;
import static java.beans.PropertyEditorManager.findEditor;
import static java.lang.Boolean.valueOf;
import static javax.faces.validator.BeanValidator.DISABLE_DEFAULT_BEAN_VALIDATOR_PARAM_NAME;
import static org.omnifaces.util.Faces.evaluateExpressionGet;
import static org.omnifaces.util.Faces.getApplication;
import static org.omnifaces.util.Faces.getInitParameter;
import static org.omnifaces.util.FacesLocal.getRequestParameter;
import static org.omnifaces.util.Messages.createError;
import static org.omnifaces.util.Platform.getBeanValidator;
import static org.omnifaces.util.Platform.isBeanValidationAvailable;
import static org.omnifaces.util.Reflection.instance;
import static org.omnifaces.util.Utils.containsByClassName;
import static org.omnifaces.util.Utils.isEmpty;

import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.validator.BeanValidator;
import javax.faces.validator.RequiredValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
import javax.validation.ConstraintViolation;

import org.omnifaces.cdi.Param;

/**
* Producer for a request parameter as defined by the {@link Param} annotation.
*
* @since 1.6
* @author Arjan Tijms
*/
public class RequestParameterProducer {

  @SuppressWarnings("unchecked")
  @Produces
  @Param
  public <V> ParamValue<V> produce(InjectionPoint injectionPoint) {

    // @Param is the annotation on the injection point that holds all data for this request parameter
    Param requestParameter = getQualifier(injectionPoint, Param.class);

    FacesContext context = FacesContext.getCurrentInstance();
    UIComponent component = context.getViewRoot();

    String label = getLabel(requestParameter, injectionPoint);

    // TODO: Save/restore existing potentially existing label?
    component.getAttributes().put("label", label);

    // Get raw submitted value from the request
    String submittedValue = getRequestParameter(context, getName(requestParameter, injectionPoint));
    Object convertedValue = null;
    boolean valid = true;

    try {

      // Convert the submitted value

      Converter converter = getConverter(requestParameter, getTargetType(injectionPoint));
      if (converter != null) {
        convertedValue = converter.getAsObject(context, component, submittedValue);
      } else {
        convertedValue = submittedValue;
      }

      // Check for required

      if (requestParameter.required() && isEmpty(convertedValue)) {
        addRequiredMessage(context, component, label, submittedValue, getRequiredMessage(requestParameter));
      }

      // Validate the converted value

      // 1. Use Bean Validation validators
      if (shouldDoBeanValidation(requestParameter)) {

        Set<ConstraintViolation<?>> violations = doBeanValidation(injectionPoint.getBean().getBeanClass(), injectionPoint.getMember().getName(), convertedValue);

        valid = violations.isEmpty();

        for (ConstraintViolation<?> violation : violations) {
          context.addMessage(component.getClientId(context), createError(violation.getMessage(), label));
        }
      }

      // 2. Use JSF native validators
      for (Validator validator : getValidators(requestParameter)) {
        try {
          validator.validate(context, component, convertedValue);
        } catch (ValidatorException ve) {
          valid = false;
          addValidatorMessages(context, component, label, submittedValue, ve, getValidatorMessage(requestParameter));
        }
      }
    } catch (ConverterException ce) {
      valid = false;
      addConverterMessage(context, component, label, submittedValue, ce, getConverterMessage(requestParameter));
    }

    if (!valid) {
      context.validationFailed();
      convertedValue = null;
    }

    return (ParamValue<V>) new ParamValue<>(submittedValue, requestParameter, getTargetType(injectionPoint), convertedValue);
  }

  public static Converter getConverter(Param requestParameter, Class<?> targetType) {

    Class<? extends Converter> converterClass = requestParameter.converterClass();
    String converterName = requestParameter.converter();

    Converter converter = null;

    if (!isEmpty(converterName)) {
      Object expressionResult = evaluateExpressionGet(converterName);
      if (expressionResult instanceof Converter) {
        converter = (Converter) expressionResult;
      } else if (expressionResult instanceof String) {
        converter = getApplication().createConverter((String) expressionResult);
      }
    } else if (!converterClass.equals(Converter.class)) { // Converter.class is default, representing null
      converter = instance(converterClass);
    }

    if (converter == null) {
      try {
        converter = getApplication().createConverter(targetType);
      } catch (Exception e) {
        return null;
      }
    }

    if (converter != null) {
      setAttributes(converter, getConverterAttributes(requestParameter));
    }

    return converter;
  }

  @SuppressWarnings("unchecked")
  private <V> Class<V> getTargetType(InjectionPoint injectionPoint) {
    Type type = injectionPoint.getType();
    if (type instanceof ParameterizedType) {
      // Assumes ParamValue now. Needs to be adjusted later.
      return (Class<V>) ((ParameterizedType) type).getActualTypeArguments()[0];
    } else if (type instanceof Class) {
      // Direct injection into class type using dynamic producer
      return  (Class<V>type;
    }

    return null;
  }

  private String getName(Param requestParameter, InjectionPoint injectionPoint) {

    String name = requestParameter.name();

    if (isEmpty(name)) {
      name = injectionPoint.getMember().getName();
    } else {
      name = evaluateExpressionAsString(name);
    }

    return name;
  }

  private String getLabel(Param requestParameter, InjectionPoint injectionPoint) {

    String label = requestParameter.label();

    if (isEmpty(label)) {
      label = getName(requestParameter, injectionPoint);
    } else {
      label = evaluateExpressionAsString(label);
    }

    return label;
  }

  private String getValidatorMessage(Param requestParameter) {
    return evaluateExpressionAsString(requestParameter.validatorMessage());
  }

  private String getConverterMessage(Param requestParameter) {
    return evaluateExpressionAsString(requestParameter.converterMessage());
  }

  private String getRequiredMessage(Param requestParameter) {
    return evaluateExpressionAsString(requestParameter.requiredMessage());
  }

  private String evaluateExpressionAsString(String expression) {

    if (isEmpty(expression)) {
      return expression;
    }

    Object expressionResult = evaluateExpressionGet(expression);

    if (expressionResult == null) {
      return null;
    }

    return expressionResult.toString();
  }

  private boolean shouldDoBeanValidation(Param requestParameter) {

    // If bean validation is explicitly disabled for this instance, immediately return false
    if (requestParameter.disableBeanValidation()) {
      return false;
    }

    // Next check if bean validation has been disabled globally, but only if this hasn't been overridden locally
    if (!requestParameter.overrideGlobalBeanValidationDisabled() && valueOf(getInitParameter(DISABLE_DEFAULT_BEAN_VALIDATOR_PARAM_NAME))) {
      return false;
    }

    // For all other cases, the availability of bean validation determines if we attempt bean validation or not.
    return isBeanValidationAvailable();
  }

  private Set<ConstraintViolation<?>> doBeanValidation(Class<?> base, String property, Object value) {

    ParamValue<?> paramValue = null;
    if (value != null) {
      paramValue = new ParamValue<>(null, null, null, value);
    }

    @SuppressWarnings("rawtypes")
    Set violationsRaw = getBeanValidator().validateValue(base, property, paramValue);

    @SuppressWarnings("unchecked")
    Set<ConstraintViolation<?>> violations = violationsRaw;

    return violations;
  }

  private List<Validator> getValidators(Param requestParameter) {

    List<Validator> validators = new ArrayList<>();

    Class<? extends Validator>[] validatorClasses = requestParameter.validatorClasses();
    String[] validatorNames = requestParameter.validators();

    for (String validatorName : validatorNames) {
      Object validator = evaluateExpressionGet(validatorName);
      if (validator instanceof Validator) {
        validators.add((Validator) validator);
      } else if (validator instanceof String) {
        validators.add(getApplication().createValidator(validatorName));
      }
    }

    for (Class<? extends Validator> validatorClass : validatorClasses) {
      validators.add(instance(validatorClass));
    }

    // Process the default validators

    Application application = getApplication();
    for (Entry<String, String> validatorEntry :  application.getDefaultValidatorInfo().entrySet()) {

      String validatorID = validatorEntry.getKey();
      String validatorClassName = validatorEntry.getValue();

      // Check that the validator ID is not the BeanValidator one which we handle in a special way.
      // And make sure the default validator is not already set manually as well.
      if (!validatorID.equals(BeanValidator.VALIDATOR_ID) && !containsByClassName(validators, validatorClassName)) {
        validators.add(application.createValidator(validatorID));
      }
    }

    // Set the attributes on all instantiated validators. We don't distinguish here
    // which attribute should go to which validator.
    Map<String, Object> validatorAttributes = getValidatorAttributes(requestParameter);
    for (Validator validator : validators) {
      setAttributes(validator, validatorAttributes);
    }

    return validators;
  }

  private static Map<String, Object> getConverterAttributes(Param requestParameter) {

    Map<String, Object> attributeMap = new HashMap<>();

    Attribute[] attributes = requestParameter.converterAttributes();
    for (Attribute attribute : attributes) {
      attributeMap.put(attribute.name(), evaluateExpressionGet(attribute.value()));
    }

    return attributeMap;
  }

  private Map<String, Object> getValidatorAttributes(Param requestParameter) {

    Map<String, Object> attributeMap = new HashMap<>();

    Attribute[] attributes = requestParameter.validatorAttributes();
    for (Attribute attribute : attributes) {
      attributeMap.put(attribute.name(), evaluateExpressionGet(attribute.value()));
    }

    return attributeMap;
  }

  private static void setAttributes(Object object, Map<String, Object> attributes) {
    try {
      for (PropertyDescriptor property : getBeanInfo(object.getClass()).getPropertyDescriptors()) {
        Method setter = property.getWriteMethod();

        if (setter == null) {
          continue;
        }

        if (attributes.containsKey(property.getName())) {

          Object value = attributes.get(property.getName());
          if (value instanceof String && !property.getPropertyType().equals(String.class)) {

            // Try to convert Strings to the type expected by the converter

            PropertyEditor editor = findEditor(property.getPropertyType());
            editor.setAsText((String) value);
            value = editor.getValue();
          }

          property.getWriteMethod().invoke(object, value);
        }

      }
    } catch (Exception e) {
      throw new IllegalStateException(e);
    }
  }

  private void addConverterMessage(FacesContext context, UIComponent component, String label, String submittedValue, ConverterException ce, String converterMessage) {
    FacesMessage message = null;

    if (!isEmpty(converterMessage)) {
      message = createError(converterMessage, submittedValue, label);
    } else {
      message = ce.getFacesMessage();
      if (message == null) {
        // If the converter didn't add a FacesMessage, set a generic one.
        message = createError("Conversion failed for {0} because: {1}", submittedValue, ce.getMessage());
      }
    }

    context.addMessage(component.getClientId(context), message);
  }

  private void addRequiredMessage(FacesContext context, UIComponent component, String label, String submittedValue, String requiredMessage) {

    FacesMessage message = null;

    if (!isEmpty(requiredMessage)) {
      message = createError(requiredMessage, submittedValue, label);
    } else {
      // Use RequiredValidator to get the same message that all required attributes are using.
      // TODO: this is a little convoluted :X
      try {
        new RequiredValidator().validate(context, component, submittedValue);
      } catch (ValidatorException ve) {
        message = ve.getFacesMessage();
      }

      if (message == null) {
        // RequiredValidator didn't throw or its exception did not have a message set.
        // Use a generic fallback message
        // TODO: Use OmniFaces resource bundle to override this globally
        message = createError("{0}: A value is required!", label);
      }
    }

    context.addMessage(component.getClientId(context), message);
  }

  private void addValidatorMessages(FacesContext context, UIComponent component, String label, String submittedValue, ValidatorException ve, String validatorMessage) {

    String clientId = component.getClientId(context);

    if (!isEmpty(validatorMessage)) {
      context.addMessage(clientId, createError(validatorMessage, submittedValue, label));
    } else {
      for (FacesMessage facesMessage : getFacesMessages(ve)) {
        context.addMessage(clientId, facesMessage);
      }
    }
  }
 
  private static List<FacesMessage> getFacesMessages(ValidatorException ve) {
    List<FacesMessage> facesMessages = new ArrayList<>();
    if (ve.getFacesMessages() != null) {
      facesMessages.addAll(ve.getFacesMessages());
    } else if (ve.getFacesMessage() != null) {
      facesMessages.add(ve.getFacesMessage());
    }

    return facesMessages;
  }

  @SuppressWarnings("unchecked")
  private static <T> T getQualifier(InjectionPoint injectionPoint, Class<T> annotationClass) {
    for (Annotation annotation : injectionPoint.getQualifiers()) {
      if (annotationClass.isAssignableFrom(annotation.getClass())) {
        return (T) annotation;
      }
    }

    return null;
  }

}
TOP

Related Classes of org.omnifaces.cdi.param.RequestParameterProducer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.