001/** 002 * Copyright 2005-2018 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.util; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.krad.datadictionary.parse.BeanTag; 020import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 021import org.kuali.rice.krad.datadictionary.parse.BeanTags; 022import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase; 023import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 024import org.kuali.rice.krad.uif.UifConstants; 025import org.kuali.rice.krad.uif.component.Component; 026import org.kuali.rice.krad.uif.container.PageGroup; 027import org.kuali.rice.krad.uif.element.BreadcrumbItem; 028import org.kuali.rice.krad.uif.element.Header; 029import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle; 030import org.kuali.rice.krad.uif.view.ExpressionEvaluator; 031import org.kuali.rice.krad.uif.view.View; 032 033import java.io.Serializable; 034import java.util.ArrayList; 035import java.util.List; 036import java.util.Map; 037 038/** 039 * ParentLocation is used to provide automatic generation/determination of Views/Pages that occur before the current 040 * View. Essentially, this class provides a way to determine a conceptual hierarchy of view/page locations. 041 * This information is used internally to generate BreadcrumbItems that can appear before the View's breadcrumbs. 042 */ 043@BeanTag(name = "parentLocation", parent = "Uif-ParentLocation") 044public class ParentLocation extends UifDictionaryBeanBase implements Serializable { 045 046 private static final long serialVersionUID = -6242148809697931126L; 047 //private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ParentLocation.class); 048 049 private UrlInfo parentViewUrl; 050 private UrlInfo parentPageUrl; 051 private String parentViewLabel; 052 private String parentPageLabel; 053 054 private BreadcrumbItem viewBreadcrumbItem; 055 private BreadcrumbItem pageBreadcrumbItem; 056 protected List<BreadcrumbItem> resolvedBreadcrumbItems = new ArrayList<BreadcrumbItem>(); 057 058 /** 059 * Construct the parent location breadcrumbItems that represent all the parent views/pages configured through 060 * parentLocation by traversing through each view by id referenced in parentViewUrl in a chain recursively. A url 061 * which is not using viewId and instead set the href explicitly ends the chain. 062 * 063 * @param view the current view being processed 064 * @param currentModel the currentModel 065 * @param currentContext the currentContext 066 * @return list of breadcrumbItems (the final list is set into the top most View's 067 * parentLocation.resolvedBreadcrumbItems) 068 */ 069 public List<BreadcrumbItem> constructParentLocationBreadcrumbItems(View view, Object currentModel, 070 Map<String, Object> currentContext) { 071 //viewBreadcrumbItem must already have an object initialized 072 if (viewBreadcrumbItem == null) { 073 return resolvedBreadcrumbItems; 074 } 075 076 //evaluate expressions on relevant content before comparisons 077 this.handleExpressions(view, currentModel, currentContext, ViewLifecycle.getExpressionEvaluator()); 078 079 //set url values into breadcrumb objects 080 if (StringUtils.isNotBlank(parentViewUrl.getOriginalHref()) || (StringUtils.isNotBlank( 081 parentViewUrl.getViewId()) && StringUtils.isNotBlank(parentViewUrl.getControllerMapping()))) { 082 viewBreadcrumbItem.setUrl(parentViewUrl); 083 viewBreadcrumbItem.setLabel(parentViewLabel); 084 } 085 086 if (StringUtils.isNotBlank(parentPageUrl.getOriginalHref()) || (StringUtils.isNotBlank( 087 parentPageUrl.getViewId()) && StringUtils.isNotBlank(parentPageUrl.getControllerMapping()))) { 088 pageBreadcrumbItem.setUrl(parentPageUrl); 089 pageBreadcrumbItem.setLabel(parentPageLabel); 090 } 091 092 //only continue if either href or viewId are explicitly set (check for validity of parent url) 093 if (viewBreadcrumbItem.getUrl() == null || StringUtils.isBlank(viewBreadcrumbItem.getUrl().getOriginalHref()) 094 && StringUtils.isBlank(viewBreadcrumbItem.getUrl().getViewId())) { 095 return resolvedBreadcrumbItems; 096 } 097 098 String parentViewId = viewBreadcrumbItem.getUrl().getViewId(); 099 String controllerMapping = viewBreadcrumbItem.getUrl().getControllerMapping(); 100 101 View parentView = null; 102 //chaining is only allowed when the controllerMapping and viewId are explicitly set 103 if (viewBreadcrumbItem.getUrl() != null && StringUtils.isNotBlank(controllerMapping) && StringUtils.isNotBlank( 104 parentViewId) && StringUtils.isBlank(viewBreadcrumbItem.getUrl().getOriginalHref())) { 105 parentView = KRADServiceLocatorWeb.getDataDictionaryService().getViewById(parentViewId); 106 } 107 108 //only do this processing if the parentView is not null (viewId was set on viewBreadcrumbItem to a valid View) 109 if (parentView != null) { 110 processParentViewDerivedContent(parentView, parentViewId, view, currentModel, currentContext); 111 } 112 113 //add parent view breadcrumb 114 if (StringUtils.isNotEmpty(viewBreadcrumbItem.getLabel())) { 115 resolvedBreadcrumbItems.add(viewBreadcrumbItem); 116 } 117 118 //add parent page breadcrumb 119 if (pageBreadcrumbItem != null && StringUtils.isNotEmpty(pageBreadcrumbItem.getLabel())) { 120 resolvedBreadcrumbItems.add(pageBreadcrumbItem); 121 } 122 123 return resolvedBreadcrumbItems; 124 } 125 126 /** 127 * Processes content that can only be derived by looking at the parentView for a parentLocation, such as 128 * expressions 129 * and sibling breadcrumb content; evaluates and adds them to the ParentLocation BreadcrumbItem(s). 130 * 131 * @param parentView the parentView to derive breadcrumb content from 132 * @param parentViewId the parentView's id 133 * @param currentView the currentView (the view this parentLocation is on) 134 * @param currentModel the current model data 135 * @param currentContext the current context to evaluate expressions against 136 */ 137 private void processParentViewDerivedContent(View parentView, String parentViewId, View currentView, 138 Object currentModel, Map<String, Object> currentContext) { 139 //populate expression graph 140 ViewLifecycle.getExpressionEvaluator().populatePropertyExpressionsFromGraph(parentView, false); 141 142 //chain parent locations if not null on parent 143 if (((View) parentView).getParentLocation() != null) { 144 resolvedBreadcrumbItems.addAll( 145 ((View) parentView).getParentLocation().constructParentLocationBreadcrumbItems(parentView, 146 currentModel, currentContext)); 147 } 148 149 handleLabelExpressions(parentView, currentModel, currentContext, ViewLifecycle.getExpressionEvaluator()); 150 151 //label automation, if parent has a label for its breadcrumb and one is not set here use that value 152 //it is assumed that if the label contains a SpringEL expression, those properties are available on the 153 //current form by the same name 154 if (StringUtils.isBlank(viewBreadcrumbItem.getLabel()) && parentView.getBreadcrumbItem() != null && 155 StringUtils.isNotBlank(parentView.getBreadcrumbItem().getLabel())) { 156 viewBreadcrumbItem.setLabel(parentView.getBreadcrumbItem().getLabel()); 157 } else if (StringUtils.isBlank(viewBreadcrumbItem.getLabel()) && StringUtils.isNotBlank( 158 parentView.getHeaderText())) { 159 viewBreadcrumbItem.setLabel(parentView.getHeaderText()); 160 } 161 162 //siblingBreadcrumb inheritance automation 163 if (parentView.getBreadcrumbItem() != null 164 && parentView.getBreadcrumbItem().getSiblingBreadcrumbComponent() != null 165 && viewBreadcrumbItem.getSiblingBreadcrumbComponent() == null) { 166 viewBreadcrumbItem.setSiblingBreadcrumbComponent( 167 parentView.getBreadcrumbItem().getSiblingBreadcrumbComponent()); 168 } 169 170 //page breadcrumb label automation, page must be a page of the view breadcrumb 171 if (pageBreadcrumbItem != null && StringUtils.isNotBlank(pageBreadcrumbItem.getUrl().getPageId()) && StringUtils 172 .isNotBlank(pageBreadcrumbItem.getUrl().getViewId()) && pageBreadcrumbItem.getUrl().getViewId().equals( 173 parentViewId)) { 174 handlePageBreadcrumb(parentView, currentModel); 175 } 176 } 177 178 /** 179 * Evaluates the expressions on properties that may be determine the value of the label used on generated view and 180 * page breadcrumbItems (if a label was not explicitly set) 181 * 182 * @param parentView the parentView 183 * @param currentModel the currentModel 184 * @param currentContext the currentContext 185 * @param expressionEvaluator instance of expression evaluator for the current view 186 */ 187 private void handleLabelExpressions(View parentView, Object currentModel, Map<String, Object> currentContext, 188 ExpressionEvaluator expressionEvaluator) { 189 try { 190 Header header = parentView.getHeader(); 191 192 if (header != null) { 193 if (StringUtils.isNotBlank(parentView.getPropertyExpressions().get( 194 UifConstants.ComponentProperties.HEADER_TEXT))) { 195 header.getPropertyExpressions().put(UifConstants.ComponentProperties.HEADER_TEXT, 196 parentView.getPropertyExpressions().get(UifConstants.ComponentProperties.HEADER_TEXT)); 197 } 198 199 expressionEvaluator.evaluateExpressionsOnConfigurable(parentView, header, currentContext); 200 } 201 202 BreadcrumbItem breadcrumbItem = parentView.getBreadcrumbItem(); 203 204 if (breadcrumbItem != null) { 205 expressionEvaluator.evaluateExpressionsOnConfigurable(parentView, breadcrumbItem, currentContext); 206 } 207 208 if (pageBreadcrumbItem != null && pageBreadcrumbItem.getUrl() != null && StringUtils.isNotBlank( 209 pageBreadcrumbItem.getUrl().getPageId())) { 210 PageGroup thePage = null; 211 if (parentView.isSinglePageView() && parentView.getPage() != null) { 212 thePage = parentView.getPage(); 213 } else { 214 for (Component item : parentView.getItems()) { 215 if (item.getId().equals(pageBreadcrumbItem.getUrl().getPageId())) { 216 thePage = (PageGroup) item; 217 break; 218 } 219 } 220 } 221 222 if (thePage == null) { 223 //TODO throw error 224 return; 225 } 226 227 //populate from expression graph 228 ViewLifecycle.getExpressionEvaluator().populatePropertyExpressionsFromGraph(thePage, false); 229 230 Header pageHeader = thePage.getHeader(); 231 232 if (pageHeader != null) { 233 if (StringUtils.isNotBlank(thePage.getPropertyExpressions().get( 234 UifConstants.ComponentProperties.HEADER_TEXT))) { 235 pageHeader.getPropertyExpressions().put(UifConstants.ComponentProperties.HEADER_TEXT, 236 thePage.getPropertyExpressions().get(UifConstants.ComponentProperties.HEADER_TEXT)); 237 } 238 239 expressionEvaluator.evaluateExpressionsOnConfigurable(parentView, pageHeader, currentContext); 240 } 241 242 BreadcrumbItem pageBreadcrumb = thePage.getBreadcrumbItem(); 243 244 if (pageBreadcrumb != null) { 245 expressionEvaluator.evaluateExpressionsOnConfigurable(parentView, pageBreadcrumb, currentContext); 246 } 247 } 248 } catch (RuntimeException e) { 249 throw new RuntimeException("There was likely a problem evaluating an expression in a parent view or page" 250 + " because a property may not exist in the current context - explicitly set the label for this" 251 + " parentLocation: " 252 + parentView.getId(), e); 253 } 254 } 255 256 /** 257 * Evaluate any expressions that may have not been evaluated for the urls and breadcrumbItems of this 258 * parentLocation 259 * class using the currentModel and currentContext 260 * 261 * @param view the view 262 * @param currentModel the current model 263 * @param currentContext the current context 264 * @param expressionEvaluator instance of expression evaluator for the current view 265 */ 266 private void handleExpressions(View view, Object currentModel, Map<String, Object> currentContext, 267 ExpressionEvaluator expressionEvaluator) { 268 try { 269 // KULRICE-10053 initialize the expression evaluator 270 expressionEvaluator.initializeEvaluationContext(currentModel); 271 272 //Evaluate view url/breadcrumb expressions 273 expressionEvaluator.evaluateExpressionsOnConfigurable(view, viewBreadcrumbItem, currentContext); 274 275 if (viewBreadcrumbItem.getUrl() != null) { 276 expressionEvaluator.evaluateExpressionsOnConfigurable(view, viewBreadcrumbItem.getUrl(), 277 currentContext); 278 } 279 280 if (parentViewUrl != null) { 281 expressionEvaluator.evaluateExpressionsOnConfigurable(view, parentViewUrl, currentContext); 282 } 283 284 //evaluate same for potential page properties 285 if (pageBreadcrumbItem != null) { 286 expressionEvaluator.evaluateExpressionsOnConfigurable(view, pageBreadcrumbItem, currentContext); 287 288 if (pageBreadcrumbItem.getUrl() != null) { 289 expressionEvaluator.evaluateExpressionsOnConfigurable(view, pageBreadcrumbItem.getUrl(), 290 currentContext); 291 } 292 } 293 294 if (parentPageUrl != null) { 295 expressionEvaluator.evaluateExpressionsOnConfigurable(view, parentPageUrl, currentContext); 296 } 297 } catch (RuntimeException e) { 298 throw new RuntimeException("There was likely a problem evaluating an expression in a parent view or page" 299 + " because a property may not exist in the current context - problem in Url or BreadcrumbItem" 300 + " - set these to something that can be evaluated - of the parentLocation: " 301 + view.getId(), e); 302 } 303 } 304 305 /** 306 * Handle setting a page breadcrumbItem label when parentPageUrl is being used based on the PageGroup's 307 * breadcrumbItem and header properties 308 * 309 * @param view the current view 310 */ 311 private void handlePageBreadcrumb(View view, Object currentModel) { 312 PageGroup thePage = null; 313 if (view.isSinglePageView() && view.getPage() != null) { 314 thePage = view.getPage(); 315 } else { 316 for (Component item : view.getItems()) { 317 if (item.getId().equals(pageBreadcrumbItem.getUrl().getPageId())) { 318 thePage = (PageGroup) item; 319 break; 320 } 321 } 322 } 323 324 if (thePage == null) { 325 return; 326 } 327 328 //set label 329 if (StringUtils.isBlank(pageBreadcrumbItem.getLabel()) && thePage.getBreadcrumbItem() != null && 330 StringUtils.isNotBlank(thePage.getBreadcrumbItem().getLabel())) { 331 pageBreadcrumbItem.setLabel(thePage.getBreadcrumbItem().getLabel()); 332 } else if (StringUtils.isBlank(pageBreadcrumbItem.getLabel()) && StringUtils.isNotBlank( 333 thePage.getHeaderText())) { 334 pageBreadcrumbItem.setLabel(thePage.getHeaderText()); 335 } 336 337 //page siblingBreadcrumb inheritance automation 338 if (thePage.getBreadcrumbItem() != null 339 && thePage.getBreadcrumbItem().getSiblingBreadcrumbComponent() != null 340 && pageBreadcrumbItem.getSiblingBreadcrumbComponent() == null) { 341 pageBreadcrumbItem.setSiblingBreadcrumbComponent( 342 thePage.getBreadcrumbItem().getSiblingBreadcrumbComponent()); 343 } 344 } 345 346 /** 347 * The parentViewUrl representing the url that is the parent of this View. 348 * 349 * <p> 350 * This url can explicitly set an href 351 * or can set a controller and viewId. Parent view traversal is only performed if the controller and viewId 352 * properties are set and NOT the explicit href (this affects if breadcrumbs are generated in a recursive chain). 353 * </p> 354 * 355 * @return the parent view url 356 */ 357 @BeanTagAttribute(name = "parentViewUrl", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 358 public UrlInfo getParentViewUrl() { 359 return parentViewUrl; 360 } 361 362 /** 363 * Set the parentViewUrl 364 * 365 * @param parentViewUrl 366 */ 367 public void setParentViewUrl(UrlInfo parentViewUrl) { 368 this.parentViewUrl = parentViewUrl; 369 } 370 371 /** 372 * The parentPageUrl representing a page url that is the parent of this View. In order for automated label 373 * determination to work for the page breadcrumbItem, the viewId and controllerMapping must match with the 374 * parentViewUrl. 375 * 376 * <p> 377 * This url can explicitly set an href or can set a pageId. The parentViewUrl MUST be set before this option can 378 * be set. If the needed behavior is such that the parent view breadcrumbItem should not be shown and only this 379 * item should be shown, set 'parentLocation.viewBreadcrumbItem.render' to false. 380 * </p> 381 * 382 * @return the parent page url 383 */ 384 @BeanTagAttribute(name = "parentPageUrl", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 385 public UrlInfo getParentPageUrl() { 386 return parentPageUrl; 387 } 388 389 /** 390 * Set the parentPageUrl 391 * 392 * @param parentPageUrl 393 */ 394 public void setParentPageUrl(UrlInfo parentPageUrl) { 395 this.parentPageUrl = parentPageUrl; 396 } 397 398 /** 399 * The parentViewLabel is the text used for breadcrumbItem label of the parent view. 400 * 401 * <p> 402 * If not set, the the label is determined 403 * by looking at the parent View's breadcrumbItem and then its headerText. If the parent view's retrieved value 404 * contain expressions, those expressions must be able to be evaluated in the current context (ie, the properties 405 * they reference must also exist on the current form at the same location) or an exception will be thrown. 406 * </p> 407 * 408 * @return the parentViewLabel set 409 */ 410 @BeanTagAttribute(name = "parentViewLabel") 411 public String getParentViewLabel() { 412 return parentViewLabel; 413 } 414 415 /** 416 * Set the parentViewLabel 417 * 418 * @param parentViewLabel 419 */ 420 public void setParentViewLabel(String parentViewLabel) { 421 this.parentViewLabel = parentViewLabel; 422 } 423 424 /** 425 * The parentPageLabel is the text used for breadcrumbItem label of the parent page. 426 * 427 * <p> 428 * If not set, the the label is determined 429 * by looking at the parent PageGroup's breadcrumbItem and then its headerText. This retrieval can only happen 430 * if the parentViewUrl is set. 431 * If the parent PageGroup's retrieved value 432 * contain expressions, those expressions must be able to be evaluated in the current context (ie, the properties 433 * they reference must also exist on the current form at the same location) or an exception will be thrown. 434 * </p> 435 * 436 * @return the parentPageLabel set 437 */ 438 @BeanTagAttribute(name = "parentPageLabel") 439 public String getParentPageLabel() { 440 return parentPageLabel; 441 } 442 443 /** 444 * Set the parentPageLabel 445 * 446 * @param parentPageLabel 447 */ 448 public void setParentPageLabel(String parentPageLabel) { 449 this.parentPageLabel = parentPageLabel; 450 } 451 452 /** 453 * The viewBreadcrumbItem to use for the parent location view breadcrumb. Url should NOT be set here because 454 * parentViewUrl is ALWAYS set into this breadcrumbItem, regardless of value. 455 * 456 * @return the viewBreadcrumbItem 457 */ 458 @BeanTagAttribute(name = "viewBreadcrumbItem", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 459 public BreadcrumbItem getViewBreadcrumbItem() { 460 return viewBreadcrumbItem; 461 } 462 463 /** 464 * Set the viewBreadcrumbItem 465 * 466 * @param breadcrumbItem 467 */ 468 public void setViewBreadcrumbItem(BreadcrumbItem breadcrumbItem) { 469 this.viewBreadcrumbItem = breadcrumbItem; 470 } 471 472 /** 473 * The pageBreadcrumbItem to use for the parent location view breadcrumb. Url should NOT be set here because 474 * parentPageUrl is ALWAYS set into this breadcrumbItem, regardless of value. 475 * 476 * @return the pageBreadcrumbItem 477 */ 478 @BeanTagAttribute(name = "pageBreadcrumbItem", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 479 public BreadcrumbItem getPageBreadcrumbItem() { 480 return pageBreadcrumbItem; 481 } 482 483 /** 484 * Set the pageBreadcrumbItem 485 * 486 * @param pageBreadcrumbItem 487 */ 488 public void setPageBreadcrumbItem(BreadcrumbItem pageBreadcrumbItem) { 489 this.pageBreadcrumbItem = pageBreadcrumbItem; 490 } 491 492 /** 493 * The resolved/generated breadcrumbItems determined by traversing the parentLocation chain. These cannot be set 494 * and must be generated by calling constructParentLocationBreadcrumbItems. 495 * 496 * @return the resolved breadcrumbItem list 497 */ 498 public List<BreadcrumbItem> getResolvedBreadcrumbItems() { 499 return resolvedBreadcrumbItems; 500 } 501 502}