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.uif.view;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.CoreApiServiceLocator;
020import org.kuali.rice.krad.datadictionary.DataDictionary;
021import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
022import org.kuali.rice.krad.datadictionary.state.StateMapping;
023import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
024import org.kuali.rice.krad.datadictionary.validator.Validator;
025import org.kuali.rice.krad.lookup.LookupView;
026import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
027import org.kuali.rice.krad.uif.UifConstants;
028import org.kuali.rice.krad.uif.UifConstants.ViewStatus;
029import org.kuali.rice.krad.uif.UifConstants.ViewType;
030import org.kuali.rice.krad.uif.UifParameters;
031import org.kuali.rice.krad.uif.UifPropertyPaths;
032import org.kuali.rice.krad.uif.component.BindingInfo;
033import org.kuali.rice.krad.uif.component.Component;
034import org.kuali.rice.krad.uif.component.DelayedCopy;
035import org.kuali.rice.krad.uif.component.ReferenceCopy;
036import org.kuali.rice.krad.uif.component.RequestParameter;
037import org.kuali.rice.krad.uif.container.ContainerBase;
038import org.kuali.rice.krad.uif.container.Group;
039import org.kuali.rice.krad.uif.container.PageGroup;
040import org.kuali.rice.krad.uif.element.BreadcrumbItem;
041import org.kuali.rice.krad.uif.element.BreadcrumbOptions;
042import org.kuali.rice.krad.uif.element.HeadLink;
043import org.kuali.rice.krad.uif.element.Header;
044import org.kuali.rice.krad.uif.element.MetaTag;
045import org.kuali.rice.krad.uif.element.ViewHeader;
046import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
047import org.kuali.rice.krad.uif.lifecycle.ViewLifecyclePhase;
048import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleRestriction;
049import org.kuali.rice.krad.uif.service.ViewHelperService;
050import org.kuali.rice.krad.uif.util.ClientValidationUtils;
051import org.kuali.rice.krad.uif.util.ComponentFactory;
052import org.kuali.rice.krad.uif.util.CopyUtils;
053import org.kuali.rice.krad.uif.util.LifecycleAwareList;
054import org.kuali.rice.krad.uif.util.LifecycleAwareMap;
055import org.kuali.rice.krad.uif.util.LifecycleElement;
056import org.kuali.rice.krad.uif.util.ParentLocation;
057import org.kuali.rice.krad.uif.util.ScriptUtils;
058import org.kuali.rice.krad.uif.widget.BlockUI;
059import org.kuali.rice.krad.uif.widget.Breadcrumbs;
060import org.kuali.rice.krad.uif.widget.Growls;
061import org.kuali.rice.krad.uif.widget.Help;
062import org.kuali.rice.krad.util.GlobalVariables;
063import org.kuali.rice.krad.util.KRADConstants;
064import org.kuali.rice.krad.util.KRADUtils;
065import org.kuali.rice.krad.web.form.UifFormBase;
066import org.slf4j.Logger;
067import org.slf4j.LoggerFactory;
068
069import java.util.ArrayList;
070import java.util.Collections;
071import java.util.HashMap;
072import java.util.HashSet;
073import java.util.List;
074import java.util.Map;
075import java.util.Set;
076
077/**
078 * Root of the component tree which encompasses a set of related
079 * GroupContainer instances tied together with a common page layout
080 * and navigation.
081 *
082 * <p>
083 * The View component ties together all the components and
084 * configuration of the User Interface for a piece of functionality. In Rice
085 * applications the view is typically associated with a Document
086 * instance.
087 * </p>
088 *
089 * <p>
090 * The view template lays out the common header, footer, and navigation for the
091 * related pages. In addition the view renders the HTML head element bringing in
092 * common script files and style sheets, along with optionally rendering a form
093 * element for pages that need to post data back to the server.
094 * </p>
095 *
096 * <p>
097 * Configuration of UIF features such as model validation is also done through
098 * the View
099 * </p>
100 *
101 * @author Kuali Rice Team (rice.collab@kuali.org)
102 */
103public class View extends ContainerBase {
104    private static final long serialVersionUID = -1220009725554576953L;
105    private static final Logger LOG = LoggerFactory.getLogger(ContainerBase.class);
106
107    private String namespaceCode;
108    private String viewName;
109    private ViewTheme theme;
110
111    private String stateObjectBindingPath;
112    private StateMapping stateMapping;
113
114    // view header setting
115    private boolean unifiedHeader;
116
117    // additional view group(s)
118    private Group topGroup;
119
120    // application
121    private Header applicationHeader;
122    private Group applicationFooter;
123    private Group buildFooter;
124    private String applicationTitleText;
125
126    // sticky flags
127    private boolean stickyTopGroup;
128    private boolean stickyBreadcrumbs;
129    private boolean stickyHeader;
130    private boolean stickyApplicationHeader;
131    private boolean stickyFooter;
132    private boolean stickyApplicationFooter;
133
134    private List<String> contentContainerCssClasses;
135
136    // Breadcrumbs
137    private Breadcrumbs breadcrumbs;
138    private BreadcrumbOptions breadcrumbOptions;
139    private BreadcrumbItem breadcrumbItem;
140    private ParentLocation parentLocation;
141    private List<BreadcrumbItem> pathBasedBreadcrumbs;
142
143    // Growls support
144    private Growls growls;
145    private boolean growlMessagingEnabled;
146
147    private BlockUI refreshBlockUI;
148    private BlockUI navigationBlockUI;
149
150    private String entryPageId;
151
152    @RequestParameter
153    private String currentPageId;
154
155    private Group navigation;
156
157    private Class<?> formClass;
158    private String defaultBindingObjectPath;
159    private Map<String, Class<?>> objectPathToConcreteClassMapping;
160
161    private List<String> additionalScriptFiles;
162    private List<String> additionalCssFiles;
163    private List<HeadLink> additionalHeadLinks;
164    private List<MetaTag> additionalMetaTags;
165    private boolean useLibraryCssClasses;
166
167    private ViewType viewTypeName;
168
169    protected ViewIndex viewIndex;
170    private Map<String, String> viewRequestParameters;
171
172    private boolean persistFormToSession;
173    private ViewSessionPolicy sessionPolicy;
174
175    private int preloadPoolSize;
176
177    private ViewPresentationController presentationController;
178    private ViewAuthorizer authorizer;
179    private Class<? extends RequestAuthorizationCache> requestAuthorizationCacheClass;
180
181    private Map<String, Boolean> actionFlags;
182    private Map<String, Boolean> editModes;
183
184    private Map<String, String> expressionVariables;
185
186    private boolean singlePageView;
187    private boolean mergeWithPageItems;
188    private PageGroup page;
189
190    @ReferenceCopy(referenceTransient=true)
191    private PageGroup currentPage;
192
193    private List<Group> dialogs;
194
195    protected boolean applyDirtyCheck;
196    private boolean translateCodesOnReadOnlyDisplay;
197    private boolean supportsRequestOverrideOfReadOnlyFields;
198    private boolean disableNativeAutocomplete;
199    private boolean disableBrowserCache;
200
201    private String preLoadScript;
202
203    @DelayedCopy
204    private List<? extends Component> items;
205
206    private List<String> viewTemplates;
207
208    private Class<? extends ViewHelperService> viewHelperServiceClass;
209
210    @ReferenceCopy
211    private ViewHelperService viewHelperService;
212
213    private Map<String, Object> preModelContext;
214
215    public View() {
216        singlePageView = false;
217        mergeWithPageItems = true;
218        translateCodesOnReadOnlyDisplay = false;
219        viewTypeName = ViewType.DEFAULT;
220        formClass = UifFormBase.class;
221        supportsRequestOverrideOfReadOnlyFields = true;
222        disableBrowserCache = true;
223        persistFormToSession = true;
224        sessionPolicy = new ViewSessionPolicy();
225
226        this.viewIndex = new ViewIndex();
227
228        additionalScriptFiles = Collections.emptyList();
229        additionalCssFiles = Collections.emptyList();
230        additionalHeadLinks = Collections.emptyList();
231        additionalMetaTags = Collections.emptyList();
232        objectPathToConcreteClassMapping = Collections.emptyMap();
233        viewRequestParameters = Collections.emptyMap();
234        expressionVariables = Collections.emptyMap();
235
236        requestAuthorizationCacheClass = RequestAuthorizationCache.class;
237
238        dialogs = Collections.emptyList();
239
240        items = Collections.emptyList();
241        viewTemplates = new LifecycleAwareList<String>(this);
242    }
243
244    /**
245     * The following initialization is performed:
246     *
247     * <ul>
248     * <li>If a single paged view, set items in page group and put the page in
249     * the items list</li>
250     * <li>If {@link ViewSessionPolicy#enableTimeoutWarning} is enabled add the session timeout dialogs to the
251     * views list of dialog groups</li>
252     * </ul>
253     *
254     * {@inheritDoc}
255     */
256    @SuppressWarnings("unchecked")
257    @Override
258    public void performInitialization(Object model) {
259        if (model instanceof UifFormBase) {
260            UifFormBase form = (UifFormBase) model;
261
262            // set view page to page requested on form
263            if (StringUtils.isNotBlank(form.getPageId())) {
264                setCurrentPageId(form.getPageId());
265            }
266
267            String dialogId = form.getActionParamaterValue(UifParameters.DIALOG_ID);
268            if (StringUtils.isNotBlank(dialogId)) {
269                form.setShowDialogId(dialogId);
270
271                // initialize the view to open the dialog if necessary
272                initializeDialogLoadScript((UifFormBase) model);
273            }
274        }
275
276        initializeDialogReclocationScript();
277
278        super.performInitialization(model);
279
280        assert this == ViewLifecycle.getView();
281
282        // populate items on page for single paged view
283        if (singlePageView) {
284            if (page != null) {
285                // remove default sections of page when requested
286                if (!mergeWithPageItems) {
287                    page.setItems(new ArrayList<Group>());
288                }
289
290                // add the items configured on the view to the page items, and set as the
291                // new page items
292                List<Component> newItems = (List<Component>) page.getItems();
293                newItems.addAll(items);
294                page.setItems(newItems);
295
296                page.sortItems();
297
298                // reset the items list to include the one page
299                items = new ArrayList<Group>();
300                ((List<Group>) items).add(page);
301            }
302        }
303        // if items is only size one and instance of page, set singlePageView to true
304        else if ((this.items != null) && (this.items.size() == 1)) {
305            Component itemComponent = this.items.get(0);
306
307            if (itemComponent instanceof PageGroup) {
308                this.singlePageView = true;
309            }
310        }
311
312        if (sessionPolicy.isEnableTimeoutWarning()) {
313            Group warningDialog = ComponentFactory.getSessionTimeoutWarningDialog();
314            warningDialog.setId(ComponentFactory.SESSION_TIMEOUT_WARNING_DIALOG);
315            getDialogs().add(warningDialog);
316
317            Group timeoutDialog = ComponentFactory.getSessionTimeoutDialog();
318            timeoutDialog.setId(ComponentFactory.SESSION_TIMEOUT_DIALOG);
319            getDialogs().add(timeoutDialog);
320        }
321
322        breadcrumbOptions.setupBreadcrumbs(model);
323    }
324
325    /**
326     * Helper method to set the view's load script to open a dialog.
327     *
328     * @param form the form containing data
329     */
330    protected void initializeDialogLoadScript(UifFormBase form) {
331        // check for edit line dialog action parameters and if present, then show the dialog once page is loaded
332        String selectedCollectionPath = form.getActionParamaterValue(UifParameters.SELECTED_COLLECTION_PATH);
333        String selectedLineIndex = form.getActionParamaterValue(UifParameters.SELECTED_LINE_INDEX);
334        String dialogId = form.getShowDialogId();
335
336        if (StringUtils.isNotBlank(dialogId) && StringUtils.isNotBlank(selectedCollectionPath) && StringUtils
337                .isNotBlank(selectedLineIndex)) {
338            // the line index and the collection path need adjusted to account for nested collections within a
339            // dialog because the dialog is shown per the root collection, but the index and path are per
340            // the nested sub-collection
341            String originalLineIndex = StringUtils.substring(dialogId, dialogId.length() - 1);
342            if (!selectedLineIndex.equals(originalLineIndex)) {
343                selectedLineIndex = originalLineIndex;
344            }
345
346            // adjust the collection path to point to the root collection rather than the sub-collection
347            if (selectedCollectionPath.contains(UifPropertyPaths.DIALOG_DATA_OBJECT)) {
348                String collectionId = StringUtils.substring(dialogId, dialogId.indexOf("_") + 1, dialogId.lastIndexOf(
349                        "_"));
350                BindingInfo bindingInfo = (BindingInfo) form.getViewPostMetadata().getComponentPostMetadata(
351                        collectionId).getData("bindingInfo");
352                selectedCollectionPath = bindingInfo.getBindingPath();
353            }
354
355            String actionScript = "setupImages();";
356            if (StringUtils.startsWith(dialogId, ComponentFactory.EDIT_LINE_DIALOG)) {
357                actionScript +=
358                        "showEditLineDialog('" + dialogId + "', '" + selectedCollectionPath + "', " + selectedLineIndex
359                                + ");";
360            } else {
361                String additionalData = "{ 'actionParameters[selectedCollectionPath]' : '" + selectedCollectionPath
362                        + "', 'actionParameters[selectedLineIndex]' : '0' }";
363                actionScript += "showDialog('" + dialogId + "', " + additionalData + ");";
364            }
365            setOnLoadScript(ScriptUtils.appendScript(getOnLoadScript(), actionScript));
366        } else if (StringUtils.isNotBlank(dialogId) && !(this instanceof LookupView)) {
367            String actionScript =
368                    "jQuery.unblockUI();setupImages();showLoading('Loading...', window.document);showDialog('"
369                            + dialogId + "');";
370            setOnLoadScript(ScriptUtils.appendScript(getOnLoadScript(), actionScript));
371        }
372    }
373
374    /**
375     * Without this script dialogs might be associated with a non visible action (i.e. inside a dropdown menu).  This
376     * would cause the dialog not being displayed.  To ensure that all dialog are able to be displayed the DIVs of the
377     * dialogs are moved to a common location.
378     */
379    protected void initializeDialogReclocationScript() {
380        String dialogRelocationScript = "jQuery('.modal').appendTo('#Uif-Dialogs');";
381        setOnLoadScript(ScriptUtils.appendScript(getOnLoadScript(), dialogRelocationScript));
382    }
383
384    /**
385     * {@inheritDoc}
386     */
387    @Override
388    public void afterEvaluateExpression() {
389        super.afterEvaluateExpression();
390
391        if (getReadOnly() == null) {
392            setReadOnly(false);
393        }
394    }
395
396    /**
397     * The following updates are done here:
398     *
399     * <ul>
400     * <li>Invoke expression evaluation on view theme</li>
401     * <li>Invoke theme to configure defaults</li>
402     * </ul>
403     */
404    @Override
405    public void performApplyModel(Object model, LifecycleElement parent) {
406        super.performApplyModel(model, parent);
407
408        View view = ViewLifecycle.getView();
409        if (theme != null) {
410            ViewLifecycle.getExpressionEvaluator().evaluateExpressionsOnConfigurable(view, theme, getContext());
411
412            theme.configureThemeDefaults();
413        }
414
415        if (sessionPolicy != null) {
416            ViewLifecycle.getExpressionEvaluator().evaluateExpressionsOnConfigurable(view, sessionPolicy, getContext());
417        }
418
419        //handle parentLocation breadcrumb chain
420        parentLocation.constructParentLocationBreadcrumbItems(view, model, view.getContext());
421    }
422
423    /**
424     * The following is performed:
425     *
426     * <ul>
427     * <li>Adds to its document ready script the setupValidator js function for setting
428     * up the validator for this view</li>
429     * </ul>
430     *
431     * {@inheritDoc}
432     */
433    @SuppressWarnings("unchecked")
434    @Override
435    public void performFinalize(Object model, LifecycleElement parent) {
436        super.performFinalize(model, parent);
437
438        assert this == ViewLifecycle.getView();
439
440        String preLoadScript = "";
441        if (this.getPreLoadScript() != null) {
442            preLoadScript = this.getPreLoadScript();
443        }
444
445        // Retrieve Growl and BlockUI settings
446        Growls gw = getGrowls();
447        if (!gw.getTemplateOptions().isEmpty()) {
448            preLoadScript += "setGrowlDefaults(" + gw.getTemplateOptionsJSString() + ");";
449        }
450
451        BlockUI navBlockUI = getNavigationBlockUI();
452        if (!navBlockUI.getTemplateOptions().isEmpty()) {
453            preLoadScript += "setBlockUIDefaults("
454                    + navBlockUI.getTemplateOptionsJSString()
455                    + ", '"
456                    + UifConstants.BLOCKUI_NAVOPTS
457                    + "');";
458        }
459
460        BlockUI refBlockUI = getRefreshBlockUI();
461        if (!refBlockUI.getTemplateOptions().isEmpty()) {
462            preLoadScript += "setBlockUIDefaults("
463                    + refBlockUI.getTemplateOptionsJSString()
464                    + ", '"
465                    + UifConstants.BLOCKUI_REFRESHOPTS
466                    + "');";
467        }
468
469        this.setPreLoadScript(preLoadScript);
470
471        String onReadyScript = "";
472        if (this.getOnDocumentReadyScript() != null) {
473            onReadyScript = this.getOnDocumentReadyScript();
474        }
475
476        // initialize session timers for giving timeout warnings
477        if (sessionPolicy.isEnableTimeoutWarning()) {
478            // warning minutes gives us the time before the timeout occurs to give the warning,
479            // so we need to determine how long that should be from the session start
480            int sessionTimeoutInterval = ((UifFormBase) model).getSessionTimeoutInterval();
481            int sessionWarningSeconds = sessionPolicy.getTimeoutWarningSeconds();
482
483            if (sessionTimeoutInterval <= 0) {
484                // force to one hour when no setting detected
485                sessionTimeoutInterval = 3600;
486            }
487
488            if (sessionWarningSeconds >= sessionTimeoutInterval  || sessionWarningSeconds <= 0) {
489                sessionWarningSeconds = sessionTimeoutInterval/10;
490            }
491
492            int sessionWarningInterval = sessionTimeoutInterval - sessionWarningSeconds;
493
494            onReadyScript = ScriptUtils.appendScript(onReadyScript, ScriptUtils.buildFunctionCall(
495                    UifConstants.JsFunctions.INITIALIZE_SESSION_TIMERS, sessionWarningInterval,
496                    sessionTimeoutInterval));
497        }
498
499        onReadyScript = ScriptUtils.appendScript(onReadyScript, "jQuery.extend(jQuery.validator.messages, "
500                + ClientValidationUtils.generateValidatorMessagesOption()
501                + ");");
502
503        this.setOnDocumentReadyScript(onReadyScript);
504
505        // Breadcrumb handling
506        breadcrumbOptions.finalizeBreadcrumbs(model, this, breadcrumbItem);
507
508        // Add validation default js options for validation framework to View's data attributes
509        Object groupValidationDataDefaults = KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean(
510                UifConstants.GROUP_VALIDATION_DEFAULTS_MAP_ID);
511        Object fieldValidationDataDefaults = KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean(
512                UifConstants.FIELD_VALIDATION_DEFAULTS_MAP_ID);
513        Object actionDataDefaults = KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean(
514                UifConstants.ACTION_DEFAULTS_MAP_ID);
515        Object requiredIndicator = KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean(
516                UifConstants.REQUIRED_INDICATOR_ID);
517
518        // Add data defaults for common components to the view for use in js (to reduce size of individual components)
519        this.addScriptDataAttribute(UifConstants.DataAttributes.GROUP_VALIDATION_DEFAULTS, ScriptUtils.convertToJsValue(
520                (Map<String, String>) groupValidationDataDefaults));
521        this.addScriptDataAttribute(UifConstants.DataAttributes.FIELD_VALIDATION_DEFAULTS, ScriptUtils.convertToJsValue(
522                (Map<String, String>) fieldValidationDataDefaults));
523        this.addScriptDataAttribute(UifConstants.DataAttributes.ACTION_DEFAULTS, ScriptUtils.convertToJsValue(
524                (Map<String, String>) actionDataDefaults));
525        this.addScriptDataAttribute(UifConstants.DataAttributes.REQ_INDICATOR, (String) requiredIndicator);
526
527        // give view role attribute for js selections
528        this.addDataAttribute(UifConstants.DataAttributes.ROLE, UifConstants.RoleTypes.VIEW);
529
530        // Add state mapping to post metadata
531        ViewLifecycle.getViewPostMetadata().addComponentPostData(this, "stateObjectBindingPath",
532                stateObjectBindingPath);
533        ViewLifecycle.getViewPostMetadata().addComponentPostData(this, "stateMapping", stateMapping);
534
535        setNestedComponentId(getInstructionalMessage(), this.getId() + UifConstants.IdSuffixes.INSTRUCTIONAL);
536
537        Header header = getHeader();
538        Help help = getHelp();
539
540        if (header != null) {
541            setNestedComponentId(header, this.getId() + UifConstants.IdSuffixes.HEADER_WRAPPER);
542            setNestedComponentId(header.getUpperGroup(), this.getId() + UifConstants.IdSuffixes.HEADER_UPPER_GROUP);
543            setNestedComponentId(header.getRightGroup(), this.getId() + UifConstants.IdSuffixes.HEADER_RIGHT_GROUP);
544            setNestedComponentId(header.getLowerGroup(), this.getId() + UifConstants.IdSuffixes.HEADER_LOWER_GROUP);
545            setNestedComponentId(help, this.getId() + UifConstants.IdSuffixes.HELP_WRAPPER);
546            if (help != null && help.getHelpAction() != null) {
547                setNestedComponentId(help.getHelpAction(), this.getId() + UifConstants.IdSuffixes.HELP_WRAPPER);
548            }
549        }
550    }
551
552    /**
553     * Helper method for setting a new ID for the nested components
554     *
555     * @param component component to adjust ID for
556     * @param newId
557     */
558    protected void setNestedComponentId(Component component, String newId) {
559        if (component != null) {
560            component.setId(newId);
561        }
562    }
563
564    /**
565     * {@inheritDoc}
566     */
567    @Override
568    public void notifyCompleted(ViewLifecyclePhase phase) {
569        super.notifyCompleted(phase);
570
571        if (phase.getViewPhase().equals(UifConstants.ViewPhases.FINALIZE)) {
572            ViewLifecycle.getHelper().performCustomViewFinalize(ViewLifecycle.getModel());
573        }
574    }
575
576    /**
577     * Gets all breadcrumb items related to this view's parent location.
578     *
579     * @return breadcrumb items
580     */
581    public List<BreadcrumbItem> getBreadcrumbItems() {
582        if (parentLocation == null) {
583            return Collections.emptyList();
584        }
585
586        List<BreadcrumbItem> breadcrumbItems = new ArrayList<BreadcrumbItem>();
587        breadcrumbItems.add(parentLocation.getPageBreadcrumbItem());
588        breadcrumbItems.add(parentLocation.getViewBreadcrumbItem());
589        for (BreadcrumbItem item : parentLocation.getResolvedBreadcrumbItems()) {
590            if (!breadcrumbItems.contains(item)) {
591                breadcrumbItems.add(item);
592            }
593        }
594
595        return breadcrumbItems;
596    }
597
598    /**
599     * {@inheritDoc}
600     */
601    @Override
602    public Set<Class<? extends Component>> getSupportedComponents() {
603        Set<Class<? extends Component>> supportedComponents = new HashSet<Class<? extends Component>>();
604        supportedComponents.add(Group.class);
605
606        return supportedComponents;
607    }
608
609    /**
610     * {@inheritDoc}
611     */
612    @Override
613    public String getComponentTypeName() {
614        return "view";
615    }
616
617    /**
618     * Iterates through the contained page items and returns the Page that matches the set current page id or
619     * the first page in the case of a single page view.
620     *
621     * @return page group instance
622     */
623    @ViewLifecycleRestriction(exclude = UifConstants.ViewPhases.PRE_PROCESS)
624    public PageGroup getCurrentPage() {
625        if (currentPage != null) {
626            return currentPage;
627        }
628
629        for (Component item : this.getItems()) {
630            if (!(item instanceof PageGroup)) {
631                continue;
632            }
633
634            if (singlePageView || StringUtils.equals(item.getId(), getCurrentPageId())) {
635                currentPage = (PageGroup) CopyUtils.unwrap(item);
636            }
637        }
638
639        return currentPage;
640    }
641
642    /**
643     * Getter for returning the view's items and page for inclusion in the pre-process phase.
644     *
645     * <p>Note this is necessary so we get IDs assigned for all the pages during the pre-process phase. For other
646     * phases, only the current page is picked up.</p>
647     *
648     * @return list of components to include for the pre-process phase
649     */
650    @ViewLifecycleRestriction(UifConstants.ViewPhases.PRE_PROCESS)
651    public List<Component> getPagesForPreprocessing() {
652        List<Component> processProcessItems = new ArrayList<Component>();
653
654        if (getItems() != null) {
655            processProcessItems.addAll(getItems());
656        }
657
658        if (getPage() != null) {
659            processProcessItems.add(getPage());
660        }
661
662        return processProcessItems;
663    }
664
665    /**
666     * {@inheritDoc}
667     */
668    @Override
669    public void sortItems() {
670        if (!singlePageView) {
671            super.sortItems();
672        }
673    }
674
675    /**
676     * Namespace code the view should be associated with.
677     *
678     * <p>The namespace code is used within the framework in such places as permission checks and parameter
679     * retrieval</p>
680     *
681     * @return namespace code
682     */
683    @BeanTagAttribute
684    public String getNamespaceCode() {
685        return namespaceCode;
686    }
687
688    /**
689     * @see View#getNamespaceCode()
690     */
691    public void setNamespaceCode(String namespaceCode) {
692        checkMutable(true);
693        this.namespaceCode = namespaceCode;
694    }
695
696    /**
697     * View name provides an identifier for a view within a type. That is if a
698     * set of View instances have the same values for the
699     * properties that are used to retrieve them by their type, the name can be
700     * given to further qualify the view that should be retrieved.
701     *
702     * <p>A view type like the LookupView might have several views for
703     * the same object class, but one that is the 'default' lookup and another
704     * that is the 'advanced' lookup. Therefore the name on the first could be
705     * set to 'default', and likewise the name for the second 'advanced'</p>
706     *
707     * @return name of view
708     */
709    @BeanTagAttribute
710    public String getViewName() {
711        return this.viewName;
712    }
713
714    /**
715     * @see View#getViewName()
716     */
717    public void setViewName(String viewName) {
718        checkMutable(true);
719        this.viewName = viewName;
720    }
721
722    /**
723     * When true, this view will use a unified header - the page header will be omitted and its title will be used
724     * in the ViewHeader supportTitle property (dynamically updated on page change).
725     *
726     * @return true if using a unified header
727     */
728    @BeanTagAttribute
729    public boolean isUnifiedHeader() {
730        return unifiedHeader;
731    }
732
733    /**
734     * @see View#isUnifiedHeader()
735     */
736    public void setUnifiedHeader(boolean unifiedHeader) {
737        checkMutable(true);
738        this.unifiedHeader = unifiedHeader;
739    }
740
741    /**
742     * TopGroup is an optional group of content that appears above the breadcrumbs and view header.
743     *
744     * @return the topGroup component
745     */
746    @BeanTagAttribute
747    public Group getTopGroup() {
748        return topGroup;
749    }
750
751    /**
752     * @see View#getTopGroup()
753     */
754    public void setTopGroup(Group topGroup) {
755        checkMutable(true);
756        this.topGroup = topGroup;
757    }
758
759    /**
760     * Header for the application containing the view.
761     *
762     * <p>When deploying outside a portal, the application header and footer property can be configured to
763     * display a consistent header/footer across all views. Here application logos, menus, login controls
764     * and so on can be rendered</p>
765     *
766     * @return application header
767     */
768    @BeanTagAttribute
769    public Header getApplicationHeader() {
770        return applicationHeader;
771    }
772
773    /**
774     * @see View#getApplicationHeader()
775     */
776    public void setApplicationHeader(Header applicationHeader) {
777        checkMutable(true);
778        this.applicationHeader = applicationHeader;
779    }
780
781    /**
782     * Footer for the application containing the view.
783     *
784     * <p>When deploying outside a portal, the application header and footer property can be configured to
785     * display a consistent header/footer across all views. Here such things as application links, copyrights
786     * and so on can be rendered</p>
787     *
788     * @return application footer
789     */
790    @BeanTagAttribute
791    public Group getApplicationFooter() {
792        return applicationFooter;
793    }
794
795    /**
796     * @see View#getApplicationFooter()
797     */
798    public void setApplicationFooter(Group applicationFooter) {
799        checkMutable(true);
800        this.applicationFooter = applicationFooter;
801    }
802
803    /**
804     * Build footer for the application containing the view.
805     *
806     * @return build footer
807     */
808    @BeanTagAttribute
809    public Group getBuildFooter() {
810        return buildFooter;
811    }
812
813    /**
814     * @see View#getBuildFooter()
815     */
816    public void setBuildFooter(Group buildFooter) {
817        this.buildFooter = buildFooter;
818    }
819
820    /**
821     * Title text to be displayed on browser tab.
822     *
823     * @return
824     */
825    @BeanTagAttribute(name = "applicationTitleText")
826    public String getApplicationTitleText() {
827        return applicationTitleText;
828    }
829
830    /**
831     * @see View#getApplicationTitleText()
832     */
833    public void setApplicationTitleText(String applicationTitleText) {
834        this.applicationTitleText = applicationTitleText;
835    }
836
837    /**
838     * If true, the top group will be sticky (fixed to top of window).
839     *
840     * @return true if the top group is sticky, false otherwise
841     */
842    @BeanTagAttribute
843    public boolean isStickyTopGroup() {
844        return stickyTopGroup;
845    }
846
847    /**
848     * @see View#isStickyTopGroup()
849     */
850    public void setStickyTopGroup(boolean stickyTopGroup) {
851        checkMutable(true);
852        this.stickyTopGroup = stickyTopGroup;
853    }
854
855    /**
856     * If true, the breadcrumb widget will be sticky (fixed to top of window).
857     *
858     * @return true if breadcrumbs are sticky, false otherwise
859     */
860    @BeanTagAttribute
861    public boolean isStickyBreadcrumbs() {
862        return stickyBreadcrumbs;
863    }
864
865    /**
866     * @see View#isStickyBreadcrumbs()
867     */
868    public void setStickyBreadcrumbs(boolean stickyBreadcrumbs) {
869        checkMutable(true);
870        this.stickyBreadcrumbs = stickyBreadcrumbs;
871    }
872
873    /**
874     * If true, the ViewHeader for this view will be sticky (fixed to top of window).
875     *
876     * @return true if the header is sticky, false otherwise
877     */
878    @BeanTagAttribute
879    public boolean isStickyHeader() {
880        if (this.getHeader() != null && this.getHeader() instanceof ViewHeader) {
881            return ((ViewHeader) this.getHeader()).isSticky();
882        } else {
883            return false;
884        }
885    }
886
887    /**
888     * @see View#isStickyHeader()
889     */
890    public void setStickyHeader(boolean stickyHeader) {
891        checkMutable(true);
892        this.stickyHeader = stickyHeader;
893        if (this.getHeader() != null && this.getHeader() instanceof ViewHeader) {
894            ((ViewHeader) this.getHeader()).setSticky(stickyHeader);
895        }
896    }
897
898    /**
899     * Set to true to make the applicationHeader sticky (fixed to top of window)
900     *
901     * @return true if applicationHeader is sticky, false otherwise
902     */
903    @BeanTagAttribute
904    public boolean isStickyApplicationHeader() {
905        return stickyApplicationHeader;
906    }
907
908    /**
909     * @see View#isStickyApplicationHeader()
910     */
911    public void setStickyApplicationHeader(boolean stickyApplicationHeader) {
912        checkMutable(true);
913        this.stickyApplicationHeader = stickyApplicationHeader;
914    }
915
916    /**
917     * If true, the view footer will become sticky (fixed to bottom of window).
918     *
919     * @return ture if the view footer is sticky, false otherwise
920     */
921    @BeanTagAttribute
922    public boolean isStickyFooter() {
923        return stickyFooter;
924    }
925
926    /**
927     * @see View#isStickyFooter()
928     */
929    public void setStickyFooter(boolean stickyFooter) {
930        checkMutable(true);
931        this.stickyFooter = stickyFooter;
932        if (this.getFooter() != null) {
933            this.getFooter().addDataAttribute(UifConstants.DataAttributes.STICKY_FOOTER, Boolean.toString(
934                    stickyFooter));
935        }
936    }
937
938    /**
939     * If true, the applicationFooter will become sticky (fixed to bottom of window).
940     *
941     * @return true if the application footer is sticky, false otherwise
942     */
943    @BeanTagAttribute
944    public boolean isStickyApplicationFooter() {
945        return stickyApplicationFooter;
946    }
947
948    /**
949     * @see View#isStickyApplicationFooter()
950     */
951    public void setStickyApplicationFooter(boolean stickyApplicationFooter) {
952        checkMutable(true);
953        this.stickyApplicationFooter = stickyApplicationFooter;
954    }
955
956    /**
957     * List of CSS style classes that will be applied to a div that wraps the content.
958     *
959     * <p>Wrapping the content gives the ability to move between a fluid width container or a fixed width
960     * container. The div is also wraps content inside sticky elements (header and footer), so visual treatment
961     * can be given to the full width of the screen while restricting the area of the content.</p>
962     *
963     * <p>In Bootstrap, use 'container-fluid' for a fluid width container, and 'container' for a fixed width
964     * container.</p>
965     *
966     * @return List of css classes to apply to content wrapper div
967     */
968    @BeanTagAttribute
969    public List<String> getContentContainerCssClasses() {
970        return contentContainerCssClasses;
971    }
972
973    /**
974     * @see View#getContentContainerCssClasses()
975     */
976    public void setContentContainerCssClasses(List<String> contentContainerCssClasses) {
977        this.contentContainerCssClasses = contentContainerCssClasses;
978    }
979
980    /**
981     * Returns the list of {@link View#getContentContainerCssClasses()} as a concatenated string (each class
982     * is separated by a space).
983     *
984     * @return String of content css classes
985     */
986    public String getContentContainerClassesAsString() {
987        if (contentContainerCssClasses != null) {
988            return StringUtils.join(contentContainerCssClasses, " ");
989        }
990
991        return "";
992    }
993
994    /**
995     * Specifies what page should be rendered by default. This is the page that
996     * will be rendered when the View is first rendered or when the
997     * current page is not set
998     *
999     * @return id of the page to render by default
1000     */
1001    @BeanTagAttribute
1002    public String getEntryPageId() {
1003        return this.entryPageId;
1004    }
1005
1006    /**
1007     * @see View#getEntryPageId()
1008     */
1009    public void setEntryPageId(String entryPageId) {
1010        checkMutable(true);
1011        this.entryPageId = entryPageId;
1012    }
1013
1014    /**
1015     * The id for the page within the view that should be displayed in the UI. Other pages of the view will not be
1016     * rendered.
1017     *
1018     * <p>If current page id is not set, it is set to the configured entry page or first item in list id</p>
1019     *
1020     * @return id of the page that should be displayed
1021     */
1022    public String getCurrentPageId() {
1023        // default current page if not set
1024        if (StringUtils.isBlank(currentPageId)) {
1025            if (StringUtils.isNotBlank(entryPageId)) {
1026                currentPageId = entryPageId;
1027            } else if ((getItems() != null) && !getItems().isEmpty()) {
1028                Component firstPageGroup = getItems().get(0);
1029                if (firstPageGroup instanceof PageGroup) {
1030                    currentPageId = firstPageGroup.getId();
1031                }
1032            }
1033        }
1034
1035        return this.currentPageId;
1036    }
1037
1038    /**
1039     * @see View#getCurrentPageId()
1040     */
1041    public void setCurrentPageId(String currentPageId) {
1042        checkMutable(true);
1043        this.currentPageId = currentPageId;
1044        this.currentPage = null;
1045    }
1046
1047    /**
1048     * NavigationGroup instance for the View<
1049     *
1050     * <p>Provides configuration necessary to render the navigation. This includes
1051     * navigation items in addition to configuration for the navigation
1052     * renderer</p>
1053     *
1054     * @return NavigationGroup
1055     */
1056    @BeanTagAttribute
1057    public Group getNavigation() {
1058        return this.navigation;
1059    }
1060
1061    /**
1062     * @see View#getNavigation()
1063     */
1064    public void setNavigation(Group navigation) {
1065        checkMutable(true);
1066        this.navigation = navigation;
1067    }
1068
1069    /**
1070     * Class of the Form that should be used with the View instance.
1071     *
1072     * <p>The form is the top level object for all the view's data and is
1073     * used to present and accept data in the user interface. All form classes
1074     * should extend UifFormBase</p>
1075     *
1076     * @return class for the view's form
1077     * @see org.kuali.rice.krad.web.form.UifFormBase
1078     */
1079    @BeanTagAttribute
1080    public Class<?> getFormClass() {
1081        return this.formClass;
1082    }
1083
1084    /**
1085     * @see View#getFormClass()
1086     */
1087    public void setFormClass(Class<?> formClass) {
1088        checkMutable(true);
1089        this.formClass = formClass;
1090    }
1091
1092    /**
1093     * For View types that work primarily with one nested object of
1094     * the form (for instance document, or bo) the default binding object path
1095     * can be set for each of the views DataBinding components. If
1096     * the component does not set its own binding object path it will inherit
1097     * the default.
1098     *
1099     * @return binding path to the object from the form
1100     */
1101    @BeanTagAttribute
1102    public String getDefaultBindingObjectPath() {
1103        return this.defaultBindingObjectPath;
1104    }
1105
1106    /**
1107     * @see View#getDefaultBindingObjectPath()
1108     */
1109    public void setDefaultBindingObjectPath(String defaultBindingObjectPath) {
1110        checkMutable(true);
1111        this.defaultBindingObjectPath = defaultBindingObjectPath;
1112    }
1113
1114    /**
1115     * Configures the concrete classes that will be used for properties in the
1116     * form object graph that have an abstract or interface type.
1117     *
1118     * <p>For properties that have an abstract or interface type, it is not
1119     * possible to perform operations like getting/settings property values and
1120     * getting information from the dictionary. When these properties are
1121     * encountered in the object graph, this Map will be consulted
1122     * to determine the concrete type to use</p>
1123     *
1124     * <p>e.g. Suppose we have a property document.accountingLine.accountNumber and
1125     * the accountingLine property on the document instance has an interface
1126     * type 'AccountingLine'. We can then put an entry into this map with key
1127     * 'document.accountingLine', and value
1128     * 'org.kuali.rice.sampleapp.TravelAccountingLine'. When getting the
1129     * property type or an entry from the dictionary for accountNumber, the
1130     * TravelAccountingLine class will be used</p>
1131     *
1132     * @return Map<String, Class> of class implementations keyed by path
1133     */
1134    @BeanTagAttribute
1135    public Map<String, Class<?>> getObjectPathToConcreteClassMapping() {
1136        if (objectPathToConcreteClassMapping == Collections.EMPTY_MAP && isMutable(true)) {
1137            objectPathToConcreteClassMapping = new HashMap<String, Class<?>>();
1138        }
1139
1140        return this.objectPathToConcreteClassMapping;
1141    }
1142
1143    /**
1144     * @see View#getObjectPathToConcreteClassMapping()
1145     */
1146    public void setObjectPathToConcreteClassMapping(Map<String, Class<?>> objectPathToConcreteClassMapping) {
1147        checkMutable(true);
1148        this.objectPathToConcreteClassMapping = objectPathToConcreteClassMapping;
1149    }
1150
1151    /**
1152     * Declares additional script files that should be included with the
1153     * View.
1154     *
1155     * <p>These files are brought into the HTML page along with
1156     * common script files configured for the Rice application. Each entry
1157     * contain the path to the CSS file, either a relative path, path from web
1158     * root, or full URI</p>
1159     *
1160     * <p>e.g. '/krad/scripts/myScript.js', '../scripts/myScript.js',
1161     * 'http://my.edu/web/myScript.js'</p>
1162     *
1163     * @return script file locations
1164     */
1165    @BeanTagAttribute
1166    public List<String> getAdditionalScriptFiles() {
1167        if (additionalScriptFiles == Collections.EMPTY_LIST && isMutable(true)) {
1168            additionalScriptFiles = new LifecycleAwareList<String>(this);
1169        }
1170
1171        return additionalScriptFiles;
1172    }
1173
1174    /**
1175     * @see View#getAdditionalScriptFiles()
1176     */
1177    public void setAdditionalScriptFiles(List<String> additionalScriptFiles) {
1178        checkMutable(true);
1179        if (additionalScriptFiles == null) {
1180            this.additionalScriptFiles = Collections.emptyList();
1181        } else {
1182            this.additionalScriptFiles = new LifecycleAwareList<String>(this, additionalScriptFiles);
1183        }
1184    }
1185
1186    /**
1187     * Declares additional CSS files that should be included with the View.
1188     *
1189     * <p>These files are brought into the HTML page along with
1190     * common CSS files configured for the Rice application. Each entry should
1191     * contain the path to the CSS file, either a relative path, path from web
1192     * root, or full URI</p>
1193     *
1194     * <p>e.g. '/krad/css/stacked-view.css', '../css/stacked-view.css',
1195     * 'http://my.edu/web/stacked-view.css'</p>
1196     *
1197     * @return CSS file locations
1198     */
1199    @BeanTagAttribute
1200    public List<String> getAdditionalCssFiles() {
1201        if (additionalCssFiles == Collections.EMPTY_LIST && isMutable(true)) {
1202            additionalCssFiles = new LifecycleAwareList<String>(this);
1203        }
1204
1205        return additionalCssFiles;
1206    }
1207
1208    /**
1209     * @see View#getAdditionalCssFiles()
1210     */
1211    public void setAdditionalCssFiles(List<String> additionalCssFiles) {
1212        checkMutable(true);
1213        if (additionalCssFiles == null) {
1214            this.additionalCssFiles = Collections.emptyList();
1215        } else {
1216            this.additionalCssFiles = new LifecycleAwareList<String>(this, additionalCssFiles);
1217        }
1218    }
1219
1220    /**
1221     * List of additional link tags that should be included with the View in the html head.
1222     *
1223     * @return headlink objects
1224     */
1225    @BeanTagAttribute
1226    public List<HeadLink> getAdditionalHeadLinks() {
1227        return additionalHeadLinks;
1228    }
1229
1230    /**
1231     * @see View#getAdditionalHeadLinks()
1232     */
1233    public void setAdditionalHeadLinks(List<HeadLink> additionalHeadLinks) {
1234        this.additionalHeadLinks = additionalHeadLinks;
1235    }
1236
1237    /**
1238     * List of additional meta tags that should be included with the View in the html head tag.
1239     *
1240     * @return   additionalMetaTags
1241     */
1242    @BeanTagAttribute
1243    public List<MetaTag> getAdditionalMetaTags() {
1244        return additionalMetaTags;
1245    }
1246
1247    /**
1248     * @see View#getAdditionalMetaTags()
1249     */
1250    public void setAdditionalMetaTags(List<MetaTag> additionalMetaTags) {
1251        this.additionalMetaTags = additionalMetaTags;
1252    }
1253
1254    /**
1255     * True if the libraryCssClasses set on components will be output to their class attribute, false otherwise.
1256     *
1257     * @return true if using libraryCssClasses on components
1258     */
1259    @BeanTagAttribute
1260    public boolean isUseLibraryCssClasses() {
1261        return useLibraryCssClasses;
1262    }
1263
1264    /**
1265     * @see View#isUseLibraryCssClasses()
1266     */
1267    public void setUseLibraryCssClasses(boolean useLibraryCssClasses) {
1268        checkMutable(true);
1269        this.useLibraryCssClasses = useLibraryCssClasses;
1270    }
1271
1272    /**
1273     * List of templates that are used to render the view.
1274     *
1275     * <p>This list will be populated by unique template names as the components of the view are being processed.
1276     * Additional templates can be added in the view configuration if desired. At the beginning of the the view
1277     * rendering, each template in the list will then be included or processed by the template language</p>
1278     *
1279     * <p>Note the user of this depends on the template language being used for rendering. Some languages might require
1280     * including the template for each component instance (for example JSP templates). While others might simply
1281     * include markup that is then available for rendering each component instance (for example FreeMarker which has
1282     * a macro for each component that the template defines)</p>
1283     *
1284     * @return list of template names that should be included for rendering the view
1285     */
1286    public List<String> getViewTemplates() {
1287        return viewTemplates;
1288    }
1289
1290    /**
1291     * Adds a template to the views include list.
1292     *
1293     * @param template path to template to add
1294     */
1295    public void addViewTemplate(String template) {
1296        if (StringUtils.isEmpty(template)) {
1297            return;
1298        }
1299
1300        if (!viewTemplates.contains(template)) {
1301            synchronized (viewTemplates) {
1302                viewTemplates.add(template);
1303            }
1304        }
1305    }
1306
1307    /**
1308     * Setter for the the list of template names that should be included to render the view
1309     *
1310     * @param viewTemplates
1311     */
1312    public void setViewTemplates(List<String> viewTemplates) {
1313        checkMutable(true);
1314
1315        if (viewTemplates == null) {
1316            this.viewTemplates = new LifecycleAwareList<String>(this);
1317        } else {
1318            this.viewTemplates = new LifecycleAwareList<String>(this, viewTemplates);
1319        }
1320    }
1321
1322    /**
1323     * View type name the view is associated with the view instance
1324     *
1325     * <p>Views that share common features and functionality can be grouped by the
1326     * view type. Usually view types extend the <code>View</code> class to
1327     * provide additional configuration and to set defaults. View types can also
1328     * implement the <code>ViewTypeService</code> to add special indexing and
1329     * retrieval of views</p>
1330     *
1331     * @return view type name for the view
1332     */
1333    @BeanTagAttribute
1334    public ViewType getViewTypeName() {
1335        return this.viewTypeName;
1336    }
1337
1338    /**
1339     * @see View#getViewTypeName()
1340     */
1341    public void setViewTypeName(ViewType viewTypeName) {
1342        checkMutable(true);
1343        this.viewTypeName = viewTypeName;
1344    }
1345
1346    /**
1347     * Class name of the ViewHelperService that handles the various phases of the Views lifecycle.
1348     *
1349     * @return Class for the spring bean
1350     * @see org.kuali.rice.krad.uif.service.ViewHelperService
1351     */
1352    @BeanTagAttribute
1353    public Class<? extends ViewHelperService> getViewHelperServiceClass() {
1354        return this.viewHelperServiceClass;
1355    }
1356
1357    /**
1358     * Setter for the <code>ViewHelperService</code> class name
1359     * Also initializes the viewHelperService
1360     *
1361     * @param viewHelperServiceClass
1362     */
1363    public void setViewHelperServiceClass(Class<? extends ViewHelperService> viewHelperServiceClass) {
1364        checkMutable(true);
1365        this.viewHelperServiceClass = viewHelperServiceClass;
1366        if ((this.viewHelperService == null) && (this.viewHelperServiceClass != null)) {
1367            viewHelperService = KRADUtils.createNewObjectFromClass(viewHelperServiceClass);
1368        }
1369    }
1370
1371    /**
1372     * Creates the <code>ViewHelperService</code> associated with the View
1373     *
1374     * @return ViewHelperService instance
1375     */
1376    @BeanTagAttribute
1377    public ViewHelperService getViewHelperService() {
1378        return viewHelperService;
1379    }
1380
1381    /**
1382     * @see View#getViewHelperServiceClass()
1383     */
1384    public void setViewHelperService(ViewHelperService viewHelperService) {
1385        checkMutable(true);
1386        this.viewHelperService = viewHelperService;
1387    }
1388
1389    /**
1390     * Invoked to produce a ViewIndex of the current view's components
1391     */
1392    public void clearIndex() {
1393        if (this.viewIndex == null) {
1394            this.viewIndex = new ViewIndex();
1395        }
1396        this.viewIndex.clearIndex(this);
1397    }
1398
1399    /**
1400     * Holds field indexes of the View instance for retrieval.
1401     *
1402     * @return ViewIndex instance
1403     */
1404    public ViewIndex getViewIndex() {
1405        return this.viewIndex;
1406    }
1407
1408    /**
1409     * Map of parameters from the request that set view options, used to rebuild
1410     * the view on each post
1411     *
1412     * <p>
1413     * Views can be configured by parameters. These might impact which parts of
1414     * the view are rendered or how the view behaves. Generally these would get
1415     * passed in when a new view is requested (by request parameters). These
1416     * will be used to initially populate the view properties. In addition, on a
1417     * post the view will be rebuilt and properties reset again by the allow
1418     * request parameters.
1419     * </p>
1420     *
1421     * <p>
1422     * Example parameter would be for MaintenaceView whether a New, Edit, or
1423     * Copy was requested (maintenance mode)
1424     * </p>
1425     *
1426     * @return
1427     */
1428    public Map<String, String> getViewRequestParameters() {
1429        return this.viewRequestParameters;
1430    }
1431
1432    /**
1433     * @see View#getViewRequestParameters()
1434     */
1435    public void setViewRequestParameters(Map<String, String> viewRequestParameters) {
1436        checkMutable(true);
1437        this.viewRequestParameters = Collections.unmodifiableMap(viewRequestParameters);
1438    }
1439
1440    /**
1441     * Indicates whether the form (model) associated with the view should be stored in the user session.
1442     *
1443     * <p>The form class (or model) is used to hold the data that backs the view along with the built view object. Storing
1444     * the form instance in session allows many things:
1445     *
1446     * <ul>
1447     * <li>Data does not need to be rebuilt for each server request (for example a collection)</li>
1448     * <li>Data that does not need to go to the user can remain on the form, reducing the size of the response and
1449     * improving security</li>
1450     * <li>Data can be keep around in a 'pre-save' state. When requested by the user changes can then be persisted to
1451     * the database</li>
1452     * <li>Certain information about the view that was rendered, such as input fields, collection paths, and refresh
1453     * components can be kept on the form to support UI interaction</li>
1454     * </ul>
1455     *
1456     * Setting this flag to false will prevent the form from being kept in session and as a result will limit what can
1457     * be done by the framework. In almost all cases this is not recommended</p>
1458     *
1459     * <p>Note all forms will be cleared when the user session expires (based on the rice configuration). In addition, the
1460     * framework enables clear points on certain actions to remove the form when it is no longer needed</p>
1461     *
1462     * @return true if the form should be stored in the user session, false if only request based
1463     */
1464    @BeanTagAttribute
1465    public boolean isPersistFormToSession() {
1466        return persistFormToSession;
1467    }
1468
1469    /**
1470     * @see View#isPersistFormToSession()
1471     */
1472    public void setPersistFormToSession(boolean persistFormToSession) {
1473        checkMutable(true);
1474        this.persistFormToSession = persistFormToSession;
1475    }
1476
1477    /**
1478     * Configures behavior that should occur when a session timeout occurs on the view.
1479     *
1480     * @return view session policy instance
1481     */
1482    @BeanTagAttribute
1483    public ViewSessionPolicy getSessionPolicy() {
1484        return sessionPolicy;
1485    }
1486
1487    /**
1488     * @see View#getSessionPolicy()
1489     */
1490    public void setSessionPolicy(ViewSessionPolicy sessionPolicy) {
1491        checkMutable(true);
1492        this.sessionPolicy = sessionPolicy;
1493    }
1494
1495    public int getPreloadPoolSize() {
1496        return preloadPoolSize;
1497    }
1498
1499    public void setPreloadPoolSize(int preloadPoolSize) {
1500        this.preloadPoolSize = preloadPoolSize;
1501    }
1502
1503    /**
1504     * PresentationController that should be used for the View instance.
1505     *
1506     * <p>The presentation controller is consulted to determine component (group,
1507     * field) state such as required, read-only, and hidden. The presentation
1508     * controller does not take into account user permissions. The presentation
1509     * controller can also output action flags and edit modes that will be set
1510     * onto the view instance and can be referred to by conditional expressions</p>
1511     *
1512     * @return PresentationController
1513     */
1514    @BeanTagAttribute
1515    public ViewPresentationController getPresentationController() {
1516        return this.presentationController;
1517    }
1518
1519    /**
1520     * @see View#getPresentationController()
1521     */
1522    public void setPresentationController(ViewPresentationController presentationController) {
1523        checkMutable(true);
1524        this.presentationController = presentationController;
1525    }
1526
1527    /**
1528     * Setter for the view's presentation controller by class
1529     *
1530     * @param presentationControllerClass
1531     */
1532    public void setPresentationControllerClass(
1533            Class<? extends ViewPresentationController> presentationControllerClass) {
1534        checkMutable(true);
1535        this.presentationController = KRADUtils.createNewObjectFromClass(presentationControllerClass);
1536    }
1537
1538    /**
1539     * Authorizer that should be used for the View instance
1540     *
1541     * <p>The authorizer class is consulted to determine component (group, field)
1542     * state such as required, read-only, and hidden based on the users
1543     * permissions. It typically communicates with the Kuali Identity Management
1544     * system to determine roles and permissions. It is used with the
1545     * presentation controller and dictionary conditional logic to determine the
1546     * final component state. The authorizer can also output action flags and
1547     * edit modes that will be set onto the view instance and can be referred to
1548     * by conditional expressions</p>
1549     *
1550     * @return Authorizer
1551     */
1552    @BeanTagAttribute
1553    public ViewAuthorizer getAuthorizer() {
1554        return this.authorizer;
1555    }
1556
1557    /**
1558     * @see View#getAuthorizer()
1559     */
1560    public void setAuthorizer(ViewAuthorizer authorizer) {
1561        checkMutable(true);
1562        this.authorizer = authorizer;
1563    }
1564
1565    /**
1566     * Setter for the view's authorizer by class
1567     *
1568     * @param authorizerClass
1569     */
1570    public void setAuthorizerClass(Class<? extends ViewAuthorizer> authorizerClass) {
1571        checkMutable(true);
1572        this.authorizer = KRADUtils.createNewObjectFromClass(authorizerClass);
1573    }
1574
1575    /**
1576     * Map of strings that flag what actions can be taken in the UI.
1577     *
1578     * <p>These can be used in conditional expressions in the dictionary or by
1579     * other UI logic</p>
1580     *
1581     * @return action flags
1582     */
1583    @BeanTagAttribute
1584    public Map<String, Boolean> getActionFlags() {
1585        return this.actionFlags;
1586    }
1587
1588    /**
1589     * @see View#getActionFlags()
1590     */
1591    public void setActionFlags(Map<String, Boolean> actionFlags) {
1592        this.actionFlags = actionFlags;
1593    }
1594
1595    /**
1596     * Map of edit modes that enabled for the view.
1597     *
1598     * <p>These can be used in conditional expressions in the dictionary or by
1599     * other UI logic</p>
1600     *
1601     * @return edit modes
1602     */
1603    @BeanTagAttribute
1604    public Map<String, Boolean> getEditModes() {
1605        return this.editModes;
1606    }
1607
1608    /**
1609     * @see View#getEditModes()
1610     */
1611    public void setEditModes(Map<String, Boolean> editModes) {
1612        this.editModes = editModes;
1613    }
1614
1615    public Class<? extends RequestAuthorizationCache> getRequestAuthorizationCacheClass() {
1616        return requestAuthorizationCacheClass;
1617    }
1618
1619    public void setRequestAuthorizationCacheClass(
1620            Class<? extends RequestAuthorizationCache> requestAuthorizationCacheClass) {
1621        this.requestAuthorizationCacheClass = requestAuthorizationCacheClass;
1622    }
1623
1624    /**
1625     * Map that contains expressions to evaluate and make available as variables
1626     * for conditional expressions within the view.
1627     *
1628     * <p>Each Map entry contains one expression variables, where the map key gives
1629     * the name for the variable, and the map value gives the variable
1630     * expression. The variables expressions will be evaluated before
1631     * conditional logic is run and made available as variables for other
1632     * conditional expressions. Variable expressions can be based on the model
1633     * and any object contained in the view's context</p>
1634     *
1635     * @return variable expressions
1636     */
1637    @BeanTagAttribute
1638    public Map<String, String> getExpressionVariables() {
1639        return this.expressionVariables;
1640    }
1641
1642    /**
1643     * @see View#getExpressionVariables()
1644     */
1645    public void setExpressionVariables(Map<String, String> expressionVariables) {
1646        checkMutable(true);
1647        this.expressionVariables = Collections.unmodifiableMap(expressionVariables);
1648    }
1649
1650    /**
1651     * Indicates whether the View only has a single page
1652     * Group or contains multiple page Group instances.
1653     *
1654     * <p>In the case of a single page it is assumed the group's items
1655     * list contains the section groups for the page, and the page itself is
1656     * given by the page property ({@link #getPage()}. This is for convenience
1657     * of configuration and also can drive other configuration like styling</p>
1658     *
1659     * @return true if the view only contains one page group, false if
1660     * it contains multple pages
1661     */
1662    @BeanTagAttribute
1663    public boolean isSinglePageView() {
1664        return this.singlePageView;
1665    }
1666
1667    /**
1668     * @see View#isSinglePageView()
1669     */
1670    public void setSinglePageView(boolean singlePageView) {
1671        checkMutable(true);
1672        this.singlePageView = singlePageView;
1673    }
1674
1675    /**
1676     * Indicates whether the default sections specified in the page items list
1677     * should be included for this view.  This only applies to single paged views.
1678     *
1679     * @return true if the view should contain the default sections
1680     * specified in the page
1681     */
1682    @BeanTagAttribute
1683    public boolean isMergeWithPageItems() {
1684        return mergeWithPageItems;
1685    }
1686
1687    /**
1688     * @see View#isMergeWithPageItems()
1689     */
1690    public void setMergeWithPageItems(boolean mergeWithPageItems) {
1691        checkMutable(true);
1692        this.mergeWithPageItems = mergeWithPageItems;
1693    }
1694
1695    /**
1696     * For single paged views ({@link #isSinglePageView()}, gives the page
1697     * Group the view should render. The actual items for the page
1698     * is taken from the group's items list ({@link #getItems()}, and set onto
1699     * the give page group. This is for convenience of configuration.
1700     *
1701     * @return page group for single page views
1702     */
1703    @ViewLifecycleRestriction
1704    @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECT)
1705    public PageGroup getPage() {
1706        return this.page;
1707    }
1708
1709    /**
1710     * @see View#getPage()
1711     */
1712    public void setPage(PageGroup page) {
1713        checkMutable(true);
1714        this.page = page;
1715    }
1716
1717    /**
1718     * {@inheritDoc}
1719     */
1720    @Override
1721    @ViewLifecycleRestriction
1722    @BeanTagAttribute
1723    public List<? extends Component> getItems() {
1724        if (items == Collections.EMPTY_LIST && isMutable(true)) {
1725            items = new LifecycleAwareList<Component>(this);
1726        }
1727
1728        return items;
1729    }
1730
1731    /**
1732     * {@inheritDoc}
1733     */
1734    @SuppressWarnings("unchecked")
1735    @Override
1736    public void setItems(List<? extends Component> items) {
1737        checkMutable(true);
1738
1739        if (items == null) {
1740            this.items = Collections.emptyList();
1741        } else {
1742            // TODO: Fix this unchecked condition.
1743            this.items = new LifecycleAwareList<Component>(this, (List<Component>) items);
1744        }
1745    }
1746
1747    /**
1748     * Provide a list of dialog groups associated with this view.
1749     *
1750     * @return List of dialog Groups
1751     */
1752    @BeanTagAttribute
1753    @ViewLifecycleRestriction(value = UifConstants.ViewPhases.FINALIZE,
1754            condition = "!ajaxRequest || (ajaxReturnType eq 'update-view') || (ajaxReturnType eq 'update-page')")
1755    public List<Group> getDialogs() {
1756        if (dialogs == Collections.EMPTY_LIST && isMutable(true)) {
1757            dialogs = new LifecycleAwareList<Group>(this);
1758        }
1759
1760        return dialogs;
1761    }
1762
1763    /**
1764     * @see View#getDialogs()
1765     */
1766    public void setDialogs(List<Group> dialogs) {
1767        checkMutable(true);
1768
1769        if (dialogs == null) {
1770            this.dialogs = Collections.emptyList();
1771        } else {
1772            this.dialogs = new LifecycleAwareList<Group>(this, dialogs);
1773        }
1774    }
1775
1776    /**
1777     * Breadcrumb widget used for displaying homeward path and history
1778     *
1779     * @return the breadcrumbs
1780     */
1781    @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
1782    public Breadcrumbs getBreadcrumbs() {
1783        return this.breadcrumbs;
1784    }
1785
1786    /**
1787     * @param breadcrumbs the breadcrumbs to set
1788     */
1789    public void setBreadcrumbs(Breadcrumbs breadcrumbs) {
1790        checkMutable(true);
1791        this.breadcrumbs = breadcrumbs;
1792    }
1793
1794    /**
1795     * The breadcrumbOptions for this view.
1796     *
1797     * <p>Render options set at the view level are always ignored (only apply to
1798     * page level BreadcrumbOptions).  BreadcrumbOptions for homewardPathBreadcrumbs,
1799     * preViewBreadcrumbs, prePageBreadcrumbs,
1800     * and breadcrumbOverrides are inherited by
1801     * child pages unless they override them themselves.</p>
1802     *
1803     * @return the BreadcrumbOptions for this view
1804     */
1805    @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
1806    public BreadcrumbOptions getBreadcrumbOptions() {
1807        return breadcrumbOptions;
1808    }
1809
1810    /**
1811     * @see View#getBreadcrumbOptions()
1812     */
1813    public void setBreadcrumbOptions(BreadcrumbOptions breadcrumbOptions) {
1814        checkMutable(true);
1815        this.breadcrumbOptions = breadcrumbOptions;
1816    }
1817
1818    /**
1819     * The View's breadcrumbItem defines settings for the breadcrumb which appears in the breadcrumb list for this
1820     * view.
1821     *
1822     * @return the breadcrumbItem
1823     */
1824    @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
1825    public BreadcrumbItem getBreadcrumbItem() {
1826        return breadcrumbItem;
1827    }
1828
1829    /**
1830     * @see View#getBreadcrumbItem()
1831     */
1832    public void setBreadcrumbItem(BreadcrumbItem breadcrumbItem) {
1833        checkMutable(true);
1834        this.breadcrumbItem = breadcrumbItem;
1835    }
1836
1837    /**
1838     * The parentLocation defines urls that represent the parent of a View in a conceptial site hierarchy.
1839     *
1840     * <p>By defining a parent with these urls defined, a breadcrumb chain can be generated and displayed automatically
1841     * before this View's breadcrumbItem(s).  To chain multiple views, the urls must be defining viewId and
1842     * controllerMapping settings instead of setting an href directly (this will end the chain).  If labels are
1843     * not set on parentLocations, the labels will attempt to be derived from parent views/pages breadcrumbItem
1844     * and headerText - if these contain expressions which cannot be evaluated in the current context an exception
1845     * will be thrown</p>
1846     *
1847     * @return the parentLocation
1848     */
1849    @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
1850    public ParentLocation getParentLocation() {
1851        return parentLocation;
1852    }
1853
1854    /**
1855     * @see View#getParentLocation()
1856     */
1857    public void setParentLocation(ParentLocation parentLocation) {
1858        checkMutable(true);
1859        this.parentLocation = parentLocation;
1860    }
1861
1862    /**
1863     * The pathBasedBreadcrumbs for this View.  These can only be set by the framework.
1864     *
1865     * @return the path based breadcrumbs
1866     */
1867    public List<BreadcrumbItem> getPathBasedBreadcrumbs() {
1868        return pathBasedBreadcrumbs;
1869    }
1870
1871    /**
1872     * The pathBasedBreadcrumbs for this View.
1873     *
1874     * @param pathBasedBreadcrumbs
1875     */
1876    public void setPathBasedBreadcrumbs(List<BreadcrumbItem> pathBasedBreadcrumbs) {
1877        checkMutable(true);
1878        this.pathBasedBreadcrumbs = pathBasedBreadcrumbs == null ? null : new LifecycleAwareList<BreadcrumbItem>(this,
1879                pathBasedBreadcrumbs);
1880    }
1881
1882    /**
1883     * Growls widget which sets up global settings for the growls used in this
1884     * view and its pages.
1885     *
1886     * @return the growls
1887     */
1888    @BeanTagAttribute
1889    public Growls getGrowls() {
1890        return this.growls;
1891    }
1892
1893    /**
1894     * @param growls the growls to set
1895     */
1896    public void setGrowls(Growls growls) {
1897        checkMutable(true);
1898        this.growls = growls;
1899    }
1900
1901    /**
1902     * @return returns the refresh block object
1903     */
1904    @BeanTagAttribute
1905    public BlockUI getRefreshBlockUI() {
1906        return refreshBlockUI;
1907    }
1908
1909    /**
1910     * Set the refresh BlockUI used with single element blocking
1911     * (such as ajax based element loading/updates)
1912     *
1913     * @param refreshBlockUI
1914     */
1915    public void setRefreshBlockUI(BlockUI refreshBlockUI) {
1916        checkMutable(true);
1917        this.refreshBlockUI = refreshBlockUI;
1918    }
1919
1920    /**
1921     * @return returns the navigation block object
1922     */
1923    @BeanTagAttribute
1924    public BlockUI getNavigationBlockUI() {
1925        return navigationBlockUI;
1926    }
1927
1928    /**
1929     * Set the navigation BlockUI used with single page blocking
1930     * (such as full page loading/saving)
1931     *
1932     * @param navigationBlockUI
1933     */
1934    public void setNavigationBlockUI(BlockUI navigationBlockUI) {
1935        checkMutable(true);
1936        this.navigationBlockUI = navigationBlockUI;
1937    }
1938
1939    /**
1940     * Whether to use growls to show messages - info, warning and error
1941     *
1942     * <p>Growls use the messages contained in the message map. If enabled, info
1943     * messages in their entirety will be displayed in growls, for warning and
1944     * error messages a growl message will notify the user that these messages
1945     * exist on the page.</p>
1946     *
1947     * <p> If this setting is disabled, it is recommended that
1948     * infoMessage display be enabled for the page ValidationMessages bean in order to
1949     * display relevant information to the user. Note: the growl scripts are
1950     * built out in the PageGroup class.</p>
1951     *
1952     * @return the growlMessagingEnabled
1953     */
1954    @BeanTagAttribute
1955    public boolean isGrowlMessagingEnabled() {
1956        return this.growlMessagingEnabled;
1957    }
1958
1959    /**
1960     * enable or disable showing of messages using growls
1961     *
1962     * @param growlMessagingEnabled the growlMessagingEnabled to set
1963     */
1964    public void setGrowlMessagingEnabled(boolean growlMessagingEnabled) {
1965        checkMutable(true);
1966        this.growlMessagingEnabled = growlMessagingEnabled;
1967    }
1968
1969    /**
1970     * Indicates whether the form should be validated for dirtyness.
1971     *
1972     * <p>For FormView, it's necessary to validate when the user tries to navigate out of the form. If set, all the
1973     * InputFields will be validated on refresh, navigate, cancel or close Action or on form
1974     * unload and if dirty, displays a message and user can decide whether to continue with
1975     * the action or stay on the form. For lookup and inquiry, it's not needed to validate</p>
1976     *
1977     * @return true if dirty validation is set
1978     */
1979    @BeanTagAttribute
1980    public boolean isApplyDirtyCheck() {
1981        return this.applyDirtyCheck;
1982    }
1983
1984    /**
1985     * @see View#isApplyDirtyCheck()
1986     */
1987    public void setApplyDirtyCheck(boolean applyDirtyCheck) {
1988        checkMutable(true);
1989        this.applyDirtyCheck = applyDirtyCheck;
1990    }
1991
1992    /**
1993     * Returns whether the current view supports displaying <code>KualiCode</code>'s name as additional display value
1994     *
1995     * @return true if the current view supports
1996     */
1997    @BeanTagAttribute
1998    public boolean isTranslateCodesOnReadOnlyDisplay() {
1999        return translateCodesOnReadOnlyDisplay;
2000    }
2001
2002    /**
2003     * Indicates whether the Name of the Code should be displayed when a property is of type <code>KualiCode</code>
2004     *
2005     * @param translateCodesOnReadOnlyDisplay indicates whether KualiCode's name should be included.
2006     */
2007    public void setTranslateCodesOnReadOnlyDisplay(boolean translateCodesOnReadOnlyDisplay) {
2008        checkMutable(true);
2009        this.translateCodesOnReadOnlyDisplay = translateCodesOnReadOnlyDisplay;
2010    }
2011
2012    /**
2013     * Indicates whether the view allows read only fields to be specified on the request URL which will
2014     * override the view setting.
2015     *
2016     * <p>If enabled, the readOnlyFields request parameter can be sent to indicate fields that should be set read only</p>
2017     *
2018     * @return true if read only request overrides are allowed, false if not
2019     */
2020    @BeanTagAttribute
2021    public boolean isSupportsRequestOverrideOfReadOnlyFields() {
2022        return supportsRequestOverrideOfReadOnlyFields;
2023    }
2024
2025    /**
2026     * @see View#isSupportsRequestOverrideOfReadOnlyFields()
2027     */
2028    public void setSupportsRequestOverrideOfReadOnlyFields(boolean supportsRequestOverrideOfReadOnlyFields) {
2029        checkMutable(true);
2030        this.supportsRequestOverrideOfReadOnlyFields = supportsRequestOverrideOfReadOnlyFields;
2031    }
2032
2033    /**
2034     * Indicates whether the browser autocomplete functionality should be disabled for the
2035     * entire form (adds autocomplete="off").
2036     *
2037     * <p>The browser's native autocomplete functionality can cause issues with security fields and also fields
2038     * with the UIF suggest widget enabled</p>
2039     *
2040     * @return true if the native autocomplete should be turned off for the form, false if not
2041     */
2042    @BeanTagAttribute
2043    public boolean isDisableNativeAutocomplete() {
2044        return disableNativeAutocomplete;
2045    }
2046
2047    /**
2048     * @see View#isDisableNativeAutocomplete()
2049     */
2050    public void setDisableNativeAutocomplete(boolean disableNativeAutocomplete) {
2051        checkMutable(true);
2052        this.disableNativeAutocomplete = disableNativeAutocomplete;
2053    }
2054
2055    /**
2056     * Enables functionality to bust the browsers cache by appending an unique cache key.
2057     *
2058     * <p>Since response headers are unreliable for preventing caching in all browsers, the
2059     * framework uses a technique for updating the URL to include an unique cache key. If the
2060     * HTML 5 History API is supported a parameter can be added to the URL which causes the browser
2061     * to not find the cached page when the user goes back. If not the framework falls back to using
2062     * a hash key and resubmitting using script to pull the latest</p>
2063     *
2064     * @return true if cache for the view should be disabled, false if not
2065     */
2066    @BeanTagAttribute
2067    public boolean isDisableBrowserCache() {
2068        return disableBrowserCache;
2069    }
2070
2071    /**
2072     * @see View#isDisableBrowserCache()
2073     */
2074    public void setDisableBrowserCache(boolean disableBrowserCache) {
2075        checkMutable(true);
2076        this.disableBrowserCache = disableBrowserCache;
2077    }
2078
2079    /**
2080     * Script that is executed at the beginning of page load (before any other script).
2081     *
2082     * <p>Many used to set server variables client side</p>
2083     *
2084     * @return pre load script
2085     */
2086    @BeanTagAttribute
2087    public String getPreLoadScript() {
2088        return preLoadScript;
2089    }
2090
2091    /**
2092     * @see View#getPreLoadScript()
2093     */
2094    public void setPreLoadScript(String preLoadScript) {
2095        checkMutable(true);
2096        this.preLoadScript = preLoadScript;
2097    }
2098
2099    /**
2100     * The theme which contains stylesheets for this view.
2101     *
2102     * @return ViewTheme
2103     */
2104    @BeanTagAttribute
2105    public ViewTheme getTheme() {
2106        return theme;
2107    }
2108
2109    /**
2110     * @see View#getTheme()
2111     */
2112    public void setTheme(ViewTheme theme) {
2113        checkMutable(true);
2114        this.theme = theme;
2115    }
2116
2117    /**
2118     * The stateObject's binding path, this will be used along with the StateMapping's statePropertyName to
2119     * determine what field in the model state information is stored in for this view.  Used during View validation.
2120     *
2121     * @return stateObjectBindingPath path to the object storing state information
2122     */
2123    @BeanTagAttribute
2124    public String getStateObjectBindingPath() {
2125        return stateObjectBindingPath;
2126    }
2127
2128    /**
2129     * The stateObject's binding path, this will be used along with the StateMapping's statePropertyName to
2130     * determine what field in the model state information is stored in for this view.  Used during View validation.
2131     *
2132     * @param stateObjectBindingPath
2133     */
2134    public void setStateObjectBindingPath(String stateObjectBindingPath) {
2135        checkMutable(true);
2136        this.stateObjectBindingPath = stateObjectBindingPath;
2137    }
2138
2139    /**
2140     * Gets the stateMapping.
2141     *
2142     * <p>The state mapping object is used to determine the state information for a view,
2143     * it must include an ordered list of states, and where to find the state information for the view.
2144     * A stateMapping must be set for state based validation to occur.  When stateMapping information is
2145     * not included, the view's model is considered stateless and all constraints will apply regardless of their
2146     * state information or replacements (ie, they will function as they did in version 2.1).</p>
2147     *
2148     * @return information needed for state based validation, if null no state based validation
2149     * functionality will exist and configured constraints will apply regardless of state
2150     * @since 2.2
2151     */
2152    @BeanTagAttribute
2153    public StateMapping getStateMapping() {
2154        return stateMapping;
2155    }
2156
2157    /**
2158     * @see View#getStateMapping()
2159     */
2160    public void setStateMapping(StateMapping stateMapping) {
2161        checkMutable(true);
2162        this.stateMapping = stateMapping;
2163    }
2164
2165    /**
2166     * Returns the general context that is available before the apply model phase (during the
2167     * initialize phase)
2168     *
2169     * @return context map
2170     */
2171    public Map<String, Object> getPreModelContext() {
2172        if (preModelContext == null) {
2173            Map<String, Object> context = new HashMap<String, Object>();
2174
2175            context.put(UifConstants.ContextVariableNames.VIEW, this);
2176            context.put(UifConstants.ContextVariableNames.VIEW_HELPER, viewHelperService);
2177
2178            ViewTheme theme = getTheme();
2179            if (theme != null) {
2180                context.put(UifConstants.ContextVariableNames.THEME_IMAGES, theme.getImageDirectory());
2181            }
2182
2183            Map<String, String> properties = CoreApiServiceLocator.getKualiConfigurationService().getAllProperties();
2184            context.put(UifConstants.ContextVariableNames.CONFIG_PROPERTIES, properties);
2185            context.put(UifConstants.ContextVariableNames.CONSTANTS, KRADConstants.class);
2186            context.put(UifConstants.ContextVariableNames.UIF_CONSTANTS, UifConstants.class);
2187            context.put(UifConstants.ContextVariableNames.USER_SESSION, GlobalVariables.getUserSession());
2188
2189            preModelContext = Collections.unmodifiableMap(context);
2190        }
2191
2192        return preModelContext;
2193    }
2194
2195    /**
2196     * @see org.kuali.rice.krad.uif.component.ComponentBase#clone()
2197     */
2198    @Override
2199    public View clone() throws CloneNotSupportedException {
2200        View viewCopy = (View) super.clone();
2201        viewCopy.viewIndex = new ViewIndex();
2202        return viewCopy;
2203    }
2204
2205    /**
2206     * {@inheritDoc}
2207     */
2208    @Override
2209    public void completeValidation(ValidationTrace tracer) {
2210        tracer.addBean(this);
2211
2212        // Check for the presence of a valid item with an not-null EntryPageId
2213        boolean validPageId = false;
2214        if (getEntryPageId() != null) {
2215            for (int i = 0; i < getItems().size(); i++) {
2216                if (getEntryPageId().compareTo(getItems().get(i).getId()) == 0) {
2217                    validPageId = true;
2218                }
2219            }
2220        } else {
2221            validPageId = true;
2222        }
2223        if (!validPageId) {
2224            String currentValues[] = {"entryPageId = " + getEntryPageId()};
2225            tracer.createError("Items must contain an item with a matching id to entryPageId", currentValues);
2226        }
2227
2228        // Check to insure the view as not already been set
2229        if (tracer.getValidationStage() == ValidationTrace.START_UP) {
2230            if (getViewStatus().compareTo(ViewStatus.CREATED) != 0) {
2231                String currentValues[] = {"viewStatus = " + getViewStatus()};
2232                tracer.createError("ViewStatus should not be set", currentValues);
2233            }
2234        }
2235
2236        // Check to insure the binding object path is a valid property
2237        boolean validDefaultBindingObjectPath = false;
2238        if (getDefaultBindingObjectPath() == null) {
2239            validDefaultBindingObjectPath = true;
2240        } else if (DataDictionary.isPropertyOf(getFormClass(), getDefaultBindingObjectPath())) {
2241            validDefaultBindingObjectPath = true;
2242        }
2243        if (!validDefaultBindingObjectPath) {
2244            String currentValues[] =
2245                    {"formClass = " + getFormClass(), "defaultBindingPath = " + getDefaultBindingObjectPath()};
2246            tracer.createError("DefaultBingdingObjectPath must be a valid property of the formClass", currentValues);
2247        }
2248
2249        // Check to insure the page is set if the view is a single page
2250        if (isSinglePageView()) {
2251            if (getPage() == null) {
2252                String currentValues[] = {"singlePageView = " + isSinglePageView(), "page = " + getPage()};
2253                tracer.createError("Page must be set if singlePageView is true", currentValues);
2254            }
2255            for (int i = 0; i < getItems().size(); i++) {
2256                if (getItems().get(i).getClass() == PageGroup.class) {
2257                    String currentValues[] =
2258                            {"singlePageView = " + isSinglePageView(), "items(" + i + ") = " + getItems().get(i)
2259                                    .getClass()};
2260                    tracer.createError("Items cannot be pageGroups if singlePageView is true", currentValues);
2261                }
2262            }
2263        }
2264
2265        // Checks to insure the Growls are set if growl messaging is enabled
2266        if (isGrowlMessagingEnabled() == true && getGrowls() == null) {
2267            if (Validator.checkExpressions(this, "growls")) {
2268                String currentValues[] =
2269                        {"growlMessagingEnabled = " + isGrowlMessagingEnabled(), "growls = " + getGrowls()};
2270                tracer.createError("Growls cannot be null if Growl Messaging is enabled", currentValues);
2271            }
2272        }
2273
2274        // Checks that there are items present if the view is not a single page
2275        if (!isSinglePageView()) {
2276            if (getItems().size() == 0) {
2277                String currentValues[] =
2278                        {"singlePageView = " + isSinglePageView(), "items.size = " + getItems().size()};
2279                tracer.createWarning("Items cannot be empty if singlePageView is false", currentValues);
2280            } else {
2281                for (int i = 0; i < getItems().size(); i++) {
2282                    if (getItems().get(i).getClass() != PageGroup.class) {
2283                        String currentValues[] =
2284                                {"singlePageView = " + isSinglePageView(), "items(" + i + ") = " + getItems().get(i)
2285                                        .getClass()};
2286                        tracer.createError("Items must be pageGroups if singlePageView is false", currentValues);
2287                    }
2288                }
2289            }
2290        }
2291        super.completeValidation(tracer.getCopy());
2292    }
2293
2294}