View Javadoc

1   /**
2    * Copyright 2005-2013 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.util;
17  
18  import com.google.common.collect.Lists;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
21  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
22  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
23  import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase;
24  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
25  import org.kuali.rice.krad.uif.UifConstants;
26  import org.kuali.rice.krad.uif.component.Component;
27  import org.kuali.rice.krad.uif.container.PageGroup;
28  import org.kuali.rice.krad.uif.element.Header;
29  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
30  import org.kuali.rice.krad.uif.view.View;
31  
32  import java.io.Serializable;
33  import java.util.ArrayList;
34  import java.util.List;
35  import java.util.Map;
36  
37  /**
38   * ParentLocation is used to provide automatic generation/determination of Views/Pages that occur before the current
39   * View.  Essentially, this class provides a way to determine a conceptual hierarchy of view/page locations.
40   * This information is used internally to generate BreadcrumbItems that can appear before the View's breadcrumbs.
41   */
42  @BeanTags({@BeanTag(name = "parentLocation-bean", parent = "Uif-ParentLocation"),
43          @BeanTag(name = "ricePortalParentLocation", parent = "Uif-RicePortalParentLocation")})
44  public class ParentLocation extends UifDictionaryBeanBase implements Serializable {
45  
46      private static final long serialVersionUID = -6242148809697931126L;
47      //private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ParentLocation.class);
48  
49      private UrlInfo parentViewUrl;
50      private UrlInfo parentPageUrl;
51      private String parentViewLabel;
52      private String parentPageLabel;
53  
54      private BreadcrumbItem viewBreadcrumbItem;
55      private BreadcrumbItem pageBreadcrumbItem;
56      protected List<BreadcrumbItem> resolvedBreadcrumbItems = new ArrayList<BreadcrumbItem>();
57  
58      /**
59       * Construct the parent location breadcrumbItems that represent all the parent views/pages configured through
60       * parentLocation by traversing through each view by id referenced in parentViewUrl in a chain recursively.  A url
61       * which is not using viewId and instead set the href explicitly ends the chain.
62       *
63       * @param view the current view being processed
64       * @param currentModel the currentModel
65       * @param currentContext the currentContext
66       * @return list of breadcrumbItems (the final list is set into the top most View's
67       *         parentLocation.resolvedBreadcrumbItems)
68       */
69      public List<BreadcrumbItem> constructParentLocationBreadcrumbItems(View view, Object currentModel,
70              Map<String, Object> currentContext) {
71          //viewBreadcrumbItem must already have an object initialized
72          if (viewBreadcrumbItem == null) {
73              return resolvedBreadcrumbItems;
74          }
75  
76          //evaluate expressions on relevant content before comparisons
77          this.handleExpressions(view, currentModel, currentContext,
78                  view.getViewHelperService().getExpressionEvaluator());
79  
80          //set url values into breadcrumb objects
81          if (StringUtils.isNotBlank(parentViewUrl.getOriginalHref()) || (StringUtils.isNotBlank(
82                  parentViewUrl.getViewId()) && StringUtils.isNotBlank(parentViewUrl.getControllerMapping()))) {
83              viewBreadcrumbItem.setUrl(parentViewUrl);
84              viewBreadcrumbItem.setLabel(parentViewLabel);
85          }
86  
87          if (StringUtils.isNotBlank(parentPageUrl.getOriginalHref()) || (StringUtils.isNotBlank(
88                  parentPageUrl.getViewId()) && StringUtils.isNotBlank(parentPageUrl.getControllerMapping()))) {
89              pageBreadcrumbItem.setUrl(parentPageUrl);
90              pageBreadcrumbItem.setLabel(parentPageLabel);
91          }
92  
93          //only continue if either href or viewId are explicitly set (check for validity of parent url)
94          if (viewBreadcrumbItem.getUrl() == null || StringUtils.isBlank(viewBreadcrumbItem.getUrl().getOriginalHref())
95                  && StringUtils.isBlank(viewBreadcrumbItem.getUrl().getViewId())) {
96              return resolvedBreadcrumbItems;
97          }
98  
99          String parentViewId = viewBreadcrumbItem.getUrl().getViewId();
100         String controllerMapping = viewBreadcrumbItem.getUrl().getControllerMapping();
101 
102         View parentView = null;
103         //chaining is only allowed when the controllerMapping and viewId are explicitly set
104         if (viewBreadcrumbItem.getUrl() != null && StringUtils.isNotBlank(controllerMapping) && StringUtils.isNotBlank(
105                 parentViewId) && StringUtils.isBlank(viewBreadcrumbItem.getUrl().getOriginalHref())) {
106             parentView = KRADServiceLocatorWeb.getDataDictionaryService().getViewById(parentViewId);
107         }
108 
109         //only do this processing if the parentView is not null (viewId was set on viewBreadcrumbItem to a valid View)
110         if (parentView != null) {
111             processParentViewDerivedContent(parentView, parentViewId, view, currentModel, currentContext);
112         }
113 
114         //add parent view breadcrumb
115         if (StringUtils.isNotEmpty(viewBreadcrumbItem.getLabel())) {
116             resolvedBreadcrumbItems.add(viewBreadcrumbItem);
117         }
118 
119         //add parent page breadcrumb
120         if (pageBreadcrumbItem != null && StringUtils.isNotEmpty(pageBreadcrumbItem.getLabel())) {
121             resolvedBreadcrumbItems.add(pageBreadcrumbItem);
122         }
123 
124         return resolvedBreadcrumbItems;
125     }
126 
127     /**
128      * Processes content that can only be derived by looking at the parentView for a parentLocation, such as
129      * expressions
130      * and sibling breadcrumb content; evaluates and adds them to the ParentLocation BreadcrumbItem(s).
131      *
132      * @param parentView the parentView to derive breadcrumb content from
133      * @param parentViewId the parentView's id
134      * @param currentView the currentView (the view this parentLocation is on)
135      * @param currentModel the current model data
136      * @param currentContext the current context to evaluate expressions against
137      */
138     private void processParentViewDerivedContent(View parentView, String parentViewId, View currentView,
139             Object currentModel, Map<String, Object> currentContext) {
140         //populate expression graph
141         ExpressionUtils.populatePropertyExpressionsFromGraph(parentView, false);
142 
143         //chain parent locations if not null on parent
144         if (((View) parentView).getParentLocation() != null) {
145             resolvedBreadcrumbItems.addAll(
146                     ((View) parentView).getParentLocation().constructParentLocationBreadcrumbItems(parentView,
147                             currentModel, currentContext));
148         }
149 
150         handleLabelExpressions(parentView, currentModel, currentContext,
151                 currentView.getViewHelperService().getExpressionEvaluator());
152 
153         //label automation, if parent has a label for its breadcrumb and one is not set here use that value
154         //it is assumed that if the label contains a SpringEL expression, those properties are available on the
155         //current form by the same name
156         if (StringUtils.isBlank(viewBreadcrumbItem.getLabel()) && parentView.getBreadcrumbItem() != null &&
157                 StringUtils.isNotBlank(parentView.getBreadcrumbItem().getLabel())) {
158             viewBreadcrumbItem.setLabel(parentView.getBreadcrumbItem().getLabel());
159         } else if (StringUtils.isBlank(viewBreadcrumbItem.getLabel()) && StringUtils.isNotBlank(
160                 parentView.getHeaderText())) {
161             viewBreadcrumbItem.setLabel(parentView.getHeaderText());
162         }
163 
164         //siblingBreadcrumb inheritance automation
165         if (parentView.getBreadcrumbItem() != null
166                 && parentView.getBreadcrumbItem().getSiblingBreadcrumbComponent() != null
167                 && viewBreadcrumbItem.getSiblingBreadcrumbComponent() == null) {
168             currentView.assignComponentIds(parentView.getBreadcrumbItem().getSiblingBreadcrumbComponent());
169             viewBreadcrumbItem.setSiblingBreadcrumbComponent(
170                     parentView.getBreadcrumbItem().getSiblingBreadcrumbComponent());
171         }
172 
173         //page breadcrumb label automation, page must be a page of the view breadcrumb
174         if (pageBreadcrumbItem != null && StringUtils.isNotBlank(pageBreadcrumbItem.getUrl().getPageId()) && StringUtils
175                 .isNotBlank(pageBreadcrumbItem.getUrl().getViewId()) && pageBreadcrumbItem.getUrl().getViewId().equals(
176                 parentViewId)) {
177             handlePageBreadcrumb(parentView, currentModel);
178         }
179     }
180 
181     /**
182      * Evaluates the expressions on properties that may be determine the value of the label used on generated view and
183      * page breadcrumbItems (if a label was not explicitly set)
184      *
185      * @param parentView the parentView
186      * @param currentModel the currentModel
187      * @param currentContext the currentContext
188      * @param expressionEvaluator instance of expression evaluator for the current view
189      */
190     private void handleLabelExpressions(View parentView, Object currentModel, Map<String, Object> currentContext,
191             ExpressionEvaluator expressionEvaluator) {
192         try {
193             Header header = parentView.getHeader();
194 
195             if (header != null) {
196                 if (StringUtils.isNotBlank(parentView.getPropertyExpressions().get(
197                         UifConstants.ComponentProperties.HEADER_TEXT))) {
198                     header.getPropertyExpressions().put(UifConstants.ComponentProperties.HEADER_TEXT,
199                             parentView.getPropertyExpressions().get(UifConstants.ComponentProperties.HEADER_TEXT));
200                 }
201 
202                 expressionEvaluator.evaluateExpressionsOnConfigurable(parentView, header, currentContext);
203             }
204 
205             BreadcrumbItem breadcrumbItem = parentView.getBreadcrumbItem();
206 
207             if (breadcrumbItem != null) {
208                 expressionEvaluator.evaluateExpressionsOnConfigurable(parentView, breadcrumbItem,
209                         currentContext);
210             }
211 
212             if (pageBreadcrumbItem != null && pageBreadcrumbItem.getUrl() != null && StringUtils.isNotBlank(
213                     pageBreadcrumbItem.getUrl().getPageId())) {
214                 PageGroup thePage = null;
215                 if (parentView.isSinglePageView() && parentView.getPage() != null) {
216                     thePage = parentView.getPage();
217                 } else {
218                     for (Component item : parentView.getItems()) {
219                         if (item.getId().equals(pageBreadcrumbItem.getUrl().getPageId())) {
220                             thePage = (PageGroup) item;
221                             break;
222                         }
223                     }
224                 }
225 
226                 if (thePage == null) {
227                     //TODO throw error
228                     return;
229                 }
230 
231                 //populate from expression graph
232                 ExpressionUtils.populatePropertyExpressionsFromGraph(thePage, false);
233 
234                 Header pageHeader = thePage.getHeader();
235 
236                 if (pageHeader != null) {
237                     if (StringUtils.isNotBlank(thePage.getPropertyExpressions().get(
238                             UifConstants.ComponentProperties.HEADER_TEXT))) {
239                         pageHeader.getPropertyExpressions().put(UifConstants.ComponentProperties.HEADER_TEXT,
240                                 thePage.getPropertyExpressions().get(UifConstants.ComponentProperties.HEADER_TEXT));
241                     }
242 
243                     expressionEvaluator.evaluateExpressionsOnConfigurable(parentView, pageHeader,
244                             currentContext);
245                 }
246 
247                 BreadcrumbItem pageBreadcrumb = thePage.getBreadcrumbItem();
248 
249                 if (pageBreadcrumb != null) {
250                     expressionEvaluator.evaluateExpressionsOnConfigurable(parentView, pageBreadcrumb,
251                             currentContext);
252                 }
253             }
254         } catch (RuntimeException e) {
255             throw new RuntimeException("There was likely a problem evaluating an expression in a parent view or page"
256                     + " because a property may not exist in the current context - explicitly set the label for this"
257                     + " parentLocation: "
258                     + parentView.getId(), e);
259         }
260     }
261 
262     /**
263      * Evaluate any expressions that may have not been evaluated for the urls and breadcrumbItems of this
264      * parentLocation
265      * class using the currentModel and currentContext
266      *
267      * @param view the view
268      * @param currentModel the current model
269      * @param currentContext the current context
270      * @param expressionEvaluator instance of expression evaluator for the current view
271      */
272     private void handleExpressions(View view, Object currentModel, Map<String, Object> currentContext,
273             ExpressionEvaluator expressionEvaluator) {
274         try {
275             // KULRICE-10053 initialize the expression evaluator
276             expressionEvaluator.initializeEvaluationContext(currentModel);
277 
278             //Evaluate view url/breadcrumb expressions
279             expressionEvaluator.evaluateExpressionsOnConfigurable(view, viewBreadcrumbItem, currentContext);
280 
281             if (viewBreadcrumbItem.getUrl() != null) {
282                 expressionEvaluator.evaluateExpressionsOnConfigurable(view, viewBreadcrumbItem.getUrl(),
283                         currentContext);
284             }
285 
286             if (parentViewUrl != null) {
287                 expressionEvaluator.evaluateExpressionsOnConfigurable(view, parentViewUrl, currentContext);
288             }
289 
290             //evaluate same for potential page properties
291             if (pageBreadcrumbItem != null) {
292                 expressionEvaluator.evaluateExpressionsOnConfigurable(view, pageBreadcrumbItem, currentContext);
293 
294                 if (pageBreadcrumbItem.getUrl() != null) {
295                     expressionEvaluator.evaluateExpressionsOnConfigurable(view, pageBreadcrumbItem.getUrl(),
296                             currentContext);
297                 }
298             }
299 
300             if (parentPageUrl != null) {
301                 expressionEvaluator.evaluateExpressionsOnConfigurable(view, parentPageUrl, currentContext);
302             }
303         } catch (RuntimeException e) {
304             throw new RuntimeException("There was likely a problem evaluating an expression in a parent view or page"
305                     + " because a property may not exist in the current context - problem in Url or BreadcrumbItem"
306                     + " - set these to something that can be evaluated - of the parentLocation: "
307                     + view.getId(), e);
308         }
309     }
310 
311     /**
312      * Handle setting a page breadcrumbItem label when parentPageUrl is being used based on the PageGroup's
313      * breadcrumbItem and header properties
314      *
315      * @param view the current view
316      */
317     private void handlePageBreadcrumb(View view, Object currentModel) {
318         PageGroup thePage = null;
319         if (view.isSinglePageView() && view.getPage() != null) {
320             thePage = view.getPage();
321         } else {
322             for (Component item : view.getItems()) {
323                 if (item.getId().equals(pageBreadcrumbItem.getUrl().getPageId())) {
324                     thePage = (PageGroup) item;
325                     break;
326                 }
327             }
328         }
329 
330         if (thePage == null) {
331             return;
332         }
333 
334         //set label
335         if (StringUtils.isBlank(pageBreadcrumbItem.getLabel()) && thePage.getBreadcrumbItem() != null &&
336                 StringUtils.isNotBlank(thePage.getBreadcrumbItem().getLabel())) {
337             pageBreadcrumbItem.setLabel(thePage.getBreadcrumbItem().getLabel());
338         } else if (StringUtils.isBlank(pageBreadcrumbItem.getLabel()) && StringUtils.isNotBlank(
339                 thePage.getHeaderText())) {
340             pageBreadcrumbItem.setLabel(thePage.getHeaderText());
341         }
342 
343         //page siblingBreadcrumb inheritance automation
344         if (thePage.getBreadcrumbItem() != null
345                 && thePage.getBreadcrumbItem().getSiblingBreadcrumbComponent() != null
346                 && pageBreadcrumbItem.getSiblingBreadcrumbComponent() == null) {
347             view.assignComponentIds(thePage.getBreadcrumbItem().getSiblingBreadcrumbComponent());
348 
349             pageBreadcrumbItem.setSiblingBreadcrumbComponent(
350                     thePage.getBreadcrumbItem().getSiblingBreadcrumbComponent());
351         }
352     }
353 
354     /**
355      * The parentViewUrl representing the url that is the parent of this View.
356      *
357      * <p>
358      * This url can explicitly set an href
359      * or can set a controller and viewId.  Parent view traversal is only performed if the controller and viewId
360      * properties are set and NOT the explicit href (this affects if breadcrumbs are generated in a recursive chain).
361      * </p>
362      *
363      * @return the parent view url
364      */
365     @BeanTagAttribute(name = "parentViewUrl", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
366     public UrlInfo getParentViewUrl() {
367         return parentViewUrl;
368     }
369 
370     /**
371      * Set the parentViewUrl
372      *
373      * @param parentViewUrl
374      */
375     public void setParentViewUrl(UrlInfo parentViewUrl) {
376         this.parentViewUrl = parentViewUrl;
377     }
378 
379     /**
380      * The parentPageUrl representing a page url that is the parent of this View.  In order for automated label
381      * determination to work for the page breadcrumbItem, the viewId and controllerMapping must match with the
382      * parentViewUrl.
383      *
384      * <p>
385      * This url can explicitly set an href or can set a pageId.  The parentViewUrl MUST be set before this option can
386      * be set.  If the needed behavior is such that the parent view breadcrumbItem should not be shown and only this
387      * item should be shown, set 'parentLocation.viewBreadcrumbItem.render' to false.
388      * </p>
389      *
390      * @return the parent page url
391      */
392     @BeanTagAttribute(name = "parentPageUrl", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
393     public UrlInfo getParentPageUrl() {
394         return parentPageUrl;
395     }
396 
397     /**
398      * Set the parentPageUrl
399      *
400      * @param parentPageUrl
401      */
402     public void setParentPageUrl(UrlInfo parentPageUrl) {
403         this.parentPageUrl = parentPageUrl;
404     }
405 
406     /**
407      * The parentViewLabel is the text used for breadcrumbItem label of the parent view.
408      *
409      * <p>
410      * If not set, the the label is determined
411      * by looking at the parent View's breadcrumbItem and then its headerText.  If the parent view's retrieved value
412      * contain expressions, those expressions must be able to be evaluated in the current context (ie, the properties
413      * they reference must also exist on the current form at the same location) or an exception will be thrown.
414      * </p>
415      *
416      * @return the parentViewLabel set
417      */
418     @BeanTagAttribute(name = "parentViewLabel")
419     public String getParentViewLabel() {
420         return parentViewLabel;
421     }
422 
423     /**
424      * Set the parentViewLabel
425      *
426      * @param parentViewLabel
427      */
428     public void setParentViewLabel(String parentViewLabel) {
429         this.parentViewLabel = parentViewLabel;
430     }
431 
432     /**
433      * The parentPageLabel is the text used for breadcrumbItem label of the parent page.
434      *
435      * <p>
436      * If not set, the the label is determined
437      * by looking at the parent PageGroup's breadcrumbItem and then its headerText.  This retrieval can only happen
438      * if the parentViewUrl is set.
439      * If the parent PageGroup's retrieved value
440      * contain expressions, those expressions must be able to be evaluated in the current context (ie, the properties
441      * they reference must also exist on the current form at the same location) or an exception will be thrown.
442      * </p>
443      *
444      * @return the parentPageLabel set
445      */
446     @BeanTagAttribute(name = "parentPageLabel")
447     public String getParentPageLabel() {
448         return parentPageLabel;
449     }
450 
451     /**
452      * Set the parentPageLabel
453      *
454      * @param parentPageLabel
455      */
456     public void setParentPageLabel(String parentPageLabel) {
457         this.parentPageLabel = parentPageLabel;
458     }
459 
460     /**
461      * The viewBreadcrumbItem to use for the parent location view breadcrumb.  Url should NOT be set here because
462      * parentViewUrl is ALWAYS set into this breadcrumbItem, regardless of value.
463      *
464      * @return the viewBreadcrumbItem
465      */
466     @BeanTagAttribute(name = "viewBreadcrumbItem", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
467     public BreadcrumbItem getViewBreadcrumbItem() {
468         return viewBreadcrumbItem;
469     }
470 
471     /**
472      * Set the viewBreadcrumbItem
473      *
474      * @param breadcrumbItem
475      */
476     public void setViewBreadcrumbItem(BreadcrumbItem breadcrumbItem) {
477         this.viewBreadcrumbItem = breadcrumbItem;
478     }
479 
480     /**
481      * The pageBreadcrumbItem to use for the parent location view breadcrumb.  Url should NOT be set here because
482      * parentPageUrl is ALWAYS set into this breadcrumbItem, regardless of value.
483      *
484      * @return the pageBreadcrumbItem
485      */
486     @BeanTagAttribute(name = "pageBreadcrumbItem", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
487     public BreadcrumbItem getPageBreadcrumbItem() {
488         return pageBreadcrumbItem;
489     }
490 
491     /**
492      * Set the pageBreadcrumbItem
493      *
494      * @param pageBreadcrumbItem
495      */
496     public void setPageBreadcrumbItem(BreadcrumbItem pageBreadcrumbItem) {
497         this.pageBreadcrumbItem = pageBreadcrumbItem;
498     }
499 
500     /**
501      * The resolved/generated breadcrumbItems determined by traversing the parentLocation chain.  These cannot be set
502      * and must be generated by calling constructParentLocationBreadcrumbItems.
503      *
504      * @return the resolved breadcrumbItem list
505      */
506     public List<BreadcrumbItem> getResolvedBreadcrumbItems() {
507         return resolvedBreadcrumbItems;
508     }
509 
510     /**
511      * Copies properties for parent location
512      *
513      * @param parentLocation to copy properties to
514      */
515     protected <T> void copyProperties(T parentLocation) {
516         ParentLocation parentLocationCopy = (ParentLocation) parentLocation;
517 
518         if (this.parentViewUrl != null) {
519             parentLocationCopy.setParentViewUrl((UrlInfo) this.parentViewUrl.copy());
520         }
521 
522         if (this.parentPageUrl != null) {
523             parentLocationCopy.setParentPageUrl((UrlInfo) this.parentPageUrl.copy());
524         }
525 
526         parentLocationCopy.setParentViewLabel(this.parentViewLabel);
527         parentLocationCopy.setParentPageLabel(this.parentPageLabel);
528 
529         if (this.viewBreadcrumbItem != null) {
530             parentLocationCopy.setViewBreadcrumbItem((BreadcrumbItem) this.viewBreadcrumbItem.copy());
531         }
532 
533         if (this.pageBreadcrumbItem != null) {
534             parentLocationCopy.setPageBreadcrumbItem((BreadcrumbItem) this.pageBreadcrumbItem.copy());
535         }
536 
537         if (this.resolvedBreadcrumbItems != null) {
538             List<BreadcrumbItem> resolvedBreadcrumbItemsCopy = new ArrayList<BreadcrumbItem>();
539             for (BreadcrumbItem resolvedBreadcrumb : this.resolvedBreadcrumbItems) {
540                 resolvedBreadcrumbItemsCopy.add((BreadcrumbItem) resolvedBreadcrumb.copy());
541             }
542             parentLocationCopy.resolvedBreadcrumbItems = resolvedBreadcrumbItemsCopy;
543         }
544     }
545 }