View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.util;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.config.property.ConfigurationService;
20  import org.kuali.rice.krad.datadictionary.validation.constraint.BaseConstraint;
21  import org.kuali.rice.krad.datadictionary.validation.constraint.CaseConstraint;
22  import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint;
23  import org.kuali.rice.krad.datadictionary.validation.constraint.MustOccurConstraint;
24  import org.kuali.rice.krad.datadictionary.validation.constraint.PrerequisiteConstraint;
25  import org.kuali.rice.krad.datadictionary.validation.constraint.SimpleConstraint;
26  import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint;
27  import org.kuali.rice.krad.datadictionary.validation.constraint.WhenConstraint;
28  import org.kuali.rice.krad.service.KRADServiceLocator;
29  import org.kuali.rice.krad.uif.UifConstants;
30  import org.kuali.rice.krad.uif.field.InputField;
31  import org.kuali.rice.krad.uif.view.View;
32  import org.kuali.rice.krad.uif.control.TextControl;
33  
34  import java.text.MessageFormat;
35  import java.util.ArrayList;
36  import java.util.EnumSet;
37  import java.util.List;
38  
39  /**
40   * This class contains all the methods necessary for generating the js required to perform validation client side.
41   * The processAndApplyConstraints(InputField field, View view) is the key method of this class used by
42   * InputField to setup its client side validation mechanisms.
43   * 
44   * @author Kuali Rice Team (rice.collab@kuali.org)
45   */
46  public class ClientValidationUtils {
47  	// used to give validation methods unique signatures
48  	private static int methodKey = 0;
49  	
50  	// list used to temporarily store mustOccurs field names for the error
51  	// message
52  	private static List<List<String>> mustOccursPathNames;
53  	
54  	public static final String LABEL_KEY_SPLIT_PATTERN = ",";
55  	
56  	
57  	public static final String PREREQ_MSG_KEY = "prerequisite";
58  	public static final String POSTREQ_MSG_KEY = "postrequisite";
59  	public static final String MUSTOCCURS_MSG_KEY = "mustOccurs";
60  	public static final String GENERIC_FIELD_MSG_KEY = "general.genericFieldName";
61  	
62  	public static final String ALL_MSG_KEY = "general.all";
63  	public static final String ATMOST_MSG_KEY = "general.atMost";
64  	public static final String AND_MSG_KEY = "general.and";
65  	public static final String OR_MSG_KEY = "general.or";
66  	
67  	private static ConfigurationService configService = KRADServiceLocator.getKualiConfigurationService();
68  	
69  	//Enum representing names of rules provided by the jQuery plugin
70  	public static enum ValidationMessageKeys{
71  		REQUIRED("required"), 
72  		MIN_EXCLUSIVE("minExclusive"), 
73  		MAX_INCLUSIVE("maxInclusive"),
74  		MIN_LENGTH("minLengthConditional"),
75  		MAX_LENGTH("maxLengthConditional");
76  		
77  		private ValidationMessageKeys(String name) {
78  			this.name = name;
79  		}
80  		
81  		private final String name;
82  		
83  		@Override
84  		public String toString() {
85  			return name;
86  		}
87  		
88  		public static boolean contains(String name){
89              for (ValidationMessageKeys element : EnumSet.allOf(ValidationMessageKeys.class)) {
90                  if (element.toString().equalsIgnoreCase(name)) {
91                      return true;
92                  }
93              }
94              return false;
95  		}
96  	}
97  	
98  	public static String generateMessageFromLabelKey(List<String> params, String labelKey){
99  		String message = "NO MESSAGE";
100 		if(StringUtils.isNotEmpty(labelKey)){
101 			message = configService.getPropertyValueAsString(labelKey);
102 			if(params != null && !params.isEmpty() && StringUtils.isNotEmpty(message)){
103 			    message = MessageFormat.format(message, params.toArray());
104 			}
105 		}
106 		if(StringUtils.isEmpty(message)){
107 		    message = labelKey;
108 		}
109 		//replace characters that might cause issues with their equivalent html codes
110 		if(message.contains("\"")){
111 		    message = message.replace("\"", "&quot;");
112 		}
113 		if(message.contains("'")){
114 		    message = message.replace("'", "&#39;");
115 		}
116 		if(message.contains("\\")){
117 		    message = message.replace("\\", "&#92;");
118 		}
119 		return message;
120 	}
121 
122 	/**
123 	 * Generates the js object used to override all default messages for validator jquery plugin with custom
124 	 * messages derived from the configService.
125 	 * 
126 	 * @return
127 	 */
128 	public static String generateValidatorMessagesOption(){
129 		String mOption = "";
130 		String keyValuePairs = "";
131 		for(ValidationMessageKeys element : EnumSet.allOf(ValidationMessageKeys.class)){
132 			String key = element.toString();
133 			String message = configService.getPropertyValueAsString(UifConstants.Messages.VALIDATION_MSG_KEY_PREFIX + key);
134 			if(StringUtils.isNotEmpty(message)){
135 				keyValuePairs = keyValuePairs + "\n" + key + ": '"+ message + "',";
136 				
137 			}
138 			
139 		}
140 		keyValuePairs = StringUtils.removeEnd(keyValuePairs, ",");
141 		if(StringUtils.isNotEmpty(keyValuePairs)){
142 			mOption="{" + keyValuePairs + "}";
143 		}
144 		
145 		return mOption;
146 	}
147 	
148 	/**
149 	 * Returns the add method jquery validator call for the regular expression
150 	 * stored in validCharactersConstraint.
151 	 * 
152 	 * @param validCharactersConstraint
153 	 * @return js validator.addMethod script
154 	 */
155 	public static String getRegexMethod(InputField field, ValidCharactersConstraint validCharactersConstraint) {
156 		String message = generateMessageFromLabelKey(validCharactersConstraint.getValidationMessageParams(), validCharactersConstraint.getLabelKey());
157 		String key = "validChar-" + field.getBindingInfo().getBindingPath() + methodKey;
158 		
159 		String regex = validCharactersConstraint.getValue();
160 		//replace characters known to cause issues if not escaped
161 		if(regex.contains("\\\\")){
162 		    regex = regex.replaceAll("\\\\", "\\\\\\\\");
163 		}
164 		if(regex.contains("/")){
165 		    regex = regex.replace("/", "\\/");
166 		}
167 		
168         return "\njQuery.validator.addMethod(\"" + ScriptUtils.escapeName(key)
169                 + "\", function(value, element) {\n "
170                 + "return this.optional(element) || /" + regex + "/.test(value);"
171                 + "}, \"" + message + "\");";
172 	}
173 	
174 	/**
175      * Returns the add method jquery validator call for the regular expression
176      * stored in validCharactersConstraint that explicitly checks a boolean.  Needed because one method
177      * accepts params and the other doesn't.
178      * 
179      * @param validCharactersConstraint
180      * @return js validator.addMethod script
181      */
182     public static String getRegexMethodWithBooleanCheck(InputField field, ValidCharactersConstraint validCharactersConstraint) {
183         String message = generateMessageFromLabelKey(validCharactersConstraint.getValidationMessageParams(), validCharactersConstraint.getLabelKey());
184         String key = "validChar-" + field.getBindingInfo().getBindingPath() + methodKey;
185         
186         String regex = validCharactersConstraint.getValue();
187         //replace characters known to cause issues if not escaped
188         if(regex.contains("\\\\")){
189             regex = regex.replaceAll("\\\\", "\\\\\\\\");
190         }
191         if(regex.contains("/")){
192 		    regex = regex.replace("/", "\\/");
193 		}
194         
195         return "\njQuery.validator.addMethod(\"" + ScriptUtils.escapeName(key)
196                 + "\", function(value, element, doCheck) {\n if(doCheck === false){return true;}else{"
197                 + "return this.optional(element) || /" + regex + "/.test(value);}"
198                 + "}, \"" + message + "\");";
199     }
200 	
201 
202 	/**
203 	 * This method processes a single CaseConstraint. Internally it makes calls
204 	 * to processWhenConstraint for each WhenConstraint that exists in this
205 	 * constraint. It adds a "dependsOn" css class to this field for the field
206 	 * which the CaseConstraint references.
207 	 * 
208 	 * @param view
209 	 * @param andedCase
210 	 *            the boolean logic to be anded when determining if this case is
211 	 *            satisfied (used for nested CaseConstraints)
212 	 */
213 	public static void processCaseConstraint(InputField field, View view, CaseConstraint constraint, String andedCase) {
214 		if (constraint.getOperator() == null) {
215 			constraint.setOperator("equals");
216 		}
217 
218 		String operator = "==";
219 		if (constraint.getOperator().equalsIgnoreCase("not_equals")) {
220 			operator = "!=";
221 		}
222 		else if (constraint.getOperator().equalsIgnoreCase("greater_than_equal")) {
223 			operator = ">=";
224 		}
225 		else if (constraint.getOperator().equalsIgnoreCase("less_than_equal")) {
226 			operator = "<=";
227 		}
228 		else if (constraint.getOperator().equalsIgnoreCase("greater_than")) {
229 			operator = ">";
230 		}
231 		else if (constraint.getOperator().equalsIgnoreCase("less_than")) {
232 			operator = "<";
233 		}
234 		else if (constraint.getOperator().equalsIgnoreCase("has_value")) {
235 			operator = "";
236 		}
237 		// add more operator types here if more are supported later
238 
239 		field.getControl().addStyleClass("dependsOn-" + ScriptUtils.escapeName(constraint.getPropertyName()));
240 
241 		if (constraint.getWhenConstraint() != null && !constraint.getWhenConstraint().isEmpty()) {
242 			//String fieldPath = field.getBindingInfo().getBindingObjectPath() + "." + constraint.getPropertyName();
243             String fieldPath = constraint.getPropertyName();
244 		    for (WhenConstraint wc : constraint.getWhenConstraint()) {
245 				processWhenConstraint(field, view, constraint, wc, ScriptUtils.escapeName(fieldPath), operator, andedCase);
246 			}
247 		}
248 	}
249 	
250 
251 
252 	/**
253 	 * This method processes the WhenConstraint passed in. The when constraint
254 	 * is used to create a boolean statement to determine if the constraint will
255 	 * be applied. The necessary rules/methods for applying this constraint are
256 	 * created in the createRule call. Note the use of the use of coerceValue js
257 	 * function call.
258 	 * 
259 	 * @param view
260 	 * @param wc
261 	 * @param fieldPath
262 	 * @param operator
263 	 * @param andedCase
264 	 */
265 	private static void processWhenConstraint(InputField field, View view, CaseConstraint caseConstraint, WhenConstraint wc, String fieldPath,
266 			String operator, String andedCase) {
267 		String ruleString = "";
268 		// prerequisite constraint
269 
270 		String booleanStatement = "";
271 		if (wc.getValues() != null) {
272 
273 			String caseStr = "";
274 			if (!caseConstraint.isCaseSensitive()) {
275 				caseStr = ".toUpperCase()";
276 			}
277 			for (int i = 0; i < wc.getValues().size(); i++) {
278 				if (operator.isEmpty()) {
279 					// has_value case
280 					if (wc.getValues().get(i) instanceof String
281 							&& ((String) wc.getValues().get(i)).equalsIgnoreCase("false")) {
282 						booleanStatement = booleanStatement + "(coerceValue('" + fieldPath + "') == '')";
283 					}
284 					else {
285 						booleanStatement = booleanStatement + "(coerceValue('" + fieldPath + "') != '')";
286 					}
287 				}
288 				else {
289 					// everything else
290 					booleanStatement = booleanStatement + "(coerceValue('" + fieldPath + "')" + caseStr + " "
291 							+ operator + " \"" + wc.getValues().get(i) + "\"" + caseStr + ")";
292 				}
293 				if ((i + 1) != wc.getValues().size()) {
294 					booleanStatement = booleanStatement + " || ";
295 				}
296 			}
297 
298 		}
299 
300 		if (andedCase != null) {
301 			booleanStatement = "(" + booleanStatement + ") && (" + andedCase + ")";
302 		}
303 
304 		if (wc.getConstraint() != null && StringUtils.isNotEmpty(booleanStatement)) {
305 			ruleString = createRule(field, wc.getConstraint(), booleanStatement, view);
306 		}
307 
308 		if (StringUtils.isNotEmpty(ruleString)) {
309 			addScriptToPage(view, field, ruleString);
310 		}
311 	}
312 	
313 
314 
315 	/**
316 	 * Adds the script to the view to execute on a jQuery document ready event.
317 	 * 
318 	 * @param view
319 	 * @param script
320 	 */
321 	public static void addScriptToPage(View view, InputField field, String script) {
322         String prefixScript = "";
323         
324         if (field.getOnDocumentReadyScript() != null) {
325             prefixScript = field.getOnDocumentReadyScript();
326         }
327         field.setOnDocumentReadyScript(prefixScript + "\n" + "runValidationScript(function(){" + script + "});");
328 	}
329 	
330 	/**
331 	 * Determines which fields are being evaluated in a boolean statement, so handlers can be
332 	 * attached to them if needed, returns these names in a list.
333 	 * 
334 	 * @param statement
335 	 * @return
336 	 */
337 	private static List<String> parseOutFields(String statement){
338 	    List<String> fieldNames = new ArrayList<String>();
339 	    String[] splits = StringUtils.splitByWholeSeparator(statement, "coerceValue('");
340 	    for(String s: splits){
341 	        String fieldName = StringUtils.substringBefore(s, "'");
342 	        fieldNames.add(fieldName);
343 	    }
344 	    return fieldNames;
345 	}
346 
347 	/**
348 	 * This method takes in a constraint to apply only when the passed in
349 	 * booleanStatement is valid. The method will create the necessary addMethod
350 	 * and addRule jquery validator calls for the rule to be applied to the
351 	 * field when the statement passed in evaluates to true during runtime and
352 	 * this field is being validated. Note the use of custom methods for min/max
353 	 * length/value.
354 	 * 
355 	 * @param field
356 	 *            the field to apply the generated methods and rules to
357 	 * @param constraint
358 	 *            the constraint to be applied when the booleanStatement
359 	 *            evaluates to true during validation
360 	 * @param booleanStatement
361 	 *            the booleanstatement in js - should return true when the
362 	 *            validation rule should be applied
363 	 * @param view
364 	 * @return
365 	 */
366 	@SuppressWarnings("boxing")
367 	private static String createRule(InputField field, Constraint constraint, String booleanStatement, View view) {
368 		String rule = "";
369 		int constraintCount = 0;
370 		if (constraint instanceof BaseConstraint && ((BaseConstraint) constraint).getApplyClientSide()) {
371 			if (constraint instanceof SimpleConstraint) {
372 				if (((SimpleConstraint) constraint).getRequired() != null && ((SimpleConstraint) constraint).getRequired()) {
373 					rule = rule + "required: function(element){\nreturn (" + booleanStatement + ");}";
374 					//special requiredness indicator handling
375 					String showIndicatorScript = "";
376 					for(String checkedField: parseOutFields(booleanStatement)){
377 					    showIndicatorScript = showIndicatorScript + 
378 					        "setupShowReqIndicatorCheck('"+ checkedField +"', '" + field.getBindingInfo().getBindingPath() + "', " + "function(){\nreturn (" + booleanStatement + ");});\n";
379 					}
380 					addScriptToPage(view, field, showIndicatorScript);
381 					
382 					constraintCount++;
383 				}
384 				if (((SimpleConstraint) constraint).getMinLength() != null) {
385 					if (constraintCount > 0) {
386 						rule = rule + ",\n";
387 					}
388 					rule = rule + "minLengthConditional: [" + ((SimpleConstraint) constraint).getMinLength()
389 							+ ", function(){return " + booleanStatement + ";}]";
390 					constraintCount++;
391 				}
392 				if (((SimpleConstraint) constraint).getMaxLength() != null) {
393 					if (constraintCount > 0) {
394 						rule = rule + ",\n";
395 					}
396 					rule = rule + "maxLengthConditional: [" + ((SimpleConstraint) constraint).getMaxLength()
397 							+ ", function(){return " + booleanStatement + ";}]";
398 					constraintCount++;
399 				}
400 
401 				if (((SimpleConstraint) constraint).getExclusiveMin() != null) {
402 					if (constraintCount > 0) {
403 						rule = rule + ",\n";
404 					}
405 					rule = rule + "minExclusive: [" + ((SimpleConstraint) constraint).getExclusiveMin()
406 							+ ", function(){return " + booleanStatement + ";}]";
407 					constraintCount++;
408 				}
409 
410 				if (((SimpleConstraint) constraint).getInclusiveMax() != null) {
411 					if (constraintCount > 0) {
412 						rule = rule + ",\n";
413 					}
414 					rule = rule + "maxInclusive: [" + ((SimpleConstraint) constraint).getInclusiveMax()
415 							+ ", function(){return " + booleanStatement + ";}]";
416 					constraintCount++;
417 				}
418 
419 				rule = "jq('[name=\"" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').rules(\"add\", {" + rule + "\n});";
420 			}
421 			else if (constraint instanceof ValidCharactersConstraint) {
422 				String regexMethod = "";
423 				String methodName = "";
424 				if(StringUtils.isNotEmpty(((ValidCharactersConstraint)constraint).getValue())) {
425 					regexMethod = ClientValidationUtils.getRegexMethodWithBooleanCheck(field, (ValidCharactersConstraint) constraint) + "\n";
426 					methodName = "validChar-" + field.getBindingInfo().getBindingPath() + methodKey;
427 					methodKey++;
428 				}
429 				else {
430 					if(StringUtils.isNotEmpty(((ValidCharactersConstraint)constraint).getLabelKey())){
431 						methodName = ((ValidCharactersConstraint)constraint).getLabelKey();
432 					}
433 				}
434 				if (StringUtils.isNotEmpty(methodName)) {
435 					rule = regexMethod + "jq('[name=\"" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').rules(\"add\", {\n\"" + methodName
436 							+ "\" : function(element){return (" + booleanStatement + ");}\n});";
437 				}
438 			}
439 			else if (constraint instanceof PrerequisiteConstraint) {
440 				processPrerequisiteConstraint(field, (PrerequisiteConstraint) constraint, view, booleanStatement);
441 			}
442 			else if (constraint instanceof CaseConstraint) {
443 				processCaseConstraint(field, view, (CaseConstraint) constraint, booleanStatement);
444 			}
445 			else if (constraint instanceof MustOccurConstraint) {
446 				processMustOccurConstraint(field, view, (MustOccurConstraint) constraint, booleanStatement);
447 			}
448 		}
449 		return rule;
450 	}
451 	
452 
453 
454 	/**
455 	 * This method is a simpler version of processPrerequisiteConstraint
456 	 * 
457 	 * @see ClientValidationUtils#processPrerequisiteConstraint(org.kuali.rice.krad.uif.field.InputField, PrerequisiteConstraint, View, String)
458 	 * @param constraint
459 	 * @param view
460 	 */
461 	public static void processPrerequisiteConstraint(InputField field, PrerequisiteConstraint constraint, View view) {
462 		processPrerequisiteConstraint(field, constraint, view, "true");
463 	}
464 
465 	/**
466 	 * This method processes a Prerequisite constraint that should be applied
467 	 * when the booleanStatement passed in evaluates to true.
468 	 * 
469 	 * @param constraint
470 	 *            prerequisiteConstraint
471 	 * @param view
472 	 * @param booleanStatement
473 	 *            the booleanstatement in js - should return true when the
474 	 *            validation rule should be applied
475 	 */
476 	public static void processPrerequisiteConstraint(InputField field, PrerequisiteConstraint constraint, View view, String booleanStatement) {
477 		if (constraint != null && constraint.getApplyClientSide().booleanValue()) {
478 		    String dependsClass = "dependsOn-" + ScriptUtils.escapeName(constraint.getPropertyName());
479 		    String addClass = "jq('[name=\""+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').addClass('" + dependsClass + "');" +
480 		        "jq('[name=\""+ ScriptUtils.escapeName(constraint.getPropertyName()) + "\"]').addClass('" + "dependsOn-" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "');";
481 			addScriptToPage(view, field, addClass + getPrerequisiteStatement(field, view, constraint, booleanStatement)
482 					+ getPostrequisiteStatement(field, constraint, booleanStatement));
483 	        //special requiredness indicator handling
484 	        String showIndicatorScript = "setupShowReqIndicatorCheck('"+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) +"', '" + ScriptUtils.escapeName(constraint.getPropertyName()) + "', " + "function(){\nreturn (coerceValue('" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "') && " + booleanStatement + ");});\n";
485 	        addScriptToPage(view, field, showIndicatorScript);
486 		}
487 	}
488 
489 	/**
490 	 * This method creates the script necessary for executing a prerequisite
491 	 * rule in which this field occurs after the field specified in the
492 	 * prerequisite rule - since it requires a specific set of UI logic. Builds
493 	 * an if statement containing an addMethod jquery validator call. Adds a
494 	 * "dependsOn" css class to this field for the field specified.
495 	 * 
496 	 * @param constraint
497 	 *            prerequisiteConstraint
498 	 * @param booleanStatement
499 	 *            the booleanstatement in js - should return true when the
500 	 *            validation rule should be applied
501 	 * @return
502 	 */
503 	private static String getPrerequisiteStatement(InputField field, View view, PrerequisiteConstraint constraint, String booleanStatement) {
504 		methodKey++;
505 		String message = "";
506 		if(StringUtils.isEmpty(constraint.getLabelKey())){
507 			message = configService.getPropertyValueAsString(UifConstants.Messages.VALIDATION_MSG_KEY_PREFIX + "prerequisite");
508 		}
509 		else{
510 			message = generateMessageFromLabelKey(constraint.getValidationMessageParams(), constraint.getLabelKey());
511 		}
512 		if(StringUtils.isEmpty(message)){
513 			message = "prerequisite - No message";
514 		}
515 		else{
516 			InputField requiredField = (InputField) view.getViewIndex().getDataFieldByPath(constraint.getPropertyName());
517 			if(requiredField != null && StringUtils.isNotEmpty(requiredField.getLabel())){
518 				message = MessageFormat.format(message, requiredField.getLabel());
519 			}
520 			else{
521 				message = MessageFormat.format(message, configService.getPropertyValueAsString(GENERIC_FIELD_MSG_KEY));
522 			}
523 		}
524 		
525 		// field occurs before case
526 		String methodName = "prConstraint-" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + methodKey;
527 		String addClass = "jq('[name=\""+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').addClass('" + methodName + "');\n";
528 		String method = "\njQuery.validator.addMethod(\""+ methodName +"\", function(value, element) {\n" +
529 			" if(" + booleanStatement + "){ return (this.optional(element) || (coerceValue('" + ScriptUtils.escapeName(constraint.getPropertyName()) + "')));}else{return true;} " +
530 			"}, \"" + message + "\");";
531 		
532 		String ifStatement = "if(occursBefore('" + ScriptUtils.escapeName(constraint.getPropertyName()) + "','" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) +
533 		"')){" + addClass + method + "}";
534 		return ifStatement;
535 	}
536 
537 	/**
538 	 * This method creates the script necessary for executing a prerequisite
539 	 * rule in which this field occurs before the field specified in the
540 	 * prerequisite rule - since it requires a specific set of UI logic. Builds
541 	 * an if statement containing an addMethod jquery validator call.
542 	 * 
543 	 * @param constraint
544 	 *            prerequisiteConstraint
545 	 * @param booleanStatement
546 	 *            the booleanstatement in js - should return true when the
547 	 *            validation rule should be applied
548 	 * @return
549 	 */
550 	private static String getPostrequisiteStatement(InputField field, PrerequisiteConstraint constraint, String booleanStatement) {
551 		// field occurs after case
552 		String message = "";
553 		if(StringUtils.isEmpty(constraint.getLabelKey())){
554 			message = configService.getPropertyValueAsString(UifConstants.Messages.VALIDATION_MSG_KEY_PREFIX + "postrequisite");
555 		}
556 		else{
557 			message = generateMessageFromLabelKey(constraint.getValidationMessageParams(), constraint.getLabelKey());
558 		}
559 		
560 		if(StringUtils.isEmpty(constraint.getLabelKey())){
561 			if(StringUtils.isNotEmpty(field.getLabel())){
562 				message = MessageFormat.format(message, field.getLabel());
563 			}
564 			else{
565 				message = MessageFormat.format(message, configService.getPropertyValueAsString(GENERIC_FIELD_MSG_KEY));
566 			}
567 			
568 		}
569 		
570 		String function = "function(element){\n" +
571 			"return (coerceValue('"+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "') && " + booleanStatement + ");}";
572 		String postStatement = "\nelse if(occursBefore('" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "','" + ScriptUtils.escapeName(constraint.getPropertyName()) +
573 			"')){\njq('[name=\""+ ScriptUtils.escapeName(constraint.getPropertyName()) +
574 			"\"]').rules(\"add\", { required: \n" + function 
575 			+ ", \nmessages: {\nrequired: \""+ message +"\"}});}\n";
576 		
577 		return postStatement;
578 
579 	}
580 
581 	/**
582 	 * This method processes the MustOccurConstraint. The constraint is only
583 	 * applied when the booleanStatement evaluates to true during validation.
584 	 * This method creates the addMethod and add rule calls for the jquery
585 	 * validation plugin necessary for applying this constraint to this field.
586 	 * 
587 	 * @param view
588 	 * @param mc
589 	 * @param booleanStatement
590 	 *            the booleanstatement in js - should return true when the
591 	 *            validation rule should be applied
592 	 */
593 	public static void processMustOccurConstraint(InputField field, View view, MustOccurConstraint mc, String booleanStatement) {
594 		methodKey++;
595 		mustOccursPathNames = new ArrayList<List<String>>();
596 		// TODO make this show the fields its requiring
597 		String methodName = "moConstraint-" + ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + methodKey;
598 		String method = "\njQuery.validator.addMethod(\""+ methodName +"\", function(value, element) {\n" +
599 		" if(" + booleanStatement + "){return (this.optional(element) || ("+ getMustOccurStatement(field, mc) + "));}else{return true;}" +
600 		"}, \"" + getMustOccursMessage(view, mc) +"\");";
601 		String rule = method + "jq('[name=\""+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').rules(\"add\", {\n\"" + methodName + "\": function(element){return (" + booleanStatement + ");}\n});";
602 		addScriptToPage(view, field, rule);
603 	}
604 
605 	/**
606 	 * This method takes in a MustOccurConstraint and returns the statement used
607 	 * in determining if the must occurs constraint has been satisfied when this
608 	 * field is validated. Note the use of the mustOccurCheck method. Nested
609 	 * mustOccurConstraints are ored against the result of the mustOccurCheck by
610 	 * calling this method recursively.
611 	 * 
612 	 * @param constraint
613 	 * @return
614 	 */
615 	@SuppressWarnings("boxing")
616 	private static String getMustOccurStatement(InputField field, MustOccurConstraint constraint) {
617 		String statement = "";
618 		List<String> attributePaths = new ArrayList<String>();
619 		if (constraint != null && constraint.getApplyClientSide()) {
620 			String array = "[";
621 			if (constraint.getPrerequisiteConstraints() != null) {
622 				for (int i = 0; i < constraint.getPrerequisiteConstraints().size(); i++) {
623 					field.getControl().addStyleClass("dependsOn-"
624 							+ constraint.getPrerequisiteConstraints().get(i).getPropertyName());
625 					array = array + "'" + ScriptUtils.escapeName(constraint.getPrerequisiteConstraints().get(i).getPropertyName()) + "'";
626 					attributePaths.add(constraint.getPrerequisiteConstraints().get(i).getPropertyName());
627 					if (i + 1 != constraint.getPrerequisiteConstraints().size()) {
628 						array = array + ",";
629 					}
630 
631 				}
632 			}
633 			array = array + "]";
634 			statement = "mustOccurTotal(" + array + ", " + constraint.getMin() + ", " + constraint.getMax() + ")";
635 			//add min to string list
636 			if(constraint.getMin()!=null){
637 				attributePaths.add(constraint.getMin().toString());
638 			}
639 			else{
640 				attributePaths.add(null);
641 			}
642 			//add max to string list
643 			if(constraint.getMax()!=null){
644 				attributePaths.add(constraint.getMax().toString());
645 			}
646 			else{
647 				attributePaths.add(null);
648 			}
649 			
650 			mustOccursPathNames.add(attributePaths);
651 			if(StringUtils.isEmpty(statement)){
652 				statement = "0";
653 			}
654 			if (constraint.getMustOccurConstraints() != null) {
655 				for (MustOccurConstraint mc : constraint.getMustOccurConstraints()) {
656 					statement = "mustOccurCheck(" + statement + " + " + getMustOccurStatement(field, mc) +
657 						", " + constraint.getMin() + ", " + constraint.getMax() + ")";
658 				}
659 			}
660 			else{
661 				statement = "mustOccurCheck(" + statement +
662 				", " + constraint.getMin() + ", " + constraint.getMax() + ")";
663 			}
664 		}
665 		return statement;
666 	}
667 
668 	
669 	/**
670 	 * Generates a message for the must occur constraint (if no label key is specified).  
671 	 * This message is most accurate when must occurs is a single
672 	 * or double level constraint.  Beyond that, the message will still be accurate but may be confusing for
673 	 * the user - this auto-generated message however will work in MOST use cases.
674 	 * 
675 	 * @param view
676 	 * @return
677 	 */
678 	private static String getMustOccursMessage(View view, MustOccurConstraint constraint){
679 		String message = "";
680 		if(StringUtils.isNotEmpty(constraint.getLabelKey())){
681 			message = generateMessageFromLabelKey(constraint.getValidationMessageParams(), constraint.getLabelKey());
682 		}
683 		else{
684 			String and = configService.getPropertyValueAsString(AND_MSG_KEY);
685 			String or = configService.getPropertyValueAsString(OR_MSG_KEY);
686 			String all = configService.getPropertyValueAsString(ALL_MSG_KEY);
687 			String atMost = configService.getPropertyValueAsString(ATMOST_MSG_KEY);
688 			String genericLabel = configService.getPropertyValueAsString(GENERIC_FIELD_MSG_KEY);
689 			String mustOccursMsg = configService.getPropertyValueAsString(
690 			        UifConstants.Messages.VALIDATION_MSG_KEY_PREFIX + MUSTOCCURS_MSG_KEY);
691 			//String postfix = configService.getPropertyValueAsString(VALIDATION_MSG_KEY_PREFIX + MUSTOCCURS_POST_MSG_KEY);
692 			String statement="";
693 			for(int i=0; i< mustOccursPathNames.size(); i++){
694 				String andedString = "";
695 				
696 				List<String> paths = mustOccursPathNames.get(i);
697 				if(!paths.isEmpty()){
698 					//note that the last 2 strings are min and max and rest are attribute paths
699 					String min = paths.get(paths.size()-2);
700 					String max = paths.get(paths.size()-1);
701 					for(int j=0; j<paths.size()-2;j++){
702 						InputField field = (InputField) view.getViewIndex().getDataFieldByPath(paths.get(j).trim());
703 						String label = genericLabel;
704 						if(field != null && StringUtils.isNotEmpty(field.getLabel())){
705 							label = field.getLabel();
706 						}
707 						if(min.equals(max)){
708 							if(j==0){
709 								andedString = label;
710 							}
711 							else if(j==paths.size()-3){
712 								andedString = andedString + " " + and + " " + label;
713 							}
714 							else{
715 								andedString = andedString + ", " + label;
716 							}
717 						}
718 						else{
719 							andedString = andedString + "<li>" + label + "</li>";
720 						}
721 					}
722 					if(min.equals(max)){
723 						andedString = "<li>" + andedString + "</li>";
724 					}
725 					andedString="<ul>" + andedString + "</ul>";
726 				
727 					if(StringUtils.isNotEmpty(min) && StringUtils.isNotEmpty(max) && !min.equals(max)){
728 						andedString = MessageFormat.format(mustOccursMsg, min + "-" + max) + "<br/>" +andedString;
729 					}
730 					else if(StringUtils.isNotEmpty(min) && StringUtils.isNotEmpty(max) && min.equals(max) && i==0){
731 						andedString = MessageFormat.format(mustOccursMsg, all) + "<br/>" +andedString;
732 					}
733 					else if(StringUtils.isNotEmpty(min) && StringUtils.isNotEmpty(max) && min.equals(max) && i!=0){
734 						//leave andedString as is
735 					}
736 					else if(StringUtils.isNotEmpty(min)){
737 						andedString = MessageFormat.format(mustOccursMsg, min) + "<br/>" +andedString;
738 					}
739 					else if(StringUtils.isNotEmpty(max)){
740 						andedString = MessageFormat.format(mustOccursMsg, atMost + " " + max) + "<br/>" +andedString;
741 					}
742 				}
743 				if(StringUtils.isNotEmpty(andedString)){
744 					if(i==0){
745 						statement = andedString;
746 					}
747 					else{
748 						statement = statement + or.toUpperCase() + andedString;
749 					}
750 				}
751 			}
752 			if(StringUtils.isNotEmpty(statement)){
753 				message = statement;
754 			}
755 		}
756 		
757 		return message;
758 	}
759 
760 	/**
761 	 * This method processes all the constraints on the InputField passed in and adds all the necessary
762 	 * jQuery and js required (validator's rules, methods, and messages) to the View's onDocumentReady call.
763 	 * The result is js that will validate all the constraints contained on an InputField during user interaction
764 	 * with the field using the jQuery validation plugin and custom code.
765 	 * 
766 	 * @param field
767 	 */
768 	@SuppressWarnings("boxing")
769 	public static void processAndApplyConstraints(InputField field, View view) {
770 		methodKey = 0;
771 		if ((field.getRequired() != null) && (field.getRequired().booleanValue())) {
772 			field.getControl().addStyleClass("required");
773 		}
774 
775 		if (field.getExclusiveMin() != null) {
776 			if (field.getControl() instanceof TextControl && ((TextControl) field.getControl()).getDatePicker() != null) {
777 				((TextControl) field.getControl()).getDatePicker().getComponentOptions().put("minDate", field.getExclusiveMin());
778 			}
779 			else{
780 				String rule = "jq('[name=\""+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').rules(\"add\", {\n minExclusive: ["+ field.getExclusiveMin() + "]});";
781 				addScriptToPage(view, field, rule);
782 			}
783 		}
784 
785 		if (field.getInclusiveMax() != null) {
786 			if (field.getControl() instanceof TextControl && ((TextControl) field.getControl()).getDatePicker() != null) {
787 				((TextControl) field.getControl()).getDatePicker().getComponentOptions().put("maxDate", field.getInclusiveMax());
788 			}
789 			else{
790 				String rule = "jq('[name=\""+ ScriptUtils.escapeName(field.getBindingInfo().getBindingPath()) + "\"]').rules(\"add\", {\n maxInclusive: ["+ field.getInclusiveMax() + "]});";
791 				addScriptToPage(view, field, rule);
792 			}
793 		}
794 
795 		if (field.getValidCharactersConstraint() != null && field.getValidCharactersConstraint().getApplyClientSide()) {
796 			if(StringUtils.isNotEmpty(field.getValidCharactersConstraint().getValue())) {
797 				// set regex value takes precedence
798 				addScriptToPage(view, field, ClientValidationUtils.getRegexMethod(field, field.getValidCharactersConstraint()));
799 				field.getControl().addStyleClass("validChar-" + field.getBindingInfo().getBindingPath()+ methodKey);
800 				methodKey++;
801 			}
802 			else {
803 				//blindly assume that if there is no regex value defined that there must be a method by this name
804 				if(StringUtils.isNotEmpty(field.getValidCharactersConstraint().getLabelKey())){
805 					field.getControl().addStyleClass(field.getValidCharactersConstraint().getLabelKey());
806 				}
807 			}
808 		}
809 
810 		if (field.getCaseConstraint() != null && field.getCaseConstraint().getApplyClientSide()) {
811 			processCaseConstraint(field, view, field.getCaseConstraint(), null);
812 		}
813 
814 		if (field.getDependencyConstraints() != null) {
815 			for (PrerequisiteConstraint prc : field.getDependencyConstraints()) {
816 				processPrerequisiteConstraint(field, prc, view);
817 			}
818 		}
819 
820 		if (field.getMustOccurConstraints() != null) {
821 			for (MustOccurConstraint mc : field.getMustOccurConstraints()) {
822 				processMustOccurConstraint(field, view, mc, "true");
823 			}
824 		}
825 		
826 	}
827 
828 }