package gw.rating.flow.validation uses com.google.common.collect.Maps uses gw.api.database.Query uses gw.api.extension.ExtensionInterfaces uses gw.api.locale.DisplayKey uses gw.api.rating.MultiFactorVariable uses gw.api.rating.flow.ValidateSetByRateflow uses gw.lang.reflect.IType uses gw.lang.reflect.TypeSystem uses gw.pl.util.ArgCheck uses gw.plugin.Plugins uses gw.plugin.rateflow.IRateRoutinePlugin uses gw.rating.RatingValidationUtil uses gw.rating.flow.domain.CalcRoutine uses gw.rating.flow.util.CostDataWrapperUtil uses gw.rating.flow.util.InScopeUsage uses gw.rating.flow.util.QueryUtils uses gw.rating.flow.util.RateFlowReflection uses gw.rating.flow.util.RateflowTypeUtils uses gw.rating.flow.util.RatingEntityGraphTraverser uses gw.rating.flow.util.TypeMaps uses gw.validation.PCValidationBase uses gw.validation.PCValidationContext uses gw.validation.ValidationUtil uses java.text.SimpleDateFormat uses java.util.function.Function class RateRoutineValidation extends PCValidationBase { final var _calcRoutineDefinition : CalcRoutineDefinition as readonly Routine final var _plugin : IRateRoutinePlugin var _costDataWrapperType : IType final var _paramSet : CalcRoutineParameterSet var _steps: List var _operands: List var _arguments: List construct(valContext : PCValidationContext, definition : CalcRoutineDefinition) { super(valContext) ArgCheck.nonNull(definition, "definition"); _calcRoutineDefinition = definition _plugin = Plugins.get(IRateRoutinePlugin) _paramSet = Routine.ParameterSet if (_paramSet != null) { _costDataWrapperType = _plugin.getCostDataWrapperType(_paramSet) } preloadCalcRoutine() } private function preloadCalcRoutine() { var calcRoutineArray = {Routine} var steps = RatingValidationUtil.loadArrays(calcRoutineArray, "Steps") _steps = steps.orderBy(\step -> step.SortOrder) var operands = RatingValidationUtil.loadArrays(_steps, "Operands") _operands = operands.orderBy(\op -> op.OperandOrder) _arguments = RatingValidationUtil.loadArrays(_operands, "ArgumentSources") RatingValidationUtil.loadArrays(_operands, "ReturnFactorColumns") } function validate(group : ValidationGroup) { switch(group) { case PARAM_SET: validateParamSet() validateRateTableArguments() break case IMPORT: validateImportExport() break case EXPORT: validateImportExport() break case ALL: default: validateAll() } } function validateAll() { Context.addToVisited(this, "validateAll") // this validation is not included during import because the ratebook will not be in draft // when imported calcRoutineCannotBeUpdatedIfReferencedByNonDraftBook() validateImportExport() } function validateImportExport() { Context.addToVisited(this, "validateImportExport") // Validations that should prevent commit ("loadsave" validations) should go first. uniqueRateRoutineCodeAndVersion() validateRateRoutineStepSortOrders() validateRatingFunctionSourceMethods() validateParamSet() validateRateTableArguments() validateTypekeys() validateCostDataSet() validateLocalVariablesDeclaredBeforeUse() validateMultiFactorVariableAssignmentSteps() // Validations that should not prevent save (which is most of them for rate routines) should go here. // The checks that should not prevent save are done at "default" level or "quotable" level. // Create a new, appropriately named function for each validation, and insert the call below. if (Context.Level < ValidationLevel.TC_LOADSAVE) { // lower numbers are stronger validations addParseErrors() } } override protected function validateImpl() { Context.addToVisited(this, "validateImpl") validateAll() } function validateRateTableArguments() { Context.addToVisited(this, "validateRateTableArguments") if (_paramSet != null) { for (operand in _operands) { if (operand.OperandType == TC_RATETABLE) { validateRateTableDefaultArgumentSourceForOperand(operand) } } } } function validateRateTableOverrideConstantArgumentSource(operand: CalcStepDefinitionOperand) { for (arg in operand.ArgumentSources) { if (! (arg.OverrideSource and arg.IsEditableConstant)) { continue } var value = arg.ConstantValue if (value != null){ switch(arg.ColumnDataType) { case TC_DATE: var dateParser = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", new Locale("en","US")); try{ dateParser.parse(value) } catch (e: java.text.ParseException) { Result.addError(operand, TC_LOADSAVE, DisplayKey.get("Web.Rating.Errors.InvalidConstantValue", "Date", arg.Parameter)) } break case TC_DECIMAL: case TC_INTEGER: try{ Double.valueOf(value) } catch (e: java.lang.NumberFormatException) { Result.addError(operand, TC_LOADSAVE, DisplayKey.get("Web.Rating.Errors.InvalidConstantValue", "Numeric", arg.Parameter)) } break default : return } } } } function validateRateTableDefaultArgumentSourceForOperand(operand: CalcStepDefinitionOperand) { var argsWithUndefinedDefaultArgSource = new ArrayList() var argsWithNotReachableDefaultArgSource = new ArrayList() for (arg in operand.ArgumentSources.where(\ c -> not c.OverrideSource)) { var argSource = arg.ArgumentSourceFromTableDefs if (defaultArgSourceUndefined(argSource)) { argsWithUndefinedDefaultArgSource.add(arg) } else if (defaultArgSourceUnreachableFromRateRoutineParamSet(argSource)) { argsWithNotReachableDefaultArgSource.add(arg) } } if (!argsWithNotReachableDefaultArgSource.Empty) { var noParamSetDefinedArgs = new ArrayList() for (argument in argsWithNotReachableDefaultArgSource) { if (noParamSetCausesUnreachableDefaultArgSource(argument)) { noParamSetDefinedArgs.add(argument) } } //If param set is not defined for rate routine if (not noParamSetDefinedArgs.Empty) { //... add error message about arguments that have a default arg source defined // that cannot be reached since no param set is defined Result.addError(operand, TC_LOADSAVE, DisplayKey.get("Web.Rating.Errors.NoParamSetSpecifiedOrParameterRequiresArgumentSourceOverride", noParamSetDefinedArgs.map(\arg -> arg.Parameter))) } else { //... if rate routine param set is defined, add error message about arguments whose default arg source // cannot be reached from that param set Result.addError(operand, TC_LOADSAVE, DisplayKey.get("Web.Rating.Errors.DefaultArgumentSourceDefinedButImpossibleToReach", argsWithNotReachableDefaultArgSource.map(\arg -> arg.Parameter))) } } // Add error message for arguments without a default arg source defined if (!argsWithUndefinedDefaultArgSource.Empty) { Result.addError(operand, TC_LOADSAVE, DisplayKey.get("Web.Rating.Errors.DefaultArgumentSourceNotAvailable", argsWithUndefinedDefaultArgSource.map(\ arg -> arg.Parameter))) } } function noParamSetCausesUnreachableDefaultArgSource(arg: CalcStepDefinitionArgument): boolean { // no param set specified, but default argument source has a non-null root return Routine.ParameterSet == null and arg.ArgumentSourceFromTableDefs.Root != null } function defaultArgSourceUndefined(arg: CalcStepDefinitionArgument): boolean { return defaultArgSourceUndefined(arg.ArgumentSourceFromTableDefs) } private function defaultArgSourceUndefined(argSource: RateTableArgumentSource): boolean { // nothing provided for default argument source if (argSource == null) { return true } // default argument source is not null, is not defined, // and has a null root if (argSource.ArgumentSource == null and argSource.Root == null) { return true } return false } function defaultArgSourceUnreachableFromRateRoutineParamSet(arg: CalcStepDefinitionArgument): boolean { return defaultArgSourceUnreachableFromRateRoutineParamSet(arg.ArgumentSourceFromTableDefs) } private function defaultArgSourceUnreachableFromRateRoutineParamSet(argSource: RateTableArgumentSource): boolean { // no param set specified for rate routine, but default argument source has a non-null root if (Routine.ParameterSet == null and argSource.Root != null) { return true } // param set specified for rate routine, but none of the params match default argument source root if (Routine.ParameterSet != null and not Routine.ParameterSet.Parameters.hasMatch(\ param -> param.Code == argSource.Root)) { return true } return false } function validateParamSet() { Context.addToVisited(this, "validateParamSet") var paramSet = Routine.ParameterSet if (paramSet != null) { if ( paramSet.PolicyLinePatternCode != null and paramSet.PolicyLinePatternCode != Routine.PolicyLinePatternCode) { Result.addError(paramSet, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.PolicyLineMismatch")) } var traverser = new RatingEntityGraphTraverser(paramSet) var usages = traverser.getImmutableTargetableElementsForParameterSet(paramSet) var cache = Maps.newHashMapWithExpectedSize>(usages.size()) if (_costDataWrapperType != null) { // adding cost data param to usages map cache.put(TC_COSTDATA, CostDataWrapperUtil.getCostDataUsages(Routine.ParameterSet).partitionUniquely(\elt -> elt.Path)) } var missHandler : Function> = \ key -> usages.get(key)?.partitionUniquely(\elt -> elt.Path) var fetchAndCache : block(CalcRoutineParamName, String) : InScopeUsage = \ inScopeParam, inScopeValue -> cache.computeIfAbsent(inScopeParam, missHandler)?.get(inScopeValue) for (step in _steps) { validateCalcStepInScopeUsage(step, fetchAndCache(step.InScopeParam, step.InScopeValue)) } for (operand in _operands) { validateInScopeUsage(operand, fetchAndCache(operand.InScopeParam, operand.InScopeValue)) } for (argSrc in _arguments) { validateInScopeUsage(argSrc, fetchAndCache(argSrc.InScopeParam, argSrc.InScopeValue)) } checkParameterSetChangedForNewerVersion() } } function validateCostDataSet() { Context.addToVisited(this, "validateCostDataSet") var paramSet = Routine.ParameterSet if (paramSet == null or _costDataWrapperType == null) { return } var requiredCostDataFields = findRequiredCostDataFields() var costDataTargets = _steps.where(\step -> step.InScopeParam == TC_COSTDATA).map(\step -> step.InScopeValue) for (field in requiredCostDataFields) { if (not costDataTargets.contains(field)) { Result.addWarning(_calcRoutineDefinition, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.CostDataFieldNotAssigned", field)) } } } function validateMultiFactorVariableAssignmentSteps() { Context.addToVisited(this, "validateMultiFactorVariableAssignmentSteps") var storeLocationToStepsListMap = _steps .where( \ step -> step.StepType == TC_ASSIGNMENT and step.StoreLocation != null) .partition( \ step -> step.StoreLocation) .filterByValues( \ steps -> steps.Count > 1) //Capture all the steps that are assignments and IN_SCOPE storeLocationToStepsListMap.putAll( _steps .where( \ step -> step.StepType == TC_ASSIGNMENT and step.InScopeValue != null) .partition( \ step -> step.InScopeValue) .filterByValues( \ steps -> steps.Count > 1)) storeLocationToStepsListMap.eachValue( \ steps -> { for (step in steps){ final var primaryOperand = step.PrimaryOperand if (primaryOperand.IsMultiFactorRateTableLookup or primaryOperand.IsMultiFactorVariable or step.StoreType == MultiFactorVariable.Type.Name) { validateMultiFactorVariableIsReassignedWithDifferentRateTableLookup(steps.subtract({step}), step, primaryOperand) } } }) } /** * Adds a validation message if the multi-factor variable in the given assignment step is being reassigned * with a different rate table look-up in another assignment step within this rate routine. */ private function validateMultiFactorVariableIsReassignedWithDifferentRateTableLookup(varReassignmentSteps: Set, multiFactorVarAssignmentStep: CalcStepDefinition, multiFactorRateTableOperand: entity.CalcStepDefinitionOperand) { if (varReassignmentSteps.hasMatch(\step -> step.SortOrder != multiFactorVarAssignmentStep.SortOrder and ( (step.InScopeValue == multiFactorVarAssignmentStep.InScopeValue) and not isReturnTypeEqual(step, multiFactorVarAssignmentStep)))) { var stepName = multiFactorVarAssignmentStep.StoreLocation != null ? multiFactorVarAssignmentStep.StoreLocation : multiFactorVarAssignmentStep.InScopeValue Result.addError(multiFactorVarAssignmentStep, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.MultiFactorVariableReassigned", stepName)) } } private function isReturnTypeEqual(assignmentStep: CalcStepDefinition, rateTableAssignmentStep: CalcStepDefinition ) : boolean { var step1ReturnTypes = getRateFlowReturnTypes(assignmentStep) var step2ReturnTypes = getRateFlowReturnTypes( rateTableAssignmentStep) if (step1ReturnTypes.Count != step2ReturnTypes.Count) { return false } var areTypesEqual = true step1ReturnTypes.eachKeyAndValue( \ k, val -> { if (not step2ReturnTypes.containsKey(k) or not TypeMaps.isComparable(val, step2ReturnTypes.get(k))) { areTypesEqual = false } }) return areTypesEqual } private function getRateFlowReturnTypes(step: CalcStepDefinition) : Map { var rtdAssignmentStep = step var rtdAssignmentStepPrimaryOperand = rtdAssignmentStep.PrimaryOperand var rtd = rtdAssignmentStepPrimaryOperand.RateTableDefinition /* * This while loop finds the first step where the variable is declared * If the rate table definition is null we will keep searching for the step that has the rate table definition * If it isn't a local var or an inscope it will most likely be a constant */ while (rtd == null) { if (rtdAssignmentStepPrimaryOperand.OperandType == TC_LOCALVAR) { rtdAssignmentStep = rtdAssignmentStep.CalcRoutineDefinition.FilteredOrderedSteps(\step2 ->step2.StoreLocation == rtdAssignmentStepPrimaryOperand.VariableName) .first() } else if (rtdAssignmentStepPrimaryOperand.OperandType == TC_INSCOPE) { rtdAssignmentStep = rtdAssignmentStep.CalcRoutineDefinition.FilteredOrderedSteps(\step2 ->step2.InScopeValue == rtdAssignmentStepPrimaryOperand.InScopeValue) .first() } else { return {"" -> TypeMaps.typeStringToRateFlowType(rtdAssignmentStepPrimaryOperand.InScopeValueType)} } rtdAssignmentStepPrimaryOperand = rtdAssignmentStep.PrimaryOperand rtd = rtdAssignmentStepPrimaryOperand.RateTableDefinition } /* * We have found the step where the look-up was defined. * If it is a multi-factor variable get all the factor columns and ITypes and return them else * just return the IType of the single factor column */ final var returnFactorColumnNames = rtdAssignmentStepPrimaryOperand.ReturnFactorColumns*.ColumnName.toList() var factors = rtd.SortedFactors.where(\factor -> returnFactorColumnNames.contains(factor.ColumnName)) var factorTypeMap : Map = {} if (factors.Count == 1) { factorTypeMap.put("", rtd.getFactorIType(factors[0].ColumnName)) } else { for (elt in factors) { factorTypeMap.put(elt.ColumnName, rtd.getFactorIType(elt.ColumnName)) } } return factorTypeMap } function validateLocalVariablesDeclaredBeforeUse() { Context.addToVisited(this, "validateLocalVariablesDeclaredBeforeUse") var whereFirstDeclared: Map = {null -> -1, "null" -> -1} // variableName null/"null" should not generate message for (step in _steps) { whereFirstDeclared.putIfAbsent(step.StoreLocation, step.SortOrder) } for (step in _steps) { for (operand in step.OrderedOperands) { if (operand.OperandType == TC_LOCALVAR) { validateVariableAlreadyDeclared(operand.VariableName, step, whereFirstDeclared.get(operand.VariableName)) } } } } private function validateVariableAlreadyDeclared(variableName: String, stepUsesVar: CalcStepDefinition, stepDeclaresVar : Integer) { if (stepDeclaresVar == null || stepDeclaresVar >= stepUsesVar.SortOrder) { Result.addError(stepUsesVar, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.VariableUsedBeforeDeclared", variableName)) } } function validateTypekeys() { Context.addToVisited(this, "validateTypekeys") var argumentsForPrimaryOperands = _steps*.ArgumentSources for (argSrc in argumentsForPrimaryOperands) { validateTypekey(argSrc) } for (operand in _operands) { validateTypekey(operand) } } private function validateTypekey(valueDelegate : CalcStepValueDelegate) { if(valueDelegate.TypeDeclaration == null) { return } if (valueDelegate.TypeDeclaration.startsWith(ExtensionInterfaces.IRatingUIUtilExtensions.TypekeyPrefix) and not valueDelegate.IsTypeKey) { Result.addError(valueDelegate.OwningStep, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.InvalidTypelist", valueDelegate.TypeDeclaration)) } // TypekeyConstantValue will try to convert ConstantValue to a typekey, and return null if it fails. if (valueDelegate.IsTypeKey and valueDelegate.ConstantValue != null and valueDelegate.TypekeyConstantValue == null) { Result.addError(valueDelegate.OwningStep, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.InvalidTypekey", valueDelegate.ConstantValue, valueDelegate.TypeDeclaration)) } } private function validateCalcStepInScopeUsage(step : CalcStepDefinition, usage : InScopeUsage) { if (step.InScopeParam == null) { return } if (usage == null) { Result.addError(step, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.UnreachableInScopeUsage", step.StoreLocationForDisplay)) } else { // Handle parametrized types. var usageType = RateflowTypeUtils.parseType(usage.FeatureType.Name) var storeType = RateflowTypeUtils.parseType(step.StoreType) if (usageType != storeType) { if (usageType != null and storeType != null and not gw.api.util.TypeUtil.isNominallyOrStructurallyAssignable(storeType, usageType)) { Result.addWarning(step, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.InScopeUsageTypeChanged", step.StoreLocationForDisplay), "") } } } } function validateCalcStepInScopeUsage(step : CalcStepDefinition, usages : Map>) { validateCalcStepInScopeUsage(step, usages.get(step.InScopeParam)?.get(step.InScopeValue)) } private function validateInScopeUsage(valueDelegate : CalcStepValueDelegate, usage : InScopeUsage) { if (valueDelegate.InScopeParam == null) { return } if (usage == null) { Result.addError(valueDelegate.OwningStep, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.UnreachableInScopeUsage", valueDelegate.StepValueDisplayName)) } else { var usageType = TypeMaps.parseType(usage.FeatureType.Name) var valueDelegateType = TypeMaps.parseType(valueDelegate.InScopeValueType) if (usageType != valueDelegateType) { if (valueDelegateType != null and usageType != null and not gw.api.util.TypeUtil.isNominallyOrStructurallyAssignable(valueDelegateType, usageType)) { Result.addWarning(valueDelegate.OwningStep, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.InScopeUsageTypeChanged", valueDelegate.StepValueDisplayName), "") } } } } function validateInScopeUsage(valueDelegate : CalcStepValueDelegate, usages : Map>) { validateInScopeUsage(valueDelegate, usages.get(valueDelegate.InScopeParam)?.get(valueDelegate.InScopeValue)) } function addParseErrors() { Context.addToVisited(this, "addParseErrors") CalcRoutine.create(Routine, Context) } function validateRatingFunctionSourceMethods() { Context.addToVisited(this, "validateRatingFunctionSourceMethods") var expectedReturnType : String for (step in _steps) { if (step.StoreLocation != null or step.StepType.hasCategory(CalcStepCategory.TC_FLOWCONTROL)) { expectedReturnType = null //reset because we found a new assignment or a new flow control statement } if (step.StoreType != null) { expectedReturnType = step.StoreType } else if (step.IsTermAmountAssignment) { expectedReturnType = "java.math.BigDecimal" } for (operand in step.Operands) { if (operand.OperandType == TC_RATEFUNC) { var methodInfo = RateFlowReflection.getMethodInfoForMethod(Routine.PolicyLinePatternCode, operand.FunctionName) if (methodInfo == null) { Result.addError(operand, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.MissingRateFunction", operand.FunctionName), "") } else { var operandType = operand.TypeDeclaration if ( (expectedReturnType != null and not TypeMaps.isAssignable(TypeSystem.getByFullName(expectedReturnType), methodInfo.ReturnType)) or (operandType != null and operandType != methodInfo.ReturnType.Name) ) { Result.addError(operand, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.RateFunctionReturnTypeMismatch", methodInfo.Name, methodInfo.ReturnType), "") } } } } } } function validateRateRoutineStepSortOrders() { Context.addToVisited(this, "validateRateRoutineStepSortOrders") _steps.eachWithIndex(\ step, i -> { if (step.SortOrder != i + 1) { Result.addError(_calcRoutineDefinition, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.OutOfSequenceRateRoutineSteps")) } }) } function uniqueRateRoutineCodeAndVersion() { Context.addToVisited(this, "uniqueRateRoutineCodeAndVersion") var q = Query.make(CalcRoutineDefinition) for (column in CalcRoutineDefinition.ColumnsInUniqueIndex) { q.compare(column, Equals, _calcRoutineDefinition.getFieldValue(column)) } for (var crd in q.select()) { if (crd.ID != _calcRoutineDefinition.ID) { Result.addError(_calcRoutineDefinition, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.DuplicateRateRoutineCode", CalcRoutineDefinition.ColumnsInUniqueIndex.join(", "))) return } else if (crd.isIncludedInPromotedRateBook() and _calcRoutineDefinition.Changed) { Result.addError(_calcRoutineDefinition, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.RoutineInUseAndHasDifferentSteps", _calcRoutineDefinition.Code, _calcRoutineDefinition.Version, _calcRoutineDefinition.BranchingFieldValues.join(", "))) return } } } function calcRoutineCannotBeUpdatedIfReferencedByNonDraftBook() { Context.addToVisited(this, "calcRoutineCannotBeUpdatedIfReferencedByNonDraftBook") if(_calcRoutineDefinition.isIncludedInPromotedRateBook()){ Result.addError(_calcRoutineDefinition, TC_LOADSAVE, DisplayKey.get("Validation.Rating.RateRoutineDefinition.ReferencedByPromotedRateBook")) return } } function checkParameterSetChangedForNewerVersion() { var previousVersion = QueryUtils.getPreviousCalcRoutineVersion(Routine) if (previousVersion != null) { var matchingParameters = Routine.ParameterSet.Parameters.where( \ elt -> previousVersion.ParameterSet.Parameters*.Code.contains(elt.Code)).toList() var addedParameters = Routine.ParameterSet.Parameters.where( \ elt -> not matchingParameters*.Code.contains(elt.Code)).toList() var removedParameters = previousVersion.ParameterSet.Parameters.where( \ elt -> not matchingParameters*.Code.contains(elt.Code)).toList() var changedParameters = matchingParameters.where( \ elt -> not previousVersion.ParameterSet.Parameters*.ParamType.contains(elt.ParamType)).toList() if (addedParameters.HasElements) { Result.addWarning(Routine, TC_LOADSAVE, DisplayKey.get("Web.Rating.Warnings.NewRateRoutineVersionAddedParameters", Routine.Code, addedParameters.ParameterSetForUIMessage)) } if (removedParameters.HasElements) { Result.addWarning(Routine, TC_LOADSAVE, DisplayKey.get("Web.Rating.Warnings.NewRateRoutineVersionRemovedParameters", Routine.Code, removedParameters.ParameterSetForUIMessage)) } if (changedParameters.HasElements) { Result.addWarning(Routine, TC_LOADSAVE, DisplayKey.get("Web.Rating.Warnings.NewRateRoutineVersionChangedParameters", Routine.Code, changedParameters.ParameterSetForUIMessage)) } } } static function validateRateRoutineDefinition(definition : CalcRoutineDefinition, level : ValidationLevel, group : ValidationGroup) { PCValidationContext.doPageLevelValidation(\ context -> { var validation = new RateRoutineValidation(context, definition) validation.validate(group) }, level) } static function validateRateTableArgumentsForOperand(definition : CalcRoutineDefinition, operand: CalcStepDefinitionOperand, level : ValidationLevel) { var context = ValidationUtil.createContext(level) var validation = new RateRoutineValidation(context, definition) validation.validateRateTableDefaultArgumentSourceForOperand(operand) validation.validateRateTableOverrideConstantArgumentSource(operand) context.raiseExceptionIfProblemsFound() } static function validateForRateRoutineEditScreen(definition : CalcRoutineDefinition, level : ValidationLevel, group : ValidationGroup, calcStepDefinitionsAndErrorNums : Map>) { var messageNumberer = new MessageNumberer() calcStepDefinitionsAndErrorNums.clear() var context = new PCValidationContext(level) var validation = new RateRoutineValidation(context, definition) validation.validate(group) // now go through the context and find the rows that need to be highlighted var bundle = gw.transaction.Transaction.getCurrent() for (e in context.Result.EntityValidations) { for (v in e.ValidationIssues) { if (v typeis gw.api.validation.FieldValidation) { // shouldn't be using these. (If they worked properly, we wouldn't be doing row highlighting.) v.Reason = "FIELD VALIDATION FOUND. Change to row validation. (Was: ${v.Reason})" } var bean = bundle.loadBean(v.EntityValidation.EntityId) switch (typeof bean) { case CalcStepDefinition: var cnt = messageNumberer.getMessageNumber(v.Reason) putOrAddToList(calcStepDefinitionsAndErrorNums, bean, cnt) v.Reason = messageNumberer.getMessageNumber(v.Reason) + ": " + v.Reason break case CalcStepDefinitionOperand: var cnt = messageNumberer.getMessageNumber(v.Reason) putOrAddToList(calcStepDefinitionsAndErrorNums, bean.CalcStep, cnt) v.Reason = messageNumberer.getMessageNumber(v.Reason) + ": " + v.Reason break case CalcStepDefinitionArgument: var cnt = messageNumberer.getMessageNumber(v.Reason) putOrAddToList(calcStepDefinitionsAndErrorNums, bean.CalcStepDefinition, cnt) v.Reason = messageNumberer.getMessageNumber(v.Reason) + ": " + v.Reason break case CalcRoutineDefinition: //When the entity that has the issue is CalcRoutineDefinition, there is no need to prefix reason with error number, and //there is no calc step to associate the error with break default: throw new IllegalStateException("Unknown bean type:" + typeof bean) } } } context.raiseExceptionIfProblemsFound() // need to do this so that the worksheet shows up } private static function putOrAddToList(map: java.util.Map>, key: CalcStepDefinition, value: Integer) { if (map.containsKey(key)) { var existingValues = map.get(key) if (not existingValues.contains(value)) { existingValues.add(value) } } else { map.put(key, {value}) } } private function findRequiredCostDataFields() : Set { return _costDataWrapperType.TypeInfo.Properties.where(\prop -> { // typeis check will only succeed if annotation != null var anntn = prop.getAnnotation(ValidateSetByRateflow) if (anntn.Instance typeis ValidateSetByRateflow) { return anntn.Instance.mustBeSet() } else { return false } }).map(\propInfo ->propInfo.Name).toSet() } static class MessageNumberer { var messageMap : Map = {} // unnumbered -> numbered function getMessageNumber(msg : String) : Integer { var m = messageMap.get(msg) if (m == null) { var msgNum = messageMap.size() + 1 m = msgNum messageMap.put(msg, m) } return m } } }