View Javadoc
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 }