View Javadoc

1   package edu.sampleu.krms.impl;
2   
3   import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
4   import org.kuali.rice.core.api.uif.RemotableAttributeError;
5   import org.kuali.rice.krms.api.KrmsApiServiceLocator;
6   import org.kuali.rice.krms.api.repository.function.FunctionDefinition;
7   import org.kuali.rice.krms.api.repository.function.FunctionRepositoryService;
8   import org.kuali.rice.krms.api.repository.operator.CustomOperator;
9   import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition;
10  import org.kuali.rice.krms.api.repository.type.KrmsTypeRepositoryService;
11  import org.kuali.rice.krms.framework.engine.Function;
12  import org.kuali.rice.krms.framework.type.FunctionTypeService;
13  import org.kuali.rice.krms.impl.repository.FunctionBoService;
14  
15  import java.text.MessageFormat;
16  import java.util.ArrayList;
17  import java.util.Collection;
18  import java.util.Collections;
19  import java.util.List;
20  
21  /**
22   * An example CustomOperator to demonstrate this functionality in of the KRMS UI and engine.
23   *
24   * <p>This one also does the FunctionTypeService duties to produce the engine executable, though that responsibility
25   * could be extracted to a separate class if desired, though that would require more wiring and database configuration.
26   * </p>
27   *
28   * <p>This implementation cheats a bit for easy configuration by persisting the FunctionDefinition on first access.
29   * The only prerequisite configuration to use it: 1) add a KRMS type with namespace "KR-SAP", name "contains operator",
30   * and the serviceName "sampleAppContainsOperatorService".  2) add a type relation of type 'A' (usage allowed) from
31   * your KRMS type to the context type you want to use it with.</p>
32   *
33   * <p>This service implementation is wired up in Spring and exported to the service bus.</p>
34   *
35   * <!-- NOTE: all ID and FK columns in the example SQL below should be replaced appropriately, but you knew that. -->
36   *
37   * <!-- sample SQL for creating the KRMS type:
38   * insert into krms_typ_t (typ_id, nm, nmspc_cd, srvc_nm, actv, ver_nbr)
39   * values ('OPERATOR-KRMS-TYPE-ID', 'contains operator', 'KR-SAP', 'sampleAppContainsOperatorService', 'Y', 1);
40   * -->
41   *
42   * <!-- sample SQL for creating the type relation:
43   * insert into krms_typ_reln_t (typ_reln_id, from_typ_id, to_typ_id, reln_typ, seq_no)
44   * values ('A-UNIQUE-ID', 'CONTEXT-TYPE-ID', 'OPERATOR-KRMS-TYPE-ID', 'A', 1);
45   * -->
46   *
47   * @author Kuali Rice Team (rice.collab@kuali.org)
48   */
49  public class ContainsOperator implements CustomOperator, FunctionTypeService {
50  
51      /**
52       * Returns the FunctionDefinition for the custom function the executable portion of this CustomOperator will
53       * call.
54       *
55       * <p>If the FunctionDefinition hasn't been persisted yet, this method will persist it and then return it.</p>
56       *
57       * <p>Note that having the KRMS type for this </p>
58       *
59       * @return
60       */
61      @Override
62      public FunctionDefinition getOperatorFunctionDefinition() {
63          FunctionBoService functionBoService =
64                  (FunctionBoService) GlobalResourceLoader.getService("functionRepositoryService");
65  
66          KrmsTypeRepositoryService typeRepository = KrmsApiServiceLocator.getKrmsTypeRepositoryService();
67          KrmsTypeDefinition containsOperatorType = typeRepository.getTypeByName("KR-SAP", "contains operator");
68  
69          if (containsOperatorType == null) {
70              throw new IllegalStateException("There must be a persisted KRMS type with namespace 'KR-SAP', "
71                      + "name 'contains operator', and serviceName 'sampleAppContainsOperatorService'");
72          }
73  
74          FunctionDefinition containsFunction = functionBoService.getFunctionByNameAndNamespace("contains", "KR-SAP");
75  
76          if (containsFunction == null) {
77              FunctionDefinition.Builder functionBuilder =
78                      FunctionDefinition.Builder.create("KR-SAP", "contains", "java.lang.Boolean", containsOperatorType.getId());
79  
80              containsFunction = functionBoService.createFunction(functionBuilder.build());
81          }
82  
83          return containsFunction;
84      }
85  
86      /**
87       * Validate the argument types.  This operator supports (String, String), and (Collection, Object).  For other
88       * classes, errors will be produced.  Note that at the current time (Rice 2.3.1), the rhsClassName will always be
89       * "java.lang.String" since the UI is constrained to set the second arg as a constant value.
90       *
91       * @param lhsClassName the class name for the left hand side operand
92       * @param rhsClassName the class name for the right hand side operand
93       * @return errors
94       */
95      @Override
96      public List<RemotableAttributeError> validateOperandClasses(String lhsClassName, String rhsClassName) {
97          List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>();
98  
99          if (String.class.getName().equals(lhsClassName)) {
100             if (!String.class.getName().equals(rhsClassName)) {
101                 RemotableAttributeError.Builder errorBuilder =
102                         RemotableAttributeError.Builder.create("operator", "right hand operand is not a String");
103                 errors.add(errorBuilder.build());
104             }
105         } else {
106             try {
107                 Class<?> clazz = getClass().getClassLoader().loadClass(lhsClassName);
108 
109                 if (!Collection.class.isAssignableFrom(clazz)) {
110                     RemotableAttributeError.Builder errorBuilder =
111                             RemotableAttributeError.Builder.create("operator", "left hand operand is not a Collection");
112                     errors.add(errorBuilder.build());
113                 }
114             } catch (ClassNotFoundException e) {
115                 // it is not a class available to the class loader so we can't determine if it extends Collection or not
116             }
117         }
118 
119         return errors;
120     }
121 
122     /**
123      * Loads the Function object that the KRMS engine can execute during rule evaluation
124      *
125      * @param functionDefinition {@link FunctionDefinition} to create the {@link Function} from.
126      * @return
127      */
128     @Override
129     public Function loadFunction(FunctionDefinition functionDefinition) {
130         if (!"contains".equals(functionDefinition.getName()) || "KR-SAP".equals(functionDefinition.getNamespace())) {
131             throw new IllegalArgumentException("oops, you have the wrong type service, I can't load this function");
132         }
133 
134         // return our KRMS engine executable operator function:
135         return new Function() {
136             @Override
137             public Object invoke(Object... arguments) {
138                 if (arguments.length != 2) {
139                     throw new IllegalArgumentException("contains operator expects 2 arguments");
140                 }
141 
142                 if (arguments[0] instanceof String) {
143                     if (arguments[1] instanceof String) {
144                         return Boolean.valueOf(((String)arguments[0]).contains((String)arguments[1]));
145                     } else {
146                         throw new IllegalArgumentException("if the first argument is a String, the second argument "
147                                 + "must be as well");
148                     }
149                 } else if (arguments[0] instanceof Collection) {
150                     return Boolean.valueOf(((Collection)arguments[0]).contains(arguments[1]));
151                 }
152 
153                 throw new IllegalArgumentException("argument types must be either (String, String) or (Collection, Object)");
154             }
155         };
156     }
157 }