001/** 002 * Copyright 2005-2016 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.krms.impl.provider.repository; 017 018import org.apache.commons.collections.CollectionUtils; 019import org.apache.commons.lang.StringUtils; 020import org.kuali.rice.krms.api.engine.Term; 021import org.kuali.rice.krms.api.engine.expression.ComparisonOperatorService; 022import org.kuali.rice.krms.api.repository.RepositoryDataException; 023import org.kuali.rice.krms.api.repository.function.FunctionDefinition; 024import org.kuali.rice.krms.api.repository.function.FunctionParameterDefinition; 025import org.kuali.rice.krms.api.repository.function.FunctionRepositoryService; 026import org.kuali.rice.krms.api.repository.proposition.PropositionDefinition; 027import org.kuali.rice.krms.api.repository.proposition.PropositionParameter; 028import org.kuali.rice.krms.api.repository.proposition.PropositionParameterType; 029import org.kuali.rice.krms.api.repository.term.TermDefinition; 030import org.kuali.rice.krms.api.repository.term.TermParameterDefinition; 031import org.kuali.rice.krms.api.repository.term.TermRepositoryService; 032import org.kuali.rice.krms.api.repository.term.TermSpecificationDefinition; 033import org.kuali.rice.krms.framework.engine.Function; 034import org.kuali.rice.krms.framework.engine.Proposition; 035import org.kuali.rice.krms.framework.engine.expression.BinaryOperatorExpression; 036import org.kuali.rice.krms.framework.engine.expression.BooleanValidatingExpression; 037import org.kuali.rice.krms.framework.engine.expression.ComparisonOperator; 038import org.kuali.rice.krms.framework.engine.expression.ConstantExpression; 039import org.kuali.rice.krms.framework.engine.expression.Expression; 040import org.kuali.rice.krms.framework.engine.expression.ExpressionBasedProposition; 041import org.kuali.rice.krms.framework.engine.expression.FunctionExpression; 042import org.kuali.rice.krms.framework.engine.expression.TermExpression; 043import org.kuali.rice.krms.framework.type.FunctionTypeService; 044import org.kuali.rice.krms.framework.type.PropositionTypeService; 045import org.kuali.rice.krms.impl.type.KrmsTypeResolver; 046 047import java.util.ArrayList; 048import java.util.LinkedList; 049import java.util.List; 050import java.util.Map; 051import java.util.TreeMap; 052 053/** 054 * A default implementation of {@link PropositionTypeService} for propositions 055 * which are composed of terms, operators, and functions. A simple proposition 056 * is self-contained and has no compound "sub" propositions. However, it's 057 * behavior is defined by the set of parameters on the {@link PropositionDefinition}. 058 * 059 * @author Kuali Rice Team (rice.collab@kuali.org) 060 * 061 */ 062public class SimplePropositionTypeService implements PropositionTypeService { 063 064 private FunctionRepositoryService functionRepositoryService; 065 private TermRepositoryService termRepositoryService; 066 private KrmsTypeResolver typeResolver; 067 private ComparisonOperatorService comparisonOperatorService; 068 069 @Override 070 public Proposition loadProposition(PropositionDefinition propositionDefinition) { 071 return new ExpressionBasedProposition(translateToExpression(propositionDefinition)); 072 } 073 074 /** 075 * Translates the parameters on the given proposition definition to create an expression for evaluation. 076 * The proposition parameters are defined in a reverse-polish notation so a stack is used for 077 * evaluation purposes. 078 * 079 * @param propositionDefinition the proposition definition to translate 080 * 081 * @return the translated expression for the given proposition, this 082 * expression, when evaluated, will return a Boolean. 083 */ 084 protected Expression<Boolean> translateToExpression(PropositionDefinition propositionDefinition) { 085 LinkedList<Expression<? extends Object>> stack = new LinkedList<Expression<? extends Object>>(); 086 for (PropositionParameter parameter : propositionDefinition.getParameters()) { 087 PropositionParameterType parameterType = PropositionParameterType.fromCode(parameter.getParameterType()); 088 if (parameterType == PropositionParameterType.CONSTANT) { 089 // TODO - need some way to define data type on the prop parameter as well? Not all constants will actually be String values!!! 090 stack.addFirst(new ConstantExpression<String>(parameter.getValue())); 091 } else if (parameterType == PropositionParameterType.FUNCTION) { 092 String functionId = parameter.getValue(); 093 FunctionDefinition functionDefinition = functionRepositoryService.getFunction(functionId); 094 if (functionDefinition == null) { 095 throw new RepositoryDataException("Unable to locate function with the given id: " + functionId); 096 } 097 FunctionTypeService functionTypeService = typeResolver.getFunctionTypeService(functionDefinition); 098 Function function = functionTypeService.loadFunction(functionDefinition); 099 // TODO throw an exception if function is null? 100 List<FunctionParameterDefinition> parameters = functionDefinition.getParameters(); 101 if (stack.size() < parameters.size()) { 102 throw new RepositoryDataException("Failed to initialize custom function '" + functionDefinition.getNamespace() + " " + functionDefinition.getName() + 103 "'. There were only " + stack.size() + " values on the stack but function requires at least " + parameters.size()); 104 } 105 List<Expression<? extends Object>> arguments = new ArrayList<Expression<? extends Object>>(); 106 // work backward through the list to match params to the stack 107 for (int index = parameters.size() - 1; index >= 0; index--) { 108 FunctionParameterDefinition parameterDefinition = parameters.get(index); 109 // TODO need to check types here? expression object probably needs a getType on it so that we can confirm that the types will be compatible? 110 parameterDefinition.getParameterType(); 111 Expression<? extends Object> argument = stack.removeFirst(); 112 arguments.add(argument); 113 } 114 115 String[] parameterTypes = getFunctionParameterTypes(functionDefinition); 116 stack.addFirst(new FunctionExpression(function, parameterTypes, arguments, getComparisonOperatorService())); 117 118 } else if (parameterType == PropositionParameterType.OPERATOR) { 119 ComparisonOperator operator = ComparisonOperator.fromCode(parameter.getValue()); 120 if (stack.size() < 2) { 121 throw new RepositoryDataException("Failed to initialize expression for comparison operator " + 122 operator + " because a sufficient number of arguments was not available on the stack. " 123 + "Current contents of stack: " + stack.toString()); 124 } 125 Expression<? extends Object> rhs = stack.removeFirst(); 126 Expression<? extends Object> lhs = stack.removeFirst(); 127 stack.addFirst(new BinaryOperatorExpression(operator, lhs, rhs)); 128 } else if (parameterType == PropositionParameterType.TERM) { 129 String termId = parameter.getValue(); 130 131 TermDefinition termDefinition = getTermRepositoryService().getTerm(termId); 132 if (termDefinition == null) { throw new RepositoryDataException("unable to load term with id " + termId);} 133 Term term = translateTermDefinition(termDefinition); 134 135 stack.addFirst(new TermExpression(term)); 136 } 137 } 138 if (stack.size() != 1) { 139 throw new RepositoryDataException("Final contents of expression stack are incorrect, there should only be one entry but was " + stack.size() +". Current contents of stack: " + stack.toString()); 140 } 141 return new BooleanValidatingExpression(stack.removeFirst()); 142 } 143 144 private String[] getFunctionParameterTypes(FunctionDefinition functionDefinition) { 145 String [] argumentTypes = null; 146 List<FunctionParameterDefinition> functionParameters = functionDefinition.getParameters(); 147 if (!CollectionUtils.isEmpty(functionParameters)) { 148 argumentTypes = new String[functionParameters.size()]; 149 150 int argTypesIndex = 0; 151 for (FunctionParameterDefinition functionParameter : functionParameters) { 152 argumentTypes[argTypesIndex] = functionParameter.getParameterType(); 153 argTypesIndex += 1; 154 } 155 } 156 return argumentTypes; 157 } 158 159 protected Term translateTermDefinition(TermDefinition termDefinition) { 160 if (termDefinition == null) { 161 throw new RepositoryDataException("Given TermDefinition is null"); 162 } 163 TermSpecificationDefinition termSpecificationDefinition = termDefinition.getSpecification(); 164 if (termSpecificationDefinition == null) { throw new RepositoryDataException("term with id " + termDefinition.getId() + " has a null specification"); } 165 166 List<TermParameterDefinition> params = termDefinition.getParameters(); 167 Map<String,String> paramsMap = new TreeMap<String,String>(); 168 if (!CollectionUtils.isEmpty(params)) for (TermParameterDefinition param : params) { 169 if (StringUtils.isBlank(param.getName())) { 170 throw new RepositoryDataException("TermParameterDefinition.name may not be blank"); 171 } 172 paramsMap.put(param.getName(), param.getValue()); 173 } 174 175 return new Term(termSpecificationDefinition.getName(), paramsMap); 176 } 177 178 public void setFunctionRepositoryService(FunctionRepositoryService functionRepositoryService) { 179 this.functionRepositoryService = functionRepositoryService; 180 } 181 182 public void setTypeResolver(KrmsTypeResolver typeResolver) { 183 this.typeResolver = typeResolver; 184 } 185 186 public ComparisonOperatorService getComparisonOperatorService() { 187 return comparisonOperatorService; 188 } 189 190 public void setComparisonOperatorService(ComparisonOperatorService comparisonOperatorService) { 191 this.comparisonOperatorService = comparisonOperatorService; 192 } 193 194 public TermRepositoryService getTermRepositoryService() { 195 return termRepositoryService; 196 } 197 198 public void setTermRepositoryService(TermRepositoryService termRepositoryService) { 199 this.termRepositoryService = termRepositoryService; 200 } 201}