View Javadoc
1   /**
2    * Copyright 2005-2016 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 org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
20  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
21  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
22  import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase;
23  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
24  import org.kuali.rice.krad.uif.UifConstants;
25  import org.kuali.rice.krad.uif.component.Component;
26  import org.kuali.rice.krad.uif.container.PageGroup;
27  import org.kuali.rice.krad.uif.element.BreadcrumbItem;
28  import org.kuali.rice.krad.uif.element.Header;
29  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
30  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
31  import org.kuali.rice.krad.uif.view.View;
32  
33  import java.io.Serializable;
34  import java.util.ArrayList;
35  import java.util.List;
36  import java.util.Map;
37  
38  /**
39   * ParentLocation is used to provide automatic generation/determination of Views/Pages that occur before the current
40   * View.  Essentially, this class provides a way to determine a conceptual hierarchy of view/page locations.
41   * This information is used internally to generate BreadcrumbItems that can appear before the View's breadcrumbs.
42   */
43  @BeanTag(name = "parentLocation", parent = "Uif-ParentLocation")
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, ViewLifecycle.getExpressionEvaluator());
78  
79          //set url values into breadcrumb objects
80          if (StringUtils.isNotBlank(parentViewUrl.getOriginalHref()) || (StringUtils.isNotBlank(
81                  parentViewUrl.getViewId()) && StringUtils.isNotBlank(parentViewUrl.getControllerMapping()))) {
82              viewBreadcrumbItem.setUrl(parentViewUrl);
83              viewBreadcrumbItem.setLabel(parentViewLabel);
84          }
85  
86          if (StringUtils.isNotBlank(parentPageUrl.getOriginalHref()) || (StringUtils.isNotBlank(
87                  parentPageUrl.getViewId()) && StringUtils.isNotBlank(parentPageUrl.getControllerMapping()))) {
88              pageBreadcrumbItem.setUrl(parentPageUrl);
89              pageBreadcrumbItem.setLabel(parentPageLabel);
90          }
91  
92          //only continue if either href or viewId are explicitly set (check for validity of parent url)
93          if (viewBreadcrumbItem.getUrl() == null || StringUtils.isBlank(viewBreadcrumbItem.getUrl().getOriginalHref())
94                  && StringUtils.isBlank(viewBreadcrumbItem.getUrl().getViewId())) {
95              return resolvedBreadcrumbItems;
96          }
97  
98          String parentViewId = viewBreadcrumbItem.getUrl().getViewId();
99          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 }