1 /**
2 * Copyright 2005-2014 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 java.beans.PropertyEditor;
19 import java.io.Serializable;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.kuali.rice.krad.uif.component.Component;
28
29 /**
30 * Holds data about the rendered view that might be needed to handle a post request.
31 *
32 * <p>When an action is requested on a view (for example add/delete line, field query, so on), it might be
33 * necessary to read configuration from the view that was rendered to cary out the action. However, the rendered
34 * view is not stored, and the new view is not rendered until after the controller completes. Therefore it is
35 * necessary to provide this mechanism.</p>
36 *
37 * <p>The post metadata is retrieved in the controller though the {@link org.kuali.rice.krad.web.form.UifFormBase}
38 * instance</p>
39 *
40 * @author Kuali Rice Team (rice.collab@kuali.org)
41 */
42 public class ViewPostMetadata implements Serializable {
43 private static final long serialVersionUID = -515221881981451818L;
44
45 private String id;
46
47 private Map<String, ComponentPostMetadata> componentPostMetadataMap;
48
49 private Map<String, PropertyEditor> fieldPropertyEditors;
50 private Map<String, PropertyEditor> secureFieldPropertyEditors;
51
52 private Set<String> inputFieldIds;
53 private Set<String> allDataFieldPropertyPaths;
54 private Map<String, List<Object>> addedCollectionObjects;
55
56 private Set<String> accessibleBindingPaths;
57 private Set<String> accessibleMethodToCalls;
58
59 /**
60 * Default contructor.
61 */
62 public ViewPostMetadata() {
63 fieldPropertyEditors = Collections.synchronizedMap(new HashMap<String, PropertyEditor>());
64 secureFieldPropertyEditors = Collections.synchronizedMap(new HashMap<String, PropertyEditor>());
65 inputFieldIds = Collections.synchronizedSet(new HashSet<String>());
66 allDataFieldPropertyPaths = Collections.synchronizedSet(new HashSet<String>());
67 addedCollectionObjects = Collections.synchronizedMap(new HashMap<String, List<Object>>());
68 accessibleBindingPaths = Collections.synchronizedSet(new HashSet<String>());
69 accessibleMethodToCalls = Collections.synchronizedSet(new HashSet<String>());
70 }
71
72 /**
73 * Constructor that takes the view id.
74 *
75 * @param id id for the view
76 */
77 public ViewPostMetadata(String id) {
78 this();
79
80 this.id = id;
81 }
82
83 /**
84 * Invoked after the lifecycle is complete to perform an necessary cleaning.
85 */
86 public void cleanAfterLifecycle() {
87 allDataFieldPropertyPaths = Collections.synchronizedSet(new HashSet<String>());
88 addedCollectionObjects = Collections.synchronizedMap(new HashMap<String, List<Object>>());
89 }
90
91 /**
92 * Id for the view the post metadata is associated with.
93 *
94 * @return view id
95 */
96 public String getId() {
97 return id;
98 }
99
100 /**
101 * @see ViewPostMetadata#getId()
102 */
103 public void setId(String id) {
104 this.id = id;
105 }
106
107 /**
108 * Map containing post metadata for a component keyed by the component id.
109 *
110 * @return post metadata map, key is component id and value is post metadata
111 */
112 public Map<String, ComponentPostMetadata> getComponentPostMetadataMap() {
113 return componentPostMetadataMap;
114 }
115
116 /**
117 * @see ViewPostMetadata#getComponentPostMetadataMap()
118 */
119 public void setComponentPostMetadataMap(Map<String, ComponentPostMetadata> componentPostMetadataMap) {
120 this.componentPostMetadataMap = componentPostMetadataMap;
121 }
122
123 /**
124 * Gets the component post metadata for the given component id.
125 *
126 * @param componentId id for the component whose post metadata should be retrieved
127 * @return post metadata object
128 */
129 public ComponentPostMetadata getComponentPostMetadata(String componentId) {
130 ComponentPostMetadata componentPostMetadata = null;
131
132 if (componentPostMetadataMap != null && (componentPostMetadataMap.containsKey(componentId))) {
133 componentPostMetadata = componentPostMetadataMap.get(componentId);
134 }
135
136 return componentPostMetadata;
137 }
138
139 /**
140 * Adds post data for the given component (this is a convenience method for add component post metadata).
141 *
142 * @param component component instance the data should be added for
143 * @param key key for the post data, this will be used to retrieve the value
144 * @param value value for the post data
145 */
146 public void addComponentPostData(Component component, String key, Object value) {
147 if (component == null) {
148 throw new IllegalArgumentException("Component must not be null for adding post data");
149 }
150
151 addComponentPostData(component.getId(), key, value);
152 }
153
154 /**
155 * Adds post data for the given component id (this is a convenience method for add component post metadata).
156 *
157 * @param componentId id for the component the data should be added for
158 * @param key key for the post data, this will be used to retrieve the value
159 * @param value value for the post data
160 */
161 public void addComponentPostData(String componentId, String key, Object value) {
162 if (value == null) {
163 return;
164 }
165
166 ComponentPostMetadata componentPostMetadata = initializeComponentPostMetadata(componentId);
167
168 componentPostMetadata.addData(key, value);
169 }
170
171 /**
172 * Retrieves post data that has been stored for the given component id and key.
173 *
174 * @param componentId id for the component the data should be retrieved for
175 * @param key key for the post data to retrieve
176 * @return value for the data, or null if the data does not exist
177 */
178 public Object getComponentPostData(String componentId, String key) {
179 ComponentPostMetadata componentPostMetadata = getComponentPostMetadata(componentId);
180
181 if (componentPostMetadata != null) {
182 return componentPostMetadata.getData(key);
183 }
184
185 return null;
186 }
187
188 /**
189 * Initializes a component post metadata instance for the given component.
190 *
191 * @param component component instance to initialize post metadata for
192 * @return post metadata instance
193 */
194 public ComponentPostMetadata initializeComponentPostMetadata(Component component) {
195 if (component == null) {
196 throw new IllegalArgumentException("Component must not be null to initialize post metadata");
197 }
198
199 return initializeComponentPostMetadata(component.getId());
200 }
201
202 /**
203 * Initializes a component post metadata instance for the given component id.
204 *
205 * @param componentId id for the component to initialize post metadata for
206 * @return post metadata instance
207 */
208 public ComponentPostMetadata initializeComponentPostMetadata(String componentId) {
209 ComponentPostMetadata componentPostMetadata;
210
211 if (componentPostMetadataMap == null) {
212 synchronized (this) {
213 if (componentPostMetadataMap == null) {
214 componentPostMetadataMap = new HashMap<String, ComponentPostMetadata>();
215 }
216 }
217 }
218
219 componentPostMetadata = componentPostMetadataMap.get(componentId);
220
221 if (componentPostMetadata == null) {
222 synchronized (componentPostMetadataMap) {
223 componentPostMetadata = new ComponentPostMetadata(componentId);
224 componentPostMetadataMap.put(componentId, componentPostMetadata);
225 }
226 }
227
228 return componentPostMetadata;
229 }
230
231 /**
232 * Maintains configuration of properties that have been configured for the view (if render was
233 * set to true) and there corresponding PropertyEdtior (if configured).
234 *
235 * <p>Information is pulled out of the View during the lifecycle so it can be used when a form post
236 * is done from the View. Note if a field is secure, it will be placed in the
237 * {@link #getSecureFieldPropertyEditors()} map instead</p>
238 *
239 * @return map of property path (full) to PropertyEditor
240 */
241 public Map<String, PropertyEditor> getFieldPropertyEditors() {
242 return fieldPropertyEditors;
243 }
244
245 /**
246 * Associates a property editor instance with the given property path.
247 *
248 * @param propertyPath path for the property the editor should be associated with
249 * @param propertyEditor editor instance to use when binding data for the property
250 */
251 public void addFieldPropertyEditor(String propertyPath, PropertyEditor propertyEditor) {
252 if (fieldPropertyEditors == null) {
253 fieldPropertyEditors = new HashMap<String, PropertyEditor>();
254 }
255
256 fieldPropertyEditors.put(propertyPath, propertyEditor);
257 }
258
259 /**
260 * Maintains configuration of secure properties that have been configured for the view (if
261 * render was set to true) and there corresponding PropertyEdtior (if configured).
262 *
263 * <p>Information is pulled out of the View during the lifecycle so it can be used when a form post
264 * is done from the View. Note if a field is non-secure, it will be placed in the
265 * {@link #getFieldPropertyEditors()} map instead</p>
266 *
267 * @return map of property path (full) to PropertyEditor
268 */
269 public Map<String, PropertyEditor> getSecureFieldPropertyEditors() {
270 return secureFieldPropertyEditors;
271 }
272
273 /**
274 * Associates a secure property editor instance with the given property path.
275 *
276 * @param propertyPath path for the property the editor should be associated with
277 * @param propertyEditor secure editor instance to use when binding data for the property
278 */
279 public void addSecureFieldPropertyEditor(String propertyPath, PropertyEditor propertyEditor) {
280 if (secureFieldPropertyEditors == null) {
281 secureFieldPropertyEditors = new HashMap<String, PropertyEditor>();
282 }
283
284 secureFieldPropertyEditors.put(propertyPath, propertyEditor);
285 }
286
287 /**
288 * Set of ids for all input fields rendered with the view.
289 *
290 * @return set of id strings
291 */
292 public Set<String> getInputFieldIds() {
293 return inputFieldIds;
294 }
295
296 /**
297 * @see ViewPostMetadata#getInputFieldIds()
298 */
299 public void setInputFieldIds(Set<String> inputFieldIds) {
300 this.inputFieldIds = inputFieldIds;
301 }
302
303 /**
304 * Set of data field property paths that have been rendered as part of the lifecycle.
305 *
306 * <p>Note this will include all property paths (of data fields) that were rendered as part of the
307 * last full lifecycle and any component refreshes since then. It will not contain all paths of a view
308 * (which would include all pages)</p>
309 *
310 * @return set of property paths as strings
311 */
312 public Set<String> getAllDataFieldPropertyPaths() {
313 return allDataFieldPropertyPaths;
314 }
315
316 /**
317 * @see ViewPostMetadata#getAllDataFieldPropertyPaths()
318 */
319 public void setAllDataFieldPropertyPaths(Set<String> allDataFieldPropertyPaths) {
320 this.allDataFieldPropertyPaths = Collections.synchronizedSet(new HashSet<String>(allDataFieldPropertyPaths));
321 }
322
323 /**
324 * Adds a property path to the list of data field property paths.
325 *
326 * @param propertyPath property path to add
327 * @see ViewPostMetadata#getAllDataFieldPropertyPaths()
328 */
329 public void addDataFieldPropertyPath(String propertyPath) {
330 if (this.allDataFieldPropertyPaths == null) {
331 this.allDataFieldPropertyPaths = Collections.synchronizedSet(new HashSet<String>());
332 }
333
334 this.allDataFieldPropertyPaths.add(propertyPath);
335 }
336
337 /**
338 * The collection objects that were added during the current controller call, these will be emptied after
339 * the lifecycle process is run.
340 *
341 * <p>Note: If a list is empty this means that a collection had an addLine call occur and a new line must
342 * be initialized for the collection.</p>
343 *
344 * @return the collection objects that were added during the current controller call if added through a process
345 * other than the collection's own addLine call
346 * @see org.kuali.rice.krad.uif.container.CollectionGroupBase
347 */
348 public Map<String, List<Object>> getAddedCollectionObjects() {
349 return addedCollectionObjects;
350 }
351
352 /**
353 * @see ViewPostMetadata#getAddedCollectionObjects()
354 */
355 public void setAddedCollectionObjects(Map<String, List<Object>> addedCollectionObjects) {
356 this.addedCollectionObjects = addedCollectionObjects;
357 }
358
359 /**
360 * Set of property paths from the view that will allow binding to (by default).
361 *
362 * <p>Used by the UIF web infrastructure to provide security during the binding process. By default, binding
363 * will only occur for properties within the view configuration (for properties that allow updating).</p>
364 *
365 * @return Set of property paths
366 */
367 public Set<String> getAccessibleBindingPaths() {
368 return accessibleBindingPaths;
369 }
370
371 /**
372 * @see ViewPostMetadata#getAccessibleBindingPaths()
373 */
374 public void setAccessibleBindingPaths(Set<String> accessibleBindingPaths) {
375 this.accessibleBindingPaths = accessibleBindingPaths;
376 }
377
378 /**
379 * Adds a path to the set of accessible binding paths.
380 *
381 * @param accessibleBindingPath path to add as accessible
382 * @see ViewPostMetadata#getAccessibleBindingPaths()
383 */
384 public void addAccessibleBindingPath(String accessibleBindingPath) {
385 if (this.accessibleBindingPaths == null) {
386 this.accessibleBindingPaths = Collections.synchronizedSet(new HashSet<String>());
387 }
388
389 this.accessibleBindingPaths.add(accessibleBindingPath);
390 }
391
392 /**
393 * Set of method to calls configured within the view that access should be allowed for.
394 *
395 * <p>Used by the UIF web infrastructure to provide security for invoking controller methods. By default,
396 * only methods within the view configuration can be called.</p>
397 *
398 * @return Set of method names
399 */
400 public Set<String> getAccessibleMethodToCalls() {
401 return accessibleMethodToCalls;
402 }
403
404 /**
405 * @see ViewPostMetadata#getAccessibleMethodToCalls()
406 */
407 public void setAccessibleMethodToCalls(Set<String> accessibleMethodToCalls) {
408 this.accessibleMethodToCalls = accessibleMethodToCalls;
409 }
410
411 /**
412 * Adds a method to the set of accessible controller methods.
413 *
414 * @param methodToCall method to add as accessible
415 * @see ViewPostMetadata#getAccessibleMethodToCalls()
416 */
417 public void addAccessibleMethodToCall(String methodToCall) {
418 if (this.accessibleMethodToCalls == null) {
419 this.accessibleMethodToCalls = Collections.synchronizedSet(new HashSet<String>());
420 }
421
422 this.accessibleMethodToCalls.add(methodToCall);
423 }
424 }