View Javadoc

1   /**
2    * Copyright 2005-2011 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.core.web.format.BooleanFormatter;
22  import org.kuali.rice.kim.api.KimConstants;
23  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
24  import org.kuali.rice.krad.UserSession;
25  import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
26  import org.kuali.rice.krad.exception.AuthorizationException;
27  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
28  import org.kuali.rice.krad.service.ModuleService;
29  import org.kuali.rice.krad.service.SessionDocumentService;
30  import org.kuali.rice.krad.uif.UifConstants;
31  import org.kuali.rice.krad.uif.UifParameters;
32  import org.kuali.rice.krad.uif.UifPropertyPaths;
33  import org.kuali.rice.krad.uif.component.Component;
34  import org.kuali.rice.krad.uif.container.CollectionGroup;
35  import org.kuali.rice.krad.uif.field.AttributeQueryResult;
36  import org.kuali.rice.krad.uif.service.ViewService;
37  import org.kuali.rice.krad.uif.util.ComponentFactory;
38  import org.kuali.rice.krad.uif.util.LookupInquiryUtils;
39  import org.kuali.rice.krad.uif.util.UifWebUtils;
40  import org.kuali.rice.krad.uif.view.History;
41  import org.kuali.rice.krad.uif.view.HistoryEntry;
42  import org.kuali.rice.krad.uif.view.View;
43  import org.kuali.rice.krad.util.GlobalVariables;
44  import org.kuali.rice.krad.util.KRADConstants;
45  import org.kuali.rice.krad.util.KRADUtils;
46  import org.kuali.rice.krad.util.UrlFactory;
47  import org.kuali.rice.krad.web.form.UifFormBase;
48  import org.springframework.validation.BindingResult;
49  import org.springframework.web.bind.annotation.ModelAttribute;
50  import org.springframework.web.bind.annotation.RequestMapping;
51  import org.springframework.web.bind.annotation.RequestMethod;
52  import org.springframework.web.bind.annotation.ResponseBody;
53  import org.springframework.web.servlet.ModelAndView;
54  
55  import javax.servlet.http.HttpServletRequest;
56  import javax.servlet.http.HttpServletResponse;
57  import java.util.Collections;
58  import java.util.HashMap;
59  import java.util.HashSet;
60  import java.util.List;
61  import java.util.Map;
62  import java.util.Map.Entry;
63  import java.util.Properties;
64  import java.util.Set;
65  
66  /**
67   * Base controller class for views within the KRAD User Interface Framework
68   *
69   * Provides common methods such as:
70   *
71   * <ul>
72   * <li>Authorization methods such as method to call check</li>
73   * <li>Preparing the View instance and setup in the returned
74   * <code>ModelAndView</code></li>
75   * </ul>
76   *
77   * All subclass controller methods after processing should call one of the
78   * #getUIFModelAndView methods to setup the <code>View</code> and return the
79   * <code>ModelAndView</code> instance.
80   *
81   * @author Kuali Rice Team (rice.collab@kuali.org)
82   */
83  public abstract class UifControllerBase {
84      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(UifControllerBase.class);
85  
86      protected static final String REDIRECT_PREFIX = "redirect:";
87  
88      /**
89       * Create/obtain the model(form) object before it is passed
90       * to the Binder/BeanWrapper. This method is not intended to be overridden
91       * by client applications as it handles framework setup and session
92       * maintenance. Clients should override createIntialForm() instead when they
93       * need custom form initialization.
94       */
95      @ModelAttribute(value = "KualiForm")
96      public UifFormBase initForm(HttpServletRequest request) {
97          UifFormBase form = null;
98          String formKeyParam = request.getParameter(UifParameters.FORM_KEY);
99          String documentNumber = request.getParameter(KRADConstants.DOCUMENT_DOCUMENT_NUMBER);
100 
101         if (StringUtils.isNotBlank(formKeyParam)) {
102             form = (UifFormBase) request.getSession().getAttribute(formKeyParam);
103 
104             // retrieve from db if form not in session
105             if (form == null) {
106                 UserSession userSession = (UserSession) request.getSession().getAttribute(
107                         KRADConstants.USER_SESSION_KEY);
108                 form = getSessionDocumentService().getDocumentForm(documentNumber, formKeyParam, userSession,
109                         request.getRemoteAddr());
110             }
111         } else {
112             form = createInitialForm(request);
113         }
114 
115         return form;
116     }
117 
118     /**
119      * Called to create a new model(form) object when
120      * necessary. This usually occurs on the initial request in a conversation
121      * (when the model is not present in the session). This method must be
122      * overridden when extending a controller and using a different form type
123      * than the superclass.
124      */
125     protected abstract UifFormBase createInitialForm(HttpServletRequest request);
126 
127     private Set<String> methodToCallsToNotCheckAuthorization = new HashSet<String>();
128 
129     {
130         methodToCallsToNotCheckAuthorization.add("performLookup");
131         methodToCallsToNotCheckAuthorization.add("performQuestion");
132         methodToCallsToNotCheckAuthorization.add("performQuestionWithInput");
133         methodToCallsToNotCheckAuthorization.add("performQuestionWithInputAgainBecauseOfErrors");
134         methodToCallsToNotCheckAuthorization.add("performQuestionWithoutInput");
135         methodToCallsToNotCheckAuthorization.add("performWorkgroupLookup");
136     }
137 
138     /**
139      * Use to add a methodToCall to the a list which will not have authorization
140      * checks. This assumes that the call will be redirected (as in the case of
141      * a lookup) that will perform the authorization.
142      */
143     protected final void addMethodToCallToUncheckedList(String methodToCall) {
144         methodToCallsToNotCheckAuthorization.add(methodToCall);
145     }
146 
147     /**
148      * Returns an immutable Set of methodToCall parameters that should not be
149      * checked for authorization.
150      */
151     public Set<String> getMethodToCallsToNotCheckAuthorization() {
152         return Collections.unmodifiableSet(methodToCallsToNotCheckAuthorization);
153     }
154 
155     /**
156      * Override this method to provide controller class-level access controls to
157      * the application.
158      */
159     public void checkAuthorization(UifFormBase form, String methodToCall) throws AuthorizationException {
160         String principalId = GlobalVariables.getUserSession().getPrincipalId();
161         Map<String, String> roleQualifier = new HashMap<String, String>(getRoleQualification(form, methodToCall));
162         Map<String, String> permissionDetails = KRADUtils.getNamespaceAndActionClass(this.getClass());
163 
164         if (!KimApiServiceLocator.getPermissionService().isAuthorizedByTemplateName(principalId,
165                 KRADConstants.KRAD_NAMESPACE, KimConstants.PermissionTemplateNames.USE_SCREEN, permissionDetails,
166                 roleQualifier)) {
167             throw new AuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalName(),
168                     methodToCall, this.getClass().getSimpleName());
169         }
170     }
171 
172     /**
173      * Override this method to add data from the form for role qualification in
174      * the authorization check
175      */
176     protected Map<String, String> getRoleQualification(UifFormBase form, String methodToCall) {
177         return new HashMap<String, String>();
178     }
179 
180     /**
181      * Initial method called when requesting a new view instance which forwards
182      * the view for rendering
183      */
184     @RequestMapping(method = RequestMethod.GET, params = "methodToCall=start")
185     public ModelAndView start(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
186             HttpServletRequest request, HttpServletResponse response) {
187 
188         return getUIFModelAndView(form);
189     }
190 
191     /**
192      * Called by the add line action for a new collection line. Method
193      * determines which collection the add action was selected for and invokes
194      * the view helper service to add the line
195      */
196     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=addLine")
197     public ModelAndView addLine(@ModelAttribute("KualiForm") UifFormBase uifForm, BindingResult result,
198             HttpServletRequest request, HttpServletResponse response) {
199 
200         String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELLECTED_COLLECTION_PATH);
201         if (StringUtils.isBlank(selectedCollectionPath)) {
202             throw new RuntimeException("Selected collection was not set for add line action, cannot add new line");
203         }
204 
205         View view = uifForm.getPreviousView();
206         view.getViewHelperService().processCollectionAddLine(view, uifForm, selectedCollectionPath);
207 
208         return updateComponent(uifForm, result, request, response);
209     }
210 
211     /**
212      * Called by the delete line action for a model collection. Method
213      * determines which collection the action was selected for and the line
214      * index that should be removed, then invokes the view helper service to
215      * process the action
216      */
217     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=deleteLine")
218     public ModelAndView deleteLine(@ModelAttribute("KualiForm") UifFormBase uifForm, BindingResult result,
219             HttpServletRequest request, HttpServletResponse response) {
220 
221         String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELLECTED_COLLECTION_PATH);
222         if (StringUtils.isBlank(selectedCollectionPath)) {
223             throw new RuntimeException("Selected collection was not set for delete line action, cannot delete line");
224         }
225 
226         int selectedLineIndex = -1;
227         String selectedLine = uifForm.getActionParamaterValue(UifParameters.SELECTED_LINE_INDEX);
228         if (StringUtils.isNotBlank(selectedLine)) {
229             selectedLineIndex = Integer.parseInt(selectedLine);
230         }
231 
232         if (selectedLineIndex == -1) {
233             throw new RuntimeException("Selected line index was not set for delete line action, cannot delete line");
234         }
235 
236         View view = uifForm.getPreviousView();
237         view.getViewHelperService().processCollectionDeleteLine(view, uifForm, selectedCollectionPath,
238                 selectedLineIndex);
239 
240         return updateComponent(uifForm, result, request, response);
241     }
242 
243     /**
244      * Invoked to toggle the show inactive indicator on the selected collection group and then
245      * rerun the component lifecycle and rendering based on the updated indicator and form data
246      *
247      * @param request - request object that should contain the request component id (for the collection group)
248      * and the show inactive indicator value
249      */
250     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=toggleInactiveRecordDisplay")
251     public ModelAndView toggleInactiveRecordDisplay(@ModelAttribute("KualiForm") UifFormBase uifForm,
252             BindingResult result, HttpServletRequest request, HttpServletResponse response) {
253         String collectionGroupId = request.getParameter(UifParameters.REQUESTED_COMPONENT_ID);
254         if (StringUtils.isBlank(collectionGroupId)) {
255             throw new RuntimeException(
256                     "Collection group id to update for inactive record display not found in request");
257         }
258 
259         String showInactiveStr = request.getParameter(UifParameters.SHOW_INACTIVE_RECORDS);
260         Boolean showInactive = false;
261         if (StringUtils.isNotBlank(showInactiveStr)) {
262             // TODO: should use property editors once we have util class
263             showInactive = (Boolean) (new BooleanFormatter()).convertFromPresentationFormat(showInactiveStr);
264         } else {
265             throw new RuntimeException("Show inactive records flag not found in request");
266         }
267 
268         CollectionGroup collectionGroup = (CollectionGroup) ComponentFactory.getNewInstanceForRefresh(uifForm.getView(),
269                 collectionGroupId);
270 
271         // update inactive flag on group
272         collectionGroup.setShowInactive(showInactive);
273 
274         // run lifecycle and update in view
275         uifForm.getView().getViewHelperService().performComponentLifecycle(uifForm.getView(), uifForm, collectionGroup,
276                 collectionGroupId);
277 
278         return UifWebUtils.getComponentModelAndView(collectionGroup, uifForm);
279     }
280 
281     /**
282      * Just returns as if return with no value was selected.
283      */
284     @RequestMapping(params = "methodToCall=cancel")
285     public ModelAndView cancel(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
286             HttpServletRequest request, HttpServletResponse response) {
287         return close(form, result, request, response);
288     }
289 
290     /**
291      * Just returns as if return with no value was selected.
292      */
293     @RequestMapping(params = "methodToCall=close")
294     public ModelAndView close(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
295             HttpServletRequest request, HttpServletResponse response) {
296         Properties props = new Properties();
297         props.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.REFRESH);
298         if (StringUtils.isNotBlank(form.getReturnFormKey())) {
299             props.put(UifParameters.FORM_KEY, form.getReturnFormKey());
300         }
301 
302         // TODO this needs setup for lightbox and possible home location
303         // property
304         String returnUrl = form.getReturnLocation();
305         if (StringUtils.isBlank(returnUrl)) {
306             returnUrl = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.APPLICATION_URL_KEY);
307         }
308 
309         return performRedirect(form, returnUrl, props);
310     }
311 
312     /**
313      * Invoked to navigate back one page in history..
314      *
315      * @param form - form object that should contain the history object
316      */
317     @RequestMapping(params = "methodToCall=returnToPrevious")
318     public ModelAndView returnToPrevious(@ModelAttribute("KualiForm") UifFormBase form) {
319 
320         return returnToHistory(form, false);
321     }
322 
323     /**
324      * Invoked to navigate back to the first page in history.
325      *
326      * @param form - form object that should contain the history object
327      */
328     @RequestMapping(params = "methodToCall=returnToHub")
329     public ModelAndView returnToHub(@ModelAttribute("KualiForm") UifFormBase form) {
330 
331         return returnToHistory(form, true);
332     }
333 
334     /**
335      * Invoked to navigate back to a history entry. The homeFlag will determine whether navigation
336      * will be back to the first or last history entry.
337      *
338      * @param form - form object that should contain the history object
339      * @param homeFlag - if true will navigate back to first entry else will navigate to last entry
340      * in the history
341      */
342     public ModelAndView returnToHistory(UifFormBase form, boolean homeFlag) {
343         // Get the history from the form
344         History hist = form.getFormHistory();
345         List<HistoryEntry> histEntries = hist.getHistoryEntries();
346 
347         // Get the history page url. Default to the application url if there is no history.
348         String histUrl = null;
349         if (histEntries.isEmpty()) {
350             histUrl = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.APPLICATION_URL_KEY);
351         } else {
352             // For home get the first entry, for previous get the last entry.
353             // Remove history up to where page is opened
354             if (homeFlag) {
355                 histUrl = histEntries.get(0).getUrl();
356                 histEntries.clear();
357             } else {
358                 histUrl = histEntries.get(histEntries.size() - 1).getUrl();
359                 histEntries.remove(histEntries.size() - 1);
360                 hist.setCurrent(null);
361             }
362         }
363 
364         // Add the refresh call
365         Properties props = new Properties();
366         props.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.REFRESH);
367 
368         return performRedirect(form, histUrl, props);
369     }
370 
371     /**
372      * Handles menu navigation between view pages
373      */
374     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=navigate")
375     public ModelAndView navigate(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
376             HttpServletRequest request, HttpServletResponse response) {
377         String pageId = form.getActionParamaterValue(UifParameters.NAVIGATE_TO_PAGE_ID);
378 
379         // only refreshing page
380         form.setRenderFullView(false);
381 
382         return getUIFModelAndView(form, pageId);
383     }
384 
385     @RequestMapping(params = "methodToCall=refresh")
386     public ModelAndView refresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
387             HttpServletRequest request, HttpServletResponse response) throws Exception {
388         // TODO: this code still needs to handle reference refreshes
389         String refreshCallerType = "";
390         if (request.getParameterMap().containsKey(KRADConstants.REFRESH_CALLER_TYPE)) {
391             refreshCallerType = request.getParameter(KRADConstants.REFRESH_CALLER_TYPE);
392         }
393 
394         // process multi-value lookup returns
395         if (StringUtils.equals(refreshCallerType, UifConstants.RefreshCallerTypes.MULTI_VALUE_LOOKUP)) {
396             String lookupCollectionName = "";
397             if (request.getParameterMap().containsKey(UifParameters.LOOKUP_COLLECTION_NAME)) {
398                 lookupCollectionName = request.getParameter(UifParameters.LOOKUP_COLLECTION_NAME);
399             }
400 
401             if (StringUtils.isBlank(lookupCollectionName)) {
402                 throw new RuntimeException(
403                         "Lookup collection name is required for processing multi-value lookup results");
404             }
405 
406             String selectedLineValues = "";
407             if (request.getParameterMap().containsKey(UifParameters.SELECTED_LINE_VALUES)) {
408                 selectedLineValues = request.getParameter(UifParameters.SELECTED_LINE_VALUES);
409             }
410 
411             // invoked view helper to populate the collection from lookup results
412             form.getPreviousView().getViewHelperService().processMultipleValueLookupResults(form.getPreviousView(),
413                     form, lookupCollectionName, selectedLineValues);
414         }
415 
416         form.setRenderFullView(true);
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.getView(), requestedComponentId);
437 
438         // run lifecycle and update in view
439         form.getView().getViewHelperService().performComponentLifecycle(form.getView(), form, comp,
440                 requestedComponentId);
441 
442         return UifWebUtils.getComponentModelAndView(comp, form);
443     }
444 
445     /**
446      * Builds up a URL to the lookup view based on the given post action
447      * parameters and redirects
448      */
449     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=performLookup")
450     public ModelAndView performLookup(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
451             HttpServletRequest request, HttpServletResponse response) {
452         Properties lookupParameters = form.getActionParametersAsProperties();
453 
454         String lookupObjectClassName = (String) lookupParameters.get(UifParameters.DATA_OBJECT_CLASS_NAME);
455         Class<?> lookupObjectClass = null;
456         try {
457             lookupObjectClass = Class.forName(lookupObjectClassName);
458         } catch (ClassNotFoundException e) {
459             LOG.error("Unable to get class for name: " + lookupObjectClassName);
460             throw new RuntimeException("Unable to get class for name: " + lookupObjectClassName, e);
461         }
462 
463         // get form values for the lookup parameter fields
464         String lookupParameterString = (String) lookupParameters.get(UifParameters.LOOKUP_PARAMETERS);
465         if (lookupParameterString != null) {
466             Map<String, String> lookupParameterFields = KRADUtils.getMapFromParameterString(lookupParameterString);
467             for (Entry<String, String> lookupParameter : lookupParameterFields.entrySet()) {
468                 String lookupParameterValue = LookupInquiryUtils.retrieveLookupParameterValue(form, request,
469                         lookupObjectClass, lookupParameter.getValue(), lookupParameter.getKey());
470 
471                 if (StringUtils.isNotBlank(lookupParameterValue)) {
472                     lookupParameters.put(UifPropertyPaths.CRITERIA_FIELDS + "['" + lookupParameter.getValue() + "']",
473                             lookupParameterValue);
474                 }
475             }
476         }
477 
478         // TODO: lookup anchors and doc number?
479 
480         String baseLookupUrl = (String) lookupParameters.get(UifParameters.BASE_LOOKUP_URL);
481         lookupParameters.remove(UifParameters.BASE_LOOKUP_URL);
482 
483         // set lookup method to call
484         lookupParameters.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.START);
485         String autoSearchString = (String) lookupParameters.get(UifParameters.AUTO_SEARCH);
486         if (Boolean.parseBoolean(autoSearchString)) {
487             lookupParameters.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.SEARCH);
488         }
489 
490         lookupParameters.put(UifParameters.RETURN_LOCATION, form.getFormPostUrl());
491         lookupParameters.put(UifParameters.RETURN_FORM_KEY, form.getFormKey());
492 
493         // special check for external object classes
494         if (lookupObjectClass != null) {
495             ModuleService responsibleModuleService =
496                     KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(lookupObjectClass);
497             if (responsibleModuleService != null && responsibleModuleService.isExternalizable(lookupObjectClass)) {
498                 Class<? extends ExternalizableBusinessObject> implLookupObjectClass =
499                         responsibleModuleService.getExternalizableBusinessObjectImplementation(
500                                 lookupObjectClass.asSubclass(ExternalizableBusinessObject.class));
501 
502                 if (implLookupObjectClass != null) {
503                     lookupParameters.put(UifParameters.DATA_OBJECT_CLASS_NAME, implLookupObjectClass.getName());
504                 } else {
505                     throw new RuntimeException(
506                             "Unable to find implementation class for EBO: " + lookupObjectClass.getName());
507                 }
508 
509                 // TODO: should call module service to get full URL, but right now it is coded to direct to the KNS lookups
510                 //                Map<String, String> parameterMap = new HashMap<String, String>();
511                 //                Enumeration<Object> e = lookupParameters.keys();
512                 //                while (e.hasMoreElements()) {
513                 //                    String paramName = (String) e.nextElement();
514                 //                    parameterMap.put(paramName, lookupParameters.getProperty(paramName));
515                 //                }
516                 //
517                 //                String lookupUrl = responsibleModuleService.getExternalizableBusinessObjectLookupUrl(lookupObjectClass,
518                 //                        parameterMap);
519                 //                return performRedirect(form, lookupUrl, new Properties());
520             }
521         }
522 
523         return performRedirect(form, baseLookupUrl, lookupParameters);
524     }
525 
526     /**
527      * Invoked to provide the options for a suggest widget. The valid options are retrieved by the associated
528      * <code>AttributeQuery</code> for the field containing the suggest widget. The controller method picks
529      * out the query parameters from the request and calls <code>AttributeQueryService</code> to perform the
530      * suggest query and prepare the result object that will be exposed with JSON
531      */
532     @RequestMapping(method = RequestMethod.GET, params = "methodToCall=performFieldSuggest")
533     public
534     @ResponseBody
535     AttributeQueryResult performFieldSuggest(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
536             HttpServletRequest request, HttpServletResponse response) {
537 
538         // retrieve query fields from request
539         Map<String, String> queryParameters = new HashMap<String, String>();
540         for (Object parameterName : request.getParameterMap().keySet()) {
541             if (parameterName.toString().startsWith(UifParameters.QUERY_PARAMETER + ".")) {
542                 String fieldName = StringUtils.substringAfter(parameterName.toString(),
543                         UifParameters.QUERY_PARAMETER + ".");
544                 String fieldValue = request.getParameter(parameterName.toString());
545                 queryParameters.put(fieldName, fieldValue);
546             }
547         }
548 
549         // retrieve id for field to perform query for
550         String queryFieldId = request.getParameter(UifParameters.QUERY_FIELD_ID);
551         if (StringUtils.isBlank(queryFieldId)) {
552             throw new RuntimeException("Unable to find id for field to perform query on under request parameter name: "
553                     + UifParameters.QUERY_FIELD_ID);
554         }
555 
556         // get the field term to match
557         String queryTerm = request.getParameter(UifParameters.QUERY_TERM);
558         if (StringUtils.isBlank(queryTerm)) {
559             throw new RuntimeException(
560                     "Unable to find id for query term value for attribute query on under request parameter name: "
561                             + UifParameters.QUERY_TERM);
562         }
563 
564         // invoke attribute query service to perform the query
565         AttributeQueryResult queryResult = KRADServiceLocatorWeb.getAttributeQueryService().performFieldSuggestQuery(
566                 form.getView(), queryFieldId, queryTerm, queryParameters);
567 
568         return queryResult;
569     }
570 
571     /**
572      * Invoked to execute the <code>AttributeQuery</code> associated with a field given the query parameters
573      * found in the request. This controller method picks out the query parameters from the request and calls
574      * <code>AttributeQueryService</code> to perform the field query and prepare the result object
575      * that will be exposed with JSON. The result is then used to update field values in the UI with client
576      * script.
577      */
578     @RequestMapping(method = RequestMethod.GET, params = "methodToCall=performFieldQuery")
579     public
580     @ResponseBody
581     AttributeQueryResult performFieldQuery(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
582             HttpServletRequest request, HttpServletResponse response) {
583 
584         // retrieve query fields from request
585         Map<String, String> queryParameters = new HashMap<String, String>();
586         for (Object parameterName : request.getParameterMap().keySet()) {
587             if (parameterName.toString().startsWith(UifParameters.QUERY_PARAMETER + ".")) {
588                 String fieldName = StringUtils.substringAfter(parameterName.toString(),
589                         UifParameters.QUERY_PARAMETER + ".");
590                 String fieldValue = request.getParameter(parameterName.toString());
591                 queryParameters.put(fieldName, fieldValue);
592             }
593         }
594 
595         // retrieve id for field to perform query for
596         String queryFieldId = request.getParameter(UifParameters.QUERY_FIELD_ID);
597         if (StringUtils.isBlank(queryFieldId)) {
598             throw new RuntimeException("Unable to find id for field to perform query on under request parameter name: "
599                     + UifParameters.QUERY_FIELD_ID);
600         }
601 
602         // invoke attribute query service to perform the query
603         AttributeQueryResult queryResult = KRADServiceLocatorWeb.getAttributeQueryService().performFieldQuery(
604                 form.getView(), queryFieldId, queryParameters);
605 
606         return queryResult;
607     }
608 
609     /**
610      * Builds a <code>ModelAndView</code> instance configured to redirect to the
611      * URL formed by joining the base URL with the given URL parameters
612      *
613      * @param form - current form instance
614      * @param baseUrl - base url to redirect to
615      * @param urlParameters - properties containing key/value pairs for the url parameters
616      * @return ModelAndView configured to redirect to the given URL
617      */
618     protected ModelAndView performRedirect(UifFormBase form, String baseUrl, Properties urlParameters) {
619         // since we are redirecting and will not be rendering the view, we need to reset the view from the previous
620         form.setView(form.getPreviousView());
621 
622         // On post redirects we need to make sure we are sending the history forward:
623         urlParameters.setProperty(UifConstants.UrlParams.HISTORY, form.getFormHistory().getHistoryParameterString());
624 
625         // If this is an Light Box call only return the redirectURL view with the URL
626         // set this is to avoid automatic redirect when using light boxes
627         if (urlParameters.get(UifParameters.LIGHTBOX_CALL) != null &&
628                 urlParameters.get(UifParameters.LIGHTBOX_CALL).equals("true")) {
629             urlParameters.remove(UifParameters.LIGHTBOX_CALL);
630             String redirectUrl = UrlFactory.parameterizeUrl(baseUrl, urlParameters);
631 
632             ModelAndView modelAndView = new ModelAndView(UifConstants.SPRING_REDIRECT_ID);
633             modelAndView.addObject("redirectUrl", redirectUrl);
634             return modelAndView;
635         }
636 
637         String redirectUrl = UrlFactory.parameterizeUrl(baseUrl, urlParameters);
638         ModelAndView modelAndView = new ModelAndView(REDIRECT_PREFIX + redirectUrl);
639 
640         return modelAndView;
641     }
642 
643     protected ModelAndView getUIFModelAndView(UifFormBase form) {
644         return getUIFModelAndView(form, form.getPageId());
645     }
646 
647     /**
648      * Configures the <code>ModelAndView</code> instance containing the form
649      * data and pointing to the UIF generic spring view
650      *
651      * @param form - Form instance containing the model data
652      * @param pageId - Id of the page within the view that should be rendered, can
653      * be left blank in which the current or default page is rendered
654      * @return ModelAndView object with the contained form
655      */
656     protected ModelAndView getUIFModelAndView(UifFormBase form, String pageId) {
657         return UifWebUtils.getUIFModelAndView(form, pageId);
658     }
659 
660     protected ViewService getViewService() {
661         return KRADServiceLocatorWeb.getViewService();
662     }
663 
664     public SessionDocumentService getSessionDocumentService() {
665         return KRADServiceLocatorWeb.getSessionDocumentService();
666     }
667 
668 }