Package org.apache.shale.validator.faces

Source Code of org.apache.shale.validator.faces.ValidatorScript

/*
* 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.shale.validator.faces;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UIForm;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.el.ValueBinding;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.Var;
import org.apache.shale.util.Tags;
import org.apache.shale.validator.CommonsValidator;

/**
* <p>A JSF component that encodes JavaScript for
*    all client-side validations specified in the same
*    JSP page (with <code>s:commonsValidator</code>.
*
* $Id: ValidatorScript.java 464373 2006-10-16 04:21:54Z rahul $
*/
public class ValidatorScript extends UIComponentBase {


    // -------------------------------------------------------- Private Variables


    /**
     * <p>The value of the <code>functionName</code> property.</p>
     */
    private String functionName;


    /**
     * <p>A map of validators, representing all of the Commons Validators
     *    attached to components in the current component hierarchy.
     *    The keys of the map are validator type names. The values are
     *    maps from IDs to CommonsValidator objects.</p>
     */
    private Map validators = new LinkedHashMap();


    /**
     * <p>Holds vars loaded with EL that are evaluated at render time.
     * The key into the map is the component's client id.</p>
     */
    private Map validatorVars = new LinkedHashMap();


    /**
     * <p>The component renders itself; therefore, this
     *    method returns null.</p>
     *
     * @return <code>null</code> component handles renderering
     */
    public String getRendererType() {
        return null;
    }


    /**
     * <p>Returns the component's family. In this case,
     *    the component is not associated with a family,
     *    so this method returns null.</p>
     *
     * @return component's family
     */
    public String getFamily() {
        return null;
    }


    /**
     * <p>Return the value of the <code>functionName</code> property.</p>
     *
     * @return callback function from the form's onsubmit.
     */
    public String getFunctionName() {

        if (this.functionName != null) {
            return functionName;
        }
        ValueBinding _vb = getValueBinding("functionName");
        if (_vb != null) {
            return (String) _vb.getValue(getFacesContext());
        } else {
            return null;
        }

    }


    /**
     * <p>Set the value of the <code>functionName</code> property.</p>
     *
     * @param functionName The new function name
     */
    public void setFunctionName(String functionName) {
        this.functionName = functionName;
    }


    /**
     * <p>Restore the state of this component.</p>
     *
     * @param context FacesContext for the current request
     * @param state State to be restored
     */
    public void restoreState(FacesContext context, Object state) {

        Object values[] = (Object[]) state;
        super.restoreState(context, values[0]);
        this.functionName = (String) values[1];

    }


    /**
     * <p>Save the state of this component.</p>
     *
     * @param context FacesContext for the current request
     * @return component's state
     */
    public Object saveState(FacesContext context) {

        Object values[] = new Object[2];
        values[0] = super.saveState(context);
        values[1] = this.functionName;
        return values;

    }


    /**
     * <p>Restructures the validator hierarchy grouping by
     * form name, type and id.</p>
     *
     * @return validators grouped by form, type and clientId
     */
    private Map getValidatorsGroupByFormName() {

        Map formValidators = new LinkedHashMap();

        Iterator vi = validators.entrySet().iterator();
        while (vi.hasNext()) {

            Map.Entry typeEntry = (Map.Entry) vi.next();
            Map typeMap = (Map) typeEntry.getValue();

            String type = (String) typeEntry.getKey();

            Iterator ti = typeMap.entrySet().iterator();
            while (ti.hasNext()) {

                Map.Entry idEntry = (Map.Entry) ti.next();
                String id = (String) idEntry.getKey();
                CommonsValidator v = (CommonsValidator) idEntry.getValue();
                String formName = v.getFormName();

                Map formTypeMap = (Map) formValidators.get(formName);
                if (formTypeMap == null) {
                    formTypeMap = new LinkedHashMap();
                    formValidators.put(formName, formTypeMap);
                }

                Map formTypeIdMap = (Map) formTypeMap.get(type);
                if (formTypeIdMap == null) {
                    formTypeIdMap = new LinkedHashMap();
                    formTypeMap.put(type, formTypeIdMap);
                }

                formTypeIdMap.put(id, v);
            }

        }

        return formValidators;

    }


    /**
     * <p>Registers a validator according to type and id.</p>
     *
     * @param type The type of the validator
     * @param id The validator's identifier
     * @param v The Commons validator associated with the id and type
     */
    private void addValidator(String type, String id, CommonsValidator v) {
        // look for validators organized by type
        Map map = (Map) validators.get(type);
        if (map == null) {
            map = new LinkedHashMap();
            validators.put(type, map);
        }
        if (id != null) {
             map.put(id, v);
        }
   }


