View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.lifecycle;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.uif.UifConstants;
20  import org.kuali.rice.krad.uif.component.Component;
21  import org.kuali.rice.krad.uif.container.CollectionGroup;
22  import org.kuali.rice.krad.uif.util.ComponentUtils;
23  import org.kuali.rice.krad.uif.util.LifecycleElement;
24  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
25  import org.kuali.rice.krad.uif.view.ViewIndex;
26  
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  
34  /**
35   * For components that can be refreshed, builds out the various paths the lifecycle needs to be run on when
36   * the component refresh process is run.
37   *
38   * @author Kuali Rice Team (rice.collab@kuali.org)
39   */
40  public class LifecycleRefreshPathBuilder {
41  
42      /**
43       * Iterates through each {@link org.kuali.rice.krad.uif.util.LifecycleElement} that has been collected in
44       * the view index and invokes refresh path processing.
45       */
46      public static void processLifecycleElements() {
47          ViewIndex viewIndex = ViewLifecycle.getView().getViewIndex();
48  
49          Map<String, LifecycleElement> lifecycleElements = viewIndex.getLifecycleElementsByPath();
50          for (LifecycleElement lifecycleElement : lifecycleElements.values()) {
51              processLifecycleElement(lifecycleElement);
52          }
53      }
54  
55      /**
56       * Determines whether the given lifecycle element is capable of being refreshed, and if so invokes
57       * {@link LifecycleRefreshPathBuilder#buildRefreshPathMappings} to build the refresh paths.
58       *
59       * @param element lifecycle element to build refresh paths for (if necessary)
60       */
61      protected static void processLifecycleElement(LifecycleElement element) {
62          if ((element == null) || !(element instanceof Component)) {
63              return;
64          }
65  
66          Component component = (Component) element;
67          if (ComponentUtils.canBeRefreshed(component) || (component instanceof CollectionGroup) ||
68                  component.isForceSessionPersistence()) {
69              ViewPostMetadata viewPostMetadata = ViewLifecycle.getViewPostMetadata();
70  
71              ComponentPostMetadata componentPostMetadata = viewPostMetadata.getComponentPostMetadata(component.getId());
72              if (componentPostMetadata == null) {
73                  componentPostMetadata = viewPostMetadata.initializeComponentPostMetadata(component);
74              }
75  
76              componentPostMetadata.setPath(component.getViewPath());
77  
78              buildRefreshPathMappings(component, componentPostMetadata);
79  
80              storePhasePathMapping(component, componentPostMetadata);
81          }
82      }
83  
84      /**
85       * Builds the refresh paths for the given lifecycle element and sets onto the post metadata for storage.
86       *
87       * <p>For each lifecycle phase, a list of paths that the refresh lifecycle for the given should process
88       * is built. These are then stored on the component post metadata for retrieval on the refresh call</p>
89       *
90       * @param lifecycleElement lifecycle element to build paths for
91       * @param componentPostMetadata post metadata instance to store the paths
92       */
93      protected static void buildRefreshPathMappings(LifecycleElement lifecycleElement,
94              ComponentPostMetadata componentPostMetadata) {
95          List<String> initializePaths = new ArrayList<String>();
96          List<String> applyModelPaths = new ArrayList<String>();
97          List<String> finalizePaths = new ArrayList<String>();
98  
99          String refreshElementPath = lifecycleElement.getViewPath();
100         processElementPath(refreshElementPath, initializePaths, applyModelPaths, finalizePaths, new HashSet<String>());
101 
102         Map<String, List<String>> refreshPathMappings = new HashMap<String, List<String>>();
103 
104         refreshPathMappings.put(UifConstants.ViewPhases.INITIALIZE, initializePaths);
105         refreshPathMappings.put(UifConstants.ViewPhases.APPLY_MODEL, applyModelPaths);
106         refreshPathMappings.put(UifConstants.ViewPhases.FINALIZE, finalizePaths);
107 
108         componentPostMetadata.setRefreshPathMappings(refreshPathMappings);
109     }
110 
111     /**
112      * Store phase path mapping for component if there are any paths different from the final path.
113      *
114      * @param lifecycleElement lifecycle element to store phase mapping for
115      * @param componentPostMetadata post metadata instance to hold mapping
116      */
117     protected static void storePhasePathMapping(LifecycleElement lifecycleElement,
118             ComponentPostMetadata componentPostMetadata) {
119         Map<String, String> storedPhasePathMapping = new HashMap<String, String>();
120 
121         String refreshElementPath = lifecycleElement.getViewPath();
122 
123         Map<String, String> phasePathMapping = lifecycleElement.getPhasePathMapping();
124         for (Map.Entry<String, String> phasePathEntry : phasePathMapping.entrySet()) {
125             if (!StringUtils.equals(phasePathEntry.getValue(), refreshElementPath)) {
126                 storedPhasePathMapping.put(phasePathEntry.getKey(), phasePathEntry.getValue());
127             }
128         }
129 
130         if (!storedPhasePathMapping.isEmpty()) {
131             componentPostMetadata.setPhasePathMapping(storedPhasePathMapping);
132         }
133     }
134 
135     /**
136      * Processes the element at the given path, and all its parents (given by properties within the path) adding
137      * their refresh paths to the lists being built.
138      *
139      * @param path path of the element to process
140      * @param initializePaths list of refresh paths for the intialize phase
141      * @param applyModelPaths list of refresh paths for the apply model phase
142      * @param finalizePaths list of refresh paths for the finalize phase
143      * @param visitedPaths list of paths that have already been processed
144      */
145     protected static void processElementPath(String path, List<String> initializePaths, List<String> applyModelPaths,
146             List<String> finalizePaths, Set<String> visitedPaths) {
147         String[] parentProperties = ObjectPropertyUtils.splitPropertyPath(path);
148 
149         String previousPath = null;
150         for (int i = 0; i < parentProperties.length; i++) {
151             String parentPath = parentProperties[i];
152             if (previousPath != null) {
153                 parentPath = previousPath + "." + parentPath;
154             }
155 
156             if (!visitedPaths.contains(parentPath)) {
157                 visitedPaths.add(parentPath);
158 
159                 addElementRefreshPaths(parentPath, initializePaths, applyModelPaths, finalizePaths, visitedPaths);
160             }
161 
162             previousPath = parentPath;
163         }
164     }
165 
166     /**
167      * Retrieves the lifecycle element from the view index that has the given path, then adds its refresh
168      * paths to the given lists.
169      *
170      * <p>If the lifecycle element had a different path in one the phases, a call is made back to
171      * {@link #processElementPath} to process any new parents.</p>
172      *
173      * @param elementPath path of the element to add paths for
174      * @param initializePaths list of refresh paths for the intialize phase
175      * @param applyModelPaths list of refresh paths for the apply model phase
176      * @param finalizePaths list of refresh paths for the finalize phase
177      * @param visitedPaths list of paths that have already been processed
178      */
179     protected static void addElementRefreshPaths(String elementPath, List<String> initializePaths,
180             List<String> applyModelPaths, List<String> finalizePaths, Set<String> visitedPaths) {
181         LifecycleElement lifecycleElement = ViewLifecycle.getView().getViewIndex().getLifecycleElementByPath(
182                 elementPath);
183         if (lifecycleElement == null) {
184             return;
185         }
186 
187         Map<String, String> phasePathMapping = lifecycleElement.getPhasePathMapping();
188         for (Map.Entry<String, String> phasePathEntry : phasePathMapping.entrySet()) {
189             String refreshPath = phasePathEntry.getValue();
190 
191             addElementRefreshPath(refreshPath, phasePathEntry.getKey(), initializePaths, applyModelPaths,
192                     finalizePaths);
193 
194             // if the path is different from the element path we are processing, process its parents
195             if (StringUtils.equals(elementPath, refreshPath)) {
196                 processElementPath(refreshPath, initializePaths, applyModelPaths, finalizePaths, visitedPaths);
197             }
198         }
199     }
200 
201     /**
202      * Adds the given path to the refresh path list for the given phase.
203      *
204      * @param path path to add
205      * @param phase phase for the list the path should be added to
206      * @param initializePaths list of refresh paths for the intialize phase
207      * @param applyModelPaths list of refresh paths for the apply model phase
208      * @param finalizePaths list of refresh paths for the finalize phase
209      */
210     protected static void addElementRefreshPath(String path, String phase, List<String> initializePaths,
211             List<String> applyModelPaths, List<String> finalizePaths) {
212         if (UifConstants.ViewPhases.INITIALIZE.equals(phase)) {
213             initializePaths.add(path);
214         } else if (UifConstants.ViewPhases.APPLY_MODEL.equals(phase)) {
215             applyModelPaths.add(path);
216         } else if (UifConstants.ViewPhases.FINALIZE.equals(phase)) {
217             finalizePaths.add(path);
218         }
219     }
220 
221 }