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.web.controller;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.config.property.ConfigContext;
020import org.kuali.rice.core.web.format.BooleanFormatter;
021import org.kuali.rice.kim.api.identity.Person;
022import org.kuali.rice.krad.exception.AuthorizationException;
023import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
024import org.kuali.rice.krad.service.ModuleService;
025import org.kuali.rice.krad.uif.UifConstants;
026import org.kuali.rice.krad.uif.UifParameters;
027import org.kuali.rice.krad.uif.UifPropertyPaths;
028import org.kuali.rice.krad.uif.component.Component;
029import org.kuali.rice.krad.uif.container.CollectionGroup;
030import org.kuali.rice.krad.uif.field.AttributeQueryResult;
031import org.kuali.rice.krad.uif.service.ViewService;
032import org.kuali.rice.krad.uif.util.ComponentFactory;
033import org.kuali.rice.krad.uif.util.LookupInquiryUtils;
034import org.kuali.rice.krad.uif.util.UifFormManager;
035import org.kuali.rice.krad.uif.util.UifWebUtils;
036import org.kuali.rice.krad.uif.view.History;
037import org.kuali.rice.krad.uif.view.HistoryEntry;
038import org.kuali.rice.krad.uif.view.View;
039import org.kuali.rice.krad.util.GlobalVariables;
040import org.kuali.rice.krad.util.KRADConstants;
041import org.kuali.rice.krad.util.KRADUtils;
042import org.kuali.rice.krad.util.UrlFactory;
043import org.kuali.rice.krad.web.form.UifFormBase;
044import org.springframework.validation.BindingResult;
045import org.springframework.web.bind.annotation.ModelAttribute;
046import org.springframework.web.bind.annotation.RequestMapping;
047import org.springframework.web.bind.annotation.RequestMethod;
048import org.springframework.web.bind.annotation.ResponseBody;
049import org.springframework.web.servlet.ModelAndView;
050
051import javax.servlet.http.HttpServletRequest;
052import javax.servlet.http.HttpServletResponse;
053import java.util.HashMap;
054import java.util.List;
055import java.util.Map;
056import java.util.Map.Entry;
057import java.util.Properties;
058
059/**
060 * Base controller class for views within the KRAD User Interface Framework
061 *
062 * Provides common methods such as:
063 *
064 * <ul>
065 * <li>Authorization methods such as method to call check</li>
066 * <li>Preparing the View instance and setup in the returned
067 * <code>ModelAndView</code></li>
068 * </ul>
069 *
070 * All subclass controller methods after processing should call one of the
071 * #getUIFModelAndView methods to setup the <code>View</code> and return the
072 * <code>ModelAndView</code> instance.
073 *
074 * @author Kuali Rice Team (rice.collab@kuali.org)
075 */
076public abstract class UifControllerBase {
077    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(UifControllerBase.class);
078
079    protected static final String REDIRECT_PREFIX = "redirect:";
080
081    /**
082     * Create/obtain the model(form) object before it is passed to the Binder/BeanWrapper. This method
083     * is not intended to be overridden by client applications as it handles framework setup and session
084     * maintenance. Clients should override createIntialForm() instead when they need custom form initialization.
085     *
086     * @param request - the http request that was made
087     */
088    @ModelAttribute(value = "KualiForm")
089    public final UifFormBase initForm(HttpServletRequest request) {
090        UifFormBase form = null;
091
092        // get Uif form manager from session if exists or setup a new one for the session
093        UifFormManager uifFormManager = (UifFormManager) request.getSession().getAttribute(
094                UifParameters.FORM_MANAGER);
095        if (uifFormManager == null) {
096            uifFormManager = new UifFormManager();
097            request.getSession().setAttribute(UifParameters.FORM_MANAGER, uifFormManager);
098        }
099
100        // add form manager to GlobalVariables for easy reference by other controller methods
101        GlobalVariables.setUifFormManager(uifFormManager);
102
103        String formKeyParam = request.getParameter(UifParameters.FORM_KEY);
104        if (StringUtils.isNotBlank(formKeyParam)) {
105            form = uifFormManager.getForm(formKeyParam);
106        } 
107
108        // if form not in manager, create a new form
109        if (form == null) {
110            form = createInitialForm(request);
111        }
112
113        uifFormManager.setCurrentForm(form);
114
115        return form;
116    }
117
118    /**
119     * Called to create a new model(form) object when necessary. This usually occurs on the initial request
120     * in a conversation (when the model is not present in the session). This method must be
121     * overridden when extending a controller and using a different form type than the superclass.
122     *
123     * @param request - the http request that was made
124     */
125    protected abstract UifFormBase createInitialForm(HttpServletRequest request);
126
127    /**
128     * Initial method called when requesting a new view instance which checks authorization and forwards
129     * the view for rendering
130     */
131    @RequestMapping(params = "methodToCall=start")
132    public ModelAndView start(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
133            HttpServletRequest request, HttpServletResponse response) {
134
135        // check view authorization
136        // TODO: this needs to be invoked for each request
137        if (form.getView() != null) {
138            String methodToCall = request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER);
139            checkViewAuthorization(form, methodToCall);
140        }
141
142        return getUIFModelAndView(form);
143    }
144
145    /**
146     * Invokes the configured {@link org.kuali.rice.krad.uif.view.ViewAuthorizer} to verify the user has access to
147     * open the view. An exception is thrown if access has not been granted
148     *
149     * <p>
150     * Note this method is invoked automatically by the controller interceptor for each request
151     * </p>
152     *
153     * @param form - form instance containing the request data
154     * @param methodToCall - the request parameter 'methodToCall' which is used to determine the controller
155     * method invoked
156     */
157    public void checkViewAuthorization(UifFormBase form, String methodToCall) throws AuthorizationException {
158        Person user = GlobalVariables.getUserSession().getPerson();
159
160        boolean canOpenView = form.getView().getAuthorizer().canOpenView(form.getView(), form, user);
161        if (!canOpenView) {
162            throw new AuthorizationException(user.getPrincipalName(), "open", form.getView().getId(),
163                    "User '" + user.getPrincipalName() + "' is not authorized to open view ID: " + form.getView()
164                            .getId(), null);
165        }
166    }
167
168    /**
169     * Called by the add line action for a new collection line. Method
170     * determines which collection the add action was selected for and invokes
171     * the view helper service to add the line
172     */
173    @RequestMapping(method = RequestMethod.POST, params = "methodToCall=addLine")
174    public ModelAndView addLine(@ModelAttribute("KualiForm") UifFormBase uifForm, BindingResult result,
175            HttpServletRequest request, HttpServletResponse response) {
176
177        String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELLECTED_COLLECTION_PATH);
178        if (StringUtils.isBlank(selectedCollectionPath)) {
179            throw new RuntimeException("Selected collection was not set for add line action, cannot add new line");
180        }
181
182        View view = uifForm.getPostedView();
183        view.getViewHelperService().processCollectionAddLine(view, uifForm, selectedCollectionPath);
184
185        return updateComponent(uifForm, result, request, response);
186    }
187
188    /**
189     * Called by the delete line action for a model collection. Method
190     * determines which collection the action was selected for and the line
191     * index that should be removed, then invokes the view helper service to
192     * process the action
193     */
194    @RequestMapping(method = RequestMethod.POST, params = "methodToCall=deleteLine")
195    public ModelAndView deleteLine(@ModelAttribute("KualiForm") UifFormBase uifForm, BindingResult result,
196            HttpServletRequest request, HttpServletResponse response) {
197
198        String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELLECTED_COLLECTION_PATH);
199        if (StringUtils.isBlank(selectedCollectionPath)) {
200            throw new RuntimeException("Selected collection was not set for delete line action, cannot delete line");
201        }
202
203        int selectedLineIndex = -1;
204        String selectedLine = uifForm.getActionParamaterValue(UifParameters.SELECTED_LINE_INDEX);
205        if (StringUtils.isNotBlank(selectedLine)) {
206            selectedLineIndex = Integer.parseInt(selectedLine);
207        }
208
209        if (selectedLineIndex == -1) {
210            throw new RuntimeException("Selected line index was not set for delete line action, cannot delete line");
211        }
212
213        View view = uifForm.getPostedView();
214        view.getViewHelperService().processCollectionDeleteLine(view, uifForm, selectedCollectionPath,
215                selectedLineIndex);
216
217        return updateComponent(uifForm, result, request, response);
218    }
219
220    /**
221     * Invoked to toggle the show inactive indicator on the selected collection group and then
222     * rerun the component lifecycle and rendering based on the updated indicator and form data
223     *
224     * @param request - request object that should contain the request component id (for the collection group)
225     * and the show inactive indicator value
226     */
227    @RequestMapping(method = RequestMethod.POST, params = "methodToCall=toggleInactiveRecordDisplay")
228    public ModelAndView toggleInactiveRecordDisplay(@ModelAttribute("KualiForm") UifFormBase uifForm,
229            BindingResult result, HttpServletRequest request, HttpServletResponse response) {
230        String collectionGroupId = request.getParameter(UifParameters.REQUESTED_COMPONENT_ID);
231        if (StringUtils.isBlank(collectionGroupId)) {
232            throw new RuntimeException(
233                    "Collection group id to update for inactive record display not found in request");
234        }
235
236        String showInactiveStr = request.getParameter(UifParameters.SHOW_INACTIVE_RECORDS);
237        Boolean showInactive = false;
238        if (StringUtils.isNotBlank(showInactiveStr)) {
239            // TODO: should use property editors once we have util class
240            showInactive = (Boolean) (new BooleanFormatter()).convertFromPresentationFormat(showInactiveStr);
241        } else {
242            throw new RuntimeException("Show inactive records flag not found in request");
243        }
244
245        CollectionGroup collectionGroup = (CollectionGroup) ComponentFactory.getNewInstanceForRefresh(
246                uifForm.getPostedView(), collectionGroupId);
247
248        // update inactive flag on group
249        collectionGroup.setShowInactive(showInactive);
250
251        // run lifecycle and update in view
252        uifForm.getPostedView().getViewHelperService().performComponentLifecycle(uifForm.getPostedView(), uifForm,
253                collectionGroup, collectionGroupId);
254
255        return UifWebUtils.getComponentModelAndView(collectionGroup, uifForm);
256    }
257
258    /**
259     * Just returns as if return with no value was selected.
260     */
261    @RequestMapping(params = "methodToCall=cancel")
262    public ModelAndView cancel(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
263            HttpServletRequest request, HttpServletResponse response) {
264        return close(form, result, request, response);
265    }
266
267    /**
268     * Just returns as if return with no value was selected.
269     */
270    @RequestMapping(params = "methodToCall=close")
271    public ModelAndView close(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
272            HttpServletRequest request, HttpServletResponse response) {
273        Properties props = new Properties();
274        props.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.REFRESH);
275        if (StringUtils.isNotBlank(form.getReturnFormKey())) {
276            props.put(UifParameters.FORM_KEY, form.getReturnFormKey());
277        }
278
279        // TODO this needs setup for lightbox and possible home location
280        // property
281        String returnUrl = form.getReturnLocation();
282        if (StringUtils.isBlank(returnUrl)) {
283            returnUrl = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.APPLICATION_URL_KEY);
284        }
285
286        // clear current form from session
287        GlobalVariables.getUifFormManager().removeForm(form);
288
289        return performRedirect(form, returnUrl, props);
290    }
291
292    /**
293     * Invoked to navigate back one page in history..
294     *
295     * @param form - form object that should contain the history object
296     */
297    @RequestMapping(params = "methodToCall=returnToPrevious")
298    public ModelAndView returnToPrevious(@ModelAttribute("KualiForm") UifFormBase form) {
299
300        return returnToHistory(form, false);
301    }
302
303    /**
304     * Invoked to navigate back to the first page in history.
305     *
306     * @param form - form object that should contain the history object
307     */
308    @RequestMapping(params = "methodToCall=returnToHub")
309    public ModelAndView returnToHub(@ModelAttribute("KualiForm") UifFormBase form) {
310
311        return returnToHistory(form, true);
312    }
313
314    /**
315     * Invoked to navigate back to a history entry. The homeFlag will determine whether navigation
316     * will be back to the first or last history entry.
317     *
318     * @param form - form object that should contain the history object
319     * @param homeFlag - if true will navigate back to first entry else will navigate to last entry
320     * in the history
321     */
322    public ModelAndView returnToHistory(UifFormBase form, boolean homeFlag) {
323        // Get the history from the form
324        History hist = form.getFormHistory();
325        List<HistoryEntry> histEntries = hist.getHistoryEntries();
326
327        // Get the history page url. Default to the application url if there is no history.
328        String histUrl = null;
329        if (histEntries.isEmpty()) {
330            histUrl = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.APPLICATION_URL_KEY);
331        } else {
332            // For home get the first entry, for previous get the last entry.
333            // Remove history up to where page is opened
334            if (homeFlag) {
335                histUrl = histEntries.get(0).getUrl();
336                histEntries.clear();
337            } else {
338                histUrl = histEntries.get(histEntries.size() - 1).getUrl();
339                histEntries.remove(histEntries.size() - 1);
340                hist.setCurrent(null);
341            }
342        }
343
344        // Add the refresh call
345        Properties props = new Properties();
346        props.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.REFRESH);
347
348        // clear current form from session
349        GlobalVariables.getUifFormManager().removeForm(form);
350
351        return performRedirect(form, histUrl, props);
352    }
353
354    /**
355     * Handles menu navigation between view pages
356     */
357    @RequestMapping(method = RequestMethod.POST, params = "methodToCall=navigate")
358    public ModelAndView navigate(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
359            HttpServletRequest request, HttpServletResponse response) {
360        String pageId = form.getActionParamaterValue(UifParameters.NAVIGATE_TO_PAGE_ID);
361
362        // only refreshing page
363        form.setRenderFullView(false);
364
365        return getUIFModelAndView(form, pageId);
366    }
367
368    /**
369     *  handles an ajax refresh
370     *
371     *  <p>The query form plugin  activates this request via a form post, where on the JS side,
372     *  {@code org.kuali.rice.krad.uif.UifParameters#RENDER_FULL_VIEW} is set to false</p>
373     *
374     * @param form  -  Holds properties necessary to determine the <code>View</code> instance that will be used to render the UI
375     * @param result  -   represents binding results
376     * @param request  - http servlet request data
377     * @param response   - http servlet response object
378     * @return  the  ModelAndView object
379     * @throws Exception
380     */
381    @RequestMapping(params = "methodToCall=refresh")
382    public ModelAndView refresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
383            HttpServletRequest request, HttpServletResponse response) throws Exception {
384        // TODO: this code still needs to handle reference refreshes
385        String refreshCallerType = "";
386        if (request.getParameterMap().containsKey(KRADConstants.REFRESH_CALLER_TYPE)) {
387            refreshCallerType = request.getParameter(KRADConstants.REFRESH_CALLER_TYPE);
388        }
389
390        // process multi-value lookup returns
391        if (StringUtils.equals(refreshCallerType, UifConstants.RefreshCallerTypes.MULTI_VALUE_LOOKUP)) {
392            String lookupCollectionName = "";
393            if (request.getParameterMap().containsKey(UifParameters.LOOKUP_COLLECTION_NAME)) {
394                lookupCollectionName = request.getParameter(UifParameters.LOOKUP_COLLECTION_NAME);
395            }
396
397            if (StringUtils.isBlank(lookupCollectionName)) {
398                throw new RuntimeException(
399                        "Lookup collection name is required for processing multi-value lookup results");
400            }
401
402            String selectedLineValues = "";
403            if (request.getParameterMap().containsKey(UifParameters.SELECTED_LINE_VALUES)) {
404                selectedLineValues = request.getParameter(UifParameters.SELECTED_LINE_VALUES);
405            }
406
407            // invoked view helper to populate the collection from lookup results
408            form.getPostedView().getViewHelperService().processMultipleValueLookupResults(form.getPostedView(),
409                    form, lookupCollectionName, selectedLineValues);
410        }
411
412        if (request.getParameterMap().containsKey(UifParameters.RENDER_FULL_VIEW)) {
413            form.setRenderFullView(Boolean.parseBoolean(request.getParameter(UifParameters.RENDER_FULL_VIEW)));
414        } else {
415            form.setRenderFullView(true);
416        }
417
418        return getUIFModelAndView(form);
419    }
420
421    /**
422     * Updates the current component by retrieving a fresh copy from the dictionary,
423     * running its component lifecycle, and returning it
424     *
425     * @param request - the request must contain reqComponentId that specifies the component to retrieve
426     */
427    @RequestMapping(method = RequestMethod.POST, params = "methodToCall=updateComponent")
428    public ModelAndView updateComponent(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
429            HttpServletRequest request, HttpServletResponse response) {
430        String requestedComponentId = request.getParameter(UifParameters.REQUESTED_COMPONENT_ID);
431        if (StringUtils.isBlank(requestedComponentId)) {
432            throw new RuntimeException("Requested component id for update not found in request");
433        }
434
435        // get a new instance of the component
436        Component comp = ComponentFactory.getNewInstanceForRefresh(form.getPostedView(), requestedComponentId);
437        
438        View postedView = form.getPostedView();
439
440        // run lifecycle and update in view
441        postedView.getViewHelperService().performComponentLifecycle(postedView, form, comp,
442                requestedComponentId);
443
444        //Regenerate server message content for page
445        postedView.getCurrentPage().getErrorsField().setDisplayNestedMessages(true);
446        postedView.getCurrentPage().getErrorsField().generateMessages(false, postedView, form,
447                postedView.getCurrentPage());
448
449        return UifWebUtils.getComponentModelAndView(comp, form);
450    }
451
452    /**
453     * Builds up a URL to the lookup view based on the given post action
454     * parameters and redirects
455     */
456    @RequestMapping(method = RequestMethod.POST, params = "methodToCall=performLookup")
457    public ModelAndView performLookup(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
458            HttpServletRequest request, HttpServletResponse response) {
459        Properties lookupParameters = form.getActionParametersAsProperties();
460
461        String lookupObjectClassName = (String) lookupParameters.get(UifParameters.DATA_OBJECT_CLASS_NAME);
462        Class<?> lookupObjectClass = null;
463        try {
464            lookupObjectClass = Class.forName(lookupObjectClassName);
465        } catch (ClassNotFoundException e) {
466            LOG.error("Unable to get class for name: " + lookupObjectClassName);
467            throw new RuntimeException("Unable to get class for name: " + lookupObjectClassName, e);
468        }
469
470        // get form values for the lookup parameter fields
471        String lookupParameterString = (String) lookupParameters.get(UifParameters.LOOKUP_PARAMETERS);
472        if (lookupParameterString != null) {
473            Map<String, String> lookupParameterFields = KRADUtils.getMapFromParameterString(lookupParameterString);
474            for (Entry<String, String> lookupParameter : lookupParameterFields.entrySet()) {
475                String lookupParameterValue = LookupInquiryUtils.retrieveLookupParameterValue(form, request,
476                        lookupObjectClass, lookupParameter.getValue(), lookupParameter.getKey());
477
478                if (StringUtils.isNotBlank(lookupParameterValue)) {
479                    lookupParameters.put(UifPropertyPaths.CRITERIA_FIELDS + "['" + lookupParameter.getValue() + "']",
480                            lookupParameterValue);
481                }
482            }
483        }
484
485        // TODO: lookup anchors and doc number?
486
487        String baseLookupUrl = (String) lookupParameters.get(UifParameters.BASE_LOOKUP_URL);
488        lookupParameters.remove(UifParameters.BASE_LOOKUP_URL);
489
490        // set lookup method to call
491        lookupParameters.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.START);
492        String autoSearchString = (String) lookupParameters.get(UifParameters.AUTO_SEARCH);
493        if (Boolean.parseBoolean(autoSearchString)) {
494            lookupParameters.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.SEARCH);
495        }
496
497        lookupParameters.put(UifParameters.RETURN_LOCATION, form.getFormPostUrl());
498        lookupParameters.put(UifParameters.RETURN_FORM_KEY, form.getFormKey());
499
500        // special check for external object classes
501        if (lookupObjectClass != null) {
502            ModuleService responsibleModuleService =
503                    KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(lookupObjectClass);
504            if (responsibleModuleService != null && responsibleModuleService.isExternalizable(lookupObjectClass)) {
505                String lookupUrl = responsibleModuleService.getExternalizableDataObjectLookupUrl(lookupObjectClass,
506                        lookupParameters);
507
508                Properties externalInquiryProperties = new Properties();
509                if (lookupParameters.containsKey(UifParameters.LIGHTBOX_CALL)) {
510                    externalInquiryProperties.put(UifParameters.LIGHTBOX_CALL, lookupParameters.get(
511                            UifParameters.LIGHTBOX_CALL));
512                }
513
514                return performRedirect(form, lookupUrl, externalInquiryProperties);
515            }
516        }
517
518        return performRedirect(form, baseLookupUrl, lookupParameters);
519    }
520
521    /**
522     * Invoked to provide the options for a suggest widget. The valid options are retrieved by the associated
523     * <code>AttributeQuery</code> for the field containing the suggest widget. The controller method picks
524     * out the query parameters from the request and calls <code>AttributeQueryService</code> to perform the
525     * suggest query and prepare the result object that will be exposed with JSON
526     */
527    @RequestMapping(method = RequestMethod.GET, params = "methodToCall=performFieldSuggest")
528    public
529    @ResponseBody
530    AttributeQueryResult performFieldSuggest(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
531            HttpServletRequest request, HttpServletResponse response) {
532
533        // retrieve query fields from request
534        Map<String, String> queryParameters = new HashMap<String, String>();
535        for (Object parameterName : request.getParameterMap().keySet()) {
536            if (parameterName.toString().startsWith(UifParameters.QUERY_PARAMETER + ".")) {
537                String fieldName = StringUtils.substringAfter(parameterName.toString(),
538                        UifParameters.QUERY_PARAMETER + ".");
539                String fieldValue = request.getParameter(parameterName.toString());
540                queryParameters.put(fieldName, fieldValue);
541            }
542        }
543
544        // retrieve id for field to perform query for
545        String queryFieldId = request.getParameter(UifParameters.QUERY_FIELD_ID);
546        if (StringUtils.isBlank(queryFieldId)) {
547            throw new RuntimeException("Unable to find id for field to perform query on under request parameter name: "
548                    + UifParameters.QUERY_FIELD_ID);
549        }
550
551        // get the field term to match
552        String queryTerm = request.getParameter(UifParameters.QUERY_TERM);
553        if (StringUtils.isBlank(queryTerm)) {
554            throw new RuntimeException(
555                    "Unable to find id for query term value for attribute query on under request parameter name: "
556                            + UifParameters.QUERY_TERM);
557        }
558
559        // invoke attribute query service to perform the query
560        AttributeQueryResult queryResult = KRADServiceLocatorWeb.getAttributeQueryService().performFieldSuggestQuery(
561                form.getPostedView(), queryFieldId, queryTerm, queryParameters);
562
563        return queryResult;
564    }
565
566    /**
567     * Invoked to execute the <code>AttributeQuery</code> associated with a field given the query parameters
568     * found in the request. This controller method picks out the query parameters from the request and calls
569     * <code>AttributeQueryService</code> to perform the field query and prepare the result object
570     * that will be exposed with JSON. The result is then used to update field values in the UI with client
571     * script.
572     */
573    @RequestMapping(method = RequestMethod.GET, params = "methodToCall=performFieldQuery")
574    public
575    @ResponseBody
576    AttributeQueryResult performFieldQuery(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
577            HttpServletRequest request, HttpServletResponse response) {
578
579        // retrieve query fields from request
580        Map<String, String> queryParameters = new HashMap<String, String>();
581        for (Object parameterName : request.getParameterMap().keySet()) {
582            if (parameterName.toString().startsWith(UifParameters.QUERY_PARAMETER + ".")) {
583                String fieldName = StringUtils.substringAfter(parameterName.toString(),
584                        UifParameters.QUERY_PARAMETER + ".");
585                String fieldValue = request.getParameter(parameterName.toString());
586                queryParameters.put(fieldName, fieldValue);
587            }
588        }
589
590        // retrieve id for field to perform query for
591        String queryFieldId = request.getParameter(UifParameters.QUERY_FIELD_ID);
592        if (StringUtils.isBlank(queryFieldId)) {
593            throw new RuntimeException("Unable to find id for field to perform query on under request parameter name: "
594                    + UifParameters.QUERY_FIELD_ID);
595        }
596
597        // invoke attribute query service to perform the query
598        AttributeQueryResult queryResult = KRADServiceLocatorWeb.getAttributeQueryService().performFieldQuery(
599                form.getPostedView(), queryFieldId, queryParameters);
600
601        return queryResult;
602    }
603
604    /**
605     * Builds a <code>ModelAndView</code> instance configured to redirect to the
606     * URL formed by joining the base URL with the given URL parameters
607     *
608     * @param form - current form instance
609     * @param baseUrl - base url to redirect to
610     * @param urlParameters - properties containing key/value pairs for the url parameters, if null or empty,
611     * the baseUrl will be used as the full URL
612     * @return ModelAndView configured to redirect to the given URL
613     */
614    protected ModelAndView performRedirect(UifFormBase form, String baseUrl, Properties urlParameters) {
615        // since we are redirecting and will not be rendering the view, we need to reset the view from the previous
616        form.setView(form.getPostedView());
617
618        // On post redirects we need to make sure we are sending the history forward:
619        urlParameters.setProperty(UifConstants.UrlParams.HISTORY, form.getFormHistory().getHistoryParameterString());
620
621        // If this is an Light Box call only return the redirectURL view with the URL
622        // set this is to avoid automatic redirect when using light boxes
623        if (urlParameters.get(UifParameters.LIGHTBOX_CALL) != null &&
624                urlParameters.get(UifParameters.LIGHTBOX_CALL).equals("true")) {
625            urlParameters.remove(UifParameters.LIGHTBOX_CALL);
626            String redirectUrl = UrlFactory.parameterizeUrl(baseUrl, urlParameters);
627
628            ModelAndView modelAndView = new ModelAndView(UifConstants.SPRING_REDIRECT_ID);
629            modelAndView.addObject("redirectUrl", redirectUrl);
630            return modelAndView;
631        }
632
633        String redirectUrl = UrlFactory.parameterizeUrl(baseUrl, urlParameters);
634        ModelAndView modelAndView = new ModelAndView(REDIRECT_PREFIX + redirectUrl);
635
636        return modelAndView;
637    }
638
639    protected ModelAndView getUIFModelAndView(UifFormBase form) {
640        return getUIFModelAndView(form, form.getPageId());
641    }
642
643    /**
644     * Configures the <code>ModelAndView</code> instance containing the form
645     * data and pointing to the UIF generic spring view
646     *
647     * @param form - Form instance containing the model data
648     * @param pageId - Id of the page within the view that should be rendered, can
649     * be left blank in which the current or default page is rendered
650     * @return ModelAndView object with the contained form
651     */
652    protected ModelAndView getUIFModelAndView(UifFormBase form, String pageId) {
653        return UifWebUtils.getUIFModelAndView(form, pageId);
654    }
655
656    // TODO: add getUIFModelAndView that takes in a view id and can perform view switching
657
658    protected ViewService getViewService() {
659        return KRADServiceLocatorWeb.getViewService();
660    }
661
662}