001/**
002 * Copyright 2005-2018 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.lookup;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.exception.RiceRuntimeException;
020import org.kuali.rice.core.api.util.RiceConstants;
021import org.kuali.rice.core.api.util.RiceKeyConstants;
022import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
023import org.kuali.rice.krad.service.ModuleService;
024import org.kuali.rice.krad.uif.UifConstants;
025import org.kuali.rice.krad.uif.UifParameters;
026import org.kuali.rice.krad.uif.UifPropertyPaths;
027import org.kuali.rice.krad.util.GlobalVariables;
028import org.kuali.rice.krad.util.KRADConstants;
029import org.kuali.rice.krad.util.KRADUtils;
030import org.kuali.rice.krad.util.UrlFactory;
031import org.kuali.rice.krad.web.form.UifFormBase;
032import org.kuali.rice.krad.web.service.ModelAndViewService;
033import org.kuali.rice.krad.web.service.impl.ControllerServiceImpl;
034import org.springframework.web.servlet.ModelAndView;
035import org.springframework.web.servlet.mvc.support.RedirectAttributes;
036
037import javax.servlet.http.HttpServletRequest;
038import java.util.ArrayList;
039import java.util.Collection;
040import java.util.Collections;
041import java.util.HashSet;
042import java.util.List;
043import java.util.Map;
044import java.util.Properties;
045import java.util.Set;
046
047/**
048 * Default implementation of the lookup controller service.
049 *
050 * @author Kuali Rice Team (rice.collab@kuali.org)
051 */
052public class LookupControllerServiceImpl extends ControllerServiceImpl implements LookupControllerService {
053
054    private ModelAndViewService modelAndViewService;
055
056    /**
057     * {@inheritDoc}
058     */
059    @Override
060    public ModelAndView start(UifFormBase form) {
061        LookupForm lookupForm = (LookupForm) form;
062
063        Lookupable lookupable = lookupForm.getLookupable();
064        if (lookupable == null) {
065            throw new RuntimeException("Lookupable is null");
066        }
067
068        HttpServletRequest request = form.getRequest();
069        if (request.getParameter(UifParameters.MESSAGE_TO_DISPLAY) != null) {
070            GlobalVariables.getMessageMap().putErrorForSectionId(UifConstants.MessageKeys.LOOKUP_RESULT_MESSAGES,
071                    request.getParameter(UifParameters.MESSAGE_TO_DISPLAY));
072        }
073
074        if (!lookupForm.isRedirectedLookup()) {
075            ModelAndView redirectModelAndView = checkForModuleLookupRedirect(lookupForm, request);
076            if (redirectModelAndView != null) {
077                return redirectModelAndView;
078            }
079        }
080
081        String dialogId = request.getParameter(UifParameters.DIALOG_ID);
082        if (dialogId != null) {
083            lookupForm.setShowDialogId(dialogId);
084        }
085
086        return super.start(lookupForm);
087    }
088
089    /**
090     * Checks for a module service that claims the lookup class as an EBO, and if found redirects to the URL
091     * given by the module service.
092     *
093     * @param lookupForm form instance containing the lookup data
094     * @param request http request being handled
095     * @return ModelAndView instance for redirecting to the lookup, or null if a redirect is not needed
096     */
097    protected ModelAndView checkForModuleLookupRedirect(LookupForm lookupForm, HttpServletRequest request) {
098        Class<?> lookupObjectClass;
099        try {
100            lookupObjectClass = Class.forName(lookupForm.getDataObjectClassName());
101        } catch (ClassNotFoundException e) {
102            throw new RiceRuntimeException("Unable to get class for name: " + lookupForm.getDataObjectClassName(),
103                    e);
104        }
105
106        ModuleService responsibleModuleService =
107                KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(lookupObjectClass);
108        if (responsibleModuleService != null && responsibleModuleService.isExternalizable(lookupObjectClass)) {
109            String lookupUrl = responsibleModuleService.getExternalizableDataObjectLookupUrl(lookupObjectClass,
110                    KRADUtils.convertRequestMapToProperties(request.getParameterMap()));
111
112            Properties redirectUrlProps = new Properties();
113            redirectUrlProps.setProperty(UifParameters.REDIRECTED_LOOKUP, "true");
114
115            // clear current form from session
116            GlobalVariables.getUifFormManager().removeSessionForm(lookupForm);
117
118            return getModelAndViewService().performRedirect(lookupForm, lookupUrl, redirectUrlProps);
119        }
120
121        return null;
122    }
123
124    /**
125     * Carries out the search action by invoking the {@link Lookupable#performSearch)} method on the
126     * configured lookupable (view helper) and then setting the results onto the given form.
127     *
128     * {@inheritDoc}
129     */
130    @Override
131    public ModelAndView search(LookupForm lookupForm) {
132        Lookupable lookupable = lookupForm.getLookupable();
133        if (lookupable == null) {
134            throw new RuntimeException("Lookupable is null.");
135        }
136
137        Collection<?> displayList = lookupable.performSearch(lookupForm, lookupForm.getLookupCriteria(), true);
138
139        lookupForm.setLookupResults(displayList);
140
141        return getModelAndViewService().getModelAndView(lookupForm);
142    }
143
144    /**
145     * Carries out the clear values action by invoking the {@link Lookupable#performClear)} method on the
146     * configured lookupable (view helper) and then setting the cleared criteria onto the given form.
147     *
148     * {@inheritDoc}
149     */
150    @Override
151    public ModelAndView clearValues(LookupForm lookupForm) {
152        Lookupable lookupable = lookupForm.getLookupable();
153        if (lookupable == null) {
154            throw new RuntimeException("Lookupable is null.");
155        }
156
157        Map<String, String> resetLookupCriteria = lookupable.performClear(lookupForm, lookupForm.getLookupCriteria());
158
159        lookupForm.setLookupCriteria(resetLookupCriteria);
160
161        return getModelAndViewService().getModelAndView(lookupForm);
162    }
163
164    /**
165     * Loops through all the lookup results generating the line identifier for each and adding the
166     * resulting set of identifies to the form property
167     * {@link org.kuali.rice.krad.web.form.UifFormBase#getSelectedLookupResultsCache()}.
168     *
169     * {@inheritDoc}
170     */
171    @Override
172    public ModelAndView selectAllPages(LookupForm lookupForm) {
173        List<? extends Object> lookupResults = (List<? extends Object>) lookupForm.getLookupResults();
174
175        List<String> fromFieldNames = new ArrayList<String>(lookupForm.getFieldConversions().keySet());
176
177        // loop through  the lookup results and store identifiers for all items in the set
178        Set<String> selectedValues = new HashSet<String>();
179        for (Object lineItem : lookupResults) {
180            String lineIdentifier = LookupUtils.generateMultiValueKey(lineItem, fromFieldNames);
181
182            selectedValues.add(lineIdentifier);
183        }
184
185        lookupForm.setSelectedLookupResultsCache(selectedValues);
186
187        return getModelAndViewService().getModelAndView(lookupForm);
188    }
189
190    /**
191     * Clears the form property {@link org.kuali.rice.krad.web.form.UifFormBase#getSelectedLookupResultsCache()}
192     * and the selected lines property.
193     *
194     * {@inheritDoc}
195     */
196    @Override
197    public ModelAndView deselectAllPages(LookupForm lookupForm) {
198        lookupForm.getSelectedLookupResultsCache().clear();
199
200        Set<String> selectedLines = lookupForm.getSelectedCollectionLines().get(UifPropertyPaths.LOOKUP_RESULTS);
201        if (selectedLines != null) {
202            selectedLines.clear();
203        }
204
205        return getModelAndViewService().getModelAndView(lookupForm);
206    }
207
208    /**
209     * Builds the URL for returning back to the calling view and passing the selected line values.
210     *
211     * <p>We attempt to pass back all the selected line identifiers as a request parameter on the return URL.
212     * However, this could result in an URL longer than the max length supported by browsers (the most restrictive
213     * is used). If this happens, for local lookups we use Spring flash attributes. In the case of a remote
214     * lookup, there is nothing we can do and return an error message.</p>
215     *
216     * {@inheritDoc}
217     */
218    @Override
219    public String returnSelected(LookupForm lookupForm, RedirectAttributes redirectAttributes) {
220        LookupUtils.refreshLookupResultSelections(lookupForm);
221
222        Properties urlParams = buildReturnSelectedParameters(lookupForm);
223        String returnUrl = UrlFactory.parameterizeUrl(lookupForm.getReturnLocation(), urlParams);
224
225        boolean lookupCameFromDifferentServer = KRADUtils.areDifferentDomains(lookupForm.getReturnLocation(),
226                lookupForm.getRequestUrl());
227
228        boolean urlGreaterThanMaxLength = returnUrl.length() > RiceConstants.MAXIMUM_URL_LENGTH;
229        if (urlGreaterThanMaxLength) {
230            // removed selected values parameter from the return url
231            urlParams.remove(UifParameters.SELECTED_LINE_VALUES);
232
233            // if lookup was on a different server, we can't return the selected lines and instead
234            // will return an error message
235            if (lookupCameFromDifferentServer) {
236                urlParams.setProperty(UifParameters.REFRESH_STATUS, UifConstants.RefreshStatus.ERROR);
237                urlParams.setProperty(UifParameters.MESSAGE_TO_DISPLAY,
238                        RiceKeyConstants.INFO_LOOKUP_RESULTS_MV_RETURN_EXCEEDS_LIMIT);
239            } else {
240                // otherwise use flash attributes instead of the URL to return the selected line identifiers
241                String selectedLineValues = getSelectedLineValues(lookupForm);
242                redirectAttributes.addFlashAttribute(UifParameters.SELECTED_LINE_VALUES, selectedLineValues);
243            }
244        }
245
246        GlobalVariables.getUifFormManager().removeSessionForm(lookupForm);
247
248        // rebuild url based on updated parameters
249        returnUrl = UrlFactory.parameterizeUrl(lookupForm.getReturnLocation(), urlParams);
250
251        return UifConstants.REDIRECT_PREFIX + returnUrl;
252    }
253
254    /**
255     * Builds all the request parameters for the return URL.
256     *
257     * @param lookupForm form instance containing the lookup data
258     * @return Properties contains the request parameters key/value pairs
259     */
260    protected Properties buildReturnSelectedParameters(LookupForm lookupForm) {
261        Properties parameters = new Properties();
262
263        parameters.setProperty(KRADConstants.DISPATCH_REQUEST_PARAMETER, KRADConstants.RETURN_METHOD_TO_CALL);
264        parameters.setProperty(KRADConstants.REFRESH_CALLER_TYPE, UifConstants.RefreshCallerTypes.MULTI_VALUE_LOOKUP);
265
266        if (StringUtils.isNotBlank(lookupForm.getView().getId())) {
267            parameters.setProperty(KRADConstants.REFRESH_CALLER, lookupForm.getView().getId());
268        }
269
270        if (StringUtils.isNotBlank(lookupForm.getDataObjectClassName())) {
271            parameters.setProperty(KRADConstants.REFRESH_DATA_OBJECT_CLASS, lookupForm.getDataObjectClassName());
272        }
273
274        if (StringUtils.isNotBlank(lookupForm.getReturnFormKey())) {
275            parameters.setProperty(UifParameters.FORM_KEY, lookupForm.getReturnFormKey());
276        }
277
278        String multiValueReturnFieldsParam = getMultiValueReturnFields(lookupForm);
279        parameters.setProperty(UifParameters.MULIT_VALUE_RETURN_FILEDS, multiValueReturnFieldsParam);
280
281        String selectedLineValues = getSelectedLineValues(lookupForm);
282        parameters.setProperty(UifParameters.SELECTED_LINE_VALUES, selectedLineValues);
283
284        if (StringUtils.isNotBlank(lookupForm.getQuickfinderId())) {
285            parameters.setProperty(UifParameters.QUICKFINDER_ID, lookupForm.getQuickfinderId());
286        }
287
288        if (StringUtils.isNotBlank(lookupForm.getLookupCollectionName())) {
289            parameters.setProperty(UifParameters.LOOKUP_COLLECTION_NAME, lookupForm.getLookupCollectionName());
290        }
291
292        if (StringUtils.isNotBlank(lookupForm.getLookupCollectionId())) {
293            parameters.setProperty(UifParameters.LOOKUP_COLLECTION_ID, lookupForm.getLookupCollectionId());
294        }
295
296        if (StringUtils.isNotBlank(lookupForm.getReferencesToRefresh())) {
297            parameters.setProperty(KRADConstants.REFERENCES_TO_REFRESH, lookupForm.getReferencesToRefresh());
298        }
299
300        if (StringUtils.isNotBlank(lookupForm.getShowDialogId())) {
301            parameters.setProperty(UifParameters.DIALOG_ID, lookupForm.getShowDialogId());
302        }
303
304        return parameters;
305    }
306
307    /**
308     * Builds a string containing the names of the fields being returned separated by a comma.
309     *
310     * @param lookupForm form instance containing the lookup data
311     * @return String names of return fields separated by a comma
312     */
313    protected String getMultiValueReturnFields(LookupForm lookupForm) {
314        String multiValueReturnFieldsParam = "";
315
316        List<String> multiValueReturnFields = lookupForm.getMultiValueReturnFields();
317        Collections.sort(multiValueReturnFields);
318        if (multiValueReturnFields != null && !multiValueReturnFields.isEmpty()) {
319            for (String field : multiValueReturnFields) {
320                multiValueReturnFieldsParam += field + ",";
321            }
322
323            multiValueReturnFieldsParam = StringUtils.removeEnd(multiValueReturnFieldsParam, ",");
324        }
325
326        return multiValueReturnFieldsParam;
327    }
328
329    /**
330     * Builds a string containing the selected line identifiers separated by a comma.
331     *
332     * @param lookupForm form instance containing the lookup data
333     * @return String selected line identifiers separated by a comma
334     */
335    protected String getSelectedLineValues(LookupForm lookupForm) {
336        String selectedLineValues = "";
337
338        Set<String> selectedLines = lookupForm.getSelectedCollectionLines().get(UifPropertyPaths.LOOKUP_RESULTS);
339        if (selectedLines != null) {
340            for (String selectedLine : selectedLines) {
341                selectedLineValues += selectedLine + ",";
342            }
343
344            selectedLineValues = StringUtils.removeEnd(selectedLineValues, ",");
345        }
346
347        return selectedLineValues;
348    }
349
350    /**
351     * Instance of model and view service to use within the collection service.
352     *
353     * @return ModelAndViewService instance
354     */
355    protected ModelAndViewService getModelAndViewService() {
356        return modelAndViewService;
357    }
358
359    /**
360     * @see LookupControllerServiceImpl#getModelAndViewService()
361     */
362    public void setModelAndViewService(ModelAndViewService modelAndViewService) {
363        this.modelAndViewService = modelAndViewService;
364    }
365}