Package eu.maydu.gwt.validation.client

Source Code of eu.maydu.gwt.validation.client.DefaultValidationProcessor

/*
  Copyright 2009 Anatol Gregory Mayen
 
  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 eu.maydu.gwt.validation.client;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.client.GWT;

import eu.maydu.gwt.validation.client.ValidationResult.ValidationError;
import eu.maydu.gwt.validation.client.exceptions.IncorrectSerializationFormatException;
import eu.maydu.gwt.validation.client.i18n.StandardValidationMessages;
import eu.maydu.gwt.validation.client.i18n.ValidationMessages;

/**
* The <code>DefaultValidationProcessor</code> is the standard implementation
* of the {@link ValidationProcessor} interface.
*
*
* @author Anatol Mayen
*
*/
public class DefaultValidationProcessor implements ValidationProcessor {


 
  //private List<Validator> validators = new LinkedList<Validator>();
  private Map<String,List<Validator>> validators = new HashMap<String,List<Validator>>();
  private List<String> validatorNames = new LinkedList<String>();
  private List<ValidationAction> globalActions = new LinkedList<ValidationAction>();
  private boolean firstRun = true;
  private ValidationMessages messages;
  private StandardValidationMessages stdMessages;
  private boolean propertyLocalizationPrefix = false;
 
  private boolean stopValidationOnFirstError;
 
  /**
   * Creates a new <code>ValidationProcessor</code> which
   * doesn't stop on the first error and that
   * uses the internal messages object for localization.
   * 
   */
  public DefaultValidationProcessor() {
    this(false, new ValidationMessages());
  }
 
 
  /**
   * Creates a new instance with the default i18n messages
   * and the given value for <code>stopValidationOnFirstError</code>
   *
   * @param stopValidationOnFirstError If <code>true</code> the validation process is aborted on the first error
   */
  public DefaultValidationProcessor(boolean stopValidationOnFirstError) {
    this(false, new ValidationMessages());
  }

  /**
   * Creates a new instance with customized i18n messages and
   * the <code>stopValidationOnFirstError = false</code>
   *
   * @param messages The (possibly) customized ValidationMessages
   */
  public DefaultValidationProcessor(ValidationMessages messages) {
    this(false, messages);
  }
 
  /**
   * Creates a new instance with customized i18n messages and
   * the given value for <code>stopValidationOnFirstError</code>.
   *
   * @param stopValidationOnFirstError If <code>true</code> the validation process is aborted on the first error
   * @param messages The (possibly) customized ValidationMessages
   */
  public DefaultValidationProcessor(boolean stopValidationOnFirstError, ValidationMessages messages) {
    this.stopValidationOnFirstError = stopValidationOnFirstError;
    this.messages = messages;
  }
 
 
  /**
   * Add new validators that the <code>ValidationProcessor</code> instance will
   * use during the validation phase.
   *
   * The <code>name</code> parameter is used to connect the client side with the server side validations.
   * If you want to validate a field on the server side you need to specify the same <code>name</code>
   * for the input field here on this method and on the validation on the server side. E.g.
   * on the server side you might have something like this:
   * <pre>
   *     new ServerValidation().inRange(car.getPs(), 0, 350, "ps");
   * </pre>
   * If you want the error to be shown on the correct input field on the client side
   * the <code>name</code> parameter must be the same as defined on the server side
   * ("ps" in this case).
   *
   *
   *
   * @param name The name that is used to connect client and server side validations
   * @param validator The validators that will be used for validations
   */
  public DefaultValidationProcessor addValidators(String name, Validator... validator) {
    if(validator == null || validator.length < 1)
      return this;
    if(name == null || name.trim().length()==0)
      throw new IllegalArgumentException("Non-empty name must be given when adding validators!");
    List<Validator> vals = new LinkedList<Validator>();
    for(Validator val : validator) {
      if(!vals.contains(val))
        vals.add(val);
    }
    if(validators.get(name) != null)
      GWT.log("ValidatonFramework: Overwriting existing validators for property with name: "+name, null);
    validators.put(name, vals);
    //We store the sequence in which the validations are added.
    //Later we want to invoke the actions in the reverse direction so that
    //a e.g. focus action for the first validated field added is invoked at last
    //so that the first element receives focus when additionally elements also had errors.
    validatorNames.add(name);
    return this;
  }
 
