1 /**
2 * Copyright 2005-2014 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.kew.docsearch;
17
18 import org.apache.commons.lang.StringUtils;
19 import org.kuali.rice.core.api.search.SearchOperator;
20 import org.kuali.rice.core.framework.persistence.jdbc.sql.SQLUtils;
21
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24
25 /**
26 * Abstract base class for numeric searchable attributes.
27 *
28 * <p>Contains common logic for validation along with a template method for retrieving a validation Pattern.</p>
29 *
30 * @author Kuali Rice Team (rice.collab@kuali.org)
31 */
32 public abstract class SearchableAttributeNumericBase extends SearchableAttributeBase {
33
34 /**
35 * Returns a Pattern object used for validating the format of number Strings.
36 *
37 * <p>{@link Pattern}s are immutable and thus safe for concurrent use, so it makes sense to return
38 * a pre-compiled static instance.</p>
39 *
40 * <p>The pattern should only match valid String representations of the numeric type</p>
41 *
42 * @return the Pattern used for validating number Strings.
43 */
44 abstract protected Pattern getDefaultValidationPattern();
45
46 /**
47 * is the given value valid for searching against this attribute?
48 *
49 * <p>This method detects the binary operators defined by
50 * {@link org.kuali.rice.core.api.search.SearchOperator#BETWEEN},
51 * {@link org.kuali.rice.core.api.search.SearchOperator#AND}, and
52 * {@link org.kuali.rice.core.api.search.SearchOperator#OR} and validates their operands by recursing on them.
53 * It also strips off other valid numeric operators before parsing the leaf operands.
54 * </p>
55 *
56 * <p>A Pattern which is provided by the template method {@link #getDefaultValidationPattern()} is used for parsing
57 * the numeric strings themselves.</p>
58 *
59 * <p>Note that the parsing of expressions done here is very rudimentary, this method is mostly focused on
60 * validating that any operands are valid numeric strings for the attribute type.</p>
61 *
62 * @param valueEntered
63 * @return true if the valueEntered is considered valid
64 */
65 @Override
66 public boolean isPassesDefaultValidation(String valueEntered) {
67
68 boolean isValid = true;
69
70 if (StringUtils.contains(valueEntered, SearchOperator.AND.op())) {
71 isValid = isOperandsValid(valueEntered, SearchOperator.AND);
72 } else if (StringUtils.contains(valueEntered, SearchOperator.OR.op())) {
73 isValid = isOperandsValid(valueEntered, SearchOperator.OR);
74 } else if (StringUtils.contains(valueEntered, SearchOperator.BETWEEN.op())) {
75 isValid = isOperandsValid(valueEntered, SearchOperator.BETWEEN);
76 } else {
77 // default case is a plain old number, no splitting or recursion required
78
79 Pattern pattern = getDefaultValidationPattern();
80 Matcher matcher = pattern.matcher(SQLUtils.cleanNumericOfValidOperators(valueEntered).trim());
81
82 isValid = matcher.matches();
83 }
84
85 return isValid;
86 }
87
88 /**
89 * Tests that (if the given binaryOperator is present in the valueEntered) the operands are valid.
90 *
91 * <p>The operand test is done by calling isPassesDefaultValidation. If the binaryOperator is not present,
92 * true is returned.</p>
93 *
94 * @param valueEntered the string being validated
95 * @param binaryOperator the operator to test
96 * @return whether the operands are valid for the given binaryOperator
97 */
98 private boolean isOperandsValid(String valueEntered, SearchOperator binaryOperator) {
99 if (StringUtils.contains(valueEntered, binaryOperator.op())) {
100 // using this split method to make sure we test both sides of the operator. Using String.split would
101 // throw away empty strings, so e.g. "&&100".split("&&") would return an array with one element, ["100"].
102 String [] l = StringUtils.splitByWholeSeparatorPreserveAllTokens(valueEntered, binaryOperator.op());
103 for(String value : l) {
104 if (!isPassesDefaultValidation(value)) {
105 return false;
106 }
107 }
108 }
109
110 return true;
111 }
112 }