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 org.kuali.rice.krad.uif.service.impl;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.config.property.ConfigurationService;
20  import org.kuali.rice.krad.service.KRADServiceLocator;
21  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
22  import org.kuali.rice.krad.service.LookupService;
23  import org.kuali.rice.krad.uif.UifConstants;
24  import org.kuali.rice.krad.uif.view.View;
25  import org.kuali.rice.krad.uif.component.MethodInvokerConfig;
26  import org.kuali.rice.krad.uif.field.InputField;
27  import org.kuali.rice.krad.uif.field.AttributeQuery;
28  import org.kuali.rice.krad.uif.field.AttributeQueryResult;
29  import org.kuali.rice.krad.uif.service.AttributeQueryService;
30  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
31  import org.kuali.rice.krad.uif.widget.Suggest;
32  import org.kuali.rice.krad.util.BeanPropertyComparator;
33  
34  import java.text.MessageFormat;
35  import java.util.ArrayList;
36  import java.util.Collection;
37  import java.util.Collections;
38  import java.util.HashMap;
39  import java.util.List;
40  import java.util.Map;
41  
42  /**
43   * Implementation of <code>AttributeQueryService</code> that prepares the attribute queries and
44   * delegates to the <code>LookupService</code>
45   *
46   * @author Kuali Rice Team (rice.collab@kuali.org)
47   */
48  public class AttributeQueryServiceImpl implements AttributeQueryService {
49  
50      private LookupService lookupService;
51      private ConfigurationService configurationService;
52  
53      /**
54       * @see org.kuali.rice.krad.uif.service.AttributeQueryService#performFieldSuggestQuery(
55       *org.kuali.rice.krad.uif.view.View, java.lang.String, java.lang.String, java.util.Map<java.lang.String,
56       *      java.lang.String>)
57       */
58      @Override
59      public AttributeQueryResult performFieldSuggestQuery(View view, String fieldId, String fieldTerm,
60              Map<String, String> queryParameters) {
61          AttributeQueryResult queryResult = new AttributeQueryResult();
62  
63          // retrieve attribute field from view index
64          InputField inputField = (InputField) view.getViewIndex().getComponentById(fieldId);
65          if (inputField == null) {
66              throw new RuntimeException("Unable to find attribute field instance for id: " + fieldId);
67          }
68  
69          Suggest fieldSuggest = inputField.getFieldSuggest();
70          AttributeQuery suggestQuery = fieldSuggest.getSuggestQuery();
71  
72          // add term as a like criteria
73          Map<String, String> additionalCriteria = new HashMap<String, String>();
74          additionalCriteria.put(fieldSuggest.getSourcePropertyName(), fieldTerm + "*");
75  
76          // execute suggest query
77          Collection<?> results = null;
78          if (suggestQuery.hasConfiguredMethod()) {
79              Object queryMethodResult = executeAttributeQueryMethod(view, suggestQuery, queryParameters);
80              if ((queryMethodResult != null) && (queryMethodResult instanceof Collection<?>)) {
81                  results = (Collection<?>) queryMethodResult;
82              }
83          } else {
84              results = executeAttributeQueryCriteria(suggestQuery, queryParameters, additionalCriteria);
85          }
86  
87          // build list of suggest data from result records
88          if (results != null) {
89              List<String> suggestData = new ArrayList<String>();
90              for (Object result : results) {
91                  Object suggestFieldValue =
92                          ObjectPropertyUtils.getPropertyValue(result, fieldSuggest.getSourcePropertyName());
93                  if (suggestFieldValue != null) {
94                      // TODO: need to apply formatter for field or have method in object property utils
95                      suggestData.add(suggestFieldValue.toString());
96                  }
97              }
98  
99              queryResult.setResultData(suggestData);
100         }
101 
102         return queryResult;
103     }
104 
105     /**
106      * @see org.kuali.rice.krad.uif.service.AttributeQueryService#performFieldQuery(org.kuali.rice.krad.uif.view.View,
107      *      java.lang.String, java.util.Map<java.lang.String,java.lang.String>)
108      */
109     @Override
110     public AttributeQueryResult performFieldQuery(View view, String fieldId, Map<String, String> queryParameters) {
111         AttributeQueryResult queryResult = new AttributeQueryResult();
112 
113         // retrieve attribute field from view index
114         InputField inputField = (InputField) view.getViewIndex().getComponentById(fieldId);
115         if (inputField == null) {
116             throw new RuntimeException("Unable to find attribute field instance for id: " + fieldId);
117         }
118 
119         AttributeQuery fieldQuery = inputField.getFieldAttributeQuery();
120         if (fieldQuery == null) {
121             throw new RuntimeException("Field query not defined for field instance with id: " + fieldId);
122         }
123 
124         // execute query and get result
125         Object resultObject = null;
126         if (fieldQuery.hasConfiguredMethod()) {
127             Object queryMethodResult = executeAttributeQueryMethod(view, fieldQuery, queryParameters);
128             if (queryMethodResult != null) {
129                 // if method returned the result then no further processing needed
130                 if (queryMethodResult instanceof AttributeQueryResult) {
131                     return (AttributeQueryResult) queryMethodResult;
132                 }
133 
134                 // if method returned collection, take first record
135                 if (queryMethodResult instanceof Collection<?>) {
136                     Collection<?> methodResultCollection = (Collection<?>) queryMethodResult;
137                     if (!methodResultCollection.isEmpty()) {
138                         resultObject = methodResultCollection.iterator().next();
139                     }
140                 } else {
141                     resultObject = queryMethodResult;
142                 }
143             }
144         } else {
145             // execute field query as object lookup
146             Collection<?> results = executeAttributeQueryCriteria(fieldQuery, queryParameters, null);
147 
148             if ((results != null) && !results.isEmpty()) {
149                 // expect only one returned row for field query
150                 if (results.size() > 1) {
151                     //finding too many results in a not found message (not specific enough)
152                     resultObject = null;
153                 }
154                 else{
155                     resultObject = results.iterator().next();
156                 }
157             }
158         }
159 
160         if (resultObject != null) {
161             // build result field data map
162             Map<String, String> resultFieldData = new HashMap<String, String>();
163             for (String fromField : fieldQuery.getReturnFieldMapping().keySet()) {
164                 String returnField = fieldQuery.getReturnFieldMapping().get(fromField);
165 
166                 String fieldValueStr = "";
167                 Object fieldValue = ObjectPropertyUtils.getPropertyValue(resultObject, fromField);
168                 if (fieldValue != null) {
169                     fieldValueStr = fieldValue.toString();
170                 }
171                 resultFieldData.put(returnField, fieldValueStr);
172             }
173             queryResult.setResultFieldData(resultFieldData);
174 
175             fieldQuery.setReturnMessageText("");
176         } else {
177             // add data not found message
178             if (fieldQuery.isRenderNotFoundMessage()) {
179                 String messageTemplate =
180                         getConfigurationService().getPropertyValueAsString(
181                                 UifConstants.MessageKeys.QUERY_DATA_NOT_FOUND);
182                 String message = MessageFormat.format(messageTemplate, inputField.getLabel());
183                 fieldQuery.setReturnMessageText(message.toLowerCase());
184             }
185         }
186 
187         // set message and message style classes on query result
188         queryResult.setResultMessage(fieldQuery.getReturnMessageText());
189         queryResult.setResultMessageStyleClasses(fieldQuery.getReturnMessageStyleClasses());
190 
191         return queryResult;
192     }
193 
194     /**
195      * Prepares the method configured on the attribute query then performs the method invocation
196      *
197      * @param view - view instance the field is contained within
198      * @param attributeQuery - attribute query instance to execute
199      * @param queryParameters - map of query parameters that provide values for the method arguments
200      * @return Object type depends on method being invoked, could be AttributeQueryResult in which
201      *         case the method has prepared the return result, or an Object that needs to be parsed for the result
202      */
203     protected Object executeAttributeQueryMethod(View view, AttributeQuery attributeQuery,
204             Map<String, String> queryParameters) {
205         String queryMethodToCall = attributeQuery.getQueryMethodToCall();
206         MethodInvokerConfig queryMethodInvoker = attributeQuery.getQueryMethodInvokerConfig();
207 
208         if (queryMethodInvoker == null) {
209             queryMethodInvoker = new MethodInvokerConfig();
210         }
211 
212         // if method not set on invoker, use queryMethodToCall, note staticMethod could be set(don't know since
213         // there is not a getter), if so it will override the target method in prepare
214         if (StringUtils.isBlank(queryMethodInvoker.getTargetMethod())) {
215             queryMethodInvoker.setTargetMethod(queryMethodToCall);
216         }
217 
218         // if target class or object not set, use view helper service
219         if ((queryMethodInvoker.getTargetClass() == null) && (queryMethodInvoker.getTargetObject() == null)) {
220             queryMethodInvoker.setTargetObject(view.getViewHelperService());
221         }
222 
223         // setup query method arguments
224         Object[] arguments = null;
225         if ((attributeQuery.getQueryMethodArgumentFieldList() != null) &&
226                 (!attributeQuery.getQueryMethodArgumentFieldList().isEmpty())) {
227             // retrieve argument types for conversion
228             Class[] argumentTypes = queryMethodInvoker.getArgumentTypes();
229             if ((argumentTypes == null) ||
230                     (argumentTypes.length != attributeQuery.getQueryMethodArgumentFieldList().size())) {
231                 throw new RuntimeException(
232                         "Query method argument field list size does not match found method argument list size");
233             }
234 
235             arguments = new Object[attributeQuery.getQueryMethodArgumentFieldList().size()];
236             for (int i = 0; i < attributeQuery.getQueryMethodArgumentFieldList().size(); i++) {
237                 String methodArgumentFromField = attributeQuery.getQueryMethodArgumentFieldList().get(i);
238                 if (queryParameters.containsKey(methodArgumentFromField)) {
239                     arguments[i] = queryParameters.get(methodArgumentFromField);
240                 } else {
241                     arguments[i] = null;
242                 }
243             }
244         }
245         queryMethodInvoker.setArguments(arguments);
246 
247         try {
248             queryMethodInvoker.prepare();
249 
250             return queryMethodInvoker.invoke();
251         } catch (Exception e) {
252             throw new RuntimeException("Unable to invoke query method: " + queryMethodInvoker.getTargetMethod(), e);
253         }
254     }
255 
256     /**
257      * Prepares a query using the configured data object, parameters, and criteria, then executes
258      * the query and returns the result Collection
259      *
260      * @param attributeQuery - attribute query instance to perform query for
261      * @param queryParameters - map of parameters that will be used in the query criteria
262      * @param additionalCriteria - map of additional name/value pairs to add to the critiera
263      * @return Collection<?> results of query
264      */
265     protected Collection<?> executeAttributeQueryCriteria(AttributeQuery attributeQuery,
266             Map<String, String> queryParameters, Map<String, String> additionalCriteria) {
267         Collection<?> results = null;
268 
269         // build criteria for query
270         Map<String, String> queryCriteria = new HashMap<String, String>();
271         for (String fieldName : attributeQuery.getQueryFieldMapping().values()) {
272             if (queryParameters.containsKey(fieldName) && StringUtils.isNotBlank(queryParameters.get(fieldName))) {
273                 queryCriteria.put(fieldName, queryParameters.get(fieldName));
274             }
275         }
276 
277         // add any static criteria
278         for (String fieldName : attributeQuery.getAdditionalCriteria().keySet()) {
279             queryCriteria.put(fieldName, attributeQuery.getAdditionalCriteria().get(fieldName));
280         }
281 
282         // add additional criteria
283         if (additionalCriteria != null) {
284             queryCriteria.putAll(additionalCriteria);
285         }
286 
287         Class<?> queryClass = null;
288         try {
289             queryClass = Class.forName(attributeQuery.getDataObjectClassName());
290         } catch (ClassNotFoundException e) {
291             throw new RuntimeException(
292                     "Invalid data object class given for suggest query: " + attributeQuery.getDataObjectClassName(), e);
293         }
294 
295         // run query
296         results = getLookupService().findCollectionBySearchUnbounded(queryClass, queryCriteria);
297 
298         // sort results
299         if (!attributeQuery.getSortPropertyNames().isEmpty() && (results != null) && (results.size() > 1)) {
300             Collections.sort((List<?>) results, new BeanPropertyComparator(attributeQuery.getSortPropertyNames()));
301         }
302 
303         return results;
304     }
305 
306     protected LookupService getLookupService() {
307         if (lookupService == null) {
308             lookupService = KRADServiceLocatorWeb.getLookupService();
309         }
310 
311         return lookupService;
312     }
313 
314     public void setLookupService(LookupService lookupService) {
315         this.lookupService = lookupService;
316     }
317 
318     protected ConfigurationService getConfigurationService() {
319         if (configurationService == null) {
320             configurationService = KRADServiceLocator.getKualiConfigurationService();
321         }
322 
323         return configurationService;
324     }
325 
326     public void setConfigurationService(ConfigurationService configurationService) {
327         this.configurationService = configurationService;
328     }
329 }