001/** 002 * Copyright 2005-2016 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.lifecycle; 017 018import org.apache.log4j.Logger; 019import org.kuali.rice.krad.uif.component.Component; 020import org.kuali.rice.krad.web.bind.UifEncryptionPropertyEditorWrapper; 021 022import java.beans.PropertyEditor; 023import java.io.Serializable; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031 032/** 033 * Holds data about the rendered view that might be needed to handle a post request. 034 * 035 * <p>When an action is requested on a view (for example add/delete line, field query, so on), it might be 036 * necessary to read configuration from the view that was rendered to cary out the action. However, the rendered 037 * view is not stored, and the new view is not rendered until after the controller completes. Therefore it is 038 * necessary to provide this mechanism.</p> 039 * 040 * <p>The post metadata is retrieved in the controller though the {@link org.kuali.rice.krad.web.form.UifFormBase} 041 * instance</p> 042 * 043 * @author Kuali Rice Team (rice.collab@kuali.org) 044 */ 045public class ViewPostMetadata implements Serializable { 046 047 private static final long serialVersionUID = -515221881981451818L; 048 private static final Logger LOG = Logger.getLogger(ViewPostMetadata.class); 049 050 private String id; 051 052 private Map<String, ComponentPostMetadata> componentPostMetadataMap; 053 054 private Map<String, PropertyEditor> fieldPropertyEditors; 055 private Map<String, PropertyEditor> secureFieldPropertyEditors; 056 057 private Set<String> inputFieldIds; 058 private Set<String> allRenderedPropertyPaths; 059 private Map<String, List<Object>> addedCollectionObjects; 060 061 private Map<String, Map<String, Object>> lookupCriteria; 062 063 private Set<String> accessibleBindingPaths; 064 private Set<String> accessibleMethodToCalls; 065 private Set<String> availableMethodToCalls; 066 067 /** 068 * Default constructor. 069 */ 070 public ViewPostMetadata() { 071 fieldPropertyEditors = Collections.synchronizedMap(new HashMap<String, PropertyEditor>()); 072 secureFieldPropertyEditors = Collections.synchronizedMap(new HashMap<String, PropertyEditor>()); 073 inputFieldIds = Collections.synchronizedSet(new HashSet<String>()); 074 allRenderedPropertyPaths = Collections.synchronizedSet(new HashSet<String>()); 075 addedCollectionObjects = Collections.synchronizedMap(new HashMap<String, List<Object>>()); 076 lookupCriteria = Collections.synchronizedMap(new HashMap<String, Map<String, Object>>()); 077 accessibleBindingPaths = Collections.synchronizedSet(new HashSet<String>()); 078 accessibleMethodToCalls = Collections.synchronizedSet(new HashSet<String>()); 079 availableMethodToCalls = Collections.synchronizedSet(new HashSet<String>()); 080 } 081 082 /** 083 * Constructor that takes the view id. 084 * 085 * @param id id for the view 086 */ 087 public ViewPostMetadata(String id) { 088 this(); 089 090 this.id = id; 091 } 092 093 /** 094 * Invoked after the lifecycle is complete to perform an necessary cleaning. 095 */ 096 public void cleanAfterLifecycle() { 097 allRenderedPropertyPaths = Collections.synchronizedSet(new HashSet<String>()); 098 addedCollectionObjects = Collections.synchronizedMap(new HashMap<String, List<Object>>()); 099 } 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}