View Javadoc
1   /**
2    * Copyright 2005-2016 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.util.CloneUtils;
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       * Adjusts the path on the query field mapping from property to match the binding
83       * path prefix of the given <code>BindingInfo</code>
84       *
85       * @param bindingInfo binding info instance to copy binding path prefix from
86       */
87      public void updateQueryFieldMapping(BindingInfo bindingInfo) {
88          Map<String, String> adjustedQueryFieldMapping = new HashMap<String, String>();
89          for (String fromFieldPath : getQueryFieldMapping().keySet()) {
90              String toField = getQueryFieldMapping().get(fromFieldPath);
91              String adjustedFromFieldPath = bindingInfo.getPropertyAdjustedBindingPath(fromFieldPath);
92  
93              adjustedQueryFieldMapping.put(adjustedFromFieldPath, toField);
94          }
95  
96          this.queryFieldMapping = adjustedQueryFieldMapping;
97      }
98  
99      /**
100      * Adjusts the path on the return field mapping to property to match the binding
101      * path prefix of the given <code>BindingInfo</code>
102      *
103      * @param bindingInfo binding info instance to copy binding path prefix from
104      */
105     public void updateReturnFieldMapping(BindingInfo bindingInfo) {
106         Map<String, String> adjustedReturnFieldMapping = new HashMap<String, String>();
107         for (String fromFieldPath : getReturnFieldMapping().keySet()) {
108             String toFieldPath = getReturnFieldMapping().get(fromFieldPath);
109             String adjustedToFieldPath = bindingInfo.getPropertyAdjustedBindingPath(toFieldPath);
110 
111             adjustedReturnFieldMapping.put(fromFieldPath, adjustedToFieldPath);
112         }
113 
114         this.returnFieldMapping = adjustedReturnFieldMapping;
115     }
116 
117     /**
118      * Adjusts the path on the query method arguments field list to match the binding
119      * path prefix of the given <code>BindingInfo</code>
120      *
121      * @param bindingInfo binding info instance to copy binding path prefix from
122      */
123     public void updateQueryMethodArgumentFieldList(BindingInfo bindingInfo) {
124         List<String> adjustedArgumentFieldList = new ArrayList<String>();
125         for (String argumentFieldPath : getQueryMethodArgumentFieldList()) {
126             String adjustedFieldPath = bindingInfo.getPropertyAdjustedBindingPath(argumentFieldPath);
127             adjustedArgumentFieldList.add(adjustedFieldPath);
128         }
129 
130         this.queryMethodArgumentFieldList = adjustedArgumentFieldList;
131     }
132 
133     /**
134      * Builds String for passing the queryFieldMapping Map as a Javascript object
135      * parameter
136      *
137      * @return js parameter string
138      */
139     public String getQueryFieldMappingJsString() {
140         String queryFieldMappingJs = "{";
141 
142         for (String queryField : queryFieldMapping.keySet()) {
143             if (!StringUtils.equals(queryFieldMappingJs, "{")) {
144                 queryFieldMappingJs += ",";
145             }
146 
147             queryFieldMappingJs += "\"" + queryField + "\":\"" + queryFieldMapping.get(queryField) + "\"";
148         }
149 
150         queryFieldMappingJs += "}";
151 
152         return queryFieldMappingJs;
153     }
154 
155     /**
156      * Builds String for passing the returnFieldMapping Map as a Javascript object
157      * parameter
158      *
159      * @return js parameter string
160      */
161     public String getReturnFieldMappingJsString() {
162         String returnFieldMappingJs = "{";
163 
164         for (String fromField : returnFieldMapping.keySet()) {
165             if (!StringUtils.equals(returnFieldMappingJs, "{")) {
166                 returnFieldMappingJs += ",";
167             }
168 
169             returnFieldMappingJs += "\"" + returnFieldMapping.get(fromField) + "\":\"" + fromField + "\"";
170         }
171 
172         returnFieldMappingJs += "}";
173 
174         return returnFieldMappingJs;
175     }
176 
177     /**
178      * Builds String for passing the queryMethodArgumentFieldList as a Javascript Object
179      *
180      * @return js parameter string
181      */
182     public String getQueryMethodArgumentFieldsJsString() {
183         String queryMethodArgsJs = "{";
184 
185         for (String methodArg : queryMethodArgumentFieldList) {
186             if (!StringUtils.equals(queryMethodArgsJs, "{")) {
187                 queryMethodArgsJs += ",";
188             }
189 
190             queryMethodArgsJs += "\"" + methodArg + "\":\"" + methodArg + "\"";
191         }
192 
193         queryMethodArgsJs += "}";
194 
195         return queryMethodArgsJs;
196     }
197 
198     /**
199      * Indicates whether this attribute query is configured to invoke a custom
200      * method as opposed to running the general object query. If either the query method
201      * to call is given, or the query method invoker is not null it is assumed the
202      * intention is to call a custom method
203      *
204      * @return true if a custom method is configured, false if not
205      */
206     public boolean hasConfiguredMethod() {
207         boolean configuredMethod = false;
208 
209         if (StringUtils.isNotBlank(getQueryMethodToCall())) {
210             configuredMethod = true;
211         } else if (getQueryMethodInvokerConfig() != null && (StringUtils.isNotBlank(
212                 getQueryMethodInvokerConfig().getTargetMethod()) || StringUtils.isNotBlank(
213                 getQueryMethodInvokerConfig().getStaticMethod()))) {
214             configuredMethod = true;
215         }
216 
217         return configuredMethod;
218     }
219 
220     /**
221      * Class name for the data object the query should be performed against
222      *
223      * @return data object class name
224      */
225     @BeanTagAttribute(name = "dataObjectClassName")
226     public String getDataObjectClassName() {
227         return dataObjectClassName;
228     }
229 
230     /**
231      * Setter for the query data object class name
232      *
233      * @param dataObjectClassName
234      */
235     public void setDataObjectClassName(String dataObjectClassName) {
236         this.dataObjectClassName = dataObjectClassName;
237     }
238 
239     /**
240      * Configures the query parameters by mapping fields in the view
241      * to properties on the data object class for the query
242      *
243      * <p>
244      * Each map entry configures one parameter for the query, where
245      * the map key is the field name to pull the value from, and the
246      * map value is the property name on the object the parameter should
247      * populate.
248      * </p>
249      *
250      * @return mapping of query parameters
251      */
252     @BeanTagAttribute(name = "queryFieldMapping", type = BeanTagAttribute.AttributeType.MAPVALUE)
253     public Map<String, String> getQueryFieldMapping() {
254         return queryFieldMapping;
255     }
256 
257     /**
258      * Setter for the query parameter mapping
259      *
260      * @param queryFieldMapping
261      */
262     public void setQueryFieldMapping(Map<String, String> queryFieldMapping) {
263         this.queryFieldMapping = queryFieldMapping;
264     }
265 
266     /**
267      * Maps properties from the result object of the query to
268      * fields in the view
269      *
270      * <p>
271      * Each map entry configures one return mapping, where the map
272      * key is the field name for the field to populate, and the map
273      * values is the name of the property on the result object to
274      * pull the value from
275      * </p>
276      *
277      * @return return field mapping
278      */
279     @BeanTagAttribute(name = "returnFieldMapping", type = BeanTagAttribute.AttributeType.MAPVALUE)
280     public Map<String, String> getReturnFieldMapping() {
281         return returnFieldMapping;
282     }
283 
284     /**
285      * Setter for the return field mapping
286      *
287      * @param returnFieldMapping
288      */
289     public void setReturnFieldMapping(Map<String, String> returnFieldMapping) {
290         this.returnFieldMapping = returnFieldMapping;
291     }
292 
293     /**
294      * Fixed criteria that will be appended to the dynamic criteria generated
295      * for the query. Map key gives name of the property the criteria should
296      * apply to, and the map value is the value (literal) for the criteria. Standard
297      * lookup wildcards are allowed
298      *
299      * @return field name/value pairs for query criteria
300      */
301     @BeanTagAttribute(name = "additionalCriteria", type = BeanTagAttribute.AttributeType.MAPVALUE)
302     public Map<String, String> getAdditionalCriteria() {
303         return additionalCriteria;
304     }
305 
306     /**
307      * Setter for the query's additional criteria map
308      *
309      * @param additionalCriteria
310      */
311     public void setAdditionalCriteria(Map<String, String> additionalCriteria) {
312         this.additionalCriteria = additionalCriteria;
313     }
314 
315     /**
316      * List of property names to sort the query results by. The sort
317      * will be performed on each property in the order they are contained
318      * within the list. Each property must be a valid property of the
319      * return query object (the data object in case of the general query)
320      *
321      * @return property names
322      */
323     @BeanTagAttribute(name = "sortPropertyNames", type = BeanTagAttribute.AttributeType.LISTVALUE)
324     public List<String> getSortPropertyNames() {
325         return sortPropertyNames;
326     }
327 
328     /**
329      * Setter for the list of property names to sort results by
330      *
331      * @param sortPropertyNames
332      */
333     public void setSortPropertyNames(List<String> sortPropertyNames) {
334         this.sortPropertyNames = sortPropertyNames;
335     }
336 
337     /**
338      * Indicates whether a message should be added to the query result
339      * object and displayed when the query return object is null
340      *
341      * @return true if not found message should be added, false otherwise
342      */
343     @BeanTagAttribute(name = "renderNotFoundMessage")
344     public boolean isRenderNotFoundMessage() {
345         return renderNotFoundMessage;
346     }
347 
348     /**
349      * Setter for the render not found message indicator
350      *
351      * @param renderNotFoundMessage
352      */
353     public void setRenderNotFoundMessage(boolean renderNotFoundMessage) {
354         this.renderNotFoundMessage = renderNotFoundMessage;
355     }
356 
357     /**
358      * Message text to display along with the query result
359      *
360      * @return literal message text
361      */
362     @BeanTagAttribute(name = "returnMessageText")
363     public String getReturnMessageText() {
364         return returnMessageText;
365     }
366 
367     /**
368      * Setter for the return message text
369      *
370      * @param returnMessageText
371      */
372     public void setReturnMessageText(String returnMessageText) {
373         this.returnMessageText = returnMessageText;
374     }
375 
376     /**
377      * CSS Style classes that should be applied to the return message.
378      * Multiple style classes should be delimited by a space
379      *
380      * @return style classes
381      */
382     @BeanTagAttribute(name = "returnMessageStyleClasses")
383     public String getReturnMessageStyleClasses() {
384         return returnMessageStyleClasses;
385     }
386 
387     /**
388      * Setter for the return messages style classes
389      *
390      * @param returnMessageStyleClasses
391      */
392     public void setReturnMessageStyleClasses(String returnMessageStyleClasses) {
393         this.returnMessageStyleClasses = returnMessageStyleClasses;
394     }
395 
396     /**
397      * Configures the name of the method that should be invoked to perform
398      * the query
399      *
400      * <p>
401      * Should contain only the method name (no parameters or return type). If only
402      * the query method name is configured it is assumed to be on the <code>ViewHelperService</code>
403      * for the contained view.
404      * </p>
405      *
406      * @return query method name
407      */
408     @BeanTagAttribute(name = "queryMethodToCall")
409     public String getQueryMethodToCall() {
410         return queryMethodToCall;
411     }
412 
413     /**
414      * Setter for the query method name
415      *
416      * @param queryMethodToCall
417      */
418     public void setQueryMethodToCall(String queryMethodToCall) {
419         this.queryMethodToCall = queryMethodToCall;
420     }
421 
422     /**
423      * List of field names that should be passed as arguments to the query method
424      *
425      * <p>
426      * Each entry in the list maps to a method parameter, in the other contained within
427      * the list. The value for the field within the view will be pulled and passed
428      * to the query method as an argument
429      * </p>
430      *
431      * @return query method argument list
432      */
433     @BeanTagAttribute(name = "queryMethodArgumentFieldList", type = BeanTagAttribute.AttributeType.LISTVALUE)
434     public List<String> getQueryMethodArgumentFieldList() {
435         return queryMethodArgumentFieldList;
436     }
437 
438     /**
439      * Setter for the query method argument list
440      *
441      * @param queryMethodArgumentFieldList
442      */
443     public void setQueryMethodArgumentFieldList(List<String> queryMethodArgumentFieldList) {
444         this.queryMethodArgumentFieldList = queryMethodArgumentFieldList;
445     }
446 
447     /**
448      * Configures the query method target class/object and method name
449      *
450      * <p>
451      * When the query method is not contained on the <code>ViewHelperService</code>, this
452      * can be configured for declaring the target class/object and method. The target class
453      * can be set in which case a new instance will be created and the given method invoked.
454      * Alternatively, the target object instance for the invocation can be given. Or finally
455      * a static method can be configured
456      * </p>
457      *
458      * @return query method config
459      */
460     @BeanTagAttribute(name = "queryMethodInvokerConfig", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
461     public MethodInvokerConfig getQueryMethodInvokerConfig() {
462         return queryMethodInvokerConfig;
463     }
464 
465     /**
466      * Setter for the query method config
467      *
468      * @param queryMethodInvokerConfig
469      */
470     public void setQueryMethodInvokerConfig(MethodInvokerConfig queryMethodInvokerConfig) {
471         this.queryMethodInvokerConfig = queryMethodInvokerConfig;
472     }
473 
474     /**
475      * @see org.kuali.rice.krad.uif.component.Component#completeValidation
476      */
477     public void completeValidation(ValidationTrace tracer) {
478         tracer.addBean("AttributeQuery", ValidationTrace.NO_BEAN_ID);
479 
480         // Checks that at least one aspect is set
481         if (getDataObjectClassName() == null
482                 && getQueryMethodToCall() == null
483                 && getQueryMethodInvokerConfig() == null) {
484             String currentValues[] = {"dataObjectClassName = " + getDataObjectClassName(),
485                     "queryMethodToCall = " + getQueryMethodToCall(),
486                     "queryMethodInvokerConfig = " + getQueryMethodInvokerConfig()};
487             tracer.createWarning(
488                     "At least 1 should be set: dataObjectClass, queryMethodToCall or queryMethodInvokerConfig",
489                     currentValues);
490         }
491     }
492 
493     /**
494      * Returns a copy of the attribute query.
495      *
496      * @return AttributeQuery copy of the component
497      */
498     public <T> T copy() {
499         T copiedClass = null;
500         try {
501             copiedClass = (T) this.getClass().newInstance();
502         } catch (Exception exception) {
503             throw new RuntimeException();
504         }
505 
506         copyProperties(copiedClass);
507 
508         return copiedClass;
509     }
510 
511     /**
512      * Copies the properties over for the copy method.
513      *
514      * @param attributeQuery The AttributeQuery to copy
515      */
516     protected <T> void copyProperties(T attributeQuery) {
517         super.copyProperties(attributeQuery);
518         AttributeQuery attributeQueryCopy = (AttributeQuery) attributeQuery;
519 
520         if (this.additionalCriteria != null) {
521             attributeQueryCopy.setAdditionalCriteria(new HashMap<String, String>(this.additionalCriteria));
522         }
523 
524         attributeQueryCopy.setDataObjectClassName(this.dataObjectClassName);
525 
526         if (this.queryFieldMapping != null) {
527             attributeQueryCopy.setQueryFieldMapping(new HashMap<String, String>(this.queryFieldMapping));
528         }
529 
530         if (this.queryMethodArgumentFieldList != null) {
531             attributeQueryCopy.setQueryMethodArgumentFieldList(new ArrayList<String>(
532                     this.queryMethodArgumentFieldList));
533         }
534 
535         attributeQueryCopy.setQueryMethodToCall(this.queryMethodToCall);
536         attributeQueryCopy.setRenderNotFoundMessage(this.renderNotFoundMessage);
537 
538         if (this.returnFieldMapping != null) {
539             attributeQueryCopy.setReturnFieldMapping(new HashMap<String, String>(this.returnFieldMapping));
540         }
541 
542         attributeQueryCopy.setReturnMessageStyleClasses(this.returnMessageStyleClasses);
543         attributeQueryCopy.setReturnMessageText(this.returnMessageText);
544 
545         if (this.sortPropertyNames != null) {
546             attributeQueryCopy.setSortPropertyNames(new ArrayList<String>(this.sortPropertyNames));
547         }
548 
549         if (this.queryMethodInvokerConfig != null) {
550             ((AttributeQuery) attributeQuery).setQueryMethodInvokerConfig(CloneUtils.deepClone(
551                     this.queryMethodInvokerConfig));
552         }
553     }
554 }