View Javadoc
1   /**
2    * Copyright 2005-2014 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 java.util.Enumeration;
19  import java.util.HashMap;
20  import java.util.Map;
21  import java.util.Map.Entry;
22  import java.util.Properties;
23  
24  import javax.servlet.http.HttpServletRequest;
25  import javax.servlet.http.HttpServletResponse;
26  
27  import org.apache.commons.lang.StringUtils;
28  import org.kuali.rice.core.api.config.property.ConfigContext;
29  import org.kuali.rice.core.api.exception.RiceRuntimeException;
30  import org.kuali.rice.kim.api.identity.Person;
31  import org.kuali.rice.krad.exception.AuthorizationException;
32  import org.kuali.rice.krad.lookup.LookupUtils;
33  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
34  import org.kuali.rice.krad.service.ModuleService;
35  import org.kuali.rice.krad.uif.UifConstants;
36  import org.kuali.rice.krad.uif.UifParameters;
37  import org.kuali.rice.krad.uif.UifPropertyPaths;
38  import org.kuali.rice.krad.uif.field.AttributeQueryResult;
39  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
40  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleRefreshBuild;
41  import org.kuali.rice.krad.uif.service.ViewService;
42  import org.kuali.rice.krad.uif.view.DialogManager;
43  import org.kuali.rice.krad.uif.view.MessageView;
44  import org.kuali.rice.krad.uif.view.View;
45  import org.kuali.rice.krad.util.GlobalVariables;
46  import org.kuali.rice.krad.util.KRADConstants;
47  import org.kuali.rice.krad.util.KRADUtils;
48  import org.kuali.rice.krad.util.UrlFactory;
49  import org.kuali.rice.krad.web.form.HistoryFlow;
50  import org.kuali.rice.krad.web.form.HistoryManager;
51  import org.kuali.rice.krad.web.form.UifFormBase;
52  import org.kuali.rice.krad.web.form.UifFormManager;
53  import org.springframework.util.Assert;
54  import org.springframework.validation.BindingResult;
55  import org.springframework.web.bind.annotation.ModelAttribute;
56  import org.springframework.web.bind.annotation.RequestMapping;
57  import org.springframework.web.bind.annotation.RequestMethod;
58  import org.springframework.web.bind.annotation.ResponseBody;
59  import org.springframework.web.servlet.ModelAndView;
60  import org.springframework.web.servlet.view.UrlBasedViewResolver;
61  
62  /**
63   * Base controller class for views within the KRAD User Interface Framework.
64   *
65   * <p>Provides common methods such as:
66   *
67   * <ul>
68   * <li>Authorization methods such as method to call check</li>
69   * <li>Preparing the View instance and setup in the returned ModelAndView</li>
70   * <li>Add/Delete Line Methods</li>
71   * <li>Navigation Methods</li>
72   * </ul>
73   *
74   * All subclass controller methods after processing should call one of the #getUIFModelAndView methods to
75   * setup the {@link org.kuali.rice.krad.uif.view.View} and return the {@link org.springframework.web.servlet.ModelAndView}
76   * instance.</p>
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       * @param response the http response object
92       */
93      @ModelAttribute(value = "KualiForm")
94      public UifFormBase initForm(HttpServletRequest request, HttpServletResponse response) {
95          // get Uif form manager from session if exists or setup a new one for the session
96          UifFormManager uifFormManager = (UifFormManager) request.getSession().getAttribute(UifParameters.FORM_MANAGER);
97          if (uifFormManager == null) {
98              uifFormManager = new UifFormManager();
99              request.getSession().setAttribute(UifParameters.FORM_MANAGER, uifFormManager);
100         }
101 
102         // add form manager to GlobalVariables for easy reference by other controller methods
103         GlobalVariables.setUifFormManager(uifFormManager);
104 
105         // create a new form for every request
106         UifFormBase requestForm = createInitialForm(request);
107 
108         String formKeyParam = request.getParameter(UifParameters.FORM_KEY);
109         if (StringUtils.isNotBlank(formKeyParam)) {
110             // retrieves the session form and updates the request from with the session transient attributes
111             uifFormManager.updateFormWithSession(requestForm, formKeyParam);
112         }
113 
114         //set the originally requested form key
115         String requestedFormKey = request.getParameter(UifParameters.REQUESTED_FORM_KEY);
116         if (StringUtils.isNotBlank(requestedFormKey)) {
117             requestForm.setRequestedFormKey(requestedFormKey);
118         } else {
119             requestForm.setRequestedFormKey(formKeyParam);
120         }
121 
122         //get the initial referer
123         String referer = request.getHeader(UifConstants.REFERER);
124 
125         //if none, set the no return flag string
126         if (StringUtils.isBlank(referer) && StringUtils.isBlank(requestForm.getReturnLocation())) {
127             requestForm.setReturnLocation(UifConstants.NO_RETURN);
128         } else if (StringUtils.isBlank(requestForm.getReturnLocation())) {
129             requestForm.setReturnLocation(referer);
130         }
131 
132         //get initial request params
133         if (requestForm.getInitialRequestParameters() == null) {
134             Map<String, String[]> requestParams = new HashMap<String, String[]>();
135             Enumeration<String> names = request.getParameterNames();
136 
137             while (names != null && names.hasMoreElements()) {
138                 String name = KRADUtils.stripXSSPatterns(names.nextElement());
139                 String[] values = KRADUtils.stripXSSPatterns(request.getParameterValues(name));
140 
141                 requestParams.put(name, values);
142             }
143 
144             requestParams.remove(UifConstants.UrlParams.LOGIN_USER);
145             requestForm.setInitialRequestParameters(requestParams);
146         }
147 
148         //set the original request url for this view/form
149         String requestUrl = KRADUtils.stripXSSPatterns(KRADUtils.getFullURL(request));
150         requestForm.setRequestUrl(requestUrl);
151 
152         Object historyManager = request.getSession().getAttribute(UifConstants.HistoryFlow.HISTORY_MANAGER);
153         String flowKey = request.getParameter(UifConstants.HistoryFlow.FLOW);
154 
155         //add history manager and current flowKey to the form
156         if (historyManager != null && historyManager instanceof HistoryManager) {
157             requestForm.setHistoryManager((HistoryManager) historyManager);
158             requestForm.setFlowKey(flowKey);
159         }
160 
161         // sets the request form in the request for later retrieval
162         request.setAttribute(UifConstants.REQUEST_FORM, requestForm);
163 
164         return requestForm;
165     }
166 
167     /**
168      * Called to create a new model(form) object when necessary. This usually occurs on the initial request
169      * in a conversation (when the model is not present in the session). This method must be
170      * overridden when extending a controller and using a different form type than the superclass.
171      *
172      * @param request - the http request that was made
173      */
174     protected abstract UifFormBase createInitialForm(HttpServletRequest request);
175 
176     /**
177      * Default method mapping for cases where the method to call is not passed, calls the start method
178      */
179     @MethodAccessible
180     @RequestMapping()
181     public ModelAndView defaultMapping(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
182             HttpServletRequest request, HttpServletResponse response) {
183         return start(form, request, response);
184     }
185 
186     /**
187      * Initial method called when requesting a new view instance which checks authorization and forwards
188      * the view for rendering
189      */
190     @MethodAccessible
191     @RequestMapping(params = "methodToCall=start")
192     public ModelAndView start(@ModelAttribute("KualiForm") UifFormBase form, HttpServletRequest request,
193             HttpServletResponse response) {
194 
195         // check view authorization
196         // TODO: this needs to be invoked for each request
197         if (form.getView() != null) {
198 
199             //indicate that default values need to be applied to this view
200             form.addViewThatNeedsDefaultValuesApplied(form.getViewId());
201 
202             String methodToCall = request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER);
203             checkViewAuthorization(form, methodToCall);
204         }
205 
206         return getUIFModelAndView(form);
207     }
208 
209     /**
210      * Invokes the configured {@link org.kuali.rice.krad.uif.view.ViewAuthorizer} to verify the user has access to
211      * open the view. An exception is thrown if access has not been granted
212      *
213      * <p>
214      * Note this method is invoked automatically by the controller interceptor for each request
215      * </p>
216      *
217      * @param form - form instance containing the request data
218      * @param methodToCall - the request parameter 'methodToCall' which is used to determine the controller
219      * method invoked
220      */
221     public void checkViewAuthorization(UifFormBase form, String methodToCall) throws AuthorizationException {
222         // if user session not established we cannnot authorize the view request
223         if (GlobalVariables.getUserSession() == null) {
224             return;
225         }
226 
227         Person user = GlobalVariables.getUserSession().getPerson();
228 
229         boolean canOpenView = form.getView().getAuthorizer().canOpenView(form.getView(), form, user);
230         if (!canOpenView) {
231             throw new AuthorizationException(user.getPrincipalName(), "open", form.getView().getId(),
232                     "User '" + user.getPrincipalName() + "' is not authorized to open view ID: " +
233                             form.getView().getId(), null);
234         }
235     }
236 
237     /**
238      * Invoked when a session timeout occurs, default impl does nothing but render the view
239      */
240     @MethodAccessible
241     @RequestMapping(params = "methodToCall=sessionTimeout")
242     public ModelAndView sessionTimeout(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
243             HttpServletRequest request, HttpServletResponse response) {
244         return getUIFModelAndView(form);
245     }
246 
247     /**
248      * Called by the add line action for a new collection line.
249      *
250      * <p>Method determines which collection the add action was selected for and invokes the view helper
251      * service to add the line</p>
252      */
253     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=addLine")
254     public ModelAndView addLine(@ModelAttribute("KualiForm") final UifFormBase uifForm, BindingResult result,
255             HttpServletRequest request, HttpServletResponse response) {
256         final String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELECTED_COLLECTION_PATH);
257         final String selectedCollectionId = uifForm.getActionParamaterValue(UifParameters.SELECTED_COLLECTION_ID);
258 
259         if (StringUtils.isBlank(selectedCollectionPath)) {
260             throw new RuntimeException("Selected collection was not set for add line action, cannot add new line");
261         }
262 
263         ViewLifecycle.encapsulateLifecycle(uifForm.getView(), uifForm, uifForm.getViewPostMetadata(), null, request,
264                 response, new Runnable() {
265             @Override
266             public void run() {
267                 ViewLifecycle.getHelper().processCollectionAddLine(uifForm, selectedCollectionId,
268                         selectedCollectionPath);
269             }
270         });
271 
272         return getUIFModelAndView(uifForm);
273     }
274 
275     /**
276      * Called by the add blank line action for a new collection line
277      *
278      * <p>
279      * Method determines which collection the add action was selected for and invokes the view helper service to
280      * add the blank line.
281      * </p>
282      *
283      * @param uifForm - form instance containing the request data
284      * @return the  ModelAndView object
285      */
286     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=addBlankLine")
287     public ModelAndView addBlankLine(@ModelAttribute("KualiForm") final UifFormBase uifForm, HttpServletRequest request,
288             HttpServletResponse response) {
289 
290         final String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELECTED_COLLECTION_PATH);
291         final String selectedCollectionId = uifForm.getActionParamaterValue(UifParameters.SELECTED_COLLECTION_ID);
292 
293         if (StringUtils.isBlank(selectedCollectionPath)) {
294             throw new RuntimeException("Selected collection was not set for add line action, cannot add new line");
295         }
296 
297         ViewLifecycle.encapsulateLifecycle(uifForm.getView(), uifForm, uifForm.getViewPostMetadata(), null, request,
298                 response, new Runnable() {
299             @Override
300             public void run() {
301                 ViewLifecycle.getHelper().processCollectionAddBlankLine(uifForm, selectedCollectionId,
302                         selectedCollectionPath);
303             }
304         });
305 
306         return getUIFModelAndView(uifForm);
307     }
308 
309     /**
310      * Called by the save line action for a new collection line. Does server side validation and provides hook
311      * for client application to persist specific data.
312      */
313     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=saveLine")
314     public ModelAndView saveLine(@ModelAttribute("KualiForm") final UifFormBase uifForm, BindingResult result,
315             HttpServletRequest request, HttpServletResponse response) {
316 
317         final String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELECTED_COLLECTION_PATH);
318         final String selectedCollectionId = uifForm.getActionParamaterValue(UifParameters.SELECTED_COLLECTION_ID);
319 
320         if (StringUtils.isBlank(selectedCollectionPath)) {
321             throw new RuntimeException("Selected collection was not set for add line action, cannot add new line");
322         }
323 
324         String selectedLine = uifForm.getActionParamaterValue(UifParameters.SELECTED_LINE_INDEX);
325         final int selectedLineIndex;
326         if (StringUtils.isNotBlank(selectedLine)) {
327             selectedLineIndex = Integer.parseInt(selectedLine);
328         } else {
329             selectedLineIndex = -1;
330         }
331 
332         if (selectedLineIndex == -1) {
333             throw new RuntimeException("Selected line index was not set for delete line action, cannot delete line");
334         }
335 
336         ViewLifecycle.encapsulateLifecycle(uifForm.getView(), uifForm, uifForm.getViewPostMetadata(), null, request,
337                 response, new Runnable() {
338             @Override
339             public void run() {
340                 ViewLifecycle.getHelper().processCollectionSaveLine(uifForm, selectedCollectionId,
341                         selectedCollectionPath, selectedLineIndex);
342             }
343         });
344 
345         return getUIFModelAndView(uifForm);
346     }
347 
348     /**
349      * Called by the delete line action for a model collection. Method
350      * determines which collection the action was selected for and the line
351      * index that should be removed, then invokes the view helper service to
352      * process the action
353      */
354     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=deleteLine")
355     public ModelAndView deleteLine(@ModelAttribute("KualiForm") final UifFormBase uifForm, BindingResult result,
356             HttpServletRequest request, HttpServletResponse response) {
357 
358         final String selectedCollectionPath = uifForm.getActionParamaterValue(UifParameters.SELECTED_COLLECTION_PATH);
359         final String selectedCollectionId = uifForm.getActionParamaterValue(UifParameters.SELECTED_COLLECTION_ID);
360 
361         if (StringUtils.isBlank(selectedCollectionPath)) {
362             throw new RuntimeException("Selected collection was not set for delete line action, cannot delete line");
363         }
364 
365         String selectedLine = uifForm.getActionParamaterValue(UifParameters.SELECTED_LINE_INDEX);
366         final int selectedLineIndex;
367         if (StringUtils.isNotBlank(selectedLine)) {
368             selectedLineIndex = Integer.parseInt(selectedLine);
369         } else {
370             selectedLineIndex = -1;
371         }
372 
373         if (selectedLineIndex == -1) {
374             throw new RuntimeException("Selected line index was not set for delete line action, cannot delete line");
375         }
376 
377         ViewLifecycle.encapsulateLifecycle(uifForm.getView(), uifForm, uifForm.getViewPostMetadata(), null, request,
378                 response, new Runnable() {
379             @Override
380             public void run() {
381                 ViewLifecycle.getHelper().processCollectionDeleteLine(uifForm, selectedCollectionId,
382                         selectedCollectionPath, selectedLineIndex);
383             }
384         });
385 
386         return getUIFModelAndView(uifForm);
387     }
388 
389     /**
390      * Just returns as if return with no value was selected.
391      */
392     @RequestMapping(params = "methodToCall=cancel")
393     public ModelAndView cancel(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
394             HttpServletRequest request, HttpServletResponse response) {
395         return back(form, result, request, response);
396     }
397 
398     /**
399      * Attempts to go back by looking at various return mechanisms in HistoryFlow and on the form.  If a back cannot
400      * be determined, returns to the application url.
401      */
402     @RequestMapping(params = "methodToCall=back")
403     public ModelAndView back(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
404             HttpServletRequest request, HttpServletResponse response) {
405         Properties props = new Properties();
406         props.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.REFRESH);
407 
408         if (StringUtils.isNotBlank(form.getReturnFormKey())) {
409             props.put(UifParameters.FORM_KEY, form.getReturnFormKey());
410         }
411 
412         HistoryFlow historyFlow = form.getHistoryManager().getMostRecentFlowByFormKey(form.getFlowKey(),
413                 form.getRequestedFormKey());
414 
415         String returnUrl = form.getReturnLocation();
416 
417         //use history flow return location
418         if (historyFlow != null) {
419             returnUrl = historyFlow.getFlowReturnPoint();
420         }
421 
422         //return to start handling
423         String returnToStart = form.getActionParamaterValue(UifConstants.HistoryFlow.RETURN_TO_START);
424         if (StringUtils.isBlank(returnToStart)) {
425             returnToStart = request.getParameter(UifConstants.HistoryFlow.RETURN_TO_START);
426         }
427 
428         if (StringUtils.isNotBlank(returnToStart) && Boolean.parseBoolean(returnToStart) && historyFlow != null &&
429                 StringUtils.isNotBlank(historyFlow.getFlowStartPoint())) {
430             returnUrl = historyFlow.getFlowStartPoint();
431         }
432 
433         //return to app url if returnUrl still blank
434         if (StringUtils.isBlank(returnUrl) || returnUrl.equals(UifConstants.NO_RETURN)) {
435             returnUrl = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.APPLICATION_URL_KEY);
436         }
437 
438         // clear current form from session
439         GlobalVariables.getUifFormManager().removeSessionForm(form);
440 
441         return performRedirect(form, returnUrl, props);
442     }
443 
444     /**
445      * Invoked to navigate back one page in history.
446      *
447      * @param form - form object that should contain the history object
448      */
449     @RequestMapping(params = "methodToCall=returnToPrevious")
450     public ModelAndView returnToPrevious(@ModelAttribute("KualiForm") UifFormBase form) {
451 
452         return returnToHistory(form, false);
453     }
454 
455     /**
456      * Invoked to navigate back to the first page in history.
457      *
458      * @param form - form object that should contain the history object
459      */
460     @RequestMapping(params = "methodToCall=returnToHub")
461     public ModelAndView returnToHub(@ModelAttribute("KualiForm") UifFormBase form) {
462 
463         return returnToHistory(form, true);
464     }
465 
466     /**
467      * Invoked to navigate back to a history entry. The homeFlag will determine whether navigation
468      * will be back to the first or last history entry.
469      *
470      * @param form - form object that should contain the history object
471      * @param homeFlag - if true will navigate back to first entry else will navigate to last entry
472      * in the history
473      */
474     public ModelAndView returnToHistory(UifFormBase form, boolean homeFlag) {
475         String returnUrl = form.getReturnLocation();
476 
477         if (StringUtils.isBlank(returnUrl) || homeFlag) {
478             returnUrl = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.APPLICATION_URL_KEY);
479         }
480 
481         // Add the refresh call
482         Properties props = new Properties();
483         props.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.REFRESH);
484 
485         // clear current form from session
486         GlobalVariables.getUifFormManager().removeSessionForm(form);
487 
488         return performRedirect(form, returnUrl, props);
489     }
490 
491     /**
492      * Handles menu navigation between view pages
493      */
494     @MethodAccessible
495     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=navigate")
496     public ModelAndView navigate(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
497             HttpServletRequest request, HttpServletResponse response) {
498         String pageId = form.getActionParamaterValue(UifParameters.NAVIGATE_TO_PAGE_ID);
499 
500         //clear dirty flag, if set
501         form.setDirtyForm(false);
502 
503         return getUIFModelAndView(form, pageId);
504     }
505 
506     /**
507      * Invoked to refresh a view, generally when returning from another view (for example a lookup))
508      */
509     @MethodAccessible
510     @RequestMapping(params = "methodToCall=refresh")
511     public ModelAndView refresh(@ModelAttribute("KualiForm") final UifFormBase form, BindingResult result,
512             final HttpServletRequest request, HttpServletResponse response) throws Exception {
513 
514         ViewLifecycle.encapsulateLifecycle(form.getView(), form, form.getViewPostMetadata(), null, request, response,
515                 new ViewLifecycleRefreshBuild());
516 
517         return getUIFModelAndView(form);
518     }
519 
520     /**
521      * Builds up a URL to the lookup view based on the given post action
522      * parameters and redirects
523      */
524     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=performLookup")
525     public ModelAndView performLookup(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
526             HttpServletRequest request, HttpServletResponse response) {
527         Properties lookupParameters = form.getActionParametersAsProperties();
528 
529         String lookupObjectClassName = (String) lookupParameters.get(UifParameters.DATA_OBJECT_CLASS_NAME);
530         Class<?> lookupObjectClass = null;
531         try {
532             lookupObjectClass = Class.forName(lookupObjectClassName);
533         } catch (ClassNotFoundException e) {
534             LOG.error("Unable to get class for name: " + lookupObjectClassName);
535             throw new RuntimeException("Unable to get class for name: " + lookupObjectClassName, e);
536         }
537 
538         // get form values for the lookup parameter fields
539         String lookupParameterString = (String) lookupParameters.get(UifParameters.LOOKUP_PARAMETERS);
540         if (lookupParameterString != null) {
541             Map<String, String> lookupParameterFields = KRADUtils.getMapFromParameterString(lookupParameterString);
542             for (Entry<String, String> lookupParameter : lookupParameterFields.entrySet()) {
543                 String lookupParameterValue = LookupUtils.retrieveLookupParameterValue(form, request, lookupObjectClass,
544                         lookupParameter.getValue(), lookupParameter.getKey());
545 
546                 if (StringUtils.isNotBlank(lookupParameterValue)) {
547                     lookupParameters.put(UifPropertyPaths.LOOKUP_CRITERIA + "['" + lookupParameter.getValue() + "']",
548                             lookupParameterValue);
549                 }
550             }
551 
552             lookupParameters.remove(UifParameters.LOOKUP_PARAMETERS);
553         }
554 
555         String baseLookupUrl = (String) lookupParameters.get(UifParameters.BASE_LOOKUP_URL);
556         lookupParameters.remove(UifParameters.BASE_LOOKUP_URL);
557 
558         // set lookup method to call
559         lookupParameters.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.START);
560         String autoSearchString = (String) lookupParameters.get(UifParameters.AUTO_SEARCH);
561         if (Boolean.parseBoolean(autoSearchString)) {
562             lookupParameters.put(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.SEARCH);
563         }
564 
565         lookupParameters.put(UifParameters.RETURN_LOCATION, form.getFormPostUrl());
566         lookupParameters.put(UifParameters.RETURN_FORM_KEY, form.getFormKey());
567 
568         // special check for external object classes
569         if (lookupObjectClass != null) {
570             ModuleService responsibleModuleService =
571                     KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(lookupObjectClass);
572             if (responsibleModuleService != null && responsibleModuleService.isExternalizable(lookupObjectClass)) {
573                 String lookupUrl = responsibleModuleService.getExternalizableDataObjectLookupUrl(lookupObjectClass,
574                         lookupParameters);
575 
576                 return performRedirect(form, lookupUrl, new Properties());
577             }
578         }
579 
580         return performRedirect(form, baseLookupUrl, lookupParameters);
581     }
582 
583     /**
584      * Checks the form/view against all current and future validations and returns warnings for any validations
585      * that fail
586      */
587     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=checkForm")
588     public ModelAndView checkForm(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
589             HttpServletRequest request, HttpServletResponse response) {
590         KRADServiceLocatorWeb.getViewValidationService().validateViewSimulation(form);
591 
592         return getUIFModelAndView(form);
593     }
594 
595     /**
596      * Invoked to provide the options for a suggest widget. The valid options are retrieved by the associated
597      * <code>AttributeQuery</code> for the field containing the suggest widget. The controller method picks
598      * out the query parameters from the request and calls <code>AttributeQueryService</code> to perform the
599      * suggest query and prepare the result object that will be exposed with JSON
600      */
601     @MethodAccessible
602     @RequestMapping(method = RequestMethod.GET, params = "methodToCall=performFieldSuggest")
603     public
604     @ResponseBody
605     AttributeQueryResult performFieldSuggest(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
606             HttpServletRequest request, HttpServletResponse response) {
607 
608         // retrieve query fields from request
609         Map<String, String> queryParameters = new HashMap<String, String>();
610         for (Object parameterName : request.getParameterMap().keySet()) {
611             if (parameterName.toString().startsWith(UifParameters.QUERY_PARAMETER + ".")) {
612                 String fieldName = StringUtils.substringAfter(parameterName.toString(),
613                         UifParameters.QUERY_PARAMETER + ".");
614                 String fieldValue = request.getParameter(parameterName.toString());
615                 queryParameters.put(fieldName, fieldValue);
616             }
617         }
618 
619         // retrieve id for field to perform query for
620         String queryFieldId = request.getParameter(UifParameters.QUERY_FIELD_ID);
621         if (StringUtils.isBlank(queryFieldId)) {
622             throw new RuntimeException(
623                     "Unable to find id for field to perform query on under request parameter name: " +
624                             UifParameters.QUERY_FIELD_ID);
625         }
626 
627         // get the field term to match
628         String queryTerm = request.getParameter(UifParameters.QUERY_TERM);
629         if (StringUtils.isBlank(queryTerm)) {
630             throw new RuntimeException(
631                     "Unable to find id for query term value for attribute query on under request parameter name: " +
632                             UifParameters.QUERY_TERM);
633         }
634 
635         // invoke attribute query service to perform the query
636         AttributeQueryResult queryResult = KRADServiceLocatorWeb.getAttributeQueryService().performFieldSuggestQuery(
637                 form.getViewPostMetadata(), queryFieldId, queryTerm, queryParameters);
638 
639         return queryResult;
640     }
641 
642     /**
643      * Invoked to execute the <code>AttributeQuery</code> associated with a field given the query parameters
644      * found in the request. This controller method picks out the query parameters from the request and calls
645      * <code>AttributeQueryService</code> to perform the field query and prepare the result object
646      * that will be exposed with JSON. The result is then used to update field values in the UI with client
647      * script.
648      */
649     @MethodAccessible
650     @RequestMapping(method = RequestMethod.GET, params = "methodToCall=performFieldQuery")
651     public
652     @ResponseBody
653     AttributeQueryResult performFieldQuery(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
654             HttpServletRequest request, HttpServletResponse response) {
655 
656         // retrieve query fields from request
657         Map<String, String> queryParameters = new HashMap<String, String>();
658         for (Object parameterName : request.getParameterMap().keySet()) {
659             if (parameterName.toString().startsWith(UifParameters.QUERY_PARAMETER + ".")) {
660                 String fieldName = StringUtils.substringAfter(parameterName.toString(),
661                         UifParameters.QUERY_PARAMETER + ".");
662                 String fieldValue = request.getParameter(parameterName.toString());
663                 queryParameters.put(fieldName, fieldValue);
664             }
665         }
666 
667         // retrieve id for field to perform query for
668         String queryFieldId = request.getParameter(UifParameters.QUERY_FIELD_ID);
669         if (StringUtils.isBlank(queryFieldId)) {
670             throw new RuntimeException(
671                     "Unable to find id for field to perform query on under request parameter name: " +
672                             UifParameters.QUERY_FIELD_ID);
673         }
674 
675         // invoke attribute query service to perform the query
676         AttributeQueryResult queryResult = KRADServiceLocatorWeb.getAttributeQueryService().performFieldQuery(
677                 form.getViewPostMetadata(), queryFieldId, queryParameters);
678 
679         return queryResult;
680     }
681 
682     /**
683      * returns whether this dialog has been displayed on the client
684      *
685      * @param dialogId - the id of the dialog
686      * @param form - form instance containing the request data
687      * @return boolean - true if dialog has been displayed, false if not
688      */
689     protected boolean hasDialogBeenDisplayed(String dialogId, UifFormBase form) {
690         return (form.getDialogManager().hasDialogBeenDisplayed(dialogId));
691     }
692 
693     /**
694      * returns whether the dialog has already been answered by the user
695      *
696      * @param dialogId - identifier for the dialog group
697      * @param form - form instance containing the request data
698      * @return boolean - true if client has already responded to the dialog, false otherwise
699      */
700     protected boolean hasDialogBeenAnswered(String dialogId, UifFormBase form) {
701         return (form.getDialogManager().hasDialogBeenAnswered(dialogId));
702     }
703 
704     /**
705      * Sets the status of the dialog tracking record to indicate that this dialog
706      * has not yet been asked or answered
707      *
708      * @param dialogId - the id of the dialog
709      * @param form - form instance containing the request data
710      */
711     protected void resetDialogStatus(String dialogId, UifFormBase form) {
712         form.getDialogManager().resetDialogStatus(dialogId);
713     }
714 
715     /**
716      * Handles a modal dialog interaction with the client user when a @{boolean} response is desired
717      *
718      * <p>
719      * If this modal dialog has not yet been presented to the user, a runtime exception is thrown.   Use the following
720      * code in the view controller to ensure the dialog has been displayed and answered:
721      * <pre>{@code
722      *  DialogManager dm = form.getDialogManager();
723      *  if (!dm.hasDialogBeenAnswered(dialogId)) {
724      *      return showDialog(dialogId, form, request, response);
725      *  }
726      *  answer = getBooleanDialogResponse(dialogId, form, request, response);
727      * }</pre>
728      * </p>
729      *
730      * <p>
731      * If the dialog has already been answered by the user.  The boolean value representing the
732      * option chosen by the user is returned back to the calling controller
733      * </p>
734      *
735      * @param dialogId - identifier of the dialog group
736      * @param form - form instance containing the request data
737      * @param request - the http request
738      * @param response - the http response
739      * @return boolean - true if user chose affirmative response, false if negative response was chosen
740      * @throws RiceRuntimeException when dialog has not been answered.
741      */
742     protected boolean getBooleanDialogResponse(String dialogId, UifFormBase form, HttpServletRequest request,
743             HttpServletResponse response) {
744         DialogManager dm = form.getDialogManager();
745         if (!dm.hasDialogBeenAnswered(dialogId)) {
746 
747             // ToDo: It would be nice if showDialog could be called here and avoid this exception.
748             //       This would also remove the need of having to call showDialog explicitly.
749 
750             throw new RiceRuntimeException("Dialog has not yet been answered by client. " +
751                     "Check that hasDialogBeenAnswered(id) returns true.");
752         }
753 
754         return dm.wasDialogAnswerAffirmative(dialogId);
755     }
756 
757     /**
758      * Handles a modal dialog interaction with the client user when a @{code String} response is desired
759      *
760      * <p>
761      * If this modal dialog has not yet been presented to the user, a runtime exception is thrown.   Use the following
762      * code in the view controller to ensure the dialog has been displayed and answered:
763      * <pre>{@code
764      *  DialogManager dm = form.getDialogManager();
765      *  if (!dm.hasDialogBeenAnswered(dialogId)) {
766      *      return showDialog(dialogId, form, request, response);
767      *  }
768      *  answer = getBooleanDialogResponse(dialogId, form, request, response);
769      * }</pre>
770      * </p>
771      *
772      * <p>
773      * If the dialog has already been answered by the user.  The string value is the key string of the key/value pair
774      * assigned to the button that the user chose.
775      * </p>
776      *
777      * @param dialogId - identifier of the dialog group
778      * @param form - form instance containing the request data
779      * @param request - the http request
780      * @param response - the http response
781      * @return the key string of the response button
782      * @throws RiceRuntimeException when dialog has not been answered.
783      */
784 
785     protected String getStringDialogResponse(String dialogId, UifFormBase form, HttpServletRequest request,
786             HttpServletResponse response) {
787         DialogManager dm = form.getDialogManager();
788         if (!dm.hasDialogBeenAnswered(dialogId)) {
789             // ToDo: It would be nice if showDialog could be called here and avoid this exception.
790             //       This would also remove the need of having to call showDialog explicitly.
791 
792             throw new RiceRuntimeException("Dialog has not yet been answered by client. " +
793                     "Check that hasDialogBeenAnswered(id) returns true.");
794         }
795 
796         return dm.getDialogAnswer(dialogId);
797     }
798 
799     /**
800      * Complete the response directly and launch lightbox with dialog content upon returning back to the client. If it
801      * is an ajax request then set the ajaxReturnType and set the updateComponentId to the dialogId.
802      *
803      * <p>
804      * Need to build up the view/component properly as we would if we returned normally back to the DispatcherServlet
805      * from the controller method.
806      * </p>
807      *
808      * @param dialogId - id of the dialog or group to use as content in the lightbox.
809      * @param form - the form associated with the view
810      * @param request - the http request
811      * @param response - the http response
812      * @return will return void.  actually, won't return at all.
813      * @throws Exception
814      */
815     protected ModelAndView showDialog(String dialogId, UifFormBase form, HttpServletRequest request,
816             HttpServletResponse response) {
817         // js script to invoke lightbox: runs onDocumentReady
818         form.setLightboxScript("openLightboxOnLoad('" + dialogId + "');");
819         form.getDialogManager().addDialog(dialogId, form.getMethodToCall());
820 
821         // if the dialog is being invoked sever side via ajax set the ajaxReturnType to update-dialog
822         // and set the updateComponentId to the dialogId
823         if (form.isAjaxRequest()) {
824             form.setAjaxReturnType(UifConstants.AjaxReturnTypes.UPDATEDIALOG.getKey());
825             form.setUpdateComponentId(dialogId);
826         }
827 
828         return getUIFModelAndView(form);
829     }
830 
831     /**
832      * Common return point for dialogs
833      *
834      * <p>
835      * Determines the user responses to the dialog. Performs dialog management and then redirects to the
836      * original contoller method.
837      * </p>
838      *
839      * @param form - current form
840      * @param result - binding result
841      * @param request - http request
842      * @param response - http response
843      * @return ModelAndView setup for redirect to original controller methodToCall
844      * @throws Exception
845      */
846     @MethodAccessible
847     @RequestMapping(params = "methodToCall=returnFromLightbox")
848     public ModelAndView returnFromLightbox(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
849             HttpServletRequest request, HttpServletResponse response) {
850         String newMethodToCall = "";
851 
852         // Save user responses from dialog
853         DialogManager dm = form.getDialogManager();
854         String dialogId = dm.getCurrentDialogId();
855         if (dialogId == null) {
856             // may have been invoked by client.
857             // TODO:  handle this case (scheduled for 2.2-m3)
858             // for now, log WARNING and default to start, can we add a growl?
859             newMethodToCall = "start";
860         } else {
861             dm.setDialogAnswer(dialogId, form.getDialogResponse());
862             dm.setDialogExplanation(dialogId, form.getDialogExplanation());
863             newMethodToCall = dm.getDialogReturnMethod(dialogId);
864             dm.setCurrentDialogId(null);
865         }
866 
867         // call intended controller method
868         Properties props = new Properties();
869         props.put(UifParameters.METHOD_TO_CALL, newMethodToCall);
870         props.put(UifParameters.VIEW_ID, form.getViewId());
871         props.put(UifParameters.FORM_KEY, form.getFormKey());
872         props.put(UifParameters.AJAX_REQUEST, "false");
873 
874         return performRedirect(form, form.getFormPostUrl(), props);
875     }
876 
877     /**
878      * Builds a <code>ModelAndView</code> instance configured to redirect to the
879      * URL formed by joining the base URL with the given URL parameters
880      *
881      * @param form current form instance
882      * @param baseUrl base url to redirect to
883      * @param urlParameters properties containing key/value pairs for the url parameters, if null or empty,
884      * the baseUrl will be used as the full URL
885      * @return ModelAndView configured to redirect to the given URL
886      */
887     protected ModelAndView performRedirect(UifFormBase form, String baseUrl, Properties urlParameters) {
888         String redirectUrl = UrlFactory.parameterizeUrl(baseUrl, urlParameters);
889 
890         return performRedirect(form, redirectUrl);
891     }
892 
893     /**
894      * Builds a <code>ModelAndView</code> instance configured to redirect to the given URL
895      *
896      * @param form current form instance
897      * @param redirectUrl URL to redirect to
898      * @return ModelAndView configured to redirect to the given URL
899      */
900     protected ModelAndView performRedirect(UifFormBase form, String redirectUrl) {
901         // indicate a redirect is occuring to prevent view processing down the line
902         form.setRequestRedirected(true);
903 
904         // set the ajaxReturnType on the form this will override the return type requested by the client
905         form.setAjaxReturnType(UifConstants.AjaxReturnTypes.REDIRECT.getKey());
906 
907         ModelAndView modelAndView;
908         if (form.isAjaxRequest()) {
909             modelAndView = getUIFModelAndView(form, form.getPageId());
910             modelAndView.addObject("redirectUrl", redirectUrl);
911         } else {
912             modelAndView = new ModelAndView(UifConstants.REDIRECT_PREFIX + redirectUrl);
913         }
914 
915         return modelAndView;
916     }
917 
918     /**
919      * Builds a message view from the given header and message text then forwards the UIF model and view
920      *
921      * <p>
922      * If an error or other type of interruption occurs during the request processing the controller can
923      * invoke this message to display the message to the user. This will abandon the view that was requested
924      * and display a view with just the message
925      * </p>
926      *
927      * @param form UIF form instance
928      * @param headerText header text for the message view (can be blank)
929      * @param messageText text for the message to display
930      * @return ModelAndView
931      */
932     protected ModelAndView getMessageView(UifFormBase form, String headerText, String messageText) {
933         // get a new message view
934         MessageView messageView = (MessageView) getViewService().getViewById(UifConstants.MESSAGE_VIEW_ID);
935 
936         messageView.setHeaderText(headerText);
937         messageView.setMessageText(messageText);
938 
939         form.setViewId(UifConstants.MESSAGE_VIEW_ID);
940         form.setView(messageView);
941 
942         return getUIFModelAndView(form);
943     }
944 
945     /**
946      * Retrieve a page defined by the page number parameter for a collection group.
947      */
948     @MethodAccessible
949     @RequestMapping(params = "methodToCall=retrieveCollectionPage")
950     public ModelAndView retrieveCollectionPage(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
951             HttpServletRequest request, HttpServletResponse response) throws Exception {
952         form.setCollectionPagingRequest(true);
953 
954         return getUIFModelAndView(form);
955     }
956 
957     /**
958      * Get method for getting aaData for jquery datatables which are using sAjaxSource option.
959      *
960      * <p>This will render the aaData JSON for the displayed page of the table matching the tableId passed in the
961      * request parameters.</p>
962      */
963     @MethodAccessible
964     @RequestMapping(method = RequestMethod.GET, params = "methodToCall=tableJsonRetrieval")
965     public ModelAndView tableJsonRetrieval(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
966             HttpServletRequest request, HttpServletResponse response) {
967         form.setCollectionPagingRequest(true);
968 
969         // set property to trigger special JSON rendering logic
970         form.setRequestJsonTemplate(UifConstants.TableToolsValues.JSON_TEMPLATE);
971 
972         return getUIFModelAndView(form);
973     }
974 
975     /**
976      * Configures the <code>ModelAndView</code> instance containing the form
977      * data and pointing to the UIF generic spring view
978      *
979      * @param form form instance containing the model data
980      * @return ModelAndView object with the contained form
981      */
982     protected ModelAndView getUIFModelAndView(UifFormBase form) {
983         return getUIFModelAndView(form, form.getPageId());
984     }
985 
986     /**
987      * Configures the <code>ModelAndView</code> instance containing the form
988      * data and pointing to the UIF generic spring view
989      *
990      * @param form form instance containing the model data
991      * @param pageId id of the page within the view that should be rendered, can
992      * be left blank in which the current or default page is rendered
993      * @return ModelAndView object with the contained form
994      */
995     protected ModelAndView getUIFModelAndView(UifFormBase form, String pageId) {
996         return UifControllerHelper.getUIFModelAndView(form, pageId);
997     }
998 
999     /**
1000      * Retrieves a new view instance for the given view id and then configures the <code>ModelAndView</code>
1001      * instance containing the form data and pointing to the UIF generic spring view
1002      *
1003      * @param form form instance containing the model data
1004      * @param viewId id for the view that should be built
1005      * @return ModelAndView object with the contained form
1006      */
1007     protected ModelAndView getUIFModelAndViewWithInit(UifFormBase form, String viewId) {
1008         View view = getViewService().getViewById(viewId);
1009 
1010         Assert.notNull(view, "View not found with id: " + viewId);
1011 
1012         form.setView(view);
1013         form.setViewId(viewId);
1014 
1015         return UifControllerHelper.getUIFModelAndView(form, form.getPageId());
1016     }
1017 
1018     /**
1019      * Configures the <code>ModelAndView</code> instance containing the form data and pointing to the UIF
1020      * generic spring view, additional attributes may be exposed to the view through the map argument
1021      *
1022      * @param form form instance containing the model data
1023      * @param additionalViewAttributes map of additional attributes to expose, key will be string the object
1024      * is exposed under
1025      * @return ModelAndView object with the contained form
1026      */
1027     protected ModelAndView getUIFModelAndView(UifFormBase form, Map<String, Object> additionalViewAttributes) {
1028         ModelAndView modelAndView = UifControllerHelper.getUIFModelAndView(form, form.getPageId());
1029 
1030         if (additionalViewAttributes != null) {
1031             for (Map.Entry<String, Object> additionalViewAttribute : additionalViewAttributes.entrySet()) {
1032                 modelAndView.getModelMap().put(additionalViewAttribute.getKey(), additionalViewAttribute.getValue());
1033             }
1034         }
1035 
1036         return modelAndView;
1037     }
1038 
1039     /**
1040      * Convenience method for getting an instance of the view service.
1041      *
1042      * @return view service implementation
1043      */
1044     protected ViewService getViewService() {
1045         return KRADServiceLocatorWeb.getViewService();
1046     }
1047 
1048 }