View Javadoc

1   /**
2    * Copyright 2005-2012 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.view;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.krad.service.DataObjectMetaDataService;
21  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
22  import org.kuali.rice.krad.uif.UifConstants;
23  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
24  import org.kuali.rice.krad.uif.util.ViewModelUtils;
25  import org.kuali.rice.krad.web.form.UifFormBase;
26  
27  import javax.servlet.http.HttpServletRequest;
28  import java.io.Serializable;
29  import java.io.UnsupportedEncodingException;
30  import java.net.URLDecoder;
31  import java.net.URLEncoder;
32  import java.util.ArrayList;
33  import java.util.Enumeration;
34  import java.util.List;
35  
36  /**
37   * History class used to keep track of views visited so they can be displayed in the ui
38   * as breadcrumbs - both as homeward path and history path interpretations
39   *
40   * @author Kuali Rice Team (rice.collab@kuali.org)
41   */
42  public class History implements Serializable {
43      private static final long serialVersionUID = -8279297694371557335L;
44      private static final Logger LOG = Logger.getLogger(History.class);
45  
46      public static final String ENTRY_TOKEN = "$";
47      public static final String VAR_TOKEN = ",";
48  
49      private boolean appendHomewardPath;
50      private boolean appendPassedHistory;
51  
52      private HistoryEntry current;
53  
54      private List<HistoryEntry> homewardPath;
55      private List<HistoryEntry> historyEntries;
56  
57      public History() {
58          historyEntries = new ArrayList<HistoryEntry>();
59      }
60  
61      /**
62       * Gets the predetermined homeward path for this view's history.
63       * This is set by the same property in the view's Breadcrumbs configuration.
64       *
65       * @return the homewardPath
66       */
67      public List<HistoryEntry> getHomewardPath() {
68          return this.homewardPath;
69      }
70  
71      /**
72       * @param homewardPath the homewardPath to set
73       */
74      public void setHomewardPath(List<HistoryEntry> homewardPath) {
75          this.homewardPath = homewardPath;
76      }
77  
78      /**
79       * Gets a list of the current HistoryEntries not including the current entry.
80       * This list does not include the "&history=" query parameter on each HistoryEntry's
81       * url variable.  For HistoryEntries that include history information to be passed to the
82       * view they are retrieving, getGeneratedBreadcrumbs is used.
83       *
84       * @return the history
85       */
86      public List<HistoryEntry> getHistoryEntries() {
87          return this.historyEntries;
88      }
89  
90      /**
91       * @param history the history to set
92       */
93      public void setHistoryEntries(List<HistoryEntry> history) {
94          this.historyEntries = history;
95      }
96  
97      /**
98       * Gets the current view's HistoryEntry.
99       * This does not include the "&history=" query parameter on its
100      * url variable.  For the HistoryEntry that includes history information to be passed
101      * on the url it is retrieving, getGeneratedCurrentBreadcrumb is used.
102      *
103      * @return the current
104      */
105     public HistoryEntry getCurrent() {
106         return this.current;
107     }
108 
109     /**
110      * Sets the current HistoryEntry to the current view
111      * @param viewId
112      * @param pageId
113      * @param title
114      * @param url
115      * @param formKey
116      */
117     private void setCurrent(String viewId, String pageId, String title, String url, String formKey) {
118         HistoryEntry entry = new HistoryEntry(viewId, pageId, title, url, formKey);
119         current = entry;
120     }
121 
122     /**
123      * @param current the current to set
124      */
125     public void setCurrent(HistoryEntry current) {
126         this.current = current;
127     }
128 
129     /**
130      * Takes in the encoded history query parameter string passed on the url and parses it to create
131      * the list of historyEntries.  It will also append any homeward path if appendHomewardPath is true.  This
132      * append will happen after the passedHistory entries are appended so it will not make sense to use both settings
133      * in most cases.
134      *
135      * @param parameterString
136      */
137     public void buildHistoryFromParameterString(String parameterString) {
138         if (StringUtils.isNotEmpty(parameterString)) {
139             try {
140                 parameterString = URLDecoder.decode(parameterString, "UTF-8");
141             } catch (UnsupportedEncodingException e) {
142                 LOG.error("Error decoding history param", e);
143             }
144 
145             historyEntries = new ArrayList<HistoryEntry>();
146             if (appendPassedHistory) {
147                 String[] historyTokens = parameterString.split("\\" + ENTRY_TOKEN);
148                 for (String token : historyTokens) {
149                     String[] params = token.split(VAR_TOKEN);
150                     pushToHistory(params[0], params[1], params[2], params[3], params[4]);
151                 }
152             }
153         }
154 
155         if (appendHomewardPath) {
156             historyEntries.addAll(homewardPath);
157         }
158     }
159 
160     /**
161      * Gets the encoded and tokenized history parameter string that is representative of the HistoryEntries
162      * currently in History and includes the current view's HistoryEntry.  This parameter should be appended on any
163      * appropriate links which perform view swapping.
164      *
165      * @return
166      */
167     public String getHistoryParameterString() {
168         String historyString = "";
169         for (HistoryEntry e : historyEntries) {
170             if (historyEntries.indexOf(e) == 0) {
171                 historyString = historyString + e.toParam();
172             } else {
173                 historyString = historyString + ENTRY_TOKEN + e.toParam();
174             }
175         }
176 
177         // add current
178         if (current != null) {
179             if (historyString.equals("")) {
180                 historyString = historyString + current.toParam();
181             } else {
182                 historyString = historyString + ENTRY_TOKEN + current.toParam();
183             }
184         }
185 
186         try {
187             historyString = URLEncoder.encode(historyString, "UTF-8");
188         } catch (Exception e) {
189             LOG.error("Error encoding history param", e);
190         }
191 
192         return historyString;
193     }
194 
195     /**
196      * Generates a list of HistoryEntries that can be used as breadcrumbs by the breadcrumb widget.  This
197      * method appends the appropriate history information on the HistoryEntry url variables so when a view is requested
198      * its history can be regenerated for use in its breadcrumbs.  It also sets the the passed showHome variable to
199      * false to prevent showing the homeward path more than once (as it is passed through the history
200      * variable backwards). This does not include the current HistoryEntry as a breadcrumb but adds the formKey as the
201      * LAST_FORM_KEY to assist with server side form cleanup.
202      *
203      * @return
204      */
205     public List<HistoryEntry> getGeneratedBreadcrumbs() {
206         List<HistoryEntry> breadcrumbs = new ArrayList<HistoryEntry>();
207         for (int i = 0; i < historyEntries.size(); i++) {
208             if (i == 0) {
209                 breadcrumbs.add(copyEntry(historyEntries.get(i)));
210             } else {
211                 HistoryEntry breadcrumb = copyEntry(historyEntries.get(i));
212                 String historyParam = "";
213                 for (int j = 0; j < i; j++) {
214                     historyParam = historyParam + ENTRY_TOKEN + historyEntries.get(j).toParam();
215                 }
216                 historyParam = historyParam.replaceFirst("\\" + ENTRY_TOKEN, "");
217                 try {
218                     historyParam = URLEncoder.encode(historyParam, "UTF-8");
219                 } catch (Exception e) {
220                     LOG.error("Error encoding history param", e);
221                 }
222 
223                 String url = "";
224                 if (breadcrumb.getUrl().contains("?")) {
225                     url = breadcrumb.getUrl() + "&" + UifConstants.UrlParams.HISTORY + "=" + historyParam
226                             + "&" + UifConstants.UrlParams.LAST_FORM_KEY + "=" + current.getFormKey();
227                 } else {
228                     url = breadcrumb.getUrl() + "?" + UifConstants.UrlParams.HISTORY + "=" + historyParam
229                             + "&" + UifConstants.UrlParams.LAST_FORM_KEY + "=" + current.getFormKey();
230                 }
231 
232                 breadcrumb.setUrl(url);
233                 breadcrumbs.add(breadcrumb);
234             }
235         }
236 
237         return breadcrumbs;
238     }
239 
240     /**
241      * Gets the current HistoryEntry in the breadcrumb format described in getGeneratedBreadcrumbs
242      *
243      * @return
244      */
245     public HistoryEntry getGeneratedCurrentBreadcrumb() {
246         if (current == null){
247             return new HistoryEntry();
248         }
249 
250         HistoryEntry breadcrumb = copyEntry(current);
251         String historyParam = "";
252         for (int j = 0; j < historyEntries.size(); j++) {
253             historyParam = historyParam + ENTRY_TOKEN + historyEntries.get(j).toParam();
254         }
255         historyParam = historyParam.replaceFirst("\\" + ENTRY_TOKEN, "");
256 
257         try {
258             historyParam = URLEncoder.encode(historyParam, "UTF-8");
259         } catch (Exception e) {
260             LOG.error("Error encoding history param", e);
261         }
262 
263         String url = "";
264         if (breadcrumb.getUrl().contains("?")) {
265             url = breadcrumb.getUrl() + "&" + UifConstants.UrlParams.HISTORY + "=" + historyParam;
266         } else {
267             url = breadcrumb.getUrl() + "?" + UifConstants.UrlParams.HISTORY + "=" + historyParam;
268         }
269         breadcrumb.setUrl(url);
270 
271         return breadcrumb;
272     }
273 
274     /**
275      * Copies a HistoryEntry, for use during breadcrumb generation.
276      *
277      * @param e
278      * @return
279      */
280     private HistoryEntry copyEntry(HistoryEntry e) {
281         return new HistoryEntry(e.getViewId(), e.getPageId(), e.getTitle(), e.getUrl(), e.getFormKey());
282     }
283 
284     /**
285      * Pushes the information passed in to history.
286      * Note: currently only used internally in the class - be cautious about its external use.
287      *
288      * @param viewId
289      * @param pageId
290      * @param title
291      * @param url
292      * @param formKey
293      */
294     public void pushToHistory(String viewId, String pageId, String title, String url, String formKey) {
295         HistoryEntry entry = new HistoryEntry(viewId, pageId, title, url, formKey);
296         historyEntries.add(entry);
297     }
298 
299     /**
300      * When this is set to true, the homeward path will be appended.
301      * Note:  For most cases this should only be on during the first view load.
302      * This setting is set automatically in most cases.
303      *
304      * @param appendHomewardPath the appendHomewardPath to set
305      */
306     public void setAppendHomewardPath(boolean appendHomewardPath) {
307         this.appendHomewardPath = appendHomewardPath;
308     }
309 
310     /**
311      * @return the appendHomewardPath
312      */
313     public boolean isAppendHomewardPath() {
314         return appendHomewardPath;
315     }
316 
317     /**
318      * Appends the passed history as each different view is shown.  This setting should be used when displaying
319      * passed history is relevant to the user (ie inquiry/lookup chains).  This setting is set automatically in
320      * most cases.
321      *
322      * @param appendPassedHistory the appendPassedHistory to set
323      */
324     public void setAppendPassedHistory(boolean appendPassedHistory) {
325         this.appendPassedHistory = appendPassedHistory;
326     }
327 
328     /**
329      * @return the appendPassedHistory
330      */
331     public boolean isAppendPassedHistory() {
332         return appendPassedHistory;
333     }
334 
335     /**
336      * Sets the current HistoryEntry using information from the form and the request.  This history parameter is
337      * extracted out of the url inorder for a "clean" url to be used in history parameter and
338      * breadcrumb generation, as passing history history through the nested urls is unnecessary.
339      *
340      * @param form
341      * @param request
342      */
343     @SuppressWarnings("unchecked")
344     public void setCurrent(UifFormBase form, HttpServletRequest request) {
345         if (!request.getMethod().equals("POST")) {
346             boolean showHomeValue = false;
347             boolean pageIdValue = false;
348             boolean formKeyValue = false;
349 
350             String queryString = "";
351             String url = request.getRequestURL().toString();
352 
353             //remove history attribute
354             Enumeration<String> params = request.getParameterNames();
355             while (params.hasMoreElements()) {
356                 String key = params.nextElement();
357                 if (!key.equals(UifConstants.UrlParams.HISTORY)) {
358                     for (String value : request.getParameterValues(key)) {
359                         queryString = queryString + "&" + key + "=" + value;
360                     }
361                 } else if (key.equals(UifConstants.UrlParams.PAGE_ID)) {
362                     pageIdValue = true;
363                 } else if (key.equals(UifConstants.UrlParams.SHOW_HOME)) {
364                     showHomeValue = true;
365                 } else if (key.equals(UifConstants.UrlParams.FORM_KEY)) {
366                     formKeyValue = true;
367                 }
368             }
369 
370             //add formKey and pageId to url
371             if (StringUtils.isNotBlank(form.getFormKey()) && !formKeyValue) {
372                 queryString = queryString + "&" + UifConstants.UrlParams.FORM_KEY + "=" + form.getFormKey();
373             }
374             if (StringUtils.isNotBlank(form.getPageId()) && !pageIdValue) {
375                 queryString = queryString + "&" + UifConstants.UrlParams.PAGE_ID + "=" + form.getPageId();
376             }
377             if (!showHomeValue) {
378                 queryString = queryString + "&" + UifConstants.UrlParams.SHOW_HOME + "=false";
379             }
380 
381             queryString = queryString.replaceFirst("&", "");
382 
383             if (StringUtils.isNotEmpty(queryString)) {
384                 url = url + "?" + queryString;
385             }
386 
387             this.setCurrent(form.getViewId(), form.getPageId(), buildViewTitle(form), url, form.getFormKey());
388         }
389     }
390 
391     /**
392      * Builds the title for the view to display in history (for example breadcrumbs)
393      *
394      * <p>
395      * Retrieves the viewLabelFieldPropertyName from the view if configured, otherwise attempts
396      * to find the title attribute for the default data object. If view label property is found the
397      * corresponding property value is retrieved and appended to the title for the view
398      * </p>
399      *
400      * TODO: Possibly move so it can be used for the actual view title, not just history
401      *
402      * @param form - form instance containing the view and view data
403      * @return String title string to use
404      */
405     protected String buildViewTitle(UifFormBase form) {
406         View view = form.getView();
407         String title = view.getHeaderText();
408 
409         // may move this into view logic instead in the future if it is required for the view's title (not just breadcrumb)
410         // if so remove this and just use getTitle - this logic would be in performFinalize instead
411         String viewLabelPropertyName = view.getBreadcrumbTitlePropertyName();
412 
413         // if view label property name given, try to retrieve the title attribute for the main data object
414         if (StringUtils.isBlank(viewLabelPropertyName)) {
415             Class<?> dataObjectClass;
416             if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) {
417                 dataObjectClass = ObjectPropertyUtils.getPropertyType(form, view.getDefaultBindingObjectPath());
418             } else {
419                 dataObjectClass = view.getFormClass();
420             }
421 
422             DataObjectMetaDataService mds = KRADServiceLocatorWeb.getDataObjectMetaDataService();
423             if (dataObjectClass != null) {
424                 viewLabelPropertyName = mds.getTitleAttribute(dataObjectClass);
425             }
426         }
427 
428         String viewLabelPropertyPath = "";
429         if (StringUtils.isNotBlank(viewLabelPropertyName)) {
430             // adjust binding prefix
431             if (!viewLabelPropertyName.startsWith(UifConstants.NO_BIND_ADJUST_PREFIX)) {
432                 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) {
433                     viewLabelPropertyPath = view.getDefaultBindingObjectPath() + "." + viewLabelPropertyName;
434                 }
435             } else {
436                 viewLabelPropertyPath = StringUtils.removeStart(viewLabelPropertyName,
437                         UifConstants.NO_BIND_ADJUST_PREFIX);
438             }
439         }
440         else {
441             // attempt to get title attribute
442             Class<?> dataObjectClass;
443             if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) {
444                 dataObjectClass = ViewModelUtils.getObjectClassForMetadata(view, form,
445                         view.getDefaultBindingObjectPath());
446             } else {
447                 dataObjectClass = view.getFormClass();
448             }
449 
450             DataObjectMetaDataService mds = KRADServiceLocatorWeb.getDataObjectMetaDataService();
451             if (dataObjectClass != null) {
452                 String titleAttribute = mds.getTitleAttribute(dataObjectClass);
453                 if (StringUtils.isNotBlank(titleAttribute)) {
454                   viewLabelPropertyPath = view.getDefaultBindingObjectPath() + "." + titleAttribute;
455                 }
456             }
457         }
458 
459         Object viewLabelPropertyValue = null;
460         if (StringUtils.isNotBlank(viewLabelPropertyPath) && ObjectPropertyUtils. isReadableProperty(form, viewLabelPropertyPath)) {
461             viewLabelPropertyValue = ObjectPropertyUtils.getPropertyValue(form, viewLabelPropertyPath);
462         }
463 
464         String titleAppend = "";
465         if (viewLabelPropertyValue != null) {
466             titleAppend = viewLabelPropertyValue.toString();
467         }
468 
469         if (StringUtils.isNotBlank(titleAppend) && view.getBreadcrumbTitleDisplayOption() != null) {
470             if (view.getBreadcrumbTitleDisplayOption().equalsIgnoreCase(UifConstants.TitleAppendTypes.DASH)) {
471                 title = title + " - " + titleAppend;
472             } else if (view.getBreadcrumbTitleDisplayOption().equalsIgnoreCase(UifConstants.TitleAppendTypes.PARENTHESIS)) {
473                 title = title + "(" + titleAppend + ")";
474             } else if (view.getBreadcrumbTitleDisplayOption().equalsIgnoreCase(UifConstants.TitleAppendTypes.REPLACE)) {
475                 title = titleAppend;
476             }
477             //else it is none or blank so no title modification will be used
478         }
479 
480         return title;
481     }
482 }