View Javadoc

1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package edu.sampleu.krms.impl;
17  
18  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
19  import org.kuali.rice.core.api.uif.RemotableAttributeError;
20  import org.kuali.rice.krms.api.KrmsApiServiceLocator;
21  import org.kuali.rice.krms.api.repository.function.FunctionDefinition;
22  import org.kuali.rice.krms.api.repository.function.FunctionRepositoryService;
23  import org.kuali.rice.krms.api.repository.operator.CustomOperator;
24  import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition;
25  import org.kuali.rice.krms.api.repository.type.KrmsTypeRepositoryService;
26  import org.kuali.rice.krms.framework.engine.Function;
27  import org.kuali.rice.krms.framework.type.FunctionTypeService;
28  import org.kuali.rice.krms.impl.repository.FunctionBoService;
29  
30  import java.text.MessageFormat;
31  import java.util.ArrayList;
32  import java.util.Collection;
33  import java.util.Collections;
34  import java.util.List;
35  
36  /**
37   * An example CustomOperator to demonstrate this functionality in of the KRMS UI and engine.
38   *
39   * <p>This one also does the FunctionTypeService duties to produce the engine executable, though that responsibility
40   * could be extracted to a separate class if desired, though that would require more wiring and database configuration.
41   * </p>
42   *
43   * <p>This implementation cheats a bit for easy configuration by persisting the FunctionDefinition on first access.
44   * The only prerequisite configuration to use it: 1) add a KRMS type with namespace "KR-SAP", name "contains operator",
45   * and the serviceName "sampleAppContainsOperatorService".  2) add a type relation of type 'A' (usage allowed) from
46   * your KRMS type to the context type you want to use it with.</p>
47   *
48   * <p>This service implementation is wired up in Spring and exported to the service bus.</p>
49   *
50   * <!-- NOTE: all ID and FK columns in the example SQL below should be replaced appropriately, but you knew that. -->
51   *
52   * <!-- sample SQL for creating the KRMS type:
53   * insert into krms_typ_t (typ_id, nm, nmspc_cd, srvc_nm, actv, ver_nbr)
54   * values ('OPERATOR-KRMS-TYPE-ID', 'contains operator', 'KR-SAP', 'sampleAppContainsOperatorService', 'Y', 1);
55   * -->
56   *
57   * <!-- sample SQL for creating the type relation:
58   * insert into krms_typ_reln_t (typ_reln_id, from_typ_id, to_typ_id, reln_typ, seq_no)
59   * values ('A-UNIQUE-ID', 'CONTEXT-TYPE-ID', 'OPERATOR-KRMS-TYPE-ID', 'A', 1);
60   * -->
61   *
62   * @author Kuali Rice Team (rice.collab@kuali.org)
63   */
64  public class ContainsOperator implements CustomOperator, FunctionTypeService {
65  
66      /**
67       * Returns the FunctionDefinition for the custom function the executable portion of this CustomOperator will
68       * call.
69       *
70       * <p>If the FunctionDefinition hasn't been persisted yet, this method will persist it and then return it.</p>
71       *
72       * <p>Note that having the KRMS type for this </p>
73       *
74       * @return
75       */
76      @Override
77      public FunctionDefinition getOperatorFunctionDefinition() {
78          FunctionBoService functionBoService =
79                  (FunctionBoService) GlobalResourceLoader.getService("functionRepositoryService");
80  
81          KrmsTypeRepositoryService typeRepository = KrmsApiServiceLocator.getKrmsTypeRepositoryService();
82          KrmsTypeDefinition containsOperatorType = typeRepository.getTypeByName("KR-SAP", "contains operator");
83  
84          if (containsOperatorType == null) {
85              throw new IllegalStateException("There must be a persisted KRMS type with namespace 'KR-SAP', "
86                      + "name 'contains operator', and serviceName 'sampleAppContainsOperatorService'");
87          }
88  
89          FunctionDefinition containsFunction = functionBoService.getFunctionByNameAndNamespace("contains", "KR-SAP");
90  
91          if (containsFunction == null) {
92              FunctionDefinition.Builder functionBuilder =
93                      FunctionDefinition.Builder.create("KR-SAP", "contains", "java.lang.Boolean", containsOperatorType.getId());
94  
95              containsFunction = functionBoService.createFunction(functionBuilder.build());
96          }
97  
98          return containsFunction;
99      }
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 }