001/** 002 * Copyright 2005-2018 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.field; 017 018import java.io.Serializable; 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.commons.lang.StringUtils; 025import org.kuali.rice.krad.datadictionary.parse.BeanTag; 026import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 027import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase; 028import org.kuali.rice.krad.datadictionary.validator.ValidationTrace; 029import org.kuali.rice.krad.uif.component.BindingInfo; 030import org.kuali.rice.krad.uif.component.MethodInvokerConfig; 031import org.kuali.rice.krad.uif.service.ViewHelperService; 032 033/** 034 * Holds configuration for executing a dynamic query on an <code>InputField</code> to 035 * pull data for updating the UI 036 * 037 * <p> 038 * There are two types of query types that can be configured and executed. The first is provided 039 * completely by the framework using the <code>LookupService</code> and will perform a query 040 * against the configured dataObjectClassName using the query parameters and return field mapping. 041 * The second type will invoke a method that will perform the query. This can be configured using the 042 * queryMethodToCall (if the method is on the view helper service), or using the queryMethodInvoker if 043 * the method is on another class or object. 044 * </p> 045 * 046 * @author Kuali Rice Team (rice.collab@kuali.org) 047 */ 048@BeanTag(name = "attributeQuery", parent = "Uif-AttributeQueryConfig") 049public class AttributeQuery extends UifDictionaryBeanBase implements Serializable { 050 private static final long serialVersionUID = -4569905665441735255L; 051 052 private String dataObjectClassName; 053 054 private boolean renderNotFoundMessage; 055 private String returnMessageText; 056 private String returnMessageStyleClasses; 057 058 private Map<String, String> queryFieldMapping; 059 private Map<String, String> returnFieldMapping; 060 private Map<String, String> additionalCriteria; 061 062 private List<String> sortPropertyNames; 063 064 private String queryMethodToCall; 065 private List<String> queryMethodArgumentFieldList; 066 private MethodInvokerConfig queryMethodInvokerConfig; 067 068 public AttributeQuery() { 069 renderNotFoundMessage = true; 070 071 queryFieldMapping = new HashMap<String, String>(); 072 returnFieldMapping = new HashMap<String, String>(); 073 additionalCriteria = new HashMap<String, String>(); 074 sortPropertyNames = new ArrayList<String>(); 075 076 queryMethodArgumentFieldList = new ArrayList<String>(); 077 queryMethodInvokerConfig = new MethodInvokerConfig(); 078 } 079 080 /** 081 * If the query is configured with a method and the target of that method is undefined, sets the target 082 * class to the class of the given view helper service. 083 * 084 * @param viewHelperService instance of view helper to use as default for query methods 085 */ 086 public void defaultQueryTarget(ViewHelperService viewHelperService) { 087 if ((queryMethodInvokerConfig != null) && (queryMethodInvokerConfig.getTargetClass() == null) 088 && (queryMethodInvokerConfig.getTargetObject() == null)) { 089 queryMethodInvokerConfig.setTargetClass(viewHelperService.getClass()); 090 } 091 } 092 093 /** 094 * Adjusts the path on the query field mapping from property to match the binding 095 * path prefix of the given <code>BindingInfo</code> 096 * 097 * @param bindingInfo binding info instance to copy binding path prefix from 098 */ 099 public void updateQueryFieldMapping(BindingInfo bindingInfo) { 100 Map<String, String> adjustedQueryFieldMapping = new HashMap<String, String>(); 101 for (String fromFieldPath : getQueryFieldMapping().keySet()) { 102 String toField = getQueryFieldMapping().get(fromFieldPath); 103 String adjustedFromFieldPath = bindingInfo.getPropertyAdjustedBindingPath(fromFieldPath); 104 105 adjustedQueryFieldMapping.put(adjustedFromFieldPath, toField); 106 } 107 108 this.queryFieldMapping = adjustedQueryFieldMapping; 109 } 110 111 /** 112 * Adjusts the path on the return field mapping to property to match the binding 113 * path prefix of the given <code>BindingInfo</code> 114 * 115 * @param bindingInfo binding info instance to copy binding path prefix from 116 */ 117 public void updateReturnFieldMapping(BindingInfo bindingInfo) { 118 Map<String, String> adjustedReturnFieldMapping = new HashMap<String, String>(); 119 for (String fromFieldPath : getReturnFieldMapping().keySet()) { 120 String toFieldPath = getReturnFieldMapping().get(fromFieldPath); 121 String adjustedToFieldPath = bindingInfo.getPropertyAdjustedBindingPath(toFieldPath); 122 123 adjustedReturnFieldMapping.put(fromFieldPath, adjustedToFieldPath); 124 } 125 126 this.returnFieldMapping = adjustedReturnFieldMapping; 127 } 128 129 /** 130 * Adjusts the path on the query method arguments field list to match the binding 131 * path prefix of the given <code>BindingInfo</code> 132 * 133 * @param bindingInfo binding info instance to copy binding path prefix from 134 */ 135 public void updateQueryMethodArgumentFieldList(BindingInfo bindingInfo) { 136 List<String> adjustedArgumentFieldList = new ArrayList<String>(); 137 for (String argumentFieldPath : getQueryMethodArgumentFieldList()) { 138 String adjustedFieldPath = bindingInfo.getPropertyAdjustedBindingPath(argumentFieldPath); 139 adjustedArgumentFieldList.add(adjustedFieldPath); 140 } 141 142 this.queryMethodArgumentFieldList = adjustedArgumentFieldList; 143 } 144 145 /** 146 * Builds String for passing the queryFieldMapping Map as a Javascript object 147 * parameter 148 * 149 * @return js parameter string 150 */ 151 public String getQueryFieldMappingJsString() { 152 String queryFieldMappingJs = "{"; 153 154 for (String queryField : queryFieldMapping.keySet()) { 155 if (!StringUtils.equals(queryFieldMappingJs, "{")) { 156 queryFieldMappingJs += ","; 157 } 158 159 queryFieldMappingJs += "\"" + queryField + "\":\"" + queryFieldMapping.get(queryField) + "\""; 160 } 161 162 queryFieldMappingJs += "}"; 163 164 return queryFieldMappingJs; 165 } 166 167 /** 168 * Builds String for passing the returnFieldMapping Map as a Javascript object 169 * parameter 170 * 171 * @return js parameter string 172 */ 173 public String getReturnFieldMappingJsString() { 174 String returnFieldMappingJs = "{"; 175 176 for (String fromField : returnFieldMapping.keySet()) { 177 if (!StringUtils.equals(returnFieldMappingJs, "{")) { 178 returnFieldMappingJs += ","; 179 } 180 181 returnFieldMappingJs += "\"" + returnFieldMapping.get(fromField) + "\":\"" + fromField + "\""; 182 } 183 184 returnFieldMappingJs += "}"; 185 186 return returnFieldMappingJs; 187 } 188 189 /** 190 * Builds String for passing the queryMethodArgumentFieldList as a Javascript Object 191 * 192 * @return js parameter string 193 */ 194 public String getQueryMethodArgumentFieldsJsString() { 195 String queryMethodArgsJs = "{"; 196 197 for (String methodArg : queryMethodArgumentFieldList) { 198 if (!StringUtils.equals(queryMethodArgsJs, "{")) { 199 queryMethodArgsJs += ","; 200 } 201 202 queryMethodArgsJs += "\"" + methodArg + "\":\"" + methodArg + "\""; 203 } 204 205 queryMethodArgsJs += "}"; 206 207 return queryMethodArgsJs; 208 } 209 210 /** 211 * Indicates whether this attribute query is configured to invoke a custom 212 * method as opposed to running the general object query. If either the query method 213 * to call is given, or the query method invoker is not null it is assumed the 214 * intention is to call a custom method 215 * 216 * @return true if a custom method is configured, false if not 217 */ 218 public boolean hasConfiguredMethod() { 219 boolean configuredMethod = false; 220 221 if (StringUtils.isNotBlank(getQueryMethodToCall())) { 222 configuredMethod = true; 223 } else if (getQueryMethodInvokerConfig() != null && (StringUtils.isNotBlank( 224 getQueryMethodInvokerConfig().getTargetMethod()) || StringUtils.isNotBlank( 225 getQueryMethodInvokerConfig().getStaticMethod()))) { 226 configuredMethod = true; 227 } 228 229 return configuredMethod; 230 } 231 232 /** 233 * Class name for the data object the query should be performed against 234 * 235 * @return data object class name 236 */ 237 @BeanTagAttribute 238 public String getDataObjectClassName() { 239 return dataObjectClassName; 240 } 241 242 /** 243 * Setter for the query data object class name 244 * 245 * @param dataObjectClassName 246 */ 247 public void setDataObjectClassName(String dataObjectClassName) { 248 this.dataObjectClassName = dataObjectClassName; 249 } 250 251 /** 252 * Configures the query parameters by mapping fields in the view 253 * to properties on the data object class for the query 254 * 255 * <p> 256 * Each map entry configures one parameter for the query, where 257 * the map key is the field name to pull the value from, and the 258 * map value is the property name on the object the parameter should 259 * populate. 260 * </p> 261 * 262 * @return mapping of query parameters 263 */ 264 @BeanTagAttribute 265 public Map<String, String> getQueryFieldMapping() { 266 return queryFieldMapping; 267 } 268 269 /** 270 * Setter for the query parameter mapping 271 * 272 * @param queryFieldMapping 273 */ 274 public void setQueryFieldMapping(Map<String, String> queryFieldMapping) { 275 this.queryFieldMapping = queryFieldMapping; 276 } 277 278 /** 279 * Maps properties from the result object of the query to 280 * fields in the view 281 * 282 * <p> 283 * Each map entry configures one return mapping, where the map 284 * key is the field name for the field to populate, and the map 285 * values is the name of the property on the result object to 286 * pull the value from 287 * </p> 288 * 289 * @return return field mapping 290 */ 291 @BeanTagAttribute 292 public Map<String, String> getReturnFieldMapping() { 293 return returnFieldMapping; 294 } 295 296 /** 297 * Setter for the return field mapping 298 * 299 * @param returnFieldMapping 300 */ 301 public void setReturnFieldMapping(Map<String, String> returnFieldMapping) { 302 this.returnFieldMapping = returnFieldMapping; 303 } 304 305 /** 306 * Fixed criteria that will be appended to the dynamic criteria generated 307 * for the query. Map key gives name of the property the criteria should 308 * apply to, and the map value is the value (literal) for the criteria. Standard 309 * lookup wildcards are allowed 310 * 311 * @return field name/value pairs for query criteria 312 */ 313 @BeanTagAttribute 314 public Map<String, String> getAdditionalCriteria() { 315 return additionalCriteria; 316 } 317 318 /** 319 * Setter for the query's additional criteria map 320 * 321 * @param additionalCriteria 322 */ 323 public void setAdditionalCriteria(Map<String, String> additionalCriteria) { 324 this.additionalCriteria = additionalCriteria; 325 } 326 327 /** 328 * List of property names to sort the query results by. The sort 329 * will be performed on each property in the order they are contained 330 * within the list. Each property must be a valid property of the 331 * return query object (the data object in case of the general query) 332 * 333 * @return property names 334 */ 335 @BeanTagAttribute 336 public List<String> getSortPropertyNames() { 337 return sortPropertyNames; 338 } 339 340 /** 341 * Setter for the list of property names to sort results by 342 * 343 * @param sortPropertyNames 344 */ 345 public void setSortPropertyNames(List<String> sortPropertyNames) { 346 this.sortPropertyNames = sortPropertyNames; 347 } 348 349 /** 350 * Indicates whether a message should be added to the query result 351 * object and displayed when the query return object is null 352 * 353 * @return true if not found message should be added, false otherwise 354 */ 355 @BeanTagAttribute 356 public boolean isRenderNotFoundMessage() { 357 return renderNotFoundMessage; 358 } 359 360 /** 361 * Setter for the render not found message indicator 362 * 363 * @param renderNotFoundMessage 364 */ 365 public void setRenderNotFoundMessage(boolean renderNotFoundMessage) { 366 this.renderNotFoundMessage = renderNotFoundMessage; 367 } 368 369 /** 370 * Message text to display along with the query result 371 * 372 * @return literal message text 373 */ 374 @BeanTagAttribute 375 public String getReturnMessageText() { 376 return returnMessageText; 377 } 378 379 /** 380 * Setter for the return message text 381 * 382 * @param returnMessageText 383 */ 384 public void setReturnMessageText(String returnMessageText) { 385 this.returnMessageText = returnMessageText; 386 } 387 388 /** 389 * CSS Style classes that should be applied to the return message. 390 * Multiple style classes should be delimited by a space 391 * 392 * @return style classes 393 */ 394 @BeanTagAttribute 395 public String getReturnMessageStyleClasses() { 396 return returnMessageStyleClasses; 397 } 398 399 /** 400 * Setter for the return messages style classes 401 * 402 * @param returnMessageStyleClasses 403 */ 404 public void setReturnMessageStyleClasses(String returnMessageStyleClasses) { 405 this.returnMessageStyleClasses = returnMessageStyleClasses; 406 } 407 408 /** 409 * Configures the name of the method that should be invoked to perform 410 * the query 411 * 412 * <p> 413 * Should contain only the method name (no parameters or return type). If only 414 * the query method name is configured it is assumed to be on the <code>ViewHelperService</code> 415 * for the contained view. 416 * </p> 417 * 418 * @return query method name 419 */ 420 @BeanTagAttribute 421 public String getQueryMethodToCall() { 422 return queryMethodToCall; 423 } 424 425 /** 426 * Setter for the query method name 427 * 428 * @param queryMethodToCall 429 */ 430 public void setQueryMethodToCall(String queryMethodToCall) { 431 this.queryMethodToCall = queryMethodToCall; 432 } 433 434 /** 435 * List of field names that should be passed as arguments to the query method 436 * 437 * <p> 438 * Each entry in the list maps to a method parameter, in the other contained within 439 * the list. The value for the field within the view will be pulled and passed 440 * to the query method as an argument 441 * </p> 442 * 443 * @return query method argument list 444 */ 445 @BeanTagAttribute 446 public List<String> getQueryMethodArgumentFieldList() { 447 return queryMethodArgumentFieldList; 448 } 449 450 /** 451 * Setter for the query method argument list 452 * 453 * @param queryMethodArgumentFieldList 454 */ 455 public void setQueryMethodArgumentFieldList(List<String> queryMethodArgumentFieldList) { 456 this.queryMethodArgumentFieldList = queryMethodArgumentFieldList; 457 } 458 459 /** 460 * Configures the query method target class/object and method name 461 * 462 * <p> 463 * When the query method is not contained on the <code>ViewHelperService</code>, this 464 * can be configured for declaring the target class/object and method. The target class 465 * can be set in which case a new instance will be created and the given method invoked. 466 * Alternatively, the target object instance for the invocation can be given. Or finally 467 * a static method can be configured 468 * </p> 469 * 470 * @return query method config 471 */ 472 @BeanTagAttribute 473 public MethodInvokerConfig getQueryMethodInvokerConfig() { 474 return queryMethodInvokerConfig; 475 } 476 477 /** 478 * Setter for the query method config 479 * 480 * @param queryMethodInvokerConfig 481 */ 482 public void setQueryMethodInvokerConfig(MethodInvokerConfig queryMethodInvokerConfig) { 483 this.queryMethodInvokerConfig = queryMethodInvokerConfig; 484 } 485 486 /** 487 * @see org.kuali.rice.krad.uif.component.Component#completeValidation 488 */ 489 public void completeValidation(ValidationTrace tracer) { 490 tracer.addBean("AttributeQuery", ValidationTrace.NO_BEAN_ID); 491 492 // Checks that at least one aspect is set 493 if (getDataObjectClassName() == null 494 && getQueryMethodToCall() == null 495 && getQueryMethodInvokerConfig() == null) { 496 String currentValues[] = {"dataObjectClassName = " + getDataObjectClassName(), 497 "queryMethodToCall = " + getQueryMethodToCall(), 498 "queryMethodInvokerConfig = " + getQueryMethodInvokerConfig()}; 499 tracer.createWarning( 500 "At least 1 should be set: dataObjectClass, queryMethodToCall or queryMethodInvokerConfig", 501 currentValues); 502 } 503 } 504}