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.web.service.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.krad.lookup.LookupUtils;
020import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
021import org.kuali.rice.krad.service.ModuleService;
022import org.kuali.rice.krad.uif.UifConstants;
023import org.kuali.rice.krad.uif.UifParameters;
024import org.kuali.rice.krad.uif.UifPropertyPaths;
025import org.kuali.rice.krad.uif.field.AttributeQueryResult;
026import org.kuali.rice.krad.uif.service.AttributeQueryService;
027import org.kuali.rice.krad.util.KRADUtils;
028import org.kuali.rice.krad.web.form.UifFormBase;
029import org.kuali.rice.krad.web.service.ModelAndViewService;
030import org.kuali.rice.krad.web.service.QueryControllerService;
031import org.springframework.web.servlet.ModelAndView;
032
033import javax.servlet.http.HttpServletRequest;
034import java.util.HashMap;
035import java.util.Map;
036import java.util.Properties;
037
038/**
039 * Default implementation of the query controller service.
040 *
041 * @author Kuali Rice Team (rice.collab@kuali.org)
042 */
043public class QueryControllerServiceImpl implements QueryControllerService {
044    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
045            QueryControllerServiceImpl.class);
046
047    private ModelAndViewService modelAndViewService;
048    private AttributeQueryService attributeQueryService;
049
050    /**
051     * Inspects the given request and action parameters on the form to build a URL to the requested
052     * lookup view.
053     *
054     * <p>First the data object class for the lookup view is found from the action parameters. A call
055     * is then made to check if there is a module service that handles that class, and if so the URL from the
056     * module service is used. If not, the base url is and other lookup URL parameters are created from the
057     * action parameters and form.</p>
058     *
059     * {@inheritDoc}
060     *
061     * @see QueryControllerServiceImpl#getLookupDataObjectClass(java.util.Properties)
062     * @see QueryControllerServiceImpl#getLookupUrlFromModuleService(java.lang.Class<?>, java.util.Properties)
063     * @see QueryControllerServiceImpl#buildLookupUrlParameters(org.kuali.rice.krad.web.form.UifFormBase,
064     * javax.servlet.http.HttpServletRequest, java.lang.Class<?>, java.util.Properties)
065     */
066    @Override
067    public ModelAndView performLookup(UifFormBase form) {
068        Properties urlParameters = form.getActionParametersAsProperties();
069
070        Class<?> lookupDataObjectClass = getLookupDataObjectClass(urlParameters);
071        if (lookupDataObjectClass == null) {
072            throw new RuntimeException("Lookup data object class not found for lookup call");
073        }
074
075        // Force skip of dirty check
076        urlParameters.put(UifParameters.PERFORM_DIRTY_CHECK, "false");
077
078        // first give module service the opportunity to build the lookup URL
079        String baseLookupUrl = getLookupUrlFromModuleService(lookupDataObjectClass, urlParameters);
080        if (StringUtils.isNotBlank(baseLookupUrl)) {
081            // url fully built by module service
082            urlParameters = new Properties();
083        } else {
084            baseLookupUrl = urlParameters.getProperty(UifParameters.BASE_LOOKUP_URL);
085            urlParameters.remove(UifParameters.BASE_LOOKUP_URL);
086
087            buildLookupUrlParameters(form, form.getRequest(), lookupDataObjectClass, urlParameters);
088        }
089
090        return getModelAndViewService().performRedirect(form, baseLookupUrl, urlParameters);
091    }
092
093    /**
094     * Returns the Class instance for the data object whose lookup view was requested.
095     *
096     * @param urlParameters properties containing the lookup configuration
097     * @return Class<?> lookup data object class
098     * @throws java.lang.RuntimeException if class cannot be created from data object class name
099     */
100    protected Class<?> getLookupDataObjectClass(Properties urlParameters) {
101        Class<?> lookupDataObjectClass;
102
103        String lookupObjectClassName = urlParameters.getProperty(UifParameters.DATA_OBJECT_CLASS_NAME);
104        try {
105            lookupDataObjectClass = Class.forName(lookupObjectClassName);
106        } catch (ClassNotFoundException e) {
107            LOG.error("Unable to get class for name: " + lookupObjectClassName);
108            throw new RuntimeException("Unable to get class for name: " + lookupObjectClassName, e);
109        }
110
111        return lookupDataObjectClass;
112    }
113
114    /**
115     * Attempts to find a module service that claims responsibility for the given data object class and if
116     * found invokes that module service to build the lookup url.
117     *
118     * @param lookupDataObjectClass data object class to find responsible module service for
119     * @param urlParameters properties containing the lookup configuration
120     * @return String lookup URL returned from module service, or null if not module service was found
121     */
122    protected String getLookupUrlFromModuleService(Class<?> lookupDataObjectClass, Properties urlParameters) {
123        String lookupUrl = null;
124
125        ModuleService responsibleModuleService =
126                KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(lookupDataObjectClass);
127        if (responsibleModuleService != null && responsibleModuleService.isExternalizable(lookupDataObjectClass)) {
128            lookupUrl = responsibleModuleService.getExternalizableDataObjectLookupUrl(lookupDataObjectClass,
129                    urlParameters);
130        }
131
132        return lookupUrl;
133    }
134
135    /**
136     * Modifies the given properties object representing the lookup URL parameters to add additional parameters
137     * based on the form and action parameters.
138     *
139     * @param form form instance containing the model data
140     * @param request http request object being handled
141     * @param lookupDataObjectClass data object class the lookup URL is being built for
142     * @param urlParameters properties instance holding the lookup URL parameters
143     */
144    protected void buildLookupUrlParameters(UifFormBase form, HttpServletRequest request,
145            Class<?> lookupDataObjectClass, Properties urlParameters) {
146        urlParameters.setProperty(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.START);
147
148        String autoSearchString = urlParameters.getProperty(UifParameters.AUTO_SEARCH);
149        if (Boolean.parseBoolean(autoSearchString)) {
150            urlParameters.setProperty(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.SEARCH);
151        }
152
153        buildLookupCriteriaParameters(form, request, lookupDataObjectClass, urlParameters);
154
155        urlParameters.setProperty(UifParameters.RETURN_LOCATION, form.getFormPostUrl());
156        urlParameters.setProperty(UifParameters.RETURN_FORM_KEY, form.getFormKey());       
157    }
158
159    /**
160     * If lookup criteria parameters were configured, pulls the values for those parameters from the form and
161     * passes as values to pre-populate the lookup view criteria.
162     *
163     * @param form form instance containing the model data
164     * @param request http request object being handled
165     * @param lookupDataObjectClass data object class the lookup URL is being built for
166     * @param urlParameters properties instance holding the lookup URL parameters
167     */
168    protected void buildLookupCriteriaParameters(UifFormBase form, HttpServletRequest request,
169            Class<?> lookupDataObjectClass, Properties urlParameters) {
170        String lookupParameterString = urlParameters.getProperty(UifParameters.LOOKUP_PARAMETERS);
171        if (StringUtils.isBlank(lookupParameterString)) {
172            return;
173        }
174
175        Map<String, String> lookupParameterFields = KRADUtils.getMapFromParameterString(lookupParameterString);
176        for (Map.Entry<String, String> lookupParameter : lookupParameterFields.entrySet()) {
177            String lookupParameterValue = LookupUtils.retrieveLookupParameterValue(form, request, lookupDataObjectClass,
178                    lookupParameter.getValue(), lookupParameter.getKey());
179
180            if (StringUtils.isNotBlank(lookupParameterValue)) {
181                urlParameters.setProperty(UifPropertyPaths.LOOKUP_CRITERIA + "['" + lookupParameter.getValue() + "']",
182                        lookupParameterValue);
183            }
184        }
185
186        urlParameters.remove(UifParameters.LOOKUP_PARAMETERS);
187    }
188
189    /**
190     * Retrieves suggest query parameters from the request and invokes
191     * {@link org.kuali.rice.krad.uif.service.AttributeQueryService#performFieldSuggestQuery} to carry out the
192     * suggest query.
193     *
194     * {@inheritDoc}
195     */
196    @Override
197    public AttributeQueryResult performFieldSuggest(UifFormBase form) {
198        HttpServletRequest request = form.getRequest();
199
200        // retrieve query fields from request
201        Map<String, String> queryParameters = new HashMap<String, String>();
202        for (Object parameterName : request.getParameterMap().keySet()) {
203            if (parameterName.toString().startsWith(UifParameters.QUERY_PARAMETERS)) {
204                String fieldName = StringUtils.substringBetween(parameterName.toString(),
205                        UifParameters.QUERY_PARAMETERS + "[\"", "\"]");
206                String fieldValue = request.getParameter(parameterName.toString());
207                queryParameters.put(fieldName, fieldValue);
208            }
209        }
210
211        // retrieve id for field to perform query for
212        String queryFieldId = request.getParameter(UifParameters.QUERY_FIELD_ID);
213        if (StringUtils.isBlank(queryFieldId)) {
214            throw new RuntimeException("Unable to find id for field to perform query on under request parameter name: "
215                    + UifParameters.QUERY_FIELD_ID);
216        }
217
218        // get the field term to match
219        String queryTerm = request.getParameter(UifParameters.QUERY_TERM);
220        if (StringUtils.isBlank(queryTerm)) {
221            throw new RuntimeException(
222                    "Unable to find id for query term value for attribute query on under request parameter name: "
223                            + UifParameters.QUERY_TERM);
224        }
225
226        return getAttributeQueryService().performFieldSuggestQuery(form.getViewPostMetadata(), queryFieldId, queryTerm,
227                queryParameters);
228    }
229
230    /**
231     * Retrieves field query parameters from the request and invokes
232     * {@link org.kuali.rice.krad.uif.service.AttributeQueryService#performFieldQuery} to carry out the
233     * field query.
234     *
235     * {@inheritDoc}
236     */
237    @Override
238    public AttributeQueryResult performFieldQuery(UifFormBase form) {
239        HttpServletRequest request = form.getRequest();
240
241        // retrieve query fields from request
242        Map<String, String> queryParameters = new HashMap<String, String>();
243        for (Object parameterName : request.getParameterMap().keySet()) {
244            if (parameterName.toString().startsWith(UifParameters.QUERY_PARAMETERS)) {
245                String fieldName = StringUtils.substringBetween(parameterName.toString(),
246                        UifParameters.QUERY_PARAMETERS + "[\"", "\"]");
247                String fieldValue = request.getParameter(parameterName.toString());
248                queryParameters.put(fieldName, fieldValue);
249            }
250        }
251
252        // retrieve id for field to perform query for
253        String queryFieldId = request.getParameter(UifParameters.QUERY_FIELD_ID);
254        if (StringUtils.isBlank(queryFieldId)) {
255            throw new RuntimeException("Unable to find id for field to perform query on under request parameter name: "
256                    + UifParameters.QUERY_FIELD_ID);
257        }
258
259        return getAttributeQueryService().performFieldQuery(form.getViewPostMetadata(), queryFieldId, queryParameters);
260    }
261
262    /**
263     * Instance of model and view service to use within the collection service.
264     *
265     * @return ModelAndViewService instance
266     */
267    protected ModelAndViewService getModelAndViewService() {
268        return modelAndViewService;
269    }
270
271    /**
272     * @see CollectionControllerServiceImpl#getModelAndViewService()
273     */
274    public void setModelAndViewService(ModelAndViewService modelAndViewService) {
275        this.modelAndViewService = modelAndViewService;
276    }
277
278    public AttributeQueryService getAttributeQueryService() {
279        return attributeQueryService;
280    }
281
282    public void setAttributeQueryService(AttributeQueryService attributeQueryService) {
283        this.attributeQueryService = attributeQueryService;
284    }
285}