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.field;
17  
18  import java.io.Serializable;
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.apache.commons.lang.StringUtils;
25  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
26  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
27  import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase;
28  import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
29  import org.kuali.rice.krad.uif.component.BindingInfo;
30  import org.kuali.rice.krad.uif.component.MethodInvokerConfig;
31  import org.kuali.rice.krad.uif.service.ViewHelperService;
32  
33  /**
34   * Holds configuration for executing a dynamic query on an <code>InputField</code> to
35   * pull data for updating the UI
36   *
37   * <p>
38   * There are two types of query types that can be configured and executed. The first is provided
39   * completely by the framework using the <code>LookupService</code> and will perform a query
40   * against the configured dataObjectClassName using the query parameters and return field mapping.
41   * The second type will invoke a method that will perform the query. This can be configured using the
42   * queryMethodToCall (if the method is on the view helper service), or using the queryMethodInvoker if
43   * the method is on another class or object.
44   * </p>
45   *
46   * @author Kuali Rice Team (rice.collab@kuali.org)
47   */
48  @BeanTag(name = "attributeQuery", parent = "Uif-AttributeQueryConfig")
49  public class AttributeQuery extends UifDictionaryBeanBase implements Serializable {
50      private static final long serialVersionUID = -4569905665441735255L;
51  
52      private String dataObjectClassName;
53  
54      private boolean renderNotFoundMessage;
55      private String returnMessageText;
56      private String returnMessageStyleClasses;
57  
58      private Map<String, String> queryFieldMapping;
59      private Map<String, String> returnFieldMapping;
60      private Map<String, String> additionalCriteria;
61  
62      private List<String> sortPropertyNames;
63  
64      private String queryMethodToCall;
65      private List<String> queryMethodArgumentFieldList;
66      private MethodInvokerConfig queryMethodInvokerConfig;
67  
68      public AttributeQuery() {
69          renderNotFoundMessage = true;
70  
71          queryFieldMapping = new HashMap<String, String>();
72          returnFieldMapping = new HashMap<String, String>();
73          additionalCriteria = new HashMap<String, String>();
74          sortPropertyNames = new ArrayList<String>();
75  
76          queryMethodArgumentFieldList = new ArrayList<String>();
77          queryMethodInvokerConfig = new MethodInvokerConfig();
78      }
79  
80      /**
81       * If the query is configured with a method and the target of that method is undefined, sets the target
82       * class to the class of the given view helper service.
83       *
84       * @param viewHelperService instance of view helper to use as default for query methods
85       */
86      public void defaultQueryTarget(ViewHelperService viewHelperService) {
87          if ((queryMethodInvokerConfig != null) && (queryMethodInvokerConfig.getTargetClass() == null)
88                  && (queryMethodInvokerConfig.getTargetObject() == null)) {
89              queryMethodInvokerConfig.setTargetClass(viewHelperService.getClass());
90          }
91      }
92  
93      /**
94       * Adjusts the path on the query field mapping from property to match the binding
95       * path prefix of the given <code>BindingInfo</code>
96       *
97       * @param bindingInfo binding info instance to copy binding path prefix from
98       */
99      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 }