    /**
     * <p>Recursively finds all Commons validators for the all of the
     *    components in a component hierarchy and adds them to a map.</p>
     * <p>If a validator's type is required, this method sets the
     *    associated component's required property to true. This is
     *    necessary because JSF does not validate empty fields unless
     *    a component's required property is true.</p>
     *
     * @param c The component at the root of the component tree
     * @param context The FacesContext for this request
     */
   private void findCommonsValidators(UIComponent c, FacesContext context) {
      if (c instanceof EditableValueHolder && c.isRendered()) {
         EditableValueHolder h = (EditableValueHolder) c;
         javax.faces.validator.Validator[] vs = h.getValidators();
         for (int i = 0; i < vs.length; i++) {
            if (vs[i] instanceof CommonsValidator) {
               CommonsValidator v = (CommonsValidator) vs[i];
               v.setFormName(findForm(context, c));
               if (Boolean.TRUE.equals(v.getClient())) {

                   //look for the clientId set
                   Map clientIds = (Map) c.getAttributes().get(ValidatorInputRenderer.VALIDATOR_CLIENTIDS_ATTR);
                   if (clientIds != null) {

                      validatorVars.putAll(clientIds);
                      Iterator ci = clientIds.entrySet().iterator();
                      while (ci.hasNext()) {
                         Map.Entry e = (Map.Entry) ci.next();

                         String id = (String) e.getKey();
                         addValidator(v.getType(), id, v);

                         ValidatorAction action = v.getValidatorAction();
                         List list = action.getDependencyList();
                         Iterator iter = list.iterator();
                         while (iter.hasNext()) {
                             String type = (String) iter.next();
                             addValidator(type, id, v);
                         }

                      }

                   } else {
                       //otherwise just try using the client id
                       String id = c.getClientId(context);
                       addValidator(v.getType(), id, v);

                       ValidatorAction action = v.getValidatorAction();
                       List list = action.getDependencyList();
                       Iterator iter = list.iterator();
                       while (iter.hasNext()) {
                           String type = (String) iter.next();
                           addValidator(type, id, v);
                       }
                   }

               }
               if (Boolean.TRUE.equals(v.getServer())) {
                  // Fields with empty values are not validated, so
                  // we force the issue here by setting the component's
                  // required attribute to true.

                  if ("required".equals(v.getType())) {
                     h.setRequired(true);
                  }
               }
            }
         }
      }

      Iterator childrenIterator = c.getFacetsAndChildren();
      while (childrenIterator.hasNext()) {
         UIComponent child = (UIComponent) childrenIterator.next();
         findCommonsValidators(child, context);
      }
      childrenIterator = null;
   }


    /**
     * <p>Write the start of the script for client-side validation.</p>
     *
     * @param writer A response writer
     *
     * @exception IOException if an input/output error occurs
     */
   private void writeScriptStart(ResponseWriter writer) throws IOException {
      writer.startElement("script", this);
      writer.writeAttribute("type", "text/javascript", null);
      writer.writeAttribute("language", "Javascript1.1", null);
      writer.write("\n");
    }


    /**
     * <p>Write the end of the script for client-side validation.</p>
     *
     * @param writer A response writer
     *
     * @exception IOException if an input/output error occurs
     */
   private void writeScriptEnd(ResponseWriter writer) throws IOException {
      writer.write("\n");
      writer.endElement("script");
   }


