View Javadoc

1   /**
2    * Copyright 2005-2013 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.web.controller;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.config.property.ConfigContext;
20  import org.kuali.rice.core.api.exception.RiceRuntimeException;
21  import org.kuali.rice.kim.api.identity.Person;
22  import org.kuali.rice.krad.exception.AuthorizationException;
23  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
24  import org.kuali.rice.krad.service.ModuleService;
25  import org.kuali.rice.krad.uif.UifConstants;
26  import org.kuali.rice.krad.uif.UifParameters;
27  import org.kuali.rice.krad.uif.UifPropertyPaths;
28  import org.kuali.rice.krad.uif.field.AttributeQueryResult;
29  import org.kuali.rice.krad.uif.service.ViewService;
30  import org.kuali.rice.krad.uif.util.LookupInquiryUtils;
31  import org.kuali.rice.krad.uif.view.MessageView;
32  import org.kuali.rice.krad.web.form.UifFormManager;
33  import org.kuali.rice.krad.uif.view.DialogManager;
34  import org.kuali.rice.krad.uif.view.History;
35  import org.kuali.rice.krad.uif.view.HistoryEntry;
36  import org.kuali.rice.krad.uif.view.View;
37  import org.kuali.rice.krad.util.GlobalVariables;
38  import org.kuali.rice.krad.util.KRADConstants;
39  import org.kuali.rice.krad.util.KRADUtils;
40  import org.kuali.rice.krad.util.UrlFactory;
41  import org.kuali.rice.krad.web.form.UifFormBase;
42  import org.springframework.validation.BindingResult;
43  import org.springframework.web.bind.annotation.ModelAttribute;
44  import org.springframework.web.bind.annotation.RequestMapping;
45  import org.springframework.web.bind.annotation.RequestMethod;
46  import org.springframework.web.bind.annotation.ResponseBody;
47  import org.springframework.web.context.WebApplicationContext;
48  import org.springframework.web.context.support.WebApplicationContextUtils;
49  import org.springframework.web.servlet.LocaleResolver;
50  import org.springframework.web.servlet.ModelAndView;
51  import org.springframework.web.servlet.support.RequestContextUtils;
52  import org.springframework.web.servlet.view.UrlBasedViewResolver;
53  
54  import javax.servlet.ServletContext;
55  import javax.servlet.http.HttpServletRequest;
56  import javax.servlet.http.HttpServletResponse;
57  import java.util.HashMap;
58  import java.util.List;
59  import java.util.Map;
60  import java.util.Map.Entry;
61  import java.util.Properties;
62  
63  /**
64   * Base controller class for views within the KRAD User Interface Framework
65   *
66   * Provides common methods such as:
67   *
68   * <ul>
69   * <li>Authorization methods such as method to call check</li>
70   * <li>Preparing the View instance and setup in the returned
71   * <code>ModelAndView</code></li>
72   * </ul>
73   *
74   * All subclass controller methods after processing should call one of the
75   * #getUIFModelAndView methods to setup the <code>View</code> and return the
76   * <code>ModelAndView</code> instance.
77   *
78   * @author Kuali Rice Team (rice.collab@kuali.org)
79   */
80  public abstract class UifControllerBase {
81      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(UifControllerBase.class);
82  
83      private UrlBasedViewResolver viewResolver;
84  
85      /**
86       * Create/obtain the model(form) object before it is passed to the Binder/BeanWrapper. This method
87       * is not intended to be overridden by client applications as it handles framework setup and session
88       * maintenance. Clients should override createInitialForm() instead when they need custom form initialization.
89       *
90       * @param request - the http request that was made
91       */
92      @ModelAttribute(value = "KualiForm")
93      public UifFormBase initForm(HttpServletRequest request) {
94          UifFormBase requestForm = null;
95  
96          // get Uif form manager from session if exists or setup a new one for the session
97          UifFormManager uifFormManager = (UifFormManager) request.getSession().getAttribute(UifParameters.FORM_MANAGER);
98          if (uifFormManager == null) {
99              uifFormManager = new UifFormManager();
100             request.getSession().setAttribute(UifParameters.FORM_MANAGER, uifFormManager);
101         }
102 
103         // add form manager to GlobalVariables for easy reference by other controller methods
104         GlobalVariables.setUifFormManager(uifFormManager);
105 
106         // create a new form for every request
107         requestForm = createInitialForm(request);
108 
109         String formKeyParam = request.getParameter(UifParameters.FORM_KEY);
110         if (StringUtils.isNotBlank(formKeyParam)) {
111             // retrieves the session form and updates the request from with the session transient attributes
112             uifFormManager.updateFormWithSession(requestForm, formKeyParam);
113         }
114 
115         // if form exist, remove unused forms from breadcrumb history
116         if (requestForm != null) {
117             UifControllerHelper.removeUnusedBreadcrumbs(uifFormManager, requestForm.getFormKey(), request.getParameter(
118                     UifConstants.UrlParams.LAST_FORM_KEY));
119         }
120 
121         // sets the request form in the request for later retrieval
122         request.setAttribute(UifConstants.REQUEST_FORM, requestForm);
123 
124         return requestForm;
125     }
126 
127     /**
128      * Called to create a new model(form) object when necessary. This usually occurs on the initial request
129      * in a conversation (when the model is not present in the session). This method must be
130      * overridden when extending a controller and using a different form type than the superclass.
131      *
132      * @param request - the http request that was made
133      */
134     protected abstract UifFormBase createInitialForm(HttpServletRequest request);
135 
136     /**
137      * Initial method called when requesting a new view instance which checks authorization and forwards
138      * the view for rendering
139      */
140     @RequestMapping(params = "methodToCall=start")
141     public ModelAndView start(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
142             HttpServletRequest request, HttpServletResponse response) {
143 
144         // check view authorization
145         // TODO: this needs to be invoked for each request
146         if (form.getView() != null) {
147             String methodToCall = request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER);
148             checkViewAuthorization(form, methodToCall);
149         }
150 
151         return getUIFModelAndView(form);
152     }
153 
154     /**
155      * Invokes the configured {@link org.kuali.rice.krad.uif.view.ViewAuthorizer} to verify the user has access to
156      * open the view. An exception is thrown if access has not been granted
157      *
158      * <p>
159      * Note this method is invoked automatically by the controller interceptor for each request
160      * </p>
161      *
162      * @param form - form instance containing the request data
163      * @param methodToCall - the request parameter 'methodToCall' which is used to determine the controller
164      * method invoked
165      */
166     public void checkViewAuthorization(UifFormBase form, String methodToCall) throws AuthorizationException {
167         Person user = GlobalVariables.getUserSession().getPerson();
168 
169         boolean canOpenView = form.getView().getAuthorizer().canOpenView(form.getView(), form, user);
170         if (!canOpenView) {
171             throw new AuthorizationException(user.getPrincipalName(), "open", form.getView().getId(),
172                     "User '" + user.getPrincipalName() + "' is not authorized to open view ID: " + form.getView()
173                             .getId(), null);
174         }
175     }
176 
177     /**
178      * Called by the add line action for a new collection line. Method
179      * determines which collection the add action was selected for and invokes
180      * the view helper service to add the line
181      */
182     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=addLine")
183     public ModelAndView addLine(@ModelAttribute("KualiForm") UifFormBase uifForm, BindingResult result,
184             HttpServletRequest request, HttpServletResponse response) {
185 
186         String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELLECTED_COLLECTION_PATH);
187         if (StringUtils.isBlank(selectedCollectionPath)) {
188             throw new RuntimeException("Selected collection was not set for add line action, cannot add new line");
189         }
190 
191         View view = uifForm.getPostedView();
192         view.getViewHelperService().processCollectionAddLine(view, uifForm, selectedCollectionPath);
193 
194         return getUIFModelAndView(uifForm);
195     }
196 
197     /**
198      * Called by the add blank line action for a new collection line
199      *
200      * <p>
201      * Method determines which collection the add action was selected for and invokes the view helper service to
202      * add the blank line.
203      * </p>
204      *
205      * @param uifForm - form instance containing the request data
206      * @return the  ModelAndView object
207      */
208     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=addBlankLine")
209     public ModelAndView addBlankLine(@ModelAttribute("KualiForm") UifFormBase uifForm) {
210 
211         String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELLECTED_COLLECTION_PATH);
212         if (StringUtils.isBlank(selectedCollectionPath)) {
213             throw new RuntimeException("Selected collection was not set for add line action, cannot add new line");
214         }
215 
216         View view = uifForm.getPostedView();
217         view.getViewHelperService().processCollectionAddBlankLine(view, uifForm, selectedCollectionPath);
218 
219         return getUIFModelAndView(uifForm);
220     }
221 
222     /**
223      * Called by the save line action for a new collection line. Does server side validation and provides hook
224      * for client application to persist specific data.
225      */
226     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=saveLine")
227     public ModelAndView saveLine(@ModelAttribute("KualiForm") UifFormBase uifForm, BindingResult result,
228             HttpServletRequest request, HttpServletResponse response) {
229 
230         String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELLECTED_COLLECTION_PATH);
231         if (StringUtils.isBlank(selectedCollectionPath)) {
232             throw new RuntimeException("Selected collection was not set for add line action, cannot add new line");
233         }
234 
235         int selectedLineIndex = -1;
236         String selectedLine = uifForm.getActionParamaterValue(UifParameters.SELECTED_LINE_INDEX);
237         if (StringUtils.isNotBlank(selectedLine)) {
238             selectedLineIndex = Integer.parseInt(selectedLine);
239         }
240 
241         if (selectedLineIndex == -1) {
242             throw new RuntimeException("Selected line index was not set for delete line action, cannot delete line");
243         }
244 
245         View view = uifForm.getPostedView();
246         view.getViewHelperService().processCollectionSaveLine(view, uifForm, selectedCollectionPath, selectedLineIndex);
247 
248         return getUIFModelAndView(uifForm);
249     }
250 
251     /**
252      * Called by the delete line action for a model collection. Method
253      * determines which collection the action was selected for and the line
254      * index that should be removed, then invokes the view helper service to
255      * process the action
256      */
257     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=deleteLine")
258     public ModelAndView deleteLine(@ModelAttribute("KualiForm") UifFormBase uifForm, BindingResult result,
259             HttpServletRequest request, HttpServletResponse response) {
260 
261         String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELLECTED_COLLECTION_PATH);
262         if (StringUtils.isBlank(selectedCollectionPath)) {
263             throw new RuntimeException("Selected collection was not set for delete line action, cannot delete line");
264         }
265 
266         int selectedLineIndex = -1;
267         String selectedLine = uifForm.getActionParamaterValue(UifParameters.SELECTED_LINE_INDEX);
268         if (StringUtils.isNotBlank(selectedLine)) {
269             selectedLineIndex = Integer.parseInt(selectedLine);
270         }
271 
272         if (selectedLineIndex == -1) {
273             throw new RuntimeException("Selected line index was not set for delete line action, cannot delete line");
274         }
275 
276         View view = uifForm.getPostedView();
277         view.getViewHelperService().processCollectionDeleteLine(view, uifForm, selectedCollectionPath,
278                 selectedLineIndex);
279 
280         return getUIFModelAndView(uifForm);
281     }
282 
283     /**
284      * Just returns as if return with no value was selected.
285      */
286     @RequestMapping(params = "methodToCall=cancel")
287     public ModelAndView cancel(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
288             HttpServletRequest request, HttpServletResponse response) {
289         return close(form, result, request, response);
290     }
291 
292     /**
293      * Just returns as if return with no value was selected.
294      */
295     @RequestMapping(params = "methodToCall=close")
296     public ModelAndView close(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
297             HttpServletRequest request, HttpServletResponse response) {
298         Properties props = new Properties();
299         props.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.REFRESH);
300         if (StringUtils.isNotBlank(form.getReturnFormKey())) {
301             props.put(UifParameters.FORM_KEY, form.getReturnFormKey());
302         }
303 
304         // TODO this needs setup for lightbox and possible home location
305         // property
306         String returnUrl = form.getReturnLocation();
307         if (StringUtils.isBlank(returnUrl)) {
308             returnUrl = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.APPLICATION_URL_KEY);
309         }
310 
311         // clear current form from session
312         GlobalVariables.getUifFormManager().removeSessionForm(form);
313 
314         return performRedirect(form, returnUrl, props);
315     }
316 
317     /**
318      * Invoked to navigate back one page in history..
319      *
320      * @param form - form object that should contain the history object
321      */
322     @RequestMapping(params = "methodToCall=returnToPrevious")
323     public ModelAndView returnToPrevious(@ModelAttribute("KualiForm") UifFormBase form) {
324 
325         return returnToHistory(form, false);
326     }
327 
328     /**
329      * Invoked to navigate back to the first page in history.
330      *
331      * @param form - form object that should contain the history object
332      */
333     @RequestMapping(params = "methodToCall=returnToHub")
334     public ModelAndView returnToHub(@ModelAttribute("KualiForm") UifFormBase form) {
335 
336         return returnToHistory(form, true);
337     }
338 
339     /**
340      * Invoked to navigate back to a history entry. The homeFlag will determine whether navigation
341      * will be back to the first or last history entry.
342      *
343      * @param form - form object that should contain the history object
344      * @param homeFlag - if true will navigate back to first entry else will navigate to last entry
345      * in the history
346      */
347     public ModelAndView returnToHistory(UifFormBase form, boolean homeFlag) {
348         // Get the history from the form
349         History hist = form.getFormHistory();
350         List<HistoryEntry> histEntries = hist.getHistoryEntries();
351 
352         // Get the history page url. Default to the application url if there is no history.
353         String histUrl = null;
354         if (histEntries.isEmpty()) {
355             // TODO: use configuration service here
356             histUrl = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.APPLICATION_URL_KEY);
357         } else {
358             // For home get the first entry, for previous get the last entry.
359             // Remove history up to where page is opened
360             if (homeFlag) {
361                 histUrl = histEntries.get(0).getUrl();
362                 histEntries.clear();
363             } else {
364                 histUrl = histEntries.get(histEntries.size() - 1).getUrl();
365                 histEntries.remove(histEntries.size() - 1);
366                 hist.setCurrent(null);
367             }
368         }
369 
370         // Add the refresh call
371         Properties props = new Properties();
372         props.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.REFRESH);
373 
374         // clear current form from session
375         GlobalVariables.getUifFormManager().removeSessionForm(form);
376 
377         return performRedirect(form, histUrl, props);
378     }
379 
380     /**
381      * Handles menu navigation between view pages
382      */
383     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=navigate")
384     public ModelAndView navigate(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
385             HttpServletRequest request, HttpServletResponse response) {
386         String pageId = form.getActionParamaterValue(UifParameters.NAVIGATE_TO_PAGE_ID);
387 
388         return getUIFModelAndView(form, pageId);
389     }
390 
391     /**
392      * handles an ajax refresh
393      *
394      * <p>The query form plugin  activates this request via a form post, where on the JS side,
395      * {@code org.kuali.rice.krad.uif.UifParameters#RENDER_FULL_VIEW} is set to false</p>
396      *
397      * @param form -  Holds properties necessary to determine the <code>View</code> instance that will be used to
398      * render
399      * the UI
400      * @param result -   represents binding results
401      * @param request - http servlet request data
402      * @param response - http servlet response object
403      * @return the  ModelAndView object
404      * @throws Exception
405      */
406     @RequestMapping(params = "methodToCall=refresh")
407     public ModelAndView refresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
408             HttpServletRequest request, HttpServletResponse response) throws Exception {
409         // TODO: this code still needs to handle reference refreshes
410         String refreshCallerType = "";
411         if (request.getParameterMap().containsKey(KRADConstants.REFRESH_CALLER_TYPE)) {
412             refreshCallerType = request.getParameter(KRADConstants.REFRESH_CALLER_TYPE);
413         }
414 
415         // process multi-value lookup returns
416         if (StringUtils.equals(refreshCallerType, UifConstants.RefreshCallerTypes.MULTI_VALUE_LOOKUP)) {
417             String lookupCollectionName = "";
418             if (request.getParameterMap().containsKey(UifParameters.LOOKUP_COLLECTION_NAME)) {
419                 lookupCollectionName = request.getParameter(UifParameters.LOOKUP_COLLECTION_NAME);
420             }
421 
422             if (StringUtils.isBlank(lookupCollectionName)) {
423                 throw new RuntimeException(
424                         "Lookup collection name is required for processing multi-value lookup results");
425             }
426 
427             String selectedLineValues = "";
428             if (request.getParameterMap().containsKey(UifParameters.SELECTED_LINE_VALUES)) {
429                 selectedLineValues = request.getParameter(UifParameters.SELECTED_LINE_VALUES);
430             }
431 
432             // invoked view helper to populate the collection from lookup results
433             form.getPostedView().getViewHelperService().processMultipleValueLookupResults(form.getPostedView(), form,
434                     lookupCollectionName, selectedLineValues);
435         }
436 
437         return getUIFModelAndView(form);
438     }
439 
440     /**
441      * Builds up a URL to the lookup view based on the given post action
442      * parameters and redirects
443      */
444     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=performLookup")
445     public ModelAndView performLookup(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
446             HttpServletRequest request, HttpServletResponse response) {
447         Properties lookupParameters = form.getActionParametersAsProperties();
448 
449         String lookupObjectClassName = (String) lookupParameters.get(UifParameters.DATA_OBJECT_CLASS_NAME);
450         Class<?> lookupObjectClass = null;
451         try {
452             lookupObjectClass = Class.forName(lookupObjectClassName);
453         } catch (ClassNotFoundException e) {
454             LOG.error("Unable to get class for name: " + lookupObjectClassName);
455             throw new RuntimeException("Unable to get class for name: " + lookupObjectClassName, e);
456         }
457 
458         // get form values for the lookup parameter fields
459         String lookupParameterString = (String) lookupParameters.get(UifParameters.LOOKUP_PARAMETERS);
460         if (lookupParameterString != null) {
461             Map<String, String> lookupParameterFields = KRADUtils.getMapFromParameterString(lookupParameterString);
462             for (Entry<String, String> lookupParameter : lookupParameterFields.entrySet()) {
463                 String lookupParameterValue = LookupInquiryUtils.retrieveLookupParameterValue(form, request,
464                         lookupObjectClass, lookupParameter.getValue(), lookupParameter.getKey());
465 
466                 if (StringUtils.isNotBlank(lookupParameterValue)) {
467                     lookupParameters.put(UifPropertyPaths.LOOKUP_CRITERIA + "['" + lookupParameter.getValue() + "']",
468                             lookupParameterValue);
469                 }
470             }
471 
472             lookupParameters.remove(UifParameters.LOOKUP_PARAMETERS);
473         }
474 
475         // TODO: lookup anchors and doc number?
476 
477         String baseLookupUrl = (String) lookupParameters.get(UifParameters.BASE_LOOKUP_URL);
478         lookupParameters.remove(UifParameters.BASE_LOOKUP_URL);
479 
480         // set lookup method to call
481         lookupParameters.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.START);
482         String autoSearchString = (String) lookupParameters.get(UifParameters.AUTO_SEARCH);
483         if (Boolean.parseBoolean(autoSearchString)) {
484             lookupParameters.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.SEARCH);
485         }
486 
487         lookupParameters.put(UifParameters.RETURN_LOCATION, form.getFormPostUrl());
488         lookupParameters.put(UifParameters.RETURN_FORM_KEY, form.getFormKey());
489 
490         // special check for external object classes
491         if (lookupObjectClass != null) {
492             ModuleService responsibleModuleService =
493                     KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(lookupObjectClass);
494             if (responsibleModuleService != null && responsibleModuleService.isExternalizable(lookupObjectClass)) {
495                 String lookupUrl = responsibleModuleService.getExternalizableDataObjectLookupUrl(lookupObjectClass,
496                         lookupParameters);
497 
498                 Properties externalInquiryProperties = new Properties();
499                 if (lookupParameters.containsKey(UifParameters.LIGHTBOX_CALL)) {
500                     externalInquiryProperties.put(UifParameters.LIGHTBOX_CALL, lookupParameters.get(
501                             UifParameters.LIGHTBOX_CALL));
502                 }
503 
504                 return performRedirect(form, lookupUrl, externalInquiryProperties);
505             }
506         }
507 
508         return performRedirect(form, baseLookupUrl, lookupParameters);
509     }
510 
511     /**
512      * Checks the form/view against all current and future validations and returns warnings for any validations
513      * that fail
514      */
515     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=checkForm")
516     public ModelAndView checkForm(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
517             HttpServletRequest request, HttpServletResponse response) {
518         KRADServiceLocatorWeb.getViewValidationService().validateViewSimulation(form.getPostedView(), form);
519 
520         return getUIFModelAndView(form);
521     }
522 
523     /**
524      * Invoked to provide the options for a suggest widget. The valid options are retrieved by the associated
525      * <code>AttributeQuery</code> for the field containing the suggest widget. The controller method picks
526      * out the query parameters from the request and calls <code>AttributeQueryService</code> to perform the
527      * suggest query and prepare the result object that will be exposed with JSON
528      */
529     @RequestMapping(method = RequestMethod.GET, params = "methodToCall=performFieldSuggest")
530     public
531     @ResponseBody
532     AttributeQueryResult performFieldSuggest(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
533             HttpServletRequest request, HttpServletResponse response) {
534 
535         // retrieve query fields from request
536         Map<String, String> queryParameters = new HashMap<String, String>();
537         for (Object parameterName : request.getParameterMap().keySet()) {
538             if (parameterName.toString().startsWith(UifParameters.QUERY_PARAMETER + ".")) {
539                 String fieldName = StringUtils.substringAfter(parameterName.toString(),
540                         UifParameters.QUERY_PARAMETER + ".");
541                 String fieldValue = request.getParameter(parameterName.toString());
542                 queryParameters.put(fieldName, fieldValue);
543             }
544         }
545 
546         // retrieve id for field to perform query for
547         String queryFieldId = request.getParameter(UifParameters.QUERY_FIELD_ID);
548         if (StringUtils.isBlank(queryFieldId)) {
549             throw new RuntimeException("Unable to find id for field to perform query on under request parameter name: "
550                     + UifParameters.QUERY_FIELD_ID);
551         }
552 
553         // get the field term to match
554         String queryTerm = request.getParameter(UifParameters.QUERY_TERM);
555         if (StringUtils.isBlank(queryTerm)) {
556             throw new RuntimeException(
557                     "Unable to find id for query term value for attribute query on under request parameter name: "
558                             + UifParameters.QUERY_TERM);
559         }
560 
561         // invoke attribute query service to perform the query
562         AttributeQueryResult queryResult = KRADServiceLocatorWeb.getAttributeQueryService().performFieldSuggestQuery(
563                 form.getPostedView(), queryFieldId, queryTerm, queryParameters);
564 
565         return queryResult;
566     }
567 
568     /**
569      * Invoked to execute the <code>AttributeQuery</code> associated with a field given the query parameters
570      * found in the request. This controller method picks out the query parameters from the request and calls
571      * <code>AttributeQueryService</code> to perform the field query and prepare the result object
572      * that will be exposed with JSON. The result is then used to update field values in the UI with client
573      * script.
574      */
575     @RequestMapping(method = RequestMethod.GET, params = "methodToCall=performFieldQuery")
576     public
577     @ResponseBody
578     AttributeQueryResult performFieldQuery(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
579             HttpServletRequest request, HttpServletResponse response) {
580 
581         // retrieve query fields from request
582         Map<String, String> queryParameters = new HashMap<String, String>();
583         for (Object parameterName : request.getParameterMap().keySet()) {
584             if (parameterName.toString().startsWith(UifParameters.QUERY_PARAMETER + ".")) {
585                 String fieldName = StringUtils.substringAfter(parameterName.toString(),
586                         UifParameters.QUERY_PARAMETER + ".");
587                 String fieldValue = request.getParameter(parameterName.toString());
588                 queryParameters.put(fieldName, fieldValue);
589             }
590         }
591 
592         // retrieve id for field to perform query for
593         String queryFieldId = request.getParameter(UifParameters.QUERY_FIELD_ID);
594         if (StringUtils.isBlank(queryFieldId)) {
595             throw new RuntimeException("Unable to find id for field to perform query on under request parameter name: "
596                     + UifParameters.QUERY_FIELD_ID);
597         }
598 
599         // invoke attribute query service to perform the query
600         AttributeQueryResult queryResult = KRADServiceLocatorWeb.getAttributeQueryService().performFieldQuery(
601                 form.getPostedView(), queryFieldId, queryParameters);
602 
603         return queryResult;
604     }
605 
606     /**
607      * returns whether this dialog has been displayed on the client
608      *
609      * @param dialogId - the id of the dialog
610      * @param form - form instance containing the request data
611      * @return boolean - true if dialog has been displayed, false if not
612      */
613     protected boolean hasDialogBeenDisplayed(String dialogId, UifFormBase form) {
614         return (form.getDialogManager().hasDialogBeenDisplayed(dialogId));
615     }
616 
617     /**
618      * returns whether the dialog has already been answered by the user
619      *
620      * @param dialogId - identifier for the dialog group
621      * @param form - form instance containing the request data
622      * @return boolean - true if client has already responded to the dialog, false otherwise
623      */
624     protected boolean hasDialogBeenAnswered(String dialogId, UifFormBase form) {
625         return (form.getDialogManager().hasDialogBeenAnswered(dialogId));
626     }
627 
628     /**
629      * Handles modal dialog interactions for a view controller When a controller method wishes to prompt the user
630      * for additional information before continuing to process the request.
631      *
632      * <p>
633      * If this modal dialog has not yet been presented to the user, a redirect back to the client
634      * is performed to display the modal dialog as a Lightbox. The DialogGroup identified by the
635      * dialogId is used as the Lightbox content.
636      * </p>
637      *
638      * <p>
639      * If the dialog has already been answered by the user.  The boolean value representing the
640      * option chosen by the user is returned back to the calling controller
641      * </p>
642      *
643      * @param dialogId - identifier of the dialog group
644      * @param form - form instance containing the request data
645      * @param request - the http request
646      * @param response - the http response
647      * @return boolean - true if user chose affirmative response, false if negative response was chosen
648      */
649     protected boolean getBooleanDialogResponse(String dialogId, UifFormBase form, HttpServletRequest request,
650             HttpServletResponse response) throws Exception {
651         DialogManager dm = form.getDialogManager();
652         if (!dm.hasDialogBeenAnswered(dialogId)) {
653             showDialog(dialogId, form, request, response);
654             
655             // throw an exception until showDialog is able to complete request.
656             // until then, programmers should check hasDialogBeenAnswered
657             throw new RiceRuntimeException("Dialog has not yet been answered by client. "
658                     + "Check that hasDialogBeenAnswered(id) returns true.");
659         }
660         
661         return dm.wasDialogAnswerAffirmative(dialogId);
662     }
663 
664     /**
665      * Handles a modal dialog interaction with the client user when a String response is desired
666      *
667      * <p>
668      * Similar to askYesOrNoQuestion() but returns a string instead of a boolean.  The string value is the key
669      * string of the key/value pair assigned to the button that the user chose.
670      * </p>
671      *
672      * @param dialogId - identifier of the dialog group
673      * @param form - form instance containing the request data
674      * @param request - the http request
675      * @param response - the http response
676      * @return
677      * @throws Exception
678      */
679     protected String getStringDialogResponse(String dialogId, UifFormBase form, HttpServletRequest request,
680             HttpServletResponse response) throws Exception {
681         DialogManager dm = form.getDialogManager();
682         if (!dm.hasDialogBeenAnswered(dialogId)) {
683             showDialog(dialogId, form, request, response);
684             
685             // throw an exception until showDialog is able to complete request.
686             // until then, programmers should check hasDialogBeenAnswered
687             throw new RiceRuntimeException("Dialog has not yet been answered by client. "
688                     + "Check that hasDialogBeenAnswered(id) returns true.");
689         }
690         
691         return dm.getDialogAnswer(dialogId);
692     }
693 
694     /**
695      * Complete the response directly and launch lightbox with dialog content upon returning back to the client. If it
696      * is an ajax request then set the ajaxReturnType and set the updateComponentId to the dialogId.
697      *
698      * <p>
699      * Need to build up the view/component properly as we would if we returned normally back to the DispatcherServlet
700      * from the controller method.
701      * </p>
702      *
703      * @param dialogId - id of the dialog or group to use as content in the lightbox.
704      * @param form - the form associated with the view
705      * @param request - the http request
706      * @param response - the http response
707      * @return will return void.  actually, won't return at all.
708      * @throws Exception
709      */
710     protected ModelAndView showDialog(String dialogId, UifFormBase form, HttpServletRequest request,
711             HttpServletResponse response) throws Exception {
712         // js script to invoke lightbox: runs onDocumentReady
713         form.setLightboxScript("openLightboxOnLoad('" + dialogId + "');");
714         form.getDialogManager().addDialog(dialogId, form.getMethodToCall());
715 
716         // if the dialog is being invoked sever side via ajax set the ajaxReturnType to update-dialog
717         // and set the updateComponentId to the dialogId
718         if (form.isAjaxRequest()) {
719             form.setAjaxReturnType(UifConstants.AjaxReturnTypes.UPDATEDIALOG.getKey());
720             form.setUpdateComponentId(dialogId);
721         }
722 
723         return getUIFModelAndView(form);
724     }
725 
726     /**
727      * Common return point for dialogs
728      *
729      * <p>
730      * Determines the user responses to the dialog. Performs dialog management and then redirects to the
731      * original contoller method.
732      * </p>
733      *
734      * @param form - current form
735      * @param result - binding result
736      * @param request - http request
737      * @param response - http response
738      * @return ModelAndView setup for redirect to original controller methodToCall
739      * @throws Exception
740      */
741     @RequestMapping(params = "methodToCall=returnFromLightbox")
742     public ModelAndView returnFromLightbox(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
743             HttpServletRequest request, HttpServletResponse response) throws Exception {
744         String newMethodToCall = "";
745         
746         // Save user responses from dialog
747         DialogManager dm = form.getDialogManager();
748         String dialogId = dm.getCurrentDialogId();
749         if (dialogId == null){
750             // may have been invoked by client.
751             // TODO:  handle this case (scheduled for 2.2-m3)
752             // for now, log WARNING and default to start, can we add a growl?
753             newMethodToCall = "start";
754         } else {
755             dm.setDialogAnswer(dialogId, form.getDialogResponse());
756             dm.setDialogExplanation(dialogId, form.getDialogExplanation());
757             newMethodToCall = dm.getDialogReturnMethod(dialogId);
758             dm.setCurrentDialogId(null);
759         }
760 
761         // call intended controller method
762         Properties props = new Properties();
763         props.put(UifParameters.METHOD_TO_CALL, newMethodToCall);
764         props.put(UifParameters.VIEW_ID, form.getViewId());
765         props.put(UifParameters.FORM_KEY, form.getFormKey());
766         props.put(UifParameters.AJAX_REQUEST,"false");
767 
768         return performRedirect(form, form.getFormPostUrl(), props);
769     }
770 
771     /**
772      * Builds a <code>ModelAndView</code> instance configured to redirect to the
773      * URL formed by joining the base URL with the given URL parameters
774      *
775      * @param form - current form instance
776      * @param baseUrl - base url to redirect to
777      * @param urlParameters - properties containing key/value pairs for the url parameters, if null or empty,
778      * the baseUrl will be used as the full URL
779      * @return ModelAndView configured to redirect to the given URL
780      */
781     protected ModelAndView performRedirect(UifFormBase form, String baseUrl, Properties urlParameters) {
782         // indicate a redirect is occuring to prevent view processing down the line
783         form.setRequestRedirected(true);
784 
785         //set the ajaxReturnType on the form this will override the return type requested by the client
786         form.setAjaxReturnType(UifConstants.AjaxReturnTypes.REDIRECT.getKey());
787 
788         if(urlParameters != null){
789             // On post redirects we need to make sure we are sending the history forward:
790             if(form.getFormHistory() != null)  {
791                 urlParameters.setProperty(UifConstants.UrlParams.HISTORY, form.getFormHistory().getHistoryParameterString());
792             }
793             // If this is an Light Box call only return the redirectURL view with the URL
794             // set this is to avoid automatic redirect when using light boxes
795             if (urlParameters.get(UifParameters.LIGHTBOX_CALL) != null && urlParameters.get(UifParameters.LIGHTBOX_CALL)
796                     .equals("true")) {
797                 urlParameters.remove(UifParameters.LIGHTBOX_CALL);
798                 String redirectUrl = UrlFactory.parameterizeUrl(baseUrl, urlParameters);
799 
800                 ModelAndView modelAndView = new ModelAndView(UifConstants.SPRING_REDIRECT_ID);
801                 modelAndView.addObject("redirectUrl", redirectUrl);
802                 return modelAndView;
803             }
804         }
805         String redirectUrl = UrlFactory.parameterizeUrl(baseUrl, urlParameters);
806 
807         //If this is an ajax redirect get the model and view from the form
808         if(form.isAjaxRequest()) {
809             ModelAndView modelAndView = getUIFModelAndView(form, form.getPageId());
810             modelAndView.addObject("redirectUrl", redirectUrl);
811             return modelAndView ;
812         }  else {
813             ModelAndView modelAndView = new ModelAndView(UifConstants.REDIRECT_PREFIX + redirectUrl);
814             return modelAndView;
815         }
816 
817     }
818 
819     /**
820      * Builds a message view from the given header and message text then forwards the UIF model and view
821      *
822      * <p>
823      * If an error or other type of interruption occurs during the request processing the controller can
824      * invoke this message to display the message to the user. This will abandon the view that was requested
825      * and display a view with just the message
826      * </p>
827      *
828      * @param form UIF form instance
829      * @param headerText header text for the message view (can be blank)
830      * @param messageText text for the message to display
831      * @return ModelAndView
832      */
833     protected ModelAndView getMessageView(UifFormBase form, String headerText, String messageText) {
834         // get a new message view
835         MessageView messageView = (MessageView) getViewService().getViewById(UifConstants.MESSAGE_VIEW_ID);
836 
837         messageView.setHeaderText(headerText);
838         messageView.setMessageText(messageText);
839 
840         form.setViewId(UifConstants.MESSAGE_VIEW_ID);
841         form.setView(messageView);
842 
843         return getUIFModelAndView(form);
844     }
845 
846     protected ModelAndView getUIFModelAndView(UifFormBase form) {
847         return getUIFModelAndView(form, form.getPageId());
848     }
849 
850     /**
851      * Configures the <code>ModelAndView</code> instance containing the form
852      * data and pointing to the UIF generic spring view
853      *
854      * @param form - Form instance containing the model data
855      * @param pageId - Id of the page within the view that should be rendered, can
856      * be left blank in which the current or default page is rendered
857      * @return ModelAndView object with the contained form
858      */
859     protected ModelAndView getUIFModelAndView(UifFormBase form, String pageId) {
860         return UifControllerHelper.getUIFModelAndView(form, pageId);
861     }
862 
863     // TODO: add getUIFModelAndView that takes in a view id and can perform view switching
864 
865     protected ViewService getViewService() {
866         return KRADServiceLocatorWeb.getViewService();
867     }
868 
869 }