001/**
002 * Copyright 2005-2016 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.service.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.config.property.ConfigurationService;
020import org.kuali.rice.krad.service.KRADServiceLocator;
021import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
022import org.kuali.rice.krad.service.LookupService;
023import org.kuali.rice.krad.uif.UifConstants;
024import org.kuali.rice.krad.uif.view.View;
025import org.kuali.rice.krad.uif.component.MethodInvokerConfig;
026import org.kuali.rice.krad.uif.field.InputField;
027import org.kuali.rice.krad.uif.field.AttributeQuery;
028import org.kuali.rice.krad.uif.field.AttributeQueryResult;
029import org.kuali.rice.krad.uif.service.AttributeQueryService;
030import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
031import org.kuali.rice.krad.uif.widget.Suggest;
032import org.kuali.rice.krad.util.BeanPropertyComparator;
033
034import java.text.MessageFormat;
035import java.util.ArrayList;
036import java.util.Collection;
037import java.util.Collections;
038import java.util.HashMap;
039import java.util.List;
040import java.util.Map;
041
042/**
043 * Implementation of <code>AttributeQueryService</code> that prepares the attribute queries and
044 * delegates to the <code>LookupService</code>
045 *
046 * @author Kuali Rice Team (rice.collab@kuali.org)
047 */
048public class AttributeQueryServiceImpl implements AttributeQueryService {
049
050    private LookupService lookupService;
051    private ConfigurationService configurationService;
052
053    /**
054     * @see org.kuali.rice.krad.uif.service.AttributeQueryService#performFieldSuggestQuery(
055     *org.kuali.rice.krad.uif.view.View, java.lang.String, java.lang.String, java.util.Map<java.lang.String,
056     *      java.lang.String>)
057     */
058    @Override
059    public AttributeQueryResult performFieldSuggestQuery(View view, String fieldId, String fieldTerm,
060            Map<String, String> queryParameters) {
061        AttributeQueryResult queryResult = new AttributeQueryResult();
062
063        // retrieve attribute field from view index
064        InputField inputField = (InputField) view.getViewIndex().getComponentById(fieldId);
065        if (inputField == null) {
066            throw new RuntimeException("Unable to find attribute field instance for id: " + fieldId);
067        }
068
069        Suggest fieldSuggest = inputField.getFieldSuggest();
070        AttributeQuery suggestQuery = fieldSuggest.getSuggestQuery();
071
072        // add term as a like criteria
073        Map<String, String> additionalCriteria = new HashMap<String, String>();
074        additionalCriteria.put(fieldSuggest.getSourcePropertyName(), fieldTerm + "*");
075
076        // execute suggest query
077        Collection<?> results = null;
078        if (suggestQuery.hasConfiguredMethod()) {
079            Object queryMethodResult = executeAttributeQueryMethod(view, suggestQuery, queryParameters);
080            if ((queryMethodResult != null) && (queryMethodResult instanceof Collection<?>)) {
081                results = (Collection<?>) queryMethodResult;
082            }
083        } else {
084            results = executeAttributeQueryCriteria(suggestQuery, queryParameters, additionalCriteria);
085        }
086
087        // build list of suggest data from result records
088        if (results != null) {
089            List<String> suggestData = new ArrayList<String>();
090            for (Object result : results) {
091                Object suggestFieldValue =
092                        ObjectPropertyUtils.getPropertyValue(result, fieldSuggest.getSourcePropertyName());
093                if (suggestFieldValue != null) {
094                    // TODO: need to apply formatter for field or have method in object property utils
095                    suggestData.add(suggestFieldValue.toString());
096                }
097            }
098
099            queryResult.setResultData(suggestData);
100        }
101
102        return queryResult;
103    }
104
105    /**
106     * @see org.kuali.rice.krad.uif.service.AttributeQueryService#performFieldQuery(org.kuali.rice.krad.uif.view.View,
107     *      java.lang.String, java.util.Map<java.lang.String,java.lang.String>)
108     */
109    @Override
110    public AttributeQueryResult performFieldQuery(View view, String fieldId, Map<String, String> queryParameters) {
111        AttributeQueryResult queryResult = new AttributeQueryResult();
112
113        // retrieve attribute field from view index
114        InputField inputField = (InputField) view.getViewIndex().getComponentById(fieldId);
115        if (inputField == null) {
116            throw new RuntimeException("Unable to find attribute field instance for id: " + fieldId);
117        }
118
119        AttributeQuery fieldQuery = inputField.getFieldAttributeQuery();
120        if (fieldQuery == null) {
121            throw new RuntimeException("Field query not defined for field instance with id: " + fieldId);
122        }
123
124        // execute query and get result
125        Object resultObject = null;
126        if (fieldQuery.hasConfiguredMethod()) {
127            Object queryMethodResult = executeAttributeQueryMethod(view, fieldQuery, queryParameters);
128            if (queryMethodResult != null) {
129                // if method returned the result then no further processing needed
130                if (queryMethodResult instanceof AttributeQueryResult) {
131                    return (AttributeQueryResult) queryMethodResult;
132                }
133
134                // if method returned collection, take first record
135                if (queryMethodResult instanceof Collection<?>) {
136                    Collection<?> methodResultCollection = (Collection<?>) queryMethodResult;
137                    if (!methodResultCollection.isEmpty()) {
138                        resultObject = methodResultCollection.iterator().next();
139                    }
140                } else {
141                    resultObject = queryMethodResult;
142                }
143            }
144        } else {
145            // execute field query as object lookup
146            Collection<?> results = executeAttributeQueryCriteria(fieldQuery, queryParameters, null);
147
148            if ((results != null) && !results.isEmpty()) {
149                // expect only one returned row for field query
150                if (results.size() > 1) {
151                    //finding too many results in a not found message (not specific enough)
152                    resultObject = null;
153                }
154                else{
155                    resultObject = results.iterator().next();
156                }
157            }
158        }
159
160        if (resultObject != null) {
161            // build result field data map
162            Map<String, String> resultFieldData = new HashMap<String, String>();
163            for (String fromField : fieldQuery.getReturnFieldMapping().keySet()) {
164                String returnField = fieldQuery.getReturnFieldMapping().get(fromField);
165
166                String fieldValueStr = "";
167                Object fieldValue = ObjectPropertyUtils.getPropertyValue(resultObject, fromField);
168                if (fieldValue != null) {
169                    fieldValueStr = fieldValue.toString();
170                }
171                resultFieldData.put(returnField, fieldValueStr);
172            }
173            queryResult.setResultFieldData(resultFieldData);
174
175            fieldQuery.setReturnMessageText("");
176        } else {
177            // add data not found message
178            if (fieldQuery.isRenderNotFoundMessage()) {
179                String messageTemplate =
180                        getConfigurationService().getPropertyValueAsString(
181                                UifConstants.MessageKeys.QUERY_DATA_NOT_FOUND);
182                String message = MessageFormat.format(messageTemplate, inputField.getLabel());
183                fieldQuery.setReturnMessageText(message.toLowerCase());
184            }
185        }
186
187        // set message and message style classes on query result
188        queryResult.setResultMessage(fieldQuery.getReturnMessageText());
189        queryResult.setResultMessageStyleClasses(fieldQuery.getReturnMessageStyleClasses());
190
191        return queryResult;
192    }
193
194    /**
195     * Prepares the method configured on the attribute query then performs the method invocation
196     *
197     * @param view - view instance the field is contained within
198     * @param attributeQuery - attribute query instance to execute
199     * @param queryParameters - map of query parameters that provide values for the method arguments
200     * @return Object type depends on method being invoked, could be AttributeQueryResult in which
201     *         case the method has prepared the return result, or an Object that needs to be parsed for the result
202     */
203    protected Object executeAttributeQueryMethod(View view, AttributeQuery attributeQuery,
204            Map<String, String> queryParameters) {
205        String queryMethodToCall = attributeQuery.getQueryMethodToCall();
206        MethodInvokerConfig queryMethodInvoker = attributeQuery.getQueryMethodInvokerConfig();
207
208        if (queryMethodInvoker == null) {
209            queryMethodInvoker = new MethodInvokerConfig();
210        }
211
212        // if method not set on invoker, use queryMethodToCall, note staticMethod could be set(don't know since
213        // there is not a getter), if so it will override the target method in prepare
214        if (StringUtils.isBlank(queryMethodInvoker.getTargetMethod())) {
215            queryMethodInvoker.setTargetMethod(queryMethodToCall);
216        }
217
218        // if target class or object not set, use view helper service
219        if ((queryMethodInvoker.getTargetClass() == null) && (queryMethodInvoker.getTargetObject() == null)) {
220            queryMethodInvoker.setTargetObject(view.getViewHelperService());
221        }
222
223        // setup query method arguments
224        Object[] arguments = null;
225        if ((attributeQuery.getQueryMethodArgumentFieldList() != null) &&
226                (!attributeQuery.getQueryMethodArgumentFieldList().isEmpty())) {
227            // retrieve argument types for conversion
228            Class[] argumentTypes = queryMethodInvoker.getArgumentTypes();
229            if ((argumentTypes == null) ||
230                    (argumentTypes.length != attributeQuery.getQueryMethodArgumentFieldList().size())) {
231                throw new RuntimeException(
232                        "Query method argument field list size does not match found method argument list size");
233            }
234
235            arguments = new Object[attributeQuery.getQueryMethodArgumentFieldList().size()];
236            for (int i = 0; i < attributeQuery.getQueryMethodArgumentFieldList().size(); i++) {
237                String methodArgumentFromField = attributeQuery.getQueryMethodArgumentFieldList().get(i);
238                if (queryParameters.containsKey(methodArgumentFromField)) {
239                    arguments[i] = queryParameters.get(methodArgumentFromField);
240                } else {
241                    arguments[i] = null;
242                }
243            }
244        }
245        queryMethodInvoker.setArguments(arguments);
246
247        try {
248            queryMethodInvoker.prepare();
249
250            return queryMethodInvoker.invoke();
251        } catch (Exception e) {
252            throw new RuntimeException("Unable to invoke query method: " + queryMethodInvoker.getTargetMethod(), e);
253        }
254    }
255
256    /**
257     * Prepares a query using the configured data object, parameters, and criteria, then executes
258     * the query and returns the result Collection
259     *
260     * @param attributeQuery - attribute query instance to perform query for
261     * @param queryParameters - map of parameters that will be used in the query criteria
262     * @param additionalCriteria - map of additional name/value pairs to add to the critiera
263     * @return Collection<?> results of query
264     */
265    protected Collection<?> executeAttributeQueryCriteria(AttributeQuery attributeQuery,
266            Map<String, String> queryParameters, Map<String, String> additionalCriteria) {
267        Collection<?> results = null;
268
269        // build criteria for query
270        Map<String, String> queryCriteria = new HashMap<String, String>();
271        for (String fieldName : attributeQuery.getQueryFieldMapping().values()) {
272            if (queryParameters.containsKey(fieldName) && StringUtils.isNotBlank(queryParameters.get(fieldName))) {
273                queryCriteria.put(fieldName, queryParameters.get(fieldName));
274            }
275        }
276
277        // add any static criteria
278        for (String fieldName : attributeQuery.getAdditionalCriteria().keySet()) {
279            queryCriteria.put(fieldName, attributeQuery.getAdditionalCriteria().get(fieldName));
280        }
281
282        // add additional criteria
283        if (additionalCriteria != null) {
284            queryCriteria.putAll(additionalCriteria);
285        }
286
287        Class<?> queryClass = null;
288        try {
289            queryClass = Class.forName(attributeQuery.getDataObjectClassName());
290        } catch (ClassNotFoundException e) {
291            throw new RuntimeException(
292                    "Invalid data object class given for suggest query: " + attributeQuery.getDataObjectClassName(), e);
293        }
294
295        // run query
296        results = getLookupService().findCollectionBySearchUnbounded(queryClass, queryCriteria);
297
298        // sort results
299        if (!attributeQuery.getSortPropertyNames().isEmpty() && (results != null) && (results.size() > 1)) {
300            Collections.sort((List<?>) results, new BeanPropertyComparator(attributeQuery.getSortPropertyNames()));
301        }
302
303        return results;
304    }
305
306    protected LookupService getLookupService() {
307        if (lookupService == null) {
308            lookupService = KRADServiceLocatorWeb.getLookupService();
309        }
310
311        return lookupService;
312    }
313
314    public void setLookupService(LookupService lookupService) {
315        this.lookupService = lookupService;
316    }
317
318    protected ConfigurationService getConfigurationService() {
319        if (configurationService == null) {
320            configurationService = KRADServiceLocator.getKualiConfigurationService();
321        }
322
323        return configurationService;
324    }
325
326    public void setConfigurationService(ConfigurationService configurationService) {
327        this.configurationService = configurationService;
328    }
329}