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