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