1 /**
2 * Copyright 2005-2015 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.log4j.Logger;
19 import org.kuali.rice.krad.uif.component.Component;
20 import org.kuali.rice.krad.web.bind.UifEncryptionPropertyEditorWrapper;
21
22 import java.beans.PropertyEditor;
23 import java.io.Serializable;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31
32 /**
33 * Holds data about the rendered view that might be needed to handle a post request.
34 *
35 * <p>When an action is requested on a view (for example add/delete line, field query, so on), it might be
36 * necessary to read configuration from the view that was rendered to cary out the action. However, the rendered
37 * view is not stored, and the new view is not rendered until after the controller completes. Therefore it is
38 * necessary to provide this mechanism.</p>
39 *
40 * <p>The post metadata is retrieved in the controller though the {@link org.kuali.rice.krad.web.form.UifFormBase}
41 * instance</p>
42 *
43 * @author Kuali Rice Team (rice.collab@kuali.org)
44 */
45 public class ViewPostMetadata implements Serializable {
46
47 private static final long serialVersionUID = -515221881981451818L;
48 private static final Logger LOG = Logger.getLogger(ViewPostMetadata.class);
49
50 private String id;
51
52 private Map<String, ComponentPostMetadata> componentPostMetadataMap;
53
54 private Map<String, PropertyEditor> fieldPropertyEditors;
55 private Map<String, PropertyEditor> secureFieldPropertyEditors;
56
57 private Set<String> inputFieldIds;
58 private Set<String> allRenderedPropertyPaths;
59 private Map<String, List<Object>> addedCollectionObjects;
60
61 private Map<String, Map<String, Object>> lookupCriteria;
62
63 private Set<String> accessibleBindingPaths;
64 private Set<String> accessibleMethodToCalls;
65 private Set<String> availableMethodToCalls;
66
67 /**
68 * Default constructor.
69 */
70 public ViewPostMetadata() {
71 fieldPropertyEditors = Collections.synchronizedMap(new HashMap<String, PropertyEditor>());
72 secureFieldPropertyEditors = Collections.synchronizedMap(new HashMap<String, PropertyEditor>());
73 inputFieldIds = Collections.synchronizedSet(new HashSet<String>());
74 allRenderedPropertyPaths = Collections.synchronizedSet(new HashSet<String>());
75 addedCollectionObjects = Collections.synchronizedMap(new HashMap<String, List<Object>>());
76 lookupCriteria = Collections.synchronizedMap(new HashMap<String, Map<String, Object>>());
77 accessibleBindingPaths = Collections.synchronizedSet(new HashSet<String>());
78 accessibleMethodToCalls = Collections.synchronizedSet(new HashSet<String>());
79 availableMethodToCalls = Collections.synchronizedSet(new HashSet<String>());
80 }
81
82 /**
83 * Constructor that takes the view id.
84 *
85 * @param id id for the view
86 */
87 public ViewPostMetadata(String id) {
88 this();
89
90 this.id = id;
91 }
92
93 /**
94 * Invoked after the lifecycle is complete to perform an necessary cleaning.
95 */
96 public void cleanAfterLifecycle() {
97 allRenderedPropertyPaths = Collections.synchronizedSet(new HashSet<String>());
98 addedCollectionObjects = Collections.synchronizedMap(new HashMap<String, List<Object>>());
99 }
100
101 /**
102 * Id for the view the post metadata is associated with.
103 *
104 * @return view id
105 */
106 public String getId() {
107 return id;
108 }
109
110 /**
111 * @see ViewPostMetadata#getId()
112 */
113 public void setId(String id) {
114 this.id = id;
115 }
116
117 /**
118 * Map containing post metadata for a component keyed by the component id.
119 *
120 * @return post metadata map, key is component id and value is post metadata
121 */
122 public Map<String, ComponentPostMetadata> getComponentPostMetadataMap() {
123 return componentPostMetadataMap;
124 }
125
126 /**
127 * @see ViewPostMetadata#getComponentPostMetadataMap()
128 */
129 public void setComponentPostMetadataMap(Map<String, ComponentPostMetadata> componentPostMetadataMap) {
130 this.componentPostMetadataMap = componentPostMetadataMap;
131 }
132
133 /**
134 * Gets the component post metadata for the given component id.
135 *
136 * @param componentId id for the component whose post metadata should be retrieved
137 * @return post metadata object
138 */
139 public ComponentPostMetadata getComponentPostMetadata(String componentId) {
140 ComponentPostMetadata componentPostMetadata = null;
141
142 if (componentPostMetadataMap != null && (componentPostMetadataMap.containsKey(componentId))) {
143 componentPostMetadata = componentPostMetadataMap.get(componentId);
144 }
145
146 return componentPostMetadata;
147 }
148
149 /**
150 * Adds post data for the given component (this is a convenience method for add component post metadata).
151 *
152 * @param component component instance the data should be added for
153 * @param key key for the post data, this will be used to retrieve the value
154 * @param value value for the post data
155 */
156 public void addComponentPostData(Component component, String key, Object value) {
157 if (component == null) {
158 throw new IllegalArgumentException("Component must not be null for adding post data");
159 }
160
161 addComponentPostData(component.getId(), key, value);
162 }
163
164 /**
165 * Adds post data for the given component id (this is a convenience method for add component post metadata).
166 *
167 * @param componentId id for the component the data should be added for
168 * @param key key for the post data, this will be used to retrieve the value
169 * @param value value for the post data
170 */
171 public void addComponentPostData(String componentId, String key, Object value) {
172 if (value == null) {
173 return;
174 }
175
176 ComponentPostMetadata componentPostMetadata = initializeComponentPostMetadata(componentId);
177
178 componentPostMetadata.addData(key, value);
179 }
180
181 /**
182 * Retrieves post data that has been stored for the given component id and key.
183 *
184 * @param componentId id for the component the data should be retrieved for
185 * @param key key for the post data to retrieve
186 * @return value for the data, or null if the data does not exist
187 */
188 public Object getComponentPostData(String componentId, String key) {
189 ComponentPostMetadata componentPostMetadata = getComponentPostMetadata(componentId);
190
191 if (componentPostMetadata != null) {
192 return componentPostMetadata.getData(key);
193 }
194
195 return null;
196 }
197
198 /**
199 * Initializes a component post metadata instance for the given component.
200 *
201 * @param component component instance to initialize post metadata for
202 * @return post metadata instance
203 */
204 public ComponentPostMetadata initializeComponentPostMetadata(Component component) {
205 if (component == null) {
206 throw new IllegalArgumentException("Component must not be null to initialize post metadata");
207 }
208
209 return initializeComponentPostMetadata(component.getId());
210 }
211
212 /**
213 * Initializes a component post metadata instance for the given component id.
214 *
215 * @param componentId id for the component to initialize post metadata for
216 * @return post metadata instance
217 */
218 public ComponentPostMetadata initializeComponentPostMetadata(String componentId) {
219 if (componentPostMetadataMap == null) {
220 synchronized (this) {
221 if (componentPostMetadataMap == null) {
222 componentPostMetadataMap = new HashMap<String, ComponentPostMetadata>();
223 }
224 }
225 }
226
227 ComponentPostMetadata componentPostMetadata;
228 if (componentPostMetadataMap.containsKey(componentId)) {
229 componentPostMetadata = componentPostMetadataMap.get(componentId);
230 } else {
231 synchronized (componentPostMetadataMap) {
232 componentPostMetadata = new ComponentPostMetadata(componentId);
233 componentPostMetadataMap.put(componentId, componentPostMetadata);
234 }
235 }
236
237 return componentPostMetadata;
238 }
239
240 /**
241 * Iterates over the componentPostMetadataMap to find a componentPostMetadata which matches the path given.
242 *
243 * @param path the path of the component to find componentPostMetadata for
244 * @return returns the componentPostMetadata that matches the path, null if not found or metadata for path not found
245 */
246 public ComponentPostMetadata getComponentPostMetadataForPath(String path) {
247 if (componentPostMetadataMap == null) {
248 return null;
249 }
250
251 Collection<ComponentPostMetadata> componentPostMetadataCollection = componentPostMetadataMap.values();
252
253 for (ComponentPostMetadata metadata : componentPostMetadataCollection) {
254 if (metadata.getPath() != null && metadata.getPath().equals(path)) {
255 return metadata;
256 }
257 }
258
259 return null;
260 }
261
262 /**
263 * Maintains configuration of properties that have been configured for the view (if render was
264 * set to true) and there corresponding PropertyEdtior (if configured).
265 *
266 * <p>Information is pulled out of the View during the lifecycle so it can be used when a form post
267 * is done from the View. Note if a field is secure, it will be placed in the
268 * {@link #getSecureFieldPropertyEditors()} map instead</p>
269 *
270 * @return map of property path (full) to PropertyEditor
271 */
272 public Map<String, PropertyEditor> getFieldPropertyEditors() {
273 return fieldPropertyEditors;
274 }
275
276 /**
277 * Associates a property editor instance with the given property path.
278 *
279 * @param propertyPath path for the property the editor should be associated with
280 * @param propertyEditor editor instance to use when binding data for the property
281 */
282 public void addFieldPropertyEditor(String propertyPath, PropertyEditor propertyEditor) {
283 if (fieldPropertyEditors == null) {
284 fieldPropertyEditors = new HashMap<String, PropertyEditor>();
285 }
286
287 fieldPropertyEditors.put(propertyPath, propertyEditor);
288 }
289
290 /**
291 * Maintains configuration of secure properties that have been configured for the view (if
292 * render was set to true) and there corresponding PropertyEdtior (if configured).
293 *
294 * <p>Information is pulled out of the View during the lifecycle so it can be used when a form post
295 * is done from the View. Note if a field is non-secure, it will be placed in the
296 * {@link #getFieldPropertyEditors()} map instead</p>
297 *
298 * @return map of property path (full) to PropertyEditor
299 */
300 public Map<String, PropertyEditor> getSecureFieldPropertyEditors() {
301 return secureFieldPropertyEditors;
302 }
303
304 /**
305 * Associates a secure property editor instance with the given property path.
306 *
307 * @param propertyPath path for the property the editor should be associated with
308 * @param propertyEditor secure editor instance to use when binding data for the property
309 */
310 public void addSecureFieldPropertyEditor(String propertyPath, PropertyEditor propertyEditor) {
311 if (secureFieldPropertyEditors == null) {
312 secureFieldPropertyEditors = new HashMap<String, PropertyEditor>();
313 }
314
315 secureFieldPropertyEditors.put(propertyPath, propertyEditor);
316 }
317
318 /**
319 * Set of ids for all input fields rendered with the view.
320 *
321 * @return set of id strings
322 */
323 public Set<String> getInputFieldIds() {
324 return inputFieldIds;
325 }
326
327 /**
328 * @see ViewPostMetadata#getInputFieldIds()
329 */
330 public void setInputFieldIds(Set<String> inputFieldIds) {
331 this.inputFieldIds = inputFieldIds;
332 }
333
334 /**
335 * Set of property paths that have been rendered as part of the lifecycle.
336 *
337 * <p>Note this will include all property paths (of data fields) that were rendered as part of the
338 * last full lifecycle and any component refreshes since then. It will not contain all paths of a view
339 * (which would include all pages)</p>
340 *
341 * @return set of property paths as strings
342 */
343 public Set<String> getAllRenderedPropertyPaths() {
344 return allRenderedPropertyPaths;
345 }
346
347 /**
348 * @see ViewPostMetadata#getAllRenderedPropertyPaths()
349 */
350 public void setAllRenderedPropertyPaths(Set<String> allRenderedPropertyPaths) {
351 this.allRenderedPropertyPaths = allRenderedPropertyPaths;
352 }
353
354 /**
355 * Adds a property path to the list of rendered property paths.
356 *
357 * @param propertyPath property path to add
358 * @see ViewPostMetadata#getAllRenderedPropertyPaths()
359 */
360 public void addRenderedPropertyPath(String propertyPath) {
361 if (this.allRenderedPropertyPaths == null) {
362 this.allRenderedPropertyPaths = new HashSet<String>();
363 }
364
365 this.allRenderedPropertyPaths.add(propertyPath);
366 }
367
368 /**
369 * The collection objects that were added during the current controller call, these will be emptied after
370 * the lifecycle process is run.
371 *
372 * <p>Note: If a list is empty this means that a collection had an addLine call occur and a new line must
373 * be initialized for the collection.</p>
374 *
375 * @return the collection objects that were added during the current controller call if added through a process
376 * other than the collection's own addLine call
377 *
378 * @see org.kuali.rice.krad.uif.container.CollectionGroupBase
379 */
380 public Map<String, List<Object>> getAddedCollectionObjects() {
381 return addedCollectionObjects;
382 }
383
384 /**
385 * @see ViewPostMetadata#getAddedCollectionObjects()
386 */
387 public void setAddedCollectionObjects(Map<String, List<Object>> addedCollectionObjects) {
388 this.addedCollectionObjects = addedCollectionObjects;
389 }
390
391 /**
392 * Set the metadata of the lookup criteria.
393 *
394 * <p>
395 * The lookup criteria property name is the key of the map. The value is a map of criteria attributes as specified
396 * by {@link org.kuali.rice.krad.uif.UifConstants.LookupCriteriaPostMetadata}. Not all criteria attribute types
397 * need to be specified. A missing boolean attribute equals to false.
398 * </p>
399 */
400 public Map<String, Map<String, Object>> getLookupCriteria() {
401 return lookupCriteria;
402 }
403
404 /**
405 * @see ViewPostMetadata#getLookupCriteria()
406 */
407 public void setLookupCriteria(Map<String, Map<String, Object>> lookupCriteria) {
408 this.lookupCriteria = lookupCriteria;
409 }
410
411 /**
412 * Set of property paths from the view that will allow binding to (by default).
413 *
414 * <p>Used by the UIF web infrastructure to provide security during the binding process. By default, binding
415 * will only occur for properties within the view configuration (for properties that allow updating).</p>
416 *
417 * @return Set of property paths
418 */
419 public Set<String> getAccessibleBindingPaths() {
420 return accessibleBindingPaths;
421 }
422
423 /**
424 * @see ViewPostMetadata#getAccessibleBindingPaths()
425 */
426 public void setAccessibleBindingPaths(Set<String> accessibleBindingPaths) {
427 this.accessibleBindingPaths = accessibleBindingPaths;
428 }
429
430 /**
431 * Adds a path to the set of accessible binding paths.
432 *
433 * @param accessibleBindingPath path to add as accessible
434 * @see ViewPostMetadata#getAccessibleBindingPaths()
435 */
436 public void addAccessibleBindingPath(String accessibleBindingPath) {
437 if (this.accessibleBindingPaths == null) {
438 this.accessibleBindingPaths = Collections.synchronizedSet(new HashSet<String>());
439 }
440
441 this.accessibleBindingPaths.add(accessibleBindingPath);
442 }
443
444 /**
445 * Set of method to calls configured within the view that access should be allowed for.
446 *
447 * <p>Used by the UIF web infrastructure to provide security for invoking controller methods. By default,
448 * only methods within the view configuration can be called.</p>
449 *
450 * @return Set of method names
451 */
452 public Set<String> getAccessibleMethodToCalls() {
453 return accessibleMethodToCalls;
454 }
455
456 /**
457 * @see ViewPostMetadata#getAccessibleMethodToCalls()
458 */
459 public void setAccessibleMethodToCalls(Set<String> accessibleMethodToCalls) {
460 this.accessibleMethodToCalls = accessibleMethodToCalls;
461 }
462
463 /**
464 * Adds a method to the set of accessible controller methods.
465 *
466 * @param methodToCall method to add as accessible
467 * @see ViewPostMetadata#getAccessibleMethodToCalls()
468 */
469 public void addAccessibleMethodToCall(String methodToCall) {
470 if (this.accessibleMethodToCalls == null) {
471 this.accessibleMethodToCalls = Collections.synchronizedSet(new HashSet<String>());
472 }
473
474 this.accessibleMethodToCalls.add(methodToCall);
475 }
476
477 /**
478 * Set of available methods to call.
479 *
480 * <p>If a methodToCall belongs to the set of available methods to call, then binding will be allowed only if the
481 * methodToCall, either, has the @MethodAccessible notation, or, is listed as one of the accessible methods to
482 * call on the view.</p>
483 *
484 * @return Set of method names
485 */
486 public Set<String> getAvailableMethodToCalls() {
487 return availableMethodToCalls;
488 }
489
490 /**
491 * @see ViewPostMetadata#getAvailableMethodToCalls()
492 */
493 public void setAvailableMethodToCalls(Set<String> availableMethodToCalls) {
494 this.availableMethodToCalls = availableMethodToCalls;
495 }
496
497 /**
498 * Adds a method to the set of available controller methods.
499 *
500 * @param methodToCall method to add as accessible
501 * @see ViewPostMetadata#getAvailableMethodToCalls()
502 */
503 public void addAvailableMethodToCall(String methodToCall) {
504 if (this.availableMethodToCalls == null) {
505 this.availableMethodToCalls = Collections.synchronizedSet(new HashSet<String>());
506 }
507
508 this.availableMethodToCalls.add(methodToCall);
509 }
510
511 /**
512 * Look up a field editor.
513 *
514 * @param propertyName name of the property to find field and editor for
515 */
516 public PropertyEditor getFieldEditor(String propertyName) {
517 if (LOG.isDebugEnabled()) {
518 LOG.debug("Attempting to find property editor for property '" + propertyName + "'");
519 }
520
521 PropertyEditor propertyEditor = null;
522 boolean requiresEncryption = false;
523
524 if (fieldPropertyEditors != null && fieldPropertyEditors.containsKey(propertyName)) {
525 propertyEditor = fieldPropertyEditors.get(propertyName);
526 } else if (secureFieldPropertyEditors != null && secureFieldPropertyEditors.containsKey(propertyName)) {
527 propertyEditor = secureFieldPropertyEditors.get(propertyName);
528 requiresEncryption = true;
529 }
530
531 if (propertyEditor != null) {
532 if (LOG.isDebugEnabled()) {
533 LOG.debug(
534 "Registering custom editor for property path '" + propertyName + "' and property editor class '"
535 + propertyEditor.getClass().getName() + "'");
536 }
537
538 if (requiresEncryption) {
539 if (LOG.isDebugEnabled()) {
540 LOG.debug("Enabling encryption for custom editor '" + propertyName +
541 "' and property editor class '" + propertyEditor.getClass().getName() + "'");
542 }
543
544 return new UifEncryptionPropertyEditorWrapper(propertyEditor);
545 }
546 }
547
548 return propertyEditor;
549 }
550 }