  /**
   * Adds an global action to this <code>ValidatorProcessor</code> instance.
   * This is primarily useful for actions that require all the validation errors
   * of a validation run. The actions added as global actions will only be
   * invoked <i>after</i> all validations and their associated actions are invoked
   * and only if there were validation errors during the validation run.
   *
   * @param action The global action
   * @return A reference to this <code>ValidationProcessor</code>'s instance for chaining.
   */
  public DefaultValidationProcessor addGlobalAction(ValidationAction action) {
   
    if(!globalActions.contains(action))
      globalActions.add(action);
   
    return this;
  }
 
 
 
  /**
   * Invoke the client side only validation.
   *
   * @param names
   * @return <code>true</code> if all client side validations were successful, <code>false</code> otherwise
   */
  public boolean validate(String... names) {
    boolean filter = false;
    if(names != null && names.length > 0)
      filter = true;
    if(!firstRun)
      reset(names);
    firstRun = false;
    boolean errorsOccured = false;
    List<ValidationResult> errors = new LinkedList<ValidationResult>();
    List<ValidatorAndResult> failedValidators = new LinkedList<ValidatorAndResult>();
    ValidationResult allResults = new ValidationResult();
    boolean stopValidating = false;
    //Set<String> propertyNames = validators.keySet();
    for(String propertyName : validatorNames) { //Validate in the right order to adhere to the stopValidationOnFirstError and preventsPropagationOfValidationChain properties.
      if(filter && !arrayIncludes(names, propertyName))
        continue; //We validate only the given properties: The actual property is not mentioned, so return
      if(stopValidating)
        break;
      List<Validator> vals = validators.get(propertyName);
      if(vals == null)
        continue;
      for(Validator val : vals) {
        ValidationResult result = val.validate(this.messages);
        if(result == null) {
          //validation passed
          continue;
        }
        addErrors(allResults, result, propertyName);
        errorsOccured = true;
       
        //Store the actions and the corresponding results in the reverse
        //order to allow for e.g. focus action to be set on the first element
        //and not on the last element that could not be successfully validated.
        ValidatorAndResult valAndResult = new ValidatorAndResult(val, result);
        failedValidators.add(0, valAndResult);
       
        //val.invokeActions(result);
        if(this.stopValidationOnFirstError || val.preventsPropagationOfValidationChain()) {
          invokeFailedValidators(failedValidators);
          invokeGlobalActions(allResults);
          //Stop on first validation error
          stopValidating = true;
        }
      }
    }
   
    if(errorsOccured) {
      invokeFailedValidators(failedValidators);
      invokeGlobalActions(allResults);
    }
   
    return !errorsOccured;
  }
 
  private boolean arrayIncludes(String[] strings, String string) {
    for(int i=0; i<strings.length; i++) {
      if(strings[i].equals(string))
        return true;
    }
    return false;
  }

  private class ValidatorAndResult {
    public Validator validator;
    public ValidationResult result;
   
    public ValidatorAndResult(Validator val, ValidationResult res) {
      validator = val;
      result = res;
    }
  }
 
  private void invokeFailedValidators(List<ValidatorAndResult> failedValidators) {
    for(ValidatorAndResult var : failedValidators) {
      var.validator.invokeActions(var.result);
    }
  }


  private void addErrors(ValidationResult allResults, ValidationResult singleResult, String propertyName) {
    for(ValidationError error : singleResult.getErrors()) {
      //localize the property names
      error.propertyName = localizePropertyName(propertyName);
    }
     
   
    allResults.addErrors(singleResult);
  }
 
  private void invokeGlobalActions(ValidationResult allResults) {
    if(globalActions.size() < 1 || allResults.getErrors().isEmpty())
      return;
   
    for(ValidationAction action : globalActions)
      action.invoke(allResults, null);
   
  }


  /**
   * Processes the validation errors that came from the server side
   * and displays them on the client side.
   *
   * @see eu.maydu.gwt.validation.server.ServerValidation
   * 
   * @param validationException
   * @return <i>true</i> if no errors were included in the given <code>ValidationException</code>
   */
  public boolean processServerErrors(ValidationException validationException) {
    this.stdMessages = this.messages.getStandardMessages();
    if(!firstRun)
      reset();
    firstRun = false;
    if(validationException.getInvalidValues().isEmpty())
      return true;
    ValidationResult allResults = new ValidationResult();
    for(InvalidValueSerializable iv : validationException.getInvalidValues()) {
      String prop = iv.getPropertyName().trim();
      if(prop.indexOf(",") == -1) {
        allResults.addErrors(buildValidationResultFromServer(prop, iv.getMessage().trim()));
        boolean cont = invokeActionsForKey(prop, iv.getMessage().trim());
        if(!cont)
          return false;
      }else {
        String[] props = prop.split(",");
        allResults.addErrors(buildValidationResultFromServer(prop, iv.getMessage().trim()));
        for(String propName : props) {
          boolean cont = invokeActionsForKey(propName.trim(), iv.getMessage().trim());
          if(!cont)
            return false;
        }
      }
    }
   
    invokeGlobalActions(allResults);
   
    return false;
  }
 
