View Javadoc

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