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.
201      *
202      * @return
203      */
204     public List<HistoryEntry> getGeneratedBreadcrumbs() {
205         List<HistoryEntry> breadcrumbs = new ArrayList<HistoryEntry>();
206         for (int i = 0; i < historyEntries.size(); i++) {
207             if (i == 0) {
208                 breadcrumbs.add(copyEntry(historyEntries.get(i)));
209             } else {
210                 HistoryEntry breadcrumb = copyEntry(historyEntries.get(i));
211                 String historyParam = "";
212                 for (int j = 0; j < i; j++) {
213                     historyParam = historyParam + ENTRY_TOKEN + historyEntries.get(j).toParam();
214                 }
215                 historyParam = historyParam.replaceFirst("\\" + ENTRY_TOKEN, "");
216                 try {
217                     historyParam = URLEncoder.encode(historyParam, "UTF-8");
218                 } catch (Exception e) {
219                     LOG.error("Error encoding history param", e);
220                 }
221 
222                 String url = "";
223                 if (breadcrumb.getUrl().contains("?")) {
224                     url = breadcrumb.getUrl() + "&" + UifConstants.UrlParams.HISTORY + "=" + historyParam;
225                 } else {
226                     url = breadcrumb.getUrl() + "?" + UifConstants.UrlParams.HISTORY + "=" + historyParam;
227                 }
228 
229                 breadcrumb.setUrl(url);
230                 breadcrumbs.add(breadcrumb);
231             }
232         }
233 
234         return breadcrumbs;
235     }
236 
237     /**
238      * Gets the current HistoryEntry in the breadcrumb format described in getGeneratedBreadcrumbs
239      *
240      * @return
241      */
242     public HistoryEntry getGeneratedCurrentBreadcrumb() {
243         if (current == null){
244             return new HistoryEntry();
245         }
246 
247         HistoryEntry breadcrumb = copyEntry(current);
248         String historyParam = "";
249         for (int j = 0; j < historyEntries.size(); j++) {
250             historyParam = historyParam + ENTRY_TOKEN + historyEntries.get(j).toParam();
251         }
252         historyParam = historyParam.replaceFirst("\\" + ENTRY_TOKEN, "");
253 
254         try {
255             historyParam = URLEncoder.encode(historyParam, "UTF-8");
256         } catch (Exception e) {
257             LOG.error("Error encoding history param", e);
258         }
259 
260         String url = "";
261         if (breadcrumb.getUrl().contains("?")) {
262             url = breadcrumb.getUrl() + "&" + UifConstants.UrlParams.HISTORY + "=" + historyParam;
263         } else {
264             url = breadcrumb.getUrl() + "?" + UifConstants.UrlParams.HISTORY + "=" + historyParam;
265         }
266         breadcrumb.setUrl(url);
267 
268         return breadcrumb;
269     }
270 
271     /**
272      * Copies a HistoryEntry, for use during breadcrumb generation.
273      *
274      * @param e
275      * @return
276      */
277     private HistoryEntry copyEntry(HistoryEntry e) {
278         return new HistoryEntry(e.getViewId(), e.getPageId(), e.getTitle(), e.getUrl(), e.getFormKey());
279     }
280 
281     /**
282      * Pushes the information passed in to history.
283      * Note: currently only used internally in the class - be cautious about its external use.
284      *
285      * @param viewId
286      * @param pageId
287      * @param title
288      * @param url
289      * @param formKey
290      */
291     public void pushToHistory(String viewId, String pageId, String title, String url, String formKey) {
292         HistoryEntry entry = new HistoryEntry(viewId, pageId, title, url, formKey);
293         historyEntries.add(entry);
294     }
295 
296     /**
297      * When this is set to true, the homeward path will be appended.
298      * Note:  For most cases this should only be on during the first view load.
299      * This setting is set automatically in most cases.
300      *
301      * @param appendHomewardPath the appendHomewardPath to set
302      */
303     public void setAppendHomewardPath(boolean appendHomewardPath) {
304         this.appendHomewardPath = appendHomewardPath;
305     }
306 
307     /**
308      * @return the appendHomewardPath
309      */
310     public boolean isAppendHomewardPath() {
311         return appendHomewardPath;
312     }
313 
314     /**
315      * Appends the passed history as each different view is shown.  This setting should be used when displaying
316      * passed history is relevant to the user (ie inquiry/lookup chains).  This setting is set automatically in
317      * most cases.
318      *
319      * @param appendPassedHistory the appendPassedHistory to set
320      */
321     public void setAppendPassedHistory(boolean appendPassedHistory) {
322         this.appendPassedHistory = appendPassedHistory;
323     }
324 
325     /**
326      * @return the appendPassedHistory
327      */
328     public boolean isAppendPassedHistory() {
329         return appendPassedHistory;
330     }
331 
332     /**
333      * Sets the current HistoryEntry using information from the form and the request.  This history parameter is
334      * extracted out of the url inorder for a "clean" url to be used in history parameter and
335      * breadcrumb generation, as passing history history through the nested urls is unnecessary.
336      *
337      * @param form
338      * @param request
339      */
340     @SuppressWarnings("unchecked")
341     public void setCurrent(UifFormBase form, HttpServletRequest request) {
342         if (!request.getMethod().equals("POST")) {
343             boolean showHomeValue = false;
344             boolean pageIdValue = false;
345             boolean formKeyValue = false;
346 
347             String queryString = "";
348             String url = request.getRequestURL().toString();
349 
350             //remove history attribute
351             Enumeration<String> params = request.getParameterNames();
352             while (params.hasMoreElements()) {
353                 String key = params.nextElement();
354                 if (!key.equals(UifConstants.UrlParams.HISTORY)) {
355                     for (String value : request.getParameterValues(key)) {
356                         queryString = queryString + "&" + key + "=" + value;
357                     }
358                 } else if (key.equals(UifConstants.UrlParams.PAGE_ID)) {
359                     pageIdValue = true;
360                 } else if (key.equals(UifConstants.UrlParams.SHOW_HOME)) {
361                     showHomeValue = true;
362                 } else if (key.equals(UifConstants.UrlParams.FORM_KEY)) {
363                     formKeyValue = true;
364                 }
365             }
366 
367             //add formKey and pageId to url
368             if (StringUtils.isNotBlank(form.getFormKey()) && !formKeyValue) {
369                 queryString = queryString + "&" + UifConstants.UrlParams.FORM_KEY + "=" + form.getFormKey();
370             }
371             if (StringUtils.isNotBlank(form.getPageId()) && !pageIdValue) {
372                 queryString = queryString + "&" + UifConstants.UrlParams.PAGE_ID + "=" + form.getPageId();
373             }
374             if (!showHomeValue) {
375                 queryString = queryString + "&" + UifConstants.UrlParams.SHOW_HOME + "=false";
376             }
377 
378             queryString = queryString.replaceFirst("&", "");
379 
380             if (StringUtils.isNotEmpty(queryString)) {
381                 url = url + "?" + queryString;
382             }
383 
384             this.setCurrent(form.getViewId(), form.getPageId(), buildViewTitle(form), url, form.getFormKey());
385         }
386     }
387 
388     /**
389      * Builds the title for the view to display in history (for example breadcrumbs)
390      *
391      * <p>
392      * Retrieves the viewLabelFieldPropertyName from the view if configured, otherwise attempts
393      * to find the title attribute for the default data object. If view label property is found the
394      * corresponding property value is retrieved and appended to the title for the view
395      * </p>
396      *
397      * TODO: Possibly move so it can be used for the actual view title, not just history
398      *
399      * @param form - form instance containing the view and view data
400      * @return String title string to use
401      */
402     protected String buildViewTitle(UifFormBase form) {
403         View view = form.getView();
404         String title = view.getTitle();
405 
406         // may move this into view logic instead in the future if it is required for the view's title (not just breadcrumb)
407         // if so remove this and just use getTitle - this logic would be in performFinalize instead
408         String viewLabelPropertyName = view.getViewLabelFieldPropertyName();
409 
410         // if view label property name given, try to retrieve the title attribute for the main data object
411         if (StringUtils.isBlank(viewLabelPropertyName)) {
412             Class<?> dataObjectClass;
413             if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) {
414                 dataObjectClass = ObjectPropertyUtils.getPropertyType(form, view.getDefaultBindingObjectPath());
415             } else {
416                 dataObjectClass = view.getFormClass();
417             }
418 
419             DataObjectMetaDataService mds = KRADServiceLocatorWeb.getDataObjectMetaDataService();
420             if (dataObjectClass != null) {
421                 viewLabelPropertyName = mds.getTitleAttribute(dataObjectClass);
422             }
423         }
424 
425         String viewLabelPropertyPath = "";
426         if (StringUtils.isNotBlank(viewLabelPropertyName)) {
427             // adjust binding prefix
428             if (!viewLabelPropertyName.startsWith(UifConstants.NO_BIND_ADJUST_PREFIX)) {
429                 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) {
430                     viewLabelPropertyPath = view.getDefaultBindingObjectPath() + "." + viewLabelPropertyName;
431                 }
432             } else {
433                 viewLabelPropertyPath = StringUtils.removeStart(viewLabelPropertyName,
434                         UifConstants.NO_BIND_ADJUST_PREFIX);
435             }
436         }
437         else {
438             // attempt to get title attribute
439             Class<?> dataObjectClass;
440             if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) {
441                 dataObjectClass = ViewModelUtils.getObjectClassForMetadata(view, form,
442                         view.getDefaultBindingObjectPath());
443             } else {
444                 dataObjectClass = view.getFormClass();
445             }
446 
447             DataObjectMetaDataService mds = KRADServiceLocatorWeb.getDataObjectMetaDataService();
448             if (dataObjectClass != null) {
449                 String titleAttribute = mds.getTitleAttribute(dataObjectClass);
450                 if (StringUtils.isNotBlank(titleAttribute)) {
451                   viewLabelPropertyPath = view.getDefaultBindingObjectPath() + "." + titleAttribute;
452                 }
453             }
454         }
455 
456         Object viewLabelPropertyValue = null;
457         if (StringUtils.isNotBlank(viewLabelPropertyPath) && ObjectPropertyUtils. isReadableProperty(form, viewLabelPropertyPath)) {
458             viewLabelPropertyValue = ObjectPropertyUtils.getPropertyValue(form, viewLabelPropertyPath);
459         }
460 
461         String titleAppend = "";
462         if (viewLabelPropertyValue != null) {
463             titleAppend = viewLabelPropertyValue.toString();
464         }
465 
466         if (StringUtils.isNotBlank(titleAppend) && view.getAppendOption() != null) {
467             if (view.getAppendOption().equalsIgnoreCase(UifConstants.TitleAppendTypes.DASH)) {
468                 title = title + " - " + titleAppend;
469             } else if (view.getAppendOption().equalsIgnoreCase(UifConstants.TitleAppendTypes.PARENTHESIS)) {
470                 title = title + "(" + titleAppend + ")";
471             } else if (view.getAppendOption().equalsIgnoreCase(UifConstants.TitleAppendTypes.REPLACE)) {
472                 title = titleAppend;
473             }
474             //else it is none or blank so no title modification will be used
475         }
476 
477         return title;
478     }
479 }