View Javadoc
1   /**
2    * Copyright 2005-2016 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 }