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 edu.sampleu.krms.impl; 017 018import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader; 019import org.kuali.rice.core.api.uif.RemotableAttributeError; 020import org.kuali.rice.krms.api.KrmsApiServiceLocator; 021import org.kuali.rice.krms.api.repository.function.FunctionDefinition; 022import org.kuali.rice.krms.api.repository.function.FunctionRepositoryService; 023import org.kuali.rice.krms.api.repository.operator.CustomOperator; 024import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition; 025import org.kuali.rice.krms.api.repository.type.KrmsTypeRepositoryService; 026import org.kuali.rice.krms.framework.engine.Function; 027import org.kuali.rice.krms.framework.type.FunctionTypeService; 028import org.kuali.rice.krms.impl.repository.FunctionBoService; 029 030import java.text.MessageFormat; 031import java.util.ArrayList; 032import java.util.Collection; 033import java.util.Collections; 034import java.util.List; 035 036/** 037 * An example CustomOperator to demonstrate this functionality in of the KRMS UI and engine. 038 * 039 * <p>This one also does the FunctionTypeService duties to produce the engine executable, though that responsibility 040 * could be extracted to a separate class if desired, though that would require more wiring and database configuration. 041 * </p> 042 * 043 * <p>This implementation cheats a bit for easy configuration by persisting the FunctionDefinition on first access. 044 * The only prerequisite configuration to use it: 1) add a KRMS type with namespace "KR-SAP", name "contains operator", 045 * and the serviceName "sampleAppContainsOperatorService". 2) add a type relation of type 'A' (usage allowed) from 046 * your KRMS type to the context type you want to use it with.</p> 047 * 048 * <p>This service implementation is wired up in Spring and exported to the service bus.</p> 049 * 050 * <!-- NOTE: all ID and FK columns in the example SQL below should be replaced appropriately, but you knew that. --> 051 * 052 * <!-- sample SQL for creating the KRMS type: 053 * insert into krms_typ_t (typ_id, nm, nmspc_cd, srvc_nm, actv, ver_nbr) 054 * values ('OPERATOR-KRMS-TYPE-ID', 'contains operator', 'KR-SAP', 'sampleAppContainsOperatorService', 'Y', 1); 055 * --> 056 * 057 * <!-- sample SQL for creating the type relation: 058 * insert into krms_typ_reln_t (typ_reln_id, from_typ_id, to_typ_id, reln_typ, seq_no) 059 * values ('A-UNIQUE-ID', 'CONTEXT-TYPE-ID', 'OPERATOR-KRMS-TYPE-ID', 'A', 1); 060 * --> 061 * 062 * @author Kuali Rice Team (rice.collab@kuali.org) 063 */ 064public class ContainsOperator implements CustomOperator, FunctionTypeService { 065 066 /** 067 * Returns the FunctionDefinition for the custom function the executable portion of this CustomOperator will 068 * call. 069 * 070 * <p>If the FunctionDefinition hasn't been persisted yet, this method will persist it and then return it.</p> 071 * 072 * <p>Note that having the KRMS type for this </p> 073 * 074 * @return 075 */ 076 @Override 077 public FunctionDefinition getOperatorFunctionDefinition() { 078 FunctionBoService functionBoService = 079 (FunctionBoService) GlobalResourceLoader.getService("functionRepositoryService"); 080 081 KrmsTypeRepositoryService typeRepository = KrmsApiServiceLocator.getKrmsTypeRepositoryService(); 082 KrmsTypeDefinition containsOperatorType = typeRepository.getTypeByName("KR-SAP", "contains operator"); 083 084 if (containsOperatorType == null) { 085 throw new IllegalStateException("There must be a persisted KRMS type with namespace 'KR-SAP', " 086 + "name 'contains operator', and serviceName 'sampleAppContainsOperatorService'"); 087 } 088 089 FunctionDefinition containsFunction = functionBoService.getFunctionByNameAndNamespace("contains", "KR-SAP"); 090 091 if (containsFunction == null) { 092 FunctionDefinition.Builder functionBuilder = 093 FunctionDefinition.Builder.create("KR-SAP", "contains", "java.lang.Boolean", containsOperatorType.getId()); 094 095 containsFunction = functionBoService.createFunction(functionBuilder.build()); 096 } 097 098 return containsFunction; 099 } 100 101 /** 102 * Validate the argument types. This operator supports (String, String), and (Collection, Object). For other 103 * classes, errors will be produced. Note that at the current time (Rice 2.3.1), the rhsClassName will always be 104 * "java.lang.String" since the UI is constrained to set the second arg as a constant value. 105 * 106 * @param lhsClassName the class name for the left hand side operand 107 * @param rhsClassName the class name for the right hand side operand 108 * @return errors 109 */ 110 @Override 111 public List<RemotableAttributeError> validateOperandClasses(String lhsClassName, String rhsClassName) { 112 List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>(); 113 114 if (String.class.getName().equals(lhsClassName)) { 115 if (!String.class.getName().equals(rhsClassName)) { 116 RemotableAttributeError.Builder errorBuilder = 117 RemotableAttributeError.Builder.create("operator", "right hand operand is not a String"); 118 errors.add(errorBuilder.build()); 119 } 120 } else { 121 try { 122 Class<?> clazz = getClass().getClassLoader().loadClass(lhsClassName); 123 124 if (!Collection.class.isAssignableFrom(clazz)) { 125 RemotableAttributeError.Builder errorBuilder = 126 RemotableAttributeError.Builder.create("operator", "left hand operand is not a Collection"); 127 errors.add(errorBuilder.build()); 128 } 129 } catch (ClassNotFoundException e) { 130 // it is not a class available to the class loader so we can't determine if it extends Collection or not 131 } 132 } 133 134 return errors; 135 } 136 137 /** 138 * Loads the Function object that the KRMS engine can execute during rule evaluation 139 * 140 * @param functionDefinition {@link FunctionDefinition} to create the {@link Function} from. 141 * @return 142 */ 143 @Override 144 public Function loadFunction(FunctionDefinition functionDefinition) { 145 if (!"contains".equals(functionDefinition.getName()) || "KR-SAP".equals(functionDefinition.getNamespace())) { 146 throw new IllegalArgumentException("oops, you have the wrong type service, I can't load this function"); 147 } 148 149 // return our KRMS engine executable operator function: 150 return new Function() { 151 @Override 152 public Object invoke(Object... arguments) { 153 if (arguments.length != 2) { 154 throw new IllegalArgumentException("contains operator expects 2 arguments"); 155 } 156 157 if (arguments[0] instanceof String) { 158 if (arguments[1] instanceof String) { 159 return Boolean.valueOf(((String)arguments[0]).contains((String)arguments[1])); 160 } else { 161 throw new IllegalArgumentException("if the first argument is a String, the second argument " 162 + "must be as well"); 163 } 164 } else if (arguments[0] instanceof Collection) { 165 return Boolean.valueOf(((Collection)arguments[0]).contains(arguments[1])); 166 } 167 168 throw new IllegalArgumentException("argument types must be either (String, String) or (Collection, Object)"); 169 } 170 }; 171 } 172}