001/**
002 * Copyright 2005-2016 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.web.form;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.commons.logging.Log;
020import org.apache.commons.logging.LogFactory;
021import org.codehaus.jackson.map.ObjectMapper;
022import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
023import org.kuali.rice.krad.uif.UifConstants;
024import org.kuali.rice.krad.uif.UifParameters;
025import org.kuali.rice.krad.uif.view.History;
026import org.kuali.rice.krad.uif.view.HistoryEntry;
027import org.kuali.rice.krad.uif.view.View;
028import org.kuali.rice.krad.uif.service.ViewService;
029import org.kuali.rice.krad.uif.view.ViewModel;
030import org.kuali.rice.krad.util.KRADUtils;
031import org.springframework.web.multipart.MultipartFile;
032import org.kuali.rice.krad.uif.UifConstants.ViewType;
033
034import javax.servlet.http.HttpServletRequest;
035import java.io.IOException;
036import java.util.ArrayList;
037import java.util.HashMap;
038import java.util.List;
039import java.util.Map;
040import java.util.Properties;
041import java.util.Set;
042import java.util.UUID;
043
044/**
045 * Base form class for views within the KRAD User Interface Framework
046 *
047 * <p>
048 * Holds properties necessary to determine the <code>View</code> instance that
049 * will be used to render the UI
050 * </p>
051 *
052 * @author Kuali Rice Team (rice.collab@kuali.org)
053 */
054public class UifFormBase implements ViewModel {
055    private static final long serialVersionUID = 8432543267099454434L;
056
057    // logger
058    private static final Log LOG = LogFactory.getLog(UifFormBase.class);
059
060    // current view
061    protected String viewId;
062    protected String viewName;
063    protected ViewType viewTypeName;
064    protected String pageId;
065    protected String methodToCall;
066    protected String formKey;
067    protected String jumpToId;
068    protected String jumpToName;
069    protected String focusId;
070    protected String formPostUrl;
071
072    protected boolean defaultsApplied;
073    protected boolean skipViewInit;
074
075    protected View view;
076    protected View postedView;
077
078    protected Map<String, String> viewRequestParameters;
079    protected List<String> readOnlyFieldsList;
080
081    protected Map<String, Object> newCollectionLines;
082    protected Map<String, String> actionParameters;
083    protected Map<String, Object> clientStateForSyncing;
084    protected Map<String, Set<String>> selectedCollectionLines;
085
086    protected MultipartFile attachmentFile;
087
088    // navigation
089    protected String returnLocation;
090    protected String returnFormKey;
091
092    protected History formHistory;
093
094    protected boolean renderFullView;
095    protected boolean validateDirty;
096
097    public UifFormBase() {
098        formKey = generateFormKey();
099        renderFullView = true;
100        defaultsApplied = false;
101        skipViewInit = false;
102
103        formHistory = new History();
104
105        readOnlyFieldsList = new ArrayList<String>();
106        viewRequestParameters = new HashMap<String, String>();
107        newCollectionLines = new HashMap<String, Object>();
108        actionParameters = new HashMap<String, String>();
109        clientStateForSyncing = new HashMap<String, Object>();
110        selectedCollectionLines = new HashMap<String, Set<String>>();
111    }
112
113    /**
114     * Creates the unique id used to store this "conversation" in the session.
115     * The default method generates a java UUID.
116     *
117     * @return
118     */
119    protected String generateFormKey() {
120        return UUID.randomUUID().toString();
121    }
122
123    /**
124     * Called after Spring binds the request to the form and before the
125     * controller method is invoked.
126     *
127     * @param request - request object containing the query parameters
128     */
129    public void postBind(HttpServletRequest request) {
130        // default form post URL to request URL
131        formPostUrl = request.getRequestURL().toString();
132
133        // get any sent client view state and parse into map
134        if (request.getParameterMap().containsKey(UifParameters.CLIENT_VIEW_STATE)) {
135            String clientStateJSON = request.getParameter(UifParameters.CLIENT_VIEW_STATE);
136            if (StringUtils.isNotBlank(clientStateJSON)) {
137                // change single quotes to double quotes (necessary because the reverse was done for sending)
138                clientStateJSON = StringUtils.replace(clientStateJSON, "'", "\"");
139
140                ObjectMapper mapper = new ObjectMapper();
141                try {
142                    clientStateForSyncing = mapper.readValue(clientStateJSON, Map.class);
143                } catch (IOException e) {
144                    throw new RuntimeException("Unable to decode client side state JSON", e);
145                }
146            }
147        }
148
149        // populate read only fields list
150        if (request.getParameter(UifParameters.READ_ONLY_FIELDS) != null) {
151            String readOnlyFields = request.getParameter(UifParameters.READ_ONLY_FIELDS);
152            setReadOnlyFieldsList(KRADUtils.convertStringParameterToList(readOnlyFields));
153        }
154
155        // reset skip view init parameter if not passed
156        if (!request.getParameterMap().containsKey(UifParameters.SKIP_VIEW_INIT)) {
157            skipViewInit = false;
158        }
159    }
160
161    /**
162     * @see org.kuali.rice.krad.uif.view.ViewModel#getViewId()
163     */
164    public String getViewId() {
165        return this.viewId;
166    }
167
168    /**
169     * @see org.kuali.rice.krad.uif.view.ViewModel#setViewId(java.lang.String)
170     */
171    public void setViewId(String viewId) {
172        this.viewId = viewId;
173    }
174
175    /**
176     * @see org.kuali.rice.krad.uif.view.ViewModel#getViewName()
177     */
178    public String getViewName() {
179        return this.viewName;
180    }
181
182    /**
183     * @see org.kuali.rice.krad.uif.view.ViewModel#setViewName(java.lang.String)
184     */
185    public void setViewName(String viewName) {
186        this.viewName = viewName;
187    }
188
189    /**
190     * @see org.kuali.rice.krad.uif.view.ViewModel#getViewTypeName()
191     */
192    public ViewType getViewTypeName() {
193        return this.viewTypeName;
194    }
195
196    /**
197     * @see org.kuali.rice.krad.uif.view.ViewModel#setViewTypeName(org.kuali.rice.krad.uif.UifConstants.ViewType)
198     */
199    public void setViewTypeName(ViewType viewTypeName) {
200        this.viewTypeName = viewTypeName;
201    }
202
203    /**
204     * @see org.kuali.rice.krad.uif.view.ViewModel#getPageId()
205     */
206    public String getPageId() {
207        return this.pageId;
208    }
209
210    /**
211     * @see org.kuali.rice.krad.uif.view.ViewModel#setPageId(java.lang.String)
212     */
213    public void setPageId(String pageId) {
214        this.pageId = pageId;
215    }
216
217    /**
218     * @see org.kuali.rice.krad.uif.view.ViewModel#getFormPostUrl()
219     */
220    public String getFormPostUrl() {
221        return this.formPostUrl;
222    }
223
224    /**
225     * @see org.kuali.rice.krad.uif.view.ViewModel#setFormPostUrl(java.lang.String)
226     */
227    public void setFormPostUrl(String formPostUrl) {
228        this.formPostUrl = formPostUrl;
229    }
230
231    public String getReturnLocation() {
232        return this.returnLocation;
233    }
234
235    public void setReturnLocation(String returnLocation) {
236        this.returnLocation = returnLocation;
237    }
238
239    public String getReturnFormKey() {
240        return this.returnFormKey;
241    }
242
243    public void setReturnFormKey(String returnFormKey) {
244        this.returnFormKey = returnFormKey;
245    }
246
247    /**
248     * Identifies the controller method that should be invoked to fulfill a
249     * request. The value will be matched up against the 'params' setting on the
250     * <code>RequestMapping</code> annotation for the controller method
251     *
252     * @return String method to call
253     */
254    public String getMethodToCall() {
255        return this.methodToCall;
256    }
257
258    /**
259     * Setter for the method to call
260     *
261     * @param methodToCall
262     */
263    public void setMethodToCall(String methodToCall) {
264        this.methodToCall = methodToCall;
265    }
266
267    /**
268     * @see org.kuali.rice.krad.uif.view.ViewModel#getViewRequestParameters()
269     */
270    public Map<String, String> getViewRequestParameters() {
271        return this.viewRequestParameters;
272    }
273
274    /**
275     * @see org.kuali.rice.krad.uif.view.ViewModel#setViewRequestParameters(java.util.Map<java.lang.String,java.lang.String>)
276     */
277    public void setViewRequestParameters(Map<String, String> viewRequestParameters) {
278        this.viewRequestParameters = viewRequestParameters;
279    }
280
281    /**
282     * @see org.kuali.rice.krad.uif.view.ViewModel#getReadOnlyFieldsList()
283     */
284    public List<String> getReadOnlyFieldsList() {
285        return readOnlyFieldsList;
286    }
287
288    /**
289     * @see org.kuali.rice.krad.uif.view.ViewModel#setReadOnlyFieldsList(java.util.List<java.lang.String>)
290     */
291    public void setReadOnlyFieldsList(List<String> readOnlyFieldsList) {
292        this.readOnlyFieldsList = readOnlyFieldsList;
293    }
294
295    /**
296     * @see org.kuali.rice.krad.uif.view.ViewModel#getNewCollectionLines()
297     */
298    public Map<String, Object> getNewCollectionLines() {
299        return this.newCollectionLines;
300    }
301
302    /**
303     * @see org.kuali.rice.krad.uif.view.ViewModel#setNewCollectionLines(java.util.Map<java.lang.String,java.lang.Object>)
304     */
305    public void setNewCollectionLines(Map<String, Object> newCollectionLines) {
306        this.newCollectionLines = newCollectionLines;
307    }
308
309    /**
310     * @see org.kuali.rice.krad.uif.view.ViewModel#getActionParameters()
311     */
312    public Map<String, String> getActionParameters() {
313        return this.actionParameters;
314    }
315
316    /**
317     * Returns the action parameters map as a <code>Properties</code> instance
318     *
319     * @return Properties action parameters
320     */
321    public Properties getActionParametersAsProperties() {
322        return KRADUtils.convertMapToProperties(actionParameters);
323    }
324
325    /**
326     * @see org.kuali.rice.krad.uif.view.ViewModel#setActionParameters(java.util.Map<java.lang.String,java.lang.String>)
327     */
328    public void setActionParameters(Map<String, String> actionParameters) {
329        this.actionParameters = actionParameters;
330    }
331
332    /**
333     * Retrieves the value for the given action parameter, or empty string if
334     * not found
335     *
336     * @param actionParameterName - name of the action parameter to retrieve value for
337     * @return String parameter value or empty string
338     */
339    public String getActionParamaterValue(String actionParameterName) {
340        if ((actionParameters != null) && actionParameters.containsKey(actionParameterName)) {
341            return actionParameters.get(actionParameterName);
342        }
343
344        return "";
345    }
346
347    /**
348     * Returns the action event that was sent in the action parameters (if any)
349     *
350     * <p>
351     * The action event is a special action parameter that can be sent to indicate a type of action being taken. This
352     * can be looked at by the view or components to render differently
353     * </p>
354     *
355     * TODO: make sure action parameters are getting reinitialized on each request
356     *
357     * @return String action event name or blank if action event was not sent
358     */
359    public String getActionEvent() {
360        if ((actionParameters != null) && actionParameters.containsKey(UifConstants.UrlParams.ACTION_EVENT)) {
361            return actionParameters.get(UifConstants.UrlParams.ACTION_EVENT);
362        }
363
364        return "";
365    }
366
367    /**
368     * @see org.kuali.rice.krad.uif.view.ViewModel#getClientStateForSyncing()
369     */
370    public Map<String, Object> getClientStateForSyncing() {
371        return clientStateForSyncing;
372    }
373
374    /**
375     * @see org.kuali.rice.krad.uif.view.ViewModel#getSelectedCollectionLines()
376     */
377    public Map<String, Set<String>> getSelectedCollectionLines() {
378        return selectedCollectionLines;
379    }
380
381    /**
382     * @see org.kuali.rice.krad.uif.view.ViewModel#setSelectedCollectionLines(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)
383     */
384    public void setSelectedCollectionLines(Map<String, Set<String>> selectedCollectionLines) {
385        this.selectedCollectionLines = selectedCollectionLines;
386    }
387
388    /**
389     * Key string that identifies the form instance in session storage
390     *
391     * <p>
392     * When the view is posted, the previous form instance is retrieved and then
393     * populated from the request parameters. This key string is retrieve the
394     * session form from the session service
395     * </p>
396     *
397     * @return String form session key
398     */
399    public String getFormKey() {
400        return this.formKey;
401    }
402
403    /**
404     * Setter for the form's session key
405     *
406     * @param formKey
407     */
408    public void setFormKey(String formKey) {
409        this.formKey = formKey;
410    }
411
412    /**
413     * @see org.kuali.rice.krad.uif.view.ViewModel#isDefaultsApplied()
414     */
415    public boolean isDefaultsApplied() {
416        return this.defaultsApplied;
417    }
418
419    /**
420     * @see org.kuali.rice.krad.uif.view.ViewModel#setDefaultsApplied(boolean)
421     */
422    public void setDefaultsApplied(boolean defaultsApplied) {
423        this.defaultsApplied = defaultsApplied;
424    }
425
426    /**
427     * Indicates whether a new view is being initialized or the call is refresh (or query) call
428     *
429     * @return boolean true if view initialization was skipped, false if new view is being created
430     */
431    public boolean isSkipViewInit() {
432        return skipViewInit;
433    }
434
435    /**
436     * Setter for the skip view initialization flag
437     *
438     * @param skipViewInit
439     */
440    public void setSkipViewInit(boolean skipViewInit) {
441        this.skipViewInit = skipViewInit;
442    }
443
444    /**
445     * Holder for files that are attached through the view
446     *
447     * @return MultipartFile representing the attachment
448     */
449    public MultipartFile getAttachmentFile() {
450        return this.attachmentFile;
451    }
452
453    /**
454     * Setter for the form's attachment file
455     *
456     * @param attachmentFile
457     */
458    public void setAttachmentFile(MultipartFile attachmentFile) {
459        this.attachmentFile = attachmentFile;
460    }
461
462    /**
463     * @return the renderFullView
464     */
465    public boolean isRenderFullView() {
466        return this.renderFullView;
467    }
468
469    /**
470     * @param renderFullView
471     */
472    public void setRenderFullView(boolean renderFullView) {
473        this.renderFullView = renderFullView;
474    }
475
476    /**
477     * @see org.kuali.rice.krad.uif.view.ViewModel#getView()
478     */
479    public View getView() {
480        return this.view;
481    }
482
483    /**
484     * @see org.kuali.rice.krad.uif.view.ViewModel#setView(org.kuali.rice.krad.uif.view.View)
485     */
486    public void setView(View view) {
487        this.view = view;
488        initHomewardPathList();
489    }
490
491    /**
492     * Set the "Home" url of the homewardPathList (ie. breadcrumbs history)
493     */
494    private void initHomewardPathList() {
495        if (getReturnLocation() == null) {
496            LOG.warn("Could not init homewardPathList.  returnLocation is null.");
497            return;
498        }
499
500        List<HistoryEntry> homewardPathList = new ArrayList<HistoryEntry>();
501        if ((view != null) && (view.getBreadcrumbs() != null) && (view.getBreadcrumbs().getHomewardPathList() != null)) {
502            homewardPathList = view.getBreadcrumbs().getHomewardPathList();
503        }
504
505        HistoryEntry historyEntry = new HistoryEntry("","","Home",getReturnLocation(),"");
506        if (homewardPathList.isEmpty()) {
507            homewardPathList.add(historyEntry);
508        } else if (StringUtils.equals(homewardPathList.get(0).getTitle(), "Home")) {
509            homewardPathList.set(0, historyEntry);
510        } else {
511            homewardPathList.add(0, historyEntry);
512        }
513    }
514
515    /**
516     * @see org.kuali.rice.krad.uif.view.ViewModel#getPostedView()
517     */
518    public View getPostedView() {
519        return this.postedView;
520    }
521
522    /**
523     * @see org.kuali.rice.krad.uif.view.ViewModel#setPostedView(org.kuali.rice.krad.uif.view.View)
524     */
525    public void setPostedView(View postedView) {
526        this.postedView = postedView;
527    }
528
529    /**
530     * Instance of the <code>ViewService</code> that can be used to retrieve
531     * <code>View</code> instances
532     *
533     * @return ViewService implementation
534     */
535    protected ViewService getViewService() {
536        return KRADServiceLocatorWeb.getViewService();
537    }
538
539    /**
540     * The jumpToId for this form, the element with this id will be jumped to automatically
541     * when the form is loaded in the view.
542     * Using "TOP" or "BOTTOM" will jump to the top or the bottom of the resulting page.
543     * jumpToId always takes precedence over jumpToName, if set.
544     *
545     * @return the jumpToId
546     */
547    public String getJumpToId() {
548        return this.jumpToId;
549    }
550
551    /**
552     * @param jumpToId the jumpToId to set
553     */
554    public void setJumpToId(String jumpToId) {
555        this.jumpToId = jumpToId;
556    }
557
558    /**
559     * The jumpToName for this form, the element with this name will be jumped to automatically
560     * when the form is loaded in the view.
561     * WARNING: jumpToId always takes precedence over jumpToName, if set.
562     *
563     * @return the jumpToName
564     */
565    public String getJumpToName() {
566        return this.jumpToName;
567    }
568
569    /**
570     * @param jumpToName the jumpToName to set
571     */
572    public void setJumpToName(String jumpToName) {
573        this.jumpToName = jumpToName;
574    }
575
576    /**
577     * Field to place focus on when the page loads
578     * An empty focusId will result in focusing on the first visible input element by default.
579     *
580     * @return the focusId
581     */
582    public String getFocusId() {
583        return this.focusId;
584    }
585
586    /**
587     * @param focusId the focusId to set
588     */
589    public void setFocusId(String focusId) {
590        this.focusId = focusId;
591    }
592
593    /**
594     * History parameter representing the History of views that have come before the
595     * viewing of the current view
596     *
597     * <p>
598     * Used for breadcrumb widget generation on the view and also for navigating back
599     * to previous or hub locations
600     * </p>
601     *
602     * @return History instance giving current history
603     */
604    public History getFormHistory() {
605        return formHistory;
606    }
607
608    /**
609     * Setter for the current History object
610     *
611     * @param history the history to set
612     */
613    public void setFormHistory(History history) {
614        this.formHistory = history;
615    }
616
617    /**
618     * Indicates whether the form should be validated for dirtyness
619     *
620     * <p>
621     * For FormView, it's necessary to validate when the user tries to navigate out of the form. If set, all the
622     * InputFields will be validated on refresh, navigate, cancel or close Action or on form
623     * unload and if dirty, displays a message and user can decide whether to continue with
624     * the action or stay on the form
625     * </p>
626     *
627     * @return boolean true if dirty validation should be enabled
628     */
629    public boolean isValidateDirty() {
630        return this.validateDirty;
631    }
632
633    /**
634     * Setter for dirty validation indicator
635     *
636     * @param validateDirty
637     */
638    public void setValidateDirty(boolean validateDirty) {
639        this.validateDirty = validateDirty;
640    }
641
642}