  /**
   * If you got a ValidationException in serialized form (if you use restlet for example)
   * you can pass it to this method and the ValidationProcessor will deserialize and process
   * it, just like the processServerErrors method which is used for processing the errors
   * you got from RPC directly as a ValidationException object.
   *
   * The serialized ValidationException mechanism should be used when you can not (or don't want
   * to) use the RPC mechanism that directly passes ValidationException's around.
   *
   * @param serializedValidationException
   * @return
   */
  public boolean processSerializedValidationErrors(String serializedValidationException) throws IncorrectSerializationFormatException {
    ValidationException ex = deserializeValidationException(serializedValidationException);
    return processServerErrors(ex);
  }
 
  /**
   *
   * This method checks if an arbitrary String is a serialized ValidationException.
   * This method will just silently quit and return a value of
   * <i>true</i> if the given String was not a serialized <code>ValidationException</code>.
   * If it was a serialized <code>ValidationException</code> it will process it.
   *
   * This is useful in cases where you don't know if the response denotes a <code>ValidationException</code>
   * or not. It will effectively just do its job when the given string is a <code>ValidationException</code>
   * and will say that everything is ok if the given string was not a <code>ValidationException</code>.
   *
   *
   * @param serializedValidationException The string that MIGHT be a serialized <code>ValidationException</code>
   * @return <i>true</i> If there were no errors (String was no serialized <code>ValidationException</code> or it was but did not contain any errors.
   */
  public boolean processSerializedValidationErrorsSilently(String serializedValidationException) {
    ValidationException ex = null;
    try {
      ex = deserializeValidationException(serializedValidationException);
    }catch(IncorrectSerializationFormatException exc) {
        return true;
    }   
    return processServerErrors(ex);
  }
 
  public ValidationException deserializeValidationException(
      String serializedValidationException) throws IncorrectSerializationFormatException {
   
    return ValidationProcessorUtils.deserializeValidationException(serializedValidationException);
  }


  private ValidationResult buildValidationResultFromServer(String prop,
      String message) {

    ValidationResult result = new ValidationResult();
    ValidationError error = result.new ValidationError();
   
    String props[] = prop.split(",");
   
    error.propertyName = localizePropertyName(props[0]);
    if(props.length > 1)
      error.propertyName += ", "+localizePropertyName(props[1]);
    error.error = localizeMsg(message);
    result.getErrors().add(error);
   
    return result;
  }

 
  /**
   * Resets all the actions specified by a possibly failed previous validation run.
   * If the names parameter is given only the specified validator actions are reset.
   *
   * @param names The names of the validations that should be reset, can be <code>null</code> to reset everything.
   */
  public void reset(String... names) {
    for(String name : this.validators.keySet()) {
      if(names != null && names.length > 0) {
        if(!arrayIncludes(names, name))
          continue;
      }
      List<Validator> vals = this.validators.get(name);
      for(Validator val : vals) {
        val.resetActions();
      }
    }
   
    for(ValidationAction action : globalActions)
      action.reset();
  }
 
  /*
  private void invokeActions(ValidationResult result, List<Validator> validators) {
    for(Validator val : validators) {
      val.invokeActions(result);
    }
  }*/
 
  /**
   * Invokes the actions for a specific property.
   *
   * Returns <code>true</code> if the <code>ValidationProcessor</code> should
   * continue to validate possible other errors and actions, <code>false</code>
   * if the <code>ValidationProcessor</code> should not process any more errors
   * after this error
   *
   */
  private boolean invokeActionsForKey(String property, String message) {
    List<Validator> vals = validators.get(property);
    if(vals == null)
      return true;
    String localizedMessage = localizeMsg(message);
    ValidationResult result = new ValidationResult(localizedMessage);
    for(Validator val : vals) {
      val.invokeActions(result);
      if(this.stopValidationOnFirstError || val.preventsPropagationOfValidationChain()) {
        //Stop on first validation error
        return false;
      }
    }
    return true;
  }
 
