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.krad.uif.util;
17
18 import org.apache.commons.lang.StringUtils;
19 import org.kuali.rice.krad.datadictionary.state.StateMapping;
20 import org.kuali.rice.krad.datadictionary.validation.constraint.BaseConstraint;
21 import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint;
22 import org.kuali.rice.krad.uif.view.View;
23
24 import java.util.ArrayList;
25 import java.util.List;
26
27 /**
28 * Various utility methods for determining when to use constraints during states
29 *
30 * @author Kuali Rice Team (rice.collab@kuali.org)
31 *@since 2.2
32 */
33 public class ConstraintStateUtils {
34
35 /**
36 * Determines if the constraint passed in applies for the applicableState, based on the stateMapping
37 *
38 * <p>Note: this method will automatically return TRUE if the stateMapping is null, the Constraint is not a BaseConstraint,
39 * if there are no states defined on the Constraint, or if the state it applies to cannot be found in the stateMapping,
40 * because in all these cases the Constraint is considered
41 * stateless and will apply to any state</p>
42 *
43 * @param applicableState the state to check to see if the constraint applies
44 * @param constraint the Constraint to check
45 * @param stateMapping the StateMapping object containing state information
46 * @return true if the Constraint applies to the applicableState, false otherwise
47 */
48 public static boolean constraintAppliesForState(String applicableState, Constraint constraint,
49 StateMapping stateMapping) {
50 List<String> stateOrder = new ArrayList<String>();
51 if (stateMapping != null) {
52 stateOrder = stateMapping.getStates();
53 }
54
55 if (stateMapping == null || !(constraint instanceof BaseConstraint) || StringUtils.isEmpty(applicableState)) {
56 //process constraint because it is considered "stateless" if not a BaseConstraint
57 //or no associated state mapping or no has no state to compare to
58 return true;
59 } else if (((BaseConstraint) constraint).getStates() == null || ((BaseConstraint) constraint).getStates()
60 .isEmpty()) {
61 //simple case - no states for this constraint, so always apply
62 return true;
63 } else if (((BaseConstraint) constraint).getStates().contains(applicableState) && stateOrder.contains(
64 applicableState)) {
65 //constraint applies for the applicableState and the state exists for the object
66 return true;
67 } else {
68 for (String constraintState : ((BaseConstraint) constraint).getStates()) {
69 //range case
70 if (constraintState.contains(">")) {
71 String[] rangeArray = constraintState.split(">");
72 if (rangeArray[1].endsWith("+")) {
73 //make 2nd part of range current state being checked if nothing is
74 //matched below for the range case
75 constraintState = rangeArray[1];
76 rangeArray[1] = StringUtils.removeEnd(rangeArray[1], "+");
77 }
78 if (stateOrder.contains(rangeArray[0]) && stateOrder.contains(rangeArray[1])) {
79 for (int i = stateOrder.indexOf(rangeArray[0]); i <= stateOrder.indexOf(rangeArray[1]); i++) {
80 if (stateOrder.get(i).equals(applicableState)) {
81 return true;
82 }
83 }
84 } else {
85 throw new RuntimeException("Invalid state range: " + constraintState);
86 }
87 }
88
89 //+ case (everything after and including this state)
90 if (constraintState.contains("+")) {
91 constraintState = StringUtils.removeEnd(constraintState, "+");
92 if (stateOrder.contains(constraintState)) {
93 for (int i = stateOrder.indexOf(constraintState); i < stateOrder.size(); i++) {
94 if (stateOrder.get(i).equals(applicableState)) {
95 return true;
96 }
97 }
98 } else {
99 throw new RuntimeException("Invalid constraint state: " + constraintState);
100 }
101 }
102 }
103 }
104 //if no case is matched, return false
105 return false;
106 }
107
108 /**
109 * Gets the constraint that applies for the validationState passed in with the appropriate StateMapping (which
110 * should include validationState as one of its states).
111 *
112 * <p>This method will essentially return the constraint
113 * passed in, in most cases, if the constraint applies. In cases where there are constraintStateOverrides
114 * set for the constraint, those will be evaluated to get the appropriate replacement. If the constraint does
115 * not apply for the validationState (as well as none of the replacements), this method will return null.
116 * If stateMapping passed in is null, the constraint is not a BaseConstraint, or validationState is blank,
117 * the original constraint will be returned
118 * (assumed stateless).</p>
119 *
120 * @param constraint the original constraint
121 * @param validationState the validation state
122 * @param stateMapping the state information for the model being evaluated
123 * @param <T> constraint type
124 * @return the applicable constraint, null if this constraint does not apply to the validationState
125 * @throws RuntimeException if the type of constraint passed in cannot be cast to the replacement constraint
126 */
127 public static <T extends Constraint> T getApplicableConstraint(T constraint, String validationState,
128 StateMapping stateMapping) {
129
130 //is state information setup?
131 if (constraint != null && constraint instanceof BaseConstraint && stateMapping != null &&
132 StringUtils.isNotBlank(validationState)) {
133
134 //Does the constraint have overrides?
135 if (((BaseConstraint) constraint).getConstraintStateOverrides() != null && !((BaseConstraint) constraint)
136 .getConstraintStateOverrides().isEmpty()) {
137 T override = null;
138 BaseConstraint theConstraint = ((BaseConstraint) constraint);
139 for (BaseConstraint bc : theConstraint.getConstraintStateOverrides()) {
140 //does the override apply for this state?
141 if (!bc.getStates().isEmpty() && ConstraintStateUtils.constraintAppliesForState(validationState, bc,
142 stateMapping)) {
143 try {
144 //Last on the list takes precedence
145 override = (T) bc;
146 } catch (ClassCastException e) {
147 throw new RuntimeException("Replacement state constraint for this constraint is not an "
148 + "appropriate type: "
149 + constraint.getClass().toString()
150 + " cannot be cast to "
151 + bc.getClass().toString());
152 }
153 }
154 }
155
156 if(override != null){
157 return override;
158 }
159 else if(override == null && ConstraintStateUtils.constraintAppliesForState(validationState, constraint, stateMapping)){
160 //use base constraint if no overrides apply and it still applies for this state
161 return constraint;
162 }
163 else{
164 //the constaint AND its overrides do not apply
165 return null;
166 }
167 } else if(ConstraintStateUtils.constraintAppliesForState(validationState, constraint, stateMapping)) {
168 //Constraint applies for this state
169 return constraint;
170 }
171 else{
172 //Constraint does not apply for this state
173 return null;
174 }
175 }
176
177 //state information either not setup or not setup correctly for this constraint/stateMapping,
178 //so constraint will apply by default
179 return constraint;
180 }
181
182 /**
183 * Gets the client validation state. If there are customClientSideValidationStates configured for the view's
184 * stateMapping, these are used, otherwise client side validation state is assumed to be the next state (or
185 * current state if there is no next state). Returns null if there is no state for client side validation (ie
186 * stateless).
187 *
188 * @param model
189 * @param view
190 * @return the state to validate against for client side view validation
191 */
192 public static String getClientViewValidationState(Object model, View view){
193 String validationState = null;
194 String path = view.getStateObjectBindingPath();
195 Object stateObject;
196
197 if (StringUtils.isNotBlank(path)) {
198 stateObject = ObjectPropertyUtils.getPropertyValue(model, path);
199 } else {
200 stateObject = model;
201 }
202 StateMapping stateMapping = view.getStateMapping();
203 if (stateMapping != null) {
204 validationState = stateMapping.getNextState(stateObject);
205
206 if (stateMapping.getCustomClientSideValidationStates() != null) {
207 String currentState = stateMapping.getCurrentState(stateObject);
208 validationState = stateMapping.getCustomClientSideValidationStates().get(currentState);
209 if (StringUtils.isBlank(validationState)) {
210 validationState = stateMapping.getNextState(stateObject);
211 }
212 }
213 }
214 return validationState;
215 }
216 }