001    /**
002     * Copyright 2005-2011 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.krad.web.controller;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.kuali.rice.core.api.config.property.ConfigContext;
020    import org.kuali.rice.core.api.exception.RiceRuntimeException;
021    import org.kuali.rice.core.web.format.BooleanFormatter;
022    import org.kuali.rice.kim.api.KimConstants;
023    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
024    import org.kuali.rice.krad.UserSession;
025    import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
026    import org.kuali.rice.krad.exception.AuthorizationException;
027    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
028    import org.kuali.rice.krad.service.ModuleService;
029    import org.kuali.rice.krad.service.SessionDocumentService;
030    import org.kuali.rice.krad.uif.UifConstants;
031    import org.kuali.rice.krad.uif.UifParameters;
032    import org.kuali.rice.krad.uif.UifPropertyPaths;
033    import org.kuali.rice.krad.uif.component.Component;
034    import org.kuali.rice.krad.uif.container.CollectionGroup;
035    import org.kuali.rice.krad.uif.field.AttributeQueryResult;
036    import org.kuali.rice.krad.uif.service.ViewService;
037    import org.kuali.rice.krad.uif.util.ComponentFactory;
038    import org.kuali.rice.krad.uif.util.LookupInquiryUtils;
039    import org.kuali.rice.krad.uif.util.UifWebUtils;
040    import org.kuali.rice.krad.uif.view.History;
041    import org.kuali.rice.krad.uif.view.HistoryEntry;
042    import org.kuali.rice.krad.uif.view.View;
043    import org.kuali.rice.krad.util.GlobalVariables;
044    import org.kuali.rice.krad.util.KRADConstants;
045    import org.kuali.rice.krad.util.KRADUtils;
046    import org.kuali.rice.krad.util.UrlFactory;
047    import org.kuali.rice.krad.web.form.UifFormBase;
048    import org.springframework.validation.BindingResult;
049    import org.springframework.web.bind.annotation.ModelAttribute;
050    import org.springframework.web.bind.annotation.RequestMapping;
051    import org.springframework.web.bind.annotation.RequestMethod;
052    import org.springframework.web.bind.annotation.ResponseBody;
053    import org.springframework.web.servlet.ModelAndView;
054    
055    import javax.servlet.http.HttpServletRequest;
056    import javax.servlet.http.HttpServletResponse;
057    import java.util.Collections;
058    import java.util.HashMap;
059    import java.util.HashSet;
060    import java.util.List;
061    import java.util.Map;
062    import java.util.Map.Entry;
063    import java.util.Properties;
064    import java.util.Set;
065    
066    /**
067     * Base controller class for views within the KRAD User Interface Framework
068     *
069     * Provides common methods such as:
070     *
071     * <ul>
072     * <li>Authorization methods such as method to call check</li>
073     * <li>Preparing the View instance and setup in the returned
074     * <code>ModelAndView</code></li>
075     * </ul>
076     *
077     * All subclass controller methods after processing should call one of the
078     * #getUIFModelAndView methods to setup the <code>View</code> and return the
079     * <code>ModelAndView</code> instance.
080     *
081     * @author Kuali Rice Team (rice.collab@kuali.org)
082     */
083    public abstract class UifControllerBase {
084        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(UifControllerBase.class);
085    
086        protected static final String REDIRECT_PREFIX = "redirect:";
087    
088        /**
089         * Create/obtain the model(form) object before it is passed
090         * to the Binder/BeanWrapper. This method is not intended to be overridden
091         * by client applications as it handles framework setup and session
092         * maintenance. Clients should override createIntialForm() instead when they
093         * need custom form initialization.
094         */
095        @ModelAttribute(value = "KualiForm")
096        public UifFormBase initForm(HttpServletRequest request) {
097            UifFormBase form = null;
098            String formKeyParam = request.getParameter(UifParameters.FORM_KEY);
099            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    }