  private String localizePropertyName(String propertyName) {
    if(this.propertyLocalizationPrefix)
      propertyName = PROPERTY_NAME_LOCALIZATION_PREFIX+propertyName;
    return messages.getPropertyName(propertyName);
  }
 
  private String localizeMsg(String msg) {
    if(msg.startsWith(SERVER_SIDE_STANDARD_PREFIX)) {
      String msgKey = msg.substring(SERVER_SIDE_STANDARD_PREFIX.length(), msg.length());
      return getLocalizedServerMessage(msgKey);
    }else if(msg.startsWith(HIBERNATE_VALIDATION_ERROR_PREFIX)) {
      String msgKey = msg.substring(HIBERNATE_VALIDATION_ERROR_PREFIX.length(), msg.length());
      return getLocalizedHibernateMessage(msgKey);
    }
   
    //Check for parameters
    Object msgParams[] = null;
   
    String tmpParams[] = msg.split(":");
    if(tmpParams.length > 1) {
      msgParams = new String[tmpParams.length-1];
     
      //Copy the params, skip the msg key
      for(int i=1; i<tmpParams.length; i++) {
        msgParams[i-1] = tmpParams[i];
      }
    }
   
    return messages.getCustomMessage(msg, msgParams);
  }
 
  private String getLocalizedServerMessage(String key) {
    if(key == null || key.trim().length() == 0)
      return "Localization key not given";
    if(key.equals("notNull"))
      return stdMessages.notNull();
    if(key.equals("isNull"))
      return stdMessages.isNull();
    if(key.equals("equal"))
      return stdMessages.equal();
    if(key.equals("notEqual"))
      return stdMessages.notEqual();
    if(key.equals("equals"))
      return stdMessages.equal();
    if(key.startsWith("min"))
      return twoParameterMessage(key);
    if(key.startsWith("max"))
      return twoParameterMessage(key);
    if(key.startsWith("inRange"))
      return threeParameterMessage(key);
    if(key.startsWith("length"))
      return threeParameterMessage(key);
    return "Unknown key: "+key;
  }
 
  private String getLocalizedHibernateMessage(String key) {
    if(key == null || key.trim().length() == 0)
      return "Localization key not given";
   
    if(key.equals("{validator.assertFalse}"))
      return stdMessages.validator_assertFalse();
    if(key.equals("{validator.assertTrue}"))
      return stdMessages.validator_assertTrue();
    if(key.equals("{validator.future}"))
      return stdMessages.validator_future();
    if(key.equals("{validator.length}"))
      return stdMessages.validator_length();
    if(key.equals("{validator.max}"))
      return stdMessages.validator_max();
    if(key.equals("{validator.min}"))
      return stdMessages.validator_min();
    if(key.equals("{validator.notNull}"))
      return stdMessages.validator_notNull();
    if(key.equals("{validator.past}"))
      return stdMessages.validator_past();
    if(key.equals("{validator.pattern}"))
      return stdMessages.validator_pattern();
    if(key.equals("{validator.range}"))
      return stdMessages.validator_range();
    if(key.equals("{validator.size}"))
      return stdMessages.validator_size();
    if(key.equals("{validator.email}"))
      return stdMessages.validator_email();
    if(key.equals("{validator.notEmpty}"))
      return stdMessages.validator_notEmpty();
    if(key.equals("{validator.digits}"))
      return stdMessages.validator_digits();
    if(key.equals("{validator.creditCard}"))
      return stdMessages.validator_creditCard();
    if(key.equals("{validator.ean}"))
      return stdMessages.validator_ean();

    return "Unknown hibernate key: "+key;
  }
 
  /**
   *
   * @return The count of all added validators.
   */
  public int getValidatorCount() {
    return this.validators.size();
  }
 
 
  /**
   *
   * @return The count fo all added global actions.
   */
  public int getGlobalActionCount() {
    return this.globalActions.size();
  }
 
  /**
   * Removes all the given validators
   */
  public void removeValidators(String... validatorNames) {
    for(String valName : validatorNames) {
      removeSingleValidator(valName);
    }
    if(validators.isEmpty())
      this.firstRun = true;
  }
 
  protected void removeSingleValidator(String name) {
    this.validatorNames.remove(name);
    this.validators.remove(name);
  }
 
  /**
   * Removes all validators.
   */
  public void removeAllValidators() {
    this.validatorNames.clear();
    this.validators.clear();
    this.firstRun = true;
  }
 
  /**
   * Removes all global actions.
   */
  public void removeAllGlobalActions() {
    this.globalActions.clear();
  }
 
