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.view; 017 018import java.io.Serializable; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.Map; 022import java.util.Set; 023 024import org.apache.commons.lang.StringUtils; 025import org.apache.log4j.Logger; 026import org.kuali.rice.krad.uif.component.Component; 027import org.kuali.rice.krad.uif.container.CollectionGroup; 028import org.kuali.rice.krad.uif.container.Container; 029import org.kuali.rice.krad.uif.field.DataField; 030import org.kuali.rice.krad.uif.util.CopyUtils; 031import org.kuali.rice.krad.uif.util.LifecycleElement; 032 033/** 034 * Holds component indexes of a {@link View} instance for convenient retrieval during the lifecycle. 035 * 036 * @author Kuali Rice Team (rice.collab@kuali.org) 037 */ 038public class ViewIndex implements Serializable { 039 private static final long serialVersionUID = 4700818801272201371L; 040 private static final Logger LOG = Logger.getLogger(ViewIndex.class); 041 042 private Map<String, Component> index; 043 private Map<String, DataField> dataFieldIndex; 044 045 private Map<String, CollectionGroup> collectionsIndex; 046 private Map<String, LifecycleElement> lifecycleElementsByPath; 047 048 private Set<String> assignedIds; 049 050 /** 051 * Default Constructor. 052 */ 053 public ViewIndex() { 054 index = new HashMap<String, Component>(); 055 dataFieldIndex = new HashMap<String, DataField>(); 056 collectionsIndex = new HashMap<String, CollectionGroup>(); 057 lifecycleElementsByPath = new HashMap<String, LifecycleElement>(); 058 assignedIds = new HashSet<String>(); 059 } 060 061 /** 062 * Clears view indexes, for reinitializing indexes at the start of each phase. 063 */ 064 protected void clearIndex(View view) { 065 index = new HashMap<String, Component>(); 066 dataFieldIndex = new HashMap<String, DataField>(); 067 collectionsIndex = new HashMap<String, CollectionGroup>(); 068 lifecycleElementsByPath = new HashMap<String, LifecycleElement>(); 069 } 070 071 /** 072 * Adds an entry to the main index for the given component. If the component is of type 073 * <code>DataField</code> or <code>CollectionGroup</code> an entry is created in the 074 * corresponding indexes for those types as well. Then the #indexComponent method is called for 075 * each of the component's children 076 * 077 * <p> 078 * If the component is already contained in the indexes, it will be replaced 079 * </p> 080 * 081 * <p> 082 * <code>DataField</code> instances are indexed by the attribute path. This is useful for 083 * retrieving the InputField based on the incoming request parameter 084 * </p> 085 * 086 * <p> 087 * <code>CollectionGroup</code> instances are indexed by the collection path. This is useful for 088 * retrieving the CollectionGroup based on the incoming request parameter 089 * </p> 090 * 091 * @param component component instance to index 092 */ 093 public void indexComponent(Component component) { 094 if (component == null) { 095 return; 096 } 097 098 synchronized (index) { 099 index.put(component.getId(), component); 100 } 101 102 synchronized (lifecycleElementsByPath) { 103 lifecycleElementsByPath.put(component.getViewPath(), component); 104 } 105 106 if (component instanceof Container) { 107 Container container = (Container) component; 108 if (container.getLayoutManager() != null) { 109 synchronized (lifecycleElementsByPath) { 110 lifecycleElementsByPath.put(container.getLayoutManager().getViewPath(), 111 container.getLayoutManager()); 112 } 113 } 114 } 115 116 if (component instanceof DataField) { 117 DataField field = (DataField) component; 118 119 synchronized (dataFieldIndex) { 120 dataFieldIndex.put(field.getBindingInfo().getBindingPath(), field); 121 } 122 } else if (component instanceof CollectionGroup) { 123 CollectionGroup collectionGroup = (CollectionGroup) component; 124 125 synchronized (collectionsIndex) { 126 collectionsIndex.put(collectionGroup.getBindingInfo().getBindingPath(), collectionGroup); 127 } 128 } 129 } 130 131 /** 132 * Observe an assigned ID. 133 * 134 * @param id ID to observe 135 * @return true if the ID is unique, false if the ID has already been observed 136 */ 137 public boolean observeAssignedId(String id) { 138 if (assignedIds.contains(id)) { 139 return false; 140 } 141 142 synchronized (assignedIds) { 143 return assignedIds.add(id); 144 } 145 } 146 147 /** 148 * Retrieves a <code>Component</code> from the view index by Id 149 * 150 * @param id id for the component to retrieve 151 * @return Component instance found in index, or null if no such component exists 152 */ 153 public Component getComponentById(String id) { 154 return index.get(id); 155 } 156 157 /** 158 * Retrieves a <code>DataField</code> instance from the index 159 * 160 * @param propertyPath full path of the data field (from the form) 161 * @return DataField instance for the path or Null if not found 162 */ 163 public DataField getDataFieldByPath(String propertyPath) { 164 return dataFieldIndex.get(propertyPath); 165 } 166 167 /** 168 * Retrieves a <code>DataField</code> instance that has the given property name specified (note 169 * this is not the full binding path and first match is returned) 170 * 171 * @param propertyName property name for field to retrieve 172 * @return DataField instance found or null if not found 173 */ 174 public DataField getDataFieldByPropertyName(String propertyName) { 175 DataField dataField = null; 176 177 for (DataField field : dataFieldIndex.values()) { 178 if (StringUtils.equals(propertyName, field.getPropertyName())) { 179 dataField = field; 180 break; 181 } 182 } 183 184 return dataField; 185 } 186 187 /** 188 * Gets the Map of lifecycle elements that are indexed by their path relative to the view. 189 * 190 * @return map of all indexed lifecycle elements, key is the element path and value is the element instance 191 */ 192 public Map<String, LifecycleElement> getLifecycleElementsByPath() { 193 return lifecycleElementsByPath; 194 } 195 196 /** 197 * Gets a lifecycle element instance by the given path (relative to the view). 198 * 199 * @param path path of the element that should be returned 200 * @return lifecycle element instance for given path or null if element at that path does not exist 201 */ 202 public LifecycleElement getLifecycleElementByPath(String path) { 203 if ((lifecycleElementsByPath != null) && lifecycleElementsByPath.containsKey(path)) { 204 return lifecycleElementsByPath.get(path); 205 } 206 207 return null; 208 } 209 210 /** 211 * Gets the Map that contains attribute field indexing information. The Map key points to an 212 * attribute binding path, and the Map value is the <code>DataField</code> instance 213 * 214 * @return data fields index map 215 */ 216 public Map<String, DataField> getDataFieldIndex() { 217 return this.dataFieldIndex; 218 } 219 220 /** 221 * Gets the Map that contains collection indexing information. The Map key gives the binding 222 * path to the collection, and the Map value givens the <code>CollectionGroup</code> instance 223 * 224 * @return collection index map 225 */ 226 public Map<String, CollectionGroup> getCollectionsIndex() { 227 return this.collectionsIndex; 228 } 229 230 /** 231 * Retrieves a <code>CollectionGroup</code> instance from the index 232 * 233 * @param collectionPath full path of the collection (from the form) 234 * @return CollectionGroup instance for the collection path or Null if not found 235 */ 236 public CollectionGroup getCollectionGroupByPath(String collectionPath) { 237 return collectionsIndex.get(collectionPath); 238 } 239 240 /** 241 * Returns a clone of the view index. 242 * 243 * @return ViewIndex clone 244 */ 245 public ViewIndex copy() { 246 ViewIndex viewIndexCopy = new ViewIndex(); 247 248 if (this.index != null) { 249 Map<String, Component> indexCopy = new HashMap<String, Component>(); 250 for (Map.Entry<String, Component> indexEntry : this.index.entrySet()) { 251 if (indexEntry.getValue() instanceof View) { 252 LOG.warn("View reference at " + indexEntry); 253 } else { 254 indexCopy.put(indexEntry.getKey(), (Component) CopyUtils.copy(indexEntry.getValue())); 255 } 256 } 257 258 viewIndexCopy.index = indexCopy; 259 } 260 261 if (this.dataFieldIndex != null) { 262 Map<String, DataField> dataFieldIndexCopy = new HashMap<String, DataField>(); 263 for (Map.Entry<String, DataField> indexEntry : this.dataFieldIndex.entrySet()) { 264 dataFieldIndexCopy.put(indexEntry.getKey(), (DataField) CopyUtils.copy(indexEntry.getValue())); 265 } 266 267 viewIndexCopy.dataFieldIndex = dataFieldIndexCopy; 268 } 269 270 if (this.collectionsIndex != null) { 271 Map<String, CollectionGroup> collectionsIndexCopy = new HashMap<String, CollectionGroup>(); 272 for (Map.Entry<String, CollectionGroup> indexEntry : this.collectionsIndex.entrySet()) { 273 collectionsIndexCopy.put(indexEntry.getKey(), (CollectionGroup) CopyUtils.copy(indexEntry.getValue())); 274 } 275 276 viewIndexCopy.collectionsIndex = collectionsIndexCopy; 277 } 278 279 return viewIndexCopy; 280 } 281 282}