    /**
     * <p>Returns the name of the JavaScript function, specified in
     *    the JSP page (presumably), that validates this JSP page's form.</p>
     *
     * @param writer A response writer
     * @param context The FacesContext for this request
     *
     * @exception IOException if an input/output error occurs
     */
   private void writeValidationFunctions(ResponseWriter writer,
      FacesContext context) throws IOException {

      StringBuffer buff = new StringBuffer();
      buff.append("var bCancel = false;\n")
          .append("function ")
          .append(getAttributes().get("functionName").toString()).append("(form) {\n")
          .append("\tvar bValid = true;\n")
          .append("\tvar sFormName = jcv_retrieveFormName(form);\n");

      Map formValidators = getValidatorsGroupByFormName();
      Iterator formIter = formValidators.entrySet().iterator();
      while (formIter.hasNext()) {
          Map.Entry typeEntry = (Map.Entry) formIter.next();
          String formName = (String) typeEntry.getKey();
          Map formTypeValidators = (Map) typeEntry.getValue();

          // for each validator type, write callback

          buff.append("\tif ((bValid && !bCancel && (\"")
              .append(formName)
              .append("\" == sFormName))) {\n")
              .append("\t\tbValid = (");


          Iterator iter = getTypesOrderedByDependencies(formTypeValidators.keySet()).iterator();
          boolean first = true;
          while (iter.hasNext()) {
              String type = (String) iter.next();
              ValidatorAction a = CommonsValidator.getValidatorAction(type);

              buff.append((!first ? " && " : ""))
                  .append(a.getJsFunctionName())
                  .append("(form)");
              first = false;

              writer.write("function ");
              StringBuffer callback = new StringBuffer();

              // most of the type the callback function is based on the form name and
              // type but for some rules require special names
              String fnameMnemonic = CommonsValidator.getJsCallbackMnemonic(type);

              callback.append(formName).append('_').append(fnameMnemonic);
              writer.write(callback.toString());
              writer.write("() { \n");
              // for each field validated by this type, add configuration object
              Map map = (Map) formTypeValidators.get(type);
              Iterator iter2 = map.keySet().iterator();
              int k = 0;
              while (iter2.hasNext()) {
                  String id = (String) iter2.next();
                  CommonsValidator v = (CommonsValidator) map.get(id);
                  writer.write("this[" + k + "] = ");
                  k++;
                  writeJavaScriptParams(writer, context, id, v);
                  writer.write(";\n");
              }
              writer.write("\t}\n");
          }

          buff.append(");\n\t\n}");

      }
      formValidators.clear();

      // write out the form function
      buff.append("\n\treturn bValid;\n")
          .append("}\n");

      writer.write(buff.toString());

      // resolve dependencies for a complete types list

      List types = new ArrayList(validators.keySet());
      types.add("includeJavaScriptUtilities");

      Iterator iter = types.iterator();
      while (iter.hasNext()) {
         String type = (String) iter.next();
         ValidatorAction a = CommonsValidator.getValidatorAction(type);
         writer.write(a.getJavascript());
         writer.write("\n");
      }

      types.clear();

   }


   /**
    * <p>Backslash-escapes the following characters from the input string:
    * &quot;, &apos;, \, \r, \n.</p>
    *
    * <p>This method escapes characters that will result in an invalid
    * Javascript statement within the validator Javascript.</p>
    *
    * @param str The string to escape.
    * @return The string <code>s</code> with each instance of a double quote,
    *         single quote, backslash, carriage-return, or line feed escaped
    *         with a leading backslash.
    */
   private String escapeJavascript(String str) {
       if (str == null) {
           return null;
       }

       int length = str.length();

       if (length == 0) {
           return str;
       }

       // guess at how many chars we'll be adding...
       StringBuffer out = new StringBuffer(length + 4);

       // run through the string escaping sensitive chars
       for (int i = 0; i < length; i++) {
           char c = str.charAt(i);

           if ((c == '"') || (c == '\'') || (c == '\\') || (c == '\n')
                   || (c == '\r')) {
               out.append('\\');
           }

           out.append(c);
       }

       return out.toString();
   }



   /**
    * <p>Returns an array of validator types organized by dependencies.</p>
    *
    * @param typeSet The type set to be processed
    * @return array of validator types ordered by dependencies
    */
   private List getTypesOrderedByDependencies(Set typeSet) {

       List tmpList = new ArrayList(typeSet);

       ordered: for (int i = 0; i < tmpList.size(); i++) {
           boolean swap = false;
           for (int j = 0; j < tmpList.size(); j++) {
               String type = (String) tmpList.get(j);
               ValidatorAction a = CommonsValidator.getValidatorAction(type);

               List dependencies  = a.getDependencyList();
               if (dependencies != null && dependencies.size() > 0) {
                   int max = -1;
                   for (int n = 0; n < dependencies.size(); n++) {
                       max = Math.max(max, tmpList.indexOf(dependencies.get(n)));
                   }
                   if (max > j) {
                       String tmp = (String) tmpList.get(j);
                       tmpList.remove(j);
                       tmpList.add(max, tmp);
                       swap = true;
                       j = max;
                   }
               }

           }
           if (!swap) {
             break ordered;
           }
       }

       return tmpList;

   }

