1 /*
2 * Copyright 2007 The Kuali Foundation
3 *
4 * Licensed under the Educational Community License, Version 1.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/ecl1.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.container;
17
18 import org.apache.commons.lang.StringUtils;
19 import org.kuali.rice.krad.uif.core.BindingInfo;
20 import org.kuali.rice.krad.uif.core.Component;
21 import org.kuali.rice.krad.uif.field.AttributeField;
22
23 import java.io.Serializable;
24 import java.util.HashMap;
25 import java.util.Map;
26
27 /**
28 * Holds field indexes of a <code>View</code> instance for retrieval
29 *
30 * @author Kuali Rice Team (rice.collab@kuali.org)
31 */
32 public class ViewIndex implements Serializable {
33 private static final long serialVersionUID = 4700818801272201371L;
34
35 private Map<String, Component> index;
36 private Map<String, AttributeField> attributeFieldIndex;
37 private Map<String, CollectionGroup> collectionsIndex;
38
39 /**
40 * Constructs new instance and performs indexing on View instance
41 *
42 * @param view - view instance to index
43 */
44 public ViewIndex(View view) {
45 index(view);
46 }
47
48 /**
49 * Walks through the View tree and indexes all components found. All components
50 * are indexed by their IDs with the special indexing done for certain components
51 *
52 * <p>
53 * <code>AttributeField</code> instances are indexed by the attribute path.
54 * This is useful for retrieving the AttributeField based on the incoming
55 * request parameter
56 * </p>
57 *
58 * <p>
59 * <code>CollectionGroup</code> instances are indexed by the collection
60 * path. This is useful for retrieving the CollectionGroup based on the
61 * incoming request parameter
62 * </p>
63 */
64 protected void index(View view) {
65 index = new HashMap<String, Component>();
66 attributeFieldIndex = new HashMap<String, AttributeField>();
67 collectionsIndex = new HashMap<String, CollectionGroup>();
68
69 indexComponent(view);
70 }
71
72 /**
73 * Adds an entry to the main index for the given component. If the component
74 * is of type <code>AttributeField</code> or <code>CollectionGroup</code> an
75 * entry is created in the corresponding indexes for those types as well. Then
76 * the #indexComponent method is called for each of the component's children
77 *
78 * <p>
79 * If the component is already contained in the indexes, it will be replaced
80 * </p>
81 *
82 * @param component - component instance to index
83 */
84 public void indexComponent(Component component) {
85 if (component == null) {
86 return;
87 }
88
89 index.put(component.getId(), component);
90
91 if (component instanceof AttributeField) {
92 AttributeField field = (AttributeField) component;
93 attributeFieldIndex.put(field.getBindingInfo().getBindingPath(), field);
94 } else if (component instanceof CollectionGroup) {
95 CollectionGroup collectionGroup = (CollectionGroup) component;
96 collectionsIndex.put(collectionGroup.getBindingInfo().getBindingPath(), collectionGroup);
97 }
98
99 for (Component nestedComponent : component.getNestedComponents()) {
100 indexComponent(nestedComponent);
101 }
102 }
103
104 /**
105 * Retrieves a <code>Component</code> from the view index by Id
106 *
107 * @param id - id for the component to retrieve
108 * @return Component instance found in index, or null if no such component exists
109 */
110 public Component getComponentById(String id) {
111 return index.get(id);
112 }
113
114 /**
115 * Retrieves a <code>AttributeField</code> instance from the index
116 *
117 * @param attributePath - full path of the attribute (from the form)
118 * @return AttributeField instance for the path or Null if not found
119 */
120 public AttributeField getAttributeFieldByPath(String attributePath) {
121 return attributeFieldIndex.get(attributePath);
122 }
123
124 /**
125 * Retrieves a <code>AttributeField</code> instance that has the given property name
126 * specified (note this is not the full binding path and first match is returned)
127 *
128 * @param propertyName - property name for field to retrieve
129 * @return AttributeField instance found or null if not found
130 */
131 public AttributeField getAttributeFieldByPropertyName(String propertyName) {
132 AttributeField attributeField = null;
133
134 for (AttributeField field : attributeFieldIndex.values()) {
135 if (StringUtils.equals(propertyName, field.getPropertyName())) {
136 attributeField = field;
137 break;
138 }
139 }
140
141 return attributeField;
142 }
143
144 /**
145 * Gets the Map that contains attribute field indexing information. The Map
146 * key points to an attribute binding path, and the Map value is the
147 * <code>AttributeField</code> instance
148 *
149 * @return Map<String, AttributeField> attribute fields index map
150 */
151 public Map<String, AttributeField> getAttributeFieldIndex() {
152 return this.attributeFieldIndex;
153 }
154
155 /**
156 * Gets the Map that contains collection indexing information. The Map key
157 * gives the binding path to the collection, and the Map value givens the
158 * <code>CollectionGroup</code> instance
159 *
160 * @return Map<String, CollectionGroup> collection index map
161 */
162 public Map<String, CollectionGroup> getCollectionsIndex() {
163 return this.collectionsIndex;
164 }
165
166 /**
167 * Retrieves a <code>CollectionGroup</code> instance from the index
168 *
169 * @param collectionPath - full path of the collection (from the form)
170 * @return CollectionGroup instance for the collection path or Null if not
171 * found
172 */
173 public CollectionGroup getCollectionGroupByPath(String collectionPath) {
174 return collectionsIndex.get(collectionPath);
175 }
176
177 /**
178 * Searches for the <code>AttributeField</code> based on the given binding path.
179 *
180 * <p>It first searches for the matching <code>AttributeField</code> by path. If not
181 * matching field found, it searches in the <code>CollectionGroup</code>
182 * </p>
183 *
184 * @param bindingInfo - <code>AttributeField</code> will be searched based on this binding path
185 * @return the attribute field for the binding path
186 */
187 public AttributeField getAttributeField(BindingInfo bindingInfo) {
188 // Find in the attribute index first.
189 AttributeField attributeField = getAttributeFieldByPath(bindingInfo.getBindingPath());
190
191 if (attributeField == null) {
192 // Lets search the collections (by collection's binding path)
193 String path = bindingInfo.getBindingObjectPath() + "." + bindingInfo.getBindByNamePrefix();
194
195 CollectionGroup collectionGroup = getCollectionGroupByPath(stripIndexesFromPropertyPath(path));
196 if (collectionGroup != null) {
197 for (Component item : ((CollectionGroup) collectionGroup).getItems()) {
198 if (item instanceof AttributeField) {
199 if (StringUtils
200 .equals(((AttributeField) item).getPropertyName(), bindingInfo.getBindingName())) {
201 attributeField = (AttributeField) item;
202 break;
203 }
204 }
205 }
206 }
207 }
208
209 return attributeField;
210 }
211
212 /**
213 * Strips indexes from the property path.
214 *
215 * <p> For example, it returns bo.fiscalOfficer.accounts.name for the input bo.fiscalOfficer.accounts[0].name,
216 * which can be used to find the components from the CollectionGroup index
217 * </p>
218 *
219 * @param propertyPath - property path
220 * @return the stripped index path
221 */
222 private String stripIndexesFromPropertyPath(String propertyPath) {
223
224 String returnValue = propertyPath;
225 String index = StringUtils.substringBetween(propertyPath, "[", "]");
226
227 if (StringUtils.isNotBlank(index)) {
228 returnValue = StringUtils.remove(propertyPath, "[" + index + "]");
229 return stripIndexesFromPropertyPath(returnValue);
230 } else {
231 return returnValue;
232 }
233 }
234 }