001    /**
002     * Copyright 2005-2014 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.krad.uif.lifecycle;
017    
018    import java.util.Collections;
019    import java.util.HashMap;
020    import java.util.List;
021    import java.util.Map;
022    import java.util.Queue;
023    import java.util.Set;
024    
025    import org.apache.commons.lang.StringUtils;
026    import org.kuali.rice.krad.uif.UifConstants;
027    import org.kuali.rice.krad.uif.component.Component;
028    import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle.LifecycleEvent;
029    import org.kuali.rice.krad.uif.lifecycle.finalize.SetReadOnlyOnDataBindingTask;
030    import org.kuali.rice.krad.uif.lifecycle.model.ApplyAuthAndPresentationLogicTask;
031    import org.kuali.rice.krad.uif.lifecycle.model.ComponentDefaultApplyModelTask;
032    import org.kuali.rice.krad.uif.lifecycle.model.EvaluateExpressionsTask;
033    import org.kuali.rice.krad.uif.lifecycle.model.HelperCustomApplyModelTask;
034    import org.kuali.rice.krad.uif.lifecycle.model.PopulateComponentContextTask;
035    import org.kuali.rice.krad.uif.lifecycle.model.RefreshStateModifyTask;
036    import org.kuali.rice.krad.uif.lifecycle.model.SyncClientSideStateTask;
037    import org.kuali.rice.krad.uif.util.LifecycleElement;
038    import org.kuali.rice.krad.uif.view.View;
039    import org.kuali.rice.krad.uif.view.ViewTheme;
040    
041    /**
042     * Lifecycle phase processing task for applying the model to a component.
043     *
044     * <p>
045     * During the apply model phase each component of the tree if invoked to setup any state based on
046     * the given model data
047     * </p>
048     *
049     * <p>
050     * Part of the view lifecycle that applies the model data to the view. Should be called after the
051     * model has been populated before the view is rendered. The main things that occur during this
052     * phase are:
053     * <ul>
054     * <li>Generation of dynamic fields (such as collection rows)</li>
055     * <li>Execution of conditional logic (hidden, read-only, required settings based on model values)</li>
056     * </ul>
057     * </p>
058     *
059     * <p>
060     * The update phase can be called multiple times for the view's lifecycle (typically only once per
061     * request)
062     * </p>
063     *
064     * @author Kuali Rice Team (rice.collab@kuali.org)
065     */
066    public class ApplyModelComponentPhase extends ViewLifecyclePhaseBase {
067    
068        /**
069         * Set of IDs that have been visited during the view's apply model phase.
070         *
071         * <p>
072         * This reference is typically shared by all component apply model phases.
073         * </p>
074         */
075        private Set<String> visitedIds;
076    
077        /**
078         * Mapping of context variables inherited from the view.
079         */
080        private Map<String, Object> commonContext;
081    
082        /**
083         * {@inheritDoc}
084         */
085        @Override
086        protected void recycle() {
087            super.recycle();
088            visitedIds = null;
089            commonContext = null;
090        }
091    
092        /**
093         * Create a new lifecycle phase processing task for applying the model to a element.
094         *
095         * @param element The element the model should be applied to
096         * @param model Top level object containing the data
097         * @param path The path to the element relative to the parent element
098         * @param refreshPaths list of paths to run lifecycle on when executing a refresh lifecycle
099         * @param parent The parent element
100         * @param nextPhase The phase to queue directly upon completion of this phase, if applicable
101         * @param visitedIds Tracks components ids that have been seen for adjusting duplicates
102         */
103        protected void prepare(LifecycleElement element, Object model, String path, List<String> refreshPaths,
104                Component parent, ViewLifecyclePhaseBase nextPhase, Set<String> visitedIds) {
105            super.prepare(element, model, path, refreshPaths, parent, nextPhase);
106    
107            this.visitedIds = visitedIds;
108    
109            Map<String, Object> commonContext = new HashMap<String, Object>();
110    
111            View view = ViewLifecycle.getView();
112            Map<String, Object> viewContext = view.getContext();
113            if (viewContext != null) {
114                commonContext.putAll(viewContext);
115            }
116    
117            ViewTheme theme = view.getTheme();
118            if (theme != null) {
119                commonContext.put(UifConstants.ContextVariableNames.THEME_IMAGES, theme.getImageDirectory());
120            }
121    
122            commonContext.put(UifConstants.ContextVariableNames.COMPONENT, element instanceof Component ? element : parent);
123    
124            this.commonContext = Collections.unmodifiableMap(commonContext);
125        }
126    
127        /**
128         * {@inheritDoc}
129         */
130        @Override
131        public String getViewPhase() {
132            return UifConstants.ViewPhases.APPLY_MODEL;
133        }
134    
135        /**
136         * {@inheritDoc}
137         */
138        @Override
139        public String getStartViewStatus() {
140            return UifConstants.ViewStatus.INITIALIZED;
141        }
142    
143        /**
144         * {@inheritDoc}
145         */
146        @Override
147        public String getEndViewStatus() {
148            return UifConstants.ViewStatus.MODEL_APPLIED;
149        }
150    
151        /**
152         * {@inheritDoc}
153         */
154        @Override
155        public LifecycleEvent getEventToNotify() {
156            return null;
157        }
158    
159        /**
160         * Gets global objects for the context map and pushes them to the context for the component
161         *
162         * @return The common context elements to use while applying model elements to the view.
163         * @see #prepare(LifecycleElement, Object, String, Component, ViewLifecyclePhaseBase, Set)
164         */
165        public Map<String, Object> getCommonContext() {
166            return commonContext;
167        }
168    
169        /**
170         * Visit a lifecycle element.
171         *
172         * @param element The lifecycle element (component or layout manager) to mark as visisted.
173         * @return True if the element has been visited before, false if this was the first visit.
174         */
175        public boolean visit(LifecycleElement element) {
176            if (visitedIds.contains(element.getId())) {
177                return true;
178            }
179    
180            synchronized (visitedIds) {
181                return !visitedIds.add(element.getId());
182            }
183        }
184    
185        /**
186         * Applies the model data to a component of the View instance
187         *
188         * <p>
189         * TODO: Revise - The component is invoked to to apply the model data. Here the component can
190         * generate any additional fields needed or alter the configured fields. After the component is
191         * invoked a hook for custom helper service processing is invoked. Finally the method is
192         * recursively called for all the component children
193         * </p>
194         *
195         * {@inheritDoc}
196         */
197        @Override
198        protected void initializePendingTasks(Queue<ViewLifecycleTask<?>> tasks) {
199            tasks.add(LifecycleTaskFactory.getTask(PopulateComponentContextTask.class, this));
200            tasks.add(LifecycleTaskFactory.getTask(EvaluateExpressionsTask.class, this));
201            tasks.add(LifecycleTaskFactory.getTask(SyncClientSideStateTask.class, this));
202            tasks.add(LifecycleTaskFactory.getTask(ApplyAuthAndPresentationLogicTask.class, this));
203    
204            if (ViewLifecycle.isRefreshComponent(getViewPhase(), getViewPath())) {
205                tasks.add(LifecycleTaskFactory.getTask(RefreshStateModifyTask.class, this));
206            }
207    
208            tasks.add(LifecycleTaskFactory.getTask(ComponentDefaultApplyModelTask.class, this));
209            getElement().initializePendingTasks(this, tasks);
210            tasks.offer(LifecycleTaskFactory.getTask(RunComponentModifiersTask.class, this));
211            tasks.add(LifecycleTaskFactory.getTask(HelperCustomApplyModelTask.class, this));
212            tasks.add(LifecycleTaskFactory.getTask(SetReadOnlyOnDataBindingTask.class, this));
213        }
214    
215        /**
216         * Define all nested lifecycle components, and component prototypes, as successors.
217         * 
218         * {@inheritDoc}
219         */
220        @Override
221        protected ViewLifecyclePhase initializeSuccessor(LifecycleElement nestedElement, String nestedPath,
222                Component parent) {
223            ApplyModelComponentPhase applyModelPhase = LifecyclePhaseFactory.applyModel(nestedElement, getModel(),
224                    nestedPath, getRefreshPaths(), parent, null, visitedIds);
225    
226            if (nestedElement.isInitialized()) {
227                return applyModelPhase;
228            }
229    
230            return LifecyclePhaseFactory.initialize(nestedElement, getModel(), nestedPath, getRefreshPaths(), parent,
231                    applyModelPhase);
232        }
233    
234    }