001/** 002 * Copyright 2005-2015 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.uif.util; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.krad.datadictionary.state.StateMapping; 020import org.kuali.rice.krad.datadictionary.validation.constraint.BaseConstraint; 021import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint; 022import org.kuali.rice.krad.uif.view.View; 023 024import java.util.ArrayList; 025import java.util.List; 026 027/** 028 * Various utility methods for determining when to use constraints during states 029 * 030 * @author Kuali Rice Team (rice.collab@kuali.org) 031 *@since 2.2 032 */ 033public class ConstraintStateUtils { 034 035 /** 036 * Determines if the constraint passed in applies for the applicableState, based on the stateMapping 037 * 038 * <p>Note: this method will automatically return TRUE if the stateMapping is null, the Constraint is not a BaseConstraint, 039 * if there are no states defined on the Constraint, or if the state it applies to cannot be found in the stateMapping, 040 * because in all these cases the Constraint is considered 041 * stateless and will apply to any state</p> 042 * 043 * @param applicableState the state to check to see if the constraint applies 044 * @param constraint the Constraint to check 045 * @param stateMapping the StateMapping object containing state information 046 * @return true if the Constraint applies to the applicableState, false otherwise 047 */ 048 public static boolean constraintAppliesForState(String applicableState, Constraint constraint, 049 StateMapping stateMapping) { 050 List<String> stateOrder = new ArrayList<String>(); 051 if (stateMapping != null) { 052 stateOrder = stateMapping.getStates(); 053 } 054 055 if (stateMapping == null || !(constraint instanceof BaseConstraint) || StringUtils.isEmpty(applicableState)) { 056 //process constraint because it is considered "stateless" if not a BaseConstraint 057 //or no associated state mapping or no has no state to compare to 058 return true; 059 } else if (((BaseConstraint) constraint).getStates() == null || ((BaseConstraint) constraint).getStates() 060 .isEmpty()) { 061 //simple case - no states for this constraint, so always apply 062 return true; 063 } else if (((BaseConstraint) constraint).getStates().contains(applicableState) && stateOrder.contains( 064 applicableState)) { 065 //constraint applies for the applicableState and the state exists for the object 066 return true; 067 } else { 068 for (String constraintState : ((BaseConstraint) constraint).getStates()) { 069 //range case 070 if (constraintState.contains(">")) { 071 String[] rangeArray = constraintState.split(">"); 072 if (rangeArray[1].endsWith("+")) { 073 //make 2nd part of range current state being checked if nothing is 074 //matched below for the range case 075 constraintState = rangeArray[1]; 076 rangeArray[1] = StringUtils.removeEnd(rangeArray[1], "+"); 077 } 078 if (stateOrder.contains(rangeArray[0]) && stateOrder.contains(rangeArray[1])) { 079 for (int i = stateOrder.indexOf(rangeArray[0]); i <= stateOrder.indexOf(rangeArray[1]); i++) { 080 if (stateOrder.get(i).equals(applicableState)) { 081 return true; 082 } 083 } 084 } else { 085 throw new RuntimeException("Invalid state range: " + constraintState); 086 } 087 } 088 089 //+ case (everything after and including this state) 090 if (constraintState.contains("+")) { 091 constraintState = StringUtils.removeEnd(constraintState, "+"); 092 if (stateOrder.contains(constraintState)) { 093 for (int i = stateOrder.indexOf(constraintState); i < stateOrder.size(); i++) { 094 if (stateOrder.get(i).equals(applicableState)) { 095 return true; 096 } 097 } 098 } else { 099 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}