    /**
     * <p>Writes the JavaScript parameters for the client-side
     *    validation code.</p>
     *
     * @param writer A response writer
     * @param context The FacesContext for this request
     * @param id The clientId of the owning component
     * @param v The Commons validator
     *
     * @exception IOException if an input/output error occurs
     */
   public void writeJavaScriptParams(ResponseWriter writer,
      FacesContext context, String id, CommonsValidator v) throws IOException {

      Map localVars = null;
      // look for var's evaluated at render time.  This is for client
      // side JavaScript validation
      if (validatorVars != null && validatorVars.containsKey(id)) {
          Map typeVars = (Map) validatorVars.get(id);
          if (typeVars != null && typeVars.containsKey(v.getType())) {
              localVars = (Map) typeVars.get(v.getType());
          }
      }

      Tags tagUtils = new Tags();
      ValidatorAction validatorAction = v.getValidatorAction();
      writer.write("new Array(\"");
      writer.write(id);
      writer.write("\", \"");
      writer.write(v.getErrorMessage(context, validatorAction, localVars));
      writer.write("\", new Function(\"x\", \"return {");

      Iterator vi = v.getVars().entrySet().iterator();

      boolean first = true;

      // vars captured at render time and are the result of
      // EL.
      Map idVars = (Map) validatorVars.get(id);

      next: while (vi.hasNext()) {
         Map.Entry e = (Map.Entry) vi.next();

         Object value = e.getValue();

         // look for a render override by clientId/type
         if (idVars != null && idVars.containsKey(v.getType())) {

             Map typeVars = (Map) idVars.get(v.getType());
             // look for a render override by clientId/type/name
             if (typeVars != null && typeVars.containsKey(e.getKey())) {
                value = typeVars.get(e.getKey());
             }
         } else {
            if (value != null && value instanceof String
                && isValueReference((String) e.getValue())) {

                value = tagUtils.eval((String) e.getValue());
            }
         }

         if (value == null) {
            continue next;
         }
         String name = (String) e.getKey();
         if (!first) {
             writer.write(",");
         } else {
             first = false;
         }
         writer.write(name);
         writer.write(":");

         String jsType = v.getVarType(name);
         // Ugh...mask validator doesn't construct RegExp
         if (jsType.equals(Var.JSTYPE_REGEXP)) {
             writer.write("/");
         } else {
             writer.write("'");
         }

         writer.write(escapeJavascript(value.toString()));

         if (jsType.equals(Var.JSTYPE_REGEXP)) {
             writer.write("/");
         } else {
             writer.write("'");
         }
      }
      writer.write("}[x];\"))");
   }


   /**
    * <p>Traverses up the tree looking for the owning form.
    * Returns the parent <code>UIForm</code> or empty string
    * if one is not found.</p>
    *
    * @param context FacesContext for the current request
    * @param component <code>UIForm</code> parent of the component.
    * @return client id of the parent form component
    */
   public String findForm(FacesContext context, UIComponent component) {

       UIComponent parent = component.getParent();
       if (parent != null) {
          if (parent instanceof UIForm) {
             return parent.getClientId(context).replace(UINamingContainer.SEPARATOR_CHAR, '_');
          } else {
             return findForm(context, parent);
          }
       }

       return "";
   }


    /**
     * <p>Begin encoding for this component. This method
     *    finds all Commons validators attached to components
     *    in the current component hierarchy and writes out
     *    JavaScript code to invoke those validators, in turn.</p>
     *
     * @param context The FacesContext for this request
     *
     * @exception IOException if an input/output error occurs
     */
   public void encodeBegin(FacesContext context) throws IOException {
      ResponseWriter writer = context.getResponseWriter();

      validators.clear();
      findCommonsValidators(context.getViewRoot(), context);

      writeScriptStart(writer);
      writeValidationFunctions(writer, context);
      writeScriptEnd(writer);
   }


    /**
     * <p>Return true if the specified string contains an EL expression.</p>
     *
     * <p>This is taken almost verbatim from {@link javax.faces.webapp.UIComponentTag}
     * in order to remove JSP dependencies from the renderers.</p>
     *
     * @param value String to be checked for being an expression
     */
    private boolean isValueReference(String value) {

        if (value == null) {
            return false;
        }

        int start = value.indexOf("#{");
        if (start < 0) {
            return false;
        }

        int end = value.lastIndexOf('}');
        return (end >= 0) && (start < end);
    }


}
TOP

Related Classes of org.apache.shale.validator.faces.ValidatorScript

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.