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 org.kuali.rice.krad.uif.service.impl; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.core.api.config.property.ConfigurationService; 020import org.kuali.rice.krad.service.KRADServiceLocator; 021import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 022import org.kuali.rice.krad.service.LookupService; 023import org.kuali.rice.krad.uif.UifConstants; 024import org.kuali.rice.krad.uif.view.View; 025import org.kuali.rice.krad.uif.component.MethodInvokerConfig; 026import org.kuali.rice.krad.uif.field.InputField; 027import org.kuali.rice.krad.uif.field.AttributeQuery; 028import org.kuali.rice.krad.uif.field.AttributeQueryResult; 029import org.kuali.rice.krad.uif.service.AttributeQueryService; 030import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 031import org.kuali.rice.krad.uif.widget.Suggest; 032import org.kuali.rice.krad.util.BeanPropertyComparator; 033 034import java.text.MessageFormat; 035import java.util.ArrayList; 036import java.util.Collection; 037import java.util.Collections; 038import java.util.HashMap; 039import java.util.List; 040import java.util.Map; 041 042/** 043 * Implementation of <code>AttributeQueryService</code> that prepares the attribute queries and 044 * delegates to the <code>LookupService</code> 045 * 046 * @author Kuali Rice Team (rice.collab@kuali.org) 047 */ 048public class AttributeQueryServiceImpl implements AttributeQueryService { 049 050 private LookupService lookupService; 051 private ConfigurationService configurationService; 052 053 /** 054 * @see org.kuali.rice.krad.uif.service.AttributeQueryService#performFieldSuggestQuery( 055 *org.kuali.rice.krad.uif.view.View, java.lang.String, java.lang.String, java.util.Map<java.lang.String, 056 * java.lang.String>) 057 */ 058 @Override 059 public AttributeQueryResult performFieldSuggestQuery(View view, String fieldId, String fieldTerm, 060 Map<String, String> queryParameters) { 061 AttributeQueryResult queryResult = new AttributeQueryResult(); 062 063 // retrieve attribute field from view index 064 InputField inputField = (InputField) view.getViewIndex().getComponentById(fieldId); 065 if (inputField == null) { 066 throw new RuntimeException("Unable to find attribute field instance for id: " + fieldId); 067 } 068 069 Suggest fieldSuggest = inputField.getFieldSuggest(); 070 AttributeQuery suggestQuery = fieldSuggest.getSuggestQuery(); 071 072 // add term as a like criteria 073 Map<String, String> additionalCriteria = new HashMap<String, String>(); 074 additionalCriteria.put(fieldSuggest.getSourcePropertyName(), fieldTerm + "*"); 075 076 // execute suggest query 077 Collection<?> results = null; 078 if (suggestQuery.hasConfiguredMethod()) { 079 Object queryMethodResult = executeAttributeQueryMethod(view, suggestQuery, queryParameters); 080 if ((queryMethodResult != null) && (queryMethodResult instanceof Collection<?>)) { 081 results = (Collection<?>) queryMethodResult; 082 } 083 } else { 084 results = executeAttributeQueryCriteria(suggestQuery, queryParameters, additionalCriteria); 085 } 086 087 // build list of suggest data from result records 088 if (results != null) { 089 List<String> suggestData = new ArrayList<String>(); 090 for (Object result : results) { 091 Object suggestFieldValue = 092 ObjectPropertyUtils.getPropertyValue(result, fieldSuggest.getSourcePropertyName()); 093 if (suggestFieldValue != null) { 094 // TODO: need to apply formatter for field or have method in object property utils 095 suggestData.add(suggestFieldValue.toString()); 096 } 097 } 098 099 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}