  /**
   * Removes a global action
   */
  public void removeGlobalAction(ValidationAction actionToRemove) {
    this.globalActions.remove(actionToRemove);
  }
 
  public void removeGlobalActions(ValidationAction... instancesToRemove) {
    for(ValidationAction action : instancesToRemove) {
      this.globalActions.remove(action);
    }
  }
 
  /**
   * Resets the <code>ValidationProcessor</code>.
   *
   * Invokes <code>removeAllValidators</code>
   * and <code>removeAllGlobalActions</code>
   */
  public void removeValidatorsAndGlobalActions() {
    removeAllValidators();
    removeAllGlobalActions();
  }
 
  /**
   * If you want to add a prefix to the property names
   * that will be handed over to the <code>ValidationMessages.getPropertyName()</code>
   * method for localization set this to <code>true</code> default is <code>false</code>
   *
   * The prefix will be: <code>ValidationProcessor.PROPERTY_NAME_LOCALIZATION_PREFIX</code>
   *
   * @param addPrefix if <code>true</code> a prefix will be added to the property name on property localization
   */
  public void setPropertyLocalizationPrefixPrepended(boolean addPrefix) {
    this.propertyLocalizationPrefix = addPrefix;
  }
 
  public boolean isPropertyLocalizationPrefixPrepended() {
    return this.propertyLocalizationPrefix;
  }
 
  private String twoParameterMessage(String keyAndParameter) {
    String s[] = keyAndParameter.split(":");
    //Key = min, max or other validation function
    String key = s[0];
    //Parameter 1
    String p1 = s[1];
    //Parameter 2
    String p2 = s[2];
    String[] r = key.split("_");
    String type = null;
    if(r.length == 2) {
      key = r[0];
      type = r[1];
    }
    if(key.equals("min")) {
      if(type.equals("i"))
        return stdMessages.min(Integer.parseInt(p1), Integer.parseInt(p2));
      else if(type.equals("l"))
        return stdMessages.min(Long.parseLong(p1), Long.parseLong(p2));
      else if(type.equals("f"))
        return stdMessages.min(Float.parseFloat(p1), Float.parseFloat(p2));
      else if(type.equals("d"))
        return stdMessages.min(Double.parseDouble(p1), Double.parseDouble(p2));
      return "min lacks a type indicator like 'i' for int or 'd' for double";
    }else if(key.equals("max")) {
      if(type.equals("i"))
        return stdMessages.max(Integer.parseInt(p1), Integer.parseInt(p2));
      else if(type.equals("l"))
        return stdMessages.max(Long.parseLong(p1), Long.parseLong(p2));
      else if(type.equals("f"))
        return stdMessages.max(Float.parseFloat(p1), Float.parseFloat(p2));
      else if(type.equals("d"))
        return stdMessages.max(Double.parseDouble(p1), Double.parseDouble(p2));
      return "max lacks a type indicator like 'i' for int or 'd' for double";
    }
   
    return "Unknown key: "+key;
  }
 
  private String threeParameterMessage(String keyAndParameter) {
    String s[] = keyAndParameter.split(":");
    //Key = min, max or other validation function
    String key = s[0];
    //Parameter 1
    String p1 = s[1];
    //Parameter 2
    String p2 = s[2];
    //Parameter 3
    String p3 = s[3];
    String[] r = key.split("_");
    String type = null;
    if(r.length == 2) {
      key = r[0];
      type = r[1];
    }
    if(key.equals("inRange")) {
      if(type.equals("i"))
        return stdMessages.notInRange(Integer.parseInt(p1), Integer.parseInt(p2), Integer.parseInt(p3));
      else if(type.equals("l"))
        return stdMessages.notInRange(Long.parseLong(p1), Long.parseLong(p2), Long.parseLong(p3));
      else if(type.equals("f"))
        return stdMessages.notInRange(Float.parseFloat(p1), Float.parseFloat(p2), Float.parseFloat(p3));
      else if(type.equals("d"))
        return stdMessages.notInRange(Double.parseDouble(p1), Double.parseDouble(p2), Double.parseDouble(p3));
      return "inRange lacks a type indicator like 'i' for int or 'd' for double";
    }else if(key.equals("length")) {
      return stdMessages.length(Integer.parseInt(p1), Integer.parseInt(p2), Integer.parseInt(p3));
    }
   
    return "Unknown key: "+key;
  }
 
}
TOP

Related Classes of eu.maydu.gwt.validation.client.DefaultValidationProcessor

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.