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}