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 */ 016package org.kuali.rice.krad.uif.lifecycle; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.krad.uif.UifConstants; 020import org.kuali.rice.krad.uif.component.Component; 021import org.kuali.rice.krad.uif.container.CollectionGroup; 022import org.kuali.rice.krad.uif.util.ComponentUtils; 023import org.kuali.rice.krad.uif.util.LifecycleElement; 024import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 025import org.kuali.rice.krad.uif.view.ViewIndex; 026 027import java.util.ArrayList; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.List; 031import java.util.Map; 032import java.util.Set; 033 034/** 035 * For components that can be refreshed, builds out the various paths the lifecycle needs to be run on when 036 * the component refresh process is run. 037 * 038 * @author Kuali Rice Team (rice.collab@kuali.org) 039 */ 040public class LifecycleRefreshPathBuilder { 041 042 /** 043 * Iterates through each {@link org.kuali.rice.krad.uif.util.LifecycleElement} that has been collected in 044 * the view index and invokes refresh path processing. 045 */ 046 public static void processLifecycleElements() { 047 ViewIndex viewIndex = ViewLifecycle.getView().getViewIndex(); 048 049 Map<String, LifecycleElement> lifecycleElements = viewIndex.getLifecycleElementsByPath(); 050 for (LifecycleElement lifecycleElement : lifecycleElements.values()) { 051 processLifecycleElement(lifecycleElement); 052 } 053 } 054 055 /** 056 * Determines whether the given lifecycle element is capable of being refreshed, and if so invokes 057 * {@link LifecycleRefreshPathBuilder#buildRefreshPathMappings} to build the refresh paths. 058 * 059 * @param element lifecycle element to build refresh paths for (if necessary) 060 */ 061 protected static void processLifecycleElement(LifecycleElement element) { 062 if ((element == null) || !(element instanceof Component)) { 063 return; 064 } 065 066 Component component = (Component) element; 067 if (ComponentUtils.canBeRefreshed(component) || (component instanceof CollectionGroup) || 068 component.isForceSessionPersistence()) { 069 ViewPostMetadata viewPostMetadata = ViewLifecycle.getViewPostMetadata(); 070 071 ComponentPostMetadata componentPostMetadata = viewPostMetadata.getComponentPostMetadata(component.getId()); 072 if (componentPostMetadata == null) { 073 componentPostMetadata = viewPostMetadata.initializeComponentPostMetadata(component); 074 } 075 076 componentPostMetadata.setPath(component.getViewPath()); 077 078 buildRefreshPathMappings(component, componentPostMetadata); 079 080 storePhasePathMapping(component, componentPostMetadata); 081 } 082 } 083 084 /** 085 * Builds the refresh paths for the given lifecycle element and sets onto the post metadata for storage. 086 * 087 * <p>For each lifecycle phase, a list of paths that the refresh lifecycle for the given should process 088 * is built. These are then stored on the component post metadata for retrieval on the refresh call</p> 089 * 090 * @param lifecycleElement lifecycle element to build paths for 091 * @param componentPostMetadata post metadata instance to store the paths 092 */ 093 protected static void buildRefreshPathMappings(LifecycleElement lifecycleElement, 094 ComponentPostMetadata componentPostMetadata) { 095 List<String> initializePaths = new ArrayList<String>(); 096 List<String> applyModelPaths = new ArrayList<String>(); 097 List<String> finalizePaths = new ArrayList<String>(); 098 099 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}